safeTransferLib

  • oo
  • 更新于 5天前
  • 阅读 262

SafeTransferLib的用处//这个库就是为了防止像usdt那样没有返回值的transfer,调用方式usingSafeTransferLibfortoken;token.safeTransferFrom


title: safeTranferLib time: 2024/05/29 tags: null

众所周知,usdt的合约并没有遵循标准的ERC20来实现,主要在于它的transfer函数没有bool的返回值,这可能导致调用它的合约因为无法接收返回值而出现函数失败(因为是call,所以往往无法revert),进而导致自己的token锁在usdt合约里面。 解决tranfser函数没有返回值这一情况,我们通常会使用SafeTransferLib库 有些小伙伴可能看不懂汇编,从而导致无法透彻理解其中的代码逻辑 没关系,今天我们就带着大家来理解SafeTransferLib中的典型函数:safeTransferFrom


//这个库就是为了防止像usdt那样没有返回值的transfer,调用方式为
//using SafeTransferLib for token;  
//token.safeTransferFrom

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument.
            mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.

//call函数在调用的函数顺利完成时返回true,也就是说success为1的情况为:
// 函数顺利调用,且没有返回值
// 或
// 函数顺利调用,且返回了1,这里gt中的returndatasize如果返回1的话是会返回0x000...01这样的值,刚好32byte
//因此函数排除的情况为:成功call但返回了除1以外的值,或call失败

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

先来看下这部分 这部分的目的是得到call()需要的calldata


//先找到calldata的指针位置
let freeMemoryPointer := mload(0x40) 
//先将selector放在指针上
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) 
//再将from和0xfff...ff作与操作,检验from的位数,然后用mstore将其存在selector后面
mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff))
//同样的操作,在from后面追加to和amount
mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) 
mstore(add(freeMemoryPointer, 68), amount) 

再来看看这一部分

//首先,and为1需要满足or和call都为1
success := and( 
//or为1需要满足:iszero为1,也就是返回值为空,对应的是开头提到的没有返回值的transfer函数;或者and为1,需要返回值为1并且数据大小大于31bytes(也就是一个数据的大小)
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize()))
//这里反直觉的是:call需要放在or操作后面的位置
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) 
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
oo
oo
关注我,带你深入研究solidity机制,熟悉各种安全库和链上攻击