solidity:非结构化代理模式

285 阅读3分钟

基础代理模式带来的问题

在基础代理模式中,通过delegatecall和相同的成员变量存储布局实现代理调用,但是也出现一些问题、

由于结构化的模式,proxy合约与Logic合约使用了相同的成员变量的堆叠,但是proxy中的这些变量的唯一作用就是占位符。这使得proxy合约并不纯粹,

非结构化代理

Proxy合约使用空白存储,把存储的"解释权"完全交付给Logic合约,数据与逻辑相分离;

在基础代理中,数据的解释权是完全由Proxy来决定的,Logic中的成员变量必须与Proxy成员变量storage layout保持一致。非结构化代理模式选择将数据的解释权交给Logic合约,由Logic合约来决定如何进行成员变量的storage layout。想办法让Proxy自身的逻辑不参与成员变量的堆叠,也就不必在logic中出现占位符变量;

Logic成为纯粹的业务逻辑处理合约,Proxy成为纯粹的逻辑执行控制合约。Proxy下派任务,Logic执行任务。 Proxy使用空白存储,那使用delegatecall时,就可以随意操作存储,比如Logic合约count占slot0,那在使用delegatecall时,就会反过来操作Proxy的slot0,因为Proxy的存储布局是空白的,所以并不会出现布局冲突。

通过代码理解

// 逻辑合约
contract Logic {
    uint public count;

    function inc() external {
        count += 1;
    }
}

// 非结构化代理合约  slot:存储槽 专栏存储布局中有提到
contract UnstructuredProxy {
    // 定义逻辑合约地址的存储位置,注意这里使用的是constant;constant变量并不参与成员变量的堆叠,
    //或者说并不会占用slot 存储槽。
    //简单解释一下这里为什么要设置这个变量;
    //在基础代理模式中,Proxy会声明一个logicAddress的成员变量保存,所以会占用存储布局,那么Logic合约就会出现占位符 logicAddress,以保证存储布局一致;
    //我们在这设置一个hash作为setLogic函数中汇编sstore(position, newLogic),将这个地址存到slot hash中,所以这里的logicPosition要足够远,确保不会参与堆叠
    bytes32 private constant logicPosition = keccak256("org.zeppelinos.proxy.implementation"); 

    // 升级到新的逻辑合约地址
    function upgradeTo(address newLogic) public {   
        setLogic(newLogic); 
    } 

    // 获取当前的逻辑合约地址
    function logic() public view returns(address impl) {  
        bytes32 position = logicPosition;   
        assembly {
            impl := sload(position) //读取slot position中的数据
        } 
    } 

    // 内部函数设置新的逻辑合约地址
    function setLogic(address newLogic) internal {   
        bytes32 position = logicPosition;   
        assembly {
            sstore(position, newLogic) //将newLogic 地址 存储到position位置
        } 
    } 

    // 使用内联汇编委托调用
    function _delegate(address _logic) internal virtual {
        assembly {
            //这里简单解释,不能保证正确;
            //calldatacopy是针对calldata的函数,他会直接找到calldata,根据参数决定copy多长的字节
            //calldatasize()针对calldata,取到calldata的size  字节大小
            //(t, f, calldatasize())
            //t是合约的内存空间起点,f是调用数据(`calldata`)的起点。
            //注意这里是memory内存,所以并不会存在storage里,只存活于函数指定期间
            calldatacopy(0, 0, calldatasize())

            //对_logic合约地址使用delegatecall
            let result := delegatecall(gas(), _logic, 0, calldatasize(), 0, 0)
            
            //这里就跟上边的差不多了 returndatacopy  返回结果result 
            returndatacopy(0, 0, returndatasize())
            
            //check result
            switch result
            
            /
            case 0 {
            // 0 就直接回滚
                revert(0, returndatasize())
            }
            default {
            // 默认返回  返回数据给外部调用者 
                return(0, returndatasize())
            }
        }
    }

    // 当调用不匹配任何函数时触发
    fallback() external {
        _delegate(logic());
    }
}