代理升级模式 / 学习智能合约#61

in hive-180932 •  2 years ago 

前不久研究了下重入攻击的玩法,里面有个重要的特性:合约的fallback()函数。 fallback是合约的底层函数,在没有其它函数匹配的情况下会执行。这个特性导致了重入的风险性,但在另一方面,合约的升级性也会用到这一特性。这充分体现了一物的两面性:是好是坏取决于你!

由于区块链不可篡改性,合约如果有漏洞就很难更改!那么,怎么才能安全地升级合约呢?这里要用到一种解藕的想法。把一个合约分成:代理、数据和逻辑三部分,通常要升级的也就是逻辑部分,其它不受影响,如下图所示:

proxy.jpg

我们来看下具体实现:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

//定义数据合约
contract storageStructure {
    //记录球员和分数
    address public implementation;//逻辑合约地址
    mapping(address=>uint256) public points;
    address public owner;
}

//定义逻辑合约
contract implementationV1 is storageStructure {
    modifier onlyowner()  {
        require(msg.sender == owner, "only owner can do");
        _;
    }
    //增加球员和分数
    function addPlayer(address player, uint256 point) public onlyowner {
        require(points[player] == 0, "player already exists");
        points[player] = point;
    }
    //修改球员和分数
    function setPlayer(address player, uint256 point) public onlyowner {
        require(points[player] != 0, "player must already exists");
        points[player] = point;
    }
}

//代理合约 代理合约调用逻辑合约的逻辑去修改本身(代理合约)的数据
contract proxy is storageStructure {
    modifier onlyowner()  {
        require(msg.sender == owner, "only owner can call");
        _;
    }
    
    constructor()  {
        owner = msg.sender;
    }
    
    //更新逻辑合约的地址
    function setImpl(address _impl) public onlyowner {
        implementation = _impl;
    }
    
    //fallback函数 调用逻辑合约中的函数,在本地(代理合约)执行
    fallback() external {
        address impl = implementation; //逻辑合约的地址
        require(impl != address(0), "implementation must exists");
        
        //底层调用
        assembly {
            //调用delegateccall
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            //delegatecall(g, a, in, insize, out, outsize)
            let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
            let size := returndatasize()
            
            //returndatacopy(t, f, s)
            returndatacopy(ptr, 0, size)
            
            switch result 
                case 0 { revert(ptr, size) }
                default { return(ptr, size) }
        }
    }  
}

可以看出,里面最核心的就是assembly中调用delegateccallassembly属于汇编的写法,是比较底层的语法(yul),不太好理解。我们大概可以理解整个地调用过程:用户调用代理合约,代理合约调用逻辑合约修改本身的数据。如果需要升级,代理合约调用逻辑合约时就可以指向新的逻辑合约即可!

proxy2.jpg

//逻辑合约升级
contract implementationV2 is implementationV1 {
    function addPlayer(address player, uint256 point) override public onlyowner virtual {
        require(points[player] == 0, "player already exists");
        points[player] = point;
        totalPlayers ++;
    }
}

升级合约差不多就是个解藕的思路,分成三个部分,把需要升级的部分单独更改升级就可以啰!

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!