// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.20; import {IERC20} from "../IERC20.sol"; import {IERC1363} from "../../../interfaces/IERC1363.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC-20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { /** * @dev An operation with an ERC-20 token failed. */ error SafeERC20FailedOperation(address token); /** * @dev Indicates a failed `decreaseAllowance` request. */ error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { if (!_safeTransfer(token, to, value, true)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { if (!_safeTransferFrom(token, from, to, value, true)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful. */ function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) { return _safeTransfer(token, to, value, false); } /** * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful. */ function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) { return _safeTransferFrom(token, from, to, value, false); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. * * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); forceApprove(token, spender, oldAllowance + value); } /** * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no * value, non-reverting calls are assumed to be successful. * * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal { unchecked { uint256 currentAllowance = token.allowance(address(this), spender); if (currentAllowance < requestedDecrease) { revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease); } forceApprove(token, spender, currentAllowance - requestedDecrease); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval * to be set to zero before setting it to a non-zero value, such as USDT. * * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being * set here. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { if (!_safeApprove(token, spender, value, false)) { if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token)); if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token)); } } /** * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when * targeting contracts. * * Reverts if the returned value is other than `true`. */ function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { if (to.code.length == 0) { safeTransfer(token, to, value); } else if (!token.transferAndCall(to, value, data)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when * targeting contracts. * * Reverts if the returned value is other than `true`. */ function transferFromAndCallRelaxed( IERC1363 token, address from, address to, uint256 value, bytes memory data ) internal { if (to.code.length == 0) { safeTransferFrom(token, from, to, value); } else if (!token.transferFromAndCall(from, to, value, data)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when * targeting contracts. * * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}. * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall} * once without retrying, and relies on the returned value to be true. * * Reverts if the returned value is other than `true`. */ function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { if (to.code.length == 0) { forceApprove(token, to, value); } else if (!token.approveAndCall(to, value, data)) { revert SafeERC20FailedOperation(address(token)); } } /** * @dev Imitates a Solidity `token.transfer(to, value)` call, 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 to The recipient of the tokens * @param value The amount of token to transfer * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean. */ function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) { bytes4 selector = IERC20.transfer.selector; assembly ("memory-safe") { let fmp := mload(0x40) mstore(0x00, selector) mstore(0x04, and(to, shr(96, not(0)))) mstore(0x24, value) success := call(gas(), token, 0, 0, 0x44, 0, 0x20) // if call success and return is true, all is good. // otherwise (not success or return is not true), we need to perform further checks if iszero(and(success, eq(mload(0x00), 1))) { // if the call was a failure and bubble is enabled, bubble the error if and(iszero(success), bubble) { returndatacopy(fmp, 0, returndatasize()) revert(fmp, returndatasize()) } // if the return value is not true, then the call is only successful if: // - the token address has code // - the returndata is empty success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0))) } mstore(0x40, fmp) } } /** * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, 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 from The sender of the tokens * @param to The recipient of the tokens * @param value The amount of token to transfer * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean. */ function _safeTransferFrom( IERC20 token, address from, address to, uint256 value, bool bubble ) private returns (bool success) { bytes4 selector = IERC20.transferFrom.selector; assembly ("memory-safe") { let fmp := mload(0x40) mstore(0x00, selector) mstore(0x04, and(from, shr(96, not(0)))) mstore(0x24, and(to, shr(96, not(0)))) mstore(0x44, value) success := call(gas(), token, 0, 0, 0x64, 0, 0x20) // if call success and return is true, all is good. // otherwise (not success or return is not true), we need to perform further checks if iszero(and(success, eq(mload(0x00), 1))) { // if the call was a failure and bubble is enabled, bubble the error if and(iszero(success), bubble) { returndatacopy(fmp, 0, returndatasize()) revert(fmp, returndatasize()) } // if the return value is not true, then the call is only successful if: // - the token address has code // - the returndata is empty success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0))) } mstore(0x40, fmp) mstore(0x60, 0) } } /** * @dev Imitates a Solidity `token.approve(spender, value)` call, 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 spender The spender of the tokens * @param value The amount of token to transfer * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean. */ function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) { bytes4 selector = IERC20.approve.selector; assembly ("memory-safe") { let fmp := mload(0x40) mstore(0x00, selector) mstore(0x04, and(spender, shr(96, not(0)))) mstore(0x24, value) success := call(gas(), token, 0, 0, 0x44, 0, 0x20) // if call success and return is true, all is good. // otherwise (not success or return is not true), we need to perform further checks if iszero(and(success, eq(mload(0x00), 1))) { // if the call was a failure and bubble is enabled, bubble the error if and(iszero(success), bubble) { returndatacopy(fmp, 0, returndatasize()) revert(fmp, returndatasize()) } // if the return value is not true, then the call is only successful if: // - the token address has code // - the returndata is empty success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0))) } mstore(0x40, fmp) } } }