Skip to content

Commit 2ea54a1

Browse files
Amxxarr00
andauthored
Refactor SafeERC20 to save gas (#5771)
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
1 parent bc8f775 commit 2ea54a1

File tree

1 file changed

+108
-40
lines changed

1 file changed

+108
-40
lines changed

contracts/token/ERC20/utils/SafeERC20.sol

Lines changed: 108 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)