智能合约设计模式 - 访问限制

本文为智能合约设计模式系列的一部分。

目的

根据适当条件禁止访问合约功能

动机

由于区块链固有的公开性,无法保证合约的完全隐私。你无法阻止别人读取智能合约的状态,因为每个人都可以看到所有内容。但通过定义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行声明状态变量ownerlastownerchange并初始化其为合约创建者和创建时间。第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库获取调用者信息。

完整内容请查看智能合约设计模式系列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容