SAC Admin Wrapper
Overview
The Stellar Asset Contract (SAC) Admin Wrapper module provides a way to implement custom administrative functionality for Stellar Asset Contracts (SACs) using the wrapper approach. This approach defines specific entry points for each admin function and forwards calls to the corresponding SAC functions, providing a straightforward and modular design.
Key Concepts
When a classic Stellar asset is ported to Soroban, it is represented by a SAC - a smart contract that provides both user-facing and administrative functions for asset management. SACs expose standard functions for handling fungible tokens, such as transfer, approve, burn, etc. Additionally, they include administrative functions (mint, clawback, set_admin, set_authorized) that are initially restricted to the issuer (a G-account).
The set_admin function enables transferring administrative control to a custom contract, allowing for more complex authorization logic. This flexibility opens up possibilities for implementing custom rules, such as role-based access control, two-step admin transfers, mint rate limits, and upgradeability.
Wrapper Approach
The Wrapper approach to SAC Admin implementation:
- Acts as a middleware, defining specific entry points for each admin function
- Forwards calls to the corresponding SAC functions
- Applies custom logic before forwarding the call
- Provides a straightforward and modular design
- Separates user-facing and admin interfaces
SACAdminWrapper Trait
The SACAdminWrapper trait defines the interface for the wrapper approach:
pub trait SACAdminWrapper {
fn set_admin(e: Env, new_admin: Address, operator: Address);
fn set_authorized(e: Env, id: Address, authorize: bool, operator: Address);
fn mint(e: Env, to: Address, amount: i128, operator: Address);
fn clawback(e: Env, from: Address, amount: i128, operator: Address);
}Example Implementation
Here’s a simplified example of a SAC Admin Wrapper contract using the OpenZeppelin access control library:
#[contract]
pub struct ExampleContract;
#[contractimpl]
impl ExampleContract {
pub fn __constructor(
e: &Env,
default_admin: Address,
manager1: Address,
manager2: Address,
sac: Address,
) {
access_control::set_admin(e, &default_admin);
// create a role "manager" and grant it to `manager1`
access_control::grant_role_no_auth(e, &default_admin, &manager1, &symbol_short!("manager"));
// grant it to `manager2`
access_control::grant_role_no_auth(e, &default_admin, &manager2, &symbol_short!("manager"));
fungible::sac_admin_wrapper::set_sac_address(e, &sac);
}
}
#[contractimpl]
impl SACAdminWrapper for ExampleContract {
#[only_admin]
fn set_admin(e: Env, new_admin: Address, _operator: Address) {
fungible::sac_admin_wrapper::set_admin(&e, &new_admin);
}
#[only_role(operator, "manager")]
fn set_authorized(e: Env, id: Address, authorize: bool, operator: Address) {
fungible::sac_admin_wrapper::set_authorized(&e, &id, authorize);
}
#[only_role(operator, "manager")]
fn mint(e: Env, to: Address, amount: i128, operator: Address) {
fungible::sac_admin_wrapper::mint(&e, &to, amount);
}
#[only_role(operator, "manager")]
fn clawback(e: Env, from: Address, amount: i128, operator: Address) {
fungible::sac_admin_wrapper::clawback(&e, &from, amount);
}
}Integration with Access Control
The wrapper approach works particularly well with the OpenZeppelin access control library, allowing for role-based access control to be applied to each admin function:
#[only_admin]: Restricts the function to be called only by the admin#[only_role(operator, "manager")]: Restricts the function to be called only by addresses with the "manager" role
Benefits and Trade-offs
Benefits
- Simpler to implement compared to the generic approach
- More flexible in terms of function-specific authorization
- Works well with role-based access control
- Clear separation of concerns
Trade-offs
- Requires additional entry points for each admin function
- Splits user-facing and admin interfaces
- May require more code for complex authorization scenarios
Full Example
A complete example implementation can be found in the sac-admin-wrapper example.