如何为智能合约设计一个安全升级方式

本节介绍的内容将有助于为你日后解决智能合约的Bug做好充足准备。

注意:在系统中添加新组件时总是有风险的。设计不当的故障保护措施本身可能会成为一个漏洞,许多精心设计的故障保护措施之间的交互也会造成漏洞的存在。仔细研究智能合约中使用的每一项技术,并仔细研究它们如何协同工作以创建一个健壮的系统。

智能合约高效升级

智能合约在运行过程中发现Bug或者代码需要改进,这会影响整个系统的健壮性,如果发现无法解决的Bug,那就可能会造成严重的经济损失。

在本文中,我们无法涉及到任何复杂的问题。然而有两种最常用的基本方法,最简单的是拥有一个注册表智能合约,包含该智能合约最新版本的地址。对于智能合约用户来说,用户可以无缝的将该智能合约调用和数据转发到最新版本的智能合约中。

无论采用哪种技术,模块化和组件之间的隔离都是非常重要,这样代码更改就不会破坏智能合约功能、孤立数据或需要大量的移植代码成本。这样代码更改就不会破坏功能、孤立数据或需要大量的移植成本。

同样重要的是要有一个安全的方式,让社区决定升级智能合约代码。根据您的智能合约,代码更改可能需要由单个受信任方、一组成员或全部涉众投票批准。如果这个过程可能需要一些时间,那么您需要考虑是否有其他方法可以在发生攻击时更快地做出反应,例如紧急停止或断路器。

示例1:使用注册表智能合约来存储智能合约的最新版本

在本例中,调用不会被转发,因此用户应该在每次与当前地址交互之前获取该地址。

contract SomeRegister {


    address backendContract;


    address[] previousBackends;


    address owner;

    function SomeRegister() {


        owner = msg.sender;


    }

    modifier onlyOwner() {


        require(msg.sender == owner)


        _;


    }

    function changeBackend(address newBackend) public


    onlyOwner()


    returns (bool)


    {


        if(newBackend != backendContract) {


            previousBackends.push(backendContract);


            backendContract = newBackend;


            return true;


        }

        return false;


    }


}

这种方法有两个主要缺点:

1. 用户必须始终查找当前地址,否则任何人都会使用旧版本的智能合约来冒充


2. 在更换智能合约时,您需要仔细考虑如何处理智能合约数据

另一种方法是让智能合约将调用和数据转发到最新版本的智能合约:

示例2:使用DELEGATECALL转发数据和调用

contract Relay {


    address public currentVersion;


    address public owner;

    modifier onlyOwner() {


        require(msg.sender == owner);


        _;


    }

    function Relay(address initAddr) {


        currentVersion = initAddr;


        owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner


    }

    function changeContract(address newVersion) public


    onlyOwner()


    {


        currentVersion = newVersion;


    }

    function() {


        require(currentVersion.delegatecall(msg.data));


    }


}

这种方法避免了先前的问题,但有其自身的问题。您在此智能合约中如何存储数据时必须格外小心。如果新智能合约的存储架构与第一个不同,则数据可能最终损坏。此外,该模式的简单版本无法从函数返回值,而只能转发它们,这限制了其适用性。(更复杂的实现尝试通过内联汇编代码和返回大小注册表来解决此问题。)

无论采用哪种方法,找到合适的智能合约升级的方法很重要,否则当在智能合约中发现不可避免的bug时,后果将变得不可预计。

断路器Circuit Breakers

如果满足某些条件,断路器将停止智能合约的执行,并且在发现新bug时非常有用。例如如果发现一个bug,智能合约中的大多数操作可能会被挂起,而现在唯一有效的操作是撤回。您可以给某些受信任方触发断路器的能力,也可以使用编程规则在满足某些条件时自动触发特定断路器。

示例:

bool private stopped = false;


address private owner;

modifier isAdmin() {


    require(msg.sender == owner);


    _;


}

function toggleContractActive() isAdmin public {


    // You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users


    stopped = !stopped;


}

modifier stopInEmergency { if (!stopped) _; }


modifier onlyInEmergency { if (stopped) _; }

function deposit() stopInEmergency public {


    // some code


}

function withdraw() onlyInEmergency public {


    // some code


}

减速带Speed Bumps (减缓智能合约操作)

减速带降低了操作的速度,因此,如果发生恶意操作,则有时间进行恢复。例如DAO在成功请求拆分DAO和这样做的能力之间需要27天。这样可以确保将资金保留在智能合约内,从而增加了收回的可能性。对于DAO,在减速带给定的时间内如没有采取任何有效的措施时,结合我们的其他技术,它们可以非常有效。

示例:

struct RequestedWithdrawal {


    uint amount;


    uint time;


}

mapping (address => uint) private balances;


mapping (address => RequestedWithdrawal) private requestedWithdrawals;


uint constant withdrawalWaitPeriod = 28 days; // 4 weeks

function requestWithdrawal() public {


    if (balances[msg.sender] > 0) {


        uint amountToWithdraw = balances[msg.sender];


        balances[msg.sender] = 0; // for simplicity, we withdraw everything;


        // presumably, the deposit function prevents new deposits when withdrawals are in progress

        requestedWithdrawals[msg.sender] = RequestedWithdrawal({


            amount: amountToWithdraw,


            time: now


        });


    }


}

function withdraw() public {


    if(requestedWithdrawals[msg.sender].amount > 0 && now > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) {


        uint amountToWithdraw = requestedWithdrawals[msg.sender].amount;


        requestedWithdrawals[msg.sender].amount = 0;

        require(msg.sender.send(amountToWithdraw));


    }


}

速率限制Rate Limiting 

速率限制暂停或需要批准才能进行重大更改。例如在某个时间段内(例如在1天之内最多只能提取100个以太币),可能只允许存款人提取一定数量或一定比例的总存款-该时间段内的其他提取可能会失败或需要某种特殊的批准 。或者速率限制可以处于智能合约级别,在一段时间内仅由智能合约发行一定数量的代币。

智能合约版本推出

智能合约新版本在正式推出使用前,已经经过大量的安全测试和代码测试。

至少您应该:

1. 拥有100%测试覆盖率的完整测试软件。


2. 在自己的testnet上部署。


3. 在公共测试网上进行大量测试和漏洞赏金。


4. 测试应允许各种参与者大量参与智能合约进行交互。


5. 在主网上部署beta版本,并限制风险数量。

自动弃用AUTOMATIC DEPRECATION

在测试过程中,您可以在一定的时间段后通过阻止任何操作来强制自动弃用。例如alpha版本智能合约可能会工作几个星期后,然后自动关闭所有操作,但最终退出除外。

modifier isActive() {


    require(block.number <= SOME_BLOCK_NUMBER);


    _;


}

function deposit() public isActive {


    // some code


}

function withdraw() public {


    // some code


}

在早期阶段,您可以限制用户(或整个智能合约)使用以太坊的数量,从而降低风险。

Bug赏金计划

进行赏金计划的一些技巧:

· 决定使用(BTC和/或ETH)哪种代币进行赏金分配。


· 决定赏金的预算总金额。


· 根据预算,确定三层奖励:


           1. 最低奖励


           2.最高奖励


           3.如果发现非常严重的漏洞,将授予额外的奖励


· 确定赏金管理员(3个可能是典型的理想人选)。


· 首席开发人员可能应该是赏金评委之一。


· 当收到一个bug报告时,开发负责人,在评审的建议下,应该评估bug的严重性。


· 这个阶段的工作应该是在一个私有的存储库中进行的,问题应在Github上归档。


· 如果这是一个应该被修复的bug,那么在私有repo中,开发人员应该编写一个测试用例,测试用例应该失败,从而确认这个bug。


· 开发人员应实施此修复程序并确保测试可以通过;根据需要编写其他测试,向赏金猎人显示修复方法,将补丁程序合并回公共仓库。


· 确定赏金猎人是否有其他关于修复的反馈。


· 赏金评委根据对漏洞的可能性和影响的评估来确定奖励的大小。


· 在整个过程中让赏金参与者了解情况,然后尽量避免延迟向他们发送奖励。

关于三层奖励的示例,请参见以太坊的赏金计划:https://bounty.ethereum.org/

奖励的价值将根据影响的严重程度而有所不同。对轻微“bug”的奖励从0.05个BTC开始。重要的bug,例如导致共识问题的bug,将会得到最多5个BTC的奖励。如果存在非常严重的漏洞,则可能获得更高的回报(最多25个BTC)。


关键词: 智能合约  以太坊  

该内容来自于互联网公开内容,非区块链原创内容,如若转载,请注明出处:https://htzkw.com/archives/28399

联系我们

aliyinhang@gmail.com