@@ -34,33 +34,33 @@ library SafeERC20 {
34
34
* non-reverting calls are assumed to be successful.
35
35
*/
36
36
function safeTransfer (IERC20 token , address to , uint256 value ) internal {
37
- Memory.Pointer ptr = Memory. getFreeMemoryPointer ();
38
- _callOptionalReturn (token, abi.encodeCall (token.transfer, (to, value) ));
39
- Memory. setFreeMemoryPointer (ptr);
37
+ if ( ! _safeTransfer (token, to, value, true )) {
38
+ revert SafeERC20FailedOperation ( address (token ));
39
+ }
40
40
}
41
41
42
42
/**
43
43
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
44
44
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
45
45
*/
46
46
function safeTransferFrom (IERC20 token , address from , address to , uint256 value ) internal {
47
- Memory.Pointer ptr = Memory. getFreeMemoryPointer ();
48
- _callOptionalReturn (token, abi.encodeCall (token.transferFrom, (from, to, value) ));
49
- Memory. setFreeMemoryPointer (ptr);
47
+ if ( ! _safeTransferFrom (token, from, to, value, true )) {
48
+ revert SafeERC20FailedOperation ( address (token ));
49
+ }
50
50
}
51
51
52
52
/**
53
53
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
54
54
*/
55
55
function trySafeTransfer (IERC20 token , address to , uint256 value ) internal returns (bool ) {
56
- return _callOptionalReturnBool (token, abi.encodeCall (token.transfer, ( to, value)) );
56
+ return _safeTransfer (token, to, value, false );
57
57
}
58
58
59
59
/**
60
60
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
61
61
*/
62
62
function trySafeTransferFrom (IERC20 token , address from , address to , uint256 value ) internal returns (bool ) {
63
- return _callOptionalReturnBool (token, abi.encodeCall (token.transferFrom, ( from, to, value)) );
63
+ return _safeTransferFrom (token, from, to, value, false );
64
64
}
65
65
66
66
/**
@@ -106,11 +106,9 @@ library SafeERC20 {
106
106
* set here.
107
107
*/
108
108
function forceApprove (IERC20 token , address spender , uint256 value ) internal {
109
- Memory.Pointer ptr = Memory.getFreeMemoryPointer ();
110
- bytes memory approvalCall = abi.encodeCall (token.approve, (spender, value));
111
- if (! _callOptionalReturnBool (token, approvalCall)) {
112
- _callOptionalReturn (token, abi.encodeCall (token.approve, (spender, 0 )));
113
- _callOptionalReturn (token, approvalCall);
109
+ if (! _safeApprove (token, spender, value, false )) {
110
+ if (! _safeApprove (token, spender, 0 , true )) revert SafeERC20FailedOperation (address (token));
111
+ if (! _safeApprove (token, spender, value, true )) revert SafeERC20FailedOperation (address (token));
114
112
}
115
113
Memory.setFreeMemoryPointer (ptr);
116
114
}
@@ -171,35 +169,116 @@ library SafeERC20 {
171
169
}
172
170
173
171
/**
174
- * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
175
- * on the return value: the return value is optional (but if data is returned, it must not be false).
176
- * @param token The token targeted by the call.
177
- * @param data The call data (encoded using abi.encode or one of its variants).
172
+ * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
173
+ * return value is optional (but if data is returned, it must not be false).
178
174
*
179
- * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
175
+ * @param token The token targeted by the call.
176
+ * @param to The recipient of the tokens
177
+ * @param value The amount of token to transfer
178
+ * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
180
179
*/
181
- function _callOptionalReturn (IERC20 token , bytes memory data ) private {
182
- ( bool success , bytes32 returnValue , ) = LowLevelCall. callReturn64Bytes ( address (token), data) ;
180
+ function _safeTransfer (IERC20 token , address to , uint256 value , bool bubble ) private returns ( bool success ) {
181
+ bytes4 selector = IERC20 .transfer. selector ;
183
182
184
- if (! success) {
185
- LowLevelCall.bubbleRevert ();
186
- } else if (LowLevelCall.returnDataSize () == 0 ? address (token).code.length == 0 : uint256 (returnValue) != 1 ) {
187
- revert SafeERC20FailedOperation (address (token));
183
+ assembly ("memory-safe" ) {
184
+ let fmp := mload (0x40 )
185
+ mstore (0x00 , selector)
186
+ mstore (0x04 , and (to, shr (96 , not (0 ))))
187
+ mstore (0x24 , value)
188
+ success := call (gas (), token, 0 , 0 , 0x44 , 0 , 0x20 )
189
+ // if call success and return is true, all is good.
190
+ // otherwise (not success or return is not true), we need to perform further checks
191
+ if iszero (and (success, eq (mload (0x00 ), 1 ))) {
192
+ // if the call was a failure and bubble is enabled, bubble the error
193
+ if and (iszero (success), bubble) {
194
+ returndatacopy (fmp, 0 , returndatasize ())
195
+ revert (fmp, returndatasize ())
196
+ }
197
+ // if the return value is not true, then the call is only successful if:
198
+ // - the token address has code
199
+ // - the returndata is empty
200
+ success := and (success, and (iszero (returndatasize ()), gt (extcodesize (token), 0 )))
201
+ }
202
+ mstore (0x40 , fmp)
188
203
}
189
204
}
190
205
191
206
/**
192
- * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
193
- * on the return value: the return value is optional (but if data is returned, it must not be false).
207
+ * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
208
+ * value: the return value is optional (but if data is returned, it must not be false).
209
+ *
194
210
* @param token The token targeted by the call.
195
- * @param data The call data (encoded using abi.encode or one of its variants).
211
+ * @param from The sender of the tokens
212
+ * @param to The recipient of the tokens
213
+ * @param value The amount of token to transfer
214
+ * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
215
+ */
216
+ function _safeTransferFrom (
217
+ IERC20 token ,
218
+ address from ,
219
+ address to ,
220
+ uint256 value ,
221
+ bool bubble
222
+ ) private returns (bool success ) {
223
+ bytes4 selector = IERC20 .transferFrom.selector ;
224
+
225
+ assembly ("memory-safe" ) {
226
+ let fmp := mload (0x40 )
227
+ mstore (0x00 , selector)
228
+ mstore (0x04 , and (from, shr (96 , not (0 ))))
229
+ mstore (0x24 , and (to, shr (96 , not (0 ))))
230
+ mstore (0x44 , value)
231
+ success := call (gas (), token, 0 , 0 , 0x64 , 0 , 0x20 )
232
+ // if call success and return is true, all is good.
233
+ // otherwise (not success or return is not true), we need to perform further checks
234
+ if iszero (and (success, eq (mload (0x00 ), 1 ))) {
235
+ // if the call was a failure and bubble is enabled, bubble the error
236
+ if and (iszero (success), bubble) {
237
+ returndatacopy (fmp, 0 , returndatasize ())
238
+ revert (fmp, returndatasize ())
239
+ }
240
+ // if the return value is not true, then the call is only successful if:
241
+ // - the token address has code
242
+ // - the returndata is empty
243
+ success := and (success, and (iszero (returndatasize ()), gt (extcodesize (token), 0 )))
244
+ }
245
+ mstore (0x40 , fmp)
246
+ mstore (0x60 , 0 )
247
+ }
248
+ }
249
+
250
+ /**
251
+ * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
252
+ * the return value is optional (but if data is returned, it must not be false).
196
253
*
197
- * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
254
+ * @param token The token targeted by the call.
255
+ * @param spender The spender of the tokens
256
+ * @param value The amount of token to transfer
257
+ * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
198
258
*/
199
- function _callOptionalReturnBool (IERC20 token , bytes memory data ) private returns (bool ) {
200
- (bool success , bytes32 returnValue , ) = LowLevelCall.callReturn64Bytes (address (token), data);
201
- return
202
- success &&
203
- (LowLevelCall.returnDataSize () == 0 ? address (token).code.length > 0 : uint256 (returnValue) == 1 );
259
+ function _safeApprove (IERC20 token , address spender , uint256 value , bool bubble ) private returns (bool success ) {
260
+ bytes4 selector = IERC20 .approve.selector ;
261
+
262
+ assembly ("memory-safe" ) {
263
+ let fmp := mload (0x40 )
264
+ mstore (0x00 , selector)
265
+ mstore (0x04 , and (spender, shr (96 , not (0 ))))
266
+ mstore (0x24 , value)
267
+ success := call (gas (), token, 0 , 0 , 0x44 , 0 , 0x20 )
268
+ // if call success and return is true, all is good.
269
+ // otherwise (not success or return is not true), we need to perform further checks
270
+ if iszero (and (success, eq (mload (0x00 ), 1 ))) {
271
+ // if the call was a failure and bubble is enabled, bubble the error
272
+ if and (iszero (success), bubble) {
273
+ returndatacopy (fmp, 0 , returndatasize ())
274
+ revert (fmp, returndatasize ())
275
+ }
276
+ // if the return value is not true, then the call is only successful if:
277
+ // - the token address has code
278
+ // - the returndata is empty
279
+ success := and (success, and (iszero (returndatasize ()), gt (extcodesize (token), 0 )))
280
+ }
281
+ mstore (0x40 , fmp)
282
+ }
204
283
}
205
284
}
0 commit comments