Join our community of builders on

Telegram!Telegram

SAC Admin Wrapper

Source Code

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.

See Also