ERC1967Proxy库实现了一个基于ERC1967标准的代理合约。通过改变存储于ERC1967标准规定的_IMPLEMENTATION_SLOT号slot中的代理合约地址,代理合约便实现了合约逻辑可升级的功能。
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
ERC1967Proxy库实现了一个基于ERC1967标准的代理合约。通过改变存储于ERC1967标准规定的_IMPLEMENTATION_SLOT号slot中的代理合约地址,代理合约便实现了合约逻辑可升级的功能。
ERC1967详情参见:https://eips.ethereum.org/EIPS/eip-1967
继承ERC1967Proxy合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract MockERC1967Proxy is ERC1967Proxy {
constructor(address logic, bytes memory data)
ERC1967Proxy(logic, data)
{}
function getImplementation() external view returns (address){
return _getImplementation();
}
}
全部foundry测试合约:
测试使用的物料合约:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
interface IImplement {
event Initialize(address);
event Receive();
event Fallback(bytes);
}
contract Implement is IImplement {
uint public i;
address public addr;
function initialize(uint i_, address addr_) external {
i = i_;
addr = addr_;
emit Initialize(msg.sender);
}
receive() external payable {
emit Receive();
}
fallback() external {
emit Fallback(msg.data);
}
}
代码实现上,ERC1967Proxy库的代理功能继承了于Proxy库,而基于ERC1967标准的逻辑合约地址存储继承于ERC1967Upgrade库。
Proxy库详解参见:https://learnblockchain.cn/article/8469
ERC1967Upgrade库详解参见:https://learnblockchain.cn/article/8581
constructor(address _logic, bytes memory _data)
:初始设置proxy背后的逻辑合约地址,并以_data作为calldata delegatecall到该逻辑合约。该delegatecall即实现了proxy的constructor函数功能,可用于初始化proxy的一些storage;_implementation() internal
:返回当前proxy背后的逻辑和合约地址。 constructor(address _logic, bytes memory _data) payable {
// 调用ERC1967Upgrade的_upgradeToAndCall(),设置逻辑合约地址_logic。
// 如果_data不为空,再执行一个额外的到新逻辑合约的delegatecall(以_data为calldata)
_upgradeToAndCall(_logic, _data, false);
}
function _implementation() internal view virtual override returns (address impl) {
// 调用ERC1967Upgrade的_getImplementation(),返回逻辑合约地址
return ERC1967Upgrade._getImplementation();
}
foundry代码验证:
contract ERC1967ProxyTest is Test, IERC1967, IImplement {
MockERC1967Proxy private _testing;
Implement private _implement = new Implement();
function test_ConstructorAndImplementation() external {
// test for {constructor}
// build the calldata of {initialize} in the implementation
uint argUint = 1024;
address argAddress = address(1024);
bytes memory data = abi.encodeCall(
_implement.initialize,
(argUint, argAddress)
);
vm.expectEmit();
emit IERC1967.Upgraded(address(_implement));
emit IImplement.Initialize(address(this));
_testing = new MockERC1967Proxy(
address(_implement),
data
);
address proxyAddress = address(_testing);
// test for {getImplementation}
assertEq(_testing.getImplementation(), address(_implement));
// test for the result of the extra delegatecall in constructor
Implement proxy = Implement(payable(proxyAddress));
assertEq(proxy.i(), argUint);
assertEq(proxy.addr(), argAddress);
// test for other delegatecall
argUint = 1;
argAddress = address(1);
proxy.initialize(argUint, argAddress);
assertEq(proxy.i(), argUint);
assertEq(proxy.addr(), argAddress);
// delegatecall without calldata
vm.expectEmit(proxyAddress);
emit IImplement.Receive();
(bool ok,) = proxyAddress.call("");
assertTrue(ok);
// delegatecall with calldata of unknown function selector
data = "known";
vm.expectEmit(proxyAddress);
emit IImplement.Fallback(data);
(ok,) = proxyAddress.call(data);
assertTrue(ok);
}
}
ps: 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!