From 76bc1f58ac84f6060e1a876703cedc58ff789ab5 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Tue, 27 Feb 2024 11:40:56 +0100 Subject: [PATCH 01/16] feat: Initial Setup --- evm/src/etherfi/EtherfiAdapter.sol | 122 +++++++++++++++++++++++++++++ evm/src/etherfi/manifest.yaml | 36 +++++++++ 2 files changed, 158 insertions(+) create mode 100644 evm/src/etherfi/EtherfiAdapter.sol create mode 100644 evm/src/etherfi/manifest.yaml diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol new file mode 100644 index 0000000..d2c6c4f --- /dev/null +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma experimental ABIEncoderV2; +pragma solidity ^0.8.13; + +import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; + +/// @title Etherfi Adapter +/// @dev This contract supports the following swaps: eETH<->ETH, wETH<->eETH, wETH<->ETH +contract EtherfiAdapter is ISwapAdapter { + + IWeEth wEeth; + IeEth eEth; + ILiquidityPool liquidityPool; + + constructor(address _wEeth) { + wEeth = IWeEth(_wEeth); + eEth = wEeth.eETH(); + liquidityPool = eEth.liquidityPool(); + } + + /// @dev Check if tokens in input are supported by this adapter + modifier checkInputTokens(address sellToken, address buyToken) { + if(sellToken == buyToken) { + revert Unavailable("This pool only supports eETH, weEth and ETH"); + } + if(sellToken != address(wEeth) && sellToken != address(eEth) && sellToken && sellToken != address(0)) { + revert Unavailable("This pool only supports eETH, weEth and ETH"); + } + if(buyToken != address(wEeth) && buyToken != address(eEth) && buyToken != address(0)) { + revert Unavailable("This pool only supports eETH, weEth and ETH"); + } + _; + } + + function price( + bytes32 _poolId, + IERC20 _sellToken, + IERC20 _buyToken, + uint256[] memory _specifiedAmounts + ) external view override returns (Fraction[] memory _prices) { + revert NotImplemented("TemplateSwapAdapter.price"); + } + + function swap( + bytes32 poolId, + IERC20 sellToken, + IERC20 buyToken, + OrderSide side, + uint256 specifiedAmount + ) external returns (Trade memory trade) { + revert NotImplemented("TemplateSwapAdapter.swap"); + } + + function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + returns (uint256[] memory limits) + { + revert NotImplemented("TemplateSwapAdapter.getLimits"); + } + + function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + returns (Capability[] memory capabilities) + { + revert NotImplemented("TemplateSwapAdapter.getCapabilities"); + } + + function getTokens(bytes32 poolId) + external + returns (IERC20[] memory tokens) + { + revert NotImplemented("TemplateSwapAdapter.getTokens"); + } + + function getPoolIds(uint256 offset, uint256 limit) + external + returns (bytes32[] memory ids) + { + revert NotImplemented("TemplateSwapAdapter.getPoolIds"); + } +} + +interface ILiquidityPool { + + function numPendingDeposits() external view returns (uint32); + function totalValueOutOfLp() external view returns (uint128); + function totalValueInLp() external view returns (uint128); + function getTotalEtherClaimOf(address _user) external view returns (uint256); + function getTotalPooledEther() external view returns (uint256); + function sharesForAmount(uint256 _amount) external view returns (uint256); + function sharesForWithdrawalAmount(uint256 _amount) external view returns (uint256); + function amountForShare(uint256 _share) external view returns (uint256); + + function deposit() external payable returns (uint256); + function deposit(address _referral) external payable returns (uint256); + function deposit(address _user, address _referral) external payable returns (uint256); + +} + +interface IeEth { + + function liquidityPool() external view returns (ILiquidityPool); + + function totalShares() external view returns (uint256); + + function shares(address _user) external view returns (uint256); + +} + +interface IWeEth { + + function eETH() external view returns (IeEth); + + function getWeETHByeETH(uint256 _eETHAmount) external view returns (uint256); + + function getEETHByWeETH(uint256 _weETHAmount) external view returns (uint256); + + function wrap(uint256 _eETHAmount) external returns (uint256); + + function unwrap(uint256 _weETHAmount) external returns (uint256); + +} diff --git a/evm/src/etherfi/manifest.yaml b/evm/src/etherfi/manifest.yaml new file mode 100644 index 0000000..c6cb262 --- /dev/null +++ b/evm/src/etherfi/manifest.yaml @@ -0,0 +1,36 @@ +# information about the author helps us reach out in case of issues. +author: + name: Propellerheads.xyz + email: alan@propellerheads.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: EtherfiAdapter.sol + +# Deployment instances used to generate chain specific bytecode. +instances: + - chain: + name: mainnet + id: 0 + arguments: + - "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee" + +# Specify some automatic test cases in case getPoolIds and +# getTokens are not implemented. +tests: + instances: + - pool_id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc" + sell_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + buy_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + block: 17000000 + chain: + id: 0 + name: mainnet From de4c816570a4d0e61a7aa018825efb5ba69cd2d2 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Tue, 27 Feb 2024 11:42:21 +0100 Subject: [PATCH 02/16] feat: Implemented getTokens --- evm/src/etherfi/EtherfiAdapter.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index d2c6c4f..a926976 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -65,11 +65,17 @@ contract EtherfiAdapter is ISwapAdapter { revert NotImplemented("TemplateSwapAdapter.getCapabilities"); } - function getTokens(bytes32 poolId) + /// @inheritdoc ISwapAdapter + function getTokens(bytes32) external + view + override returns (IERC20[] memory tokens) { - revert NotImplemented("TemplateSwapAdapter.getTokens"); + tokens = new IERC20[](3); + tokens[0] = IERC20(address(0)); + tokens[1] = IERC20(address(eEth)); + tokens[2] = IERC20(address(wEeth)); } function getPoolIds(uint256 offset, uint256 limit) From d4e9e213ae12a67704c9424a5ec236049e0907d8 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Tue, 27 Feb 2024 11:43:26 +0100 Subject: [PATCH 03/16] feat: Implemented getPoolIds --- evm/src/etherfi/EtherfiAdapter.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index a926976..32c7067 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -78,11 +78,13 @@ contract EtherfiAdapter is ISwapAdapter { tokens[2] = IERC20(address(wEeth)); } - function getPoolIds(uint256 offset, uint256 limit) + /// @inheritdoc ISwapAdapter + function getPoolIds(uint256, uint256) external returns (bytes32[] memory ids) { - revert NotImplemented("TemplateSwapAdapter.getPoolIds"); + ids[] = new bytes32[](1); + ids[0] = bytes20(address(liquidityPool)); } } From 61d4c4eb9f29b980d380eba15d6662864d71cfd6 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Tue, 27 Feb 2024 12:11:04 +0100 Subject: [PATCH 04/16] feat: Implemented helper functions and missing constants --- evm/src/etherfi/EtherfiAdapter.sol | 64 +++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index 32c7067..33ccc1b 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -8,14 +8,19 @@ import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; /// @dev This contract supports the following swaps: eETH<->ETH, wETH<->eETH, wETH<->ETH contract EtherfiAdapter is ISwapAdapter { + uint16 mintFee; // fee = 0.001 ETH * 'mintFee' + uint16 burnFee; // fee = 0.001 ETH * 'burnFee' + IWeEth wEeth; IeEth eEth; ILiquidityPool liquidityPool; + IMembershipManager membershipManager; constructor(address _wEeth) { wEeth = IWeEth(_wEeth); eEth = wEeth.eETH(); liquidityPool = eEth.liquidityPool(); + membershipManager = liquidityPool.membershipManager(); } /// @dev Check if tokens in input are supported by this adapter @@ -32,6 +37,9 @@ contract EtherfiAdapter is ISwapAdapter { _; } + /// @dev enable receive as this contract supports ETH + receive() external payable {} + function price( bytes32 _poolId, IERC20 _sellToken, @@ -51,11 +59,42 @@ contract EtherfiAdapter is ISwapAdapter { revert NotImplemented("TemplateSwapAdapter.swap"); } - function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + function getLimits(bytes32, IERC20 sellToken, IERC20 buyToken) external + view + override + checkInputTokens(address(sellToken), address(buyToken)) returns (uint256[] memory limits) { - revert NotImplemented("TemplateSwapAdapter.getLimits"); + address sellTokenAddress = address(sellToken); + address buyTokenAddress = address(buyToken); + limits = new uint256[](2); + + if(sellTokenAddress == address(0)) { + if(buyTokenAddress == address(eEth)) { + + } + else { // ETH-weETH + + } + } + else if(sellTokenAddress == address(wEeth)) { + if(buyTokenAddress == address(0)) { + + } + else { // wEeth-ETH + + } + } + else if(sellTokenAddress == address(eEth)) { + if(buyTokenAddress == address(0)) { + + } + else { // eEth-wEeth + + } + } + } function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) @@ -86,6 +125,25 @@ contract EtherfiAdapter is ISwapAdapter { ids[] = new bytes32[](1); ids[0] = bytes20(address(liquidityPool)); } + + /// @notice Swap ETH for eETH using MembershipManager + /// @param payedAmount result of getETHRequiredToMintEeth(), the amount of ETH to pay(incl. fee) + /// @param receivedAmount eETH received + function swapEthForEeth(uint256 payedAmount, uint256 receivedAmount) internal { + return membershipManager.wrapEth(_amount, 0); + } + + /// @notice Get ETH required to mint `_amount` eETH + function getETHRequiredToMintEeth(uint256 _amount) internal returns (uint256) { + uint256 feeAmount = uint256(mintFee) * 0.001 ether; + return _amount + feeAmount; + } +} + +interface IMembershipManager { + + function wrapEth(uint256 _amount, uint256 _amountForPoints) external payable returns (uint256); + } interface ILiquidityPool { @@ -103,6 +161,8 @@ interface ILiquidityPool { function deposit(address _referral) external payable returns (uint256); function deposit(address _user, address _referral) external payable returns (uint256); + function membershipManager() external view returns (IMembershipManager); + } interface IeEth { From 0f3fe80b00bcae4c100241025bcd12e002e5eabd Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 28 Feb 2024 12:07:57 +0100 Subject: [PATCH 05/16] feat: Implemented internal swap functions and getAmountIn --- evm/src/etherfi/EtherfiAdapter.sol | 108 ++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index 33ccc1b..d2ac197 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -3,24 +3,21 @@ pragma experimental ABIEncoderV2; pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; +import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title Etherfi Adapter -/// @dev This contract supports the following swaps: eETH<->ETH, wETH<->eETH, wETH<->ETH +/// @dev This contract supports the following swaps: ETH->eETH, wETH<->eETH, ETH->weETH contract EtherfiAdapter is ISwapAdapter { + using SafeERC20 for IERC20; - uint16 mintFee; // fee = 0.001 ETH * 'mintFee' - uint16 burnFee; // fee = 0.001 ETH * 'burnFee' - - IWeEth wEeth; + IWeEth weEth; IeEth eEth; - ILiquidityPool liquidityPool; - IMembershipManager membershipManager; + ILiquidityPool public liquidityPool; - constructor(address _wEeth) { - wEeth = IWeEth(_wEeth); - eEth = wEeth.eETH(); + constructor(address _weEth) { + weEth = IWeEth(_weEth); + eEth = weEth.eETH(); liquidityPool = eEth.liquidityPool(); - membershipManager = liquidityPool.membershipManager(); } /// @dev Check if tokens in input are supported by this adapter @@ -28,10 +25,10 @@ contract EtherfiAdapter is ISwapAdapter { if(sellToken == buyToken) { revert Unavailable("This pool only supports eETH, weEth and ETH"); } - if(sellToken != address(wEeth) && sellToken != address(eEth) && sellToken && sellToken != address(0)) { + if(sellToken != address(weEth) && sellToken != address(eEth) && sellToken != address(0)) { revert Unavailable("This pool only supports eETH, weEth and ETH"); } - if(buyToken != address(wEeth) && buyToken != address(eEth) && buyToken != address(0)) { + if(buyToken != address(weEth) && buyToken != address(eEth)) { revert Unavailable("This pool only supports eETH, weEth and ETH"); } _; @@ -74,15 +71,15 @@ contract EtherfiAdapter is ISwapAdapter { if(buyTokenAddress == address(eEth)) { } - else { // ETH-weETH + else { // ETH-weEth } } - else if(sellTokenAddress == address(wEeth)) { + else if(sellTokenAddress == address(weEth)) { if(buyTokenAddress == address(0)) { } - else { // wEeth-ETH + else { // weEth-ETH } } @@ -90,7 +87,7 @@ contract EtherfiAdapter is ISwapAdapter { if(buyTokenAddress == address(0)) { } - else { // eEth-wEeth + else { // eEth-weEth } } @@ -114,7 +111,7 @@ contract EtherfiAdapter is ISwapAdapter { tokens = new IERC20[](3); tokens[0] = IERC20(address(0)); tokens[1] = IERC20(address(eEth)); - tokens[2] = IERC20(address(wEeth)); + tokens[2] = IERC20(address(weEth)); } /// @inheritdoc ISwapAdapter @@ -122,27 +119,75 @@ contract EtherfiAdapter is ISwapAdapter { external returns (bytes32[] memory ids) { - ids[] = new bytes32[](1); + ids = new bytes32[](1); ids[0] = bytes20(address(liquidityPool)); } - /// @notice Swap ETH for eETH using MembershipManager - /// @param payedAmount result of getETHRequiredToMintEeth(), the amount of ETH to pay(incl. fee) - /// @param receivedAmount eETH received - function swapEthForEeth(uint256 payedAmount, uint256 receivedAmount) internal { - return membershipManager.wrapEth(_amount, 0); + /// @notice Swap ETH for eETH + /// @param amount amountIn or amountOut depending on side + function swapEthForEeth(uint256 amount, OrderSide side) internal returns (uint256) { + if(side == OrderSide.Buy) { + uint256 amountIn = getAmountIn(address(0), address(eEth), amount); + uint256 receivedAmount = liquidityPool.deposit{value: amountIn}(); + IERC20(address(eEth)).safeTransfer(address(msg.sender), receivedAmount); + return amountIn; + } + else { + uint256 receivedAmount = liquidityPool.deposit{value: amount}(); + IERC20(address(eEth)).safeTransfer(address(msg.sender), receivedAmount); + return receivedAmount; + } } - /// @notice Get ETH required to mint `_amount` eETH - function getETHRequiredToMintEeth(uint256 _amount) internal returns (uint256) { - uint256 feeAmount = uint256(mintFee) * 0.001 ether; - return _amount + feeAmount; + /// @notice Swap ETH for weEth + /// @param amount amountIn or amountOut depending on side + function swapEthForWeEth(uint256 amount, OrderSide side) internal returns (uint256) { + if(side == OrderSide.Buy) { + uint256 amountIn = getAmountIn(address(0), address(weEth), amount); + IERC20(address(eEth)).approve(address(weEth), amountIn); + uint256 receivedAmountEeth = liquidityPool.deposit{value: amountIn}(); + uint256 receivedAmount = weEth.wrap(receivedAmountEeth); + IERC20(address(weEth)).safeTransfer(address(msg.sender), receivedAmount); + return amountIn; + } + else { + IERC20(address(eEth)).approve(address(weEth), amount); + uint256 receivedAmountEeth = liquidityPool.deposit{value: amount}(); + uint256 receivedAmount = weEth.wrap(receivedAmountEeth); + IERC20(address(weEth)).safeTransfer(address(msg.sender), receivedAmount); + return receivedAmount; + } } + + /// @notice Get amountIn for swap functions with OrderSide buy + function getAmountIn(address sellToken, address buyToken, uint256 amountOut) internal view returns (uint256) { + if(sellToken == address(0)) { + if(buyToken == address(eEth)) { + return liquidityPool.amountForShare(amountOut); + } + else { + uint256 ethRequiredForEeth = liquidityPool.amountForShare(amountOut); + return liquidityPool.amountForShare(ethRequiredForEeth); + } + } + else if(sellToken == address(eEth)) { // eEth-weEth + return weEth.getEETHByWeETH(amountOut); + } + else { // weEth-eEth + return weEth.getWeETHByeETH(amountOut); + } + } + } -interface IMembershipManager { +interface IWithdrawRequestNFT { - function wrapEth(uint256 _amount, uint256 _amountForPoints) external payable returns (uint256); + function requestWithdraw(uint96 amountOfEEth, uint96 shareOfEEth, address recipient, uint256 fee) + external payable returns (uint256); + + function getClaimableAmount(uint256 tokenId) external view returns (uint256); + + function claimWithdraw(uint256 tokenId) external; } @@ -161,7 +206,8 @@ interface ILiquidityPool { function deposit(address _referral) external payable returns (uint256); function deposit(address _user, address _referral) external payable returns (uint256); - function membershipManager() external view returns (IMembershipManager); + function requestWithdraw(address recipient, uint256 amount) external returns (uint256); + function withdrawRequestNFT() external view returns (IWithdrawRequestNFT); } From 6cd57ebaf4c5cc4f49619b549c3655cf19ad52c2 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 28 Feb 2024 12:38:59 +0100 Subject: [PATCH 06/16] feat: Implemented price --- evm/src/etherfi/EtherfiAdapter.sol | 261 ++++++++++++++++++++--------- 1 file changed, 183 insertions(+), 78 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index d2ac197..4a15437 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -3,10 +3,12 @@ pragma experimental ABIEncoderV2; pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; -import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; +import {SafeERC20} from + "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title Etherfi Adapter -/// @dev This contract supports the following swaps: ETH->eETH, wETH<->eETH, ETH->weETH +/// @dev This contract supports the following swaps: ETH->eETH, wETH<->eETH, +/// ETH->weETH contract EtherfiAdapter is ISwapAdapter { using SafeERC20 for IERC20; @@ -22,13 +24,16 @@ contract EtherfiAdapter is ISwapAdapter { /// @dev Check if tokens in input are supported by this adapter modifier checkInputTokens(address sellToken, address buyToken) { - if(sellToken == buyToken) { + if (sellToken == buyToken) { revert Unavailable("This pool only supports eETH, weEth and ETH"); } - if(sellToken != address(weEth) && sellToken != address(eEth) && sellToken != address(0)) { + if ( + sellToken != address(weEth) && sellToken != address(eEth) + && sellToken != address(0) + ) { revert Unavailable("This pool only supports eETH, weEth and ETH"); } - if(buyToken != address(weEth) && buyToken != address(eEth)) { + if (buyToken != address(weEth) && buyToken != address(eEth)) { revert Unavailable("This pool only supports eETH, weEth and ETH"); } _; @@ -37,23 +42,66 @@ contract EtherfiAdapter is ISwapAdapter { /// @dev enable receive as this contract supports ETH receive() external payable {} + /// @inheritdoc ISwapAdapter function price( - bytes32 _poolId, + bytes32, IERC20 _sellToken, IERC20 _buyToken, uint256[] memory _specifiedAmounts - ) external view override returns (Fraction[] memory _prices) { - revert NotImplemented("TemplateSwapAdapter.price"); + ) + external + view + override + checkInputTokens(address(_sellToken), address(_buyToken)) + returns (Fraction[] memory _prices) + { + _prices = new Fraction[](_specifiedAmounts.length); + address sellTokenAddress = address(_sellToken); + address buyTokenAddress = address(_buyToken); + + for (uint256 i = 0; i < _specifiedAmounts.length; i++) { + _prices[i] = getPriceAt(sellTokenAddress, buyTokenAddress, _specifiedAmounts[i]); + } } function swap( - bytes32 poolId, + bytes32, IERC20 sellToken, IERC20 buyToken, OrderSide side, uint256 specifiedAmount - ) external returns (Trade memory trade) { - revert NotImplemented("TemplateSwapAdapter.swap"); + ) + external + override + checkInputTokens(address(sellToken), address(buyToken)) + returns (Trade memory trade) + { + // if (specifiedAmount == 0) { + // return trade; + // } + + // address sellTokenAddress = address(sellToken); + // address buyTokenAddress = address(buyToken); + // uint256 gasBefore = gasleft(); + // if (sellTokenAddress == address(0)) { + // if (buyTokenAddress == address(eEth)) { + // trade.calculatedAmount = swapEthForEeth(specifiedAmount, + // side); + // } else { + // trade.calculatedAmount = swapEthForWeEth(specifiedAmount, + // side); + // } + // } else { + // if (sellTokenAddress == address(eEth)) { + // trade.calculatedAmount = swapEthForWeEth(specifiedAmount, + // side); + // } else { + // trade.calculatedAmount = swapWeEthForEeth(specifiedAmount, + // side); + // } + // } + // trade.gasUsed = gasBefore - gasleft(); + // // trade.price = getPriceAt(); } function getLimits(bytes32, IERC20 sellToken, IERC20 buyToken) @@ -66,32 +114,17 @@ contract EtherfiAdapter is ISwapAdapter { address sellTokenAddress = address(sellToken); address buyTokenAddress = address(buyToken); limits = new uint256[](2); - - if(sellTokenAddress == address(0)) { - if(buyTokenAddress == address(eEth)) { + if (sellTokenAddress == address(0)) { + if (buyTokenAddress == address(eEth)) {} else { // ETH-weEth } - else { // ETH-weEth - + } else if (sellTokenAddress == address(weEth)) { + if (buyTokenAddress == address(0)) {} else { // weEth-ETH + } + } else if (sellTokenAddress == address(eEth)) { + if (buyTokenAddress == address(0)) {} else { // eEth-weEth } } - else if(sellTokenAddress == address(weEth)) { - if(buyTokenAddress == address(0)) { - - } - else { // weEth-ETH - - } - } - else if(sellTokenAddress == address(eEth)) { - if(buyTokenAddress == address(0)) { - - } - else { // eEth-weEth - - } - } - } function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) @@ -125,112 +158,184 @@ contract EtherfiAdapter is ISwapAdapter { /// @notice Swap ETH for eETH /// @param amount amountIn or amountOut depending on side - function swapEthForEeth(uint256 amount, OrderSide side) internal returns (uint256) { - if(side == OrderSide.Buy) { + function swapEthForEeth(uint256 amount, OrderSide side) + internal + returns (uint256) + { + if (side == OrderSide.Buy) { uint256 amountIn = getAmountIn(address(0), address(eEth), amount); uint256 receivedAmount = liquidityPool.deposit{value: amountIn}(); - IERC20(address(eEth)).safeTransfer(address(msg.sender), receivedAmount); + IERC20(address(eEth)).safeTransfer( + address(msg.sender), receivedAmount + ); return amountIn; - } - else { + } else { uint256 receivedAmount = liquidityPool.deposit{value: amount}(); - IERC20(address(eEth)).safeTransfer(address(msg.sender), receivedAmount); + IERC20(address(eEth)).safeTransfer( + address(msg.sender), receivedAmount + ); return receivedAmount; } } /// @notice Swap ETH for weEth /// @param amount amountIn or amountOut depending on side - function swapEthForWeEth(uint256 amount, OrderSide side) internal returns (uint256) { - if(side == OrderSide.Buy) { + function swapEthForWeEth(uint256 amount, OrderSide side) + internal + returns (uint256) + { + if (side == OrderSide.Buy) { uint256 amountIn = getAmountIn(address(0), address(weEth), amount); IERC20(address(eEth)).approve(address(weEth), amountIn); - uint256 receivedAmountEeth = liquidityPool.deposit{value: amountIn}(); + uint256 receivedAmountEeth = + liquidityPool.deposit{value: amountIn}(); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); - IERC20(address(weEth)).safeTransfer(address(msg.sender), receivedAmount); + IERC20(address(weEth)).safeTransfer( + address(msg.sender), receivedAmount + ); return amountIn; - } - else { + } else { IERC20(address(eEth)).approve(address(weEth), amount); uint256 receivedAmountEeth = liquidityPool.deposit{value: amount}(); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); - IERC20(address(weEth)).safeTransfer(address(msg.sender), receivedAmount); + IERC20(address(weEth)).safeTransfer( + address(msg.sender), receivedAmount + ); return receivedAmount; } } - /// @notice Get amountIn for swap functions with OrderSide buy - function getAmountIn(address sellToken, address buyToken, uint256 amountOut) internal view returns (uint256) { - if(sellToken == address(0)) { - if(buyToken == address(eEth)) { - return liquidityPool.amountForShare(amountOut); + /// @notice Swap eETH for weETH + /// @param amount amountIn or amountOut depending on side + function swapEethForWeEth(uint256 amount, OrderSide side) + internal + returns (uint256) + { + if (side == OrderSide.Buy) {} else {} + } + + /// @notice Swap weETH for eEth + /// @param amount amountIn or amountOut depending on side + function swapWeEthForEeth(uint256 amount, OrderSide side) + internal + returns (uint256) + { + if (side == OrderSide.Buy) {} else {} + } + + /// @notice Get swap price + /// @param sellToken token to sell + /// @param buyToken token to buy + function getPriceAt(address sellToken, address buyToken, uint256 amount) + internal + view + returns (Fraction memory) + { + if (sellToken == address(0)) { + if (buyToken == address(eEth)) { + return Fraction(liquidityPool.sharesForAmount(amount), amount); + } else { + uint256 eEthOut = liquidityPool.sharesForAmount(amount); + return Fraction(liquidityPool.sharesForAmount(eEthOut), amount); } - else { - uint256 ethRequiredForEeth = liquidityPool.amountForShare(amountOut); - return liquidityPool.amountForShare(ethRequiredForEeth); - } - } - else if(sellToken == address(eEth)) { // eEth-weEth - return weEth.getEETHByWeETH(amountOut); - } - else { // weEth-eEth - return weEth.getWeETHByeETH(amountOut); + } else if (sellToken == address(eEth)) { + return Fraction(liquidityPool.sharesForAmount(amount), amount); + } else { + return Fraction(liquidityPool.amountForShare(amount), amount); } } + /// @notice Get amountIn for swap functions with OrderSide buy + function getAmountIn(address sellToken, address buyToken, uint256 amountOut) + internal + view + returns (uint256) + { + if (sellToken == address(0)) { + if (buyToken == address(eEth)) { + return liquidityPool.amountForShare(amountOut); + } else { + uint256 ethRequiredForEeth = + liquidityPool.amountForShare(amountOut); + return liquidityPool.amountForShare(ethRequiredForEeth); + } + } else if (sellToken == address(eEth)) { + // eEth-weEth + return weEth.getEETHByWeETH(amountOut); + } else { + // weEth-eEth + return weEth.getWeETHByeETH(amountOut); + } + } } interface IWithdrawRequestNFT { + function requestWithdraw( + uint96 amountOfEEth, + uint96 shareOfEEth, + address recipient, + uint256 fee + ) external payable returns (uint256); - function requestWithdraw(uint96 amountOfEEth, uint96 shareOfEEth, address recipient, uint256 fee) - external payable returns (uint256); - - function getClaimableAmount(uint256 tokenId) external view returns (uint256); + function getClaimableAmount(uint256 tokenId) + external + view + returns (uint256); function claimWithdraw(uint256 tokenId) external; - } interface ILiquidityPool { - function numPendingDeposits() external view returns (uint32); function totalValueOutOfLp() external view returns (uint128); function totalValueInLp() external view returns (uint128); - function getTotalEtherClaimOf(address _user) external view returns (uint256); + function getTotalEtherClaimOf(address _user) + external + view + returns (uint256); function getTotalPooledEther() external view returns (uint256); function sharesForAmount(uint256 _amount) external view returns (uint256); - function sharesForWithdrawalAmount(uint256 _amount) external view returns (uint256); + function sharesForWithdrawalAmount(uint256 _amount) + external + view + returns (uint256); function amountForShare(uint256 _share) external view returns (uint256); function deposit() external payable returns (uint256); function deposit(address _referral) external payable returns (uint256); - function deposit(address _user, address _referral) external payable returns (uint256); + function deposit(address _user, address _referral) + external + payable + returns (uint256); - function requestWithdraw(address recipient, uint256 amount) external returns (uint256); + function requestWithdraw(address recipient, uint256 amount) + external + returns (uint256); function withdrawRequestNFT() external view returns (IWithdrawRequestNFT); - } interface IeEth { - function liquidityPool() external view returns (ILiquidityPool); function totalShares() external view returns (uint256); function shares(address _user) external view returns (uint256); - } interface IWeEth { - function eETH() external view returns (IeEth); - function getWeETHByeETH(uint256 _eETHAmount) external view returns (uint256); + function getWeETHByeETH(uint256 _eETHAmount) + external + view + returns (uint256); - function getEETHByWeETH(uint256 _weETHAmount) external view returns (uint256); + function getEETHByWeETH(uint256 _weETHAmount) + external + view + returns (uint256); function wrap(uint256 _eETHAmount) external returns (uint256); function unwrap(uint256 _weETHAmount) external returns (uint256); - } From 08917023e06a37c6f71ca6c71d5fae8b60a01d19 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 28 Feb 2024 16:31:28 +0100 Subject: [PATCH 07/16] feat: Implemented getLimits --- evm/src/etherfi/EtherfiAdapter.sol | 73 +++++++++++++++--------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index 4a15437..ee9ba0b 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -60,7 +60,9 @@ contract EtherfiAdapter is ISwapAdapter { address buyTokenAddress = address(_buyToken); for (uint256 i = 0; i < _specifiedAmounts.length; i++) { - _prices[i] = getPriceAt(sellTokenAddress, buyTokenAddress, _specifiedAmounts[i]); + _prices[i] = getPriceAt( + sellTokenAddress, buyTokenAddress, _specifiedAmounts[i] + ); } } @@ -76,34 +78,39 @@ contract EtherfiAdapter is ISwapAdapter { checkInputTokens(address(sellToken), address(buyToken)) returns (Trade memory trade) { - // if (specifiedAmount == 0) { - // return trade; - // } + if (specifiedAmount == 0) { + return trade; + } - // address sellTokenAddress = address(sellToken); - // address buyTokenAddress = address(buyToken); - // uint256 gasBefore = gasleft(); - // if (sellTokenAddress == address(0)) { - // if (buyTokenAddress == address(eEth)) { - // trade.calculatedAmount = swapEthForEeth(specifiedAmount, - // side); - // } else { - // trade.calculatedAmount = swapEthForWeEth(specifiedAmount, - // side); - // } - // } else { - // if (sellTokenAddress == address(eEth)) { - // trade.calculatedAmount = swapEthForWeEth(specifiedAmount, - // side); - // } else { - // trade.calculatedAmount = swapWeEthForEeth(specifiedAmount, - // side); - // } - // } - // trade.gasUsed = gasBefore - gasleft(); - // // trade.price = getPriceAt(); + address sellTokenAddress = address(sellToken); + address buyTokenAddress = address(buyToken); + uint256 gasBefore = gasleft(); + if (sellTokenAddress == address(0)) { + if (buyTokenAddress == address(eEth)) { + trade.calculatedAmount = swapEthForEeth(specifiedAmount, side); + } else { + trade.calculatedAmount = swapEthForWeEth(specifiedAmount, side); + } + } else { + if (sellTokenAddress == address(eEth)) { + trade.calculatedAmount = swapEthForWeEth(specifiedAmount, side); + } else { + trade.calculatedAmount = swapWeEthForEeth(specifiedAmount, side); + } + } + trade.gasUsed = gasBefore - gasleft(); + if (side == OrderSide.Sell) { + trade.price = getPriceAt( + sellTokenAddress, buyTokenAddress, specifiedAmount + ); + } else { + trade.price = getPriceAt( + sellTokenAddress, buyTokenAddress, trade.calculatedAmount + ); + } } + /// @inheritdoc ISwapAdapter function getLimits(bytes32, IERC20 sellToken, IERC20 buyToken) external view @@ -115,16 +122,10 @@ contract EtherfiAdapter is ISwapAdapter { address buyTokenAddress = address(buyToken); limits = new uint256[](2); - if (sellTokenAddress == address(0)) { - if (buyTokenAddress == address(eEth)) {} else { // ETH-weEth - } - } else if (sellTokenAddress == address(weEth)) { - if (buyTokenAddress == address(0)) {} else { // weEth-ETH - } - } else if (sellTokenAddress == address(eEth)) { - if (buyTokenAddress == address(0)) {} else { // eEth-weEth - } - } + /// @dev only limit on Etherfi is applied on deposits(eth->eETH), and is type(uint128).max + /// but we use the same amount for the others to underestimate + limits[0] = type(uint128).max; + limits[1] = limits[0]; } function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) From 1064fbf7eb0fb80463bcac9565a39a359722cb72 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 28 Feb 2024 16:32:37 +0100 Subject: [PATCH 08/16] feat: Implemented swap --- evm/src/etherfi/EtherfiAdapter.sol | 11 +++++++-- evm/test/EtherfiAdapter.t.sol | 37 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 evm/test/EtherfiAdapter.t.sol diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index ee9ba0b..1ac19de 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -66,6 +66,7 @@ contract EtherfiAdapter is ISwapAdapter { } } + /// @inheritdoc ISwapAdapter function swap( bytes32, IERC20 sellToken, @@ -128,11 +129,17 @@ contract EtherfiAdapter is ISwapAdapter { limits[1] = limits[0]; } - function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + /// @inheritdoc ISwapAdapter + function getCapabilities(bytes32, IERC20, IERC20) external + pure + override returns (Capability[] memory capabilities) { - revert NotImplemented("TemplateSwapAdapter.getCapabilities"); + capabilities = new Capability[](3); + capabilities[0] = Capability.SellOrder; + capabilities[1] = Capability.BuyOrder; + capabilities[2] = Capability.PriceFunction; } /// @inheritdoc ISwapAdapter diff --git a/evm/test/EtherfiAdapter.t.sol b/evm/test/EtherfiAdapter.t.sol new file mode 100644 index 0000000..2834ef6 --- /dev/null +++ b/evm/test/EtherfiAdapter.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; +import "src/interfaces/ISwapAdapterTypes.sol"; +import "src/libraries/FractionMath.sol"; +import "src/etherfi/EtherfiAdapter.sol"; + +contract EtherfiAdapterTest is Test, ISwapAdapterTypes { + EtherfiAdapter adapter; + IWeEth wEeth = IWeEth(0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee); + IeEth eEth; + + function setUp() public { + uint256 forkBlock = 19218495; + vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); + adapter = new EtherfiAdapter(address(wEeth)); + eEth = wEeth.eETH(); + + vm.label(address(wEeth), "WeETH"); + vm.label(address(eEth), "eETH"); + } + + receive() external payable {} + + function testMe() public { + // // uint256 requiredETH = adapter.getETHRequiredToMintEeth(1 ether); + // deal(address(adapter), 100 ether); + // // adapter.swapEthForEeth(1 ether, OrderSide.Buy); + // IERC20(address(eEth)).approve(address(adapter), type(uint256).max); + + // console.log(IERC20(address(eEth)).balanceOf(address(this))); + // console.log(adapter.swapEthForWeEth(1 ether, OrderSide.Buy)); + // console.log(IERC20(address(eEth)).balanceOf(address(this))); + } +} From 24682409c46a32115347455e5b8b94089af70176 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 29 Feb 2024 18:08:42 +0100 Subject: [PATCH 09/16] fix: Fixed swap function and initial tests --- evm/src/etherfi/EtherfiAdapter.sol | 85 ++++++++++-- evm/test/EtherfiAdapter.t.sol | 213 +++++++++++++++++++++++++++-- 2 files changed, 272 insertions(+), 26 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index 1ac19de..da3f8f8 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -94,7 +94,7 @@ contract EtherfiAdapter is ISwapAdapter { } } else { if (sellTokenAddress == address(eEth)) { - trade.calculatedAmount = swapEthForWeEth(specifiedAmount, side); + trade.calculatedAmount = swapEethForWeEth(specifiedAmount, side); } else { trade.calculatedAmount = swapWeEthForEeth(specifiedAmount, side); } @@ -119,13 +119,11 @@ contract EtherfiAdapter is ISwapAdapter { checkInputTokens(address(sellToken), address(buyToken)) returns (uint256[] memory limits) { - address sellTokenAddress = address(sellToken); - address buyTokenAddress = address(buyToken); limits = new uint256[](2); /// @dev only limit on Etherfi is applied on deposits(eth->eETH), and is type(uint128).max /// but we use the same amount for the others to underestimate - limits[0] = type(uint128).max; + limits[0] = IERC20(address(eEth)).totalSupply(); limits[1] = limits[0]; } @@ -158,6 +156,8 @@ contract EtherfiAdapter is ISwapAdapter { /// @inheritdoc ISwapAdapter function getPoolIds(uint256, uint256) external + view + override returns (bytes32[] memory ids) { ids = new bytes32[](1); @@ -172,15 +172,17 @@ contract EtherfiAdapter is ISwapAdapter { { if (side == OrderSide.Buy) { uint256 amountIn = getAmountIn(address(0), address(eEth), amount); - uint256 receivedAmount = liquidityPool.deposit{value: amountIn}(); + liquidityPool.deposit{value: amountIn}(); IERC20(address(eEth)).safeTransfer( - address(msg.sender), receivedAmount + address(msg.sender), amount ); return amountIn; } else { - uint256 receivedAmount = liquidityPool.deposit{value: amount}(); - IERC20(address(eEth)).safeTransfer( - address(msg.sender), receivedAmount + uint256 balBefore = IERC20(address(eEth)).balanceOf(address(msg.sender)); + liquidityPool.deposit{value: amount}(); + uint256 receivedAmount = IERC20(address(eEth)).balanceOf(address(msg.sender)) - balBefore; + IERC20(address(eEth)).transfer( + msg.sender, receivedAmount ); return receivedAmount; } @@ -193,22 +195,30 @@ contract EtherfiAdapter is ISwapAdapter { returns (uint256) { if (side == OrderSide.Buy) { + IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); uint256 amountIn = getAmountIn(address(0), address(weEth), amount); IERC20(address(eEth)).approve(address(weEth), amountIn); + uint256 receivedAmountEeth = liquidityPool.deposit{value: amountIn}(); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); + IERC20(address(weEth)).safeTransfer( address(msg.sender), receivedAmount ); + return amountIn; } else { + IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); IERC20(address(eEth)).approve(address(weEth), amount); + uint256 receivedAmountEeth = liquidityPool.deposit{value: amount}(); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); + IERC20(address(weEth)).safeTransfer( address(msg.sender), receivedAmount ); + return receivedAmount; } } @@ -219,7 +229,30 @@ contract EtherfiAdapter is ISwapAdapter { internal returns (uint256) { - if (side == OrderSide.Buy) {} else {} + if (side == OrderSide.Buy) { + uint256 amountIn = getAmountIn(address(eEth), address(weEth), amount); + IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); + IERC20(address(eEth)).approve(address(weEth), amountIn); + + uint256 balBefore = eEth.shares(address(this)); + uint256 receivedAmount = weEth.wrap(amountIn); + uint256 realSpentEeth = balBefore - eEth.shares(address(this)); + + IERC20(address(weEth)).safeTransfer( + address(msg.sender), receivedAmount + ); + + return realSpentEeth; + } else { + IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); + IERC20(address(eEth)).approve(address(weEth), amount); + uint256 receivedAmount = weEth.wrap(amount); + + IERC20(address(weEth)).safeTransfer( + address(msg.sender), receivedAmount + ); + return receivedAmount; + } } /// @notice Swap weETH for eEth @@ -228,7 +261,31 @@ contract EtherfiAdapter is ISwapAdapter { internal returns (uint256) { - if (side == OrderSide.Buy) {} else {} + if (side == OrderSide.Buy) { + uint256 amountIn = getAmountIn(address(weEth), address(eEth), amount); + IERC20(address(weEth)).safeTransferFrom(msg.sender, address(this), amountIn); + uint256 receivedAmount = weEth.unwrap(amountIn); + IERC20(address(eEth)).safeTransfer( + address(msg.sender), receivedAmount + ); + return amountIn; + } else { + IERC20(address(weEth)).safeTransferFrom(msg.sender, address(this), amount); + uint256 receivedAmount = weEth.unwrap(amount); + IERC20(address(eEth)).safeTransfer( + address(msg.sender), receivedAmount + ); + return receivedAmount; + } + } + + /// @dev copy of '_sharesForDepositAmount' internal function in LiquidityPool + function _sharesForDepositAmount(uint256 _depositAmount) internal view returns (uint256) { + uint256 totalPooledEther = liquidityPool.getTotalPooledEther() - _depositAmount; + if (totalPooledEther == 0) { + return _depositAmount; + } + return (_depositAmount * eEth.totalShares()) / totalPooledEther; } /// @notice Get swap price @@ -241,9 +298,9 @@ contract EtherfiAdapter is ISwapAdapter { { if (sellToken == address(0)) { if (buyToken == address(eEth)) { - return Fraction(liquidityPool.sharesForAmount(amount), amount); + return Fraction(_sharesForDepositAmount(amount), amount); } else { - uint256 eEthOut = liquidityPool.sharesForAmount(amount); + uint256 eEthOut = _sharesForDepositAmount(amount); return Fraction(liquidityPool.sharesForAmount(eEthOut), amount); } } else if (sellToken == address(eEth)) { @@ -269,7 +326,7 @@ contract EtherfiAdapter is ISwapAdapter { } } else if (sellToken == address(eEth)) { // eEth-weEth - return weEth.getEETHByWeETH(amountOut); + return liquidityPool.amountForShare(amountOut); } else { // weEth-eEth return weEth.getWeETHByeETH(amountOut); diff --git a/evm/test/EtherfiAdapter.t.sol b/evm/test/EtherfiAdapter.t.sol index 2834ef6..0bef988 100644 --- a/evm/test/EtherfiAdapter.t.sol +++ b/evm/test/EtherfiAdapter.t.sol @@ -9,29 +9,218 @@ import "src/etherfi/EtherfiAdapter.sol"; contract EtherfiAdapterTest is Test, ISwapAdapterTypes { EtherfiAdapter adapter; - IWeEth wEeth = IWeEth(0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee); + IWeEth weEth = IWeEth(0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee); IeEth eEth; function setUp() public { uint256 forkBlock = 19218495; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - adapter = new EtherfiAdapter(address(wEeth)); - eEth = wEeth.eETH(); + adapter = new EtherfiAdapter(address(weEth)); + eEth = weEth.eETH(); - vm.label(address(wEeth), "WeETH"); + vm.label(address(weEth), "WeETH"); vm.label(address(eEth), "eETH"); } receive() external payable {} - function testMe() public { - // // uint256 requiredETH = adapter.getETHRequiredToMintEeth(1 ether); - // deal(address(adapter), 100 ether); - // // adapter.swapEthForEeth(1 ether, OrderSide.Buy); - // IERC20(address(eEth)).approve(address(adapter), type(uint256).max); + function testPriceFuzzEtherfi(uint256 amount0, uint256 amount1) public { + bytes32 pair = bytes32(0); + uint256[] memory limits = adapter.getLimits(pair, IERC20(address(weEth)), IERC20(address(eEth))); + vm.assume(amount0 < limits[0] && amount0 > 0); + vm.assume(amount1 < limits[1] && amount1 > 0); + + uint256[] memory amounts = new uint256[](2); + amounts[0] = amount0; + amounts[1] = amount1; + + Fraction[] memory prices = adapter.price(pair, IERC20(address(weEth)), IERC20(address(eEth)), amounts); + + for (uint256 i = 0; i < prices.length; i++) { + assertGt(prices[i].numerator, 0); + assertGt(prices[i].denominator, 0); + } + } - // console.log(IERC20(address(eEth)).balanceOf(address(this))); - // console.log(adapter.swapEthForWeEth(1 ether, OrderSide.Buy)); - // console.log(IERC20(address(eEth)).balanceOf(address(this))); + function testSwapFuzzEtherfiEethWeEth(uint256 specifiedAmount, bool isBuy) public { + OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; + + IERC20 eEth_ = IERC20(address(eEth)); + IERC20 weEth_ = IERC20(address(weEth)); + bytes32 pair = bytes32(0); + uint256[] memory limits = adapter.getLimits(pair, eEth_, weEth_); + + if (side == OrderSide.Buy) { + vm.assume(specifiedAmount < limits[1] && specifiedAmount > 100); + + /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + deal(address(adapter), type(uint256).max); + adapter.swap(pair, IERC20(address(0)), eEth_, OrderSide.Buy, limits[0]); + + eEth_.approve(address(adapter), type(uint256).max); + } else { + vm.assume(specifiedAmount < limits[0] && specifiedAmount > 100); + + /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + deal(address(adapter), type(uint128).max); + adapter.swap(pair, IERC20(address(0)), eEth_, OrderSide.Buy, specifiedAmount); + + eEth_.approve(address(adapter), specifiedAmount); + } + + uint256 eEth_balance = eEth_.balanceOf(address(this)); + uint256 weEth_balance = weEth_.balanceOf(address(this)); + + Trade memory trade = + adapter.swap(pair, eEth_, weEth_, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + if (side == OrderSide.Buy) { + assertGe( + specifiedAmount, + weEth_.balanceOf(address(this)) - weEth_balance + ); + /// @dev Transfer function contains rounding errors because of rewards in weETH contract, therefore we assume a +/-2 tolerance + assertLe( + specifiedAmount - 2, + weEth_.balanceOf(address(this)) - weEth_balance + ); + assertLe( + trade.calculatedAmount - 2, + eEth_balance - eEth_.balanceOf(address(this)) + ); + } else { + assertGe( + specifiedAmount, + eEth_balance - eEth_.balanceOf(address(this)) + ); + /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance + assertLe( + specifiedAmount - 2, + eEth_balance - eEth_.balanceOf(address(this)) + ); + assertEq( + trade.calculatedAmount, + weEth_.balanceOf(address(this)) - weEth_balance + ); + } + } + } + + function testSwapFuzzEtherfiWeEthEth(uint256 specifiedAmount, bool isBuy) public { + OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; + + IERC20 eEth_ = IERC20(address(eEth)); + IERC20 weEth_ = IERC20(address(weEth)); + bytes32 pair = bytes32(0); + uint256[] memory limits = adapter.getLimits(pair, weEth_, eEth_); + + if (side == OrderSide.Buy) { + vm.assume(specifiedAmount < limits[1] && specifiedAmount > 100); + + /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + deal(address(adapter), type(uint256).max); + adapter.swap(pair, IERC20(address(0)), weEth_, OrderSide.Buy, limits[0]); + + weEth_.approve(address(adapter), type(uint256).max); + } else { + vm.assume(specifiedAmount < limits[0] && specifiedAmount > 100); + + /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + deal(address(adapter), type(uint128).max); + adapter.swap(pair, IERC20(address(0)), weEth_, OrderSide.Buy, specifiedAmount); + + weEth_.approve(address(adapter), specifiedAmount); + } + + uint256 eEth_balance = eEth_.balanceOf(address(this)); + uint256 weEth_balance = weEth_.balanceOf(address(this)); + + Trade memory trade = + adapter.swap(pair, weEth_, eEth_, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + if (side == OrderSide.Buy) { + assertGe( + specifiedAmount, + eEth_.balanceOf(address(this)) - eEth_balance + ); + /// @dev Transfer function contains rounding errors because of rewards in weETH contract, therefore we assume a +/-2 tolerance + assertLe( + specifiedAmount - 2, + eEth_.balanceOf(address(this)) - eEth_balance + ); + assertLe( + trade.calculatedAmount - 2, + weEth_balance - weEth_.balanceOf(address(this)) + ); + } else { + assertGe( + specifiedAmount, + weEth_balance - weEth_.balanceOf(address(this)) + ); + /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance + assertLe( + specifiedAmount - 2, + weEth_balance - weEth_.balanceOf(address(this)) + ); + assertEq( + trade.calculatedAmount, + eEth_.balanceOf(address(this)) - eEth_balance + ); + } + } + } + + function testSwapFuzzEtherfiEthEeth(uint256 specifiedAmount, bool isBuy) public { + OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; + + IERC20 eth_ = IERC20(address(0)); + IERC20 eEth_ = IERC20(address(eEth)); + bytes32 pair = bytes32(0); + uint256[] memory limits = adapter.getLimits(pair, eth_, eEth_); + + if (side == OrderSide.Buy) { + vm.assume(specifiedAmount < limits[1] && specifiedAmount > 10); + + deal(address(adapter), 100**18); + } else { + vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10); + + deal(address(adapter), specifiedAmount); + } + + uint256 eth_balance = address(adapter).balance; + uint256 eEth_balance = eEth_.balanceOf(address(this)); + + Trade memory trade = + adapter.swap(pair, eth_, eEth_, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + if (side == OrderSide.Buy) { + assertGe( + specifiedAmount, + eEth_.balanceOf(address(this)) - eEth_balance + ); + /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance + assertLe( + specifiedAmount - 2, + eEth_.balanceOf(address(this)) - eEth_balance + ); + assertEq( + trade.calculatedAmount, + eth_balance - address(adapter).balance + ); + } else { + assertEq( + specifiedAmount, + eth_balance - address(adapter).balance + ); + assertEq( + trade.calculatedAmount, + eEth_.balanceOf(address(this)) - eEth_balance + ); + } + } } } From 57dc954f0caa4567dc29ea52edc302275c9f330b Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 1 Mar 2024 16:23:56 +0100 Subject: [PATCH 10/16] fix: Fixed getLimits and improved swap --- evm/src/etherfi/EtherfiAdapter.sol | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index da3f8f8..5db8764 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -121,9 +121,13 @@ contract EtherfiAdapter is ISwapAdapter { { limits = new uint256[](2); - /// @dev only limit on Etherfi is applied on deposits(eth->eETH), and is type(uint128).max - /// but we use the same amount for the others to underestimate - limits[0] = IERC20(address(eEth)).totalSupply(); + /// @dev Limits are underestimated to 90% of totalSupply as both weEth and eEth have no limits but revert in some cases + if(address(sellToken) == address(weEth) || address(buyToken) == address(weEth)) { + limits[0] = IERC20(address(weEth)).totalSupply() * 90 / 100; + } + else { + limits[0] = IERC20(address(eEth)).totalSupply() * 90 / 100; + } limits[1] = limits[0]; } @@ -194,13 +198,13 @@ contract EtherfiAdapter is ISwapAdapter { internal returns (uint256) { + IERC20 eEth_ = IERC20(address(eEth)); if (side == OrderSide.Buy) { - IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); uint256 amountIn = getAmountIn(address(0), address(weEth), amount); - IERC20(address(eEth)).approve(address(weEth), amountIn); uint256 receivedAmountEeth = liquidityPool.deposit{value: amountIn}(); + eEth_.approve(address(weEth), receivedAmountEeth); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); IERC20(address(weEth)).safeTransfer( @@ -209,10 +213,8 @@ contract EtherfiAdapter is ISwapAdapter { return amountIn; } else { - IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); - IERC20(address(eEth)).approve(address(weEth), amount); - uint256 receivedAmountEeth = liquidityPool.deposit{value: amount}(); + eEth_.approve(address(weEth), receivedAmountEeth); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); IERC20(address(weEth)).safeTransfer( From 9deddb52412e939b650bc3882390a70674fb55e3 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 1 Mar 2024 19:02:00 +0100 Subject: [PATCH 11/16] feat: Finalized tests --- evm/test/EtherfiAdapter.t.sol | 153 ++++++++++++++++++++++++++++++---- 1 file changed, 139 insertions(+), 14 deletions(-) diff --git a/evm/test/EtherfiAdapter.t.sol b/evm/test/EtherfiAdapter.t.sol index 0bef988..1527d5c 100644 --- a/evm/test/EtherfiAdapter.t.sol +++ b/evm/test/EtherfiAdapter.t.sol @@ -107,11 +107,12 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { } } - function testSwapFuzzEtherfiWeEthEth(uint256 specifiedAmount, bool isBuy) public { + function testSwapFuzzEtherfiWeEthEeth(uint256 specifiedAmount, bool isBuy) public { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; IERC20 eEth_ = IERC20(address(eEth)); IERC20 weEth_ = IERC20(address(weEth)); + uint256 weEth_bal_before = weEth_.balanceOf(address(this)); bytes32 pair = bytes32(0); uint256[] memory limits = adapter.getLimits(pair, weEth_, eEth_); @@ -136,18 +137,22 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { uint256 eEth_balance = eEth_.balanceOf(address(this)); uint256 weEth_balance = weEth_.balanceOf(address(this)); + /// @dev as of rounding errors in Etherfi, specifiedAmount might lose small digits for small numbers + /// therefore we use weEth_balance - weEth_bal_before as specifiedAmount + uint256 realAmountWeEth_ = weEth_balance - weEth_bal_before; + Trade memory trade = - adapter.swap(pair, weEth_, eEth_, side, specifiedAmount); + adapter.swap(pair, weEth_, eEth_, side, realAmountWeEth_); if (trade.calculatedAmount > 0) { if (side == OrderSide.Buy) { assertGe( - specifiedAmount, + realAmountWeEth_, eEth_.balanceOf(address(this)) - eEth_balance ); /// @dev Transfer function contains rounding errors because of rewards in weETH contract, therefore we assume a +/-2 tolerance assertLe( - specifiedAmount - 2, + realAmountWeEth_ - 2, eEth_.balanceOf(address(this)) - eEth_balance ); assertLe( @@ -155,16 +160,15 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { weEth_balance - weEth_.balanceOf(address(this)) ); } else { - assertGe( - specifiedAmount, - weEth_balance - weEth_.balanceOf(address(this)) - ); - /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance - assertLe( - specifiedAmount - 2, - weEth_balance - weEth_.balanceOf(address(this)) - ); assertEq( + realAmountWeEth_, + weEth_balance - weEth_.balanceOf(address(this)) + ); + assertLe( + trade.calculatedAmount - 2, + eEth_.balanceOf(address(this)) - eEth_balance + ); + assertGe( trade.calculatedAmount, eEth_.balanceOf(address(this)) - eEth_balance ); @@ -183,7 +187,7 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { if (side == OrderSide.Buy) { vm.assume(specifiedAmount < limits[1] && specifiedAmount > 10); - deal(address(adapter), 100**18); + deal(address(adapter), eEth_.totalSupply()); } else { vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10); @@ -223,4 +227,125 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { } } } + + function testSwapFuzzEtherfiEthWeEth(uint256 specifiedAmount, bool isBuy) public { + OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; + + IERC20 eth_ = IERC20(address(0)); + IERC20 weEth_ = IERC20(address(weEth)); + bytes32 pair = bytes32(0); + uint256[] memory limits = adapter.getLimits(pair, eth_, weEth_); + + if (side == OrderSide.Buy) { + vm.assume(specifiedAmount < limits[1] && specifiedAmount > 10); + + deal(address(adapter), weEth_.totalSupply()); + } else { + vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10); + + deal(address(adapter), specifiedAmount); + } + + uint256 eth_balance = address(adapter).balance; + uint256 weEth_balance = weEth_.balanceOf(address(this)); + + Trade memory trade = + adapter.swap(pair, eth_, weEth_, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + if (side == OrderSide.Buy) { + assertGe( + specifiedAmount, + weEth_.balanceOf(address(this)) - weEth_balance + ); + /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance + assertLe( + specifiedAmount - 2, + weEth_.balanceOf(address(this)) - weEth_balance + ); + assertEq( + trade.calculatedAmount, + eth_balance - address(adapter).balance + ); + } else { + assertEq( + specifiedAmount, + eth_balance - address(adapter).balance + ); + assertEq( + trade.calculatedAmount, + weEth_.balanceOf(address(this)) - weEth_balance + ); + } + } + } + + function testSwapSellIncreasingEtherfi() public { + executeIncreasingSwapsEtherfi(OrderSide.Sell); + } + + function testSwapBuyIncreasingEtherfi() public { + executeIncreasingSwapsEtherfi(OrderSide.Buy); + } + + function executeIncreasingSwapsIntegral(OrderSide side) internal { + bytes32 pair = bytes32(0); + + uint256 amountConstant_ = side == 10**18; + + uint256[] memory amounts = new uint256[](TEST_ITERATIONS); + amounts[0] = amountConstant_; + for (uint256 i = 1; i < TEST_ITERATIONS; i++) { + amounts[i] = amountConstant_ * i; + } + + Trade[] memory trades = new Trade[](TEST_ITERATIONS); + uint256 beforeSwap; + for (uint256 i = 1; i < TEST_ITERATIONS; i++) { + beforeSwap = vm.snapshot(); + + deal(address(USDC), address(this), amounts[i]); + USDC.approve(address(adapter), amounts[i]); + + trades[i] = adapter.swap(pair, USDC, WETH, side, amounts[i]); + vm.revertTo(beforeSwap); + } + + for (uint256 i = 1; i < TEST_ITERATIONS - 1; i++) { + assertLe( + trades[i].calculatedAmount, + trades[i + 1].calculatedAmount + ); + assertLe(trades[i].gasUsed, trades[i + 1].gasUsed); + assertEq(trades[i].price.compareFractions(trades[i + 1].price), 0); + } + } + + function testGetCapabilitiesEtherfi( + bytes32 pair, + address t0, + address t1 + ) public { + Capability[] memory res = adapter.getCapabilities( + pair, + IERC20(t0), + IERC20(t1) + ); + + assertEq(res.length, 3); + } + + function testGetTokensEtherfi() public { + bytes32 pair = bytes32(0); + IERC20[] memory tokens = adapter.getTokens(pair); + + assertEq(tokens.length, 3); + } + + function testGetLimitsEtherfi() public { + bytes32 pair = bytes32(0); + uint256[] memory limits = adapter.getLimits(pair, IERC20(address(eEth)), IERC20(address(weEth))); + + assertEq(limits.length, 2); + } } From 3fabb488055b424a69d9fd0760cd4db413cd6150 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 4 Mar 2024 10:10:23 +0100 Subject: [PATCH 12/16] feat: Finalized tests --- evm/test/EtherfiAdapter.t.sol | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/evm/test/EtherfiAdapter.t.sol b/evm/test/EtherfiAdapter.t.sol index 1527d5c..b7db414 100644 --- a/evm/test/EtherfiAdapter.t.sol +++ b/evm/test/EtherfiAdapter.t.sol @@ -8,10 +8,14 @@ import "src/libraries/FractionMath.sol"; import "src/etherfi/EtherfiAdapter.sol"; contract EtherfiAdapterTest is Test, ISwapAdapterTypes { + using FractionMath for Fraction; + EtherfiAdapter adapter; IWeEth weEth = IWeEth(0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee); IeEth eEth; + uint256 constant TEST_ITERATIONS = 100; + function setUp() public { uint256 forkBlock = 19218495; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); @@ -288,15 +292,15 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { executeIncreasingSwapsEtherfi(OrderSide.Buy); } - function executeIncreasingSwapsIntegral(OrderSide side) internal { + function executeIncreasingSwapsEtherfi(OrderSide side) internal { bytes32 pair = bytes32(0); - uint256 amountConstant_ = side == 10**18; + uint256 amountConstant_ = 10**18; uint256[] memory amounts = new uint256[](TEST_ITERATIONS); amounts[0] = amountConstant_; for (uint256 i = 1; i < TEST_ITERATIONS; i++) { - amounts[i] = amountConstant_ * i; + amounts[i] = amountConstant_ + i; } Trade[] memory trades = new Trade[](TEST_ITERATIONS); @@ -304,10 +308,10 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { for (uint256 i = 1; i < TEST_ITERATIONS; i++) { beforeSwap = vm.snapshot(); - deal(address(USDC), address(this), amounts[i]); - USDC.approve(address(adapter), amounts[i]); + deal(address(weEth), address(this), amounts[i]); + IERC20(address(weEth)).approve(address(adapter), amounts[i]); - trades[i] = adapter.swap(pair, USDC, WETH, side, amounts[i]); + trades[i] = adapter.swap(pair, IERC20(address(weEth)), IERC20(address(eEth)), side, amounts[i]); vm.revertTo(beforeSwap); } @@ -317,7 +321,6 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { trades[i + 1].calculatedAmount ); assertLe(trades[i].gasUsed, trades[i + 1].gasUsed); - assertEq(trades[i].price.compareFractions(trades[i + 1].price), 0); } } From 45f8aa1268f0e1510594744e42ba4c77291ae832 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 4 Mar 2024 10:11:15 +0100 Subject: [PATCH 13/16] chore: Changed approve to safeIncreaseAllowance --- evm/src/etherfi/EtherfiAdapter.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index 5db8764..ad5d78d 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -204,7 +204,7 @@ contract EtherfiAdapter is ISwapAdapter { uint256 receivedAmountEeth = liquidityPool.deposit{value: amountIn}(); - eEth_.approve(address(weEth), receivedAmountEeth); + eEth_.safeIncreaseAllowance(address(weEth), receivedAmountEeth); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); IERC20(address(weEth)).safeTransfer( @@ -214,7 +214,7 @@ contract EtherfiAdapter is ISwapAdapter { return amountIn; } else { uint256 receivedAmountEeth = liquidityPool.deposit{value: amount}(); - eEth_.approve(address(weEth), receivedAmountEeth); + eEth_.safeIncreaseAllowance(address(weEth), receivedAmountEeth); uint256 receivedAmount = weEth.wrap(receivedAmountEeth); IERC20(address(weEth)).safeTransfer( @@ -234,7 +234,7 @@ contract EtherfiAdapter is ISwapAdapter { if (side == OrderSide.Buy) { uint256 amountIn = getAmountIn(address(eEth), address(weEth), amount); IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); - IERC20(address(eEth)).approve(address(weEth), amountIn); + IERC20(address(eEth)).safeIncreaseAllowance(address(weEth), amountIn); uint256 balBefore = eEth.shares(address(this)); uint256 receivedAmount = weEth.wrap(amountIn); @@ -247,7 +247,7 @@ contract EtherfiAdapter is ISwapAdapter { return realSpentEeth; } else { IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); - IERC20(address(eEth)).approve(address(weEth), amount); + IERC20(address(eEth)).safeIncreaseAllowance(address(weEth), amount); uint256 receivedAmount = weEth.wrap(amount); IERC20(address(weEth)).safeTransfer( From 2a6093a9947b3872d42109598b00cab5b7880ef6 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 6 Mar 2024 18:17:56 +0100 Subject: [PATCH 14/16] fix: Review fixes and improvements --- evm/src/etherfi/EtherfiAdapter.sol | 214 +++++++++++++++++++---------- evm/test/EtherfiAdapter.t.sol | 161 ++++++++++++++-------- 2 files changed, 241 insertions(+), 134 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index ad5d78d..0ba73d9 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -7,13 +7,15 @@ import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title Etherfi Adapter -/// @dev This contract supports the following swaps: ETH->eETH, wETH<->eETH, +/// @dev This contract supports the following swaps: ETH->eETH, weETH<->eETH, /// ETH->weETH contract EtherfiAdapter is ISwapAdapter { using SafeERC20 for IERC20; - IWeEth weEth; - IeEth eEth; + uint256 constant PRECISE_UNIT = 10 ** 18; + + IWeEth immutable weEth; + IeEth immutable eEth; ILiquidityPool public liquidityPool; constructor(address _weEth) { @@ -58,10 +60,28 @@ contract EtherfiAdapter is ISwapAdapter { _prices = new Fraction[](_specifiedAmounts.length); address sellTokenAddress = address(_sellToken); address buyTokenAddress = address(_buyToken); + uint256 totalPooledEther = liquidityPool.getTotalPooledEther(); + uint256 eEthTotalShares = eEth.totalShares(); for (uint256 i = 0; i < _specifiedAmounts.length; i++) { + if (sellTokenAddress == address(0)) { + uint256 sharesForDepositAmount = _sharesForDepositAmount( + _specifiedAmounts[i], totalPooledEther, eEthTotalShares + ); + _prices[i] = getPriceAt( + sellTokenAddress, + buyTokenAddress, + _specifiedAmounts[i], + totalPooledEther + _specifiedAmounts[i], + eEthTotalShares + sharesForDepositAmount + ); + } _prices[i] = getPriceAt( - sellTokenAddress, buyTokenAddress, _specifiedAmounts[i] + sellTokenAddress, + buyTokenAddress, + _specifiedAmounts[i], + totalPooledEther, + eEthTotalShares ); } } @@ -100,15 +120,13 @@ contract EtherfiAdapter is ISwapAdapter { } } trade.gasUsed = gasBefore - gasleft(); - if (side == OrderSide.Sell) { - trade.price = getPriceAt( - sellTokenAddress, buyTokenAddress, specifiedAmount - ); - } else { - trade.price = getPriceAt( - sellTokenAddress, buyTokenAddress, trade.calculatedAmount - ); - } + trade.price = getPriceAt( + sellTokenAddress, + buyTokenAddress, + PRECISE_UNIT, + liquidityPool.getTotalPooledEther(), + eEth.totalShares() + ); } /// @inheritdoc ISwapAdapter @@ -121,11 +139,14 @@ contract EtherfiAdapter is ISwapAdapter { { limits = new uint256[](2); - /// @dev Limits are underestimated to 90% of totalSupply as both weEth and eEth have no limits but revert in some cases - if(address(sellToken) == address(weEth) || address(buyToken) == address(weEth)) { + /// @dev Limits are underestimated to 90% of totalSupply as both weEth + /// and eEth have no limits but revert in some cases + if ( + address(sellToken) == address(weEth) + || address(buyToken) == address(weEth) + ) { limits[0] = IERC20(address(weEth)).totalSupply() * 90 / 100; - } - else { + } else { limits[0] = IERC20(address(eEth)).totalSupply() * 90 / 100; } limits[1] = limits[0]; @@ -177,18 +198,15 @@ contract EtherfiAdapter is ISwapAdapter { if (side == OrderSide.Buy) { uint256 amountIn = getAmountIn(address(0), address(eEth), amount); liquidityPool.deposit{value: amountIn}(); - IERC20(address(eEth)).safeTransfer( - address(msg.sender), amount - ); + IERC20(address(eEth)).safeTransfer(address(msg.sender), amount); return amountIn; } else { - uint256 balBefore = IERC20(address(eEth)).balanceOf(address(msg.sender)); - liquidityPool.deposit{value: amount}(); - uint256 receivedAmount = IERC20(address(eEth)).balanceOf(address(msg.sender)) - balBefore; - IERC20(address(eEth)).transfer( - msg.sender, receivedAmount - ); - return receivedAmount; + uint256 receivedAmount = liquidityPool.deposit{value: amount}(); + uint256 balBeforeUser = + IERC20(address(eEth)).balanceOf(address(msg.sender)); + IERC20(address(eEth)).transfer(msg.sender, receivedAmount); + return IERC20(address(eEth)).balanceOf(address(msg.sender)) + - balBeforeUser; } } @@ -232,21 +250,26 @@ contract EtherfiAdapter is ISwapAdapter { returns (uint256) { if (side == OrderSide.Buy) { - uint256 amountIn = getAmountIn(address(eEth), address(weEth), amount); - IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); - IERC20(address(eEth)).safeIncreaseAllowance(address(weEth), amountIn); + uint256 amountIn = + getAmountIn(address(eEth), address(weEth), amount); + IERC20(address(eEth)).safeTransferFrom( + msg.sender, address(this), amountIn + ); + IERC20(address(eEth)).safeIncreaseAllowance( + address(weEth), amountIn + ); - uint256 balBefore = eEth.shares(address(this)); uint256 receivedAmount = weEth.wrap(amountIn); - uint256 realSpentEeth = balBefore - eEth.shares(address(this)); IERC20(address(weEth)).safeTransfer( address(msg.sender), receivedAmount ); - return realSpentEeth; + return amountIn; } else { - IERC20(address(eEth)).safeTransferFrom(msg.sender, address(this), amount); + IERC20(address(eEth)).safeTransferFrom( + msg.sender, address(this), amount + ); IERC20(address(eEth)).safeIncreaseAllowance(address(weEth), amount); uint256 receivedAmount = weEth.wrap(amount); @@ -264,51 +287,111 @@ contract EtherfiAdapter is ISwapAdapter { returns (uint256) { if (side == OrderSide.Buy) { - uint256 amountIn = getAmountIn(address(weEth), address(eEth), amount); - IERC20(address(weEth)).safeTransferFrom(msg.sender, address(this), amountIn); + uint256 amountIn = + getAmountIn(address(weEth), address(eEth), amount); + IERC20(address(weEth)).safeTransferFrom( + msg.sender, address(this), amountIn + ); uint256 receivedAmount = weEth.unwrap(amountIn); IERC20(address(eEth)).safeTransfer( address(msg.sender), receivedAmount ); return amountIn; } else { - IERC20(address(weEth)).safeTransferFrom(msg.sender, address(this), amount); - uint256 receivedAmount = weEth.unwrap(amount); - IERC20(address(eEth)).safeTransfer( - address(msg.sender), receivedAmount + IERC20(address(weEth)).safeTransferFrom( + msg.sender, address(this), amount ); - return receivedAmount; + uint256 receivedAmount = weEth.unwrap(amount); + uint256 balBeforeUser = + IERC20(address(eEth)).balanceOf(address(msg.sender)); + IERC20(address(eEth)).transfer(msg.sender, receivedAmount); + return IERC20(address(eEth)).balanceOf(address(msg.sender)) + - balBeforeUser; } } - /// @dev copy of '_sharesForDepositAmount' internal function in LiquidityPool - function _sharesForDepositAmount(uint256 _depositAmount) internal view returns (uint256) { - uint256 totalPooledEther = liquidityPool.getTotalPooledEther() - _depositAmount; - if (totalPooledEther == 0) { + /// @dev copy of '_sharesForDepositAmount' internal function in + /// LiquidityPool, without ether subtraction + function _sharesForDepositAmount( + uint256 _depositAmount, + uint256 _totalPooledEther, + uint256 _eEthTotalShares + ) internal pure returns (uint256) { + if (_totalPooledEther == 0) { return _depositAmount; } - return (_depositAmount * eEth.totalShares()) / totalPooledEther; + return (_depositAmount * _eEthTotalShares) / _totalPooledEther; + } + + /// @dev copy of 'getWeETHByeEth' function in weETH, dynamic + function _getWeETHByeEth( + uint256 _depositAmount, + uint256 _totalPooledEther, + uint256 _eEthTotalShares + ) internal pure returns (uint256) { + if (_totalPooledEther == 0) { + return 0; + } + return (_depositAmount * _eEthTotalShares) / _totalPooledEther; + } + + /// @dev copy of 'getEethByWeEth' function in weETH, dynamic + function _getEethByWeEth( + uint256 _depositAmount, + uint256 _totalPooledEther, + uint256 _eEthTotalShares + ) internal pure returns (uint256) { + if (_eEthTotalShares == 0) { + return 0; + } + return (_depositAmount * _totalPooledEther) / _eEthTotalShares; } /// @notice Get swap price /// @param sellToken token to sell /// @param buyToken token to buy - function getPriceAt(address sellToken, address buyToken, uint256 amount) - internal - view - returns (Fraction memory) - { + /// @param totalPooledEther total pooled ether after or before trade if + /// required + /// @param eEthTotalShares total shares of eETH after or before trade if + /// required + function getPriceAt( + address sellToken, + address buyToken, + uint256 amount, + uint256 totalPooledEther, + uint256 eEthTotalShares + ) internal view returns (Fraction memory) { if (sellToken == address(0)) { if (buyToken == address(eEth)) { - return Fraction(_sharesForDepositAmount(amount), amount); + return Fraction( + _sharesForDepositAmount( + amount, totalPooledEther, eEthTotalShares + ), + amount + ); } else { - uint256 eEthOut = _sharesForDepositAmount(amount); - return Fraction(liquidityPool.sharesForAmount(eEthOut), amount); + uint256 eEthOut = _sharesForDepositAmount( + amount, totalPooledEther, eEthTotalShares + ); + return Fraction( + _getWeETHByeEth( + eEthOut, + totalPooledEther + amount, + eEthTotalShares + eEthOut + ), + amount + ); } } else if (sellToken == address(eEth)) { - return Fraction(liquidityPool.sharesForAmount(amount), amount); + return Fraction( + _getWeETHByeEth(amount, totalPooledEther, eEthTotalShares), + amount + ); } else { - return Fraction(liquidityPool.amountForShare(amount), amount); + return Fraction( + _getEethByWeEth(amount, totalPooledEther, eEthTotalShares), + amount + ); } } @@ -328,7 +411,7 @@ contract EtherfiAdapter is ISwapAdapter { } } else if (sellToken == address(eEth)) { // eEth-weEth - return liquidityPool.amountForShare(amountOut); + return weEth.getEETHByWeETH(amountOut); } else { // weEth-eEth return weEth.getWeETHByeETH(amountOut); @@ -336,22 +419,6 @@ contract EtherfiAdapter is ISwapAdapter { } } -interface IWithdrawRequestNFT { - function requestWithdraw( - uint96 amountOfEEth, - uint96 shareOfEEth, - address recipient, - uint256 fee - ) external payable returns (uint256); - - function getClaimableAmount(uint256 tokenId) - external - view - returns (uint256); - - function claimWithdraw(uint256 tokenId) external; -} - interface ILiquidityPool { function numPendingDeposits() external view returns (uint32); function totalValueOutOfLp() external view returns (uint128); @@ -378,7 +445,6 @@ interface ILiquidityPool { function requestWithdraw(address recipient, uint256 amount) external returns (uint256); - function withdrawRequestNFT() external view returns (IWithdrawRequestNFT); } interface IeEth { diff --git a/evm/test/EtherfiAdapter.t.sol b/evm/test/EtherfiAdapter.t.sol index b7db414..8d0c563 100644 --- a/evm/test/EtherfiAdapter.t.sol +++ b/evm/test/EtherfiAdapter.t.sol @@ -30,23 +30,29 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { function testPriceFuzzEtherfi(uint256 amount0, uint256 amount1) public { bytes32 pair = bytes32(0); - uint256[] memory limits = adapter.getLimits(pair, IERC20(address(weEth)), IERC20(address(eEth))); + uint256[] memory limits = adapter.getLimits( + pair, IERC20(address(weEth)), IERC20(address(eEth)) + ); vm.assume(amount0 < limits[0] && amount0 > 0); vm.assume(amount1 < limits[1] && amount1 > 0); - + uint256[] memory amounts = new uint256[](2); amounts[0] = amount0; amounts[1] = amount1; - - Fraction[] memory prices = adapter.price(pair, IERC20(address(weEth)), IERC20(address(eEth)), amounts); - + + Fraction[] memory prices = adapter.price( + pair, IERC20(address(weEth)), IERC20(address(eEth)), amounts + ); + for (uint256 i = 0; i < prices.length; i++) { assertGt(prices[i].numerator, 0); assertGt(prices[i].denominator, 0); } } - function testSwapFuzzEtherfiEethWeEth(uint256 specifiedAmount, bool isBuy) public { + function testSwapFuzzEtherfiEethWeEth(uint256 specifiedAmount, bool isBuy) + public + { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; IERC20 eEth_ = IERC20(address(eEth)); @@ -57,17 +63,23 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { if (side == OrderSide.Buy) { vm.assume(specifiedAmount < limits[1] && specifiedAmount > 100); - /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + /// @dev workaround for eETH "deal", as standard ERC20 does not + /// work(balance is shares) deal(address(adapter), type(uint256).max); - adapter.swap(pair, IERC20(address(0)), eEth_, OrderSide.Buy, limits[0]); + adapter.swap( + pair, IERC20(address(0)), eEth_, OrderSide.Buy, limits[0] + ); eEth_.approve(address(adapter), type(uint256).max); } else { vm.assume(specifiedAmount < limits[0] && specifiedAmount > 100); - /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + /// @dev workaround for eETH "deal", as standard ERC20 does not + /// work(balance is shares) deal(address(adapter), type(uint128).max); - adapter.swap(pair, IERC20(address(0)), eEth_, OrderSide.Buy, specifiedAmount); + adapter.swap( + pair, IERC20(address(0)), eEth_, OrderSide.Buy, specifiedAmount + ); eEth_.approve(address(adapter), specifiedAmount); } @@ -81,24 +93,32 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { if (trade.calculatedAmount > 0) { if (side == OrderSide.Buy) { assertGe( - specifiedAmount, - weEth_.balanceOf(address(this)) - weEth_balance + weEth_.balanceOf(address(this)) - weEth_balance, + specifiedAmount - 2 ); - /// @dev Transfer function contains rounding errors because of rewards in weETH contract, therefore we assume a +/-2 tolerance + /// @dev Transfer function contains rounding errors because of + /// rewards in weETH contract, therefore we assume a +/-2 + /// tolerance assertLe( - specifiedAmount - 2, - weEth_.balanceOf(address(this)) - weEth_balance + weEth_.balanceOf(address(this)) - weEth_balance, + specifiedAmount ); assertLe( - trade.calculatedAmount - 2, - eEth_balance - eEth_.balanceOf(address(this)) + eEth_balance - eEth_.balanceOf(address(this)), + trade.calculatedAmount + 2 + ); + assertGe( + eEth_balance - eEth_.balanceOf(address(this)), + trade.calculatedAmount - 1 ); } else { assertGe( specifiedAmount, eEth_balance - eEth_.balanceOf(address(this)) ); - /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance + /// @dev Transfer function contains rounding errors because of + /// rewards in eETH contract, therefore we assume a +/-2 + /// tolerance assertLe( specifiedAmount - 2, eEth_balance - eEth_.balanceOf(address(this)) @@ -111,7 +131,9 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { } } - function testSwapFuzzEtherfiWeEthEeth(uint256 specifiedAmount, bool isBuy) public { + function testSwapFuzzEtherfiWeEthEeth(uint256 specifiedAmount, bool isBuy) + public + { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; IERC20 eEth_ = IERC20(address(eEth)); @@ -123,17 +145,23 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { if (side == OrderSide.Buy) { vm.assume(specifiedAmount < limits[1] && specifiedAmount > 100); - /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + /// @dev workaround for eETH "deal", as standard ERC20 does not + /// work(balance is shares) deal(address(adapter), type(uint256).max); - adapter.swap(pair, IERC20(address(0)), weEth_, OrderSide.Buy, limits[0]); + adapter.swap( + pair, IERC20(address(0)), weEth_, OrderSide.Buy, limits[0] + ); weEth_.approve(address(adapter), type(uint256).max); } else { vm.assume(specifiedAmount < limits[0] && specifiedAmount > 100); - /// @dev workaround for eETH "deal", as standard ERC20 does not work(balance is shares) + /// @dev workaround for eETH "deal", as standard ERC20 does not + /// work(balance is shares) deal(address(adapter), type(uint128).max); - adapter.swap(pair, IERC20(address(0)), weEth_, OrderSide.Buy, specifiedAmount); + adapter.swap( + pair, IERC20(address(0)), weEth_, OrderSide.Buy, specifiedAmount + ); weEth_.approve(address(adapter), specifiedAmount); } @@ -141,7 +169,8 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { uint256 eEth_balance = eEth_.balanceOf(address(this)); uint256 weEth_balance = weEth_.balanceOf(address(this)); - /// @dev as of rounding errors in Etherfi, specifiedAmount might lose small digits for small numbers + /// @dev as of rounding errors in Etherfi, specifiedAmount might lose + /// small digits for small numbers /// therefore we use weEth_balance - weEth_bal_before as specifiedAmount uint256 realAmountWeEth_ = weEth_balance - weEth_bal_before; @@ -154,7 +183,9 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { realAmountWeEth_, eEth_.balanceOf(address(this)) - eEth_balance ); - /// @dev Transfer function contains rounding errors because of rewards in weETH contract, therefore we assume a +/-2 tolerance + /// @dev Transfer function contains rounding errors because of + /// rewards in weETH contract, therefore we assume a +/-2 + /// tolerance assertLe( realAmountWeEth_ - 2, eEth_.balanceOf(address(this)) - eEth_balance @@ -180,7 +211,9 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { } } - function testSwapFuzzEtherfiEthEeth(uint256 specifiedAmount, bool isBuy) public { + function testSwapFuzzEtherfiEthEeth(uint256 specifiedAmount, bool isBuy) + public + { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; IERC20 eth_ = IERC20(address(0)); @@ -210,7 +243,9 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { specifiedAmount, eEth_.balanceOf(address(this)) - eEth_balance ); - /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance + /// @dev Transfer function contains rounding errors because of + /// rewards in eETH contract, therefore we assume a +/-2 + /// tolerance assertLe( specifiedAmount - 2, eEth_.balanceOf(address(this)) - eEth_balance @@ -221,8 +256,7 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { ); } else { assertEq( - specifiedAmount, - eth_balance - address(adapter).balance + specifiedAmount, eth_balance - address(adapter).balance ); assertEq( trade.calculatedAmount, @@ -232,7 +266,9 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { } } - function testSwapFuzzEtherfiEthWeEth(uint256 specifiedAmount, bool isBuy) public { + function testSwapFuzzEtherfiEthWeEth(uint256 specifiedAmount, bool isBuy) + public + { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; IERC20 eth_ = IERC20(address(0)); @@ -262,7 +298,9 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { specifiedAmount, weEth_.balanceOf(address(this)) - weEth_balance ); - /// @dev Transfer function contains rounding errors because of rewards in eETH contract, therefore we assume a +/-2 tolerance + /// @dev Transfer function contains rounding errors because of + /// rewards in eETH contract, therefore we assume a +/-2 + /// tolerance assertLe( specifiedAmount - 2, weEth_.balanceOf(address(this)) - weEth_balance @@ -273,8 +311,7 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { ); } else { assertEq( - specifiedAmount, - eth_balance - address(adapter).balance + specifiedAmount, eth_balance - address(adapter).balance ); assertEq( trade.calculatedAmount, @@ -295,60 +332,64 @@ contract EtherfiAdapterTest is Test, ISwapAdapterTypes { function executeIncreasingSwapsEtherfi(OrderSide side) internal { bytes32 pair = bytes32(0); - uint256 amountConstant_ = 10**18; - + uint256 amountConstant_ = 10 ** 18; + uint256[] memory amounts = new uint256[](TEST_ITERATIONS); amounts[0] = amountConstant_; for (uint256 i = 1; i < TEST_ITERATIONS; i++) { - amounts[i] = amountConstant_ + i; + amounts[i] = amountConstant_ * i; } - + Trade[] memory trades = new Trade[](TEST_ITERATIONS); uint256 beforeSwap; for (uint256 i = 1; i < TEST_ITERATIONS; i++) { beforeSwap = vm.snapshot(); - + deal(address(weEth), address(this), amounts[i]); IERC20(address(weEth)).approve(address(adapter), amounts[i]); - - trades[i] = adapter.swap(pair, IERC20(address(weEth)), IERC20(address(eEth)), side, amounts[i]); + + trades[i] = adapter.swap( + pair, + IERC20(address(weEth)), + IERC20(address(eEth)), + side, + amounts[i] + ); vm.revertTo(beforeSwap); } - + for (uint256 i = 1; i < TEST_ITERATIONS - 1; i++) { - assertLe( - trades[i].calculatedAmount, - trades[i + 1].calculatedAmount + assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount); + console.log( + "Prices", trades[i].price.numerator, trades[i].price.denominator ); + console.log("Amounts", trades[i].calculatedAmount, amounts[i]); assertLe(trades[i].gasUsed, trades[i + 1].gasUsed); } } - function testGetCapabilitiesEtherfi( - bytes32 pair, - address t0, - address t1 - ) public { - Capability[] memory res = adapter.getCapabilities( - pair, - IERC20(t0), - IERC20(t1) - ); - + function testGetCapabilitiesEtherfi(bytes32 pair, address t0, address t1) + public + { + Capability[] memory res = + adapter.getCapabilities(pair, IERC20(t0), IERC20(t1)); + assertEq(res.length, 3); } - + function testGetTokensEtherfi() public { bytes32 pair = bytes32(0); IERC20[] memory tokens = adapter.getTokens(pair); - + assertEq(tokens.length, 3); } - + function testGetLimitsEtherfi() public { bytes32 pair = bytes32(0); - uint256[] memory limits = adapter.getLimits(pair, IERC20(address(eEth)), IERC20(address(weEth))); - + uint256[] memory limits = adapter.getLimits( + pair, IERC20(address(eEth)), IERC20(address(weEth)) + ); + assertEq(limits.length, 2); } } From 21a2ecac2890c95d90013f6eeb82cd2981307cbc Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 5 Apr 2024 17:53:50 +0200 Subject: [PATCH 15/16] fix: Review Fixes --- evm/src/etherfi/EtherfiAdapter.sol | 37 ++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/evm/src/etherfi/EtherfiAdapter.sol b/evm/src/etherfi/EtherfiAdapter.sol index 0ba73d9..e4a5c16 100644 --- a/evm/src/etherfi/EtherfiAdapter.sol +++ b/evm/src/etherfi/EtherfiAdapter.sol @@ -16,7 +16,7 @@ contract EtherfiAdapter is ISwapAdapter { IWeEth immutable weEth; IeEth immutable eEth; - ILiquidityPool public liquidityPool; + ILiquidityPool immutable liquidityPool; constructor(address _weEth) { weEth = IWeEth(_weEth); @@ -24,19 +24,26 @@ contract EtherfiAdapter is ISwapAdapter { liquidityPool = eEth.liquidityPool(); } - /// @dev Check if tokens in input are supported by this adapter + /// @dev Check if swap between provided sellToken and buyToken are supported + /// by this adapter modifier checkInputTokens(address sellToken, address buyToken) { if (sellToken == buyToken) { - revert Unavailable("This pool only supports eETH, weEth and ETH"); + revert Unavailable( + "This pool only supports ETH->eETH, weETH<->eETH and ETH->weETH swaps" + ); } if ( sellToken != address(weEth) && sellToken != address(eEth) && sellToken != address(0) ) { - revert Unavailable("This pool only supports eETH, weEth and ETH"); + revert Unavailable( + "This pool only supports ETH->eETH, weETH<->eETH and ETH->weETH swaps" + ); } if (buyToken != address(weEth) && buyToken != address(eEth)) { - revert Unavailable("This pool only supports eETH, weEth and ETH"); + revert Unavailable( + "This pool only supports ETH->eETH, weETH<->eETH and ETH->weETH swaps" + ); } _; } @@ -75,14 +82,15 @@ contract EtherfiAdapter is ISwapAdapter { totalPooledEther + _specifiedAmounts[i], eEthTotalShares + sharesForDepositAmount ); + } else { + _prices[i] = getPriceAt( + sellTokenAddress, + buyTokenAddress, + _specifiedAmounts[i], + totalPooledEther, + eEthTotalShares + ); } - _prices[i] = getPriceAt( - sellTokenAddress, - buyTokenAddress, - _specifiedAmounts[i], - totalPooledEther, - eEthTotalShares - ); } } @@ -120,6 +128,11 @@ contract EtherfiAdapter is ISwapAdapter { } } trade.gasUsed = gasBefore - gasleft(); + + /// @dev as the price is constant for all the traded amounts and depends + /// only on the totalPooledEther and totalShares, we can use a standard + /// amount(PRECISE_UNIT) to render a well-formatted price without + /// precisions loss trade.price = getPriceAt( sellTokenAddress, buyTokenAddress, From 0ae7fd2ed0c65b6d1060cf025e9f5c903f816700 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 5 Apr 2024 17:56:43 +0200 Subject: [PATCH 16/16] updated manifest --- evm/src/etherfi/manifest.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/etherfi/manifest.yaml b/evm/src/etherfi/manifest.yaml index c6cb262..0f655e4 100644 --- a/evm/src/etherfi/manifest.yaml +++ b/evm/src/etherfi/manifest.yaml @@ -1,7 +1,7 @@ # information about the author helps us reach out in case of issues. author: - name: Propellerheads.xyz - email: alan@propellerheads.xyz + name: shadowycoders.dev + email: hello@shadowycreators.com # Protocol Constants constants: