@@ -31,29 +31,33 @@ library SafeERC20 {
31
31
* non-reverting calls are assumed to be successful.
32
32
*/
33
33
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
+ }
35
37
}
36
38
37
39
/**
38
40
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
39
41
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
40
42
*/
41
43
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
+ }
43
47
}
44
48
45
49
/**
46
50
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
47
51
*/
48
52
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 );
50
54
}
51
55
52
56
/**
53
57
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
54
58
*/
55
59
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 );
57
61
}
58
62
59
63
/**
@@ -99,11 +103,9 @@ library SafeERC20 {
99
103
* set here.
100
104
*/
101
105
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));
107
109
}
108
110
}
109
111
@@ -163,50 +165,116 @@ library SafeERC20 {
163
165
}
164
166
165
167
/**
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).
170
170
*
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.
172
175
*/
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
+
176
179
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 )))
183
197
}
184
- returnSize := returndatasize ()
185
- returnValue := mload (0 )
198
+ mstore (0x40 , fmp)
186
199
}
200
+ }
187
201
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 )
190
243
}
191
244
}
192
245
193
246
/**
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).
198
249
*
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.
200
254
*/
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
+
205
258
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)
209
278
}
210
- return success && (returnSize == 0 ? address (token).code.length > 0 : returnValue == 1 );
211
279
}
212
280
}
0 commit comments