What is safe about `safeTransfer`?

Different from SafeERC20 wrapper by OZ, the following function by Pancakeswap actually makes a low-level function call to a token address. It is called a safeTransfer, and I am wondering why and what is made safe by it?

Is it because a bool value and data can always be returned such that it is reliable to judge if a call is successful or not? is there anything else making it safer than a direct function call? Thanks.

function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
    }
1 Like

What makes it safer is that the boolean return value is automatically asserted. If a token returns false on transfer or any other operation, a contract using safeTransfer will revert. Otherwise, it’s easy to forget to check the return value.

Note that this piece of code has an issue because it doesn’t assert that token is a contract, and if the account doesn’t have bytecode it will assume that transfers are going through successfully.

5 Likes

Thanks, @frangio . This is a very clear explanation.

I’m also curious about that bytes memory data field. What could this variable possibly be? If success is true, data can be a 32-bit number with a single 1 as the right-most digit. Is it possible to emunerate all possibilities? Why bytes? Why not bytes32? It can be unpredictable or it is always falling into a set of possibilities?

Thanks.

It’s defined by the Solidity language to return bytes. It corresponds to the returndata of the EVM, which is unbounded in length, so a static length such as bytes32 does not work.

1 Like