From 24682409c46a32115347455e5b8b94089af70176 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 29 Feb 2024 18:08:42 +0100 Subject: [PATCH] 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 + ); + } + } } }