美文网首页
以太坊智能合约升级

以太坊智能合约升级

作者: SlowGO | 来源:发表于2018-11-30 11:24 被阅读6次

    问题

    智能合约一旦发布就不能修改了,那如果出现bug怎么办?可以开发一个新的合约,但之前合约中的数据怎么办?重新导入一遍吗?可能需要几天的时间,而且使用新的合约又涉及到调用者更改合约地址,比较麻烦。

    所以我们需要一个能够升级的合约,来解决这2个麻烦的问题。

    方案

    普通合约的方式:

    合约肯定是不能修改的,那么升级就肯定是使用新的合约,为了使旧合约的数据复用,就需要把数据层提取出来,作为一个单独的存储合约,供业务合约调用。

    为了使合约的变动对 client 透明,我们可以添加一个 proxy 代理合约,client 和 proxy 合约对接,不再直接调用业务合约,当业务合约升级时,只需要通过 proxy 指定新版合约。

    示例

    我们需要开发3个类型的合约:

    1. 存储合约

    处理底层通用的存储操作。

    1. 委托合约

    也就是约我合约,处理业务逻辑,每次业务升级就创建一个新版本的委托合约。

    1. 代理合约

    用于和 client 对接,内部使用 delegatecall 机制可以调用不同的合约。

    (1)创建项目

    新建目录 upgradetest,进入目录执行初始化命令:

    $ truffle init 
    

    (2)存储合约

    contracts/KeyValueStorage.sol

    pragma solidity ^0.4.18;
    
    contract KeyValueStorage {
    
      mapping(address => mapping(bytes32 => uint256)) _uintStorage;
      mapping(address => mapping(bytes32 => address)) _addressStorage;
      mapping(address => mapping(bytes32 => bool)) _boolStorage;
      mapping(address => mapping (address => mapping(address => uint256)))  _allowanceStorage;
    
      /**** Get Methods ***********/
    
      function getAddress(bytes32 key) public view returns (address) {
        return _addressStorage[msg.sender][key];
      }
    
      function getUint(bytes32 key) public view returns (uint) {
        return _uintStorage[msg.sender][key];
      }
    
      function getBool(bytes32 key) public view returns (bool) {
        return _boolStorage[msg.sender][key];
      }
      function  getAllowance(address owner,address spender) public view returns (uint) {
        return _allowanceStorage[msg.sender][owner][spender];
      }
      /**** Set Methods ***********/
    
      function setAddress(bytes32 key, address value) public {
        _addressStorage[msg.sender][key] = value;
      }
    
      function setUint(bytes32 key, uint value) public {
        _uintStorage[msg.sender][key] = value;
      }
    
      function setBool(bytes32 key, bool value) public {
        _boolStorage[msg.sender][key] = value;
      }
      function setAllowance(address owner,address spender,uint value) public {
        _allowanceStorage[msg.sender][owner][spender] = value;
      }
      /**** Delete Methods ***********/
    
      function deleteAddress(bytes32 key) public {
        delete _addressStorage[msg.sender][key];
      }
    
      function deleteUint(bytes32 key) public {
        delete _uintStorage[msg.sender][key];
      }
    
      function deleteBool(bytes32 key) public {
        delete _boolStorage[msg.sender][key];
      }
    
    }
    

    contracts/StorageState.sol

    pragma solidity ^0.4.18;
    import "./KeyValueStorage.sol";
    
    contract StorageState {
        KeyValueStorage _storage; 
    }
    

    (3)委托合约V1版本

    contracts/SafeMath.sol

    pragma solidity ^0.4.18;
    
    
    /**
     * @title SafeMath
     * @dev Math operations with safety checks that throw on error
     */
    library SafeMath {
    
      /**
      * @dev Multiplies two numbers, throws on overflow.
      */
      function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
          return 0;
        }
        uint256 c = a * b;
        assert(c / a == b);
        return c;
      }
    
      /**
      * @dev Integer division of two numbers, truncating the quotient.
      */
      function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // assert(b > 0); // Solidity automatically throws when dividing by 0
        // uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold
        return a / b;
      }
    
      /**
      * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
      */
      function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        assert(b <= a);
        return a - b;
      }
    
      /**
      * @dev Adds two numbers, throws on overflow.
      */
      function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        assert(c >= a);
        return c;
      }
    }
    

    contracts/DelegateV1.sol

    pragma solidity ^0.4.18;
    
    import "./SafeMath.sol";
    import "./StorageState.sol";
    
    contract DelegateV1 is StorageState {
        using SafeMath for uint256;
      
        function setNumberOfOwners(uint256 num) public {
            _storage.setUint("total", num);
        }
    
        function getNumberOfOwners() view public returns (uint256) {
            return _storage.getUint("total");
        }
    }
    

    (4)代理合约

    contracts/Ownable.sol

    pragma solidity ^0.4.18;
    
    /**
     * @title Ownable
     * @dev The Ownable contract has an owner address, and provides basic authorization control
     * functions, this simplifies the implementation of "user permissions".
     */
    
    contract Ownable {
        address owner;
    
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        /**
        * @dev The Ownable constructor sets the original `owner` of the contract to the sender
        * account.
        */
        constructor() public {
            owner =  msg.sender;
        }
        /**
        * @dev Throws if called by any account other than the owner.
        */
    
        modifier onlyOwner() {
            require(msg.sender == owner);
            _;
        }
    
        
        /**
        * @dev Allows the current owner to transfer control of the contract to a newOwner.
        * @param newOwner The address to transfer ownership to.
        */
        function transferOwnership(address newOwner) public onlyOwner {
            require(newOwner != address(0));
            emit OwnershipTransferred(owner, newOwner); // solhint-disable-line
            owner = newOwner;
        }
    }
    

    contracts/Proxy.sol

    pragma solidity ^0.4.18;
    
    import "./Ownable.sol";
    import "./StorageState.sol";
    
    contract Proxy is StorageState , Ownable {
        
        constructor(KeyValueStorage storage_, address _owner) public {
            _storage = storage_;
            _storage.setAddress("owner", _owner);
        }
    
        event Upgraded(address indexed implementation);
    
        address public _implementation;
    
        function implementation() public view returns (address) {
            return _implementation;
        }
    
        function upgradeTo(address impl) public onlyOwner {
            require(
                _implementation != impl,
                "Cannot upgrade to the same implementation."
            );
            _implementation = impl;
            emit Upgraded(impl);
        }
      
        function () public payable {
            address _impl = implementation();
            require(
                _impl != address(0),
                "Cannot set implementation to address(0)"
            );
            bytes memory data = msg.data;
    
            assembly {
              let result := delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0)
              let size := returndatasize
              let ptr := mload(0x40)
              returndatacopy(ptr, 0, size)
              switch result
              case 0 { revert(ptr, size) }
              default { return(ptr, size) }
            }
        }
    }
    

    (5)部署测试

    migrations/2_Storage_migration.js

    const KeyValueStorage = artifacts.require("KeyValueStorage")
    const Proxy = artifacts.require("Proxy")
    
    module.exports = async function(deployer, network, accounts) {
      deployer.deploy(KeyValueStorage).then(function() {
        return deployer.deploy(Proxy, KeyValueStorage.address, accounts[0]);
      });
    };
    

    migrations/3_v1_migration.js

    const DelegateV1 = artifacts.require('DelegateV1')
    
    module.exports = async function(deployer, network, accounts) {
      deployer.deploy(DelegateV1);
    };
    

    在项目根目录位置执行进入控制台的命令:

    $ truffle develop
    

    编译部署:

    > compile
    > migrate
    

    测试代码 test/test.js

    const KeyValueStorage = artifacts.require('KeyValueStorage')
    const DelegateV1 = artifacts.require('DelegateV1')
    const ProxyContract = artifacts.require('Proxy')
    
    contract('Storage and upgradability example', async (accounts) => {
      let proxy, delegateV1
      
      it('initalize test', async() => {
        proxy = await ProxyContract.deployed()
        delegateV1 = await DelegateV1.deployed()
      });
      
    
      it('should ...', async () => {
        await proxy.upgradeTo(delegateV1.address)
        proxy = _.extend(proxy,DelegateV1.at(proxy.address));
    
        await proxy.setNumberOfOwners(10);
        let numOwnerV1 = await proxy.getNumberOfOwners();
        console.log(numOwnerV1.toNumber())
      });
    });
    

    运行测试代码:

    > test
    

    执行成功后,我们可升级合约模式已经跑通了。

    (6)委托合约V2版本

    开发新版本的业务合约 contracts/DelegateV2.sol

    pragma solidity ^0.4.18;
    
    import "./DelegateV1.sol";
    import "./StorageState.sol";
    
    contract DelegateV2 is StorageState {
    
        modifier onlyOwner() {
            require(
                msg.sender == _storage.getAddress("owner"),
                "msg.sender is not owner"
            );
            _;
        }
    
        function setNumberOfOwners(uint num) public onlyOwner {
            _storage.setUint("total", num);
        }
        function getNumberOfOwners() public view returns (uint) {
            return _storage.getUint("total");
        }
    }
    

    (7)部署测试

    V2合约的迁移脚本 migrations/2_v2_migration.js

    const DelegateV2 = artifacts.require('DelegateV2')
    
    module.exports = async function(deployer, network, accounts) {
      deployer.deploy(DelegateV2);
    };
    

    编译部署:

    > compile
    > migrate
    

    测试代码 test/test.js

    const KeyValueStorage = artifacts.require('KeyValueStorage')
    const DelegateV1 = artifacts.require('DelegateV1')
    const DelegateV2 = artifacts.require('DelegateV2')
    const ProxyContract = artifacts.require('Proxy')
    
    contract('Storage and upgradability example', async (accounts) => {
      let proxy, delegateV2
      
      it('initalize test', async() => {
        proxy = await ProxyContract.deployed()
        delegateV1 = await DelegateV1.deployed()
        delegateV2 = await DelegateV2.deployed()
      });
      
    
      it('should create and upgrade idap token', async () => {
        // 调用 v1 的 set 方法
        await proxy.upgradeTo(delegateV1.address)
        proxy = _.extend(proxy,DelegateV1.at(proxy.address));
        await proxy.setNumberOfOwners(101);
    
        // 调用 v2 的 get
        await proxy.upgradeTo(delegateV2.address)
        proxy = DelegateV2.at(proxy.address);
        let numOwnerV2 = await proxy.getNumberOfOwners();
        console.log(numOwnerV2.toNumber()) // 输出了 v1 设置的 101
      });
    });
    

    运行:

    > test
    

    相关文章

      网友评论

          本文标题:以太坊智能合约升级

          本文链接:https://www.haomeiwen.com/subject/nigccqtx.html