In openZeppelin's SafeERC20, the functions safeTransfer
and safeTransferFrom
call an internal function called _callOptionalReturn
, which makes a low-level call to the token's transfer
and transferFrom
functions and checks that the return value is true, if there is a return value.
This is the code:
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
Why is there a check for the return value? Isn't there always a Boolean return value for ERC20 transfer
and transferFrom
?
This is what is defined in the ERC20 standard, and also in the IERC20 interface that this contract references.