EIP-1167,又称Minimal Proxy Contract
,提供了一种低成本复制合约的方法,也可以叫作是克隆合约的方法。
EIP-1167,又称
Minimal Proxy Contract
,提供了一种低成本复制合约的方法,也可以叫作是克隆合约的方法。如何理解克隆呢?克隆就是类似复制的意思,这里的合约克隆是指:克隆合约和原合约具有相同的逻辑功能。而且创建克隆合约的成本比直接部署原合约低,部署克隆合约的前提是得有一个原件。
一说到代理,首先就会想到代理合约,合约升级。但是ERC1167
不是合约升级,它只是负责合约的调用转发。
可升级合约的代理合约架构:
整个架构中存在一个代理合约和多个逻辑合约,只有一套数据(即代理合约的数据),需要升级时则替换掉代理合约中的逻辑合约,而且同一时间只能存在一个逻辑合约。
Minimal Proxy Contract
合约架构:
整个架构中存在多个代理合约和一个逻辑合约,有多套数据分别存储在不同的代理合约中,所有代理合约共享逻辑合约的执行逻辑,同一时间存在多个代理合约。Minimal Proxy Contract
的原理就是将代理合约作为逻辑合约的复制品,各个代理合约存储各自的数据,需要多少份复制品就创建多少个代理合约。而代理合约本身只负责请求转发,因此其内容很少,从而耗费的gas就更少。
从官方文档上可以看到这串字节码: 363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
,经过反编译之后:
0x0: CALLDATASIZE
0x1: RETURNDATASIZE
0x2: RETURNDATASIZE
0x3: CALLDATACOPY
0x4: RETURNDATASIZE
0x5: RETURNDATASIZE
0x6: RETURNDATASIZE
0x7: CALLDATASIZE
0x8: RETURNDATASIZE
0x9: PUSH20 0xbebebebebebebebebebebebebebebebebebebebe
0x1e: GAS
0x1f: DELEGATECALL
0x20: RETURNDATASIZE
0x21: DUP3
0x22: DUP1
0x23: RETURNDATACOPY
0x24: SWAP1
0x25: RETURNDATASIZE
0x26: SWAP2
0x27: PUSH1 0x2b
0x29: JUMPI
0x2a: REVERT
0x2b: JUMPDEST
0x2c: RETURN
这串字节码的执行的逻辑就是对 0xbebebebebebebebebebebebebebebebebebebebe
地址执行delegatecall
,如果调用失败则revert
,如果调用成功则返回代理调用返回的结果。
可以自己用汇编语言写出来,returndata
的部分可能不太对,但是大体逻辑是这样的。
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
要如何实现克隆功能,可以参考openzeppelin
官方的代码。
function clone(address implementation, uint256 value) internal returns (address instance) {
if (address(this).balance < value) {
revert Errors.InsufficientBalance(address(this).balance, value);
}
/// @solidity memory-safe-assembly
assembly {
// Stores the bytecode after address
mstore(0x20, 0x5af43d82803e903d91602b57fd5bf3)
// implementation address
mstore(0x11, implementation)
// Packs the first 3 bytes of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0x88, implementation), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
instance := create(value, 0x09, 0x37)
}
if (instance == address(0)) {
revert Errors.FailedDeployment();
}
}
直接看到汇编部分,三个mstore
操作码的作用是拼接克隆合约的createionCode
,拼接的结果:
0x3d602d80600a3d3981f3363d3d373d3d3d363d73 + implementation + 5af43d82803e903d91602b57fd5bf3
然后通过create
操作码部署克隆合约。
演示:
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.22;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/Clones.sol";
contract Demo {
uint256 public num;
fallback() external payable {
++num;
}
}
contract Test {
uint256 public num;
event result(uint256);
function call(address cloner) public {
cloner.call("aaa"); // call fallback()
(, bytes memory _result) = cloner.call(abi.encodeWithSignature("num()"));
emit result(abi.decode(_result, (uint256)));
}
}
contract CloneLib {
using Clones for address;
function clone(address implementation) public returns (address cloner){
cloner = implementation.clone();
}
}
Demo
合约CloneLib
合约,并调用clone
函数,并传入Demo
合约地址Test
合约,并调用call
函数,传入克隆地址结果:
可以看到结果返回1
,说明克隆成功。
同理,知道了克隆的逻辑,可以用另一种方式复现:
contract Clone {
uint256 public num;
event a(bytes);
constructor(address implementation) {
bytes memory head = hex"363d3d373d3d3d363d73";
bytes memory tail = hex"5af43d82803e903d91602b57fd5bf3";
bytes memory runtimeCode = abi.encodePacked(head, implementation, tail);
emit a(runtimeCode);
assembly {
return(add(runtimeCode, 0x20), mload(runtimeCode))
}
}
}
solidity的智能合约执行的逻辑都是通过runtimeCode
,而只要将合约runtimeCode
部分的内容按克隆合约的逻辑编写,即照样也可以完成相同的要求。
执行操作相同,执行结果为:
通过部署原合约和部署克隆合约所需的gas费的多少来判断
部署Demo
所需的gas费用为:"122325"
部署克隆合约所需的gas费用为:"63334"
可以看到几乎是节省了一倍的花销。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!