模式:能力(Capability)

在编程中,**能力(capability)**是指授予所有者执行特定操作权利的令牌。它是一种用于控制对资源和操作的访问的模式。一个简单的能力示例是门的钥匙。如果你有钥匙,你就可以打开门;如果没有钥匙,你就不能打开门。一个更实际的例子是“管理员能力”,它允许所有者执行常规用户无法执行的管理操作。

能力是一个对象

Sui对象模型中,能力被表示为对象。一个对象的所有者可以将该对象传递给函数,以证明他们有执行特定操作的权利。由于严格的类型检查,只能使用正确的能力调用接受能力作为参数的函数。

有一个约定,将能力命名为Cap后缀,例如AdminCapKioskOwnerCap

module book::capability {
    use std::string::String;
    use sui::event;

    /// The capability granting the application admin the right to create new
    /// accounts in the system.
    public struct AdminCap has key, store { id: UID }

    /// The user account in the system.
    public struct Account has key, store {
        id: UID,
        name: String
    }

    /// A simple `Ping` event with no data.
    public struct Ping has copy, drop { by: ID }

    /// Creates a new account in the system. Requires the `AdminCap` capability
    /// to be passed as the first argument.
    public fun new(_: &AdminCap, name: String, ctx: &mut TxContext): Account {
        Account {
            id: object::new(ctx),
            name,
        }
    }

    /// Account, and any other objects, can also be used as a Capability in the
    /// application. For example, to emit an event.
    public fun send_ping(acc: &Account) {
        event::emit(Ping {
            by: acc.id.to_inner()
        })
    }

    /// Updates the account name. Can only be called by the `Account` owner.
    public fun update(account: &mut Account, name: String) {
        account.name = name;
    }
}

使用init创建管理员能力

一种常见的做法是在包发布时创建一个单独的AdminCap对象。这样,应用程序可以有一个设置阶段,管理员账户可以准备应用程序的状态。

module book::admin_cap {
    /// The capability granting the admin privileges in the system.
    /// Created only once in the `init` function.
    public struct AdminCap has key { id: UID }

    /// Create the AdminCap object on package publish and transfer it to the
    /// package owner.
    fun init(ctx: &mut TxContext) {
        transfer::transfer(
            AdminCap { id: object::new(ctx) },
            ctx.sender()
        )
    }
}

地址检查与能力

在区块链编程中,将对象用作能力是一个相对较新的概念。在其他智能合约语言中,授权通常是通过检查发送方地址来执行的。这种模式在Sui上仍然可行,但总体建议是使用能力以获得更好的安全性、可发现性和代码组织性。

让我们看一下如果使用地址检查的方式来实现创建用户的new函数会是什么样子:

/// Error code for unauthorized access.
const ENotAuthorized: u64 = 0;

/// The application admin address.
const APPLICATION_ADMIN: address = @0xa11ce;

/// Creates a new user in the system. Requires the sender to be the application
/// admin.
public fun new(ctx: &mut TxContext): User {
    assert!(ctx.sender() == APPLICATION_ADMIN, ENotAuthorized);
    User { id: object::new(ctx) }
}

现在,让我们看看如果使用能力的方式来实现相同的函数会是什么样子:

/// Grants the owner the right to create new users in the system.
public struct AdminCap {}

/// Creates a new user in the system. Requires the `AdminCap` capability to be
/// passed as the first argument.
public fun new(_: &AdminCap, ctx: &mut TxContext): User {
    User { id: object::new(ctx) }
}

与地址检查相比,使用能力具有以下几个优势:

  • 对于能力来说,迁移管理员权限更加容易,因为它们是对象。如果使用地址,则如果管理员地址发生更改,所有检查地址的函数都需要更新,因此需要升级包
  • 使用能力时,函数签名更具描述性。很明显,new函数需要传递AdminCap作为参数。而且,没有这个能力就无法调用该函数。
  • 对象能力不需要在函数体中进行额外的检查,因此减少了开发人员错误的机会。
  • 拥有的能力还可以用于发现。AdminCap的所有者可以在其账户中看到该对象(通过钱包或浏览器),并知道他们拥有管理员权限。使用地址检查就没有这么直观。

然而,地址方法也有其优势。例如,如果地址是多签名的,并且事务构建变得更复杂,使用地址检查可能更容易。此外,如果应用程序中有一个在每个函数中都使用的中央对象,它可以存储管理员地址,并简化迁移。中央对象的方法在可撤销的能力中也很有价值,管理员可以从用户那里收回能力。