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