Skip to content

Commit

Permalink
feat: simplified SafeERC20
Browse files Browse the repository at this point in the history
  • Loading branch information
chapati23 committed Oct 7, 2024
1 parent e858391 commit 2800297
Showing 1 changed file with 9 additions and 45 deletions.
54 changes: 9 additions & 45 deletions contracts/common/SafeERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,29 @@ pragma solidity ^0.8.0;

import { IERC20MintableBurnable as IERC20 } from "contracts/common/IERC20MintableBurnable.sol";
import { Address } from "openzeppelin-contracts-next/contracts/utils/Address.sol";
//TODO: merge with lattest impl of openzeppelin

/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;

function safeTransfer(IERC20 token, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}

function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}

function safeMint(IERC20 token, address account, uint256 amount) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.mint.selector, account, amount));
_callOptionalReturn(token, abi.encodeWithSelector(token.mint.selector, account, amount));
}

function safeBurn(IERC20 token, uint256 amount) internal {
callOptionalReturn(token, abi.encodeWithSelector(token.burn.selector, amount));
}

function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}

function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}

function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 newAllowance = token.allowance(address(this), spender) - value;
callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
_callOptionalReturn(token, abi.encodeWithSelector(token.burn.selector, amount));
}

/**
Expand All @@ -59,24 +33,14 @@ library SafeERC20 {
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function callOptionalReturn(IERC20 token, bytes memory data) private {
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.

// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
require(address(token).isContract(), "SafeERC20: call to non-contract");

// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.

bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");

Check warning on line 44 in contracts/common/SafeERC20.sol

View workflow job for this annotation

GitHub Actions / Lint & Test

GC: Use Custom Errors instead of require statements
}
}
Expand Down

0 comments on commit 2800297

Please sign in to comment.