@@ -31,29 +31,33 @@ library SafeERC20 {
3131 * non-reverting calls are assumed to be successful.
3232 */
3333 function safeTransfer (IERC20 token , address to , uint256 value ) internal {
34- _callOptionalReturn (token, abi.encodeCall (token.transfer, (to, value)));
34+ if (! _safeTransfer (token, to, value, true )) {
35+ revert SafeERC20FailedOperation (address (token));
36+ }
3537 }
3638
3739 /**
3840 * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
3941 * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
4042 */
4143 function safeTransferFrom (IERC20 token , address from , address to , uint256 value ) internal {
42- _callOptionalReturn (token, abi.encodeCall (token.transferFrom, (from, to, value)));
44+ if (! _safeTransferFrom (token, from, to, value, true )) {
45+ revert SafeERC20FailedOperation (address (token));
46+ }
4347 }
4448
4549 /**
4650 * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
4751 */
4852 function trySafeTransfer (IERC20 token , address to , uint256 value ) internal returns (bool ) {
49- return _callOptionalReturnBool (token, abi.encodeCall (token.transfer, ( to, value)) );
53+ return _safeTransfer (token, to, value, false );
5054 }
5155
5256 /**
5357 * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
5458 */
5559 function trySafeTransferFrom (IERC20 token , address from , address to , uint256 value ) internal returns (bool ) {
56- return _callOptionalReturnBool (token, abi.encodeCall (token.transferFrom, ( from, to, value)) );
60+ return _safeTransferFrom (token, from, to, value, false );
5761 }
5862
5963 /**
@@ -99,11 +103,9 @@ library SafeERC20 {
99103 * set here.
100104 */
101105 function forceApprove (IERC20 token , address spender , uint256 value ) internal {
102- bytes memory approvalCall = abi.encodeCall (token.approve, (spender, value));
103-
104- if (! _callOptionalReturnBool (token, approvalCall)) {
105- _callOptionalReturn (token, abi.encodeCall (token.approve, (spender, 0 )));
106- _callOptionalReturn (token, approvalCall);
106+ if (! _safeApprove (token, spender, value, false )) {
107+ if (! _safeApprove (token, spender, 0 , true )) revert SafeERC20FailedOperation (address (token));
108+ if (! _safeApprove (token, spender, value, true )) revert SafeERC20FailedOperation (address (token));
107109 }
108110 }
109111
@@ -163,50 +165,116 @@ library SafeERC20 {
163165 }
164166
165167 /**
166- * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
167- * on the return value: the return value is optional (but if data is returned, it must not be false).
168- * @param token The token targeted by the call.
169- * @param data The call data (encoded using abi.encode or one of its variants).
168+ * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
169+ * return value is optional (but if data is returned, it must not be false).
170170 *
171- * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
171+ * @param token The token targeted by the call.
172+ * @param to The recipient of the tokens
173+ * @param value The amount of token to transfer
174+ * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
172175 */
173- function _callOptionalReturn (IERC20 token , bytes memory data ) private {
174- uint256 returnSize ;
175- uint256 returnValue;
176+ function _safeTransfer (IERC20 token , address to , uint256 value , bool bubble ) private returns ( bool success ) {
177+ bytes4 selector = IERC20 .transfer. selector ;
178+
176179 assembly ("memory-safe" ) {
177- let success := call (gas (), token, 0 , add (data, 0x20 ), mload (data), 0 , 0x20 )
178- // bubble errors
179- if iszero (success) {
180- let ptr := mload (0x40 )
181- returndatacopy (ptr, 0 , returndatasize ())
182- revert (ptr, returndatasize ())
180+ let fmp := mload (0x40 )
181+ mstore (0x00 , selector)
182+ mstore (0x04 , and (to, shr (96 , not (0 ))))
183+ mstore (0x24 , value)
184+ success := call (gas (), token, 0 , 0 , 0x44 , 0 , 0x20 )
185+ // if call success and return is true, all is good.
186+ // otherwise (not success or return is not true), we need to perform further checks
187+ if iszero (and (success, eq (mload (0x00 ), 1 ))) {
188+ // if the call was a failure and bubble is enabled, bubble the error
189+ if and (iszero (success), bubble) {
190+ returndatacopy (fmp, 0 , returndatasize ())
191+ revert (fmp, returndatasize ())
192+ }
193+ // if the return value is not true, then the call is only successful if:
194+ // - the token address has code
195+ // - the returndata is empty
196+ success := and (success, and (iszero (returndatasize ()), gt (extcodesize (token), 0 )))
183197 }
184- returnSize := returndatasize ()
185- returnValue := mload (0 )
198+ mstore (0x40 , fmp)
186199 }
200+ }
187201
188- if (returnSize == 0 ? address (token).code.length == 0 : returnValue != 1 ) {
189- revert SafeERC20FailedOperation (address (token));
202+ /**
203+ * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
204+ * value: the return value is optional (but if data is returned, it must not be false).
205+ *
206+ * @param token The token targeted by the call.
207+ * @param from The sender of the tokens
208+ * @param to The recipient of the tokens
209+ * @param value The amount of token to transfer
210+ * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
211+ */
212+ function _safeTransferFrom (
213+ IERC20 token ,
214+ address from ,
215+ address to ,
216+ uint256 value ,
217+ bool bubble
218+ ) private returns (bool success ) {
219+ bytes4 selector = IERC20 .transferFrom.selector ;
220+
221+ assembly ("memory-safe" ) {
222+ let fmp := mload (0x40 )
223+ mstore (0x00 , selector)
224+ mstore (0x04 , and (from, shr (96 , not (0 ))))
225+ mstore (0x24 , and (to, shr (96 , not (0 ))))
226+ mstore (0x44 , value)
227+ success := call (gas (), token, 0 , 0 , 0x64 , 0 , 0x20 )
228+ // if call success and return is true, all is good.
229+ // otherwise (not success or return is not true), we need to perform further checks
230+ if iszero (and (success, eq (mload (0x00 ), 1 ))) {
231+ // if the call was a failure and bubble is enabled, bubble the error
232+ if and (iszero (success), bubble) {
233+ returndatacopy (fmp, 0 , returndatasize ())
234+ revert (fmp, returndatasize ())
235+ }
236+ // if the return value is not true, then the call is only successful if:
237+ // - the token address has code
238+ // - the returndata is empty
239+ success := and (success, and (iszero (returndatasize ()), gt (extcodesize (token), 0 )))
240+ }
241+ mstore (0x40 , fmp)
242+ mstore (0x60 , 0 )
190243 }
191244 }
192245
193246 /**
194- * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
195- * on the return value: the return value is optional (but if data is returned, it must not be false).
196- * @param token The token targeted by the call.
197- * @param data The call data (encoded using abi.encode or one of its variants).
247+ * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
248+ * the return value is optional (but if data is returned, it must not be false).
198249 *
199- * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
250+ * @param token The token targeted by the call.
251+ * @param spender The spender of the tokens
252+ * @param value The amount of token to transfer
253+ * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
200254 */
201- function _callOptionalReturnBool (IERC20 token , bytes memory data ) private returns (bool ) {
202- bool success;
203- uint256 returnSize;
204- uint256 returnValue;
255+ function _safeApprove (IERC20 token , address spender , uint256 value , bool bubble ) private returns (bool success ) {
256+ bytes4 selector = IERC20 .approve.selector ;
257+
205258 assembly ("memory-safe" ) {
206- success := call (gas (), token, 0 , add (data, 0x20 ), mload (data), 0 , 0x20 )
207- returnSize := returndatasize ()
208- returnValue := mload (0 )
259+ let fmp := mload (0x40 )
260+ mstore (0x00 , selector)
261+ mstore (0x04 , and (spender, shr (96 , not (0 ))))
262+ mstore (0x24 , value)
263+ success := call (gas (), token, 0 , 0 , 0x44 , 0 , 0x20 )
264+ // if call success and return is true, all is good.
265+ // otherwise (not success or return is not true), we need to perform further checks
266+ if iszero (and (success, eq (mload (0x00 ), 1 ))) {
267+ // if the call was a failure and bubble is enabled, bubble the error
268+ if and (iszero (success), bubble) {
269+ returndatacopy (fmp, 0 , returndatasize ())
270+ revert (fmp, returndatasize ())
271+ }
272+ // if the return value is not true, then the call is only successful if:
273+ // - the token address has code
274+ // - the returndata is empty
275+ success := and (success, and (iszero (returndatasize ()), gt (extcodesize (token), 0 )))
276+ }
277+ mstore (0x40 , fmp)
209278 }
210- return success && (returnSize == 0 ? address (token).code.length > 0 : returnValue == 1 );
211279 }
212280}
0 commit comments