From c470b2a20e377d532af8eeabd92054e152fef324 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 21 Dec 2023 17:43:07 +0100 Subject: [PATCH] fix: Fixed limits --- evm/src/integral/IntegralSwapAdapter.sol | 110 ++++++++++------------- 1 file changed, 46 insertions(+), 64 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index c35d6f9..a79c75e 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -16,6 +16,12 @@ contract IntegralSwapAdapter is ISwapAdapter { } /// @inheritdoc ISwapAdapter + /// @dev Integral always relies on a single pool linked to the factory to map two pairs, and does not use routing + /// we can then use getPriceByTokenAddresses() instead of getPriceByPairAddresses() + /// as they both return the same value and the first also handles the order of tokens inside. + /// @dev Since the price of a token is determined externally by Integral Oracles and not by reserves + /// it will always be the same (pre and post trade) and independent of the amounts swapped, + /// but we still return an array of length=specifiedAmounts.length with same values to make sure the return value is the expected from caller. function price( bytes32 _poolId, IERC20 _sellToken, @@ -24,16 +30,15 @@ contract IntegralSwapAdapter is ISwapAdapter { ) external view override returns (Fraction[] memory _prices) { _prices = new Fraction[](_specifiedAmounts.length); ITwapPair pair = ITwapPair(address(bytes20(_poolId))); - uint112 r0; - uint112 r1; - if (address(_sellToken) == pair.token0()) { // sell - (r0, r1) = pair.getReserves(); - } else { // buy - (r1, r0) = pair.getReserves(); + + bool inverted = false; + if (address(_sellToken) == pair.token1()) { + inverted = true; } + uint256 price = relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)); for (uint256 i = 0; i < _specifiedAmounts.length; i++) { - _prices[i] = getPriceAt(_specifiedAmounts[i], r0, r1, pair); + _prices[i] = price; } } @@ -49,14 +54,6 @@ contract IntegralSwapAdapter is ISwapAdapter { return trade; } - ITwapPair pair = ITwapPair(address(bytes20(poolId))); - uint112 r0; - uint112 r1; - if (address(sellToken) == pair.token0()) { - (r0, r1) = pair.getReserves(); - } else { - (r1, r0) = pair.getReserves(); - } uint256 gasBefore = gasleft(); if (side == OrderSide.Sell) { // sell trade.calculatedAmount = @@ -66,7 +63,7 @@ contract IntegralSwapAdapter is ISwapAdapter { buy(address(sellToken), address(buyToken), specifiedAmount); } trade.gasUsed = gasBefore - gasleft(); - trade.price = getPriceAt(specifiedAmount, r0, r1, pair); + trade.price = relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)); } /// @inheritdoc ISwapAdapter @@ -76,20 +73,7 @@ contract IntegralSwapAdapter is ISwapAdapter { override returns (uint256[] memory limits) { - ( - uint256 price_, - uint256 fee, - uint256 limitMin0, - uint256 limitMax0, - uint256 limitMin1, - uint256 limitMax1 - ) = relayer.getPoolState(address(sellToken), address(buyToken)); - - limits = new uint256[](4); - limits[0] = limitMax0; - limits[1] = limitMax1; - limits[2] = limitMin0; - limits[3] = limitMin1; + return _getLimits(poollId, sellToken, buyToken); } /// @inheritdoc ISwapAdapter @@ -136,38 +120,6 @@ contract IntegralSwapAdapter is ISwapAdapter { } } - /// @notice Calculates pool prices after trade for specified amounts - /// @param amountIn The amount of the token being sold. - /// @param reserveIn The reserve of the token being sold. - /// @param reserveOut The reserve of the token being bought. - /// @param pair (ITwapPair) The pair where to execute the swap in. - /// @dev Although Integral declares in its Docs that the fee is 1 BP(0.01%, 0.0001 multiplier), - /// it can be changed at any time by calling a function of the contract by its owner or operator, - /// therefore it is obtained dynamically to ensure this function output remains reliable over time - /// @return The price as a fraction corresponding to the provided amount. - function getPriceAt(uint256 amountIn, uint256 reserveIn, uint256 reserveOut, ITwapPair pair) - internal - view - returns (Fraction memory) - { - if (reserveIn == 0 || reserveOut == 0) { - revert Unavailable("At least one reserve is zero!"); - } - uint256 feeBP = relayer.swapFee(address(pair)); - - uint256 amountInWithFee = amountIn - ( (amountIn * feeBP) / 10**18 ); - uint256 numerator = amountInWithFee * reserveOut; - uint256 denominator = (reserveIn * 1000) + amountInWithFee; - uint256 amountOut = numerator / denominator; - uint256 newReserveOut = reserveOut - amountOut; - uint256 newReserveIn = reserveIn + amountIn; - - return Fraction( - newReserveOut * 1000, - newReserveIn - ( (newReserveIn * feeBP) / 10**18 ) - ); - } - /// @notice Executes a sell order on a given pool. /// @param sellToken The address of the token being sold. /// @param buyToken The address of the token being bought. @@ -179,8 +131,13 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256 amount ) internal returns (uint256) { address swapper = msg.sender; - uint256 amountOut = relayer.quoteSell(sellToken, buyToken, amount); + (uint256 limitMinSellToken, , uint256 limitMaxSellToken) = _getLimits(_poolId, _sellToken, _buyToken); + if(amount < limitMinSellToken || amount > limitMaxSellToken) { + revert Unavailable("specifiedAmount is out of limits range"); + } + + uint256 amountOut = relayer.quoteSell(sellToken, buyToken, amount); if (amountOut == 0) { revert Unavailable("AmountOut is zero!"); } @@ -212,8 +169,13 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256 amountBought ) internal returns (uint256) { address swapper = msg.sender; - uint256 amountIn = relayer.quoteBuy(sellToken, buyToken, amountBought); + (, uint256 limitMinBuyToken, , uint256 limitMaxBuyToken) = _getLimits(_poolId, _sellToken, _buyToken); + if(amountBought < limitMinBuyToken || amountBought > limitMaxBuyToken) { + revert Unavailable("specifiedAmount is out of limits range"); + } + + uint256 amountIn = relayer.quoteBuy(sellToken, buyToken, amountBought); if (amountIn == 0) { revert Unavailable("AmountIn is zero!"); } @@ -233,6 +195,26 @@ contract IntegralSwapAdapter is ISwapAdapter { return amountIn; } + + /// @notice Internal counterpart of _getLimits + /// @dev As Integral also has minimum limits of sell/buy amounts, we return them too. + /// @return limits[4]: [0] = limitMax of sellToken, [1] = limitMax of buyToken, [2] = limitMin of sellToken, [3] = limitMin of buyToken + function _getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) internal view returns (uint256[] memory limits) { + ( + uint256 price_, + uint256 fee, + uint256 limitMin0, + uint256 limitMax0, + uint256 limitMin1, + uint256 limitMax1 + ) = relayer.getPoolState(address(sellToken), address(buyToken)); + + limits = new uint256[](4); + limits[0] = limitMax0; + limits[1] = limitMax1; + limits[2] = limitMin0; + limits[3] = limitMin1; + } } interface ITwapRelayer {