合约的代理与升级
合约主要有3种代理模式:
一、透明代理Transparent
contract TransparentAdminUpgradeableProxy { address implementation; address admin; fallback() external payable { require(msg.sender != admin); implementation.delegatecall.value(msg.value)(msg.data); } function upgrade(address newImplementation) external { if (msg.sender != admin) fallback(); implementation = newImplementation; } }
这种模式直观好理解,代理合约负责升级,实现合约负责具体逻辑。
二、UUPS
// 代理合约
contract UUPSProxy { address implementation; fallback() external payable { implementation.delegatecall.value(msg.value)(msg.data); } }
// 实现合约里定义implementation属性(因为是delegatecall,所以运行时是代理合约的上下文slot)指向实现合约地址 abstract contract UUPSProxiable { address implementation; address admin;
// 升级方法修改implementation属性, 也就修改了运行时代理合约指向实现合约地址了
function upgrade(address newImplementation) external {
require(msg.sender == admin); implementation = newImplementation;
}
}
代理合约是空的,几乎不包含任何逻辑;实现合约既要有具体业务逻辑,还要有升级逻辑。
升级时,调用代理合约的upgrade方法, 会deletegatecall到实现合约的upgrade方法; 因为delegate实现合约会运行在代理合约的上下文中,所以实现合约的upgrade方法修改implementation属性,实际上就修改了代理合约的implementation属性。
三、钻石模式
1个代理合约,会有多份实现合约
透明代理 | UUPS | 钻石模式 | |
优点 | 容易理解 | gas费用低 | |
缺点 |
运行gas费高,每次函数调用,都要从存储中找admin 代理部署gas费高, |
新代理合约必须记得实现upgrade,否则再也无法升级 | |
openzepplin实现方式 | contract MERC1967Proxy is Proxy, ERC1967Upgrade |
参考:https://blog.openzeppelin.com/the-state-of-smart-contract-upgrades
可升级合约不同之处
不要使用constructor,而是定义initialize函数。 因为实现合约不会直接与用户交互,都是通过代理合约用delegatecall把调用转发到实现合约里。所以实例化属性应该实例代理合约的,而不是实现合约自己的。
constructor完成的事情,就是实例化实现合约自己的属性值。 这个后续就毫无意义,纯粹是浪费gas。