{"content":{"title":"safeTransferLib","body":"---\r\ntitle: safeTranferLib\r\ntime: 2024/05/29\r\ntags: null\r\n---\r\n\r\n众所周知，usdt的合约并没有遵循标准的ERC20来实现，主要在于它的transfer函数没有bool的返回值，这可能导致调用它的合约因为无法接收返回值而出现函数失败（因为是call，所以往往无法revert），进而导致自己的token锁在usdt合约里面。\r\n解决tranfser函数没有返回值这一情况，我们通常会使用SafeTransferLib库\r\n有些小伙伴可能看不懂汇编，从而导致无法透彻理解其中的代码逻辑\r\n没关系，今天我们就带着大家来理解SafeTransferLib中的典型函数：safeTransferFrom\r\n\r\n```solidity\r\n\r\n//这个库就是为了防止像usdt那样没有返回值的transfer，调用方式为\r\n//using SafeTransferLib for token;  \r\n//token.safeTransferFrom\r\n\r\n\r\n\r\n\r\n    function safeTransferFrom(\r\n        ERC20 token,\r\n        address from,\r\n        address to,\r\n        uint256 amount\r\n    ) internal {\r\n        bool success;\r\n\r\n        /// @solidity memory-safe-assembly\r\n        assembly {\r\n            // Get a pointer to some free memory.\r\n            let freeMemoryPointer := mload(0x40)\r\n\r\n            // Write the abi-encoded calldata into memory, beginning with the function selector.\r\n            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)\r\n            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the \"from\" argument.\r\n            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the \"to\" argument.\r\n            mstore(add(freeMemoryPointer, 68), amount) // Append the \"amount\" argument. Masking not required as it's a full 32 byte type.\r\n\r\n//call函数在调用的函数顺利完成时返回true，也就是说success为1的情况为：\r\n// 函数顺利调用，且没有返回值\r\n// 或\r\n// 函数顺利调用，且返回了1,这里gt中的returndatasize如果返回1的话是会返回0x000...01这样的值，刚好32byte\r\n//因此函数排除的情况为：成功call但返回了除1以外的值，或call失败\r\n\r\n            success := and(\r\n                // Set success to whether the call reverted, if not we check it either\r\n                // returned exactly 1 (can't just be non-zero data), or had no return data.\r\n                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\r\n                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.\r\n                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\r\n                // Counterintuitively, this call must be positioned second to the or() call in the\r\n                // surrounding and() call or else returndatasize() will be zero during the computation.\r\n                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)\r\n            )\r\n        }\r\n\r\n        require(success, \"TRANSFER_FROM_FAILED\");\r\n    }\r\n\r\n```\r\n先来看下这部分\r\n这部分的目的是得到call()需要的calldata\r\n```solidity\r\n\r\n//先找到calldata的指针位置\r\nlet freeMemoryPointer := mload(0x40) \r\n//先将selector放在指针上\r\nmstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) \r\n//再将from和0xfff...ff作与操作，检验from的位数，然后用mstore将其存在selector后面\r\nmstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff))\r\n//同样的操作，在from后面追加to和amount\r\nmstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) \r\nmstore(add(freeMemoryPointer, 68), amount) \r\n\r\n```\r\n再来看看这一部分\r\n```solidity\r\n//首先，and为1需要满足or和call都为1\r\nsuccess := and( \r\n//or为1需要满足：iszero为1，也就是返回值为空，对应的是开头提到的没有返回值的transfer函数；或者and为1，需要返回值为1并且数据大小大于31bytes（也就是一个数据的大小）\r\nor(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize()))\r\n//这里反直觉的是：call需要放在or操作后面的位置\r\ncall(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) \r\n\r\n\r\n```"},"author":{"user":"https://learnblockchain.cn/people/14307","address":null},"history":null,"timestamp":1719213717,"version":1}