以太坊智能合约升级

2018-11-30  本文已影响6人  SlowGO

问题

智能合约一旦发布就不能修改了,那如果出现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
上一篇下一篇

猜你喜欢

热点阅读