feat: Add execution for curve
- Add CurveSwapStructEncoder and tests - Add CurveSwapExecutorExposed and tests - Add needed interfaces #time 0m #time 0m #time 0m
This commit is contained in:
174
evm/src/curve/CurveSwapExecutor.sol
Normal file
174
evm/src/curve/CurveSwapExecutor.sol
Normal file
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: UNLICENCED
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../interfaces/ISwapExecutor.sol";
|
||||
import "./interfaces/ICurvePool.sol";
|
||||
import "./interfaces/ICurvePoolNoReturn.sol";
|
||||
import "./interfaces/ICurveCryptoPool.sol";
|
||||
import "./interfaces/ICurvePoolNoReturn.sol";
|
||||
import "./interfaces/ICurvePoolWithReturn.sol";
|
||||
import {
|
||||
IERC20,
|
||||
SafeERC20
|
||||
} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "src/libraries/EfficientERC20.sol";
|
||||
|
||||
interface IWETH is IERC20 {
|
||||
function deposit() external payable;
|
||||
|
||||
function withdraw(uint256) external;
|
||||
}
|
||||
|
||||
contract CurveSwapExecutor is ISwapExecutor, ISwapExecutorErrors {
|
||||
using EfficientERC20 for IERC20;
|
||||
|
||||
IWETH private constant weth =
|
||||
IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
|
||||
address private constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
|
||||
|
||||
function _decodeParams(bytes calldata data)
|
||||
internal
|
||||
pure
|
||||
returns (
|
||||
IERC20 tokenOut,
|
||||
address target,
|
||||
address receiver,
|
||||
uint8 poolType,
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded
|
||||
)
|
||||
{
|
||||
tokenOut = IERC20(address(bytes20(data[0:20])));
|
||||
target = address(bytes20(data[20:40]));
|
||||
receiver = address(bytes20(data[40:60]));
|
||||
poolType = uint8(data[60]);
|
||||
i = int128(uint128(uint8(data[61])));
|
||||
j = int128(uint128(uint8(data[62])));
|
||||
tokenApprovalNeeded = data[63] != 0;
|
||||
}
|
||||
|
||||
function swap(uint256 amountIn, bytes calldata data)
|
||||
external
|
||||
payable
|
||||
returns (uint256 res)
|
||||
{
|
||||
(
|
||||
IERC20 tokenOut,
|
||||
address target,
|
||||
address receiver,
|
||||
uint8 poolType,
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded
|
||||
) = _decodeParams(data);
|
||||
|
||||
// Approve the token for the pool's address if `tokenApprovalNeeded` is
|
||||
// true
|
||||
if (tokenApprovalNeeded) {
|
||||
address tokenIn;
|
||||
// pool type 6 has a different function signature to get the coins
|
||||
if (poolType == 6) {
|
||||
tokenIn = ICurvePoolNoReturn(target).underlying_coins(int128(i));
|
||||
} else {
|
||||
tokenIn = ICurvePool(target).coins(uint256(uint128(i)));
|
||||
}
|
||||
IERC20(tokenIn).forceApprove(target, type(uint256).max);
|
||||
}
|
||||
if (poolType == 0) {
|
||||
// simple exchange with int128
|
||||
// e.g. AAVE, EURS
|
||||
res = ICurvePoolWithReturn(target).exchange(i, j, amountIn, 0);
|
||||
if (receiver != address(this)) {
|
||||
tokenOut.safeTransfer(receiver, res);
|
||||
}
|
||||
} else if (poolType == 1) {
|
||||
// simple exchange with int128 but no amountOut,
|
||||
// e.g. BUSD, HBTC, PAX, renBTC, sBTC, SUSD, USDT, Y, 3pool
|
||||
uint256 tokenOutBalanceBeforeSwap =
|
||||
tokenOut.balanceOf(address(this));
|
||||
ICurvePoolNoReturn(target).exchange(i, j, amountIn, 0);
|
||||
uint256 tokenOutBalanceAfterSwap = tokenOut.balanceOf(address(this));
|
||||
res = tokenOutBalanceAfterSwap - tokenOutBalanceBeforeSwap;
|
||||
if (receiver != address(this)) {
|
||||
tokenOut.safeTransfer(receiver, res);
|
||||
}
|
||||
} else if (poolType == 3) {
|
||||
// tricrypto case
|
||||
uint256 tokenOutBalanceBeforeSwap =
|
||||
tokenOut.balanceOf(address(this));
|
||||
ICurveCryptoPool(target).exchange(
|
||||
uint256(uint128(i)),
|
||||
uint256(uint128(j)),
|
||||
amountIn,
|
||||
0,
|
||||
false //TODO: Check if we can call the entrypoint without
|
||||
// 'use_eth' as it's false by default.
|
||||
);
|
||||
uint256 tokenOutBalanceAfterSwap = tokenOut.balanceOf(address(this));
|
||||
res = tokenOutBalanceAfterSwap - tokenOutBalanceBeforeSwap;
|
||||
if (receiver != address(this)) {
|
||||
tokenOut.safeTransfer(receiver, res);
|
||||
}
|
||||
} else if (poolType == 4) {
|
||||
// (payable) ether based stableswaps - so far no liquidity
|
||||
// e.g. sETH, stETH, rETH, etc
|
||||
ICurveCryptoPool pool = ICurveCryptoPool(target);
|
||||
if (pool.coins(uint256(uint128(i))) == ETH) {
|
||||
weth.withdraw(amountIn);
|
||||
res = pool.exchange{value: amountIn}(i, j, amountIn, 0);
|
||||
} else {
|
||||
res = pool.exchange(i, j, amountIn, 0);
|
||||
}
|
||||
|
||||
if (pool.coins(uint256(uint128(j))) == ETH) {
|
||||
weth.deposit{value: res}();
|
||||
}
|
||||
if (receiver != address(this)) {
|
||||
tokenOut.safeTransfer(receiver, res);
|
||||
}
|
||||
} else if (poolType == 5) {
|
||||
// metapool or lending pool interface using int128
|
||||
// e.g. AAVE
|
||||
res = ICurvePoolWithReturn(target).exchange_underlying(
|
||||
i, j, amountIn, 0
|
||||
);
|
||||
if (receiver != address(this)) {
|
||||
tokenOut.safeTransfer(receiver, res);
|
||||
}
|
||||
} else if (poolType == 6) {
|
||||
// metapool or lending pool interface using int128 no amountOut
|
||||
// returned
|
||||
// e.g. Y, Compound
|
||||
uint256 tokenOutBalanceBeforeSwap =
|
||||
tokenOut.balanceOf(address(this));
|
||||
ICurvePoolNoReturn(target).exchange_underlying(i, j, amountIn, 0);
|
||||
uint256 tokenOutBalanceAfterSwap = tokenOut.balanceOf(address(this));
|
||||
res = tokenOutBalanceAfterSwap - tokenOutBalanceBeforeSwap;
|
||||
if (receiver != address(this)) {
|
||||
tokenOut.safeTransfer(receiver, res);
|
||||
}
|
||||
} else if (poolType == 7) {
|
||||
// cryptov2 pool with two tokens
|
||||
// e.g. LDO/ETH
|
||||
res = ICurvePoolWithReturn(target).exchange(
|
||||
uint256(uint128(i)),
|
||||
uint256(uint128(j)),
|
||||
amountIn,
|
||||
0,
|
||||
false,
|
||||
receiver
|
||||
);
|
||||
} else if (poolType == 8) {
|
||||
// cryptov2 two tokens not factory pools ETH/CRV and ETH/CVX
|
||||
res = ICurvePoolWithReturn(target).exchange(
|
||||
uint256(uint128(i)), uint256(uint128(j)), amountIn, 0, false
|
||||
);
|
||||
if (receiver != address(this)) {
|
||||
tokenOut.safeTransfer(receiver, res);
|
||||
}
|
||||
} else {
|
||||
revert UnknownPoolType(poolType);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
evm/src/curve/interfaces/ICurveCryptoPool.sol
Normal file
26
evm/src/curve/interfaces/ICurveCryptoPool.sol
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.4.0;
|
||||
|
||||
interface ICurveCryptoPool {
|
||||
function get_dy(uint256 i, uint256 j, uint256 dx)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
// tricrypto
|
||||
function exchange(
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
bool use_eth
|
||||
) external payable;
|
||||
|
||||
// eth accepting pools
|
||||
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external
|
||||
payable
|
||||
returns (uint256);
|
||||
|
||||
function coins(uint256 i) external view returns (address);
|
||||
}
|
||||
174
evm/src/curve/interfaces/ICurvePool.sol
Normal file
174
evm/src/curve/interfaces/ICurvePool.sol
Normal file
@@ -0,0 +1,174 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
pragma solidity >=0.4.0;
|
||||
|
||||
interface ICurvePool {
|
||||
function initialize(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
address _coin,
|
||||
uint256 _decimals,
|
||||
uint256 _A,
|
||||
uint256 _fee,
|
||||
address _admin
|
||||
) external;
|
||||
function decimals() external view returns (uint256);
|
||||
function transfer(address _to, uint256 _value) external returns (bool);
|
||||
function transferFrom(address _from, address _to, uint256 _value)
|
||||
external
|
||||
returns (bool);
|
||||
function approve(address _spender, uint256 _value)
|
||||
external
|
||||
returns (bool);
|
||||
function get_previous_balances()
|
||||
external
|
||||
view
|
||||
returns (uint256[2] memory);
|
||||
function get_balances() external view returns (uint256[2] memory);
|
||||
function get_twap_balances(
|
||||
uint256[2] memory _first_balances,
|
||||
uint256[2] memory _last_balances,
|
||||
uint256 _time_elapsed
|
||||
) external view returns (uint256[2] memory);
|
||||
function get_price_cumulative_last()
|
||||
external
|
||||
view
|
||||
returns (uint256[2] memory);
|
||||
function admin_fee() external view returns (uint256);
|
||||
function A() external view returns (uint256);
|
||||
function A_precise() external view returns (uint256);
|
||||
function get_virtual_price() external view returns (uint256);
|
||||
function calc_token_amount(uint256[2] memory _amounts, bool _is_deposit)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
function calc_token_amount(
|
||||
uint256[2] memory _amounts,
|
||||
bool _is_deposit,
|
||||
bool _previous
|
||||
) external view returns (uint256);
|
||||
function add_liquidity(uint256[2] memory _amounts, uint256 _min_mint_amount)
|
||||
external
|
||||
returns (uint256);
|
||||
function add_liquidity(
|
||||
uint256[2] memory _amounts,
|
||||
uint256 _min_mint_amount,
|
||||
address _receiver
|
||||
) external returns (uint256);
|
||||
function get_dy(int128 i, int128 j, uint256 dx)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
function get_dy(int128 i, int128 j, uint256 dx, uint256[2] memory _balances)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
function get_dy_underlying(int128 i, int128 j, uint256 dx)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
function get_dy_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 dx,
|
||||
uint256[2] memory _balances
|
||||
) external view returns (uint256);
|
||||
function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy)
|
||||
external;
|
||||
function exchange(
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
address _receiver
|
||||
) external;
|
||||
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external;
|
||||
function exchange(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
address _receiver
|
||||
) external;
|
||||
function exchange_underlying(
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy
|
||||
) external;
|
||||
function exchange_underlying(
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
address _receiver
|
||||
) external;
|
||||
function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external;
|
||||
function exchange_underlying(
|
||||
int128 i,
|
||||
int128 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
address _receiver
|
||||
) external;
|
||||
function remove_liquidity(
|
||||
uint256 _burn_amount,
|
||||
uint256[2] memory _min_amounts
|
||||
) external returns (uint256[2] memory);
|
||||
function remove_liquidity(
|
||||
uint256 _burn_amount,
|
||||
uint256[2] memory _min_amounts,
|
||||
address _receiver
|
||||
) external returns (uint256[2] memory);
|
||||
function remove_liquidity_imbalance(
|
||||
uint256[2] memory _amounts,
|
||||
uint256 _max_burn_amount
|
||||
) external returns (uint256);
|
||||
function remove_liquidity_imbalance(
|
||||
uint256[2] memory _amounts,
|
||||
uint256 _max_burn_amount,
|
||||
address _receiver
|
||||
) external returns (uint256);
|
||||
function calc_withdraw_one_coin(uint256 _burn_amount, int128 i)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
function calc_withdraw_one_coin(
|
||||
uint256 _burn_amount,
|
||||
int128 i,
|
||||
bool _previous
|
||||
) external view returns (uint256);
|
||||
function remove_liquidity_one_coin(
|
||||
uint256 _burn_amount,
|
||||
int128 i,
|
||||
uint256 _min_received
|
||||
) external returns (uint256);
|
||||
function remove_liquidity_one_coin(
|
||||
uint256 _burn_amount,
|
||||
int128 i,
|
||||
uint256 _min_received,
|
||||
address _receiver
|
||||
) external returns (uint256);
|
||||
function ramp_A(uint256 _future_A, uint256 _future_time) external;
|
||||
function stop_ramp_A() external;
|
||||
function admin_balances(uint256 i) external view returns (uint256);
|
||||
function withdraw_admin_fees() external;
|
||||
function admin() external view returns (address);
|
||||
function coins(uint256 arg0) external view returns (address);
|
||||
function balances(uint256 arg0) external view returns (uint256);
|
||||
function fee() external view returns (uint256);
|
||||
function block_timestamp_last() external view returns (uint256);
|
||||
function initial_A() external view returns (uint256);
|
||||
function future_A() external view returns (uint256);
|
||||
function initial_A_time() external view returns (uint256);
|
||||
function future_A_time() external view returns (uint256);
|
||||
function name() external view returns (string memory);
|
||||
function symbol() external view returns (string memory);
|
||||
function balanceOf(address arg0) external view returns (uint256);
|
||||
function allowance(address arg0, address arg1)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
function totalSupply() external view returns (uint256);
|
||||
}
|
||||
22
evm/src/curve/interfaces/ICurvePoolNoReturn.sol
Normal file
22
evm/src/curve/interfaces/ICurvePoolNoReturn.sol
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.4.0;
|
||||
|
||||
interface ICurvePoolNoReturn {
|
||||
function get_dy(int128 i, int128 j, uint256 dx)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function get_dy_underlying(int128 i, int128 j, uint256 dx)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external;
|
||||
|
||||
function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external;
|
||||
function coins(int128 arg0) external view returns (address);
|
||||
function underlying_coins(int128 arg0) external view returns (address);
|
||||
}
|
||||
39
evm/src/curve/interfaces/ICurvePoolWithReturn.sol
Normal file
39
evm/src/curve/interfaces/ICurvePoolWithReturn.sol
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.4.0;
|
||||
|
||||
interface ICurvePoolWithReturn {
|
||||
function get_dy(int128 i, int128 j, uint256 dx)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function get_dy_underlying(int128 i, int128 j, uint256 dx)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external
|
||||
returns (uint256);
|
||||
|
||||
function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external
|
||||
returns (uint256);
|
||||
|
||||
function exchange(
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
bool use_eth,
|
||||
address receiver
|
||||
) external returns (uint256);
|
||||
|
||||
function exchange(
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
bool use_eth
|
||||
) external returns (uint256);
|
||||
}
|
||||
295
evm/src/libraries/EfficientERC20.sol
Normal file
295
evm/src/libraries/EfficientERC20.sol
Normal file
@@ -0,0 +1,295 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
|
||||
import "openzeppelin-contracts/contracts/utils/Address.sol";
|
||||
|
||||
/**
|
||||
* @title Propellerheads Safe ERC20 Transfer Library
|
||||
* @author PropellerHeads Developers
|
||||
* @dev Gas-efficient version of Openzeppelin's SafeERC20 contract.
|
||||
* This is a mix between SafeERC20 and GPv2SafeERC20 libraries. It
|
||||
* provides efficient transfers optimised for router contracts, while
|
||||
* keeping the Openzeppelins compatibility for approvals.
|
||||
*/
|
||||
library EfficientERC20 {
|
||||
using Address for address;
|
||||
|
||||
error TransferFailed(uint256 balance, uint256 amount);
|
||||
error TransferFromFailed(uint256 balance, uint256 amount);
|
||||
|
||||
bytes4 private constant _balanceOfSelector = hex"70a08231";
|
||||
bytes4 private constant _transferSelector = hex"a9059cbb";
|
||||
|
||||
/// @dev Wrapper around a call to the ERC20 function `transfer` that reverts
|
||||
/// also when the token returns `false`.
|
||||
function safeTransfer(IERC20 token, address to, uint256 value) internal {
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
let freeMemoryPointer := mload(0x40)
|
||||
mstore(freeMemoryPointer, _transferSelector)
|
||||
mstore(
|
||||
add(freeMemoryPointer, 4),
|
||||
and(to, 0xffffffffffffffffffffffffffffffffffffffff)
|
||||
)
|
||||
mstore(add(freeMemoryPointer, 36), value)
|
||||
|
||||
if iszero(call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)) {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
}
|
||||
|
||||
if (!getLastTransferResult(token)) {
|
||||
uint256 balance = token.balanceOf(address(this));
|
||||
revert TransferFailed(balance, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfers the callers balance - 1. This effectively leaves dust on
|
||||
* the contract
|
||||
* which will lead to more gas efficient transfers in the future.
|
||||
*/
|
||||
function transferBalanceLeavingDust(IERC20 token, address to) internal {
|
||||
uint256 amount;
|
||||
assembly {
|
||||
// Load free memory pointer
|
||||
let input := mload(0x40)
|
||||
// Prepare call data: function selector (4 bytes) + contract address
|
||||
// (32 bytes)
|
||||
mstore(input, _balanceOfSelector)
|
||||
mstore(add(input, 0x04), address())
|
||||
|
||||
// Call 'balanceOf' function and store result in 'amount'
|
||||
let success := staticcall(gas(), token, input, 0x24, input, 0x20)
|
||||
|
||||
if iszero(success) {
|
||||
// Get the size of the returned error message and forward it
|
||||
let returnSize := returndatasize()
|
||||
returndatacopy(input, 0, returnSize)
|
||||
revert(input, returnSize)
|
||||
}
|
||||
|
||||
amount := sub(mload(input), 1)
|
||||
|
||||
// Prepare call data: function selector (4 bytes) + to (32 bytes) +
|
||||
// amount (32 bytes)
|
||||
mstore(input, _transferSelector)
|
||||
mstore(add(input, 0x04), to)
|
||||
mstore(add(input, 0x24), amount)
|
||||
|
||||
if iszero(call(gas(), token, 0, input, 0x44, 0, 0)) {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
}
|
||||
|
||||
if (!getLastTransferResult(token)) {
|
||||
uint256 balance = token.balanceOf(address(this));
|
||||
revert TransferFailed(balance, amount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Wrapper around a call to the ERC20 function `transferFrom` that
|
||||
* reverts also when the token returns `false`.
|
||||
*/
|
||||
function safeTransferFrom(
|
||||
IERC20 token,
|
||||
address from,
|
||||
address to,
|
||||
uint256 value
|
||||
) internal {
|
||||
bytes4 selector_ = token.transferFrom.selector;
|
||||
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
let freeMemoryPointer := mload(0x40)
|
||||
mstore(freeMemoryPointer, selector_)
|
||||
mstore(
|
||||
add(freeMemoryPointer, 4),
|
||||
and(from, 0xffffffffffffffffffffffffffffffffffffffff)
|
||||
)
|
||||
mstore(
|
||||
add(freeMemoryPointer, 36),
|
||||
and(to, 0xffffffffffffffffffffffffffffffffffffffff)
|
||||
)
|
||||
mstore(add(freeMemoryPointer, 68), value)
|
||||
|
||||
if iszero(call(gas(), token, 0, freeMemoryPointer, 100, 0, 0)) {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
}
|
||||
|
||||
if (!getLastTransferResult(token)) {
|
||||
uint256 balance = token.balanceOf(address(this));
|
||||
revert TransferFailed(balance, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Deprecated. This function has issues similar to the ones found in
|
||||
* {IERC20-approve}, and its usage is discouraged.
|
||||
*
|
||||
* Whenever possible, use {safeIncreaseAllowance} and
|
||||
* {safeDecreaseAllowance} instead.
|
||||
*/
|
||||
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'
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Set the calling contract's allowance toward `spender` to `value`. If
|
||||
* `token` returns no value,
|
||||
* non-reverting calls are assumed to be successful. Meant to be used with
|
||||
* tokens that require the approval
|
||||
* to be set to zero before setting it to a non-zero value, such as USDT.
|
||||
*/
|
||||
function forceApprove(IERC20 token, address spender, uint256 value)
|
||||
internal
|
||||
{
|
||||
bytes memory approvalCall =
|
||||
abi.encodeCall(token.approve, (spender, value));
|
||||
|
||||
if (!_callOptionalReturnBool(token, approvalCall)) {
|
||||
_callOptionalReturn(
|
||||
token, abi.encodeCall(token.approve, (spender, 0))
|
||||
);
|
||||
_callOptionalReturn(token, approvalCall);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Imitates a Solidity high-level call (i.e. a regular function call to
|
||||
* a contract), relaxing the requirement
|
||||
* on the return value: the return value is optional (but if data is
|
||||
* returned, it must not be false).
|
||||
* @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 {
|
||||
// We need to perform a low level call here, to bypass Solidity's return
|
||||
// data size checking mechanism, since
|
||||
// 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);
|
||||
if (returndata.length > 0) {
|
||||
// Return data is optional
|
||||
require(
|
||||
abi.decode(returndata, (bool)),
|
||||
"SafeERC20: ERC20 operation did not succeed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Imitates a Solidity high-level call (i.e. a regular function call to
|
||||
* a contract), relaxing the requirement
|
||||
* on the return value: the return value is optional (but if data is
|
||||
* returned, it must not be false).
|
||||
* @param token The token targeted by the call.
|
||||
* @param data The call data (encoded using abi.encode or one of its
|
||||
* variants).
|
||||
*
|
||||
* This is a variant of {_callOptionalReturn} that silently catches all
|
||||
* reverts and returns a bool instead.
|
||||
*/
|
||||
function _callOptionalReturnBool(IERC20 token, bytes memory data)
|
||||
private
|
||||
returns (bool)
|
||||
{
|
||||
// We need to perform a low level call here, to bypass Solidity's return
|
||||
// data size checking mechanism, since
|
||||
// we're implementing it ourselves. We cannot use {Address-functionCall}
|
||||
// here since this should return false
|
||||
// and not revert is the subcall reverts.
|
||||
|
||||
(bool success, bytes memory returndata) = address(token).call(data);
|
||||
return success
|
||||
&& (returndata.length == 0 || abi.decode(returndata, (bool)))
|
||||
&& address(token).code.length > 0;
|
||||
}
|
||||
|
||||
/// @dev Verifies that the last return was a successful `transfer*` call.
|
||||
/// This is done by checking that the return data is either empty, or
|
||||
/// is a valid ABI encoded boolean.
|
||||
function getLastTransferResult(IERC20 token)
|
||||
private
|
||||
view
|
||||
returns (bool success)
|
||||
{
|
||||
// NOTE: Inspecting previous return data requires assembly. Note that
|
||||
// we write the return data to memory 0 in the case where the return
|
||||
// data size is 32, this is OK since the first 64 bytes of memory are
|
||||
// reserved by Solidy as a scratch space that can be used within
|
||||
// assembly blocks.
|
||||
// <https://docs.soliditylang.org/en/v0.7.6/internals/layout_in_memory.html>
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
/// @dev Revert with an ABI encoded Solidity error with a message
|
||||
/// that fits into 32-bytes.
|
||||
///
|
||||
/// An ABI encoded Solidity error has the following memory layout:
|
||||
///
|
||||
/// ------------+----------------------------------
|
||||
/// byte range | value
|
||||
/// ------------+----------------------------------
|
||||
/// 0x00..0x04 | selector("Error(string)")
|
||||
/// 0x04..0x24 | string offset (always 0x20)
|
||||
/// 0x24..0x44 | string length
|
||||
/// 0x44..0x64 | string value, padded to 32-bytes
|
||||
function revertWithMessage(length, message) {
|
||||
mstore(0x00, "\x08\xc3\x79\xa0")
|
||||
mstore(0x04, 0x20)
|
||||
mstore(0x24, length)
|
||||
mstore(0x44, message)
|
||||
revert(0x00, 0x64)
|
||||
}
|
||||
|
||||
switch returndatasize()
|
||||
// Non-standard ERC20 transfer without return.
|
||||
case 0 {
|
||||
// NOTE: When the return data size is 0, verify that there
|
||||
// is code at the address. This is done in order to maintain
|
||||
// compatibility with Solidity calling conventions.
|
||||
// <https://docs.soliditylang.org/en/v0.7.6/control-structures.html#external-function-calls>
|
||||
if iszero(extcodesize(token)) {
|
||||
revertWithMessage(20, "GPv2: not a contract")
|
||||
}
|
||||
|
||||
success := 1
|
||||
}
|
||||
// Standard ERC20 transfer returning boolean success value.
|
||||
case 32 {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
|
||||
// NOTE: For ABI encoding v1, any non-zero value is accepted
|
||||
// as `true` for a boolean. In order to stay compatible with
|
||||
// OpenZeppelin's `SafeERC20` library which is known to work
|
||||
// with the existing ERC20 implementation we care about,
|
||||
// make sure we return success for any non-zero return value
|
||||
// from the `transfer*` call.
|
||||
success := iszero(iszero(mload(0)))
|
||||
}
|
||||
default { revertWithMessage(31, "GPv2: malformed transfer result") }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user