本文为智能合约设计模式系列的一部分。
目的
根据适当条件禁止访问合约功能
动机
由于区块链固有的公开性,无法保证合约的完全隐私。你无法阻止别人读取智能合约的状态,因为每个人都可以看到所有内容。但通过定义private
状态,防止合约状态被其它合约读取。方法也可以声明为private
,但这样就禁止一切外部调用。简单的声明称public
,又会导致每个人都可以访问。一般来说,需要制定一些访问规则来限制合约访问。通常,方法访问权限被授予特定的实体,例如合约管理员。其它限制有在特定的时间点或访问者愿意为访问付费。所有这些限制,以及更多,都可以使用访问限制模式,来保护合约的安全性。
适用性
在以下条件时使用访问限制模式
- 合约方法只能某些情况下被调用
- 合约的多个方法都需要类似限制
- 提高合约安全性,防止未授权的访问
参与者和协作
此模式参与者是方法的调用者和方法所属的合约。调用者可以是用户或合约,通过对合约地址发送事务来调用方法。被调用方的参与者是被保护的方法以及负责访问限制的附加组件。
实现
访问控制模式需要用到守卫检查模式。守卫检查模式检查调用方法是否满足规定要求,如果不满足就抛出异常。这里推荐采用 modifier 而非写到方法开头,因为这些检查经常要被多个方法复用。modifier 可以接受被保护方法的参数,也可以接受自己的参数。当然也可以将判断条件写死到 modifier 中,但这样会降低复用性。
modifier 结构通常遵守相同的规则:最开始检查规定条件,然后执行被修饰方法,_;
在 modifier 中代表被修饰方法。如果需要在方法最后执行额外的动作,可以在_;
后添加代码。通过这些方式,可以实现多种访问限制。
代码示例
下面示例改变自Solidity官方文档示例,它展示了3种访问限制方式。合约所有权既可以由所有人主动变更,也可以在上次购买变更一个月后由任何人以1以太币价格购买变更。
// This code has not been professionally audited, therefore I cannot make any promises about
// safety or correctness. Use at own risk.
pragma solidity ^0.4.21;
contract AccessRestriction {
address public owner = msg.sender;
uint public lastOwnerChange = now;
modifier onlyBy(address _account) {
require(msg.sender == _account);
_;
}
modifier onlyAfter(uint _time) {
require(now >= _time);
_;
}
modifier costs(uint _amount) {
require(msg.value >= _amount);
_;
if (msg.value > _amount) {
msg.sender.transfer(msg.value - _amount);
}
}
function changeOwner(address _newOwner) public onlyBy(owner) {
owner = _newOwner;
}
function buyContract() public payable onlyAfter(lastOwnerChange + 4 weeks) costs(1 ether) {
owner = msg.sender;
lastOwnerChange = now;
}
}
第7、8行声明状态变量owner
和lastownerchange
并初始化其为合约创建者和创建时间。第10行定义的onlyBy
modifier 应用于第28行changeowner
方法,确保本方法的调用者为合约的owner
变量。如果其他人调用此方法就会触发异常。
第二个 modifier onlyAfter
确保被附加的第32行方法只能在指定时间之后调用,本例为上次变更时间后4周(Solidity不支持月)。确保此方法至少经过4周才能调用一次。
第20行的最后一个 modifier cost
具有一个金额入参,它确保调用者提交的金额不小于指定金额。这个 modifer 不同于前两个的之处在于,它在方法执行后(第22行的_
代表方法执行)还有代码。从第23行开始,if语句检查提交金额是否大于指定金额,并返还多余金额给调用方。这个示例展示了多种 modifier。结合前面的 modifier,这个合约只有经过上次购买后4周才可以被再次购买,并且调用事物至少包含1个以太币。第32行buyContract
方法的payable
modifier 使此方法可以接受以太币。
需要注意的是,在上面示例中,我们可以不用 modifier 而直接在个方法体中实现相应的代码,功能完全一样,甚至更简单。但是,如果两个或多个方法包含相同或类似逻辑(如状态机模式),将它们实现到 modifier 中的好处就显而易见,因为 modifier 允许轻松复用。
后果
应用访问限制模式,必须注意几个后果。一个有争议的后果是代码可读性。一方面,modifier 是代码更容易理解,以为它在方法头中清楚标识,特别是当其名字有意义时。另一方面,代码执行从一行跳转到完全不同的其它行,使得难于跟踪和维护,从而容易导入引入歧义(甚至恶意)代码。因此,新的智能合约开发语言 Vyper 放弃了 modifier。
该模式的优点是:它能适应不同的情况和高复用性,同时提供了一种限制方法安全访问的方式,从而提高智能合约的安全性。
已知应用
这个模式最著名的应用可能是OpenZeppelin的Ownable合约。
另个例子是CrytoKitties DApp的核心合约,它有3个所有者,CEO,CFO和COO,他们具有不同的安全级别,可以访问不同的功能。
推而广之
智能合约由于其公开透明去中心化的特质,导致安全成为影响它的重要问题之一。公开透明意味着源代码甚至合约状态谁都可以查看。去中心化意味着谁都可以调用合约的方法。以太坊、EOS等公链的匿名性更为黑客提供了肆无忌惮攻击的前期。联盟链提供的身份机制,从一定程度上缓解了安全问题(事后追查),但目前联盟链并不能设置方法级别的访问权限,也就是说无法阻止联盟中用户调用合约方法,况且有些方法从业务上就需要多个角色调用。因此对方法调用的安全保护成为智能合约开发的头等大事。
守卫检查模式着重于方法参数、方法返回等方面的安全。
访问限制模式着重于访问权限、合约状态等方面的安全。
本模式建议智能合约方法检查本次调用是否合法,一般有两大类检查,一是调用者是否有权利调用此方法,二是此方法是否处于可调用状态。
其它链并没有Solidity中修饰器,但它们都可以很容易地实现类似方式。至少可以把检查逻辑封装到单独的检查方法中,在智能合约暴露的方法里(一般是开始处)调用。另外,如果要进行调用者相关检查,必须获得调用者,各个链都有提供。EOS使用require_auth(account_name)
确保当前调用者为指定账号。HyperLedger Fabric则提供了cid库获取调用者信息。
完整内容请查看智能合约设计模式系列
网友评论