代理的核心逻辑contractProxy{addresspublicimplement;constructor(addressimpl){implement=impl;}fallback()externalpayab
智能合约一旦部署到网络上就无法更改, 如果代码中出现某些bug 无法直接修改。只能重新部署 这样合约地址就会变,里面存储的状态变量也会丢失。所以需要一种将状态存储与业务逻辑分开的方法。
主要是为了解决以下几个问题, 引申出不同的变种形式。
contract Proxy {
// 逻辑合约地址
address public implement;
constructor(address impl){
implement = impl;
}
// 当没有匹配的函数选择器时默认执行该函数
fallback() external payable {
implementation.delegatecall(msg.data);
}
}
当在和合约中没有找到必配的函数选择器时, 会执行fallback函数. 在fallback中通过实际的逻辑合约地址调用delegatecall,执行上下文是Proxy中的. 如果在逻辑合约中执行存储、读取数据时, 实际的地址都是在Proxy中查找的。
透明代理模式, 更新逻辑合约地址的方法在代理合约中, 为了防止"代理合约与逻辑合约函数选择器冲突问题"问题, 在代理合约的每个方法都要添加admin校验(除了fallback) 这就会导致gas费用增加。函数选择器冲突具体可参见这里https://learnblockchain.cn/article/5588
为了解决"代理合约与逻辑合约状态变量覆盖问题"使用ERC1967, 将代理、admin地址存储到固定的slot中。避免和逻辑合约中的变量冲突。 在文档中描述建议使用ProxyAdmin做为代理合约的admin地址, 在更新逻辑合约地址时使用:
ProxyAdmin.upgrade(Proxy, newLogicAddress)
相关使用方式
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);
考虑一种情况, 逻辑合约部署一个 有个多个代理指向该逻辑合约。如下图所示。 当想要更新逻辑合约地址时需要每个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);
与透明代理不同, UUPS代理模式 更新逻辑合约地址的函数是在逻辑合约中。所以逻辑合约要继承UPPSUpgradeable。他的好处是在proxy中不需要每次都判断是否是admin 节省了gas费用, 同时很好的解决了函数选择器冲突问题。
使用方式如下:
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);
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!