From 64ae56be854c06e375467430d00e7612e7e94403 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:42:02 +0700 Subject: [PATCH 1/4] init EulerSwapAdapter --- evm/src/euler-swap/EulerSwapAdapter.sol | 136 ++++++++++++++++++++++++ evm/src/euler-swap/manifest.yaml | 24 +++++ 2 files changed, 160 insertions(+) create mode 100644 evm/src/euler-swap/EulerSwapAdapter.sol create mode 100644 evm/src/euler-swap/manifest.yaml diff --git a/evm/src/euler-swap/EulerSwapAdapter.sol b/evm/src/euler-swap/EulerSwapAdapter.sol new file mode 100644 index 00000000..2b885be5 --- /dev/null +++ b/evm/src/euler-swap/EulerSwapAdapter.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; +import { + IERC20, + SafeERC20 +} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; + +contract EulerSwapAdapter is ISwapAdapter { + using SafeERC20 for IERC20; + + IMaglevEulerSwapFactory immutable factory; + + constructor(address factory_) { + factory = IMaglevEulerSwapFactory(factory_); + } + + /// @inheritdoc ISwapAdapter + function price( + bytes32 poolId, + address sellToken, + address buyToken, + uint256[] memory specifiedAmounts + ) external view override returns (Fraction[] memory prices) { + prices = new Fraction[](specifiedAmounts.length); + + IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); + for (uint256 i = 0; i < specifiedAmounts.length; i++) { + prices[i] = priceSingle(pool, sellToken, buyToken, specifiedAmounts[i]); + } + } + + /// @inheritdoc ISwapAdapter + function getCapabilities(bytes32, address, address) + external + pure + override + returns (Capability[] memory capabilities) + { + capabilities = new Capability[](3); + capabilities[0] = Capability.SellOrder; + capabilities[1] = Capability.BuyOrder; + capabilities[2] = Capability.PriceFunction; + } + + /// @inheritdoc ISwapAdapter + function getTokens(bytes32 poolId) + external + view + override + returns (address[] memory tokens) + { + tokens = new address[](2); + IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); + tokens[0] = address(pool.asset0()); + tokens[1] = address(pool.asset1()); + } + + /// @inheritdoc ISwapAdapter + function getPoolIds(uint256 offset, uint256 limit) + external + view + override + returns (bytes32[] memory ids) + { + uint256 endIdx = offset + limit; + if (endIdx > factory.allPoolsLength()) { + endIdx = factory.allPoolsLength(); + } + ids = new bytes32[](endIdx - offset); + for (uint256 i = 0; i < ids.length; i++) { + ids[i] = bytes20(factory.allPools(offset + i)); + } + } + + /// @notice Calculates pool prices for specified amounts + function priceSingle( + IMaglevEulerSwap pool, + address tokenIn, + address tokenOut, + uint256 amountIn + ) internal view returns (Fraction memory calculatedPrice) { + calculatedPrice = + Fraction(pool.quoteExactInput(tokenIn, tokenOut, amountIn), amountIn); + } +} + +interface IMaglevEulerSwapFactory { + event PoolDeployed(address indexed asset0, address indexed asset1, uint256 indexed feeMultiplier, address pool); + + function deployPool( + address vault0, + address vault1, + address holder, + uint112 debtLimit0, + uint112 debtLimit1, + uint256 fee, + uint256 priceX, + uint256 priceY, + uint256 concentrationX, + uint256 concentrationY + ) external returns (address); + + function evc() external view returns (address); + function allPools(uint256 index) external view returns (address); + function getPool(address assetA, address assetB, uint256 fee) external view returns (address); + function allPoolsLength() external view returns (uint256); + function getAllPoolsListSlice(uint256 start, uint256 end) external view returns (address[] memory); +} + +interface IMaglevEulerSwap { + // IMaglevBase + function configure() external; + function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; + function quoteExactInput(address tokenIn, address tokenOut, uint256 amountIn) external view returns (uint256); + function quoteExactOutput(address tokenIn, address tokenOut, uint256 amountOut) external view returns (uint256); + + function vault0() external view returns (address); + function vault1() external view returns (address); + function asset0() external view returns (address); + function asset1() external view returns (address); + function myAccount() external view returns (address); + function debtLimit0() external view returns (uint112); + function debtLimit1() external view returns (uint112); + function feeMultiplier() external view returns (uint256); + function getReserves() external view returns (uint112, uint112, uint32); + + // IMaglevEulerSwap + function priceX() external view returns (uint256); + function priceY() external view returns (uint256); + function concentrationX() external view returns (uint256); + function concentrationY() external view returns (uint256); + function initialReserve0() external view returns (uint112); + function initialReserve1() external view returns (uint112); +} diff --git a/evm/src/euler-swap/manifest.yaml b/evm/src/euler-swap/manifest.yaml new file mode 100644 index 00000000..886d3adb --- /dev/null +++ b/evm/src/euler-swap/manifest.yaml @@ -0,0 +1,24 @@ +# information about the author helps us reach out in case of issues. +author: + name: Haythem + email: haythem@euler.xyz + +# Protocol Constants +constants: + protocol_gas: 30000 + # minimum capabilities we can expect, individual pools may extend these + capabilities: + - SellSide + - BuySide + - PriceFunction + +# The file containing the adapter contract +contract: EulerSwapAdapter.sol + +# Deployment instances used to generate chain specific bytecode. +instances: + - chain: + name: mainnet + id: 1 + arguments: + - "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f" From 7538a592d14eaf3875bf2386e07848a1138c7e0b Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:11:08 +0700 Subject: [PATCH 2/4] swap --- evm/src/euler-swap/EulerSwapAdapter.sol | 52 ++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/evm/src/euler-swap/EulerSwapAdapter.sol b/evm/src/euler-swap/EulerSwapAdapter.sol index 2b885be5..bc27d70f 100644 --- a/evm/src/euler-swap/EulerSwapAdapter.sol +++ b/evm/src/euler-swap/EulerSwapAdapter.sol @@ -27,7 +27,45 @@ contract EulerSwapAdapter is ISwapAdapter { IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); for (uint256 i = 0; i < specifiedAmounts.length; i++) { - prices[i] = priceSingle(pool, sellToken, buyToken, specifiedAmounts[i]); + prices[i] = quoteExactInput(pool, sellToken, buyToken, specifiedAmounts[i]); + } + } + + /// @inheritdoc ISwapAdapter + function swap( + bytes32 poolId, + address sellToken, + address buyToken, + OrderSide side, + uint256 specifiedAmount + ) external returns (Trade memory trade) { + IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); + + bool isAmountOutAsset0 = buyToken == pool.asset0(); + uint256 amountIn; + uint256 amountOut; + if (side == OrderSide.Buy) { + amountIn = (quoteExactOutput(pool, sellToken, buyToken, specifiedAmount).denominator); + trade.calculatedAmount = amountOut = specifiedAmount; + } else { + trade.calculatedAmount = amountIn = specifiedAmount; + amountOut = + (quoteExactInput(pool, sellToken, buyToken, specifiedAmount).numerator); + } + + IERC20(sellToken).safeTransferFrom( + msg.sender, address(pool), amountIn + ); + + uint256 gasBefore = gasleft(); + (isAmountOutAsset0) ? pool.swap(amountOut, 0, msg.sender, "") : pool.swap(0, amountOut, msg.sender, ""); + trade.gasUsed = gasBefore - gasleft(); + + if (side == OrderSide.Buy) { + trade.price = quoteExactOutput(pool, sellToken, buyToken, specifiedAmount); + } else { + trade.price = + quoteExactInput(pool, sellToken, buyToken, specifiedAmount); } } @@ -75,7 +113,7 @@ contract EulerSwapAdapter is ISwapAdapter { } /// @notice Calculates pool prices for specified amounts - function priceSingle( + function quoteExactInput( IMaglevEulerSwap pool, address tokenIn, address tokenOut, @@ -84,6 +122,16 @@ contract EulerSwapAdapter is ISwapAdapter { calculatedPrice = Fraction(pool.quoteExactInput(tokenIn, tokenOut, amountIn), amountIn); } + + function quoteExactOutput( + IMaglevEulerSwap pool, + address tokenIn, + address tokenOut, + uint256 amountOut + ) internal view returns (Fraction memory calculatedPrice) { + calculatedPrice = + Fraction(amountOut, pool.quoteExactOutput(tokenIn, tokenOut, amountOut)); + } } interface IMaglevEulerSwapFactory { From 4601a773373e2b641601a485f76106a2a8da4795 Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Tue, 11 Feb 2025 12:38:59 +0800 Subject: [PATCH 3/4] getLimits --- evm/src/euler-swap/EulerSwapAdapter.sol | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/evm/src/euler-swap/EulerSwapAdapter.sol b/evm/src/euler-swap/EulerSwapAdapter.sol index bc27d70f..14374010 100644 --- a/evm/src/euler-swap/EulerSwapAdapter.sol +++ b/evm/src/euler-swap/EulerSwapAdapter.sol @@ -69,6 +69,29 @@ contract EulerSwapAdapter is ISwapAdapter { } } + /// @inheritdoc ISwapAdapter + function getLimits(bytes32 poolId, address sellToken, address buyToken) + external view override + returns (uint256[] memory limits) { + limits = new uint256[](2); + + IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); + address swapAccount = pool.myAccount(); + (address token0, address token1) = (sellToken < buyToken) ? (sellToken, buyToken) : (buyToken, sellToken); + + if(token0 == buyToken) { + // max amount for buyToken + uint256 maxWithdraw = vaultBalance(pool.vault0(), swapAccount); + + limits[1] = maxWithdraw; + } else { + // max amount for buyToken + uint256 maxWithdraw = vaultBalance(pool.vault1(), swapAccount); + + limits[1] = maxWithdraw; + } + } + /// @inheritdoc ISwapAdapter function getCapabilities(bytes32, address, address) external @@ -132,6 +155,14 @@ contract EulerSwapAdapter is ISwapAdapter { calculatedPrice = Fraction(amountOut, pool.quoteExactOutput(tokenIn, tokenOut, amountOut)); } + + function vaultBalance(address vault, address swapAccount) internal view returns (uint256) { + uint256 shares = IEVault(vault).balanceOf(swapAccount); + + return shares == 0 ? 0 : IEVault(vault).convertToAssets(shares); + + // return IEVault(vault).maxWithdraw(swapAccount); + } } interface IMaglevEulerSwapFactory { @@ -182,3 +213,9 @@ interface IMaglevEulerSwap { function initialReserve0() external view returns (uint112); function initialReserve1() external view returns (uint112); } + +interface IEVault { + function balanceOf(address account) external view returns (uint256); + function convertToAssets(uint256 shares) external view returns (uint256); + function maxWithdraw(address owner) external view returns (uint256); +} \ No newline at end of file From 0db4463f273565b78dcd57b0d32da25f0cc0674e Mon Sep 17 00:00:00 2001 From: Haythem Sellami <17862704+haythemsellami@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:07:13 +0100 Subject: [PATCH 4/4] init tests --- evm/src/euler-swap/EulerSwapAdapter.sol | 118 ++++++++++++++++-------- evm/test/EulerSwapAdapter.t.sol | 42 +++++++++ 2 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 evm/test/EulerSwapAdapter.t.sol diff --git a/evm/src/euler-swap/EulerSwapAdapter.sol b/evm/src/euler-swap/EulerSwapAdapter.sol index 14374010..897e1d5a 100644 --- a/evm/src/euler-swap/EulerSwapAdapter.sol +++ b/evm/src/euler-swap/EulerSwapAdapter.sol @@ -24,10 +24,11 @@ contract EulerSwapAdapter is ISwapAdapter { uint256[] memory specifiedAmounts ) external view override returns (Fraction[] memory prices) { prices = new Fraction[](specifiedAmounts.length); - + IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); for (uint256 i = 0; i < specifiedAmounts.length; i++) { - prices[i] = quoteExactInput(pool, sellToken, buyToken, specifiedAmounts[i]); + prices[i] = + quoteExactInput(pool, sellToken, buyToken, specifiedAmounts[i]); } } @@ -45,24 +46,30 @@ contract EulerSwapAdapter is ISwapAdapter { uint256 amountIn; uint256 amountOut; if (side == OrderSide.Buy) { - amountIn = (quoteExactOutput(pool, sellToken, buyToken, specifiedAmount).denominator); + amountIn = ( + quoteExactOutput(pool, sellToken, buyToken, specifiedAmount) + .denominator + ); trade.calculatedAmount = amountOut = specifiedAmount; } else { trade.calculatedAmount = amountIn = specifiedAmount; - amountOut = - (quoteExactInput(pool, sellToken, buyToken, specifiedAmount).numerator); + amountOut = ( + quoteExactInput(pool, sellToken, buyToken, specifiedAmount) + .numerator + ); } - IERC20(sellToken).safeTransferFrom( - msg.sender, address(pool), amountIn - ); + IERC20(sellToken).safeTransferFrom(msg.sender, address(pool), amountIn); uint256 gasBefore = gasleft(); - (isAmountOutAsset0) ? pool.swap(amountOut, 0, msg.sender, "") : pool.swap(0, amountOut, msg.sender, ""); + (isAmountOutAsset0) + ? pool.swap(amountOut, 0, msg.sender, "") + : pool.swap(0, amountOut, msg.sender, ""); trade.gasUsed = gasBefore - gasleft(); if (side == OrderSide.Buy) { - trade.price = quoteExactOutput(pool, sellToken, buyToken, specifiedAmount); + trade.price = + quoteExactOutput(pool, sellToken, buyToken, specifiedAmount); } else { trade.price = quoteExactInput(pool, sellToken, buyToken, specifiedAmount); @@ -71,25 +78,30 @@ contract EulerSwapAdapter is ISwapAdapter { /// @inheritdoc ISwapAdapter function getLimits(bytes32 poolId, address sellToken, address buyToken) - external view override - returns (uint256[] memory limits) { - limits = new uint256[](2); + external + view + override + returns (uint256[] memory limits) + { + limits = new uint256[](2); - IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); - address swapAccount = pool.myAccount(); - (address token0, address token1) = (sellToken < buyToken) ? (sellToken, buyToken) : (buyToken, sellToken); + IMaglevEulerSwap pool = IMaglevEulerSwap(address(bytes20(poolId))); + address swapAccount = pool.myAccount(); + (address token0, address token1) = (sellToken < buyToken) + ? (sellToken, buyToken) + : (buyToken, sellToken); - if(token0 == buyToken) { - // max amount for buyToken - uint256 maxWithdraw = vaultBalance(pool.vault0(), swapAccount); + if (token0 == buyToken) { + // max amount for buyToken + uint256 maxWithdraw = vaultBalance(pool.vault0(), swapAccount); - limits[1] = maxWithdraw; - } else { - // max amount for buyToken - uint256 maxWithdraw = vaultBalance(pool.vault1(), swapAccount); + limits[1] = maxWithdraw; + } else { + // max amount for buyToken + uint256 maxWithdraw = vaultBalance(pool.vault1(), swapAccount); - limits[1] = maxWithdraw; - } + limits[1] = maxWithdraw; + } } /// @inheritdoc ISwapAdapter @@ -142,8 +154,9 @@ contract EulerSwapAdapter is ISwapAdapter { address tokenOut, uint256 amountIn ) internal view returns (Fraction memory calculatedPrice) { - calculatedPrice = - Fraction(pool.quoteExactInput(tokenIn, tokenOut, amountIn), amountIn); + calculatedPrice = Fraction( + pool.quoteExactInput(tokenIn, tokenOut, amountIn), amountIn + ); } function quoteExactOutput( @@ -152,11 +165,16 @@ contract EulerSwapAdapter is ISwapAdapter { address tokenOut, uint256 amountOut ) internal view returns (Fraction memory calculatedPrice) { - calculatedPrice = - Fraction(amountOut, pool.quoteExactOutput(tokenIn, tokenOut, amountOut)); + calculatedPrice = Fraction( + amountOut, pool.quoteExactOutput(tokenIn, tokenOut, amountOut) + ); } - function vaultBalance(address vault, address swapAccount) internal view returns (uint256) { + function vaultBalance(address vault, address swapAccount) + internal + view + returns (uint256) + { uint256 shares = IEVault(vault).balanceOf(swapAccount); return shares == 0 ? 0 : IEVault(vault).convertToAssets(shares); @@ -166,8 +184,13 @@ contract EulerSwapAdapter is ISwapAdapter { } interface IMaglevEulerSwapFactory { - event PoolDeployed(address indexed asset0, address indexed asset1, uint256 indexed feeMultiplier, address pool); - + event PoolDeployed( + address indexed asset0, + address indexed asset1, + uint256 indexed feeMultiplier, + address pool + ); + function deployPool( address vault0, address vault1, @@ -183,17 +206,36 @@ interface IMaglevEulerSwapFactory { function evc() external view returns (address); function allPools(uint256 index) external view returns (address); - function getPool(address assetA, address assetB, uint256 fee) external view returns (address); + function getPool(address assetA, address assetB, uint256 fee) + external + view + returns (address); function allPoolsLength() external view returns (uint256); - function getAllPoolsListSlice(uint256 start, uint256 end) external view returns (address[] memory); + function getAllPoolsListSlice(uint256 start, uint256 end) + external + view + returns (address[] memory); } interface IMaglevEulerSwap { // IMaglevBase function configure() external; - function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; - function quoteExactInput(address tokenIn, address tokenOut, uint256 amountIn) external view returns (uint256); - function quoteExactOutput(address tokenIn, address tokenOut, uint256 amountOut) external view returns (uint256); + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + function quoteExactInput( + address tokenIn, + address tokenOut, + uint256 amountIn + ) external view returns (uint256); + function quoteExactOutput( + address tokenIn, + address tokenOut, + uint256 amountOut + ) external view returns (uint256); function vault0() external view returns (address); function vault1() external view returns (address); @@ -218,4 +260,4 @@ interface IEVault { function balanceOf(address account) external view returns (uint256); function convertToAssets(uint256 shares) external view returns (uint256); function maxWithdraw(address owner) external view returns (uint256); -} \ No newline at end of file +} diff --git a/evm/test/EulerSwapAdapter.t.sol b/evm/test/EulerSwapAdapter.t.sol new file mode 100644 index 00000000..671be1a0 --- /dev/null +++ b/evm/test/EulerSwapAdapter.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import "./AdapterTest.sol"; +import {EulerSwapAdapter, IERC20} from "src/euler-swap/EulerSwapAdapter.sol"; +import {FractionMath} from "src/libraries/FractionMath.sol"; + +contract EulerSwapAdapterTest is AdapterTest { + using FractionMath for Fraction; + + EulerSwapAdapter adapter; + address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + + function setUp() public { + uint256 forkBlock = 21845705; + vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); + adapter = + new EulerSwapAdapter(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); + + vm.label(address(adapter), "EulerSwapAdapter"); + vm.label(WETH, "WETH"); + vm.label(USDC, "USDC"); + } + + function testGetCapabilities( + bytes32 poolId, + address sellToken, + address buyToken + ) public view { + Capability[] memory res = + adapter.getCapabilities(poolId, sellToken, buyToken); + + assertEq(res.length, 3); + } + + // function testGetLimits() public { + // bytes32 pair = bytes32(bytes20(USDC_WETH_POOL)); + // uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); + // assertEq(limits.length, 2); + // } +} \ No newline at end of file