|
@@ -31,7 +31,9 @@ library SafeERC20 {
|
|
|
* non-reverting calls are assumed to be successful.
|
|
|
*/
|
|
|
function safeTransfer(IERC20 token, address to, uint256 value) internal {
|
|
|
- _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
|
|
|
+ if (!_safeTransfer(token, to, value, true)) {
|
|
|
+ revert SafeERC20FailedOperation(address(token));
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -39,21 +41,23 @@ library SafeERC20 {
|
|
|
* 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 {
|
|
|
- _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
|
|
|
+ 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 _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
|
|
|
+ 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 _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
|
|
|
+ return _safeTransferFrom(token, from, to, value, false);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -99,11 +103,9 @@ library SafeERC20 {
|
|
|
* set here.
|
|
|
*/
|
|
|
function forceApprove(IERC20 token, address spender, uint256 value) internal {
|
|
|
- bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
|
|
|
-
|
|
|
- if (!_callOptionalReturnBool(token, approvalCall)) {
|
|
|
- _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
|
|
|
- _callOptionalReturn(token, approvalCall);
|
|
|
+ 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));
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -163,50 +165,116 @@ library SafeERC20 {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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).
|
|
|
+ * @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).
|
|
|
*
|
|
|
- * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
|
|
|
+ * @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 _callOptionalReturn(IERC20 token, bytes memory data) private {
|
|
|
- uint256 returnSize;
|
|
|
- uint256 returnValue;
|
|
|
+ function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
|
|
|
+ bytes4 selector = IERC20.transfer.selector;
|
|
|
+
|
|
|
assembly ("memory-safe") {
|
|
|
- let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
|
|
|
- // bubble errors
|
|
|
- if iszero(success) {
|
|
|
- let ptr := mload(0x40)
|
|
|
- returndatacopy(ptr, 0, returndatasize())
|
|
|
- revert(ptr, returndatasize())
|
|
|
+ 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)))
|
|
|
}
|
|
|
- returnSize := returndatasize()
|
|
|
- returnValue := mload(0)
|
|
|
+ mstore(0x40, fmp)
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
|
|
|
- revert SafeERC20FailedOperation(address(token));
|
|
|
+ /**
|
|
|
+ * @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 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).
|
|
|
+ * @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).
|
|
|
*
|
|
|
- * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
|
|
|
+ * @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 _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
|
|
|
- bool success;
|
|
|
- uint256 returnSize;
|
|
|
- uint256 returnValue;
|
|
|
+ function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {
|
|
|
+ bytes4 selector = IERC20.approve.selector;
|
|
|
+
|
|
|
assembly ("memory-safe") {
|
|
|
- success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
|
|
|
- returnSize := returndatasize()
|
|
|
- returnValue := mload(0)
|
|
|
+ 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)
|
|
|
}
|
|
|
- return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
|
|
|
}
|
|
|
}
|