智能合约升级方案

754 阅读3分钟

合约升级的诉求:业务逻辑升级,数据结构升级,原始数据保留.

传统升级方案

对旧合约做快照,然后把数据导入到新合约中。工作量大,需要脚本扫链,很容易出错。一旦出错,后果可能是无法承担的.保留了区块链特性,合约的更新是用户有感的

proxy-delegate合约升级框架

预备知识

call()

调用远程合约

delegateCall()

拉取远程合约逻辑在本合约环境执行.

特性:在本合约的环境执行远程代码,对数据的更新无视变量名,而是位置(插槽),因此可以扩展数据,也因此不能改变已有数据的位置顺序

fallback函数

image.png

function()external{
​
    }

特征:没有入参,没有返回值,没有方法名,没有标记为payable时如果附带币直接报错

功能:当调用一个合约中不存在的函数时,将调用这个函数。在这里我们重载了这个函数,实现了重定向的功能

assembly:内联汇编

assembly {
    let ptr := mload(0x40)
    calldatacopy(ptr, 0, calldatasize)
    let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
    let size := returndatasize
    returndatacopy(ptr, 0, size)
​
    switch result
    case 0 { revert(ptr, size) }
    default { return(ptr, size) }
 }

Solidity汇编 — Solidity develop 文档 (solidity-cn.readthedocs.io)

使用操作码直接与EVM进行交互。 可以对程序(=智能合约)要执行的操作进行更精细的控制,也可以节省gas

在合约中直接使用delegatecall()无法获取返回值,需要汇编辅助

思路和分析

image.png

将合约分为逻辑层和代理层,数据存储在代理层,用户调用的合约地址也是代理层,用detegateCall()拉取逻辑层代码在代理层执行

优势:

代理层合约地址不变,用户无感知

升级代价小

可保留历史记录

劣势:

违背了智能合约,区块链去中心,不可篡改等特性

逻辑层新合约必须继承老合约

合约数据可扩展,但不能更改已有的数据结构

示例

pragma solidity ^0.4.24;
​
​
contract logic {
    address public l;
    mapping(string => string) private store;
    string  public plus;
    int public plus1;
​
    function Store(string  _key,string  _value) public {
        store[_key] = _value;
    }
​
    function get(string  _key) public view returns (string) {
        return store[_key];
    }
​
    function PlusMessage(string  _val) public {
        plus = _val;
    }
​
    function Plus1Message(int  _val) public {
        plus1 = _val;
    }
​
}
pragma solidity ^0.4.24;
​
contract proxy {
    address public logic;
    mapping(string => string) private store;
​
    constructor(address _logic) public {
        logic = _logic;
    }
​
    function setLogic(address _logic) public {
        logic = _logic;
    }
​
    function get(string memory _key)public view returns (string memory){
        return store[_key];
    }
​
    function () payable public {
        address _impl = logic;
        require(_impl != address(0));
​
        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize)
            let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
            let size := returndatasize
            returndatacopy(ptr, 0, size)
​
            switch result
            case 0 { revert(ptr, size) }
            default { return(ptr, size) }
        }
    }
​
    function getPositionAt(uint n) public view returns (address) {
        assembly {
            let d := sload(n)
            mstore(0x80, d)
            return(0x80,32)
        }
    }
}
​

部署流程

初始化

逻辑合约必须包含代理合约中已有的全局变量,且顺序必须一致

  1. 部署逻辑合约 logic
  2. 部署代理合约 proxy,传入逻辑合约地址
  3. 使用代理合约地址调用逻辑合约的方法

升级

新的逻辑合约必须包含上一代合约中已有的全局变量,且顺序必须一致

  1. 部署新逻辑合约
  2. 在代理合约中设置新的逻辑合约地址
  3. 使用代理合约调用新逻辑合约方法