SafeTransferLib的用处//这个库就是为了防止像usdt那样没有返回值的transfer,调用方式usingSafeTransferLibfortoken;token.safeTransferFrom
众所周知,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) )
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!