SAC Admin Generic

概述

Stellar Asset Contract (SAC) Admin Generic 模块提供了一种使用通用方法为 Stellar Asset Contracts (SAC) 实现自定义管理功能的方式。这种方法利用 __check_auth 函数来处理身份验证和授权逻辑,同时为面向用户和管理功能保持统一的接口。

主要概念

当经典的 Stellar 资产被移植到 Soroban 时,它由一个 SAC 表示 - 一个智能合约,为资产管理提供面向用户和管理的功能。SAC 公开用于处理同质化代币的标准函数,例如 transferapproveburn 等。此外,它们还包括管理功能(mintclawbackset_adminset_authorized),这些功能最初仅限于发行者(G 账户)。

set_admin 函数能够将管理控制权转移到自定义合约,从而允许更复杂的授权逻辑。这种灵活性为实现自定义规则开辟了可能性,例如基于角色的访问控制、两步管理转移、mint 速率限制和可升级性。

通用方法

SAC Admin 实现的通用方法:

  • 利用 __check_auth 函数来处理身份验证和授权逻辑

  • 为面向用户和管理功能保持统一的接口

  • 允许注入任何自定义授权逻辑

  • 需要更复杂的授权机制

示例实现

这是一个简化的 SAC Admin Generic 合约示例:

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum SACAdminGenericError {
    Unauthorized = 1,
    InvalidContext = 2,
    MintingLimitExceeded = 3,
}

#[contracttype]
#[derive(Clone)]
pub struct Signature {
    pub public_key: BytesN<32>,
    pub signature: BytesN<64>,
}

#[contracttype]
pub enum SacDataKey {
    Chief,
    Operator(BytesN<32>),     // -> true/false
    MintingLimit(BytesN<32>), // -> (max_limit, curr)
}

#[contract]
pub struct SacAdminExampleContract;

#[contractimpl]
impl SacAdminExampleContract {
    pub fn __constructor(e: Env, sac: Address, chief: BytesN<32>, operator: BytesN<32>) {
        set_sac_address(&e, &sac);
        e.storage().instance().set(&SacDataKey::Chief, &chief);
        e.storage().instance().set(&SacDataKey::Operator(operator.clone()), &true);
        e.storage()
            .instance()
            .set(&SacDataKey::MintingLimit(operator), &(1_000_000_000i128, 0i128));
    }

    pub fn get_sac_address(e: &Env) -> Address {
        get_sac_address(e)
    }
}

自定义授权逻辑

Generic 方法的关键特性是在 __check_auth 函数中实现自定义授权逻辑的能力:

use soroban_sdk::{
    auth::{Context, CustomAccountInterface},
    contract, contracterror, contractimpl, contracttype,
    crypto::Hash,
    Address, BytesN, Env, IntoVal, Val, Vec,
};

#[contractimpl]
impl CustomAccountInterface for SacAdminExampleContract {
    type Error = SACAdminGenericError;
    type Signature = Signature;

    fn __check_auth(
        e: Env,
        payload: Hash<32>,
        signature: Self::Signature,
        auth_context: Vec<Context>,
    ) -> Result<(), SACAdminGenericError> {
        // authenticate
        // 验证
        e.crypto().ed25519_verify(
            &signature.public_key,
            &payload.clone().into(),
            &signature.signature,
        );
        let caller = signature.public_key.clone();

        // extract from context and check required permissions for every function
        // 从上下文中提取并检查每个函数所需的权限
        for ctx in auth_context.iter() {
            let context = match ctx {
                Context::Contract(c) => c,
                _ => return Err(SACAdminGenericError::InvalidContext),
            };

            match extract_sac_contract_context(&e, &context) {
                SacFn::Mint(amount) => {
                    // ensure caller has required permissions
                    // 确保调用者具有所需的权限
                    ensure_caller_operator(&e, &SacDataKey::Operator(caller.clone()))?;
                    // ensure operator has minting limit
                    // 确保 operator 具有 minting 限制
                    ensure_minting_limit(&e, &caller, amount)?;
                }
                SacFn::Clawback(_amount) => {
                    // ensure caller has required permissions
                    // 确保调用者具有所需的权限
                    ensure_caller_operator(&e, &SacDataKey::Operator(caller.clone()))?;
                }
                SacFn::SetAuthorized(_) => {
                    // ensure caller has required permissions
                    // 确保调用者具有所需的权限
                    ensure_caller_operator(&e, &SacDataKey::Operator(caller.clone()))?;
                }
                SacFn::SetAdmin => {
                    // ensure caller has required permissions
                    // 确保调用者具有所需的权限
                    ensure_caller_chief(&e, &caller, &SacDataKey::Chief)?;
                }
                SacFn::Unknown => {
                    // ensure only chief can call other functions
                    // 确保只有 chief 才能调用其他函数
                    ensure_caller_chief(&e, &caller, &SacDataKey::Chief)?
                }
            }
        }

        Ok(())
    }
}

// Helper functions
// 辅助函数
fn ensure_caller_chief<K: IntoVal<Env, Val>>(
    e: &Env,
    caller: &BytesN<32>,
    key: &K,
) -> Result<(), SACAdminGenericError> {
    let operator: BytesN<32> = e.storage().instance().get(key).expect("chief or operator not set");
    if *caller != operator {
        return Err(SACAdminGenericError::Unauthorized);
    }
    Ok(())
}

fn ensure_caller_operator<K: IntoVal<Env, Val>>(
    e: &Env,
    key: &K,
) -> Result<(), SACAdminGenericError> {
    match e.storage().instance().get::<_, bool>(key) {
        Some(is_op) if is_op => Ok(()),
        _ => Err(SACAdminGenericError::Unauthorized),
    }
}

优点和缺点

优点

  • 为面向用户和管理功能保持统一的接口

  • 允许复杂的授权逻辑

  • 在实现自定义规则方面提供了灵活性

缺点

  • 需要更复杂的授权机制

  • 与包装器方法相比,实现起来更复杂

  • 需要了解 Soroban 授权系统

完整示例

一个完整的示例实现可以在 sac-admin-generic 示例中找到。