在區塊鏈上建立可更新的智慧合約(二)

這篇介紹用library的方式來建立可更新的合約邏輯。

Library

Library是另外一種形式的contract,宣告方式也幾乎一樣: Library libA{}。Library會被部署在鏈上,有一個專屬的address,任何人都可以呼叫它,但是Library
1. 不能持有ether
2. 沒辦法儲存任何資料,Library裡面只有函式(動作)。合約呼叫Library是用delegatecall的形式,所以變成合約用Library裡的函式(動作)來對合約自己的變數做操作。

如果合約會用到Library,則合約在編譯完後會在bytecode中留下一段空白,這個空白就是要用來填Library的位置的。可以手動填,solc和truffle等工具都有提供link到Library的功能。

Library還有另外一個使用技巧 — using lib for type; ,
用來將指定的Library函式依附(attach)到指定的型別上。例如

library Action {
  struct Data { mapping(address => uint) amount; }

  function insert(Data storage self, uint value)
      returns (bool)
  {
      if (self.amount[msg.sender] >= 0)
        return false;
      self.amount[msg.sender] = value;
      return true;
  }
}


contract Bet{
    using Action for Action.Data;
    Action.Data playerMapping;

    function register(uint value) {
        if (!playerMapping.insert(value))
            throw;
    }
}

合約裡的 using Action for Action.Data 表示將Action library裡的函式都依附到Action.Data這個資料型別(一個struct)上,型別為Action.Data的playerMapping就可以直接使用Action library的insert函式。如果是使用這種方式呼叫函式的話,則被依附的變數會被當作函式的第一個參數(在這個例子就是playeMapping被當作insert的self參數)。

或是套用在其他資料型別上,這個官方範例將Search library的函式依附到正整數陣列上:

library Search {
    function indexOf(uint[] storage self, uint value) returns (uint) {
        for (uint i = 0; i < self.length; i++)
            if (self[i] == value) return i;
        return uint(-1);
    }
}


contract C {
    using Search for uint[];
    uint[] data;

    function append(uint value) {
        data.push(value);
    }

    function replace(uint _old, uint _new) {
        // This performs the library function call
        uint index = data.indexOf(_old);
        if (index == uint(-1))
            data.push(_new);
        else
            data[index] = _new;
    }
}

如果type是星號(*),表示將函式依附到所有資料型別上:

using lib for *;

不使用using for並不會影響Library的使用,就差在某些情況使用using for會讓函式執行比較易懂,例如:

playerMapping.insert(value) v.s Action.insert(playerMapping, value)

Upgradable Library

如果要用Library來建立可更新的合約邏輯,那表示我們也會需要更新Library。但Library不是在部署前就需要將地址寫死在bytecode裡嗎?
這裏我們一樣利用一個Dispathcer來解決。

我們要做的是將Dispatcher的address寫死在主合約,讓主合約把Dispatcher當作是Library用delegatecall的方式呼叫Dispatcher,Dispatcher再一次用delegatecall傳給Library。主合約並不會知道Dispatcher是不是真的是一個Library(它認為它是),只要Dispatcher收到這個呼叫能順利執行且成功返回結果即可。

這是我們原本用合約的方式建立可更新合約邏輯:

contract Upgrade {
    mapping(bytes4=>uint32) returnSizes;
    int z;
    
    function initialize() {
        returnSizes[bytes4(sha3("get()"))] = 32;
    }
    
    function plus(int _x, int _y) {
        z = _x + _y;
    }
    function get() returns(int) {
        return z;
    }
}
contract Dispatcher {
    mapping(bytes4=>uint32) returnSizes;
    int z;
    address upgradeContract;
    address public dispatcherContract;
function replace(address newUpgradeContract) {
        upgradeContract = newUpgradeContract;
        upgradeContract.delegatecall(bytes4(sha3("initialize()")));
    }
function() {
        bytes4 sig;
        assembly { sig := calldataload(0) }
        var len = returnSizes[sig];
        var target = upgradeContract;
        
        assembly {
            calldatacopy(mload(0x40), 0x0, calldatasize)
            delegatecall(sub(gas, 10000), target, mload(0x40),
                         calldatasize, mload(0x40), len)
            return(mload(0x40), len)
        }
    }
}
contract Main {
    mapping(bytes4=>uint32) public returnSizes;
    int public z;
    address public upgradeContract;
    address public dispatcherContract;
    
    function deployDispatcher() {
        dispatcherContract = new Dispatcher();
    }
    
    function updateUpgrade(address newUpgradeContract) {
        dispatcherContract.delegatecall(
            bytes4( sha3("replace(address)")), newUpgradeContract
        );
    }
    
    function delegateCall(bytes4 _sig, int _x, int _y) {
        dispatcherContract.delegatecall(_sig, _x, _y);
    }
    
    function get() constant returns(int output){
        dispatcherContract.delegatecall(bytes4( sha3("get()")));
        assembly {
            output := mload(0x60)
        }
    }
}

這邊我們要把
1. Upgrade改成Library
2. 將Upgrade的變數移除,Upgrade和main的z值改放進struct裡
3. main裡用using for的方式修改z的值

library Upgrade {
    struct Data{
        int z;
    }
    
    function plus(Data storage self, int _x, int _y) {
        self.z = _x + _y;
    }
    function get(Data storage self) returns(int) {
        return self.z;
    }
}
contract DispatcherStorage {
    address public addrUpgrade;
    mapping(bytes4 => uint32) public sizes;
    
    function DispatcherStorage(address newUpgrade) {
        sizes[bytes4(sha3("get(Upgrade.Data storage)"))] = 32;
        replace(newUpgrade);
    }
    
    function replace(address newUpgrade) {
        addrUpgrade = newUpgrade;
    }
}
contract Dispatcher {
function() {
        DispatcherStorage dispatcherStorage = DispatcherStorage(0xc8e2211a1241dc1906bc1eee85b1807fd4c820e4);
        uint32 len = dispatcherStorage.sizes(msg.sig);
        address target = dispatcherStorage.addrUpgrade();
        
        assembly {
            calldatacopy(mload(0x40), 0x0, calldatasize)
            delegatecall(sub(gas, 10000), target, mload(0x40),
                         calldatasize, mload(0x40), len)
            return(mload(0x40), len)
        }
    }
}
contract Main {
    using Upgrade for Upgrade.Data;
    Upgrade.Data data;
    
    function plus(int _x, int _y) {
        data.plus(_x, _y);
    }
    
    function get() constant returns(int output){
        data.get();
        assembly {
            output := mload(0x60)
        }
    }
}

還有一個新的改變是新增了DispatcherStorage合約,將Dispatcher的變數放進DispatcherStorage裡。這是因為main現在將Dispatcher視為Upgrade,只能用Upgrade的函式呼叫,所以如果Dispatcher裡面有變數也沒辦法操作。另外main也沒辦法用deployDispatcher()函式來動態部署新的Dispatcher合約,Dispatcher的address必須在部署前塞進main的bytecode裡。

所以新增了一個DispatcherStorage來儲存Dispatcher需要用的資訊,每次Dispatcher收到呼叫,就會透過DispatcherStorage(這邊用call而不是delegatecall)來取回相關資訊。取回資訊後再送出到指定的Library address。

如果要更新Library,部署完後透過DispatcherStorage.replace()來更新。

部署的順序:

1. Upgrade
2. DispatcherStorage
3. 將DispatcherStorage的address填入Dispatcher的code裡編譯後再部署Dispatcher
4. 將Dispatcher的address填入Main的bytecode裡再部署Main

使用合約或Library的方式都可以建立出可更新的合約邏輯。
用Library的方式可以省下較多的儲存成本(當然如果你不在意儲存成本的話就沒有差)但限制是Library只能對他知道的struct裡的變數做操作。

Reference:

[1]http://solidity.readthedocs.io/en/develop/contracts.html#libraries

[2]https://blog.aragon.one/library-driven-development-in-solidity-2bebcaf88736

[3]https://medium.com/zeppelin-blog/proxy-libraries-in-solidity-79fbe4b970fd

原文地址: https://medium.com/@twedusuck/%E5%9C%A8%E5%8D%80%E5%A1%8A%E9%8F%88%E4%B8%8A%E5%BB%BA%E7%AB%8B%E5%8F%AF%E6%9B%B4%E6%96%B0%E7%9A%84%E6%99%BA%E6%85%A7%E5%90%88%E7%B4%84-%E4%BA%8C-24f07206d033

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值