模式:能力(Capability)
在编程中,**能力(capability)**是指授予所有者执行特定操作权利的令牌。它是一种用于控制对资源和操作的访问的模式。一个简单的能力示例是门的钥匙。如果你有钥匙,你就可以打开门;如果没有钥匙,你就不能打开门。一个更实际的例子是“管理员能力”,它允许所有者执行常规用户无法执行的管理操作。
能力是一个对象
在Sui对象模型中,能力被表示为对象。一个对象的所有者可以将该对象传递给函数,以证明他们有执行特定操作的权利。由于严格的类型检查,只能使用正确的能力调用接受能力作为参数的函数。
有一个约定,将能力命名为
Cap
后缀,例如AdminCap
或KioskOwnerCap
。
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的所有者可以在其账户中看到该对象(通过钱包或浏览器),并知道他们拥有管理员权限。使用地址检查就没有这么直观。
然而,地址方法也有其优势。例如,如果地址是多签名的,并且事务构建变得更复杂,使用地址检查可能更容易。此外,如果应用程序中有一个在每个函数中都使用的中央对象,它可以存储管理员地址,并简化迁移。中央对象的方法在可撤销的能力中也很有价值,管理员可以从用户那里收回能力。