本文来源:天算Delphy技术团队/johnathon
说起智能合约,大家一般都会想到其不可篡改的特性。合约一旦部署便永不可更改,是以太坊智能合约的一大优点。正因为这个特点,大家才可以放心地使用,而不用担心合约开发者跑路。
在许多合约里,这个特性非常重要。然而,对另一些应用来说,尽管可以让用户用得放心,代码不可修改也为开发带来了很大的麻烦。在以太坊中,我们可以通过将合约模块化,并更新主合约中记录的子合约地址来实现代码更新。
在EOS中,情况便发生了180°大转变。默认情况下,合约账户可以随时修改代码而不受任何约束。换而言之,即便用户在这一刻验证过合约源码无误,下一秒,合约便可以修改代码从而实现任何操作。
这对于涉及到钱的应用来说是个大忌。由于代码可以变更,用户不放心把钱转入该合约,从而使得用户使用的门槛提升不少。但是,另一方面,如果把active权限设定为合约本身,使代码绝对不可更改,如果代码出现了漏洞,又或者在功能上需要更新时,迁移用户至新的合约又十分麻烦。
综上所述,每个合约里都应该有一个权限短暂移交的机制。这个机制能保证合约在一定的时间内绝对不可被更改,而且合约的所有者可以随时延长这个时间。只有当这个时间结束时,合约所有者才可以重新掌握合约的代码更改权限。
举个例子,一个游戏的合约里应该有一个机制,使得游戏开始前,所有者可以移交权限。而只有在一轮游戏结束后,所有者才可以进行代码更新。这个机制可以大大降低用户参与的信任门槛,而又不会失去合约更新的能力。
按照这个思路,代码上可以有很多方法实现,这里介绍一个简单实现。完整的源码已经在GitHub上发布:
GitHub - xJonathanLEI/eosyield
合约接口
首先,按照上述的思路,我们至少需要以下几个接口:
setowner:
合约本身以及现有owner都可以调用这个接口来设置合约所有者。
yieldcontrol:
只有owner本身可以调用,重设合约的owner和active权限。
extend:
延长移交时限,只有owner可以调用。除非代码在短期内需要更新,合约所有者应该经常调用这个接口来保证接下来的一段时间内,合约不可被篡改。
regain:
移交时限结束后,合约owner可以通过这个接口,重新掌握合约的owner和active权限。
数据表
接下来设计一个表,用来存储权限移交相关的数据。在EOS智能合约中,一般会使用multi_index类来承载数据。然而,在我们这个合约中,由于只有一条记录,使用singleton会更加方便。
singleton实际上是对multi_index类的一个简单封装。它将表名本身作为主键存储了一条记录,背后用的实际上还是multi_index。详情可以查阅EOS合约库源码:
eosiolib/singleton.hpp
我们需要在数据表中存储当前的owner以及移交期限,定义为以下字段:
struct yield_info{ account_name owner; time_point_sec expiration;};
其中,time_point_sec是一个对uint32_t的封装,代表从Epoch到当前时间的秒数,精度为1秒,向下取整。
expiration字段为0时表示权限未被移交,否则其代表移交结束时间的时间戳。
接口实现
首先在合约中加入yield变量表示数据表记录:
class eosyield : public contract{ eosyield(account_name self) : contract(self), yield(self, self) {}
typedef singleton<N(yieldinfo), yield_info> tbl_yield; tbl_yield yield;}
setowner:
接口接受一个类型为account_name的参数,代表新的owner账户名:
void setowner(account_name new_owner);
合约本身以及现有的owner都可以设定新的owner。即便是合约权限已经移交,owner也可以被重设:
yield_info new_info{ new_owner, time_point_sec(0)};yield.set(new_info, _self);
yieldcontrol:
只有owner可以调用该接口,调用时需要提供移交期限的秒数:
void yieldcontrol(uint32_t yield_seconds);
检查合约现有状态以及参数后,将合约的owner以及active权限都设置为eosio.code本身:
// 定义账户权限authority owner_active = authority{ .threshold = 1, .keys = {}, .accounts = { permission_level_weight{ .permission = {_self, N(eosio.code)}, .weight = 1}}, .waits = {}};// 变更owner权限action(permission_level(_self, N(owner)), N(eosio), N(updateauth), updateauth_args{_self, N(active), N(owner), owner_active}).send();// 变更active权限action(permission_level(_self, N(owner)), N(eosio), N(updateauth), updateauth_args{_self, N(owner), 0, owner_active}).send();
extend:
该接口接受一个新的参数,代表新的移交期限秒数。设定的新移交期限必须比现有的移交期限更晚:
void extend(uint32_t new_yield_seconds);
regain:
只有owner可以调用该接口,接口不接受任何参数。成功调用后,合约的权限会被重设为合约本身的eosio.code权限以及owner的active权限:
void regain();
值得注意的是,EOS中对于权限中的accounts字段有排序要求。由于我们正在设定超过一个账户的权限,我们必须先对这两个权限进行排序,否则有可能会报错,导致永远无法重获账户权限。
首先定义两个权限:
// 定义合约eosio.code权限permission_level_weight contract_permission{ .permission = {_self, N(eosio.code)}, .weight = 1};// 定义owner的active权限permission_level_weight owner_permission{ .permission = {info.owner, N(active)}, .weight = 1};
然后对两个权限进行排序:
vector<permission_level_weight> accounts;if (std::tie(contract_permission.permission.actor, contract_permission.permission.permission) < std::tie(owner_permission.permission.actor, owner_permission.permission.permission)) accounts = {contract_permission, owner_permission};else accounts = {owner_permission, contract_permission};authority owner_active = authority{ .threshold = 1, .keys = {}, .accounts = accounts, .waits = {}};
最后再和yieldcontrol接口中一样进行权限变更。
合约测试
按照GitHub中指引的步骤进行部署后,过程中查询账户权限,可以看到其变化。
调用yieldcontrol之后:
[ { "perm_name": "active", "parent": "owner", "required_auth": { "threshold": 1, "keys": [], "accounts": [ { "permission": { "actor": "yield", "permission": "eosio.code" }, "weight": 1 } ], "waits": [] } }, { "perm_name": "owner", "parent": "", "required_auth": { "threshold": 1, "keys": [], "accounts": [ { "permission": { "actor": "yield", "permission": "eosio.code" }, "weight": 1 } ], "waits": [] } }]
调用regain之后:
[ { "perm_name": "active", "parent": "owner", "required_auth": { "threshold": 1, "keys": [], "accounts": [ { "permission": { "actor": "jonathan", "permission": "active" }, "weight": 1 }, { "permission": { "actor": "yield", "permission": "eosio.code" }, "weight": 1 } ], "waits": [] } }, { "perm_name": "owner", "parent": "", "required_auth": { "threshold": 1, "keys": [], "accounts": [ { "permission": { "actor": "jonathan", "permission": "active" }, "weight": 1 }, { "permission": { "actor": "yield", "permission": "eosio.code" }, "weight": 1 } ], "waits": [] } }]
可见,在regain之前,没有任何人拥有对合约的修改权限。
以上便是整个合约的实现思路,完整的源码以及部署指引请参见GitHub页面。合约权限短暂移交对开发者和用户都有很多好处,希望可以看到类似的权限架构在更多的合约中实现。
声明:本文由入驻金色财经的作者撰写,观点仅代表作者本人,绝不代表金色财经赞同其观点或证实其描述。
提示:投资有风险,入市须谨慎。本资讯不作为投资理财建议。
区块律动BlockBeats
3EX AI交易平台
金色精选
金色财经 善欧巴
元宇宙NEWS
DODO Research