独特的所有权¶
介绍¶
在Sui上,每项资产都有它的归属,这个概念与现实世界一致 —— 你拥有一部手机、一台电脑、一辆豪车、一栋别墅...同样的,你也可以拥有一项数字资产,你对该资产全权掌控。但是,一旦资产被转移,之前的所有者就完全失去了对该资产的控制权。
对这个账户独享的所有权的概念进行适当延伸,就可以构造出一种权限控制的方法:在合约中定义一个名为AdminCap
的对象,它可以有一个也可以有多个,但是只有被认可的用户才会拥有这个对象,然后在敏感操作的函数中传入该对象进行权限验证...
以下是一个简单的例子:
module admin::admin;
public struct AdminCap has key {
id: UID
}
fun init(ctx: &mut TxContext) {
transfer::transfer(AdminCap {
id: object::new(ctx)
}, ctx.sender());
}
public fun sensitive_operation(_: &AdminCap) {
// do something...
}
没错,你只需要把AdminCap
传入,不需要做任何显式操作,它会自动对你传入的对象进行类型判断:<package::module::object_name>
。
例题¶
接下来,考虑这么一个问题:
module admin::admin;
use sui::event;
use sui::package::{Self, Publisher};
// 一次性见证者结构,结构名是模块名的全大写,只有在`init`中使用
public struct ADMIN has drop {}
public struct Flag has copy, drop {
user: address,
success: bool
}
// 仅在发布上链时自动调用一次
fun init(otw: ADMIN, ctx: &mut TxContext) {
// 使用一次性见证者`ADMIN`,创建`Publisher`并转移其所有权给发布者
// 每个模块都可以生成模块对应的`Publisher`
package::claim_and_keep(otw, ctx);
}
// 尝试使用`Publisher`进行权限控制,阻止夺旗
public fun get_flag(_: &Publisher, ctx: &TxContext) {
event::emit(Flag {
user: ctx.sender(),
success: true
});
}
按照前文所述,在调用函数时会自动对传入的对象进行类型判断,而唯一的Publisher
已经转移给了该合约的发布者,那么该如何破局夺旗?
前文也提到,<package::module::object_name>
是简单类型判断的依据,那么Publisher
从何而来 —— sui::package::Publisher
!!!换句话说,每一个Publisher
(不同模块甚至是不同合约),都可以通过该例题的校验!!!
所以,对于这道例题而言,想要夺旗,只需要自己发布并得到一个Publisher
,用它来get_flag
。
那么,
Publisher
真的无法用来做权限控制吗?在
sui::package
模块中,提供了两个函数:from_package
和from_module
,需要根据实际情况手动调用进行断言。但对权限控制而言,直接使用Publisher
局限性非常大,不如自己构造一个AdminCap
灵活且更具扩展性。
拓展¶
在Sui上,除了独享所有权,还有共享所有权和冻结(不可变)所有权(可视作特殊的共享,允许只读访问,无法修改和转移)。但是,所有的操作都只能是其原合约中定义的函数功能,也就是说,即使是独享/共享所有权,你也无法对它为所欲为。
举一反三,是不是又可以发现几个可供审查的漏洞?比如:将敏感的对象误设为共享;本应独享的控制权却存储于共享的对象当中......