feat: Refactor Curve Executor not to use the router
We don't need to use all the functionalities of the Curve Router, only the swap type 1 (exchange). By bypassing the router we can save gas on 2 token transfers and with smaller calldata A nice side effect is that the executor is much more readable and understandable now --- don't change below this line --- ENG-4305 Took 2 hours 25 minutes Took 12 seconds
This commit is contained in:
@@ -2,62 +2,45 @@
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import "@interfaces/IExecutor.sol";
|
||||
import "@interfaces/ICurveRouter.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
error CurveExecutor__InvalidAddresses();
|
||||
|
||||
interface CryptoPool {
|
||||
// slither-disable-next-line naming-convention
|
||||
function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy)
|
||||
external
|
||||
payable;
|
||||
}
|
||||
|
||||
interface StablePool {
|
||||
// slither-disable-next-line naming-convention
|
||||
function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy)
|
||||
external
|
||||
payable;
|
||||
}
|
||||
|
||||
interface CryptoPoolETH {
|
||||
// slither-disable-start naming-convention
|
||||
function exchange(
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
uint256 dx,
|
||||
uint256 min_dy,
|
||||
bool use_eth
|
||||
) external payable;
|
||||
// slither-disable-end naming-convention
|
||||
}
|
||||
|
||||
contract CurveExecutor is IExecutor {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
ICurveRouter public immutable curveRouter;
|
||||
address public immutable nativeToken;
|
||||
|
||||
/**
|
||||
* @dev Struct representing the parameters for a Curve swap.
|
||||
*
|
||||
* `route` is an array of [initial token, pool or zap, token, pool or zap, token, ...]
|
||||
* The array is iterated until a pool address of 0x00, then the last given token is transferred to `receiver`.
|
||||
*
|
||||
* `swapParams` is a multidimensional array of [i, j, swap_type, pool_type, n_coins] where:
|
||||
* - i is the index of input token
|
||||
* - j is the index of output token
|
||||
*
|
||||
* The swap_type should be:
|
||||
* 1. for `exchange`
|
||||
* 2. for `exchange_underlying`
|
||||
* 3. for underlying exchange via zap: factory stable metapools with lending base pool `exchange_underlying`
|
||||
* and factory crypto-meta pools underlying exchange (`exchange` method in zap)
|
||||
* 4. for coin -> LP token "exchange" (actually `add_liquidity`)
|
||||
* 5. for lending pool underlying coin -> LP token "exchange" (actually `add_liquidity`)
|
||||
* 6. for LP token -> coin "exchange" (actually `remove_liquidity_one_coin`)
|
||||
* 7. for LP token -> lending or fake pool underlying coin "exchange" (actually `remove_liquidity_one_coin`)
|
||||
* 8. for ETH <-> WETH, ETH -> stETH or ETH -> frxETH, stETH <-> wstETH, frxETH <-> sfrxETH, ETH -> wBETH, USDe -> sUSDe
|
||||
*
|
||||
* pool_type: 1 - stable, 2 - twocrypto, 3 - tricrypto, 4 - llamma
|
||||
* 10 - stable-ng, 20 - twocrypto-ng, 30 - tricrypto-ng
|
||||
*
|
||||
* n_coins is the number of coins in the pool.
|
||||
*
|
||||
* `receiver` is the address of the receiver of the final token.
|
||||
*
|
||||
* `needsApproval` is a flag indicating whether the initial token needs approval before the swap.
|
||||
*
|
||||
* For more see https://github.com/curvefi/curve-router-ng/blob/9ab006ca848fc7f1995b6fbbecfecc1e0eb29e2a/contracts/Router.vy
|
||||
*
|
||||
*/
|
||||
struct CurveSwapParams {
|
||||
address[11] route;
|
||||
uint256[5][5] swapParams;
|
||||
address receiver;
|
||||
bool needsApproval;
|
||||
}
|
||||
|
||||
constructor(address _curveRouter, address _nativeToken) {
|
||||
if (_curveRouter == address(0) || _nativeToken == address(0)) {
|
||||
constructor(address _nativeToken) {
|
||||
if (_nativeToken == address(0)) {
|
||||
revert CurveExecutor__InvalidAddresses();
|
||||
}
|
||||
curveRouter = ICurveRouter(_curveRouter);
|
||||
nativeToken = _nativeToken;
|
||||
}
|
||||
|
||||
@@ -67,30 +50,84 @@ contract CurveExecutor is IExecutor {
|
||||
payable
|
||||
returns (uint256)
|
||||
{
|
||||
CurveSwapParams memory params = _decodeData(data);
|
||||
(
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
address pool,
|
||||
uint8 poolType,
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded
|
||||
) = _decodeData(data);
|
||||
|
||||
if (params.needsApproval) {
|
||||
if (tokenApprovalNeeded && tokenIn != nativeToken) {
|
||||
// slither-disable-next-line unused-return
|
||||
IERC20(params.route[0]).approve(
|
||||
address(curveRouter), type(uint256).max
|
||||
);
|
||||
IERC20(tokenIn).approve(address(pool), type(uint256).max);
|
||||
}
|
||||
// slither-disable-next-line uninitialized-local
|
||||
address[5] memory pools;
|
||||
return curveRouter.exchange{
|
||||
value: params.route[0] == nativeToken ? amountIn : 0
|
||||
}(params.route, params.swapParams, amountIn, 0, pools, params.receiver);
|
||||
|
||||
/// Inspired by Curve's router contract: https://github.com/curvefi/curve-router-ng/blob/9ab006ca848fc7f1995b6fbbecfecc1e0eb29e2a/contracts/Router.vy#L44
|
||||
uint256 balanceBefore = _balanceOf(tokenOut);
|
||||
|
||||
uint256 ethAmount = 0;
|
||||
if (tokenIn == nativeToken) {
|
||||
ethAmount = amountIn;
|
||||
}
|
||||
|
||||
if (poolType == 1 || poolType == 10) {
|
||||
// stable and stable_ng
|
||||
// slither-disable-next-line arbitrary-send-eth
|
||||
StablePool(pool).exchange{value: ethAmount}(i, j, amountIn, 0);
|
||||
} else {
|
||||
// crypto or llamma
|
||||
if (tokenIn == nativeToken || tokenOut == nativeToken) {
|
||||
// slither-disable-next-line arbitrary-send-eth
|
||||
CryptoPoolETH(pool).exchange{value: ethAmount}(
|
||||
uint256(int256(i)), uint256(int256(j)), amountIn, 0, true
|
||||
);
|
||||
} else {
|
||||
CryptoPool(pool).exchange(
|
||||
uint256(int256(i)), uint256(int256(j)), amountIn, 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uint256 balanceAfter = _balanceOf(tokenOut);
|
||||
return balanceAfter - balanceBefore;
|
||||
}
|
||||
|
||||
function _decodeData(bytes calldata data)
|
||||
internal
|
||||
pure
|
||||
returns (CurveSwapParams memory params)
|
||||
returns (
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
address pool,
|
||||
uint8 poolType,
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded
|
||||
)
|
||||
{
|
||||
return abi.decode(data, (CurveSwapParams));
|
||||
tokenIn = address(bytes20(data[0:20]));
|
||||
tokenOut = address(bytes20(data[20:40]));
|
||||
pool = 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;
|
||||
}
|
||||
|
||||
receive() external payable {
|
||||
require(msg.sender.code.length != 0);
|
||||
}
|
||||
|
||||
function _balanceOf(address token)
|
||||
internal
|
||||
view
|
||||
returns (uint256 balance)
|
||||
{
|
||||
balance = token == nativeToken
|
||||
? address(this).balance
|
||||
: IERC20(token).balanceOf(address(this));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user