From 9f2184258aab968f7d83df8a443c1a77b87a3e4c Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 3 Apr 2025 18:14:17 +0100 Subject: [PATCH] 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 --- foundry/interfaces/ICurveRouter.sol | 27 --- foundry/src/executors/CurveExecutor.sol | 151 +++++++----- foundry/test/Constants.sol | 3 - foundry/test/CurveRouterGasTest.t.sol | 42 ---- foundry/test/executors/CurveExecutor.t.sol | 257 +++++++++------------ 5 files changed, 197 insertions(+), 283 deletions(-) delete mode 100644 foundry/interfaces/ICurveRouter.sol delete mode 100644 foundry/test/CurveRouterGasTest.t.sol diff --git a/foundry/interfaces/ICurveRouter.sol b/foundry/interfaces/ICurveRouter.sol deleted file mode 100644 index 29475f3..0000000 --- a/foundry/interfaces/ICurveRouter.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.26; - -/** - * @title Curve Router Interface - * @notice Interface for interacting with Curve's router contract for token swaps across various Curve pools - * @dev This interface allows for executing swaps through Curve's router, which can handle different pool types - */ -interface ICurveRouter { - /** - * @notice Executes a token swap through Curve pools - * @dev This function handles the routing of tokens through one or more Curve pools - * @dev The parameters are encoded in the `CurveRouterParams` struct - * @return Amount of output tokens received from the swap - */ - function exchange( - address[11] memory route, - uint256[5][5] memory swapParams, - uint256 amountIn, - uint256 minAmountOut, - address[5] memory pools, - address receiver - ) external payable returns (uint256); -} - - - diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index a3e8535..6d98777 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -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)); + } } diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 50a3260..8ff3dc4 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -130,9 +130,6 @@ contract Constants is Test, BaseConstants { bytes32 PANCAKEV3_POOL_CODE_INIT_HASH = 0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2; - // Curve router - address CURVE_ROUTER = 0x16C6521Dff6baB339122a0FE25a9116693265353; - // Curve meta registry address CURVE_META_REGISTRY = 0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC; diff --git a/foundry/test/CurveRouterGasTest.t.sol b/foundry/test/CurveRouterGasTest.t.sol deleted file mode 100644 index 7b86995..0000000 --- a/foundry/test/CurveRouterGasTest.t.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.26; - -import {Constants} from "./Constants.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {ICurveRouter} from "../interfaces/ICurveRouter.sol"; - -contract CurveRouterGasTest is Constants { - ICurveRouter curveRouter = ICurveRouter(CURVE_ROUTER); - - function setUp() public { - uint256 forkBlock = 22031795; - vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - } - - function testCurveRouter() public { - address[11] memory route; - route[0] = WETH_ADDR; - route[1] = TRICRYPTO_POOL; - route[2] = USDC_ADDR; - - uint256[5][5] memory swapParams; - swapParams[0][0] = 2; // tokenIn Index - swapParams[0][1] = 0; // tokenOut Index - swapParams[0][2] = 1; // swap type - swapParams[0][3] = 3; // pool type - swapParams[0][4] = 3; // n_coins - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(WETH_ADDR, ALICE, amountIn); - - vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(address(curveRouter), amountIn); - curveRouter.exchange( - route, swapParams, amountIn, minAmountOut, pools, address(this) - ); - vm.stopPrank(); - } -} diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 77d0dfa..32c3a3a 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -35,14 +35,20 @@ interface IAaveLendingPool { } contract CurveExecutorExposed is CurveExecutor { - constructor(address _curveRouter, address _nativeToken) - CurveExecutor(_curveRouter, _nativeToken) - {} + constructor(address _nativeToken) CurveExecutor(_nativeToken) {} - function decodeParams(bytes calldata data) + function decodeData(bytes calldata data) external pure - returns (CurveSwapParams memory params) + returns ( + address tokenIn, + address tokenOut, + address pool, + uint8 poolType, + int128 i, + int128 j, + bool tokenApprovalNeeded + ) { return _decodeData(data); } @@ -57,86 +63,78 @@ contract CurveExecutorTest is Test, Constants { function setUp() public { uint256 forkBlock = 22031795; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - curveExecutorExposed = new CurveExecutorExposed(CURVE_ROUTER, ETH_ADDR); + curveExecutorExposed = new CurveExecutorExposed(ETH_ADDR); metaRegistry = MetaRegistry(CURVE_META_REGISTRY); } function testDecodeParams() public view { - address[11] memory route = - _getRoute(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL); + bytes memory data = abi.encodePacked( + WETH_ADDR, + USDC_ADDR, + TRICRYPTO_POOL, + uint8(3), + uint8(2), + uint8(0), + true + ); - // The meta registry does not have information about the pool. - // We manually set the swap params. - uint256[5][5] memory swapParams; - swapParams[0][0] = 2; // tokenIn Index - swapParams[0][1] = 0; // tokenOut Index - swapParams[0][2] = 1; // swap type - swapParams[0][3] = 3; // pool type - swapParams[0][4] = 3; // n_coins + ( + address tokenIn, + address tokenOut, + address pool, + uint8 poolType, + int128 i, + int128 j, + bool tokenApprovalNeeded + ) = curveExecutorExposed.decodeData(data); - bytes memory data = abi.encode(route, swapParams, address(this), true); - - CurveExecutor.CurveSwapParams memory params = - curveExecutorExposed.decodeParams(data); - - assertEq(params.route[0], WETH_ADDR); - assertEq(params.route[1], TRICRYPTO_POOL); - assertEq(params.route[2], USDC_ADDR); - assertEq(params.swapParams[0][0], 2); - assertEq(params.swapParams[0][1], 0); - assertEq(params.swapParams[0][2], 1); - assertEq(params.swapParams[0][3], 3); - assertEq(params.swapParams[0][4], 3); - assertEq(params.receiver, address(this)); - assertEq(params.needsApproval, true); + assertEq(tokenIn, WETH_ADDR); + assertEq(tokenOut, USDC_ADDR); + assertEq(pool, TRICRYPTO_POOL); + assertEq(poolType, 3); + assertEq(i, 2); + assertEq(j, 0); + assertEq(tokenApprovalNeeded, true); } function testTriPool() public { // Swapping DAI -> USDC on TriPool 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7 - address[11] memory route = _getRoute(DAI_ADDR, USDC_ADDR, TRIPOOL); - uint256[5][5] memory swapParams = - _getSwapParams(TRIPOOL, DAI_ADDR, USDC_ADDR, 1, 1); - uint256 amountIn = 1 ether; - deal(DAI_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode(route, swapParams, address(this), true); + + bytes memory data = _getData(DAI_ADDR, USDC_ADDR, TRIPOOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertEq(amountOut, 999796); - assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); + assertEq(amountOut, 999797); + assertEq( + IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); } function testStEthPool() public { // Swapping ETH -> stETH on StEthPool 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022 - address[11] memory route = _getRoute(ETH_ADDR, STETH_ADDR, STETH_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(STETH_POOL, ETH_ADDR, STETH_ADDR, 1, 1); - uint256 amountIn = 1 ether; - deal(address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode(route, swapParams, address(this), false); + + bytes memory data = _getData(ETH_ADDR, STETH_ADDR, STETH_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertTrue(amountOut == 1001072414418410898); - assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); //// Gets 1 wei less than amountOut + assertEq(amountOut, 1001072414418410897); + assertEq( + IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); } function testTricrypto2Pool() public { // Swapping WETH -> WBTC on Tricrypto2Pool 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46 - address[11] memory route = - _getRoute(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(TRICRYPTO2_POOL, WETH_ADDR, WBTC_ADDR, 1, 3); - uint256 amountIn = 1 ether; - deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + + bytes memory data = _getData(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL, 3); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -149,15 +147,10 @@ contract CurveExecutorTest is Test, Constants { function testSUSDPool() public { // Swapping USDC -> SUSD on SUSDPool 0xA5407eAE9Ba41422680e2e00537571bcC53efBfD - address[11] memory route = _getRoute(USDC_ADDR, SUSD_ADDR, SUSD_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(SUSD_POOL, USDC_ADDR, SUSD_ADDR, 1, 1); - uint256 amountIn = 100 * 10 ** 6; - deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + + bytes memory data = _getData(USDC_ADDR, SUSD_ADDR, SUSD_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -170,20 +163,14 @@ contract CurveExecutorTest is Test, Constants { function testFraxUsdcPool() public { // Swapping FRAX -> USDC on FraxUsdcPool 0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2 - address[11] memory route = - _getRoute(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(FRAX_USDC_POOL, FRAX_ADDR, USDC_ADDR, 1, 1); - uint256 amountIn = 1 ether; - deal(FRAX_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + + bytes memory data = _getData(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertEq(amountOut, 998096); + assertEq(amountOut, 998097); assertEq( IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), amountOut @@ -192,16 +179,10 @@ contract CurveExecutorTest is Test, Constants { function testUsdeUsdcPool() public { // Swapping USDC -> USDE on a CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf (plain pool) - address[11] memory route = - _getRoute(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(USDE_USDC_POOL, USDC_ADDR, USDE_ADDR, 1, 1); - uint256 amountIn = 100 * 10 ** 6; - deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + + bytes memory data = _getData(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -214,20 +195,15 @@ contract CurveExecutorTest is Test, Constants { function testDolaFraxPyusdPool() public { // Swapping DOLA -> FRAXPYUSD on a CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf (meta pool) - address[11] memory route = - _getRoute(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(DOLA_FRAXPYUSD_POOL, DOLA_ADDR, FRAXPYUSD_POOL, 1, 1); - uint256 amountIn = 100 * 10 ** 6; - deal(DOLA_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + _getData(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertEq(amountOut, 99688991); + assertEq(amountOut, 99688992); assertEq( IERC20(FRAXPYUSD_POOL).balanceOf(address(curveExecutorExposed)), amountOut @@ -236,15 +212,11 @@ contract CurveExecutorTest is Test, Constants { function testCryptoPoolWithETH() public { // Swapping XYO -> ETH on a CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - address[11] memory route = _getRoute(XYO_ADDR, ETH_ADDR, ETH_XYO_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(ETH_XYO_POOL, XYO_ADDR, ETH_ADDR, 1, 2); - uint256 amountIn = 1 ether; uint256 initialBalance = address(curveExecutorExposed).balance; // this address already has some ETH assigned to it deal(XYO_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + + bytes memory data = _getData(XYO_ADDR, ETH_ADDR, ETH_XYO_POOL, 2); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -256,16 +228,10 @@ contract CurveExecutorTest is Test, Constants { function testCryptoPool() public { // Swapping BSGG -> USDT on a CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - address[11] memory route = - _getRoute(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(BSGG_USDT_POOL, BSGG_ADDR, USDT_ADDR, 1, 2); - uint256 amountIn = 1000 ether; - deal(BSGG_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + + bytes memory data = _getData(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL, 2); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -278,33 +244,26 @@ contract CurveExecutorTest is Test, Constants { function testTricryptoPool() public { // Swapping WETH -> USDC on a Tricrypto pool, deployed by factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963 - address[11] memory route = - _getRoute(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(TRICRYPTO_POOL, WETH_ADDR, USDC_ADDR, 1, 2); - uint256 amountIn = 1 ether; - deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode(route, swapParams, address(this), true); + + bytes memory data = _getData(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL, 2); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertEq(amountOut, 1861130973); - assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); + assertEq(amountOut, 1861130974); + assertEq( + IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); } function testTwoCryptoPool() public { // Swapping UWU -> WETH on a Twocrypto pool, deployed by factory 0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f - address[11] memory route = _getRoute(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(UWU_WETH_POOL, UWU_ADDR, WETH_ADDR, 1, 2); - uint256 amountIn = 1 ether; - deal(UWU_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + + bytes memory data = _getData(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL, 2); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -317,16 +276,11 @@ contract CurveExecutorTest is Test, Constants { function testStableSwapPool() public { // Swapping CRVUSD -> USDT on a StableSwap pool, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d (plain pool) - address[11] memory route = - _getRoute(CRVUSD_ADDR, USDT_ADDR, CRVUSD_USDT_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(CRVUSD_USDT_POOL, CRVUSD_ADDR, USDT_ADDR, 1, 1); - uint256 amountIn = 1 ether; - deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + _getData(CRVUSD_ADDR, USDT_ADDR, CRVUSD_USDT_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -339,52 +293,47 @@ contract CurveExecutorTest is Test, Constants { function testMetaPool() public { // Swapping WTAO -> WSTTAO on a MetaPool deployed by factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 (plain pool) - address[11] memory route = - _getRoute(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(WSTTAO_WTAO_POOL, WTAO_ADDR, WSTTAO_ADDR, 1, 1); - uint256 amountIn = 100 * 10 ** 9; // 9 decimals - deal(WTAO_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = - abi.encode(route, swapParams, address(curveExecutorExposed), true); + _getData(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL, 1); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertEq(amountOut, 32797923609); + assertEq(amountOut, 32797923610); assertEq( IERC20(WSTTAO_ADDR).balanceOf(address(curveExecutorExposed)), amountOut ); } - function _getRoute(address tokenIn, address tokenOut, address pool) - internal - pure - returns (address[11] memory route) - { - route[0] = tokenIn; - route[2] = tokenOut; - route[1] = pool; - } - - function _getSwapParams( - address pool, + function _getData( address tokenIn, address tokenOut, - uint256 swapType, - uint256 poolType - ) internal view returns (uint256[5][5] memory swapParams) { - uint256 nCoins = metaRegistry.get_n_coins(pool); + address pool, + uint8 poolType + ) internal view returns (bytes memory data) { + (int128 i, int128 j) = _getIndexes(tokenIn, tokenOut, pool); + data = abi.encodePacked( + tokenIn, + tokenOut, + pool, + poolType, + uint8(uint256(uint128(i))), + uint8(uint256(uint128(j))), + true + ); + } + + function _getIndexes(address tokenIn, address tokenOut, address pool) + internal + view + returns (int128, int128) + { (int128 coinInIndex, int128 coinOutIndex,) = metaRegistry.get_coin_indices(pool, tokenIn, tokenOut); - - swapParams[0][0] = uint256(int256(coinInIndex)); - swapParams[0][1] = uint256(int256(coinOutIndex)); - swapParams[0][2] = swapType; - swapParams[0][3] = poolType; - swapParams[0][4] = nCoins; + return (coinInIndex, coinOutIndex); } function dealAaveDai() internal {