# 合约代理模式

By [cyptoJune](https://paragraph.com/@cyptojune) · 2024-01-04

---

概述
--

合约部署在链上之后，代码是不可变的，这样做既有优点也是缺点

优点：安全，用户知道合约内容，会知道发生什么；（合约开源）

缺点：如果合约中有Bug，既不能修改也不能升级，只能部署新合约，但是新合约和旧合约的地址不一样，而且合约的数据也需要花费大量的gas进行迁移，成本相对较大！

那么有没有办法合约部署之后进行修改和升级呢，答案就是代理模式。

原理
--

代理模式将合约数据和逻辑分开，分别保存在不同的合约中。以下的图可以看出，数据（状态变量）保存在代理合约中，而逻辑函数保存在逻辑合约中。代理合约通过delegatecall，将函数调用全权委托给逻辑合约（Implementation）执行，再把最终的结果返回给调用者（caller)

![](https://storage.googleapis.com/papyrus_images/7fd84fda29af7180d0bc4f8985e9a48fdf84bd9723220863a10f9697fce3d479.png)

优势
--

1.  可升级：当我们需要升级合约的逻辑时，只需要将代理合约指向新的逻辑合约
    
2.  省gas: 如果多个合约复用一套逻辑，我们只需要部署一个逻辑合约，然后在部署多个只保存数据的代理合约，指向逻辑合约。
    

代码演示
----

    // SPDX-License-Identifier: GPL-3.0
    pragma solidity ^0.8.20;
    
    // 代理合约
    contract Proxy {
        address public implementation; // 逻辑合约地址
    
        /*
         @ 初始化逻辑合约地址
        */
        constructor(address implementation_) {
            implementation = implementation_;
        }
    
        fallback() external payable {
            address _implementation = implementation;
            assembly {
                // 将msg.data拷贝到内存里
                // calldatacopy操作码的参数: 内存起始位置，calldata起始位置，calldata长度
                calldatacopy(0, 0, calldatasize())
    
                // 利用delegatecall调用implementation合约
                // delegatecall操作码的参数：gas, 目标合约地址，input mem起始位置，input mem长度，output area mem起始位置，output area mem长度
                // output area起始位置和长度位置，所以设为0
                // delegatecall成功返回1，失败返回0
                let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
    
                // 将return data拷贝到内存
                // returndata操作码的参数：内存起始位置，returndata起始位置，returndata长度
                returndatacopy(0, 0, returndatasize())
    
                switch result
                // 如果delegate call失a败，revert
                case 0 {
                    revert(0, returndatasize())
                }
                // 如果delegate call成功，返回mem起始位置为0，长度为returndatasize()的数据（格式为bytes）
                default {
                    return(0, returndatasize())
                }
            }
        }
    }
    
    // 逻辑合约
    contract Logic {
        address public implementation; // 与Proxy保持一致，防止插槽冲突
        uint public x = 99; 
        event CallSuccess(); // 调用成功事件
    
        // 这个函数会释放CallSuccess事件并返回一个uint。
        // 函数selector: 0xd09de08a
        function increment() external returns(uint) {
            emit CallSuccess();
            return x + 1;
        }
    }
    
    contract Caller {
        // 代理合约的地址
        address public proxy;
    
        // 构造函数，在合约部署的时候初始化proxy变量
        constructor(address proxy_) {
            proxy = proxy_;
        }
    
        // 利用abi.encodeWithSignature()获取了increment()函数的selector
        function increment() external returns(uint) {
            ( , bytes memory data) = proxy.call(abi.encodeWithSignature("increment()"));
            return abi.decode(data,(uint));
        }
    }
    

*   代理合约Proxy： 使用了fallback函数，fallback()函数会在调用合约不存在的函数时被触发。可用于接收eth，也可以用于代理合约proxy contract. fallback()声明的时不需要function关键字，必须由exteral()函数。其中fallback用了内联汇编，其中操作码如下：
    
    *   `calldatacopy(t, f, s)`：将calldata（输入数据）从位置`f`开始复制`s`字节到mem（内存）的位置`t`。
        
    *   `delegatecall(g, a, in, insize, out, outsize)`：调用地址`a`的合约，输入为`mem[in..(in+insize))` ，输出为`mem[out..(out+outsize))`， 提供`g`wei的以太坊gas。这个操作码在错误时返回`0`，在成功时返回`1`。
        
    *   `returndatacopy(t, f, s)`：将returndata（输出数据）从位置`f`开始复制`s`字节到mem（内存）的位置`t`。
        
    *   `switch`：基础版`if/else`，不同的情况`case`返回不同值。可以有一个默认的`default`情况。
        
    *   `return(p, s)`：终止函数执行, 返回数据`mem[p..(p+s))`。
        
    *   `revert(p, s)`：终止函数执行, 回滚状态，返回数据`mem[p..(p+s))`。
        
*   逻辑合约Logic：increment()函数会被Proxy合约调用，释放CallSuccess事件，并返回一个uint.它的函数选择器是0xd09de08a。
    
*   调用者合约Caller: • `increase()`：利用`call`来调用代理合约的`increment()`函数，并返回一个`uint`。在调用时，我们利用`abi.encodeWithSignature()`获取了`increment()`函数的`selector`。在返回时，利用`abi.decode()`将返回值解码为`uint`类型。
    

总结
--

代理合约利用`delegatecall`将函数调用委托给了另一个逻辑合约，使得数据和逻辑分别由不同合约负责。并且，它利用内联汇编黑魔法，让没有返回值的回调函数也可以返回数据。

参考
--

*   WTF-Solidity 学院代理合约部分
    

[https://github.com/AmazingAng/WTF-Solidity/blob/main/46\_ProxyContract/readme.md](https://github.com/AmazingAng/WTF-Solidity/blob/main/46_ProxyContract/readme.md)

*   [openzeppelin proxy代理部分](https://docs.openzeppelin.com/contracts/3.x/api/proxy)

---

*Originally published on [cyptoJune](https://paragraph.com/@cyptojune/SCJ8ogxuerun7y5iPwuT)*
