本文主要是介绍Michael.W基于Foundry精读Openzeppelin第59期——Proxy.sol,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Michael.W基于Foundry精读Openzeppelin第59期——Proxy.sol
- 0. 版本
- 0.1 Proxy.sol
- 1. 目标合约
- 2. 代码精读
- 2.1 _delegate(address implementation) internal
- 2.2 _implementation() internal && _beforeFallback() internal
- 2.3 fallback() && receive()
0. 版本
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
0.1 Proxy.sol
Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/proxy/Proxy.sol
Proxy库对外只暴露了fallback和receive函数,是代理合约的基础实现。所有对Proxy合约的call都将被delegatecall到implement合约并且delegatecall的执行结果会原封不动地返还给Proxy合约的调用方。我们通常称implement合约为代理合约背后的逻辑合约。
1. 目标合约
继承Proxy合约:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/proxy/MockProxy.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;import "openzeppelin-contracts/contracts/proxy/Proxy.sol";contract MockProxy is Proxy {address immutable private _IMPLEMENTATION_ADDR;bool immutable private _ENABLE_BEFORE_FALLBACK;event ProxyBeforeFallback(uint value);constructor(address implementationAddress,bool enableBeforeFallback){_IMPLEMENTATION_ADDR = implementationAddress;_ENABLE_BEFORE_FALLBACK = enableBeforeFallback;}function _implementation() internal view override returns (address){return _IMPLEMENTATION_ADDR;}function _beforeFallback() internal override {if (_ENABLE_BEFORE_FALLBACK) {emit ProxyBeforeFallback(msg.value);}}
}
全部foundry测试合约:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/proxy/Proxy/Proxy.t.sol
测试使用的物料合约:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/proxy/Proxy/Implement.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;contract Implement {uint public i;address public addr;uint[3] public fixedArray;uint[] public dynamicArray;mapping(uint => uint) public map;event ImplementReceive(uint value);event ImplementFallback(uint value);function setUint(uint target) external {i = target;}function setUintPayable(uint target) external payable {i = target;}function setAddress(address target) external {addr = target;}function setAddressPayable(address target) external payable {addr = target;}function setFixedArray(uint[3] memory target) external {fixedArray = target;}function setFixedArrayPayable(uint[3] memory target) external payable {fixedArray = target;}function setDynamicArray(uint[] memory target) external {dynamicArray = target;}function setDynamicArrayPayable(uint[] memory target) external payable {dynamicArray = target;}function setMapping(uint key, uint value) external {map[key] = value;}function setMappingPayable(uint key, uint value) external payable {map[key] = value;}function triggerRevert() external pure {revert("Implement: revert");}function triggerRevertPayable() external payable {revert("Implement: revert");}function getPure() external pure returns (string memory){return "pure return value";}receive() external payable {emit ImplementReceive(msg.value);}fallback() external payable {emit ImplementFallback(msg.value);}
}
2. 代码精读
2.1 _delegate(address implementation) internal
将当前的call,委托调用到implementation地址。
注:通过内联汇编“黑魔法”,使得没有返回值的_delegate()函数可以动态返回delegatecall的返回值。
function _delegate(address implementation) internal virtual {// 内联汇编assembly {// 从当前calldata的position 0开始将全部calldata都复制到内存中。内存中的数据存储也是从位置0开始。// 为何此处使用内存的起始position不是从0x40处取空闲内存指针?原因见后文。calldatacopy(0, 0, calldatasize())// 使用delegatecall去调用逻辑合约。// 第一个参数:调用delegatecall的过程允许使用的gas上限。为gas(),即执行到此处剩余可用的全部gas;// 第二个参数:逻辑合约的地址;// 第三个参数:delegatecall所携带的calldata相关。calldata是从当前内存中获取,第三个参数为开始载入的内存position;// 第四个参数:delegatecall所携带的calldata相关。第四个参数为从内存中读取calldata的字节长度;// 综上可知,delegatecall所用的calldata就是进入_delegate(address implementation)时的calldata;// 第五个参数:delegatecall得到的返回数据存储在内存中,第五个参数为开始存储返回值的内存position;// 第六个参数:delegatecall得到的返回数据存储在内存中的字节长度。// 注:由于第五和第六个参数都设为0,即用来存储返回数据的内存长度为0。很明显delegatecall的返回数据长度(如有)要大于设定的存储空间,// 此时,全部的返回数据都要用returndatacopy()来复制到内存中。具体细则详见:https://learnblockchain.cn/article/6309let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)// 由于delegatecall()时设定的存储返回数据的空间为0,要用returndatacopy()和returndatasize()来获取全部的返回数据。// 第一个参数:内存中存储返回数据的起始position,即从position 0处开始存储;// 第二个参数:返回数据被复制的起始position,即从头开始复制返回数据;// 第三个参数:复制返回数据的字节长度。returndatasize()表示未存储到delegatecall()时设定的存储空间的返回数据字节长度,此时该// 值应该为全部返回数据字节长度。// 综上可知,delegatecall()得到的全部返回数据都存储到从0开始的内存空间中returndatacopy(0, 0, returndatasize())// 判断delegatecall是否成功调用switch resultcase 0 {// 如果delegatecall调用失败(例如gas不足),result为0// 那么就直接revert,revert携带的数据为内存中存储的delegatecall的全部返回数据revert(0, returndatasize())}default {// 如果非0(即1),表示delegatecall调用成功// 那么就进行函数返回,返回值为内存中存储的delegatecall的全部返回数据return(0, returndatasize())}}}
为何_delegate()
中使用内存的起始position不是从0x40处取空闲内存指针,而是直接从position 0开始?
答:因为在该内联汇编代码块结束时直接进行函数返回,不会再有回到solidity代码逻辑的地方。全部内存都只供汇编代码块使用。只要在内联汇编中手动管理好内存指针,内存就是安全的。
2.2 _implementation() internal && _beforeFallback() internal
_implementation()
:返回逻辑合约的地址。该函数未带实现体,需要在主合约中进行重写;_beforeFallback()
:执行delegatecall之前会执行的hook函数,如果有需要可以重写该函数并在其中增添逻辑。
function _implementation() internal view virtual returns (address);function _beforeFallback() internal virtual {}
foundry代码验证:
contract ProxyTest is Test {Implement private _implement = new Implement();address payable private _testingAddress = payable(address(new MockProxy(address(_implement), false)));event ImplementFallback(uint value);event ImplementReceive(uint value);event ProxyBeforeFallback(uint value);function test_beforeFallback() external {_testingAddress = payable(address(new MockProxy(address(_implement), true)));Implement proxy = Implement(_testingAddress);uint proxyBalance = _testingAddress.balance;assertEq(proxyBalance, 0);uint ethValue = 1 wei;// case 1: test setUint()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(0);proxy.setUint(1024);// case 2:test setUintPayable()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(ethValue);proxy.setUintPayable{value: ethValue}(1024);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// case 3: test setAddress()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(0);proxy.setAddress(address(1));// case 4: test setAddressPayable()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(ethValue);proxy.setAddressPayable{value: ethValue}(address(1));assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// case 5: test setFixedArray()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(0);uint[3] memory targetFixedArray = [uint(1024), 2048, 4096];proxy.setFixedArray(targetFixedArray);// case 6: test setFixedArrayPayable()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(ethValue);proxy.setFixedArrayPayable{value: ethValue}(targetFixedArray);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// case 7: test setDynamicArray()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(0);// build dynamic array as inputuint[] memory targetDynamicArray = new uint[](3);targetDynamicArray[0] = 1024;targetDynamicArray[1] = 2048;targetDynamicArray[2] = 4096;proxy.setDynamicArray(targetDynamicArray);// case 8: test setDynamicArrayPayable()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(ethValue);proxy.setDynamicArrayPayable{value: ethValue}(targetDynamicArray);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// case 9: test setMapping()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(0);proxy.setMapping(1024, 2048);// case 10: test setMapping()vm.expectEmit(_testingAddress);emit ProxyBeforeFallback(ethValue);proxy.setMappingPayable{value: ethValue}(1024, 2048);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// case 11: revert with any static call because it emits event in _beforeFallback()// and causes the evm error: "StateChangeDuringStaticCall"vm.expectRevert();proxy.i();vm.expectRevert();proxy.addr();vm.expectRevert();proxy.fixedArray(0);vm.expectRevert();proxy.dynamicArray(0);vm.expectRevert();proxy.map(1024);vm.expectRevert();proxy.triggerRevert();vm.expectRevert();proxy.getPure();// case 12: revert in the function of implement during a callvm.expectRevert("Implement: revert");proxy.triggerRevertPayable{value: ethValue}();// case 13: call the function not exists in the implement// and delegate call to the fallback function of implement// without valuevm.expectEmit(_testingAddress);emit ProxyBeforeFallback(0);emit ImplementFallback(0);bytes memory calldata_ = abi.encodeWithSignature("unknown()");(bool ok,) = _testingAddress.call(calldata_);assertTrue(ok);// with valuevm.expectEmit(_testingAddress);emit ProxyBeforeFallback(ethValue);emit ImplementFallback(ethValue);(ok,) = _testingAddress.call{value: ethValue}(calldata_);assertTrue(ok);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// case 14: call the proxy with empty call data// and delegate call to the receive function of implement// without valuevm.expectEmit(_testingAddress);emit ProxyBeforeFallback(0);emit ImplementReceive(0);(ok,) = _testingAddress.call("");assertTrue(ok);// with valuevm.expectEmit(_testingAddress);emit ProxyBeforeFallback(ethValue);emit ImplementReceive(ethValue);(ok,) = _testingAddress.call{value: ethValue}("");assertTrue(ok);assertEq(_testingAddress.balance, proxyBalance + ethValue);}
}
2.3 fallback() && receive()
fallback()
:当本合约被携带calldata的call调用时,进入该函数。随即将该call的calldata直接delegatecall到逻辑合约;receive()
:当本合约被不携带任何calldata的call调用时,进入该函数。随即直接delegatecall到逻辑合约(不携带任何calldata)。
fallback() external payable virtual {// 调用_fallback()_fallback();}receive() external payable virtual {// 调用_fallback()_fallback();}// 携带当前对本合约的call的calldata,delegatecall到逻辑合约function _fallback() internal virtual {// delegatecall之前运行hook函数_beforeFallback();// 携带当前对本合约的call的calldata,delegatecall到逻辑合约_delegate(_implementation());}
foundry代码验证:
contract ProxyTest is Test {Implement private _implement = new Implement();address payable private _testingAddress = payable(address(new MockProxy(address(_implement), false)));event ImplementFallback(uint value);event ImplementReceive(uint value);function test_Call() external {Implement proxy = Implement(_testingAddress);// case 1: set uint256assertEq(proxy.i(), 0);assertEq(_implement.i(), 0);proxy.setUint(1024);// check storage by static callassertEq(proxy.i(), 1024);assertEq(_implement.i(), 0);// check storage by slot numberbytes32 slotNumber = bytes32(uint(0));assertEq(vm.load(_testingAddress, slotNumber), bytes32(uint(1024)));// case 2: set addressassertEq(proxy.addr(), address(0));assertEq(_implement.addr(), address(0));proxy.setAddress(address(2048));// check storage by static callassertEq(proxy.addr(), address(2048));assertEq(_implement.addr(), address(0));// check storage by slot numberslotNumber = bytes32(uint(1));assertEq(vm.load(_testingAddress, slotNumber), bytes32(uint(2048)));// case 3: set fixed arrayassertEq(proxy.fixedArray(0), 0);assertEq(_implement.fixedArray(0), 0);uint[3] memory targetFixedArray = [uint(1024), 2048, 4096];proxy.setFixedArray(targetFixedArray);for (uint i; i < 3; ++i) {// check storage by static callassertEq(proxy.fixedArray(i), targetFixedArray[i]);assertEq(_implement.fixedArray(i), 0);// check storage by slot numberslotNumber = bytes32(uint(2 + i));assertEq(vm.load(_testingAddress, slotNumber), bytes32(targetFixedArray[i]));}// case 4: set dynamic array// revert during static call because dynamic array isn't initializedvm.expectRevert();proxy.dynamicArray(0);vm.expectRevert();_implement.dynamicArray(0);// build dynamic array as inputuint[] memory targetDynamicArray = new uint[](3);targetDynamicArray[0] = 1024;targetDynamicArray[1] = 2048;targetDynamicArray[2] = 4096;proxy.setDynamicArray(targetDynamicArray);for (uint i; i < 3; ++i) {// check storage by static callassertEq(proxy.dynamicArray(i), targetDynamicArray[i]);vm.expectRevert();assertEq(_implement.dynamicArray(i), 0);// check storage by slot numberslotNumber = bytes32(uint(keccak256(abi.encodePacked(uint(5)))) + i);assertEq(vm.load(_testingAddress, slotNumber), bytes32(targetDynamicArray[i]));}// case 5: set mappinguint key = 1024;uint value = 2048;assertEq(proxy.map(key), 0);assertEq(_implement.map(key), 0);proxy.setMapping(key, value);// check storage by static callassertEq(proxy.map(key), value);assertEq(_implement.map(key), 0);// check storage by slot numberslotNumber = bytes32(uint(keccak256(abi.encodePacked(key, uint(6)))));assertEq(vm.load(_testingAddress, slotNumber), bytes32(value));// case 6: revert with msgvm.expectRevert("Implement: revert");proxy.triggerRevert();// case 7: call pure (staticcall)assertEq(proxy.getPure(), "pure return value");// case 8: call the function not exists in the implement// and delegate call to the fallback function of implementvm.expectEmit(_testingAddress);emit ImplementFallback(0);bytes memory calldata_ = abi.encodeWithSignature("unknown()");(bool ok,) = _testingAddress.call(calldata_);assertTrue(ok);// case 9: call without value and calldata// and delegate call to the receive function of implementvm.expectEmit(_testingAddress);emit ImplementReceive(0);(ok,) = _testingAddress.call("");assertTrue(ok);}function test_PayableCall() external {Implement proxy = Implement(_testingAddress);uint proxyBalance = _testingAddress.balance;assertEq(proxyBalance, 0);// case 1: set uint256 payableassertEq(proxy.i(), 0);assertEq(_implement.i(), 0);uint ethValue = 1 wei;proxy.setUintPayable{value: ethValue}(1024);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// check storage by static callassertEq(proxy.i(), 1024);assertEq(_implement.i(), 0);// check storage by slot numberbytes32 slotNumber = bytes32(uint(0));assertEq(vm.load(_testingAddress, slotNumber), bytes32(uint(1024)));// case 2: set address paybleassertEq(proxy.addr(), address(0));assertEq(_implement.addr(), address(0));proxy.setAddressPayable{value: ethValue}(address(2048));assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// check storage by static callassertEq(proxy.addr(), address(2048));assertEq(_implement.addr(), address(0));// check storage by slot numberslotNumber = bytes32(uint(1));assertEq(vm.load(_testingAddress, slotNumber), bytes32(uint(2048)));// case 3: set fixed array payableassertEq(proxy.fixedArray(0), 0);assertEq(_implement.fixedArray(0), 0);uint[3] memory targetFixedArray = [uint(1024), 2048, 4096];proxy.setFixedArrayPayable{value: ethValue}(targetFixedArray);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;for (uint i; i < 3; ++i) {// check storage by static callassertEq(proxy.fixedArray(i), targetFixedArray[i]);assertEq(_implement.fixedArray(i), 0);// check storage by slot numberslotNumber = bytes32(uint(2 + i));assertEq(vm.load(_testingAddress, slotNumber), bytes32(targetFixedArray[i]));}// case 4: set dynamic array payable// revert during static call because dynamic array isn't initializedvm.expectRevert();proxy.dynamicArray(0);vm.expectRevert();_implement.dynamicArray(0);// build dynamic array as inputuint[] memory targetDynamicArray = new uint[](3);targetDynamicArray[0] = 1024;targetDynamicArray[1] = 2048;targetDynamicArray[2] = 4096;proxy.setDynamicArrayPayable{value: ethValue}(targetDynamicArray);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;for (uint i; i < 3; ++i) {// check storage by static callassertEq(proxy.dynamicArray(i), targetDynamicArray[i]);vm.expectRevert();assertEq(_implement.dynamicArray(i), 0);// check storage by slot numberslotNumber = bytes32(uint(keccak256(abi.encodePacked(uint(5)))) + i);assertEq(vm.load(_testingAddress, slotNumber), bytes32(targetDynamicArray[i]));}// case 5: set mapping payableuint key = 1024;uint value = 2048;assertEq(proxy.map(key), 0);assertEq(_implement.map(key), 0);proxy.setMappingPayable{value: ethValue}(key, value);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// check storage by static callassertEq(proxy.map(key), value);assertEq(_implement.map(key), 0);// check storage by slot numberslotNumber = bytes32(uint(keccak256(abi.encodePacked(key, uint(6)))));assertEq(vm.load(_testingAddress, slotNumber), bytes32(value));// case 6: revert with msg payablevm.expectRevert("Implement: revert");proxy.triggerRevertPayable{value: ethValue}();// case 7: call the function not exists in the implement with value// and delegate call to the fallback function of implementvm.expectEmit(_testingAddress);emit ImplementFallback(ethValue);bytes memory calldata_ = abi.encodeWithSignature("unknown()");(bool ok,) = _testingAddress.call{value: ethValue}(calldata_);assertTrue(ok);assertEq(_testingAddress.balance, proxyBalance + ethValue);proxyBalance += ethValue;// case 8: call with value and empty callata// and delegate call to the receive function of implementvm.expectEmit(_testingAddress);emit ImplementReceive(ethValue);(ok,) = _testingAddress.call{value: ethValue}("");assertTrue(ok);assertEq(_testingAddress.balance, proxyBalance + ethValue);}
}
ps:
本人热爱图灵,热爱中本聪,热爱V神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人
这篇关于Michael.W基于Foundry精读Openzeppelin第59期——Proxy.sol的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!