From 93bdc86dc665e02d877bec1b749ee5cf7a399e32 Mon Sep 17 00:00:00 2001 From: royvardhan Date: Thu, 13 Mar 2025 22:56:29 +0530 Subject: [PATCH] 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(); + } } +