Merge branch 'main' into feature/gas-optimization

This commit is contained in:
Tamara
2025-04-07 19:59:55 -04:00
committed by Diana Carvalho
7 changed files with 531 additions and 4 deletions

View File

@@ -0,0 +1,133 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "@interfaces/IExecutor.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;
address public immutable nativeToken;
constructor(address _nativeToken) {
if (_nativeToken == address(0)) {
revert CurveExecutor__InvalidAddresses();
}
nativeToken = _nativeToken;
}
// slither-disable-next-line locked-ether
function swap(uint256 amountIn, bytes calldata data)
external
payable
returns (uint256)
{
(
address tokenIn,
address tokenOut,
address pool,
uint8 poolType,
int128 i,
int128 j,
bool tokenApprovalNeeded
) = _decodeData(data);
if (tokenApprovalNeeded && tokenIn != nativeToken) {
// slither-disable-next-line unused-return
IERC20(tokenIn).approve(address(pool), type(uint256).max);
}
/// 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 (
address tokenIn,
address tokenOut,
address pool,
uint8 poolType,
int128 i,
int128 j,
bool tokenApprovalNeeded
)
{
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));
}
}

View File

@@ -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,7 +40,21 @@ 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);
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);
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;
@@ -76,6 +91,27 @@ contract Constants is Test, BaseConstants {
address PANCAKESWAPV3_DEPLOYER_ETHEREUM =
0x41ff9AA7e16B8B1a8a8dc4f0eFacd93D02d071c9;
// Curve
address TRIPOOL = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7;
address TRICRYPTO_POOL = 0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B;
address STETH_POOL = 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022;
address LUSD_POOL = 0xEd279fDD11cA84bEef15AF5D39BB4d4bEE23F0cA;
address CPOOL = 0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56;
address LDO_POOL = 0x9409280DC1e6D33AB7A8C6EC03e5763FB61772B5;
address CRV_POOL = 0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511;
address AAVE_POOL = 0xDeBF20617708857ebe4F679508E7b7863a8A8EeE;
address FRAXPYUSD_POOL = address(0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb);
address TRICRYPTO2_POOL = 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46;
address SUSD_POOL = 0xA5407eAE9Ba41422680e2e00537571bcC53efBfD;
address FRAX_USDC_POOL = 0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2;
address USDE_USDC_POOL = 0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72;
address DOLA_FRAXPYUSD_POOL = 0xef484de8C07B6e2d732A92B5F78e81B38f99f95E;
address ETH_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;
@@ -94,6 +130,9 @@ contract Constants is Test, BaseConstants {
bytes32 PANCAKEV3_POOL_CODE_INIT_HASH =
0x6ce8eb472fa82df5469c6ab6d485f17c3ad13c8cd7af59b3d4a8026c5ce0f7e2;
// Curve meta registry
address CURVE_META_REGISTRY = 0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC;
/**
* @dev Deploys a dummy contract with non-empty bytecode
*/

View File

@@ -0,0 +1,338 @@
// 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";
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 {
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 IAaveLendingPool {
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 _nativeToken) CurveExecutor(_nativeToken) {}
function decodeData(bytes calldata data)
external
pure
returns (
address tokenIn,
address tokenOut,
address pool,
uint8 poolType,
int128 i,
int128 j,
bool tokenApprovalNeeded
)
{
return _decodeData(data);
}
}
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(ETH_ADDR);
metaRegistry = MetaRegistry(CURVE_META_REGISTRY);
}
function testDecodeParams() public view {
bytes memory data = abi.encodePacked(
WETH_ADDR,
USDC_ADDR,
TRICRYPTO_POOL,
uint8(3),
uint8(2),
uint8(0),
true
);
(
address tokenIn,
address tokenOut,
address pool,
uint8 poolType,
int128 i,
int128 j,
bool tokenApprovalNeeded
) = curveExecutorExposed.decodeData(data);
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
uint256 amountIn = 1 ether;
deal(DAI_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(DAI_ADDR, USDC_ADDR, TRIPOOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 999797);
assertEq(
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testStEthPool() public {
// Swapping ETH -> stETH on StEthPool 0xDC24316b9AE028F1497c275EB9192a3Ea0f67022
uint256 amountIn = 1 ether;
deal(address(curveExecutorExposed), amountIn);
bytes memory data = _getData(ETH_ADDR, STETH_ADDR, STETH_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 1001072414418410897);
assertEq(
IERC20(STETH_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testTricrypto2Pool() public {
// Swapping WETH -> WBTC on Tricrypto2Pool 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46
uint256 amountIn = 1 ether;
deal(WETH_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL, 3);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 2279618);
assertEq(
IERC20(WBTC_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testSUSDPool() public {
// Swapping USDC -> SUSD on SUSDPool 0xA5407eAE9Ba41422680e2e00537571bcC53efBfD
uint256 amountIn = 100 * 10 ** 6;
deal(USDC_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(USDC_ADDR, SUSD_ADDR, SUSD_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 100488101605550214590);
assertEq(
IERC20(SUSD_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testFraxUsdcPool() public {
// Swapping FRAX -> USDC on FraxUsdcPool 0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2
uint256 amountIn = 1 ether;
deal(FRAX_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 998097);
assertEq(
IERC20(USDC_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testUsdeUsdcPool() public {
// Swapping USDC -> USDE on a CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf (plain pool)
uint256 amountIn = 100 * 10 ** 6;
deal(USDC_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 100064812138999986170);
assertEq(
IERC20(USDE_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testDolaFraxPyusdPool() public {
// Swapping DOLA -> FRAXPYUSD on a CryptoSwapNG, deployed by factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf (meta pool)
uint256 amountIn = 100 * 10 ** 6;
deal(DOLA_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 99688992);
assertEq(
IERC20(FRAXPYUSD_POOL).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testCryptoPoolWithETH() public {
// Swapping XYO -> ETH on a CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99
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 = _getData(XYO_ADDR, ETH_ADDR, ETH_XYO_POOL, 2);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 6081816039338);
assertEq(
address(curveExecutorExposed).balance, initialBalance + amountOut
);
}
function testCryptoPool() public {
// Swapping BSGG -> USDT on a CryptoPool, deployed by factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99
uint256 amountIn = 1000 ether;
deal(BSGG_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL, 2);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 23429);
assertEq(
IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testTricryptoPool() public {
// Swapping WETH -> USDC on a Tricrypto pool, deployed by factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963
uint256 amountIn = 1 ether;
deal(WETH_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL, 2);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
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
uint256 amountIn = 1 ether;
deal(UWU_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL, 2);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 2873786684675);
assertEq(
IERC20(WETH_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testStableSwapPool() public {
// Swapping CRVUSD -> USDT on a StableSwap pool, deployed by factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d (plain pool)
uint256 amountIn = 1 ether;
deal(CRVUSD_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(CRVUSD_ADDR, USDT_ADDR, CRVUSD_USDT_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 999910);
assertEq(
IERC20(USDT_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function testMetaPool() public {
// Swapping WTAO -> WSTTAO on a MetaPool deployed by factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 (plain pool)
uint256 amountIn = 100 * 10 ** 9; // 9 decimals
deal(WTAO_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data =
_getData(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
assertEq(amountOut, 32797923610);
assertEq(
IERC20(WSTTAO_ADDR).balanceOf(address(curveExecutorExposed)),
amountOut
);
}
function _getData(
address tokenIn,
address tokenOut,
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);
return (coinInIndex, coinOutIndex);
}
}