Michael.W基于Foundry精读Openzeppelin

2024年08月13日更新 120 人订阅
专栏简介 Michael.W基于Foundry精读Openzeppelin第8期——Context.sol Michael.W基于Foundry精读Openzeppelin第1期——Address.sol Michael.W基于Foundry精读Openzeppelin第2期——StorageSlot.sol Michael.W基于Foundry精读Openzeppelin第3期——Arrays.sol Michael.W基于Foundry精读Openzeppelin第4期——Base64.sol Michael.W基于Foundry精读Openzeppelin第5期——Counters.sol Michael.W基于Foundry精读Openzeppelin第6期——Strings.sol Michael.W基于Foundry精读Openzeppelin第7期——Timers.sol Michael.W基于Foundry精读Openzeppelin第9期——Multicall.sol Michael.W基于Foundry精读Openzeppelin第10期——Create2.sol Michael.W基于Foundry精读Openzeppelin第11期——Math.sol Michael.W基于Foundry精读Openzeppelin第12期——SafeCast.sol Michael.W基于Foundry精读Openzeppelin第13期——Checkpoints.sol Michael.W基于Foundry精读Openzeppelin第14期——SafeMath.sol Michael.W基于Foundry精读Openzeppelin第15期——SignedMath.sol Michael.W基于Foundry精读Openzeppelin第16期——SignedSafeMath.sol Michael.W基于Foundry精读Openzeppelin第17期——BitMaps.sol Michael.W基于Foundry精读Openzeppelin第18期——DoubleEndedQueue.sol Michael.W基于Foundry精读Openzeppelin第19期——EnumerableSet.sol Michael.W基于Foundry精读Openzeppelin第20期——EnumerableMap.sol Michael.W基于Foundry精读Openzeppelin第21期——ERC165.sol (番外篇)Michael.W基于Foundry精读Openzeppelin第22期——内联汇编staticcall Michael.W基于Foundry精读Openzeppelin第23期——ERC165Checker.sol Michael.W基于Foundry精读Openzeppelin第24期——ERC165Storage.sol Michael.W基于Foundry精读Openzeppelin第25期——IERC1820Registry.sol Michael.W基于Foundry精读Openzeppelin第26期——ERC1820Implementer.sol Michael.W基于Foundry精读Openzeppelin第27期——Escrow.sol Michael.W基于Foundry精读Openzeppelin第28期——ConditionalEscrow.sol Michael.W基于Foundry精读Openzeppelin第29期——RefundEscrow.sol Michael.W基于Foundry精读Openzeppelin第30期——ECDSA.sol Michael.W基于Foundry精读Openzeppelin第31期——IERC1271.sol Michael.W基于Foundry精读Openzeppelin第32期——SignatureChecker.sol Michael.W基于Foundry精读Openzeppelin第33期——EIP712.sol Michael.W基于Foundry精读Openzeppelin第34期——MerkleProof.sol Michael.W基于Foundry精读Openzeppelin第35期——Ownable.sol Michael.W基于Foundry精读Openzeppelin第36期——Ownable2Step.sol Michael.W基于Foundry精读Openzeppelin第37期——AccessControl.sol Michael.W基于Foundry精读Openzeppelin第38期——AccessControlEnumerable.sol Michael.W基于Foundry精读Openzeppelin第39期——ERC20.sol Michael.W基于Foundry精读Openzeppelin第40期——ERC20Burnable.sol Michael.W基于Foundry精读Openzeppelin第41期——ERC20Capped.sol Michael.W基于Foundry精读Openzeppelin第42期——draft-ERC20Permit.sol Michael.W基于Foundry精读Openzeppelin第43期——Pausable.sol Michael.W基于Foundry精读Openzeppelin第44期——ERC20Pausable.sol Michael.W基于Foundry精读Openzeppelin第45期——ERC20FlashMint.sol Michael.W基于Foundry精读Openzeppelin第46期——ERC20Snapshot.sol Michael.W基于Foundry精读Openzeppelin第47期——SafeERC20.sol Michael.W基于Foundry精读Openzeppelin第48期——TokenTimelock.sol Michael.W基于Foundry精读Openzeppelin第49期——ERC20Wrapper.sol Michael.W基于Foundry精读Openzeppelin第50期——ERC20Votes.sol Michael.W基于Foundry精读Openzeppelin第51期——ERC20VotesComp.sol Michael.W基于Foundry精读Openzeppelin第52期——ERC4626.sol Michael.W基于Foundry精读Openzeppelin第53期——ERC20PresetFixedSupply.sol Michael.W基于Foundry精读Openzeppelin第54期——ERC20PresetMinterPauser.sol Michael.W基于Foundry精读Openzeppelin第55期——PaymentSplitter.sol Michael.W基于Foundry精读Openzeppelin第56期——VestingWallet.sol Michael.W基于Foundry精读Openzeppelin第57期——ReentrancyGuard.sol Michael.W基于Foundry精读Openzeppelin第58期——PullPayment.sol Michael.W基于Foundry精读Openzeppelin第59期——Proxy.sol Michael.W基于Foundry精读Openzeppelin第60期——Clones.sol Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol Michael.W基于Foundry精读Openzeppelin第62期——ERC1967Proxy.sol Michael.W基于Foundry精读Openzeppelin第63期——Initializable.sol Michael.W基于Foundry精读Openzeppelin第64期——UUPSUpgradeable.sol Michael.W基于Foundry精读Openzeppelin第65期——TransparentUpgradeableProxy.sol Michael.W基于Foundry精读Openzeppelin第66期——ProxyAdmin.sol Michael.W基于Foundry精读Openzeppelin第67期——BeaconProxy.sol Michael.W基于Foundry精读Openzeppelin第68期——UpgradeableBeacon.sol

(番外篇)Michael.W基于Foundry精读Openzeppelin第22期——内联汇编staticcall

  • Michael.W
  • 发布于 2023-08-09 19:53
  • 阅读 1967

内联汇编中,Instruction "staticcall"的功能及使用方法与Instruction "call"类似。唯一不同的是在"staticcall"的过程中不允许发生storage的修改。

0. 版本

[forge-std]:v1.5.6

1. 关于内联汇编staticcall

内联汇编中,Instruction staticcall的功能及使用方法与Instruction call类似。唯一不同的是在staticcall中不允许发生storage的修改。

staticcall的使用方法如下:

assembly{
    staticcall(g, a, in, insize, out, outsize)
}

其中各个参数为:

  • g: 本次调用时设置的gas上限;
  • a: call的目标合约地址;
  • in: memory中的staticcall的calldata的起始位置;
  • insize: memory中的staticcall的calldata的长度;
  • out: memory中存放返回数据的起始位置;
  • outsize: memory中存放返回数据的长度。

需要注意的是:Instruction callcallcodedelegatecall在调用时的参数都与上述staticcall一样。其中,out和outsize是在memory中定义的用来存放返回数据的区域。这个区域的改写规则由返回数据的字节长度来决定:

  1. 当返回数据的字节长度>outsize时,只有mem [out...(out+outsize))会存储返回数据,剩下的返回数据需要通过Instruction returndatacopy来获取;
  2. 当返回数据的字节长度<outsize时,mem [out...(out+outsize))中前面会存储全部返回数据,剩余的mem保持不变;
  3. 当返回数据的字节长度=outsize时,mem [out...(out+outsize))中正好存储全部的返回数据。

2. foundry代码验证

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/extra/assembly/STATICCALL.t.sol

2.1 目标合约

contract Target {
    uint _n = 1024;
    address _addr = address(1);
    uint[] _arr = [1, 2];

    // 返回数据字节长度为32
    function getN() external view returns (uint){
        return _n;
    }

    // 返回数据字节长度为64
    function getNAndAddr() external view returns (uint, address){
        return (_n, _addr);
    }

    // 返回数据为动态数组
    function getArr() external view returns (uint[] memory){
        return _arr;
    }
}

2.2 返回数据字节长度为32

contract STATICCALLTest is Test {
    Target t = new Target();

    function test_Staticcall_ReturnUint() external {
        bytes memory encodedParams = abi.encodeCall(t.getN, ());
        address targetAddr = address(t);
        bytes32 outPtr;
        // case 1: right outsize for return data (uint)
        uint outSize = 0x20;
        bool success;
        uint returnSize;
        bytes32 originalValueOnOutPtr;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            originalValueOnOutPtr := mload(outPtr)
        }
        assertTrue(success);
        assertEq(returnSize, 0x20);
        uint n = t.getN();
        assertEq(n, uint(originalValueOnOutPtr));

        // case 2: insufficient outsize for return data
        outSize = 0x00;
        bytes32 outValueFromReturnDataCopy;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            originalValueOnOutPtr := mload(outPtr)
            // copy return data (returnSize bytes) with returndatacopy() to the outPtr
            returndatacopy(outPtr, 0x00, returnSize)
            outValueFromReturnDataCopy := mload(outPtr)
        }
        assertTrue(success);
        assertEq(returnSize, 0x20);
        assertNotEq(n, uint(originalValueOnOutPtr));
        assertEq(n, uint(outValueFromReturnDataCopy));

        // case 3: outsize more than the size of return data
        outSize = 0x40;
        bytes32 restBytesInOutSize;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            originalValueOnOutPtr := mload(outPtr)
            // outsize中多余出来的未使用的内容
            restBytesInOutSize := mload(add(outPtr, returnSize))
        }

        assertTrue(success);
        assertEq(returnSize, 0x20);
        assertEq(n, uint(originalValueOnOutPtr));
        // untouched
        assertEq(0, restBytesInOutSize);
    }
}

2.3 返回数据字节长度为64

contract STATICCALLTest is Test {
    Target t = new Target();

    function test_Staticcall_ReturnUintAndAddress() external {
        bytes memory encodedParams = abi.encodeCall(t.getNAndAddr, ());
        address targetAddr = address(t);
        bytes32 outPtr;
        // case 1: right outsize for return data (uint and address)
        uint outSize = 0x40;
        bool success;
        uint returnSize;
        bytes32 originalValueOnOutPtr;
        bytes32 nextWordOfOutPtr;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            // outsize为2个字,即32*2=64个字节(即0x40)
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            // outPtr开始,第一个字为第一个uint返回值,第二个字为第二个address返回值
            originalValueOnOutPtr := mload(outPtr)
            nextWordOfOutPtr := mload(add(outPtr, 0x20))
        }
        assertTrue(success);
        assertEq(returnSize, 0x40);
        (uint n, address addr) = t.getNAndAddr();
        assertEq(n, uint(originalValueOnOutPtr));
        assertEq(addr, address(uint160(uint(nextWordOfOutPtr))));

        // case 2: insufficient outsize for return data
        outSize = 0x20;
        bytes32 outValueFromReturnDataCopy;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            // outsize为1个字,即32*1=32个字节(即0x20)
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            // outPtr开始,第一个字为第一个uint返回值
            originalValueOnOutPtr := mload(outPtr)
            // 第二个字不再是第二个address返回值,因为outSize不够
            nextWordOfOutPtr := mload(add(outPtr, 0x20))
            // 从returndata中复制第二个字到outPtr指向的内存中
            returndatacopy(outPtr, 0x20, 0x20)
            // 从outPtr指向的内存中取出内容(即returndata中的第二个address返回值)
            outValueFromReturnDataCopy := mload(outPtr)
        }
        assertTrue(success);
        assertEq(returnSize, 0x40);
        assertEq(n, uint(originalValueOnOutPtr));
        // 由于outSize设置过小,outPtr开始的第二个字内容不是returndata中的第二个address返回值
        assertNotEq(addr, address(uint160(uint(nextWordOfOutPtr))));
        assertEq(addr, address(uint160(uint(outValueFromReturnDataCopy))));

        // case 3: outsize more than the size of return data
        outSize = 0x60;
        bytes32 restBytesInOutSize;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            // outsize为3个字,即32*3=96个字节(即0x60)
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            originalValueOnOutPtr := mload(outPtr)
            nextWordOfOutPtr := mload(add(outPtr, 0x20))
            // outsize中多余出来的未使用的内容
            restBytesInOutSize := mload(add(outPtr, returnSize))
        }
        assertTrue(success);
        assertEq(returnSize, 0x40);
        assertEq(n, uint(originalValueOnOutPtr));
        assertEq(addr, address(uint160(uint(nextWordOfOutPtr))));
        // untouched
        assertEq(0, restBytesInOutSize);
    }
}

2.4 返回数据为动态数组

contract STATICCALLTest is Test {
    Target t = new Target();

    function test_Staticcall_ReturnUintArr() external {
        bytes memory encodedParams = abi.encodeCall(t.getArr, ());
        address targetAddr = address(t);
        bytes32 outPtr;
        // case 1: right outsize for return data
        // (uint[] with 2 members —— 1(offset to start the array) + 1(length of array) + 2(2 words for 3 members) = 4)
        uint outSize = 0x80;
        bool success;
        uint returnSize;
        bytes32 originalValueOnOutPtr;
        bytes32 secondWordOfOutPtr;
        bytes32 thirdWordOfOutPtr;
        bytes32 forthWordOfOutPtr;
        bytes32 fifthWordOfOutPtr;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            // outsize为4个字,即32*4=128个字节(即0x80)
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            // outPtr开始,第一个字为动态数组真实数据的偏移值
            originalValueOnOutPtr := mload(outPtr)
            // 第二个字为动态数组长度
            secondWordOfOutPtr := mload(add(outPtr, 0x20))
            // 第三个字为第一个元素值
            thirdWordOfOutPtr := mload(add(outPtr, 0x40))
            // 第四个字为第二个元素值
            forthWordOfOutPtr := mload(add(outPtr, 0x60))
        }
        assertTrue(success);
        assertEq(0x80, returnSize);
        uint[] memory arr = t.getArr();
        // offset to start the uint array is 0x20
        assertEq(0x20, uint(originalValueOnOutPtr));
        // 动态数组长度
        assertEq(arr.length, uint(secondWordOfOutPtr));
        // 依次为2个元素的值
        assertEq(arr[0], uint(thirdWordOfOutPtr));
        assertEq(arr[1], uint(forthWordOfOutPtr));

        // case 2: insufficient outsize for return data
        // (uint[] with 2 members —— 1(offset to start the array) + 1(length of array) + 2(2 words for 3 members) = 4)
        outSize = 0x20;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            // outsize为1个字,即32*1=32个字节(即0x20)
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            // outPtr开始,第一个字为动态数组真实数据的偏移值
            originalValueOnOutPtr := mload(outPtr)
            // 第二个字不再是动态数组的长度,因为outSize不够
            secondWordOfOutPtr := mload(add(outPtr, 0x20))
        }
        assertTrue(success);
        assertEq(0x80, returnSize);
        // offset to start the uint array is 0x20
        assertEq(0x20, uint(originalValueOnOutPtr));
        // returndatacopy()之前,outPtr后的第二个字内容不是数组长度
        assertNotEq(arr.length, uint(secondWordOfOutPtr));

        assembly{
            // 从returndata中复制第二个至第四个字到outPtr+0x20开始的内存中
            returndatacopy(add(outPtr, 0x20), 0x20, sub(returnSize, 0x20))
            // outPtr后的第二个字为数组长度
            secondWordOfOutPtr := mload(add(outPtr, 0x20))
            // 第三个字为第一个元素值
            thirdWordOfOutPtr := mload(add(outPtr, 0x40))
            // 第四个字为第二个元素值
            forthWordOfOutPtr := mload(add(outPtr, 0x60))
        }
        // returndatacopy()之后,outPtr后的第二个字内容为数组长度
        assertEq(arr.length, uint(secondWordOfOutPtr));
        // returndatacopy()之后,outPtr后的第三个至第五个字内容依次为数组元素值
        assertEq(arr[0], uint(thirdWordOfOutPtr));
        assertEq(arr[1], uint(forthWordOfOutPtr));

        // case 3: outsize more than the size of return data
        // (uint[] with 2 members —— 1(offset to start the array) + 1(length of array) + 2(2 words for 3 members) = 4)
        outSize = (4 + 1) * 0x20;
        assembly{
            outPtr := mload(0x40)  // free memory pointer
            // outsize为5个字,即32*5=160个字节(即0xa0)
            success := staticcall(100000, targetAddr, add(encodedParams, 0x20), mload(encodedParams), outPtr, outSize)
            returnSize := returndatasize()
            // outPtr开始,第一个字为动态数组真实数据的偏移值
            originalValueOnOutPtr := mload(outPtr)
            // 第二个字为动态数组长度
            secondWordOfOutPtr := mload(add(outPtr, 0x20))
            // 第三个字为第一个元素值
            thirdWordOfOutPtr := mload(add(outPtr, 0x40))
            // 第四个字为第二个元素值
            forthWordOfOutPtr := mload(add(outPtr, 0x60))
            // 第五个字为outsize中未使用的空间
            fifthWordOfOutPtr := mload(add(outPtr, 0x80))
        }
        assertTrue(success);
        assertEq(0x80, returnSize);
        // offset to start the uint array is 0x20
        assertEq(0x20, uint(originalValueOnOutPtr));
        // 动态数组长度
        assertEq(arr.length, uint(secondWordOfOutPtr));
        // 依次为2个元素的值
        assertEq(arr[0], uint(thirdWordOfOutPtr));
        assertEq(arr[1], uint(forthWordOfOutPtr));
        // 第五个字为untouched
        assertEq(0, fifthWordOfOutPtr);
    }
}

ps:\ 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

公众号名称:后现代泼痞浪漫主义奠基人

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论