From 7cde5130d6038916dcb4a6a96c723c366b90da12 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 13 Mar 2025 00:40:12 +0530 Subject: [PATCH 01/15] feat: add curve executor with router tests Took 36 minutes Took 2 minutes --- foundry/interfaces/ICurveRouter.sol | 32 ++++++++ foundry/src/executors/CurveExecutor.sol | 41 ++++++++++ foundry/test/Constants.sol | 7 ++ foundry/test/CurveRouterGasTest.t.sol | 43 ++++++++++ foundry/test/executors/CurveExecutor.t.sol | 91 ++++++++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 foundry/interfaces/ICurveRouter.sol create mode 100644 foundry/src/executors/CurveExecutor.sol create mode 100644 foundry/test/CurveRouterGasTest.t.sol create mode 100644 foundry/test/executors/CurveExecutor.t.sol diff --git a/foundry/interfaces/ICurveRouter.sol b/foundry/interfaces/ICurveRouter.sol new file mode 100644 index 0000000..00bb9e2 --- /dev/null +++ b/foundry/interfaces/ICurveRouter.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +interface ICurveRouter { + 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); + + function get_dy( + address[] memory route, + uint256[] memory swapParams, + uint256 amountIn, + address[] memory pools + ) external view returns (uint256); + + +} + + struct CurveRouterParams { + address[11] route; + uint256[5][5] swapParams; + uint256 amountIn; + uint256 minAmountOut; + address[5] pools; + address receiver; + } + diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol new file mode 100644 index 0000000..0fd277c --- /dev/null +++ b/foundry/src/executors/CurveExecutor.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "@interfaces/IExecutor.sol"; +import "@interfaces/ICurveRouter.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract CurveExecutor is IExecutor { + using SafeERC20 for IERC20; + + ICurveRouter public immutable curveRouter; + + constructor(address _curveRouter) { + curveRouter = ICurveRouter(_curveRouter); + } + + function swap(uint256 amountIn, bytes calldata data) + external + payable + returns (uint256) + { + CurveRouterParams memory params = _decodeData(data); + IERC20(params.route[0]).approve(address(curveRouter), amountIn); + return curveRouter.exchange( + params.route, + params.swapParams, + amountIn, + params.minAmountOut, + params.pools, + params.receiver + ); + } + + function _decodeData(bytes calldata data) + internal + pure + returns (CurveRouterParams memory params) + { + return abi.decode(data, (CurveRouterParams)); + } +} diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 1a1fff7..b99f102 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -76,6 +76,10 @@ contract Constants is Test, BaseConstants { address PANCAKESWAPV3_DEPLOYER_ETHEREUM = 0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9; + // Curve + address TRICRYPTO_USDC_WBTC_WETH = + 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B; + // Uniswap universal router address UNIVERSAL_ROUTER = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af; @@ -94,6 +98,9 @@ contract Constants is Test, BaseConstants { bytes32 PANCAKEV3_POOL_CODE_INIT_HASH = 0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2; + // Curve router + address CURVE_ROUTER = 0x16C6521Dff6baB339122a0FE25a9116693265353; + /** * @dev Deploys a dummy contract with non-empty bytecode */ diff --git a/foundry/test/CurveRouterGasTest.t.sol b/foundry/test/CurveRouterGasTest.t.sol new file mode 100644 index 0000000..38c8cf4 --- /dev/null +++ b/foundry/test/CurveRouterGasTest.t.sol @@ -0,0 +1,43 @@ +// 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 {console} from "forge-std/console.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_USDC_WBTC_WETH; + 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); + uint256 amountOut = 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 new file mode 100644 index 0000000..ce60c5c --- /dev/null +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "@src/executors/CurveExecutor.sol"; +import {Test} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "../Constants.sol"; + +contract CurveExecutorExposed is CurveExecutor { + constructor(address _curveRouter) CurveExecutor(_curveRouter) {} + + function decodeParams(bytes calldata data) + external + pure + returns (CurveRouterParams memory params) + { + return _decodeData(data); + } +} + +contract CurveExecutorTest is Test, Constants { + using SafeERC20 for IERC20; + + CurveExecutorExposed curveExecutorExposed; + + function setUp() public { + uint256 forkBlock = 22031795; + vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); + curveExecutorExposed = new CurveExecutorExposed(CURVE_ROUTER); + } + + function testDecodeParams() public view { + address[11] memory route; + route[0] = WETH_ADDR; + route[1] = TRICRYPTO_USDC_WBTC_WETH; + 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; + + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(this) + ); + + CurveRouterParams memory params = + curveExecutorExposed.decodeParams(data); + + assertEq(params.route[0], WETH_ADDR); + assertEq(params.route[1], TRICRYPTO_USDC_WBTC_WETH); + 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); + } + + function testSwapCurve() public { + address[11] memory route; + route[0] = WETH_ADDR; + route[1] = TRICRYPTO_USDC_WBTC_WETH; + 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, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(this) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 1861130973); + assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); + } +} From 93bdc86dc665e02d877bec1b749ee5cf7a399e32 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 13 Mar 2025 22:56:29 +0530 Subject: [PATCH 02/15] feat: allow executor to do native swaps, add diff pool type tests Took 4 seconds --- foundry/src/executors/CurveExecutor.sol | 25 +- foundry/test/Constants.sol | 22 ++ foundry/test/executors/CurveExecutor.t.sol | 277 ++++++++++++++++++++- 3 files changed, 307 insertions(+), 17 deletions(-) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 0fd277c..042b494 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -9,26 +9,41 @@ contract CurveExecutor is IExecutor { using SafeERC20 for IERC20; ICurveRouter public immutable curveRouter; + address public immutable ethAddress; - constructor(address _curveRouter) { + constructor(address _curveRouter, address _ethAddress) { curveRouter = ICurveRouter(_curveRouter); + ethAddress = _ethAddress; } + // slither-disable-next-line locked-ether function swap(uint256 amountIn, bytes calldata data) external payable returns (uint256) { CurveRouterParams memory params = _decodeData(data); - IERC20(params.route[0]).approve(address(curveRouter), amountIn); - return curveRouter.exchange( + if (params.route[0] != ethAddress) { + IERC20(params.route[0]).approve(address(curveRouter), amountIn); + + return curveRouter.exchange( params.route, params.swapParams, amountIn, params.minAmountOut, params.pools, params.receiver - ); + ); + } else { + return curveRouter.exchange{value: amountIn}( + params.route, + params.swapParams, + amountIn, + params.minAmountOut, + params.pools, + params.receiver + ); + } } function _decodeData(bytes calldata data) @@ -38,4 +53,6 @@ contract CurveExecutor is IExecutor { { return abi.decode(data, (CurveRouterParams)); } + + receive() external payable {} } diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index b99f102..3459a14 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -30,6 +30,7 @@ contract Constants is Test, BaseConstants { address UNPAUSER = makeAddr("unpauser"); // Assets + address ETH_ADDR = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); address WETH_ADDR = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); address DAI_ADDR = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); address BAL_ADDR = address(0xba100000625a3754423978a60c9317c58a424e3D); @@ -39,6 +40,12 @@ contract Constants is Test, BaseConstants { address USDE_ADDR = address(0x4c9EDD5852cd905f086C759E8383e09bff1E68B3); address USDT_ADDR = address(0xdAC17F958D2ee523a2206206994597C13D831ec7); address PEPE_ADDR = address(0x6982508145454Ce325dDbE47a25d4ec3d2311933); + address STETH_ADDR = address(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); + address LUSD_ADDR = address(0x5f98805A4E8be255a32880FDeC7F6728C6568bA0); + address LDO_ADDR = address(0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32); + address CRV_ADDR = address(0xD533a949740bb3306d119CC777fa900bA034cd52); + address ADAI_ADDR = address(0x028171bCA77440897B824Ca71D1c56caC55b68A3); + address AUSDC_ADDR = address(0xBcca60bB61934080951369a648Fb03DF4F96263C); // Uniswap v2 address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11; @@ -77,8 +84,23 @@ contract Constants is Test, BaseConstants { 0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9; // Curve + // 3pool - Pool type 1 + address TRIPOOL_USDT_USDC_DAI = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; + // Tricrypto - Pool type 3 address TRICRYPTO_USDC_WBTC_WETH = 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B; + // stEth - Pool type 4 + address STETH_POOL = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022; + // LUSD - Pool type 5 + address LUSD_POOL = 0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA; + // Compound - Pool type 6 + address CPOOL = 0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56; + // LDO - Pool type 7 + address LDO_POOL = 0x9409280DC1e6D33AB7A8C6EC03e5763FB61772B5; + // CRV - Pool type 8 + address CRV_POOL = 0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511; + // AAVE - Pool type 0 + address AAVE_POOL = 0xDeBF20617708857ebe4F679508E7b7863a8A8EeE; // Uniswap universal router address UNIVERSAL_ROUTER = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af; diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index ce60c5c..694ae89 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -5,8 +5,26 @@ import "@src/executors/CurveExecutor.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; + +interface ICurvePool { + function coins(uint256 i) external view returns (address); +} + +interface ILendingPool { + function deposit( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) external; + + function withdraw(address asset, uint256 amount, address to) + external + returns (uint256); +} + contract CurveExecutorExposed is CurveExecutor { - constructor(address _curveRouter) CurveExecutor(_curveRouter) {} + constructor(address _curveRouter, address _ethAddress) CurveExecutor(_curveRouter, _ethAddress) {} function decodeParams(bytes calldata data) external @@ -25,7 +43,7 @@ contract CurveExecutorTest is Test, Constants { function setUp() public { uint256 forkBlock = 22031795; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - curveExecutorExposed = new CurveExecutorExposed(CURVE_ROUTER); + curveExecutorExposed = new CurveExecutorExposed(CURVE_ROUTER, ETH_ADDR); } function testDecodeParams() public view { @@ -61,18 +79,52 @@ contract CurveExecutorTest is Test, Constants { assertEq(params.swapParams[0][3], 3); } - function testSwapCurve() public { - address[11] memory route; - route[0] = WETH_ADDR; - route[1] = TRICRYPTO_USDC_WBTC_WETH; - 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 + function testCurveSwapPoolType0() public { + address[11] memory route = _getRoute(ADAI_ADDR, AUSDC_ADDR, AAVE_POOL); + uint256[5][5] memory swapParams = _getSwapParams(AAVE_POOL, ADAI_ADDR, AUSDC_ADDR, 1, 1); + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + dealAaveDai(); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 999734); + assertEq(IERC20(AUSDC_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + } + + function testCurveSwapPoolType1() public { + address[11] memory route = + _getRoute(DAI_ADDR, USDC_ADDR, TRIPOOL_USDT_USDC_DAI); + uint256[5][5] memory swapParams = + _getSwapParams(TRIPOOL_USDT_USDC_DAI, DAI_ADDR, USDC_ADDR, 1, 1); + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(DAI_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(this) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 999796); + assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); + } + + function testCurveSwapPoolType3() public { + address[11] memory route = + _getRoute(WETH_ADDR, USDC_ADDR, TRICRYPTO_USDC_WBTC_WETH); + uint256[5][5] memory swapParams = + _getSwapParams(TRICRYPTO_USDC_WBTC_WETH, WETH_ADDR, USDC_ADDR, 1, 3); uint256 amountIn = 1 ether; uint256 minAmountOut = 0; @@ -88,4 +140,203 @@ contract CurveExecutorTest is Test, Constants { assertEq(amountOut, 1861130973); assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); } + + function testCurveSwapPoolType4() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap( + amountIn, data + ); + + assertTrue(amountOut >= 1 ether); + assertEq(IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)), amountOut - 1); // Gets 1 wei less than amountOut + + // Now reverse the swap + amountIn = amountOut - 1; + route = + _getRoute(STETH_ADDR, ETH_ADDR, STETH_POOL); + swapParams = + _getSwapParams(STETH_POOL, STETH_ADDR, ETH_ADDR, 1, 1); + + data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + ); + + amountOut = curveExecutorExposed.swap( + amountIn, data + ); + + assertEq(address(curveExecutorExposed).balance, 999800010006950374); + } + + + function testCurveSwapPoolType5() public { + address[11] memory route = + _getRoute(LUSD_ADDR, USDT_ADDR, LUSD_POOL); + uint256[5][5] memory swapParams = + _getSwapParams(LUSD_POOL, LUSD_ADDR, USDT_ADDR, 2, 1); + + // pool.coins(index) reverts, defaulting tokenOut index to 0 + swapParams[0][1] = 3; + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(LUSD_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap( + amountIn, data + ); + + assertEq(amountOut, 1001785); + assertEq(IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + } + + + function testCurveSwapPoolType6() public { + address[11] memory route = + _getRoute(DAI_ADDR, USDC_ADDR, CPOOL); + uint256[5][5] memory swapParams = + _getSwapParams(CPOOL, DAI_ADDR, USDC_ADDR, 2, 1); + + // pool.coins(index) reverts, defaulting tokenOut index to 0 + swapParams[0][1] = 1; + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(DAI_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap( + amountIn, data); + + assertEq(amountOut, 999549); + assertEq(IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + } + + function testCurveSwapPoolType7() public { + address[11] memory route = _getRoute(WETH_ADDR, LDO_ADDR, LDO_POOL); + uint256[5][5] memory swapParams = _getSwapParams(LDO_POOL, WETH_ADDR, LDO_ADDR, 1, 4); + + // pool.coins(index) reverts, defaulting tokenOut index to 0 + swapParams[0][1] = 1; + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(WETH_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap( + amountIn, data); + + + assertEq(amountOut, 2075236672516568049094); + assertEq(IERC20(LDO_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + } + + function testCurveSwapPoolType8() public { + address[11] memory route = _getRoute(CRV_ADDR, WETH_ADDR, CRV_POOL); + uint256[5][5] memory swapParams = _getSwapParams(CRV_POOL, CRV_ADDR, WETH_ADDR, 1, 4); + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(CRV_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap( + amountIn, data); + + assertEq(amountOut, 21806692849); + assertEq(IERC20(WETH_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, + address tokenIn, + address tokenOut, + uint256 swapType, + uint256 poolType + ) internal view returns (uint256[5][5] memory swapParams) { + // Get number of coins in pool and their indices + uint256 coinInIndex; + uint256 coinOutIndex; + uint256 nCoins; + address lastCoinAddress = address(1); + while (lastCoinAddress != address(0)) { + try ICurvePool(pool).coins(nCoins) returns (address coin) { + lastCoinAddress = coin; + nCoins++; + if (coin == tokenIn) { + coinInIndex = nCoins - 1; + } + if (coin == tokenOut) { + coinOutIndex = nCoins - 1; + } + } catch { + lastCoinAddress = address(0); + } + } + + swapParams[0][0] = coinInIndex; + swapParams[0][1] = coinOutIndex; + swapParams[0][2] = swapType; + swapParams[0][3] = poolType; + swapParams[0][4] = nCoins; + } + + + function dealAaveDai() internal { + deal(DAI_ADDR, address(curveExecutorExposed), 100_000 * 10 ** 18); + ILendingPool aave = + ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); + + vm.startPrank(address(curveExecutorExposed)); + IERC20(DAI_ADDR).approve(address(aave), type(uint256).max); + aave.deposit(DAI_ADDR, 100_000 * 10 ** 18, address(curveExecutorExposed), 0); + vm.stopPrank(); + } } + From c3d36ae25fce61f31e9cdc10bc50786726026007 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Fri, 14 Mar 2025 00:52:11 +0530 Subject: [PATCH 03/15] test: add curve swap tests with pools from indexer --- foundry/interfaces/ICurveRouter.sol | 1 + foundry/src/executors/CurveExecutor.sol | 18 +- foundry/test/Constants.sol | 23 ++ foundry/test/executors/CurveExecutor.t.sol | 421 +++++++++++++++++---- 4 files changed, 379 insertions(+), 84 deletions(-) diff --git a/foundry/interfaces/ICurveRouter.sol b/foundry/interfaces/ICurveRouter.sol index 00bb9e2..57eb17a 100644 --- a/foundry/interfaces/ICurveRouter.sol +++ b/foundry/interfaces/ICurveRouter.sol @@ -11,6 +11,7 @@ interface ICurveRouter { address receiver ) external payable returns (uint256); + // slither-disable-next-line naming-convention function get_dy( address[] memory route, uint256[] memory swapParams, diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 042b494..fc5748b 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -5,6 +5,8 @@ import "@interfaces/IExecutor.sol"; import "@interfaces/ICurveRouter.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +error CurveExecutor__InvalidAddresses(); + contract CurveExecutor is IExecutor { using SafeERC20 for IERC20; @@ -12,6 +14,9 @@ contract CurveExecutor is IExecutor { address public immutable ethAddress; constructor(address _curveRouter, address _ethAddress) { + if (_curveRouter == address(0) || _ethAddress == address(0)) { + revert CurveExecutor__InvalidAddresses(); + } curveRouter = ICurveRouter(_curveRouter); ethAddress = _ethAddress; } @@ -24,15 +29,16 @@ contract CurveExecutor is IExecutor { { CurveRouterParams memory params = _decodeData(data); if (params.route[0] != ethAddress) { + // slither-disable-next-line unused-return IERC20(params.route[0]).approve(address(curveRouter), amountIn); return curveRouter.exchange( - params.route, - params.swapParams, - amountIn, - params.minAmountOut, - params.pools, - params.receiver + params.route, + params.swapParams, + amountIn, + params.minAmountOut, + params.pools, + params.receiver ); } else { return curveRouter.exchange{value: amountIn}( diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 3459a14..567b201 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -46,6 +46,12 @@ contract Constants is Test, BaseConstants { address CRV_ADDR = address(0xD533a949740bb3306d119CC777fa900bA034cd52); address ADAI_ADDR = address(0x028171bCA77440897B824Ca71D1c56caC55b68A3); address AUSDC_ADDR = address(0xBcca60bB61934080951369a648Fb03DF4F96263C); + address SUSD_ADDR = address(0x57Ab1ec28D129707052df4dF418D58a2D46d5f51); + address FRAX_ADDR = address(0x853d955aCEf822Db058eb8505911ED77F175b99e); + address DOLA_ADDR = address(0x865377367054516e17014CcdED1e7d814EDC9ce4); + address XYO_ADDR = address(0x55296f69f40Ea6d20E478533C15A6B08B654E758); + address UWU_ADDR = address(0x55C08ca52497e2f1534B59E2917BF524D4765257); + address CRVUSD_ADDR = address(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E); // Uniswap v2 address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11; @@ -102,6 +108,20 @@ contract Constants is Test, BaseConstants { // AAVE - Pool type 0 address AAVE_POOL = 0xDeBF20617708857ebe4F679508E7b7863a8A8EeE; + // BASE META POOL + address FRAXPYUSD_ADDR = address(0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb); + + // Curve pools taken from the substreams + address TRICRYPTO_USDT_WETH_WBTC = + 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46; + address SUSD_POOL = 0xA5407eAE9Ba41422680e2e00537571bcC53efBfD; + address FRAX_USDC_POOL = 0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2; + address USDE_USDC_POOL = 0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72; + address DOLA_FRAXPYUSD_POOL = 0xef484de8C07B6e2d732A92B5F78e81B38f99f95E; + address WETH_XYO_POOL = 0x99e09ee2d6Bb16c0F5ADDfEA649dbB2C1d524624; + address UWU_WETH_POOL = 0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71; + address CRVUSD_USDT_POOL = 0x390f3595bCa2Df7d23783dFd126427CCeb997BF4; + // Uniswap universal router address UNIVERSAL_ROUTER = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af; @@ -123,6 +143,9 @@ contract Constants is Test, BaseConstants { // Curve router address CURVE_ROUTER = 0x16C6521Dff6baB339122a0FE25a9116693265353; + // Curve meta registry + address CURVE_META_REGISTRY = 0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC; + /** * @dev Deploys a dummy contract with non-empty bytecode */ diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 694ae89..32572cd 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -5,11 +5,18 @@ import "@src/executors/CurveExecutor.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; - interface ICurvePool { function coins(uint256 i) external view returns (address); } +interface MetaRegistry { + function get_n_coins(address pool) external view returns (uint256); + function get_coin_indices(address pool, address from, address to) + external + view + returns (int128, int128, bool); +} + interface ILendingPool { function deposit( address asset, @@ -24,7 +31,9 @@ interface ILendingPool { } contract CurveExecutorExposed is CurveExecutor { - constructor(address _curveRouter, address _ethAddress) CurveExecutor(_curveRouter, _ethAddress) {} + constructor(address _curveRouter, address _ethAddress) + CurveExecutor(_curveRouter, _ethAddress) + {} function decodeParams(bytes calldata data) external @@ -39,11 +48,13 @@ contract CurveExecutorTest is Test, Constants { using SafeERC20 for IERC20; CurveExecutorExposed curveExecutorExposed; + MetaRegistry metaRegistry; function setUp() public { uint256 forkBlock = 22031795; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); curveExecutorExposed = new CurveExecutorExposed(CURVE_ROUTER, ETH_ADDR); + metaRegistry = MetaRegistry(CURVE_META_REGISTRY); } function testDecodeParams() public view { @@ -79,10 +90,10 @@ contract CurveExecutorTest is Test, Constants { assertEq(params.swapParams[0][3], 3); } - function testCurveSwapPoolType0() public { address[11] memory route = _getRoute(ADAI_ADDR, AUSDC_ADDR, AAVE_POOL); - uint256[5][5] memory swapParams = _getSwapParams(AAVE_POOL, ADAI_ADDR, AUSDC_ADDR, 1, 1); + uint256[5][5] memory swapParams = + _getSwapParams(AAVE_POOL, ADAI_ADDR, AUSDC_ADDR, 1, 1); uint256 amountIn = 1 ether; uint256 minAmountOut = 0; @@ -90,16 +101,24 @@ contract CurveExecutorTest is Test, Constants { dealAaveDai(); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); assertEq(amountOut, 999734); - assertEq(IERC20(AUSDC_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + assertEq( + IERC20(AUSDC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); } - function testCurveSwapPoolType1() public { + function testCurveSwapPoolType1() public { address[11] memory route = _getRoute(DAI_ADDR, USDC_ADDR, TRIPOOL_USDT_USDC_DAI); uint256[5][5] memory swapParams = @@ -142,8 +161,7 @@ contract CurveExecutorTest is Test, Constants { } function testCurveSwapPoolType4() public { - address[11] memory route = - _getRoute(ETH_ADDR, STETH_ADDR, STETH_POOL); + 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); @@ -153,40 +171,45 @@ contract CurveExecutorTest is Test, Constants { deal(address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) ); - uint256 amountOut = curveExecutorExposed.swap( - amountIn, data - ); + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); assertTrue(amountOut >= 1 ether); - assertEq(IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)), amountOut - 1); // Gets 1 wei less than amountOut + assertEq( + IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut - 1 + ); // Gets 1 wei less than amountOut // Now reverse the swap amountIn = amountOut - 1; - route = - _getRoute(STETH_ADDR, ETH_ADDR, STETH_POOL); - swapParams = - _getSwapParams(STETH_POOL, STETH_ADDR, ETH_ADDR, 1, 1); + route = _getRoute(STETH_ADDR, ETH_ADDR, STETH_POOL); + swapParams = _getSwapParams(STETH_POOL, STETH_ADDR, ETH_ADDR, 1, 1); data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) ); - amountOut = curveExecutorExposed.swap( - amountIn, data - ); + amountOut = curveExecutorExposed.swap(amountIn, data); assertEq(address(curveExecutorExposed).balance, 999800010006950374); } - function testCurveSwapPoolType5() public { - address[11] memory route = - _getRoute(LUSD_ADDR, USDT_ADDR, LUSD_POOL); + address[11] memory route = _getRoute(LUSD_ADDR, USDT_ADDR, LUSD_POOL); uint256[5][5] memory swapParams = - _getSwapParams(LUSD_POOL, LUSD_ADDR, USDT_ADDR, 2, 1); + _getSwapParams(LUSD_POOL, LUSD_ADDR, USDT_ADDR, 2, 1); // pool.coins(index) reverts, defaulting tokenOut index to 0 swapParams[0][1] = 3; @@ -197,24 +220,28 @@ contract CurveExecutorTest is Test, Constants { deal(LUSD_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) ); - uint256 amountOut = curveExecutorExposed.swap( - amountIn, data - ); + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); assertEq(amountOut, 1001785); - assertEq(IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + assertEq( + IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); } - function testCurveSwapPoolType6() public { - address[11] memory route = - _getRoute(DAI_ADDR, USDC_ADDR, CPOOL); + address[11] memory route = _getRoute(DAI_ADDR, USDC_ADDR, CPOOL); uint256[5][5] memory swapParams = - _getSwapParams(CPOOL, DAI_ADDR, USDC_ADDR, 2, 1); - + _getSwapParams(CPOOL, DAI_ADDR, USDC_ADDR, 2, 1); + // pool.coins(index) reverts, defaulting tokenOut index to 0 swapParams[0][1] = 1; @@ -224,19 +251,27 @@ contract CurveExecutorTest is Test, Constants { deal(DAI_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) ); - uint256 amountOut = curveExecutorExposed.swap( - amountIn, data); + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); assertEq(amountOut, 999549); - assertEq(IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + assertEq( + IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); } function testCurveSwapPoolType7() public { address[11] memory route = _getRoute(WETH_ADDR, LDO_ADDR, LDO_POOL); - uint256[5][5] memory swapParams = _getSwapParams(LDO_POOL, WETH_ADDR, LDO_ADDR, 1, 4); + uint256[5][5] memory swapParams = + _getSwapParams(LDO_POOL, WETH_ADDR, LDO_ADDR, 1, 4); // pool.coins(index) reverts, defaulting tokenOut index to 0 swapParams[0][1] = 1; @@ -247,20 +282,33 @@ contract CurveExecutorTest is Test, Constants { deal(WETH_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) ); - uint256 amountOut = curveExecutorExposed.swap( - amountIn, data); - + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); assertEq(amountOut, 2075236672516568049094); - assertEq(IERC20(LDO_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + assertEq( + IERC20(LDO_ADDR).balanceOf(address(curveExecutorExposed)), amountOut + ); } - + function testCurveSwapPoolType8() public { address[11] memory route = _getRoute(CRV_ADDR, WETH_ADDR, CRV_POOL); - uint256[5][5] memory swapParams = _getSwapParams(CRV_POOL, CRV_ADDR, WETH_ADDR, 1, 4); + + // The registry does not have information about the pool. + // We manually set the swap params. + uint256[5][5] memory swapParams; + swapParams[0][0] = 1; + swapParams[0][1] = 0; + swapParams[0][2] = 1; + swapParams[0][3] = 4; + swapParams[0][4] = 2; uint256 amountIn = 1 ether; uint256 minAmountOut = 0; @@ -268,20 +316,253 @@ contract CurveExecutorTest is Test, Constants { deal(CRV_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(curveExecutorExposed) + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) ); - uint256 amountOut = curveExecutorExposed.swap( - amountIn, data); + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); assertEq(amountOut, 21806692849); - assertEq(IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), amountOut); + assertEq( + IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); } + /// These tests are taken from the substreams + function testCurveSwapTricrypto2() public { + address[11] memory route = + _getRoute(WETH_ADDR, WBTC_ADDR, TRICRYPTO_USDT_WETH_WBTC); + uint256[5][5] memory swapParams = + _getSwapParams(TRICRYPTO_USDT_WETH_WBTC, WETH_ADDR, WBTC_ADDR, 1, 3); + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + deal(WETH_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 2279618); + assertEq( + IERC20(WBTC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testCurveSwapSUSD() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(USDC_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 100488101605550214590); + assertEq( + IERC20(SUSD_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testCurveSwapFraxUSDC() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(FRAX_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 998096); + assertEq( + IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testCurveSwapCryptoSwapNGFactoryPlainPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(USDC_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 100064812138999986170); + assertEq( + IERC20(USDE_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testCurveSwapMetaPool() public { + address[11] memory route = + _getRoute(DOLA_ADDR, FRAXPYUSD_ADDR, DOLA_FRAXPYUSD_POOL); + uint256[5][5] memory swapParams = + _getSwapParams(DOLA_FRAXPYUSD_POOL, DOLA_ADDR, FRAXPYUSD_ADDR, 1, 1); + + uint256 amountIn = 100 * 10 ** 6; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(DOLA_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 99688991); + assertEq( + IERC20(FRAXPYUSD_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testCurveSwapWethXyo() public { + address[11] memory route = _getRoute(XYO_ADDR, WETH_ADDR, WETH_XYO_POOL); + uint256[5][5] memory swapParams = + _getSwapParams(WETH_XYO_POOL, XYO_ADDR, WETH_ADDR, 1, 2); + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(XYO_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 6081816039338); + assertEq( + IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testCurveSwapUwuWeth() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(UWU_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 2873786684675); + assertEq( + IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testCurveSwapStableSwapFactoryPlainPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 999910); + assertEq( + IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } function _getRoute(address tokenIn, address tokenOut, address pool) internal @@ -300,43 +581,27 @@ contract CurveExecutorTest is Test, Constants { uint256 swapType, uint256 poolType ) internal view returns (uint256[5][5] memory swapParams) { - // Get number of coins in pool and their indices - uint256 coinInIndex; - uint256 coinOutIndex; - uint256 nCoins; - address lastCoinAddress = address(1); - while (lastCoinAddress != address(0)) { - try ICurvePool(pool).coins(nCoins) returns (address coin) { - lastCoinAddress = coin; - nCoins++; - if (coin == tokenIn) { - coinInIndex = nCoins - 1; - } - if (coin == tokenOut) { - coinOutIndex = nCoins - 1; - } - } catch { - lastCoinAddress = address(0); - } - } + uint256 nCoins = metaRegistry.get_n_coins(pool); + (int128 coinInIndex, int128 coinOutIndex, bool isMetapool) = + metaRegistry.get_coin_indices(pool, tokenIn, tokenOut); - swapParams[0][0] = coinInIndex; - swapParams[0][1] = coinOutIndex; + swapParams[0][0] = uint256(int256(coinInIndex)); + swapParams[0][1] = uint256(int256(coinOutIndex)); swapParams[0][2] = swapType; swapParams[0][3] = poolType; swapParams[0][4] = nCoins; } - - function dealAaveDai() internal { - deal(DAI_ADDR, address(curveExecutorExposed), 100_000 * 10 ** 18); + function dealAaveDai() internal { + deal(DAI_ADDR, address(curveExecutorExposed), 100_000 * 10 ** 18); ILendingPool aave = ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); vm.startPrank(address(curveExecutorExposed)); IERC20(DAI_ADDR).approve(address(aave), type(uint256).max); - aave.deposit(DAI_ADDR, 100_000 * 10 ** 18, address(curveExecutorExposed), 0); + aave.deposit( + DAI_ADDR, 100_000 * 10 ** 18, address(curveExecutorExposed), 0 + ); vm.stopPrank(); } } - From 05ea2c77344151bfd0a67f9c2244153900683da2 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Fri, 14 Mar 2025 20:47:57 +0530 Subject: [PATCH 04/15] chore: cleanup --- foundry/interfaces/ICurveRouter.sol | 59 +- foundry/src/executors/CurveExecutor.sol | 8 +- foundry/test/Constants.sol | 22 +- foundry/test/CurveRouterGasTest.t.sol | 5 +- foundry/test/executors/CurveExecutor.t.sol | 675 ++++++++++----------- 5 files changed, 379 insertions(+), 390 deletions(-) diff --git a/foundry/interfaces/ICurveRouter.sol b/foundry/interfaces/ICurveRouter.sol index 57eb17a..357128c 100644 --- a/foundry/interfaces/ICurveRouter.sol +++ b/foundry/interfaces/ICurveRouter.sol @@ -1,28 +1,29 @@ // 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 { - 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); - // slither-disable-next-line naming-convention - function get_dy( - address[] memory route, - uint256[] memory swapParams, - uint256 amountIn, - address[] memory pools - ) external view returns (uint256); - - -} - - struct CurveRouterParams { + /** + * @notice Parameters for executing a swap through the Curve router + * @dev This struct encapsulates all necessary parameters for a Curve swap + * @param route Array of addresses representing the swap path (tokens and pools) + * @param swapParams 2D array containing swap parameters for each hop: + * [0]: tokenIn index in the pool + * [1]: tokenOut index in the pool + * [2]: swap type (1 for regular swap) + * [3]: pool type (1-4 depending on the Curve pool implementation) + * [4]: number of coins in the pool + * @param amountIn Amount of input token to swap + * @param minAmountOut Minimum amount of output token to receive + * @param pools Array of pool addresses involved in the swap + * @param receiver Address to receive the output tokens + */ + struct CurveRouterParams { address[11] route; uint256[5][5] swapParams; uint256 amountIn; @@ -31,3 +32,21 @@ interface ICurveRouter { address receiver; } + /** + * @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 fc5748b..0010b9a 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -27,7 +27,7 @@ contract CurveExecutor is IExecutor { payable returns (uint256) { - CurveRouterParams memory params = _decodeData(data); + ICurveRouter.CurveRouterParams memory params = _decodeData(data); if (params.route[0] != ethAddress) { // slither-disable-next-line unused-return IERC20(params.route[0]).approve(address(curveRouter), amountIn); @@ -55,10 +55,8 @@ contract CurveExecutor is IExecutor { function _decodeData(bytes calldata data) internal pure - returns (CurveRouterParams memory params) + returns (ICurveRouter.CurveRouterParams memory params) { - return abi.decode(data, (CurveRouterParams)); + return abi.decode(data, (ICurveRouter.CurveRouterParams)); } - - receive() external payable {} } diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index 567b201..a6905d6 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -90,30 +90,16 @@ contract Constants is Test, BaseConstants { 0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9; // Curve - // 3pool - Pool type 1 - address TRIPOOL_USDT_USDC_DAI = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; - // Tricrypto - Pool type 3 - address TRICRYPTO_USDC_WBTC_WETH = - 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B; - // stEth - Pool type 4 + address TRIPOOL = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; + address TRICRYPTO_POOL = 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B; address STETH_POOL = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022; - // LUSD - Pool type 5 address LUSD_POOL = 0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA; - // Compound - Pool type 6 address CPOOL = 0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56; - // LDO - Pool type 7 address LDO_POOL = 0x9409280DC1e6D33AB7A8C6EC03e5763FB61772B5; - // CRV - Pool type 8 address CRV_POOL = 0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511; - // AAVE - Pool type 0 address AAVE_POOL = 0xDeBF20617708857ebe4F679508E7b7863a8A8EeE; - - // BASE META POOL - address FRAXPYUSD_ADDR = address(0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb); - - // Curve pools taken from the substreams - address TRICRYPTO_USDT_WETH_WBTC = - 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46; + address FRAXPYUSD_POOL = address(0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb); + address TRICRYPTO2_POOL = 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46; address SUSD_POOL = 0xA5407eAE9Ba41422680e2e00537571bcC53efBfD; address FRAX_USDC_POOL = 0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2; address USDE_USDC_POOL = 0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72; diff --git a/foundry/test/CurveRouterGasTest.t.sol b/foundry/test/CurveRouterGasTest.t.sol index 38c8cf4..7b86995 100644 --- a/foundry/test/CurveRouterGasTest.t.sol +++ b/foundry/test/CurveRouterGasTest.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.26; import {Constants} from "./Constants.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {console} from "forge-std/console.sol"; import {ICurveRouter} from "../interfaces/ICurveRouter.sol"; contract CurveRouterGasTest is Constants { @@ -17,7 +16,7 @@ contract CurveRouterGasTest is Constants { function testCurveRouter() public { address[11] memory route; route[0] = WETH_ADDR; - route[1] = TRICRYPTO_USDC_WBTC_WETH; + route[1] = TRICRYPTO_POOL; route[2] = USDC_ADDR; uint256[5][5] memory swapParams; @@ -35,7 +34,7 @@ contract CurveRouterGasTest is Constants { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(address(curveRouter), amountIn); - uint256 amountOut = curveRouter.exchange( + 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 32572cd..9771845 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -9,15 +9,21 @@ interface ICurvePool { function coins(uint256 i) external view returns (address); } +// Curve pool registry +// This is the registry that contains the information about the pool +// The naming convention is different because it is in vyper interface MetaRegistry { + // Get the number of coins in the pool function get_n_coins(address pool) external view returns (uint256); + // Get the indices of the coins in the pool function get_coin_indices(address pool, address from, address to) external view returns (int128, int128, bool); } -interface ILendingPool { +// Aave lending pool +interface IAaveLendingPool { function deposit( address asset, uint256 amount, @@ -38,7 +44,7 @@ contract CurveExecutorExposed is CurveExecutor { function decodeParams(bytes calldata data) external pure - returns (CurveRouterParams memory params) + returns (ICurveRouter.CurveRouterParams memory params) { return _decodeData(data); } @@ -58,11 +64,11 @@ contract CurveExecutorTest is Test, Constants { } function testDecodeParams() public view { - address[11] memory route; - route[0] = WETH_ADDR; - route[1] = TRICRYPTO_USDC_WBTC_WETH; - route[2] = USDC_ADDR; + address[11] memory route = + _getRoute(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL); + // 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 @@ -78,11 +84,11 @@ contract CurveExecutorTest is Test, Constants { route, swapParams, amountIn, minAmountOut, pools, address(this) ); - CurveRouterParams memory params = + ICurveRouter.CurveRouterParams memory params = curveExecutorExposed.decodeParams(data); assertEq(params.route[0], WETH_ADDR); - assertEq(params.route[1], TRICRYPTO_USDC_WBTC_WETH); + assertEq(params.route[1], TRICRYPTO_POOL); assertEq(params.route[2], USDC_ADDR); assertEq(params.swapParams[0][0], 2); assertEq(params.swapParams[0][1], 0); @@ -90,7 +96,316 @@ contract CurveExecutorTest is Test, Constants { assertEq(params.swapParams[0][3], 3); } - function testCurveSwapPoolType0() public { + // The following pools are unique and do not have a factory + + function testSwapTriPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(DAI_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(this) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 999796); + assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); + } + + function testSwapStEthPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(this) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertTrue(amountOut >= 1 ether); + assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); // Gets 1 wei less than amountOut + } + + function testSwapTricrypto2Pool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(WETH_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 2279618); + assertEq( + IERC20(WBTC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testSwapSUSDPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(USDC_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 100488101605550214590); + assertEq( + IERC20(SUSD_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testSwapFraxUsdcPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(FRAX_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 998096); + assertEq( + IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf + // - It is a plain pool + + function testSwapUsdeUsdcPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(USDC_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 100064812138999986170); + assertEq( + IERC20(USDE_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf + // - It is a meta pool + + function testSwapDolaFraxPyusdPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(DOLA_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 99688991); + assertEq( + IERC20(FRAXPYUSD_POOL).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + // The following pool is from CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 + + function testSwapWethXyoPool() public { + address[11] memory route = _getRoute(XYO_ADDR, WETH_ADDR, WETH_XYO_POOL); + uint256[5][5] memory swapParams = + _getSwapParams(WETH_XYO_POOL, XYO_ADDR, WETH_ADDR, 1, 2); + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(XYO_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 6081816039338); + assertEq( + IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + // The following pool is from Tricrypto, deployed by factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963 + + function testSwapTricryptoPool() public { + address[11] memory route = + _getRoute(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL); + uint256[5][5] memory swapParams = + _getSwapParams(TRICRYPTO_POOL, WETH_ADDR, USDC_ADDR, 1, 3); + + uint256 amountIn = 1 ether; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(WETH_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, amountIn, minAmountOut, pools, address(this) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 1861130973); + assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); + } + + // The following pool is from Twocrypto, deployed by factory 0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f + + function testSwapUwuWethPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(UWU_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 2873786684675); + assertEq( + IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + // The following pool is from StableSwap, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d + // - It is a plain pool + + function testSwapCrvusdUsdtPool() public { + 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; + uint256 minAmountOut = 0; + address[5] memory pools; + + deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(curveExecutorExposed) + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 999910); + assertEq( + IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + // The following pools were taken from the old curve executor tests + + function testSwapAavePool() public { address[11] memory route = _getRoute(ADAI_ADDR, AUSDC_ADDR, AAVE_POOL); uint256[5][5] memory swapParams = _getSwapParams(AAVE_POOL, ADAI_ADDR, AUSDC_ADDR, 1, 1); @@ -118,102 +433,11 @@ contract CurveExecutorTest is Test, Constants { ); } - function testCurveSwapPoolType1() public { - address[11] memory route = - _getRoute(DAI_ADDR, USDC_ADDR, TRIPOOL_USDT_USDC_DAI); - uint256[5][5] memory swapParams = - _getSwapParams(TRIPOOL_USDT_USDC_DAI, DAI_ADDR, USDC_ADDR, 1, 1); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(DAI_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 999796); - assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); - } - - function testCurveSwapPoolType3() public { - address[11] memory route = - _getRoute(WETH_ADDR, USDC_ADDR, TRICRYPTO_USDC_WBTC_WETH); - uint256[5][5] memory swapParams = - _getSwapParams(TRICRYPTO_USDC_WBTC_WETH, WETH_ADDR, USDC_ADDR, 1, 3); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 1861130973); - assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); - } - - function testCurveSwapPoolType4() public { - 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; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertTrue(amountOut >= 1 ether); - assertEq( - IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - 1 - ); // Gets 1 wei less than amountOut - - // Now reverse the swap - amountIn = amountOut - 1; - route = _getRoute(STETH_ADDR, ETH_ADDR, STETH_POOL); - swapParams = _getSwapParams(STETH_POOL, STETH_ADDR, ETH_ADDR, 1, 1); - - data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(address(curveExecutorExposed).balance, 999800010006950374); - } - - function testCurveSwapPoolType5() public { + function testSwapLusdUsdtPool() public { address[11] memory route = _getRoute(LUSD_ADDR, USDT_ADDR, LUSD_POOL); uint256[5][5] memory swapParams = _getSwapParams(LUSD_POOL, LUSD_ADDR, USDT_ADDR, 2, 1); - // pool.coins(index) reverts, defaulting tokenOut index to 0 - swapParams[0][1] = 3; - uint256 amountIn = 1 ether; uint256 minAmountOut = 0; address[5] memory pools; @@ -237,14 +461,11 @@ contract CurveExecutorTest is Test, Constants { ); } - function testCurveSwapPoolType6() public { + function testSwapCompoundPool() public { address[11] memory route = _getRoute(DAI_ADDR, USDC_ADDR, CPOOL); uint256[5][5] memory swapParams = _getSwapParams(CPOOL, DAI_ADDR, USDC_ADDR, 2, 1); - // pool.coins(index) reverts, defaulting tokenOut index to 0 - swapParams[0][1] = 1; - uint256 amountIn = 1 ether; uint256 minAmountOut = 0; address[5] memory pools; @@ -268,14 +489,11 @@ contract CurveExecutorTest is Test, Constants { ); } - function testCurveSwapPoolType7() public { + function testSwapLdoPool() public { address[11] memory route = _getRoute(WETH_ADDR, LDO_ADDR, LDO_POOL); uint256[5][5] memory swapParams = _getSwapParams(LDO_POOL, WETH_ADDR, LDO_ADDR, 1, 4); - // pool.coins(index) reverts, defaulting tokenOut index to 0 - swapParams[0][1] = 1; - uint256 amountIn = 1 ether; uint256 minAmountOut = 0; address[5] memory pools; @@ -298,7 +516,7 @@ contract CurveExecutorTest is Test, Constants { ); } - function testCurveSwapPoolType8() public { + function testSwapCrvPool() public { address[11] memory route = _getRoute(CRV_ADDR, WETH_ADDR, CRV_POOL); // The registry does not have information about the pool. @@ -333,237 +551,6 @@ contract CurveExecutorTest is Test, Constants { ); } - /// These tests are taken from the substreams - - function testCurveSwapTricrypto2() public { - address[11] memory route = - _getRoute(WETH_ADDR, WBTC_ADDR, TRICRYPTO_USDT_WETH_WBTC); - uint256[5][5] memory swapParams = - _getSwapParams(TRICRYPTO_USDT_WETH_WBTC, WETH_ADDR, WBTC_ADDR, 1, 3); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 2279618); - assertEq( - IERC20(WBTC_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testCurveSwapSUSD() public { - 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; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 100488101605550214590); - assertEq( - IERC20(SUSD_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testCurveSwapFraxUSDC() public { - 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; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(FRAX_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 998096); - assertEq( - IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testCurveSwapCryptoSwapNGFactoryPlainPool() public { - 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; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 100064812138999986170); - assertEq( - IERC20(USDE_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testCurveSwapMetaPool() public { - address[11] memory route = - _getRoute(DOLA_ADDR, FRAXPYUSD_ADDR, DOLA_FRAXPYUSD_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(DOLA_FRAXPYUSD_POOL, DOLA_ADDR, FRAXPYUSD_ADDR, 1, 1); - - uint256 amountIn = 100 * 10 ** 6; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(DOLA_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 99688991); - assertEq( - IERC20(FRAXPYUSD_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testCurveSwapWethXyo() public { - address[11] memory route = _getRoute(XYO_ADDR, WETH_ADDR, WETH_XYO_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(WETH_XYO_POOL, XYO_ADDR, WETH_ADDR, 1, 2); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(XYO_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 6081816039338); - assertEq( - IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testCurveSwapUwuWeth() public { - 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; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(UWU_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 2873786684675); - assertEq( - IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testCurveSwapStableSwapFactoryPlainPool() public { - 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; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed) - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 999910); - assertEq( - IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - function _getRoute(address tokenIn, address tokenOut, address pool) internal pure @@ -582,7 +569,7 @@ contract CurveExecutorTest is Test, Constants { uint256 poolType ) internal view returns (uint256[5][5] memory swapParams) { uint256 nCoins = metaRegistry.get_n_coins(pool); - (int128 coinInIndex, int128 coinOutIndex, bool isMetapool) = + (int128 coinInIndex, int128 coinOutIndex,) = metaRegistry.get_coin_indices(pool, tokenIn, tokenOut); swapParams[0][0] = uint256(int256(coinInIndex)); @@ -594,8 +581,8 @@ contract CurveExecutorTest is Test, Constants { function dealAaveDai() internal { deal(DAI_ADDR, address(curveExecutorExposed), 100_000 * 10 ** 18); - ILendingPool aave = - ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); + IAaveLendingPool aave = + IAaveLendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); vm.startPrank(address(curveExecutorExposed)); IERC20(DAI_ADDR).approve(address(aave), type(uint256).max); From 3054ca042b2f37a709d1b9060e45d93b4b7bd212 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Mon, 17 Mar 2025 18:25:01 +0530 Subject: [PATCH 05/15] chore: rename ethAddress to nativeToken, update tests --- foundry/src/executors/CurveExecutor.sol | 10 +++--- foundry/test/executors/CurveExecutor.t.sol | 38 ++++++++-------------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 0010b9a..c46a138 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -11,14 +11,14 @@ contract CurveExecutor is IExecutor { using SafeERC20 for IERC20; ICurveRouter public immutable curveRouter; - address public immutable ethAddress; + address public immutable nativeTokens; - constructor(address _curveRouter, address _ethAddress) { - if (_curveRouter == address(0) || _ethAddress == address(0)) { + constructor(address _curveRouter, address _nativeTokens) { + if (_curveRouter == address(0) || _nativeTokens == address(0)) { revert CurveExecutor__InvalidAddresses(); } curveRouter = ICurveRouter(_curveRouter); - ethAddress = _ethAddress; + nativeTokens = _nativeTokens; } // slither-disable-next-line locked-ether @@ -28,7 +28,7 @@ contract CurveExecutor is IExecutor { returns (uint256) { ICurveRouter.CurveRouterParams memory params = _decodeData(data); - if (params.route[0] != ethAddress) { + if (params.route[0] != nativeTokens) { // slither-disable-next-line unused-return IERC20(params.route[0]).approve(address(curveRouter), amountIn); diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 9771845..0dd0e30 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -13,16 +13,14 @@ interface ICurvePool { // This is the registry that contains the information about the pool // The naming convention is different because it is in vyper interface MetaRegistry { - // Get the number of coins in the pool function get_n_coins(address pool) external view returns (uint256); - // Get the indices of the coins in the pool + function get_coin_indices(address pool, address from, address to) external view returns (int128, int128, bool); } -// Aave lending pool interface IAaveLendingPool { function deposit( address asset, @@ -37,8 +35,8 @@ interface IAaveLendingPool { } contract CurveExecutorExposed is CurveExecutor { - constructor(address _curveRouter, address _ethAddress) - CurveExecutor(_curveRouter, _ethAddress) + constructor(address _curveRouter, address _nativeToken) + CurveExecutor(_curveRouter, _nativeToken) {} function decodeParams(bytes calldata data) @@ -134,8 +132,8 @@ contract CurveExecutorTest is Test, Constants { uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertTrue(amountOut >= 1 ether); - assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); // Gets 1 wei less than amountOut + assertTrue(amountOut == 1 ether - 1); //// Gets 1 wei less than amountOut + assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); } function testSwapTricrypto2Pool() public { @@ -224,10 +222,9 @@ contract CurveExecutorTest is Test, Constants { ); } - // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - // - It is a plain pool - function testSwapUsdeUsdcPool() public { + // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf + // - It is a plain pool address[11] memory route = _getRoute(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL); uint256[5][5] memory swapParams = @@ -256,10 +253,9 @@ contract CurveExecutorTest is Test, Constants { ); } - // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - // - It is a meta pool - function testSwapDolaFraxPyusdPool() public { + // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf + // - It is a meta pool address[11] memory route = _getRoute(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL); uint256[5][5] memory swapParams = @@ -288,9 +284,8 @@ contract CurveExecutorTest is Test, Constants { ); } - // The following pool is from CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - function testSwapWethXyoPool() public { + // The following pool is from CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 address[11] memory route = _getRoute(XYO_ADDR, WETH_ADDR, WETH_XYO_POOL); uint256[5][5] memory swapParams = _getSwapParams(WETH_XYO_POOL, XYO_ADDR, WETH_ADDR, 1, 2); @@ -318,9 +313,8 @@ contract CurveExecutorTest is Test, Constants { ); } - // The following pool is from Tricrypto, deployed by factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963 - function testSwapTricryptoPool() public { + // The following pool is from Tricrypto, deployed by factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963 address[11] memory route = _getRoute(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL); uint256[5][5] memory swapParams = @@ -341,9 +335,8 @@ contract CurveExecutorTest is Test, Constants { assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); } - // The following pool is from Twocrypto, deployed by factory 0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f - function testSwapUwuWethPool() public { + // The following pool is from Twocrypto, 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); @@ -371,10 +364,9 @@ contract CurveExecutorTest is Test, Constants { ); } - // The following pool is from StableSwap, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d - // - It is a plain pool - function testSwapCrvusdUsdtPool() public { + // The following pool is from StableSwap, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d + // - It is a plain pool address[11] memory route = _getRoute(CRVUSD_ADDR, USDT_ADDR, CRVUSD_USDT_POOL); uint256[5][5] memory swapParams = @@ -403,8 +395,6 @@ contract CurveExecutorTest is Test, Constants { ); } - // The following pools were taken from the old curve executor tests - function testSwapAavePool() public { address[11] memory route = _getRoute(ADAI_ADDR, AUSDC_ADDR, AAVE_POOL); uint256[5][5] memory swapParams = From 826eca4a804eea8f247e3282b2eef0c5cbc5a0e7 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Mon, 17 Mar 2025 19:32:53 +0530 Subject: [PATCH 06/15] refactor: move swap params from interface to executor, add needsApproval --- foundry/interfaces/ICurveRouter.sol | 25 --------- foundry/src/executors/CurveExecutor.sol | 56 ++++++++++---------- foundry/test/executors/CurveExecutor.t.sol | 60 ++++++++++++++-------- 3 files changed, 69 insertions(+), 72 deletions(-) diff --git a/foundry/interfaces/ICurveRouter.sol b/foundry/interfaces/ICurveRouter.sol index 357128c..29475f3 100644 --- a/foundry/interfaces/ICurveRouter.sol +++ b/foundry/interfaces/ICurveRouter.sol @@ -7,31 +7,6 @@ pragma solidity ^0.8.26; * @dev This interface allows for executing swaps through Curve's router, which can handle different pool types */ interface ICurveRouter { - - /** - * @notice Parameters for executing a swap through the Curve router - * @dev This struct encapsulates all necessary parameters for a Curve swap - * @param route Array of addresses representing the swap path (tokens and pools) - * @param swapParams 2D array containing swap parameters for each hop: - * [0]: tokenIn index in the pool - * [1]: tokenOut index in the pool - * [2]: swap type (1 for regular swap) - * [3]: pool type (1-4 depending on the Curve pool implementation) - * [4]: number of coins in the pool - * @param amountIn Amount of input token to swap - * @param minAmountOut Minimum amount of output token to receive - * @param pools Array of pool addresses involved in the swap - * @param receiver Address to receive the output tokens - */ - struct CurveRouterParams { - address[11] route; - uint256[5][5] swapParams; - uint256 amountIn; - uint256 minAmountOut; - address[5] pools; - address receiver; - } - /** * @notice Executes a token swap through Curve pools * @dev This function handles the routing of tokens through one or more Curve pools diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index c46a138..df13894 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -11,14 +11,24 @@ contract CurveExecutor is IExecutor { using SafeERC20 for IERC20; ICurveRouter public immutable curveRouter; - address public immutable nativeTokens; + address public immutable nativeToken; - constructor(address _curveRouter, address _nativeTokens) { - if (_curveRouter == address(0) || _nativeTokens == address(0)) { + struct SwapParams { + address[11] route; + uint256[5][5] swapParams; + uint256 amountIn; + uint256 minAmountOut; + address[5] pools; + address receiver; + bool needsApproval; + } + + constructor(address _curveRouter, address _nativeToken) { + if (_curveRouter == address(0) || _nativeToken == address(0)) { revert CurveExecutor__InvalidAddresses(); } curveRouter = ICurveRouter(_curveRouter); - nativeTokens = _nativeTokens; + nativeToken = _nativeToken; } // slither-disable-next-line locked-ether @@ -27,36 +37,30 @@ contract CurveExecutor is IExecutor { payable returns (uint256) { - ICurveRouter.CurveRouterParams memory params = _decodeData(data); - if (params.route[0] != nativeTokens) { - // slither-disable-next-line unused-return - IERC20(params.route[0]).approve(address(curveRouter), amountIn); + SwapParams memory params = _decodeData(data); - return curveRouter.exchange( - params.route, - params.swapParams, - amountIn, - params.minAmountOut, - params.pools, - params.receiver - ); - } else { - return curveRouter.exchange{value: amountIn}( - params.route, - params.swapParams, - amountIn, - params.minAmountOut, - params.pools, - params.receiver + if (params.needsApproval) { + // slither-disable-next-line unused-return + IERC20(params.route[0]).approve( + address(curveRouter), type(uint256).max ); } + // Only add the value parameter when the first token is the native token + return curveRouter.exchange{value: params.route[0] == nativeToken ? amountIn : 0}( + params.route, + params.swapParams, + amountIn, + params.minAmountOut, + params.pools, + params.receiver + ); } function _decodeData(bytes calldata data) internal pure - returns (ICurveRouter.CurveRouterParams memory params) + returns (SwapParams memory params) { - return abi.decode(data, (ICurveRouter.CurveRouterParams)); + return abi.decode(data, (SwapParams)); } } diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 0dd0e30..b09ecb2 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -42,7 +42,7 @@ contract CurveExecutorExposed is CurveExecutor { function decodeParams(bytes calldata data) external pure - returns (ICurveRouter.CurveRouterParams memory params) + returns (SwapParams memory params) { return _decodeData(data); } @@ -79,10 +79,10 @@ contract CurveExecutorTest is Test, Constants { address[5] memory pools; bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this) + route, swapParams, amountIn, minAmountOut, pools, address(this), true ); - ICurveRouter.CurveRouterParams memory params = + CurveExecutor.SwapParams memory params = curveExecutorExposed.decodeParams(data); assertEq(params.route[0], WETH_ADDR); @@ -92,6 +92,11 @@ contract CurveExecutorTest is Test, Constants { 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.amountIn, amountIn); + assertEq(params.minAmountOut, minAmountOut); + assertEq(params.receiver, address(this)); + assertEq(params.needsApproval, true); } // The following pools are unique and do not have a factory @@ -107,7 +112,7 @@ contract CurveExecutorTest is Test, Constants { deal(DAI_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this) + route, swapParams, amountIn, minAmountOut, pools, address(this), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -127,13 +132,13 @@ contract CurveExecutorTest is Test, Constants { deal(address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this) + route, swapParams, amountIn, minAmountOut, pools, address(this), false ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertTrue(amountOut == 1 ether - 1); //// Gets 1 wei less than amountOut - assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); + assertTrue(amountOut == 1001072414418410898); + assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); //// Gets 1 wei less than amountOut } function testSwapTricrypto2Pool() public { @@ -153,7 +158,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -181,7 +187,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -210,7 +217,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -241,7 +249,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -272,7 +281,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -301,7 +311,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -326,7 +337,7 @@ contract CurveExecutorTest is Test, Constants { deal(WETH_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this) + route, swapParams, amountIn, minAmountOut, pools, address(this), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -352,7 +363,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -383,7 +395,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -411,7 +424,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -439,7 +453,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -467,7 +482,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -495,7 +511,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -529,7 +546,8 @@ contract CurveExecutorTest is Test, Constants { amountIn, minAmountOut, pools, - address(curveExecutorExposed) + address(curveExecutorExposed), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); From 07d63233167c96384662f9e8d784668440071211 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Mon, 17 Mar 2025 22:15:35 +0530 Subject: [PATCH 07/15] chore: fmt --- foundry/src/executors/CurveExecutor.sol | 4 ++- foundry/test/executors/CurveExecutor.t.sol | 34 ++++++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index df13894..891cab2 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -46,7 +46,9 @@ contract CurveExecutor is IExecutor { ); } // Only add the value parameter when the first token is the native token - return curveRouter.exchange{value: params.route[0] == nativeToken ? amountIn : 0}( + return curveRouter.exchange{ + value: params.route[0] == nativeToken ? amountIn : 0 + }( params.route, params.swapParams, amountIn, diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index b09ecb2..39c84cc 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -79,7 +79,13 @@ contract CurveExecutorTest is Test, Constants { address[5] memory pools; bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this), true + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(this), + true ); CurveExecutor.SwapParams memory params = @@ -112,7 +118,13 @@ contract CurveExecutorTest is Test, Constants { deal(DAI_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this), true + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(this), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -132,12 +144,18 @@ contract CurveExecutorTest is Test, Constants { deal(address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this), false + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(this), + false ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - assertTrue(amountOut == 1001072414418410898); + assertTrue(amountOut == 1001072414418410898); assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); //// Gets 1 wei less than amountOut } @@ -337,7 +355,13 @@ contract CurveExecutorTest is Test, Constants { deal(WETH_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, swapParams, amountIn, minAmountOut, pools, address(this), true + route, + swapParams, + amountIn, + minAmountOut, + pools, + address(this), + true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); From 42d1ab36fd71af7a10d17120e5f14edce9f6422a Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 18 Mar 2025 18:18:11 +0530 Subject: [PATCH 08/15] fix: fix slither CI action Took 5 seconds --- .github/workflows/slither.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml index dc18ce8..5557211 100644 --- a/.github/workflows/slither.yml +++ b/.github/workflows/slither.yml @@ -11,7 +11,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + - name: Build the contracts + run: forge build --build-info + with: + target: "foundry/" - uses: crytic/slither-action@f197989dea5b53e986d0f88c60a034ddd77ec9a8 with: - target: 'foundry/' - slither-args: '--filter-paths foundry/lib/' + target: "foundry/" + ignore-compile: true + slither-args: "--filter-paths foundry/lib/" \ No newline at end of file From 9e2a9f5329f798990d4dde3e7e76af4896aef6f6 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Tue, 18 Mar 2025 23:41:53 +0530 Subject: [PATCH 09/15] fix: resolve pr comments Took 2 seconds --- foundry/src/executors/CurveExecutor.sol | 7 +- foundry/test/executors/CurveExecutor.t.sol | 273 ++------------------- 2 files changed, 19 insertions(+), 261 deletions(-) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index 891cab2..b45a3f4 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -16,9 +16,7 @@ contract CurveExecutor is IExecutor { struct SwapParams { address[11] route; uint256[5][5] swapParams; - uint256 amountIn; uint256 minAmountOut; - address[5] pools; address receiver; bool needsApproval; } @@ -45,7 +43,8 @@ contract CurveExecutor is IExecutor { address(curveRouter), type(uint256).max ); } - // Only add the value parameter when the first token is the native token + // slither-disable-next-line uninitialized-local + address[5] memory pools; return curveRouter.exchange{ value: params.route[0] == nativeToken ? amountIn : 0 }( @@ -53,7 +52,7 @@ contract CurveExecutor is IExecutor { params.swapParams, amountIn, params.minAmountOut, - params.pools, + pools, params.receiver ); } diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 39c84cc..430e720 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -74,19 +74,10 @@ contract CurveExecutorTest is Test, Constants { swapParams[0][3] = 3; // pool type swapParams[0][4] = 3; // n_coins - uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(this), - true - ); + bytes memory data = + abi.encode(route, swapParams, minAmountOut, address(this), true); CurveExecutor.SwapParams memory params = curveExecutorExposed.decodeParams(data); @@ -99,7 +90,6 @@ contract CurveExecutorTest is Test, Constants { assertEq(params.swapParams[0][2], 1); assertEq(params.swapParams[0][3], 3); assertEq(params.swapParams[0][4], 3); - assertEq(params.amountIn, amountIn); assertEq(params.minAmountOut, minAmountOut); assertEq(params.receiver, address(this)); assertEq(params.needsApproval, true); @@ -114,18 +104,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(DAI_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(this), - true - ); + bytes memory data = + abi.encode(route, swapParams, minAmountOut, address(this), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -140,18 +122,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(this), - false - ); + bytes memory data = + abi.encode(route, swapParams, minAmountOut, address(this), false); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -167,17 +141,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(WETH_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -196,17 +163,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 100 * 10 ** 6; uint256 minAmountOut = 0; - address[5] memory pools; deal(USDC_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -226,17 +186,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(FRAX_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -258,17 +211,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 100 * 10 ** 6; uint256 minAmountOut = 0; - address[5] memory pools; deal(USDC_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -290,17 +236,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 100 * 10 ** 6; uint256 minAmountOut = 0; - address[5] memory pools; deal(DOLA_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -320,17 +259,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(XYO_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -351,18 +283,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(this), - true - ); + bytes memory data = + abi.encode(route, swapParams, minAmountOut, address(this), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -378,17 +302,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(UWU_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -410,17 +327,10 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; uint256 minAmountOut = 0; - address[5] memory pools; deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn); bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true + route, swapParams, minAmountOut, address(curveExecutorExposed), true ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -432,157 +342,6 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapAavePool() public { - address[11] memory route = _getRoute(ADAI_ADDR, AUSDC_ADDR, AAVE_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(AAVE_POOL, ADAI_ADDR, AUSDC_ADDR, 1, 1); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - dealAaveDai(); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 999734); - assertEq( - IERC20(AUSDC_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testSwapLusdUsdtPool() public { - address[11] memory route = _getRoute(LUSD_ADDR, USDT_ADDR, LUSD_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(LUSD_POOL, LUSD_ADDR, USDT_ADDR, 2, 1); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(LUSD_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 1001785); - assertEq( - IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testSwapCompoundPool() public { - address[11] memory route = _getRoute(DAI_ADDR, USDC_ADDR, CPOOL); - uint256[5][5] memory swapParams = - _getSwapParams(CPOOL, DAI_ADDR, USDC_ADDR, 2, 1); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(DAI_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 999549); - assertEq( - IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - - function testSwapLdoPool() public { - address[11] memory route = _getRoute(WETH_ADDR, LDO_ADDR, LDO_POOL); - uint256[5][5] memory swapParams = - _getSwapParams(LDO_POOL, WETH_ADDR, LDO_ADDR, 1, 4); - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 2075236672516568049094); - assertEq( - IERC20(LDO_ADDR).balanceOf(address(curveExecutorExposed)), amountOut - ); - } - - function testSwapCrvPool() public { - address[11] memory route = _getRoute(CRV_ADDR, WETH_ADDR, CRV_POOL); - - // The registry does not have information about the pool. - // We manually set the swap params. - uint256[5][5] memory swapParams; - swapParams[0][0] = 1; - swapParams[0][1] = 0; - swapParams[0][2] = 1; - swapParams[0][3] = 4; - swapParams[0][4] = 2; - - uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - address[5] memory pools; - - deal(CRV_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, - swapParams, - amountIn, - minAmountOut, - pools, - address(curveExecutorExposed), - true - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 21806692849); - assertEq( - IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - function _getRoute(address tokenIn, address tokenOut, address pool) internal pure From 3a08c0d71dabe5406de6816a83341fa462bac11b Mon Sep 17 00:00:00 2001 From: royvardhan Date: Wed, 19 Mar 2025 22:55:56 +0530 Subject: [PATCH 10/15] test: test two more pool types --- foundry/test/Constants.sol | 6 ++- foundry/test/executors/CurveExecutor.t.sol | 51 +++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index a6905d6..c1d0217 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -52,7 +52,9 @@ contract Constants is Test, BaseConstants { address XYO_ADDR = address(0x55296f69f40Ea6d20E478533C15A6B08B654E758); address UWU_ADDR = address(0x55C08ca52497e2f1534B59E2917BF524D4765257); address CRVUSD_ADDR = address(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E); - + address WSTTAO_ADDR = address(0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67); + address WTAO_ADDR = address(0x77E06c9eCCf2E797fd462A92B6D7642EF85b0A44); + address BSGG_ADDR = address(0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3); // Uniswap v2 address WETH_DAI_POOL = 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11; address DAI_USDC_POOL = 0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5; @@ -107,6 +109,8 @@ contract Constants is Test, BaseConstants { address WETH_XYO_POOL = 0x99e09ee2d6Bb16c0F5ADDfEA649dbB2C1d524624; address UWU_WETH_POOL = 0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71; address CRVUSD_USDT_POOL = 0x390f3595bCa2Df7d23783dFd126427CCeb997BF4; + address WSTTAO_WTAO_POOL = 0xf2DCf6336D8250754B4527f57b275b19c8D5CF88; + address BSGG_USDT_POOL = 0x5500307Bcf134E5851FB4D7D8D1Dc556dCdB84B4; // Uniswap universal router address UNIVERSAL_ROUTER = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af; diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 430e720..e4327d3 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -252,7 +252,7 @@ contract CurveExecutorTest is Test, Constants { } function testSwapWethXyoPool() public { - // The following pool is from CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 + // The following pool is from CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - with ETH address[11] memory route = _getRoute(XYO_ADDR, WETH_ADDR, WETH_XYO_POOL); uint256[5][5] memory swapParams = _getSwapParams(WETH_XYO_POOL, XYO_ADDR, WETH_ADDR, 1, 2); @@ -342,6 +342,55 @@ contract CurveExecutorTest is Test, Constants { ); } + function testSwapWsttaoWtaoPool() public { + // The following pool is deployed by factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 + // - It is a 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 + uint256 minAmountOut = 0; + + deal(WTAO_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, minAmountOut, address(curveExecutorExposed), true + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 32797923609); + assertEq( + IERC20(WSTTAO_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + + function testSwapBsggUsdtPool() public { + // The following pool is from 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; + uint256 minAmountOut = 0; + + deal(BSGG_ADDR, address(curveExecutorExposed), amountIn); + bytes memory data = abi.encode( + route, swapParams, minAmountOut, address(curveExecutorExposed), true + ); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 23429); + assertEq( + IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), + amountOut + ); + } + function _getRoute(address tokenIn, address tokenOut, address pool) internal pure From f468a7831a86eef96682504bc93207f33b28cf17 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 3 Apr 2025 12:12:24 +0100 Subject: [PATCH 11/15] fix: Improve curve executor tests and docstrings --- don't change below this line --- ENG-4305 Took 34 minutes Took 8 seconds --- foundry/src/executors/CurveExecutor.sol | 55 +++++-- foundry/test/Constants.sol | 2 +- foundry/test/executors/CurveExecutor.t.sol | 180 +++++++++------------ 3 files changed, 117 insertions(+), 120 deletions(-) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index b45a3f4..a3e8535 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -13,10 +13,42 @@ contract CurveExecutor is IExecutor { ICurveRouter public immutable curveRouter; address public immutable nativeToken; - struct SwapParams { + /** + * @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; - uint256 minAmountOut; address receiver; bool needsApproval; } @@ -35,7 +67,7 @@ contract CurveExecutor is IExecutor { payable returns (uint256) { - SwapParams memory params = _decodeData(data); + CurveSwapParams memory params = _decodeData(data); if (params.needsApproval) { // slither-disable-next-line unused-return @@ -47,21 +79,18 @@ contract CurveExecutor is IExecutor { address[5] memory pools; return curveRouter.exchange{ value: params.route[0] == nativeToken ? amountIn : 0 - }( - params.route, - params.swapParams, - amountIn, - params.minAmountOut, - pools, - params.receiver - ); + }(params.route, params.swapParams, amountIn, 0, pools, params.receiver); } function _decodeData(bytes calldata data) internal pure - returns (SwapParams memory params) + returns (CurveSwapParams memory params) { - return abi.decode(data, (SwapParams)); + return abi.decode(data, (CurveSwapParams)); + } + + receive() external payable { + require(msg.sender.code.length != 0); } } diff --git a/foundry/test/Constants.sol b/foundry/test/Constants.sol index c1d0217..50a3260 100644 --- a/foundry/test/Constants.sol +++ b/foundry/test/Constants.sol @@ -106,7 +106,7 @@ contract Constants is Test, BaseConstants { address FRAX_USDC_POOL = 0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2; address USDE_USDC_POOL = 0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72; address DOLA_FRAXPYUSD_POOL = 0xef484de8C07B6e2d732A92B5F78e81B38f99f95E; - address WETH_XYO_POOL = 0x99e09ee2d6Bb16c0F5ADDfEA649dbB2C1d524624; + address ETH_XYO_POOL = 0x99e09ee2d6Bb16c0F5ADDfEA649dbB2C1d524624; address UWU_WETH_POOL = 0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71; address CRVUSD_USDT_POOL = 0x390f3595bCa2Df7d23783dFd126427CCeb997BF4; address WSTTAO_WTAO_POOL = 0xf2DCf6336D8250754B4527f57b275b19c8D5CF88; diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index e4327d3..77d0dfa 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -42,7 +42,7 @@ contract CurveExecutorExposed is CurveExecutor { function decodeParams(bytes calldata data) external pure - returns (SwapParams memory params) + returns (CurveSwapParams memory params) { return _decodeData(data); } @@ -74,12 +74,9 @@ contract CurveExecutorTest is Test, Constants { swapParams[0][3] = 3; // pool type swapParams[0][4] = 3; // n_coins - uint256 minAmountOut = 0; + bytes memory data = abi.encode(route, swapParams, address(this), true); - bytes memory data = - abi.encode(route, swapParams, minAmountOut, address(this), true); - - CurveExecutor.SwapParams memory params = + CurveExecutor.CurveSwapParams memory params = curveExecutorExposed.decodeParams(data); assertEq(params.route[0], WETH_ADDR); @@ -90,24 +87,20 @@ contract CurveExecutorTest is Test, Constants { assertEq(params.swapParams[0][2], 1); assertEq(params.swapParams[0][3], 3); assertEq(params.swapParams[0][4], 3); - assertEq(params.minAmountOut, minAmountOut); assertEq(params.receiver, address(this)); assertEq(params.needsApproval, true); } - // The following pools are unique and do not have a factory - - function testSwapTriPool() public { + 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; - uint256 minAmountOut = 0; deal(DAI_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, minAmountOut, address(this), true); + bytes memory data = abi.encode(route, swapParams, address(this), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -115,17 +108,16 @@ contract CurveExecutorTest is Test, Constants { assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); } - function testSwapStEthPool() public { + 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; - uint256 minAmountOut = 0; deal(address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, minAmountOut, address(this), false); + bytes memory data = abi.encode(route, swapParams, address(this), false); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -133,19 +125,18 @@ contract CurveExecutorTest is Test, Constants { assertEq(IERC20(STETH_ADDR).balanceOf(address(this)), amountOut - 1); //// Gets 1 wei less than amountOut } - function testSwapTricrypto2Pool() public { + 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; - uint256 minAmountOut = 0; deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -156,18 +147,17 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapSUSDPool() public { + 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; - uint256 minAmountOut = 0; deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -178,19 +168,18 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapFraxUsdcPool() public { + 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; - uint256 minAmountOut = 0; deal(FRAX_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -201,21 +190,18 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapUsdeUsdcPool() public { - // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - // - It is a plain pool + 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; - uint256 minAmountOut = 0; deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -226,21 +212,18 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapDolaFraxPyusdPool() public { - // The following pool is from CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - // - It is a meta pool + 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; - uint256 minAmountOut = 0; deal(DOLA_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -251,42 +234,59 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapWethXyoPool() public { - // The following pool is from CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - with ETH - address[11] memory route = _getRoute(XYO_ADDR, WETH_ADDR, WETH_XYO_POOL); + 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(WETH_XYO_POOL, XYO_ADDR, WETH_ADDR, 1, 2); + _getSwapParams(ETH_XYO_POOL, XYO_ADDR, ETH_ADDR, 1, 2); uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; - + 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, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); assertEq(amountOut, 6081816039338); assertEq( - IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)), + address(curveExecutorExposed).balance, initialBalance + amountOut + ); + } + + 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); + + uint256 amountOut = curveExecutorExposed.swap(amountIn, data); + + assertEq(amountOut, 23429); + assertEq( + IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), amountOut ); } - function testSwapTricryptoPool() public { - // The following pool is from Tricrypto, deployed by factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963 + 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, 3); + _getSwapParams(TRICRYPTO_POOL, WETH_ADDR, USDC_ADDR, 1, 2); uint256 amountIn = 1 ether; - uint256 minAmountOut = 0; deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - abi.encode(route, swapParams, minAmountOut, address(this), true); + bytes memory data = abi.encode(route, swapParams, address(this), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -294,19 +294,17 @@ contract CurveExecutorTest is Test, Constants { assertEq(IERC20(USDC_ADDR).balanceOf(address(this)), amountOut); } - function testSwapUwuWethPool() public { - // The following pool is from Twocrypto, deployed by factory 0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f + 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; - uint256 minAmountOut = 0; deal(UWU_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -317,21 +315,18 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapCrvusdUsdtPool() public { - // The following pool is from StableSwap, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d - // - It is a plain pool + 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; - uint256 minAmountOut = 0; deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -342,21 +337,18 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapWsttaoWtaoPool() public { - // The following pool is deployed by factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 - // - It is a plain pool + 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 - uint256 minAmountOut = 0; deal(WTAO_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); + bytes memory data = + abi.encode(route, swapParams, address(curveExecutorExposed), true); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -367,30 +359,6 @@ contract CurveExecutorTest is Test, Constants { ); } - function testSwapBsggUsdtPool() public { - // The following pool is from 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; - uint256 minAmountOut = 0; - - deal(BSGG_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = abi.encode( - route, swapParams, minAmountOut, address(curveExecutorExposed), true - ); - - uint256 amountOut = curveExecutorExposed.swap(amountIn, data); - - assertEq(amountOut, 23429); - assertEq( - IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)), - amountOut - ); - } - function _getRoute(address tokenIn, address tokenOut, address pool) internal pure From 9f2184258aab968f7d83df8a443c1a77b87a3e4c Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 3 Apr 2025 18:14:17 +0100 Subject: [PATCH 12/15] 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 { From 8d75b372e63683e4c67689510fd25265fca09a43 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 4 Apr 2025 09:11:43 +0100 Subject: [PATCH 13/15] ci: Fix slither action --- don't change below this line --- ENG-4305 Took 4 minutes --- .github/workflows/slither.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/slither.yml b/.github/workflows/slither.yml index 5557211..1c5be96 100644 --- a/.github/workflows/slither.yml +++ b/.github/workflows/slither.yml @@ -11,14 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - name: Build the contracts - run: forge build --build-info with: - target: "foundry/" + ref: ${{ github.event.pull_request.head.sha }} + submodules: recursive - uses: crytic/slither-action@f197989dea5b53e986d0f88c60a034ddd77ec9a8 with: - target: "foundry/" - ignore-compile: true - slither-args: "--filter-paths foundry/lib/" \ No newline at end of file + target: 'foundry/' + slither-args: '--filter-paths foundry/lib/' \ No newline at end of file From 49aefc8c2ab25864b4056f44738a96c3905f8396 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 4 Apr 2025 09:13:36 +0100 Subject: [PATCH 14/15] fix: Remove unnecessary test method --- don't change below this line --- ENG-4305 Took 2 minutes --- foundry/test/executors/CurveExecutor.t.sol | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 32c3a3a..c3b9cad 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -335,17 +335,4 @@ contract CurveExecutorTest is Test, Constants { metaRegistry.get_coin_indices(pool, tokenIn, tokenOut); return (coinInIndex, coinOutIndex); } - - function dealAaveDai() internal { - deal(DAI_ADDR, address(curveExecutorExposed), 100_000 * 10 ** 18); - IAaveLendingPool aave = - IAaveLendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); - - vm.startPrank(address(curveExecutorExposed)); - IERC20(DAI_ADDR).approve(address(aave), type(uint256).max); - aave.deposit( - DAI_ADDR, 100_000 * 10 ** 18, address(curveExecutorExposed), 0 - ); - vm.stopPrank(); - } } From f7cdc6f5372629a026d6a5db1bc6462505f3c7be Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 7 Apr 2025 16:04:21 +0000 Subject: [PATCH 15/15] chore(release): 0.77.0 [skip ci] ## [0.77.0](https://github.com/propeller-heads/tycho-execution/compare/0.76.0...0.77.0) (2025-04-07) ### Features * add curve executor with router tests ([7cde513](https://github.com/propeller-heads/tycho-execution/commit/7cde5130d6038916dcb4a6a96c723c366b90da12)) * allow executor to do native swaps, add diff pool type tests ([93bdc86](https://github.com/propeller-heads/tycho-execution/commit/93bdc86dc665e02d877bec1b749ee5cf7a399e32)) * Refactor Curve Executor not to use the router ([9f21842](https://github.com/propeller-heads/tycho-execution/commit/9f2184258aab968f7d83df8a443c1a77b87a3e4c)) ### Bug Fixes * fix slither CI action ([42d1ab3](https://github.com/propeller-heads/tycho-execution/commit/42d1ab36fd71af7a10d17120e5f14edce9f6422a)) * Improve curve executor tests and docstrings ([f468a78](https://github.com/propeller-heads/tycho-execution/commit/f468a7831a86eef96682504bc93207f33b28cf17)) * Remove unnecessary test method ([49aefc8](https://github.com/propeller-heads/tycho-execution/commit/49aefc8c2ab25864b4056f44738a96c3905f8396)) * resolve pr comments ([9e2a9f5](https://github.com/propeller-heads/tycho-execution/commit/9e2a9f5329f798990d4dde3e7e76af4896aef6f6)) --- CHANGELOG.md | 17 +++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ffaa1..2cebf79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## [0.77.0](https://github.com/propeller-heads/tycho-execution/compare/0.76.0...0.77.0) (2025-04-07) + + +### Features + +* add curve executor with router tests ([7cde513](https://github.com/propeller-heads/tycho-execution/commit/7cde5130d6038916dcb4a6a96c723c366b90da12)) +* allow executor to do native swaps, add diff pool type tests ([93bdc86](https://github.com/propeller-heads/tycho-execution/commit/93bdc86dc665e02d877bec1b749ee5cf7a399e32)) +* Refactor Curve Executor not to use the router ([9f21842](https://github.com/propeller-heads/tycho-execution/commit/9f2184258aab968f7d83df8a443c1a77b87a3e4c)) + + +### Bug Fixes + +* fix slither CI action ([42d1ab3](https://github.com/propeller-heads/tycho-execution/commit/42d1ab36fd71af7a10d17120e5f14edce9f6422a)) +* Improve curve executor tests and docstrings ([f468a78](https://github.com/propeller-heads/tycho-execution/commit/f468a7831a86eef96682504bc93207f33b28cf17)) +* Remove unnecessary test method ([49aefc8](https://github.com/propeller-heads/tycho-execution/commit/49aefc8c2ab25864b4056f44738a96c3905f8396)) +* resolve pr comments ([9e2a9f5](https://github.com/propeller-heads/tycho-execution/commit/9e2a9f5329f798990d4dde3e7e76af4896aef6f6)) + ## [0.76.0](https://github.com/propeller-heads/tycho-execution/compare/0.75.1...0.76.0) (2025-04-03) diff --git a/Cargo.lock b/Cargo.lock index 7d69956..8df5aa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4341,7 +4341,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.76.0" +version = "0.77.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 3979f0d..c5bb0d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.76.0" +version = "0.77.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution"