代理合约

代理的核心逻辑contractProxy{addresspublicimplement;constructor(addressimpl){implement=impl;}fallback()externalpayab

1、为什么需要代理模式

智能合约一旦部署到网络上就无法更改, 如果代码中出现某些bug 无法直接修改。只能重新部署 这样合约地址就会变,里面存储的状态变量也会丢失。所以需要一种将状态存储与业务逻辑分开的方法。

2、代理模式为什么有这么多?

主要是为了解决以下几个问题, 引申出不同的变种形式。

  • 代理合约与逻辑合约状态变量覆盖问题
  • 代理合约与逻辑合约函数选择器冲突问题
  • gas费用问题
  • 多个逻辑合约如何避免变量覆盖问题

3、代理的核心逻辑实现

contract Proxy {
    // 逻辑合约地址
    address public implement;

    constructor(address impl){
        implement = impl;
    }
    // 当没有匹配的函数选择器时默认执行该函数
    fallback() external payable {
        implementation.delegatecall(msg.data);
    }
}

当在和合约中没有找到必配的函数选择器时, 会执行fallback函数. 在fallback中通过实际的逻辑合约地址调用delegatecall,执行上下文是Proxy中的. 如果在逻辑合约中执行存储、读取数据时, 实际的地址都是在Proxy中查找的。

4、透明代理

透明代理模式, 更新逻辑合约地址的方法在代理合约中, 为了防止"代理合约与逻辑合约函数选择器冲突问题"问题, 在代理合约的每个方法都要添加admin校验(除了fallback) 这就会导致gas费用增加。函数选择器冲突具体可参见这里https://learnblockchain.cn/article/5588

为了解决"代理合约与逻辑合约状态变量覆盖问题"使用ERC1967, 将代理、admin地址存储到固定的slot中。避免和逻辑合约中的变量冲突。 在文档中描述建议使用ProxyAdmin做为代理合约的admin地址, 在更新逻辑合约地址时使用:

ProxyAdmin.upgrade(Proxy, newLogicAddress)

image.png

相关使用方式

        const [owner, otherAccount] = await ethers.getSigners();

        // 部署逻辑合约
        const Logic1 = await ethers.getContractFactory("Logic1");
        const logic1 = await Logic1.deploy();

        // 部署代理合约并设置逻辑合约地址、admin管理地址
        const TUProxy = await ethers.getContractFactory("TransparentProxy");
        const tuProxy = await TUProxy.deploy(logic1.address, owner.getAddress(), "0x");

        // 升级逻辑合约地址
        await tuProxy.upgradeTo(newLogic.address);

5、beacon模式代理

考虑一种情况, 逻辑合约部署一个 有个多个代理指向该逻辑合约。如下图所示。 image.png 当想要更新逻辑合约地址时需要每个Proxy都执一次upgrade很麻烦。所以在上面这个图中引入UpgradeableBeacon合约他的作用是存储逻辑合约地址 共给BeaconProxy代理合约读取真正的逻辑合约地址。所以在更新逻辑合约地址时 执行UpgradeableBeacon.upgradeTo()就可以了 使用方式如下:

const [owner, otherAccount] = await ethers.getSigners();

const Logic = await ethers.getContractFactory("Logic1");
const logic = await Logic.deploy();

// beacon合约地址
const MyUpgradeableBeacon = await ethers.getContractFactory("MyUpgradeableBeacon");
const upgradeableBeacon = await MyUpgradeableBeacon.deploy(logic.address);

// 代理合约添加beacon合约地址
const MyBeaconProxy = await ethers.getContractFactory("MyBeaconProxy");
const beaconProxy1 = await MyBeaconProxy.deploy(upgradeableBeacon.address, "0x");
const beaconProxy2 = await MyBeaconProxy.deploy(upgradeableBeacon.address, "0x");

// 更新逻辑合约地址
await upgradeableBeacon.upgradeTo(newLogic.address);

6、UUPS代理

与透明代理不同, UUPS代理模式 更新逻辑合约地址的函数是在逻辑合约中。所以逻辑合约要继承UPPSUpgradeable。他的好处是在proxy中不需要每次都判断是否是admin 节省了gas费用, 同时很好的解决了函数选择器冲突问题。 image.png

使用方式如下:

const [owner, otherAccount] = await ethers.getSigners();

// 创建逻辑合约
const Logic3 = await ethers.getContractFactory("Logic3");
const logic3 = await Logic3.deploy();

// 创建代理合约
const uupsProxyFactory = await ethers.getContractFactory("UUPSProxy");
const uupsProxy = await uupsProxyFactory.deploy(logic3.address, "0x");

// 更新逻辑合约地址(这里的upgradeTo是逻辑合约中的函数)
const proxyMigration = Logic3.attach(uupsProxy.address);
await proxyMigration.upgradeTo(newLogic.address);
点赞 1
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
打野工程师
打野工程师
江湖只有他的大名,没有他的介绍。