From b8794fd3a41ea79fe4331136c4759e56c06e917e Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 13 Dec 2023 18:11:58 +0100 Subject: [PATCH 01/43] feat: added documentation, manifests and initial libraries for Integral support --- evm/src/integral/IntegralSwapAdapter.sol | 237 +++++++++++++++++++++++ evm/src/integral/manifest.yaml | 36 ++++ 2 files changed, 273 insertions(+) create mode 100644 evm/src/integral/IntegralSwapAdapter.sol create mode 100644 evm/src/integral/manifest.yaml diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol new file mode 100644 index 0000000..66ecba0 --- /dev/null +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; + +// Integral handles arbirary amounts, but we limit the amount to 10x just in case +uint256 constant RESERVE_LIMIT_FACTOR = 10; + +/// @title Integral Swap Adapter +contract IntegralSwapAdapter is ISwapAdapter { + ITwapFactory immutable factory; + + constructor(address factory_) { + factory = ITwapFactory(factory_); + } + + function price( + bytes32 _poolId, + IERC20 _sellToken, + IERC20 _buyToken, + uint256[] memory _specifiedAmounts + ) external view override returns (Fraction[] memory _prices) { + revert NotImplemented("IntegralSwapAdapter.price"); + } + + function swap( + bytes32 poolId, + IERC20 sellToken, + IERC20 buyToken, + OrderSide side, + uint256 specifiedAmount + ) external returns (Trade memory trade) { + revert NotImplemented("IntegralSwapAdapter.swap"); + } + + function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + returns (uint256[] memory limits) + { + revert NotImplemented("IntegralSwapAdapter.getLimits"); + } + + function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + returns (Capability[] memory capabilities) + { + revert NotImplemented("IntegralSwapAdapter.getCapabilities"); + } + + function getTokens(bytes32 poolId) + external + returns (IERC20[] memory tokens) + { + revert NotImplemented("IntegralSwapAdapter.getTokens"); + } + + /// @inheritdoc ISwapAdapter + function getPoolIds(uint256 offset, uint256 limit) + external + view + override + returns (bytes32[] memory ids) + { + revert NotImplemented("IntegralSwapAdapter.getPoolIds"); + } +} + +interface ITwapFactory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint256); + event OwnerSet(address owner); + + function owner() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + + function allPairs(uint256) external view returns (address pair); + + function allPairsLength() external view returns (uint256); + + function createPair( + address tokenA, + address tokenB, + address oracle, + address trader + ) external returns (address pair); + + function setOwner(address) external; + + function setMintFee( + address tokenA, + address tokenB, + uint256 fee + ) external; + + function setBurnFee( + address tokenA, + address tokenB, + uint256 fee + ) external; + + function setSwapFee( + address tokenA, + address tokenB, + uint256 fee + ) external; + + function setOracle( + address tokenA, + address tokenB, + address oracle + ) external; + + function setTrader( + address tokenA, + address tokenB, + address trader + ) external; + + function collect( + address tokenA, + address tokenB, + address to + ) external; + + function withdraw( + address tokenA, + address tokenB, + uint256 amount, + address to + ) external; +} + +interface ITwapPair is ITwapERC20, IReserves { + event Mint(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 liquidityOut, address indexed to); + event Burn(address indexed sender, uint256 amount0Out, uint256 amount1Out, uint256 liquidityIn, address indexed to); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 amount1Out, + address indexed to + ); + event SetMintFee(uint256 fee); + event SetBurnFee(uint256 fee); + event SetSwapFee(uint256 fee); + event SetOracle(address account); + event SetTrader(address trader); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function oracle() external view returns (address); + + function trader() external view returns (address); + + function mintFee() external view returns (uint256); + + function setMintFee(uint256 fee) external; + + function mint(address to) external returns (uint256 liquidity); + + function burnFee() external view returns (uint256); + + function setBurnFee(uint256 fee) external; + + function burn(address to) external returns (uint256 amount0, uint256 amount1); + + function swapFee() external view returns (uint256); + + function setSwapFee(uint256 fee) external; + + function setOracle(address account) external; + + function setTrader(address account) external; + + function collect(address to) external; + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function sync() external; + + function initialize( + address _token0, + address _token1, + address _oracle, + address _trader + ) external; + + function getSwapAmount0In(uint256 amount1Out, bytes calldata data) external view returns (uint256 swapAmount0In); + + function getSwapAmount1In(uint256 amount0Out, bytes calldata data) external view returns (uint256 swapAmount1In); + + function getSwapAmount0Out(uint256 amount1In, bytes calldata data) external view returns (uint256 swapAmount0Out); + + function getSwapAmount1Out(uint256 amount0In, bytes calldata data) external view returns (uint256 swapAmount1Out); + + function getDepositAmount0In(uint256 amount0, bytes calldata data) external view returns (uint256 depositAmount0In); + + function getDepositAmount1In(uint256 amount1, bytes calldata data) external view returns (uint256 depositAmount1In); +} + +interface ITwapERC20 is IERC20 { + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} + +interface IReserves { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1); + + function getFees() external view returns (uint256 fee0, uint256 fee1); +} diff --git a/evm/src/integral/manifest.yaml b/evm/src/integral/manifest.yaml new file mode 100644 index 0000000..5d3c68e --- /dev/null +++ b/evm/src/integral/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: IntegralSwapAdapter.sol + +# Deployment instances used to generate chain specific bytecode. +instances: + - chain: + name: mainnet + id: 0 + arguments: + - "0xC480b33eE5229DE3FbDFAD1D2DCD3F3BAD0C56c6" + +# 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 d9a1102c323b20252b604067454c4942c1995cf2 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 13 Dec 2023 18:13:30 +0100 Subject: [PATCH 02/43] feat: Implemented getPoolIds --- evm/src/integral/IntegralSwapAdapter.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 66ecba0..727b129 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -61,7 +61,14 @@ contract IntegralSwapAdapter is ISwapAdapter { override returns (bytes32[] memory ids) { - revert NotImplemented("IntegralSwapAdapter.getPoolIds"); + uint256 endIdx = offset + limit; + if (endIdx > factory.allPairsLength()) { + endIdx = factory.allPairsLength(); + } + ids = new bytes32[](endIdx - offset); + for (uint256 i = 0; i < ids.length; i++) { + ids[i] = bytes20(factory.allPairs(offset + i)); + } } } From 54c8253371b880e557e59d0b40cd2c8a9699e9a8 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 14 Dec 2023 10:29:04 +0100 Subject: [PATCH 03/43] feat: Implemented getTokens --- evm/src/integral/IntegralSwapAdapter.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 727b129..0d47243 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -47,11 +47,15 @@ contract IntegralSwapAdapter is ISwapAdapter { revert NotImplemented("IntegralSwapAdapter.getCapabilities"); } + /// @inheritdoc ISwapAdapter function getTokens(bytes32 poolId) external returns (IERC20[] memory tokens) { - revert NotImplemented("IntegralSwapAdapter.getTokens"); + tokens = new IERC20[](2); + IUniswapV2Pair pair = ITwapPair(address(bytes20(poolId))); + tokens[0] = IERC20(pair.token0()); + tokens[1] = IERC20(pair.token1()); } /// @inheritdoc ISwapAdapter From 2decec2bd3aec6c93c30f3d02b49573a545f787e Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 14 Dec 2023 10:30:03 +0100 Subject: [PATCH 04/43] feat: Implemented getCapabilities --- evm/src/integral/IntegralSwapAdapter.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 0d47243..b3c7212 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -40,11 +40,15 @@ contract IntegralSwapAdapter is ISwapAdapter { revert NotImplemented("IntegralSwapAdapter.getLimits"); } + /// @inheritdoc ISwapAdapter function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) external returns (Capability[] memory capabilities) { - revert NotImplemented("IntegralSwapAdapter.getCapabilities"); + capabilities = new Capability[](3); + capabilities[0] = Capability.SellOrder; + capabilities[1] = Capability.BuyOrder; + capabilities[2] = Capability.PriceFunction; } /// @inheritdoc ISwapAdapter From 06e86bdb9bcf2ce14841baf323eb18dfad2fbc76 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 14 Dec 2023 12:58:27 +0100 Subject: [PATCH 05/43] feat: created initial IntegralSwapAdapter test file --- evm/test/IntegralSwapAdapter.t.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 evm/test/IntegralSwapAdapter.t.sol diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol new file mode 100644 index 0000000..cb211e8 --- /dev/null +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "src/interfaces/ISwapAdapterTypes.sol"; +import "src/libraries/FractionMath.sol"; + +/// @title TemplateSwapAdapterTest +/// @dev This is a template for a swap adapter test. +/// Test all functions that are implemented in your swap adapter, the two test included here are just an example. +/// Feel free to use UniswapV2SwapAdapterTest and BalancerV2SwapAdapterTest as a reference. +contract TemplateSwapAdapterTest is Test, ISwapAdapterTypes { + using FractionMath for Fraction; + + function testPriceFuzz(uint256 amount0, uint256 amount1) public {} + + function testSwapFuzz(uint256 specifiedAmount) public {} +} From d993e110d117e92764f943da7d2d818a2730f78c Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 14 Dec 2023 16:43:16 +0100 Subject: [PATCH 06/43] fix: typos, inheritance errors and missing overrides --- evm/src/integral/IntegralSwapAdapter.sol | 58 +++++++++++++----------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index b3c7212..9c976e4 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -43,6 +43,8 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @inheritdoc ISwapAdapter function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) external + pure + override returns (Capability[] memory capabilities) { capabilities = new Capability[](3); @@ -54,10 +56,12 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @inheritdoc ISwapAdapter function getTokens(bytes32 poolId) external + view + override returns (IERC20[] memory tokens) { tokens = new IERC20[](2); - IUniswapV2Pair pair = ITwapPair(address(bytes20(poolId))); + ITwapPair pair = ITwapPair(address(bytes20(poolId))); tokens[0] = IERC20(pair.token0()); tokens[1] = IERC20(pair.token1()); } @@ -145,6 +149,32 @@ interface ITwapFactory { ) external; } +interface ITwapERC20 is IERC20 { + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} + +interface IReserves { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1); + + function getFees() external view returns (uint256 fee0, uint256 fee1); +} + interface ITwapPair is ITwapERC20, IReserves { event Mint(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 liquidityOut, address indexed to); event Burn(address indexed sender, uint256 amount0Out, uint256 amount1Out, uint256 liquidityIn, address indexed to); @@ -224,29 +254,3 @@ interface ITwapPair is ITwapERC20, IReserves { function getDepositAmount1In(uint256 amount1, bytes calldata data) external view returns (uint256 depositAmount1In); } - -interface ITwapERC20 is IERC20 { - function PERMIT_TYPEHASH() external pure returns (bytes32); - - function nonces(address owner) external view returns (uint256); - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function increaseAllowance(address spender, uint256 addedValue) external returns (bool); - - function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); -} - -interface IReserves { - function getReserves() external view returns (uint112 reserve0, uint112 reserve1); - - function getFees() external view returns (uint256 fee0, uint256 fee1); -} From 4e519cf21333e068dcbc36fedd91df6cd64ed4fc Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 14 Dec 2023 16:56:45 +0100 Subject: [PATCH 07/43] feat: Added Relayer in constructor and as main target contract --- evm/src/integral/IntegralSwapAdapter.sol | 206 ++++++++++++++++++++++- 1 file changed, 199 insertions(+), 7 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 9c976e4..15461ec 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -3,15 +3,12 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; -// Integral handles arbirary amounts, but we limit the amount to 10x just in case -uint256 constant RESERVE_LIMIT_FACTOR = 10; - /// @title Integral Swap Adapter contract IntegralSwapAdapter is ISwapAdapter { - ITwapFactory immutable factory; + ITwapRelayer immutable relayer; - constructor(address factory_) { - factory = ITwapFactory(factory_); + constructor(address relayer_) { + relayer = ITwapRelayer(relayer_); } function price( @@ -33,11 +30,23 @@ contract IntegralSwapAdapter is ISwapAdapter { revert NotImplemented("IntegralSwapAdapter.swap"); } + /// @inheritdoc ISwapAdapter function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) external returns (uint256[] memory limits) { - revert NotImplemented("IntegralSwapAdapter.getLimits"); + ( + uint256 price_, + uint256 fee, + uint256 limitMin0, + uint256 limitMax0, + uint256 limitMin1, + uint256 limitMax1 + ) = relayer.getPoolState(address(sellToken), address(buyToken)); + + limits = new uint256[](2); + limits[0] = limitMax0; + limits[1] = limitMax1; } /// @inheritdoc ISwapAdapter @@ -73,6 +82,7 @@ contract IntegralSwapAdapter is ISwapAdapter { override returns (bytes32[] memory ids) { + ITwapFactory factory = ITwapFactory(relayer.factory()); uint256 endIdx = offset + limit; if (endIdx > factory.allPairsLength()) { endIdx = factory.allPairsLength(); @@ -84,6 +94,188 @@ contract IntegralSwapAdapter is ISwapAdapter { } } +interface ITwapRelayer { + event OwnerSet(address owner); + event RebalancerSet(address rebalancer); + event DelaySet(address delay); + event PairEnabledSet(address pair, bool enabled); + event SwapFeeSet(address pair, uint256 fee); + event TwapIntervalSet(address pair, uint32 interval); + event EthTransferGasCostSet(uint256 gasCost); + event ExecutionGasLimitSet(uint256 limit); + event TokenLimitMinSet(address token, uint256 limit); + event TokenLimitMaxMultiplierSet(address token, uint256 limit); + event ToleranceSet(address pair, uint16 tolerance); + event Approve(address token, address to, uint256 amount); + event Withdraw(address token, address to, uint256 amount); + event Sell( + address indexed sender, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountOut, + uint256 amountOutMin, + bool wrapUnwrap, + uint256 fee, + address indexed to, + address orderContract, + uint256 indexed orderId + ); + event Buy( + address indexed sender, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountInMax, + uint256 amountOut, + bool wrapUnwrap, + uint256 fee, + address indexed to, + address orderContract, + uint256 indexed orderId + ); + event RebalanceSellWithDelay( + address indexed sender, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 indexed delayOrderId + ); + event RebalanceSellWithOneInch(address indexed oneInchRouter, uint256 gas, bytes data); + event OneInchRouterWhitelisted(address indexed oneInchRouter, bool whitelisted); + + function factory() external pure returns (address); + + function delay() external pure returns (address); + + function weth() external pure returns (address); + + function owner() external view returns (address); + + function rebalancer() external view returns (address); + + function isOneInchRouterWhitelisted(address oneInchRouter) external view returns (bool); + + function setOwner(address _owner) external; + + function swapFee(address pair) external view returns (uint256); + + function setSwapFee(address pair, uint256 fee) external; + + function twapInterval(address pair) external pure returns (uint32); + + function isPairEnabled(address pair) external view returns (bool); + + function setPairEnabled(address pair, bool enabled) external; + + function ethTransferGasCost() external pure returns (uint256); + + function executionGasLimit() external pure returns (uint256); + + function tokenLimitMin(address token) external pure returns (uint256); + + function tokenLimitMaxMultiplier(address token) external pure returns (uint256); + + function tolerance(address pair) external pure returns (uint16); + + function setRebalancer(address _rebalancer) external; + + function whitelistOneInchRouter(address oneInchRouter, bool whitelisted) external; + + function getTolerance(address pair) external pure returns (uint16); + + function getTokenLimitMin(address token) external pure returns (uint256); + + function getTokenLimitMaxMultiplier(address token) external pure returns (uint256); + + function getTwapInterval(address pair) external pure returns (uint32); + + struct SellParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint256 amountOutMin; + bool wrapUnwrap; + address to; + uint32 submitDeadline; + } + + function sell(SellParams memory sellParams) external payable returns (uint256 orderId); + + struct BuyParams { + address tokenIn; + address tokenOut; + uint256 amountInMax; + uint256 amountOut; + bool wrapUnwrap; + address to; + uint32 submitDeadline; + } + + function buy(BuyParams memory buyParams) external payable returns (uint256 orderId); + + function getPriceByPairAddress(address pair, bool inverted) + external + view + returns ( + uint8 xDecimals, + uint8 yDecimals, + uint256 price + ); + + function getPriceByTokenAddresses(address tokenIn, address tokenOut) external view returns (uint256 price); + + function getPoolState(address token0, address token1) + external + view + returns ( + uint256 price, + uint256 fee, + uint256 limitMin0, + uint256 limitMax0, + uint256 limitMin1, + uint256 limitMax1 + ); + + function quoteSell( + address tokenIn, + address tokenOut, + uint256 amountIn + ) external view returns (uint256 amountOut); + + function quoteBuy( + address tokenIn, + address tokenOut, + uint256 amountOut + ) external view returns (uint256 amountIn); + + function approve( + address token, + uint256 amount, + address to + ) external; + + function withdraw( + address token, + uint256 amount, + address to + ) external; + + function rebalanceSellWithDelay( + address tokenIn, + address tokenOut, + uint256 amountIn + ) external; + + function rebalanceSellWithOneInch( + address tokenIn, + uint256 amountIn, + address oneInchRouter, + uint256 _gas, + bytes calldata data + ) external; +} + interface ITwapFactory { event PairCreated(address indexed token0, address indexed token1, address pair, uint256); event OwnerSet(address owner); From ed8166fc956df6eca29e4e7bbd711c7f44db3d8a Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 14 Dec 2023 16:59:40 +0100 Subject: [PATCH 08/43] feat: Implemented getLimits --- evm/src/integral/IntegralSwapAdapter.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 15461ec..7364e67 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -33,6 +33,8 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @inheritdoc ISwapAdapter function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) external + view + override returns (uint256[] memory limits) { ( From b08963ece059299f8ee2866c400faedecbf45b54 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 14 Dec 2023 17:23:40 +0100 Subject: [PATCH 09/43] fix: changed integral manifest address to Integral Relayer address --- evm/src/integral/manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/integral/manifest.yaml b/evm/src/integral/manifest.yaml index 5d3c68e..ac31df9 100644 --- a/evm/src/integral/manifest.yaml +++ b/evm/src/integral/manifest.yaml @@ -21,7 +21,7 @@ instances: name: mainnet id: 0 arguments: - - "0xC480b33eE5229DE3FbDFAD1D2DCD3F3BAD0C56c6" + - "0xd17b3c9784510E33cD5B87b490E79253BcD81e2E" # Specify some automatic test cases in case getPoolIds and # getTokens are not implemented. From 878f788a505659520bf158a5392ec9391e760f8f Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 15 Dec 2023 10:02:13 +0100 Subject: [PATCH 10/43] feat: Implemented price --- evm/src/integral/IntegralSwapAdapter.sol | 47 +++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 7364e67..3b83238 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -11,13 +11,26 @@ contract IntegralSwapAdapter is ISwapAdapter { relayer = ITwapRelayer(relayer_); } + /// @inheritdoc ISwapAdapter function price( bytes32 _poolId, IERC20 _sellToken, IERC20 _buyToken, uint256[] memory _specifiedAmounts ) external view override returns (Fraction[] memory _prices) { - revert NotImplemented("IntegralSwapAdapter.price"); + _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(); + } + + for (uint256 i = 0; i < _specifiedAmounts.length; i++) { + _prices[i] = getPriceAt(_specifiedAmounts[i], r0, r1, pair); + } } function swap( @@ -94,6 +107,38 @@ contract IntegralSwapAdapter is ISwapAdapter { ids[i] = bytes20(factory.allPairs(offset + i)); } } + + /// @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 ) + ); + } } interface ITwapRelayer { From 5686e194027488c38313d412287c6d6b817ce7a9 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 15 Dec 2023 20:32:16 +0100 Subject: [PATCH 11/43] feat: Implemented swap with buy and sell functions --- evm/src/integral/IntegralSwapAdapter.sol | 84 +++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 3b83238..db5e906 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -3,6 +3,9 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; +/// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap +uint32 constant SWAP_DEADLINE_SEC = 1000; + /// @title Integral Swap Adapter contract IntegralSwapAdapter is ISwapAdapter { ITwapRelayer immutable relayer; @@ -40,7 +43,28 @@ contract IntegralSwapAdapter is ISwapAdapter { OrderSide side, uint256 specifiedAmount ) external returns (Trade memory trade) { - revert NotImplemented("IntegralSwapAdapter.swap"); + if (specifiedAmount == 0) { + 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 = + sell(address(sellToken), address(buyToken), specifiedAmount); + } else { // buy + trade.calculatedAmount = + buy(address(sellToken), address(buyToken), specifiedAmount); + } + trade.gasUsed = gasBefore - gasleft(); + trade.price = getPriceAt(specifiedAmount, r0, r1, pair); } /// @inheritdoc ISwapAdapter @@ -139,6 +163,64 @@ contract IntegralSwapAdapter is ISwapAdapter { 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. + /// @param amount The amount to be traded. + /// @return uint256 The amount of tokens received. + function sell( + address sellToken, + address buyToken, + uint256 amount + ) internal returns (uint256) { + address swapper = msg.sender; + uint256 amountOut = relayer.quoteSell(sellToken, buyToken, amount); + + if (amountOut == 0) { + revert Unavailable("AmountOut is zero!"); + } + relayer.sell(ITwapRelayer.SellParams({ + tokenIn: sellToken, + tokenOut: buyToken, + wrapUnwrap: false, + to: swapper, + submitDeadline: SWAP_DEADLINE_SEC, + amountIn: amount, + amountOutMin: amountOut + })); + + return amountOut; + } + + /// @notice Executes a buy order on a given pool. + /// @param sellToken The address of the token being sold. + /// @param buyToken The address of the token being bought. + /// @param amountBought The amount of buyToken tokens to buy. + /// @return uint256 The amount of tokens received. + function buy( + address sellToken, + address buyToken, + uint256 amountBought + ) internal returns (uint256) { + address swapper = msg.sender; + uint256 amountIn = relayer.quoteBuy(sellToken, buyToken, amountBought); + + if (amountIn == 0) { + revert Unavailable("AmountIn is zero!"); + } + relayer.buy(ITwapRelayer.BuyParams({ + tokenIn: sellToken, + tokenOut: buyToken, + wrapUnwrap: false, + to: swapper, + submitDeadline: SWAP_DEADLINE_SEC, + amountInMax: amountIn, + amountOut: amountBought + })); + + return amountIn; + } } interface ITwapRelayer { From cd8e9c61b37e06f056e961e8a268e28316f4cebf Mon Sep 17 00:00:00 2001 From: domenicodev Date: Tue, 19 Dec 2023 11:43:00 +0100 Subject: [PATCH 12/43] fix: Improved SWAP_DEADLINE_SEC comment text --- evm/src/integral/IntegralSwapAdapter.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index db5e906..25d498f 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; -/// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap +/// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but it is not strictly necessary to be this long +/// as the contract allows less durations, we use 1000 seconds (15 minutes) as deadline uint32 constant SWAP_DEADLINE_SEC = 1000; /// @title Integral Swap Adapter From 2565b3eba521f6d2473040d8b42379665681b329 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Tue, 19 Dec 2023 16:35:41 +0100 Subject: [PATCH 13/43] Added missing desc in swap function --- evm/src/integral/IntegralSwapAdapter.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 25d498f..fdae3ba 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -37,6 +37,7 @@ contract IntegralSwapAdapter is ISwapAdapter { } } + /// @inheritdoc ISwapAdapter function swap( bytes32 poolId, IERC20 sellToken, From 5e7d39fa7ae72bca165a5d86510a38443ef57cd6 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 21 Dec 2023 10:08:22 +0100 Subject: [PATCH 14/43] fix: fixed submit deadline and transfers on sell and buy functions --- evm/src/integral/IntegralSwapAdapter.sol | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index fdae3ba..c35d6f9 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; /// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but it is not strictly necessary to be this long -/// as the contract allows less durations, we use 1000 seconds (15 minutes) as deadline -uint32 constant SWAP_DEADLINE_SEC = 1000; +/// as the contract allows less durations, we use 1000 seconds (15 minutes) as a deadline +uint256 constant SWAP_DEADLINE_SEC = 1000; /// @title Integral Swap Adapter contract IntegralSwapAdapter is ISwapAdapter { @@ -85,9 +85,11 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256 limitMax1 ) = relayer.getPoolState(address(sellToken), address(buyToken)); - limits = new uint256[](2); + limits = new uint256[](4); limits[0] = limitMax0; limits[1] = limitMax1; + limits[2] = limitMin0; + limits[3] = limitMin1; } /// @inheritdoc ISwapAdapter @@ -182,12 +184,16 @@ contract IntegralSwapAdapter is ISwapAdapter { if (amountOut == 0) { revert Unavailable("AmountOut is zero!"); } + + IERC20(sellToken).transferFrom(msg.sender, address(this), amount); + IERC20(sellToken).approve(address(relayer), amount); + relayer.sell(ITwapRelayer.SellParams({ tokenIn: sellToken, tokenOut: buyToken, wrapUnwrap: false, to: swapper, - submitDeadline: SWAP_DEADLINE_SEC, + submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), amountIn: amount, amountOutMin: amountOut })); @@ -211,12 +217,16 @@ contract IntegralSwapAdapter is ISwapAdapter { if (amountIn == 0) { revert Unavailable("AmountIn is zero!"); } + + IERC20(sellToken).transferFrom(msg.sender, address(this), amountIn); + IERC20(sellToken).approve(address(relayer), amountIn); + relayer.buy(ITwapRelayer.BuyParams({ tokenIn: sellToken, tokenOut: buyToken, wrapUnwrap: false, to: swapper, - submitDeadline: SWAP_DEADLINE_SEC, + submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), amountInMax: amountIn, amountOut: amountBought })); From c470b2a20e377d532af8eeabd92054e152fef324 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 21 Dec 2023 17:43:07 +0100 Subject: [PATCH 15/43] 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 { From 87193425a8ea76dd6a772c3c7a44cc357bd63565 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:22:55 +0100 Subject: [PATCH 16/43] added IntegralSwapAdapterFix.sol and set up IntegralSwapAdapter.t.sol --- evm/src/integral/IntegralSwapAdapterFix.sol | 585 ++++++++++++++++++++ evm/test/IntegralSwapAdapter.t.sol | 28 +- 2 files changed, 606 insertions(+), 7 deletions(-) create mode 100644 evm/src/integral/IntegralSwapAdapterFix.sol diff --git a/evm/src/integral/IntegralSwapAdapterFix.sol b/evm/src/integral/IntegralSwapAdapterFix.sol new file mode 100644 index 0000000..c70c0e6 --- /dev/null +++ b/evm/src/integral/IntegralSwapAdapterFix.sol @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.13; + +import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; +import { console } from "forge-std/Test.sol"; + +/// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but it is not strictly necessary to be this long +/// as the contract allows less durations, we use 1000 seconds (15 minutes) as a deadline +uint256 constant SWAP_DEADLINE_SEC = 1000; + +/// @title Integral Swap Adapter +contract IntegralSwapAdapter is ISwapAdapter { + ITwapRelayer immutable relayer; + + constructor(address relayer_) { + relayer = ITwapRelayer(relayer_); + } + + /// @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, + IERC20 _buyToken, + uint256[] memory _specifiedAmounts + ) external view override returns (Fraction[] memory _prices) { + _prices = new Fraction[](_specifiedAmounts.length); + ITwapPair pair = ITwapPair(address(bytes20(_poolId))); + + 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] = Fraction(price_, 1); + } + } + + /// @inheritdoc ISwapAdapter + function swap( + bytes32 poolId, + IERC20 sellToken, + IERC20 buyToken, + OrderSide side, + uint256 specifiedAmount + ) external returns (Trade memory trade) { + if (specifiedAmount == 0) { + return trade; + } + + uint256 gasBefore = gasleft(); + if (side == OrderSide.Sell) { // sell + trade.calculatedAmount = + sell(sellToken, buyToken, specifiedAmount); + } else { // buy + trade.calculatedAmount = + buy(sellToken, buyToken, specifiedAmount); + } + trade.gasUsed = gasBefore - gasleft(); + trade.price = Fraction(relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)), 1); + } + + /// @inheritdoc ISwapAdapter + function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + view + override + returns (uint256[] memory limits) + { + return _getLimits(poolId, sellToken, buyToken); + } + + /// @inheritdoc ISwapAdapter + function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + external + pure + override + returns (Capability[] memory capabilities) + { + capabilities = new Capability[](3); + capabilities[0] = Capability.SellOrder; + capabilities[1] = Capability.BuyOrder; + capabilities[2] = Capability.PriceFunction; + } + + /// @inheritdoc ISwapAdapter + function getTokens(bytes32 poolId) + external + view + override + returns (IERC20[] memory tokens) + { + tokens = new IERC20[](2); + ITwapPair pair = ITwapPair(address(bytes20(poolId))); + tokens[0] = IERC20(pair.token0()); + tokens[1] = IERC20(pair.token1()); + } + + /// @inheritdoc ISwapAdapter + function getPoolIds(uint256 offset, uint256 limit) + external + view + override + returns (bytes32[] memory ids) + { + ITwapFactory factory = ITwapFactory(relayer.factory()); + uint256 endIdx = offset + limit; + if (endIdx > factory.allPairsLength()) { + endIdx = factory.allPairsLength(); + } + ids = new bytes32[](endIdx - offset); + for (uint256 i = 0; i < ids.length; i++) { + ids[i] = bytes20(factory.allPairs(offset + i)); + } + } + + /// @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. + /// @param amount The amount to be traded. + /// @return uint256 The amount of tokens received. + function sell( + IERC20 sellToken, + IERC20 buyToken, + uint256 amount + ) internal returns (uint256) { + address swapper = msg.sender; + + uint256[] memory limits = _getLimits(0, sellToken, buyToken); + + console.log("FIN QUI OK"); + + if(amount > limits[0] || amount < limits[2]) { + revert Unavailable("amount is out of limits range"); + } + + console.log("FIN QUI OK 1"); + + uint256 amountOut = relayer.quoteSell(address(sellToken), address(buyToken), amount); + console.log("FIN QUI OK 2"); + if (amountOut == 0) { + revert Unavailable("AmountOut is zero!"); + } + + console.log("FIN QUI OK 3"); + + sellToken.transferFrom(msg.sender, address(this), amount); + console.log("FIN QUI OK 4"); + sellToken.approve(address(relayer), amount); + console.log("FIN QUI OK 5"); + + relayer.sell(ITwapRelayer.SellParams({ + tokenIn: address(sellToken), + tokenOut: address(buyToken), + wrapUnwrap: false, + to: swapper, + submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), + amountIn: amount, + amountOutMin: amountOut + })); + console.log("FIN QUI OK 6"); + + return amountOut; + } + + /// @notice Executes a buy order on a given pool. + /// @param sellToken The address of the token being sold. + /// @param buyToken The address of the token being bought. + /// @param amountBought The amount of buyToken tokens to buy. + /// @return uint256 The amount of tokens received. + function buy( + IERC20 sellToken, + IERC20 buyToken, + uint256 amountBought + ) internal returns (uint256) { + address swapper = msg.sender; + + uint256[] memory limits = _getLimits(0, sellToken, buyToken); + if(amountBought > limits[1] || amountBought < limits[3]) { + revert Unavailable("amountBought is out of limits range"); + } + + uint256 amountIn = relayer.quoteBuy(address(sellToken), address(buyToken), amountBought); + if (amountIn == 0) { + revert Unavailable("AmountIn is zero!"); + } + + sellToken.transferFrom(msg.sender, address(this), amountIn); + sellToken.approve(address(relayer), amountIn); + + relayer.buy(ITwapRelayer.BuyParams({ + tokenIn: address(sellToken), + tokenOut: address(buyToken), + wrapUnwrap: false, + to: swapper, + submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), + amountInMax: amountIn, + amountOut: amountBought + })); + + return amountIn; + } + + /// @notice Internal counterpart of _getLimits + /// @dev As Integral also has minimum limits of sell/buy amounts, we return them too. + /// @return limits [length: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)); + + uint256[] memory limits_ = new uint256[](4); + limits_[0] = limitMax0; + limits_[1] = limitMax1; + limits_[2] = limitMin0; + limits_[3] = limitMin1; + + return limits_; + } +} + +interface ITwapRelayer { + event OwnerSet(address owner); + event RebalancerSet(address rebalancer); + event DelaySet(address delay); + event PairEnabledSet(address pair, bool enabled); + event SwapFeeSet(address pair, uint256 fee); + event TwapIntervalSet(address pair, uint32 interval); + event EthTransferGasCostSet(uint256 gasCost); + event ExecutionGasLimitSet(uint256 limit); + event TokenLimitMinSet(address token, uint256 limit); + event TokenLimitMaxMultiplierSet(address token, uint256 limit); + event ToleranceSet(address pair, uint16 tolerance); + event Approve(address token, address to, uint256 amount); + event Withdraw(address token, address to, uint256 amount); + event Sell( + address indexed sender, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountOut, + uint256 amountOutMin, + bool wrapUnwrap, + uint256 fee, + address indexed to, + address orderContract, + uint256 indexed orderId + ); + event Buy( + address indexed sender, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 amountInMax, + uint256 amountOut, + bool wrapUnwrap, + uint256 fee, + address indexed to, + address orderContract, + uint256 indexed orderId + ); + event RebalanceSellWithDelay( + address indexed sender, + address tokenIn, + address tokenOut, + uint256 amountIn, + uint256 indexed delayOrderId + ); + event RebalanceSellWithOneInch(address indexed oneInchRouter, uint256 gas, bytes data); + event OneInchRouterWhitelisted(address indexed oneInchRouter, bool whitelisted); + + function factory() external pure returns (address); + + function delay() external pure returns (address); + + function weth() external pure returns (address); + + function owner() external view returns (address); + + function rebalancer() external view returns (address); + + function isOneInchRouterWhitelisted(address oneInchRouter) external view returns (bool); + + function setOwner(address _owner) external; + + function swapFee(address pair) external view returns (uint256); + + function setSwapFee(address pair, uint256 fee) external; + + function twapInterval(address pair) external pure returns (uint32); + + function isPairEnabled(address pair) external view returns (bool); + + function setPairEnabled(address pair, bool enabled) external; + + function ethTransferGasCost() external pure returns (uint256); + + function executionGasLimit() external pure returns (uint256); + + function tokenLimitMin(address token) external pure returns (uint256); + + function tokenLimitMaxMultiplier(address token) external pure returns (uint256); + + function tolerance(address pair) external pure returns (uint16); + + function setRebalancer(address _rebalancer) external; + + function whitelistOneInchRouter(address oneInchRouter, bool whitelisted) external; + + function getTolerance(address pair) external pure returns (uint16); + + function getTokenLimitMin(address token) external pure returns (uint256); + + function getTokenLimitMaxMultiplier(address token) external pure returns (uint256); + + function getTwapInterval(address pair) external pure returns (uint32); + + struct SellParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint256 amountOutMin; + bool wrapUnwrap; + address to; + uint32 submitDeadline; + } + + function sell(SellParams memory sellParams) external payable returns (uint256 orderId); + + struct BuyParams { + address tokenIn; + address tokenOut; + uint256 amountInMax; + uint256 amountOut; + bool wrapUnwrap; + address to; + uint32 submitDeadline; + } + + function buy(BuyParams memory buyParams) external payable returns (uint256 orderId); + + function getPriceByPairAddress(address pair, bool inverted) + external + view + returns ( + uint8 xDecimals, + uint8 yDecimals, + uint256 price + ); + + function getPriceByTokenAddresses(address tokenIn, address tokenOut) external view returns (uint256 price); + + function getPoolState(address token0, address token1) + external + view + returns ( + uint256 price, + uint256 fee, + uint256 limitMin0, + uint256 limitMax0, + uint256 limitMin1, + uint256 limitMax1 + ); + + function quoteSell( + address tokenIn, + address tokenOut, + uint256 amountIn + ) external view returns (uint256 amountOut); + + function quoteBuy( + address tokenIn, + address tokenOut, + uint256 amountOut + ) external view returns (uint256 amountIn); + + function approve( + address token, + uint256 amount, + address to + ) external; + + function withdraw( + address token, + uint256 amount, + address to + ) external; + + function rebalanceSellWithDelay( + address tokenIn, + address tokenOut, + uint256 amountIn + ) external; + + function rebalanceSellWithOneInch( + address tokenIn, + uint256 amountIn, + address oneInchRouter, + uint256 _gas, + bytes calldata data + ) external; +} + +interface ITwapFactory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint256); + event OwnerSet(address owner); + + function owner() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + + function allPairs(uint256) external view returns (address pair); + + function allPairsLength() external view returns (uint256); + + function createPair( + address tokenA, + address tokenB, + address oracle, + address trader + ) external returns (address pair); + + function setOwner(address) external; + + function setMintFee( + address tokenA, + address tokenB, + uint256 fee + ) external; + + function setBurnFee( + address tokenA, + address tokenB, + uint256 fee + ) external; + + function setSwapFee( + address tokenA, + address tokenB, + uint256 fee + ) external; + + function setOracle( + address tokenA, + address tokenB, + address oracle + ) external; + + function setTrader( + address tokenA, + address tokenB, + address trader + ) external; + + function collect( + address tokenA, + address tokenB, + address to + ) external; + + function withdraw( + address tokenA, + address tokenB, + uint256 amount, + address to + ) external; +} + +interface ITwapERC20 is IERC20 { + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + function increaseAllowance(address spender, uint256 addedValue) external returns (bool); + + function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); +} + +interface IReserves { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1); + + function getFees() external view returns (uint256 fee0, uint256 fee1); +} + +interface ITwapPair is ITwapERC20, IReserves { + event Mint(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 liquidityOut, address indexed to); + event Burn(address indexed sender, uint256 amount0Out, uint256 amount1Out, uint256 liquidityIn, address indexed to); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 amount1Out, + address indexed to + ); + event SetMintFee(uint256 fee); + event SetBurnFee(uint256 fee); + event SetSwapFee(uint256 fee); + event SetOracle(address account); + event SetTrader(address trader); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function oracle() external view returns (address); + + function trader() external view returns (address); + + function mintFee() external view returns (uint256); + + function setMintFee(uint256 fee) external; + + function mint(address to) external returns (uint256 liquidity); + + function burnFee() external view returns (uint256); + + function setBurnFee(uint256 fee) external; + + function burn(address to) external returns (uint256 amount0, uint256 amount1); + + function swapFee() external view returns (uint256); + + function setSwapFee(uint256 fee) external; + + function setOracle(address account) external; + + function setTrader(address account) external; + + function collect(address to) external; + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function sync() external; + + function initialize( + address _token0, + address _token1, + address _oracle, + address _trader + ) external; + + function getSwapAmount0In(uint256 amount1Out, bytes calldata data) external view returns (uint256 swapAmount0In); + + function getSwapAmount1In(uint256 amount0Out, bytes calldata data) external view returns (uint256 swapAmount1In); + + function getSwapAmount0Out(uint256 amount1In, bytes calldata data) external view returns (uint256 swapAmount0Out); + + function getSwapAmount1Out(uint256 amount0In, bytes calldata data) external view returns (uint256 swapAmount1Out); + + function getDepositAmount0In(uint256 amount0, bytes calldata data) external view returns (uint256 depositAmount0In); + + function getDepositAmount1In(uint256 amount1, bytes calldata data) external view returns (uint256 depositAmount1In); +} \ No newline at end of file diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index cb211e8..d791b3d 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -2,17 +2,31 @@ 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/integral/IntegralSwapAdapterFix.sol"; -/// @title TemplateSwapAdapterTest -/// @dev This is a template for a swap adapter test. -/// Test all functions that are implemented in your swap adapter, the two test included here are just an example. -/// Feel free to use UniswapV2SwapAdapterTest and BalancerV2SwapAdapterTest as a reference. -contract TemplateSwapAdapterTest is Test, ISwapAdapterTypes { +contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { using FractionMath for Fraction; - function testPriceFuzz(uint256 amount0, uint256 amount1) public {} + IntegralSwapAdapter adapter; + IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + address constant USDC_WETH_PAIR = 0x2fe16Dd18bba26e457B7dD2080d5674312b026a2; + address constant relayerAddress = 0xd17b3c9784510E33cD5B87b490E79253BcD81e2E; + + uint256 constant TEST_ITERATIONS = 100; + + function setUp() public { + uint256 forkBlock = 18835309; + vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); + adapter = new IntegralSwapAdapter(relayerAddress); + + vm.label(address(WETH), "WETH"); + vm.label(address(USDC), "USDC"); + vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR"); + + } - function testSwapFuzz(uint256 specifiedAmount) public {} } From 6c3eeff32908b9ae83f27b820096408f5a55b5b7 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:29:02 +0100 Subject: [PATCH 17/43] testPriceFuzzIntegral --- evm/src/integral/IntegralSwapAdapter.sol | 2 ++ evm/test/IntegralSwapAdapter.t.sol | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index a79c75e..6f6d2f7 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -1,3 +1,4 @@ +/* // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.13; @@ -569,3 +570,4 @@ interface ITwapPair is ITwapERC20, IReserves { function getDepositAmount1In(uint256 amount1, bytes calldata data) external view returns (uint256 depositAmount1In); } +*/ \ No newline at end of file diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index d791b3d..e13cbc4 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -29,4 +29,22 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } + function testPriceFuzzIntegral(uint256 amount0, uint256 amount1) public { + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); + vm.assume(amount0 < limits[0]); + vm.assume(amount1 < limits[1]); + + uint256[] memory amounts = new uint256[](2); + amounts[0] = amount0; + amounts[1] = amount1; + + Fraction[] memory prices = adapter.price(pair, WETH, USDC, amounts); + + for (uint256 i = 0; i < prices.length; i++) { + assertGt(prices[i].numerator, 0); + assertGt(prices[i].denominator, 0); + } + } + } From d736631d521629d132af68a8cfe66b0805464370 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:32:10 +0100 Subject: [PATCH 18/43] testPriceDecreasing failing --- evm/test/IntegralSwapAdapter.t.sol | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index e13cbc4..a677ec7 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -29,6 +29,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } + function testPriceFuzzIntegral(uint256 amount0, uint256 amount1) public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); @@ -47,4 +48,22 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } + + function testPriceDecreasingIntegral() public { + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + uint256[] memory amounts = new uint256[](TEST_ITERATIONS); + + for (uint256 i = 0; i < TEST_ITERATIONS; i++) { + amounts[i] = 1000 * i * 10 ** 6; + } + + Fraction[] memory prices = adapter.price(pair, USDC, WETH, amounts); + + for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) { + assertEq(prices[i].compareFractions(prices[i + 1]), 1); + assertGt(prices[i].denominator, 0); + assertGt(prices[i + 1].denominator, 0); + } + } + } From 8b8f22f59469091b9f50a36d9c33a8bf995add03 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:34:50 +0100 Subject: [PATCH 19/43] testSwapBuyWethIntegral passed --- evm/test/IntegralSwapAdapter.t.sol | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index a677ec7..c112aed 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -66,4 +66,35 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } + function testSwapBuyWethIntegral(uint256 specifiedAmount) public { + OrderSide side = OrderSide.Buy; + + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + + uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); + + vm.assume(specifiedAmount < limits[1]); + vm.assume(specifiedAmount > limits[3]); + + deal(address(USDC), address(this), type(uint256).max); + USDC.approve(address(adapter), type(uint256).max); + + uint256 usdc_balance = USDC.balanceOf(address(this)); + uint256 weth_balance = WETH.balanceOf(address(this)); + + Trade memory trade = adapter.swap(pair, USDC, WETH, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + assertEq(specifiedAmount, + WETH.balanceOf(address(this)) + weth_balance + ); + + assertEq( + trade.calculatedAmount, + usdc_balance - USDC.balanceOf(address(this)) + ); + } + + } + } From 6a9cae7b0f7f9c33f9837f2fc443490fb7af3181 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:40:27 +0100 Subject: [PATCH 20/43] testSwapSellUsdcIntegral failed, check AmountIn must be AmountIn + Fee --- evm/test/IntegralSwapAdapter.t.sol | 41 +++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index c112aed..be7adfa 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -66,6 +66,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } + function testSwapBuyWethIntegral(uint256 specifiedAmount) public { OrderSide side = OrderSide.Buy; @@ -79,22 +80,54 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { deal(address(USDC), address(this), type(uint256).max); USDC.approve(address(adapter), type(uint256).max); - uint256 usdc_balance = USDC.balanceOf(address(this)); - uint256 weth_balance = WETH.balanceOf(address(this)); + uint256 usdc_balance_before = USDC.balanceOf(address(this)); + uint256 weth_balance_before = WETH.balanceOf(address(this)); Trade memory trade = adapter.swap(pair, USDC, WETH, side, specifiedAmount); if (trade.calculatedAmount > 0) { assertEq(specifiedAmount, - WETH.balanceOf(address(this)) + weth_balance + WETH.balanceOf(address(this)) + weth_balance_before ); assertEq( trade.calculatedAmount, - usdc_balance - USDC.balanceOf(address(this)) + usdc_balance_before - USDC.balanceOf(address(this)) ); } } + + function testSwapSellUsdcIntegral(uint256 specifiedAmount) public { + OrderSide side = OrderSide.Sell; + + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + + uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); + + vm.assume(specifiedAmount < limits[0]); + vm.assume(specifiedAmount > limits[2]); + + deal(address(USDC), address(this), type(uint256).max); + USDC.approve(address(adapter), type(uint256).max); + + uint256 usdc_balance_before = USDC.balanceOf(address(this)); + uint256 weth_balance_before = WETH.balanceOf(address(this)); + + Trade memory trade = adapter.swap(pair, USDC, WETH, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + assertEq(specifiedAmount, + usdc_balance_before - USDC.balanceOf(address(this)) + ); + + assertEq( + trade.calculatedAmount, + weth_balance_before + WETH.balanceOf(address(this)) + ); + } + + } + } From 3453b72a3ae0a47b3dfeefd70bb68dd73341686d Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:46:52 +0100 Subject: [PATCH 21/43] testSwapFuzzIntegral fails at times --- evm/test/IntegralSwapAdapter.t.sol | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index be7adfa..1b68c4d 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -130,4 +130,63 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } + + function testSwapFuzzIntegral(uint256 specifiedAmount, bool isBuy) public { + OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; + + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + uint256[] memory limits = new uint256[](4); + + if (side == OrderSide.Buy) { + limits = adapter.getLimits(pair, USDC, WETH); + vm.assume(specifiedAmount < limits[1]); + vm.assume(specifiedAmount > limits[3]); + + deal(address(USDC), address(this), type(uint256).max); + USDC.approve(address(adapter), type(uint256).max); + } + else { + limits = adapter.getLimits(pair, USDC, WETH); + vm.assume(specifiedAmount < limits[0]); + vm.assume(specifiedAmount > limits[2]); + + deal(address(USDC), address(this), type(uint256).max); + USDC.approve(address(adapter), specifiedAmount); + + } + + uint256 usdc_balance_before = USDC.balanceOf(address(this)); + uint256 weth_balance_before = WETH.balanceOf(address(this)); + + Trade memory trade = adapter.swap(pair, USDC, WETH, side, specifiedAmount); + + if (trade.calculatedAmount > 0) { + if (side == OrderSide.Buy) { + + assertEq( + specifiedAmount, + WETH.balanceOf(address(this)) + weth_balance_before + ); + + assertEq( + trade.calculatedAmount, + usdc_balance_before - USDC.balanceOf(address(this)) + ); + + } else { + + assertEq( + specifiedAmount, + usdc_balance_before - USDC.balanceOf(address(this)) + ); + + assertEq( + trade.calculatedAmount, + weth_balance_before + WETH.balanceOf(address(this)) + ); + } + } + + } + } From f139236ec39049e255cef0b7c743bd93f943ef2b Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:56:43 +0100 Subject: [PATCH 22/43] testSwappSellIncreasingIntegral and testSwapBuyIncreasingIntegral failed --- evm/test/IntegralSwapAdapter.t.sol | 91 ++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index 1b68c4d..da467fe 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -13,8 +13,10 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { IntegralSwapAdapter adapter; IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); - address constant USDC_WETH_PAIR = 0x2fe16Dd18bba26e457B7dD2080d5674312b026a2; - address constant relayerAddress = 0xd17b3c9784510E33cD5B87b490E79253BcD81e2E; + address constant USDC_WETH_PAIR = + 0x2fe16Dd18bba26e457B7dD2080d5674312b026a2; + address constant relayerAddress = + 0xd17b3c9784510E33cD5B87b490E79253BcD81e2E; uint256 constant TEST_ITERATIONS = 100; @@ -26,10 +28,8 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { vm.label(address(WETH), "WETH"); vm.label(address(USDC), "USDC"); vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR"); - } - function testPriceFuzzIntegral(uint256 amount0, uint256 amount1) public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); @@ -48,7 +48,6 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } - function testPriceDecreasingIntegral() public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory amounts = new uint256[](TEST_ITERATIONS); @@ -66,7 +65,6 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } - function testSwapBuyWethIntegral(uint256 specifiedAmount) public { OrderSide side = OrderSide.Buy; @@ -83,10 +81,17 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { uint256 usdc_balance_before = USDC.balanceOf(address(this)); uint256 weth_balance_before = WETH.balanceOf(address(this)); - Trade memory trade = adapter.swap(pair, USDC, WETH, side, specifiedAmount); + Trade memory trade = adapter.swap( + pair, + USDC, + WETH, + side, + specifiedAmount + ); if (trade.calculatedAmount > 0) { - assertEq(specifiedAmount, + assertEq( + specifiedAmount, WETH.balanceOf(address(this)) + weth_balance_before ); @@ -95,10 +100,8 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { usdc_balance_before - USDC.balanceOf(address(this)) ); } - } - function testSwapSellUsdcIntegral(uint256 specifiedAmount) public { OrderSide side = OrderSide.Sell; @@ -115,10 +118,17 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { uint256 usdc_balance_before = USDC.balanceOf(address(this)); uint256 weth_balance_before = WETH.balanceOf(address(this)); - Trade memory trade = adapter.swap(pair, USDC, WETH, side, specifiedAmount); + Trade memory trade = adapter.swap( + pair, + USDC, + WETH, + side, + specifiedAmount + ); if (trade.calculatedAmount > 0) { - assertEq(specifiedAmount, + assertEq( + specifiedAmount, usdc_balance_before - USDC.balanceOf(address(this)) ); @@ -127,10 +137,8 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { weth_balance_before + WETH.balanceOf(address(this)) ); } - } - function testSwapFuzzIntegral(uint256 specifiedAmount, bool isBuy) public { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; @@ -144,25 +152,28 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { deal(address(USDC), address(this), type(uint256).max); USDC.approve(address(adapter), type(uint256).max); - } - else { + } else { limits = adapter.getLimits(pair, USDC, WETH); vm.assume(specifiedAmount < limits[0]); vm.assume(specifiedAmount > limits[2]); deal(address(USDC), address(this), type(uint256).max); USDC.approve(address(adapter), specifiedAmount); - } uint256 usdc_balance_before = USDC.balanceOf(address(this)); uint256 weth_balance_before = WETH.balanceOf(address(this)); - Trade memory trade = adapter.swap(pair, USDC, WETH, side, specifiedAmount); - + Trade memory trade = adapter.swap( + pair, + USDC, + WETH, + side, + specifiedAmount + ); + if (trade.calculatedAmount > 0) { if (side == OrderSide.Buy) { - assertEq( specifiedAmount, WETH.balanceOf(address(this)) + weth_balance_before @@ -172,9 +183,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { trade.calculatedAmount, usdc_balance_before - USDC.balanceOf(address(this)) ); - } else { - assertEq( specifiedAmount, usdc_balance_before - USDC.balanceOf(address(this)) @@ -186,7 +195,43 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { ); } } - } + function executeIncreasingSwapsIntegral(OrderSide side) internal { + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + + uint256[] memory amounts = new uint256[](TEST_ITERATIONS); + for (uint256 i = 0; i < TEST_ITERATIONS; i++) { + amounts[i] = 1000 * i * 10 ** 6; + } + + Trade[] memory trades = new Trade[](TEST_ITERATIONS); + uint256 beforeSwap; + for (uint256 i = 0; 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), 1); + } + } + + function testSwapSellIncreasingIntegral() public { + executeIncreasingSwapsIntegral(OrderSide.Sell); + } + + function testSwapBuyIncreasingIntegral() public { + executeIncreasingSwapsIntegral(OrderSide.Buy); + } } From 3141ef5b842fe8394dcdd240a9a5aa1e0a3422f7 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 21 Dec 2023 18:59:22 +0100 Subject: [PATCH 23/43] testGetCapabilitiesIntegral and testGetLimitsIntegral passed --- evm/test/IntegralSwapAdapter.t.sol | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index da467fe..aeb1a42 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -234,4 +234,25 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { function testSwapBuyIncreasingIntegral() public { executeIncreasingSwapsIntegral(OrderSide.Buy); } + + function testGetCapabilitiesIntegral( + bytes32 pair, + address t0, + address t1 + ) public { + Capability[] memory res = adapter.getCapabilities( + pair, + IERC20(t0), + IERC20(t1) + ); + + assertEq(res.length, 3); + } + + function testGetLimitsIntegral() public { + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); + + assertEq(limits.length, 4); + } } From cc1a17c889458eb655884259b314026ba6fcd8f7 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 22 Dec 2023 10:24:03 +0100 Subject: [PATCH 24/43] fix: Fixed getLimits and address conversions --- evm/src/integral/IntegralSwapAdapter.sol | 68 +-- evm/src/integral/IntegralSwapAdapterFix.sol | 585 -------------------- 2 files changed, 34 insertions(+), 619 deletions(-) delete mode 100644 evm/src/integral/IntegralSwapAdapterFix.sol diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 6f6d2f7..241e29d 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -1,4 +1,3 @@ -/* // SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.8.13; @@ -36,10 +35,10 @@ contract IntegralSwapAdapter is ISwapAdapter { if (address(_sellToken) == pair.token1()) { inverted = true; } - uint256 price = relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)); + uint256 price_ = relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)); for (uint256 i = 0; i < _specifiedAmounts.length; i++) { - _prices[i] = price; + _prices[i] = Fraction(price_, 1); } } @@ -58,13 +57,13 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256 gasBefore = gasleft(); if (side == OrderSide.Sell) { // sell trade.calculatedAmount = - sell(address(sellToken), address(buyToken), specifiedAmount); + sell(sellToken, buyToken, specifiedAmount); } else { // buy trade.calculatedAmount = - buy(address(sellToken), address(buyToken), specifiedAmount); + buy(sellToken, buyToken, specifiedAmount); } trade.gasUsed = gasBefore - gasleft(); - trade.price = relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)); + trade.price = Fraction(relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)), 1); } /// @inheritdoc ISwapAdapter @@ -74,7 +73,7 @@ contract IntegralSwapAdapter is ISwapAdapter { override returns (uint256[] memory limits) { - return _getLimits(poollId, sellToken, buyToken); + return _getLimits(poolId, sellToken, buyToken); } /// @inheritdoc ISwapAdapter @@ -127,28 +126,28 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @param amount The amount to be traded. /// @return uint256 The amount of tokens received. function sell( - address sellToken, - address buyToken, + IERC20 sellToken, + IERC20 buyToken, uint256 amount ) internal returns (uint256) { address swapper = msg.sender; - (uint256 limitMinSellToken, , uint256 limitMaxSellToken) = _getLimits(_poolId, _sellToken, _buyToken); - if(amount < limitMinSellToken || amount > limitMaxSellToken) { - revert Unavailable("specifiedAmount is out of limits range"); + uint256[] memory limits = _getLimits(0, sellToken, buyToken); + if(amount > limits[0] || amount < limits[2]) { + revert Unavailable("amount is out of limits range"); } - uint256 amountOut = relayer.quoteSell(sellToken, buyToken, amount); + uint256 amountOut = relayer.quoteSell(address(sellToken), address(buyToken), amount); if (amountOut == 0) { revert Unavailable("AmountOut is zero!"); } - IERC20(sellToken).transferFrom(msg.sender, address(this), amount); - IERC20(sellToken).approve(address(relayer), amount); + sellToken.transferFrom(msg.sender, address(this), amount); + sellToken.approve(address(relayer), amount); relayer.sell(ITwapRelayer.SellParams({ - tokenIn: sellToken, - tokenOut: buyToken, + tokenIn: address(sellToken), + tokenOut: address(buyToken), wrapUnwrap: false, to: swapper, submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), @@ -165,28 +164,28 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @param amountBought The amount of buyToken tokens to buy. /// @return uint256 The amount of tokens received. function buy( - address sellToken, - address buyToken, + IERC20 sellToken, + IERC20 buyToken, uint256 amountBought ) internal returns (uint256) { address swapper = msg.sender; - (, uint256 limitMinBuyToken, , uint256 limitMaxBuyToken) = _getLimits(_poolId, _sellToken, _buyToken); - if(amountBought < limitMinBuyToken || amountBought > limitMaxBuyToken) { - revert Unavailable("specifiedAmount is out of limits range"); + uint256[] memory limits = _getLimits(0, sellToken, buyToken); + if(amountBought > limits[1] || amountBought < limits[3]) { + revert Unavailable("amountBought is out of limits range"); } - uint256 amountIn = relayer.quoteBuy(sellToken, buyToken, amountBought); + uint256 amountIn = relayer.quoteBuy(address(sellToken), address(buyToken), amountBought); if (amountIn == 0) { revert Unavailable("AmountIn is zero!"); } - IERC20(sellToken).transferFrom(msg.sender, address(this), amountIn); - IERC20(sellToken).approve(address(relayer), amountIn); + sellToken.transferFrom(msg.sender, address(this), amountIn); + sellToken.approve(address(relayer), amountIn); relayer.buy(ITwapRelayer.BuyParams({ - tokenIn: sellToken, - tokenOut: buyToken, + tokenIn: address(sellToken), + tokenOut: address(buyToken), wrapUnwrap: false, to: swapper, submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), @@ -199,7 +198,7 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @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 + /// @return limits [length: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_, @@ -210,11 +209,13 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256 limitMax1 ) = relayer.getPoolState(address(sellToken), address(buyToken)); - limits = new uint256[](4); - limits[0] = limitMax0; - limits[1] = limitMax1; - limits[2] = limitMin0; - limits[3] = limitMin1; + uint256[] memory limits_ = new uint256[](4); + limits_[0] = limitMax0; + limits_[1] = limitMax1; + limits_[2] = limitMin0; + limits_[3] = limitMin1; + + return limits_; } } @@ -570,4 +571,3 @@ interface ITwapPair is ITwapERC20, IReserves { function getDepositAmount1In(uint256 amount1, bytes calldata data) external view returns (uint256 depositAmount1In); } -*/ \ No newline at end of file diff --git a/evm/src/integral/IntegralSwapAdapterFix.sol b/evm/src/integral/IntegralSwapAdapterFix.sol deleted file mode 100644 index c70c0e6..0000000 --- a/evm/src/integral/IntegralSwapAdapterFix.sol +++ /dev/null @@ -1,585 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later -pragma solidity ^0.8.13; - -import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; -import { console } from "forge-std/Test.sol"; - -/// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but it is not strictly necessary to be this long -/// as the contract allows less durations, we use 1000 seconds (15 minutes) as a deadline -uint256 constant SWAP_DEADLINE_SEC = 1000; - -/// @title Integral Swap Adapter -contract IntegralSwapAdapter is ISwapAdapter { - ITwapRelayer immutable relayer; - - constructor(address relayer_) { - relayer = ITwapRelayer(relayer_); - } - - /// @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, - IERC20 _buyToken, - uint256[] memory _specifiedAmounts - ) external view override returns (Fraction[] memory _prices) { - _prices = new Fraction[](_specifiedAmounts.length); - ITwapPair pair = ITwapPair(address(bytes20(_poolId))); - - 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] = Fraction(price_, 1); - } - } - - /// @inheritdoc ISwapAdapter - function swap( - bytes32 poolId, - IERC20 sellToken, - IERC20 buyToken, - OrderSide side, - uint256 specifiedAmount - ) external returns (Trade memory trade) { - if (specifiedAmount == 0) { - return trade; - } - - uint256 gasBefore = gasleft(); - if (side == OrderSide.Sell) { // sell - trade.calculatedAmount = - sell(sellToken, buyToken, specifiedAmount); - } else { // buy - trade.calculatedAmount = - buy(sellToken, buyToken, specifiedAmount); - } - trade.gasUsed = gasBefore - gasleft(); - trade.price = Fraction(relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)), 1); - } - - /// @inheritdoc ISwapAdapter - function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) - external - view - override - returns (uint256[] memory limits) - { - return _getLimits(poolId, sellToken, buyToken); - } - - /// @inheritdoc ISwapAdapter - function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) - external - pure - override - returns (Capability[] memory capabilities) - { - capabilities = new Capability[](3); - capabilities[0] = Capability.SellOrder; - capabilities[1] = Capability.BuyOrder; - capabilities[2] = Capability.PriceFunction; - } - - /// @inheritdoc ISwapAdapter - function getTokens(bytes32 poolId) - external - view - override - returns (IERC20[] memory tokens) - { - tokens = new IERC20[](2); - ITwapPair pair = ITwapPair(address(bytes20(poolId))); - tokens[0] = IERC20(pair.token0()); - tokens[1] = IERC20(pair.token1()); - } - - /// @inheritdoc ISwapAdapter - function getPoolIds(uint256 offset, uint256 limit) - external - view - override - returns (bytes32[] memory ids) - { - ITwapFactory factory = ITwapFactory(relayer.factory()); - uint256 endIdx = offset + limit; - if (endIdx > factory.allPairsLength()) { - endIdx = factory.allPairsLength(); - } - ids = new bytes32[](endIdx - offset); - for (uint256 i = 0; i < ids.length; i++) { - ids[i] = bytes20(factory.allPairs(offset + i)); - } - } - - /// @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. - /// @param amount The amount to be traded. - /// @return uint256 The amount of tokens received. - function sell( - IERC20 sellToken, - IERC20 buyToken, - uint256 amount - ) internal returns (uint256) { - address swapper = msg.sender; - - uint256[] memory limits = _getLimits(0, sellToken, buyToken); - - console.log("FIN QUI OK"); - - if(amount > limits[0] || amount < limits[2]) { - revert Unavailable("amount is out of limits range"); - } - - console.log("FIN QUI OK 1"); - - uint256 amountOut = relayer.quoteSell(address(sellToken), address(buyToken), amount); - console.log("FIN QUI OK 2"); - if (amountOut == 0) { - revert Unavailable("AmountOut is zero!"); - } - - console.log("FIN QUI OK 3"); - - sellToken.transferFrom(msg.sender, address(this), amount); - console.log("FIN QUI OK 4"); - sellToken.approve(address(relayer), amount); - console.log("FIN QUI OK 5"); - - relayer.sell(ITwapRelayer.SellParams({ - tokenIn: address(sellToken), - tokenOut: address(buyToken), - wrapUnwrap: false, - to: swapper, - submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), - amountIn: amount, - amountOutMin: amountOut - })); - console.log("FIN QUI OK 6"); - - return amountOut; - } - - /// @notice Executes a buy order on a given pool. - /// @param sellToken The address of the token being sold. - /// @param buyToken The address of the token being bought. - /// @param amountBought The amount of buyToken tokens to buy. - /// @return uint256 The amount of tokens received. - function buy( - IERC20 sellToken, - IERC20 buyToken, - uint256 amountBought - ) internal returns (uint256) { - address swapper = msg.sender; - - uint256[] memory limits = _getLimits(0, sellToken, buyToken); - if(amountBought > limits[1] || amountBought < limits[3]) { - revert Unavailable("amountBought is out of limits range"); - } - - uint256 amountIn = relayer.quoteBuy(address(sellToken), address(buyToken), amountBought); - if (amountIn == 0) { - revert Unavailable("AmountIn is zero!"); - } - - sellToken.transferFrom(msg.sender, address(this), amountIn); - sellToken.approve(address(relayer), amountIn); - - relayer.buy(ITwapRelayer.BuyParams({ - tokenIn: address(sellToken), - tokenOut: address(buyToken), - wrapUnwrap: false, - to: swapper, - submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), - amountInMax: amountIn, - amountOut: amountBought - })); - - return amountIn; - } - - /// @notice Internal counterpart of _getLimits - /// @dev As Integral also has minimum limits of sell/buy amounts, we return them too. - /// @return limits [length: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)); - - uint256[] memory limits_ = new uint256[](4); - limits_[0] = limitMax0; - limits_[1] = limitMax1; - limits_[2] = limitMin0; - limits_[3] = limitMin1; - - return limits_; - } -} - -interface ITwapRelayer { - event OwnerSet(address owner); - event RebalancerSet(address rebalancer); - event DelaySet(address delay); - event PairEnabledSet(address pair, bool enabled); - event SwapFeeSet(address pair, uint256 fee); - event TwapIntervalSet(address pair, uint32 interval); - event EthTransferGasCostSet(uint256 gasCost); - event ExecutionGasLimitSet(uint256 limit); - event TokenLimitMinSet(address token, uint256 limit); - event TokenLimitMaxMultiplierSet(address token, uint256 limit); - event ToleranceSet(address pair, uint16 tolerance); - event Approve(address token, address to, uint256 amount); - event Withdraw(address token, address to, uint256 amount); - event Sell( - address indexed sender, - address tokenIn, - address tokenOut, - uint256 amountIn, - uint256 amountOut, - uint256 amountOutMin, - bool wrapUnwrap, - uint256 fee, - address indexed to, - address orderContract, - uint256 indexed orderId - ); - event Buy( - address indexed sender, - address tokenIn, - address tokenOut, - uint256 amountIn, - uint256 amountInMax, - uint256 amountOut, - bool wrapUnwrap, - uint256 fee, - address indexed to, - address orderContract, - uint256 indexed orderId - ); - event RebalanceSellWithDelay( - address indexed sender, - address tokenIn, - address tokenOut, - uint256 amountIn, - uint256 indexed delayOrderId - ); - event RebalanceSellWithOneInch(address indexed oneInchRouter, uint256 gas, bytes data); - event OneInchRouterWhitelisted(address indexed oneInchRouter, bool whitelisted); - - function factory() external pure returns (address); - - function delay() external pure returns (address); - - function weth() external pure returns (address); - - function owner() external view returns (address); - - function rebalancer() external view returns (address); - - function isOneInchRouterWhitelisted(address oneInchRouter) external view returns (bool); - - function setOwner(address _owner) external; - - function swapFee(address pair) external view returns (uint256); - - function setSwapFee(address pair, uint256 fee) external; - - function twapInterval(address pair) external pure returns (uint32); - - function isPairEnabled(address pair) external view returns (bool); - - function setPairEnabled(address pair, bool enabled) external; - - function ethTransferGasCost() external pure returns (uint256); - - function executionGasLimit() external pure returns (uint256); - - function tokenLimitMin(address token) external pure returns (uint256); - - function tokenLimitMaxMultiplier(address token) external pure returns (uint256); - - function tolerance(address pair) external pure returns (uint16); - - function setRebalancer(address _rebalancer) external; - - function whitelistOneInchRouter(address oneInchRouter, bool whitelisted) external; - - function getTolerance(address pair) external pure returns (uint16); - - function getTokenLimitMin(address token) external pure returns (uint256); - - function getTokenLimitMaxMultiplier(address token) external pure returns (uint256); - - function getTwapInterval(address pair) external pure returns (uint32); - - struct SellParams { - address tokenIn; - address tokenOut; - uint256 amountIn; - uint256 amountOutMin; - bool wrapUnwrap; - address to; - uint32 submitDeadline; - } - - function sell(SellParams memory sellParams) external payable returns (uint256 orderId); - - struct BuyParams { - address tokenIn; - address tokenOut; - uint256 amountInMax; - uint256 amountOut; - bool wrapUnwrap; - address to; - uint32 submitDeadline; - } - - function buy(BuyParams memory buyParams) external payable returns (uint256 orderId); - - function getPriceByPairAddress(address pair, bool inverted) - external - view - returns ( - uint8 xDecimals, - uint8 yDecimals, - uint256 price - ); - - function getPriceByTokenAddresses(address tokenIn, address tokenOut) external view returns (uint256 price); - - function getPoolState(address token0, address token1) - external - view - returns ( - uint256 price, - uint256 fee, - uint256 limitMin0, - uint256 limitMax0, - uint256 limitMin1, - uint256 limitMax1 - ); - - function quoteSell( - address tokenIn, - address tokenOut, - uint256 amountIn - ) external view returns (uint256 amountOut); - - function quoteBuy( - address tokenIn, - address tokenOut, - uint256 amountOut - ) external view returns (uint256 amountIn); - - function approve( - address token, - uint256 amount, - address to - ) external; - - function withdraw( - address token, - uint256 amount, - address to - ) external; - - function rebalanceSellWithDelay( - address tokenIn, - address tokenOut, - uint256 amountIn - ) external; - - function rebalanceSellWithOneInch( - address tokenIn, - uint256 amountIn, - address oneInchRouter, - uint256 _gas, - bytes calldata data - ) external; -} - -interface ITwapFactory { - event PairCreated(address indexed token0, address indexed token1, address pair, uint256); - event OwnerSet(address owner); - - function owner() external view returns (address); - - function getPair(address tokenA, address tokenB) external view returns (address pair); - - function allPairs(uint256) external view returns (address pair); - - function allPairsLength() external view returns (uint256); - - function createPair( - address tokenA, - address tokenB, - address oracle, - address trader - ) external returns (address pair); - - function setOwner(address) external; - - function setMintFee( - address tokenA, - address tokenB, - uint256 fee - ) external; - - function setBurnFee( - address tokenA, - address tokenB, - uint256 fee - ) external; - - function setSwapFee( - address tokenA, - address tokenB, - uint256 fee - ) external; - - function setOracle( - address tokenA, - address tokenB, - address oracle - ) external; - - function setTrader( - address tokenA, - address tokenB, - address trader - ) external; - - function collect( - address tokenA, - address tokenB, - address to - ) external; - - function withdraw( - address tokenA, - address tokenB, - uint256 amount, - address to - ) external; -} - -interface ITwapERC20 is IERC20 { - function PERMIT_TYPEHASH() external pure returns (bytes32); - - function nonces(address owner) external view returns (uint256); - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function increaseAllowance(address spender, uint256 addedValue) external returns (bool); - - function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool); -} - -interface IReserves { - function getReserves() external view returns (uint112 reserve0, uint112 reserve1); - - function getFees() external view returns (uint256 fee0, uint256 fee1); -} - -interface ITwapPair is ITwapERC20, IReserves { - event Mint(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 liquidityOut, address indexed to); - event Burn(address indexed sender, uint256 amount0Out, uint256 amount1Out, uint256 liquidityIn, address indexed to); - event Swap( - address indexed sender, - uint256 amount0In, - uint256 amount1In, - uint256 amount0Out, - uint256 amount1Out, - address indexed to - ); - event SetMintFee(uint256 fee); - event SetBurnFee(uint256 fee); - event SetSwapFee(uint256 fee); - event SetOracle(address account); - event SetTrader(address trader); - - function MINIMUM_LIQUIDITY() external pure returns (uint256); - - function factory() external view returns (address); - - function token0() external view returns (address); - - function token1() external view returns (address); - - function oracle() external view returns (address); - - function trader() external view returns (address); - - function mintFee() external view returns (uint256); - - function setMintFee(uint256 fee) external; - - function mint(address to) external returns (uint256 liquidity); - - function burnFee() external view returns (uint256); - - function setBurnFee(uint256 fee) external; - - function burn(address to) external returns (uint256 amount0, uint256 amount1); - - function swapFee() external view returns (uint256); - - function setSwapFee(uint256 fee) external; - - function setOracle(address account) external; - - function setTrader(address account) external; - - function collect(address to) external; - - function swap( - uint256 amount0Out, - uint256 amount1Out, - address to, - bytes calldata data - ) external; - - function sync() external; - - function initialize( - address _token0, - address _token1, - address _oracle, - address _trader - ) external; - - function getSwapAmount0In(uint256 amount1Out, bytes calldata data) external view returns (uint256 swapAmount0In); - - function getSwapAmount1In(uint256 amount0Out, bytes calldata data) external view returns (uint256 swapAmount1In); - - function getSwapAmount0Out(uint256 amount1In, bytes calldata data) external view returns (uint256 swapAmount0Out); - - function getSwapAmount1Out(uint256 amount0In, bytes calldata data) external view returns (uint256 swapAmount1Out); - - function getDepositAmount0In(uint256 amount0, bytes calldata data) external view returns (uint256 depositAmount0In); - - function getDepositAmount1In(uint256 amount1, bytes calldata data) external view returns (uint256 depositAmount1In); -} \ No newline at end of file From b46c662607eaee4abf40baaf25736647232dc10c Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 22 Dec 2023 11:17:12 +0100 Subject: [PATCH 25/43] fix: Changed error code as per ISwapAdapter.sol definition --- evm/src/integral/IntegralSwapAdapter.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 241e29d..9aaea0f 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -134,7 +134,7 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256[] memory limits = _getLimits(0, sellToken, buyToken); if(amount > limits[0] || amount < limits[2]) { - revert Unavailable("amount is out of limits range"); + revert LimitExceeded(amount); } uint256 amountOut = relayer.quoteSell(address(sellToken), address(buyToken), amount); @@ -172,7 +172,7 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256[] memory limits = _getLimits(0, sellToken, buyToken); if(amountBought > limits[1] || amountBought < limits[3]) { - revert Unavailable("amountBought is out of limits range"); + revert LimitExceeded(amountBought); } uint256 amountIn = relayer.quoteBuy(address(sellToken), address(buyToken), amountBought); From bb2853395167a219a3c572a13a0a3c582fbd9a09 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Wed, 27 Dec 2023 17:58:03 +0100 Subject: [PATCH 26/43] fix: Fixed swap tests and limits calculations on IntegralSwapAdapter contract; feat: Added getTokens test --- evm/src/integral/IntegralSwapAdapter.sol | 8 +- evm/test/IntegralSwapAdapter.t.sol | 116 +++-------------------- 2 files changed, 21 insertions(+), 103 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 9aaea0f..c913550 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -198,6 +198,10 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @notice Internal counterpart of _getLimits /// @dev As Integral also has minimum limits of sell/buy amounts, we return them too. + /// @dev Since TwapRelayer's calculateAmountIn and calculateAmountOut functions are internal, and using quoteBuy and quoteSell would + /// revert the transactions if internally calculated(in or out) amounts are not within the minimum limits, + /// we need a threshold to cover the losses on this internal amount, applied to the input amount. + /// limitMins of sellToken: +15% and buyToken: +3% are enough to cover these inconsistences. /// @return limits [length: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) { ( @@ -212,8 +216,8 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256[] memory limits_ = new uint256[](4); limits_[0] = limitMax0; limits_[1] = limitMax1; - limits_[2] = limitMin0; - limits_[3] = limitMin1; + limits_[2] = (limitMin0 * 115 / 100); + limits_[3] = (limitMin1 * 103 / 100); return limits_; } diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index aeb1a42..f153729 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import "src/interfaces/ISwapAdapterTypes.sol"; import "src/libraries/FractionMath.sol"; -import "src/integral/IntegralSwapAdapterFix.sol"; +import "src/integral/IntegralSwapAdapter.sol"; contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { using FractionMath for Fraction; @@ -48,98 +48,13 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } - function testPriceDecreasingIntegral() public { - bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); - uint256[] memory amounts = new uint256[](TEST_ITERATIONS); - - for (uint256 i = 0; i < TEST_ITERATIONS; i++) { - amounts[i] = 1000 * i * 10 ** 6; - } - - Fraction[] memory prices = adapter.price(pair, USDC, WETH, amounts); - - for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) { - assertEq(prices[i].compareFractions(prices[i + 1]), 1); - assertGt(prices[i].denominator, 0); - assertGt(prices[i + 1].denominator, 0); - } - } - - function testSwapBuyWethIntegral(uint256 specifiedAmount) public { - OrderSide side = OrderSide.Buy; - - bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); - - uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); - - vm.assume(specifiedAmount < limits[1]); - vm.assume(specifiedAmount > limits[3]); - - deal(address(USDC), address(this), type(uint256).max); - USDC.approve(address(adapter), type(uint256).max); - - uint256 usdc_balance_before = USDC.balanceOf(address(this)); - uint256 weth_balance_before = WETH.balanceOf(address(this)); - - Trade memory trade = adapter.swap( - pair, - USDC, - WETH, - side, - specifiedAmount - ); - - if (trade.calculatedAmount > 0) { - assertEq( - specifiedAmount, - WETH.balanceOf(address(this)) + weth_balance_before - ); - - assertEq( - trade.calculatedAmount, - usdc_balance_before - USDC.balanceOf(address(this)) - ); - } - } - - function testSwapSellUsdcIntegral(uint256 specifiedAmount) public { - OrderSide side = OrderSide.Sell; - - bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); - - uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); - - vm.assume(specifiedAmount < limits[0]); - vm.assume(specifiedAmount > limits[2]); - - deal(address(USDC), address(this), type(uint256).max); - USDC.approve(address(adapter), type(uint256).max); - - uint256 usdc_balance_before = USDC.balanceOf(address(this)); - uint256 weth_balance_before = WETH.balanceOf(address(this)); - - Trade memory trade = adapter.swap( - pair, - USDC, - WETH, - side, - specifiedAmount - ); - - if (trade.calculatedAmount > 0) { - assertEq( - specifiedAmount, - usdc_balance_before - USDC.balanceOf(address(this)) - ); - - assertEq( - trade.calculatedAmount, - weth_balance_before + WETH.balanceOf(address(this)) - ); - } - } - + /// @dev Since TwapRelayer's calculateAmountOut function is internal, and using quoteSell would + /// revert the transaction if calculateAmountOut is not enough, + /// we need a threshold to cover this internal amount, applied to function testSwapFuzzIntegral(uint256 specifiedAmount, bool isBuy) public { + // Fails at times | FAIL. Reason: revert: TR03; + // + // OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); @@ -227,14 +142,6 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } - function testSwapSellIncreasingIntegral() public { - executeIncreasingSwapsIntegral(OrderSide.Sell); - } - - function testSwapBuyIncreasingIntegral() public { - executeIncreasingSwapsIntegral(OrderSide.Buy); - } - function testGetCapabilitiesIntegral( bytes32 pair, address t0, @@ -249,10 +156,17 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { assertEq(res.length, 3); } + function testGetTokensIntegral() public { + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + IERC20[] memory tokens = adapter.getTokens(pair); + + assertEq(tokens.length, 2); + } + function testGetLimitsIntegral() public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); assertEq(limits.length, 4); } -} +} \ No newline at end of file From af196d6793c960dc999919f494eedd541e5eff2d Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 4 Jan 2024 17:36:57 +0100 Subject: [PATCH 27/43] fix: Removed unused reverts and constants --- evm/src/integral/IntegralSwapAdapter.sol | 34 ++++++------------------ 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index c913550..d5f7673 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -29,12 +29,7 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256[] memory _specifiedAmounts ) external view override returns (Fraction[] memory _prices) { _prices = new Fraction[](_specifiedAmounts.length); - ITwapPair pair = ITwapPair(address(bytes20(_poolId))); - 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++) { @@ -63,6 +58,11 @@ contract IntegralSwapAdapter is ISwapAdapter { buy(sellToken, buyToken, specifiedAmount); } trade.gasUsed = gasBefore - gasleft(); + /** + * @dev once we get reply from propeller about return values in price() function and in every Fraction + * Fraction[0] = relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)) * 10^(IERC20(sellToken).decimals) / 10^18 + * Fraction[1] = 10^(IERC20(buyToken).decimals) + */ trade.price = Fraction(relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)), 1); } @@ -132,11 +132,6 @@ contract IntegralSwapAdapter is ISwapAdapter { ) internal returns (uint256) { address swapper = msg.sender; - uint256[] memory limits = _getLimits(0, sellToken, buyToken); - if(amount > limits[0] || amount < limits[2]) { - revert LimitExceeded(amount); - } - uint256 amountOut = relayer.quoteSell(address(sellToken), address(buyToken), amount); if (amountOut == 0) { revert Unavailable("AmountOut is zero!"); @@ -170,11 +165,6 @@ contract IntegralSwapAdapter is ISwapAdapter { ) internal returns (uint256) { address swapper = msg.sender; - uint256[] memory limits = _getLimits(0, sellToken, buyToken); - if(amountBought > limits[1] || amountBought < limits[3]) { - revert LimitExceeded(amountBought); - } - uint256 amountIn = relayer.quoteBuy(address(sellToken), address(buyToken), amountBought); if (amountIn == 0) { revert Unavailable("AmountIn is zero!"); @@ -197,27 +187,19 @@ contract IntegralSwapAdapter is ISwapAdapter { } /// @notice Internal counterpart of _getLimits - /// @dev As Integral also has minimum limits of sell/buy amounts, we return them too. - /// @dev Since TwapRelayer's calculateAmountIn and calculateAmountOut functions are internal, and using quoteBuy and quoteSell would - /// revert the transactions if internally calculated(in or out) amounts are not within the minimum limits, - /// we need a threshold to cover the losses on this internal amount, applied to the input amount. - /// limitMins of sellToken: +15% and buyToken: +3% are enough to cover these inconsistences. - /// @return limits [length: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)); - uint256[] memory limits_ = new uint256[](4); + uint256[] memory limits_ = new uint256[](2); limits_[0] = limitMax0; limits_[1] = limitMax1; - limits_[2] = (limitMin0 * 115 / 100); - limits_[3] = (limitMin1 * 103 / 100); return limits_; } From c446a09caa9d1fc405a6302650b0135391e90a31 Mon Sep 17 00:00:00 2001 From: mp-web3 Date: Thu, 4 Jan 2024 18:36:32 +0100 Subject: [PATCH 28/43] Fixing testSwapFuzzIntegral --- evm/test/IntegralSwapAdapter.t.sol | 94 ++++++++++++++++++------------ 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index f153729..adb3893 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -1,53 +1,70 @@ // 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/integral/IntegralSwapAdapter.sol"; - + contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { using FractionMath for Fraction; - + IntegralSwapAdapter adapter; + ITwapRelayer relayer; IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); address constant USDC_WETH_PAIR = 0x2fe16Dd18bba26e457B7dD2080d5674312b026a2; address constant relayerAddress = 0xd17b3c9784510E33cD5B87b490E79253BcD81e2E; - + uint256 constant TEST_ITERATIONS = 100; - + function setUp() public { uint256 forkBlock = 18835309; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); adapter = new IntegralSwapAdapter(relayerAddress); - + relayer = ITwapRelayer(relayerAddress); + vm.label(address(WETH), "WETH"); vm.label(address(USDC), "USDC"); vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR"); } - + + function getMinLimits(IERC20 sellToken, IERC20 buyToken) public view returns (uint256[] memory limits) { + ( + uint256 price_, + uint256 fee, + uint256 limitMin0, + uint256 limitMax0, + uint256 limitMin1, + uint256 limitMax1 + ) = relayer.getPoolState(address(sellToken), address(buyToken)); + + uint256[] memory limits_ = new uint256[](2); + limits_[0] = limitMin0; + limits_[1] = limitMin1; + } + function testPriceFuzzIntegral(uint256 amount0, uint256 amount1) public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); vm.assume(amount0 < limits[0]); vm.assume(amount1 < limits[1]); - + uint256[] memory amounts = new uint256[](2); amounts[0] = amount0; amounts[1] = amount1; - + Fraction[] memory prices = adapter.price(pair, WETH, USDC, amounts); - + for (uint256 i = 0; i < prices.length; i++) { assertGt(prices[i].numerator, 0); assertGt(prices[i].denominator, 0); } } - + /// @dev Since TwapRelayer's calculateAmountOut function is internal, and using quoteSell would /// revert the transaction if calculateAmountOut is not enough, /// we need a threshold to cover this internal amount, applied to @@ -56,29 +73,34 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { // // OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; - + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); - uint256[] memory limits = new uint256[](4); - + uint256[] memory limits = new uint256[](2); + uint256[] memory limitsMin = new uint256[](2); + if (side == OrderSide.Buy) { limits = adapter.getLimits(pair, USDC, WETH); vm.assume(specifiedAmount < limits[1]); - vm.assume(specifiedAmount > limits[3]); - + + limitsMin = getMinLimits(USDC, WETH); + vm.assume(specifiedAmount > limitsMin[1] * 115 / 100); + deal(address(USDC), address(this), type(uint256).max); USDC.approve(address(adapter), type(uint256).max); } else { limits = adapter.getLimits(pair, USDC, WETH); vm.assume(specifiedAmount < limits[0]); - vm.assume(specifiedAmount > limits[2]); - + + limitsMin = getMinLimits(USDC, WETH); + vm.assume(specifiedAmount > limitsMin[0] * 115 / 100); + deal(address(USDC), address(this), type(uint256).max); USDC.approve(address(adapter), specifiedAmount); } - + uint256 usdc_balance_before = USDC.balanceOf(address(this)); uint256 weth_balance_before = WETH.balanceOf(address(this)); - + Trade memory trade = adapter.swap( pair, USDC, @@ -86,14 +108,14 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { side, specifiedAmount ); - + if (trade.calculatedAmount > 0) { if (side == OrderSide.Buy) { assertEq( specifiedAmount, WETH.balanceOf(address(this)) + weth_balance_before ); - + assertEq( trade.calculatedAmount, usdc_balance_before - USDC.balanceOf(address(this)) @@ -103,7 +125,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { specifiedAmount, usdc_balance_before - USDC.balanceOf(address(this)) ); - + assertEq( trade.calculatedAmount, weth_balance_before + WETH.balanceOf(address(this)) @@ -111,27 +133,27 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { } } } - + function executeIncreasingSwapsIntegral(OrderSide side) internal { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); - + uint256[] memory amounts = new uint256[](TEST_ITERATIONS); for (uint256 i = 0; i < TEST_ITERATIONS; i++) { amounts[i] = 1000 * i * 10 ** 6; } - + Trade[] memory trades = new Trade[](TEST_ITERATIONS); uint256 beforeSwap; for (uint256 i = 0; 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, @@ -141,7 +163,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { assertEq(trades[i].price.compareFractions(trades[i + 1].price), 1); } } - + function testGetCapabilitiesIntegral( bytes32 pair, address t0, @@ -152,21 +174,21 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { IERC20(t0), IERC20(t1) ); - + assertEq(res.length, 3); } - + function testGetTokensIntegral() public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); IERC20[] memory tokens = adapter.getTokens(pair); - + assertEq(tokens.length, 2); } - + function testGetLimitsIntegral() public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); - - assertEq(limits.length, 4); + + assertEq(limits.length, 2); } } \ No newline at end of file From 3a2e6202466f67575100fe631379822acef3f988 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Sat, 6 Jan 2024 17:27:36 +0100 Subject: [PATCH 29/43] fix: Fixed Fractions --- evm/lib/forge-std | 2 +- evm/src/integral/IntegralSwapAdapter.sol | 24 ++++++++++++------------ evm/test/IntegralSwapAdapter.t.sol | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/evm/lib/forge-std b/evm/lib/forge-std index f73c73d..155d547 160000 --- a/evm/lib/forge-std +++ b/evm/lib/forge-std @@ -1 +1 @@ -Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 +Subproject commit 155d547c449afa8715f538d69454b83944117811 diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index d5f7673..80e4446 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; +import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; /// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but it is not strictly necessary to be this long /// as the contract allows less durations, we use 1000 seconds (15 minutes) as a deadline @@ -33,7 +34,10 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256 price_ = relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)); for (uint256 i = 0; i < _specifiedAmounts.length; i++) { - _prices[i] = Fraction(price_, 1); + _prices[i] = Fraction( + relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)) * 10**(ERC20(address(_sellToken)).decimals()) / 10**18, + 10**(ERC20(address(_buyToken)).decimals()) + ); } } @@ -58,12 +62,10 @@ contract IntegralSwapAdapter is ISwapAdapter { buy(sellToken, buyToken, specifiedAmount); } trade.gasUsed = gasBefore - gasleft(); - /** - * @dev once we get reply from propeller about return values in price() function and in every Fraction - * Fraction[0] = relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)) * 10^(IERC20(sellToken).decimals) / 10^18 - * Fraction[1] = 10^(IERC20(buyToken).decimals) - */ - trade.price = Fraction(relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)), 1); + trade.price = Fraction( + relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)) * 10**(ERC20(address(sellToken)).decimals()) / 10**18, + 10**(ERC20(address(buyToken)).decimals()) + ); } /// @inheritdoc ISwapAdapter @@ -197,11 +199,9 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256 limitMax1 ) = relayer.getPoolState(address(sellToken), address(buyToken)); - uint256[] memory limits_ = new uint256[](2); - limits_[0] = limitMax0; - limits_[1] = limitMax1; - - return limits_; + limits = new uint256[](2); + limits[0] = limitMax0; + limits[1] = limitMax1; } } diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index adb3893..0120198 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -42,9 +42,9 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { uint256 limitMax1 ) = relayer.getPoolState(address(sellToken), address(buyToken)); - uint256[] memory limits_ = new uint256[](2); - limits_[0] = limitMin0; - limits_[1] = limitMin1; + limits = new uint256[](2); + limits[0] = limitMin0; + limits[1] = limitMin1; } function testPriceFuzzIntegral(uint256 amount0, uint256 amount1) public { From 37ea829592616cf9b76a1dd05953d710680c6aa5 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 16:29:55 +0100 Subject: [PATCH 30/43] fix: Fixed prices --- evm/lib/forge-std | 2 +- evm/src/integral/IntegralSwapAdapter.sol | 28 ++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/evm/lib/forge-std b/evm/lib/forge-std index 155d547..f73c73d 160000 --- a/evm/lib/forge-std +++ b/evm/lib/forge-std @@ -1 +1 @@ -Subproject commit 155d547c449afa8715f538d69454b83944117811 +Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 80e4446..d4bb2a4 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -30,14 +30,9 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256[] memory _specifiedAmounts ) external view override returns (Fraction[] memory _prices) { _prices = new Fraction[](_specifiedAmounts.length); - - uint256 price_ = relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)); - + for (uint256 i = 0; i < _specifiedAmounts.length; i++) { - _prices[i] = Fraction( - relayer.getPriceByTokenAddresses(address(_sellToken), address(_buyToken)) * 10**(ERC20(address(_sellToken)).decimals()) / 10**18, - 10**(ERC20(address(_buyToken)).decimals()) - ); + _prices[i] = getPriceAt(address(_sellToken), address(_buyToken)); } } @@ -62,10 +57,7 @@ contract IntegralSwapAdapter is ISwapAdapter { buy(sellToken, buyToken, specifiedAmount); } trade.gasUsed = gasBefore - gasleft(); - trade.price = Fraction( - relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)) * 10**(ERC20(address(sellToken)).decimals()) / 10**18, - 10**(ERC20(address(buyToken)).decimals()) - ); + trade.price = getPriceAt(address(sellToken), address(buyToken)); } /// @inheritdoc ISwapAdapter @@ -203,6 +195,20 @@ contract IntegralSwapAdapter is ISwapAdapter { limits[0] = limitMax0; limits[1] = limitMax1; } + + /// @notice Get swap price including fee + /// @param sellToken token to sell + /// @param buyToken token to buy + function getPriceAt(address sellToken, address buyToken) internal view returns(Fraction memory) { + uint256 priceWithoutFee = relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)); + ITwapFactory factory = ITwapFactory(relayer.factory()); + address pairAddress = factory.getPair(address(sellToken), address(buyToken)); + + return Fraction( + priceWithoutFee * 10**18, + 10**(ERC20(sellToken).decimals()) * 10**18 * (10**18 - relayer.swapFee(pairAddress)) / 10**(ERC20(buyToken).decimals()) + ); + } } interface ITwapRelayer { From af7b9cd9a634486fc69d1c6fa530a618de419794 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 16:43:03 +0100 Subject: [PATCH 31/43] feat: Integrated SafeERC20 --- evm/lib/forge-std | 2 +- evm/src/integral/IntegralSwapAdapter.sol | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/evm/lib/forge-std b/evm/lib/forge-std index f73c73d..155d547 160000 --- a/evm/lib/forge-std +++ b/evm/lib/forge-std @@ -1 +1 @@ -Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 +Subproject commit 155d547c449afa8715f538d69454b83944117811 diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index d4bb2a4..8af8487 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; /// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but it is not strictly necessary to be this long /// as the contract allows less durations, we use 1000 seconds (15 minutes) as a deadline @@ -10,6 +11,8 @@ uint256 constant SWAP_DEADLINE_SEC = 1000; /// @title Integral Swap Adapter contract IntegralSwapAdapter is ISwapAdapter { + using SafeERC20 for IERC20; + ITwapRelayer immutable relayer; constructor(address relayer_) { @@ -124,21 +127,19 @@ contract IntegralSwapAdapter is ISwapAdapter { IERC20 buyToken, uint256 amount ) internal returns (uint256) { - address swapper = msg.sender; - uint256 amountOut = relayer.quoteSell(address(sellToken), address(buyToken), amount); if (amountOut == 0) { revert Unavailable("AmountOut is zero!"); } - sellToken.transferFrom(msg.sender, address(this), amount); - sellToken.approve(address(relayer), amount); + sellToken.safeTransferFrom(msg.sender, address(this), amount); + sellToken.safeIncreaseAllowance(address(relayer), amount); relayer.sell(ITwapRelayer.SellParams({ tokenIn: address(sellToken), tokenOut: address(buyToken), wrapUnwrap: false, - to: swapper, + to: msg.sender, submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), amountIn: amount, amountOutMin: amountOut @@ -157,21 +158,19 @@ contract IntegralSwapAdapter is ISwapAdapter { IERC20 buyToken, uint256 amountBought ) internal returns (uint256) { - address swapper = msg.sender; - uint256 amountIn = relayer.quoteBuy(address(sellToken), address(buyToken), amountBought); if (amountIn == 0) { revert Unavailable("AmountIn is zero!"); } - sellToken.transferFrom(msg.sender, address(this), amountIn); - sellToken.approve(address(relayer), amountIn); + sellToken.safeTransferFrom(msg.sender, address(this), amountIn); + sellToken.safeIncreaseAllowance(address(relayer), amountIn); relayer.buy(ITwapRelayer.BuyParams({ tokenIn: address(sellToken), tokenOut: address(buyToken), wrapUnwrap: false, - to: swapper, + to: msg.sender, submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), amountInMax: amountIn, amountOut: amountBought From 8798fc7313cb9873d30616a354f8aa37e0368fbc Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 16:48:07 +0100 Subject: [PATCH 32/43] fix: Removed internal _getLimits --- evm/src/integral/IntegralSwapAdapter.sol | 29 ++++++++++-------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 8af8487..3d7a887 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -70,7 +70,18 @@ contract IntegralSwapAdapter is ISwapAdapter { override returns (uint256[] memory limits) { - return _getLimits(poolId, sellToken, buyToken); + ( + , + , + , + uint256 limitMax0, + , + uint256 limitMax1 + ) = relayer.getPoolState(address(sellToken), address(buyToken)); + + limits = new uint256[](2); + limits[0] = limitMax0; + limits[1] = limitMax1; } /// @inheritdoc ISwapAdapter @@ -179,22 +190,6 @@ contract IntegralSwapAdapter is ISwapAdapter { return amountIn; } - /// @notice Internal counterpart of _getLimits - function _getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) internal view returns (uint256[] memory limits) { - ( - uint256 price_, - uint256 fee, - , - uint256 limitMax0, - , - uint256 limitMax1 - ) = relayer.getPoolState(address(sellToken), address(buyToken)); - - limits = new uint256[](2); - limits[0] = limitMax0; - limits[1] = limitMax1; - } - /// @notice Get swap price including fee /// @param sellToken token to sell /// @param buyToken token to buy From 46e9a3dd0e97b9c6a91c95544708079154b696a6 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 16:53:55 +0100 Subject: [PATCH 33/43] feat: Added ConstantPrice capability --- evm/src/integral/IntegralSwapAdapter.sol | 3 ++- evm/test/IntegralSwapAdapter.t.sol | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 3d7a887..b958f41 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -91,10 +91,11 @@ contract IntegralSwapAdapter is ISwapAdapter { override returns (Capability[] memory capabilities) { - capabilities = new Capability[](3); + capabilities = new Capability[](4); capabilities[0] = Capability.SellOrder; capabilities[1] = Capability.BuyOrder; capabilities[2] = Capability.PriceFunction; + capabilities[3] = Capability.ConstantPrice; } /// @inheritdoc ISwapAdapter diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index 0120198..4e9e23e 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -175,7 +175,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { IERC20(t1) ); - assertEq(res.length, 3); + assertEq(res.length, 4); } function testGetTokensIntegral() public { From a7ea4d3604bba4c93a93526e936383326b5fe1de Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 18:04:20 +0100 Subject: [PATCH 34/43] fix: Fixed and Improved tests --- evm/src/integral/IntegralSwapAdapter.sol | 2 +- evm/test/IntegralSwapAdapter.t.sol | 77 ++++++++++++++++-------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index b958f41..425c786 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -46,7 +46,7 @@ contract IntegralSwapAdapter is ISwapAdapter { IERC20 buyToken, OrderSide side, uint256 specifiedAmount - ) external returns (Trade memory trade) { + ) external override returns (Trade memory trade) { if (specifiedAmount == 0) { return trade; } diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index 4e9e23e..65b8fd5 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -32,21 +32,6 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR"); } - function getMinLimits(IERC20 sellToken, IERC20 buyToken) public 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[](2); - limits[0] = limitMin0; - limits[1] = limitMin1; - } - function testPriceFuzzIntegral(uint256 amount0, uint256 amount1) public { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); uint256[] memory limits = adapter.getLimits(pair, USDC, WETH); @@ -64,14 +49,30 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { assertGt(prices[i].denominator, 0); } } + + function testPriceKeepingIntegral() public { + bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + uint256[] memory amounts = new uint256[](TEST_ITERATIONS); + + for (uint256 i = 0; i < TEST_ITERATIONS; i++) { + amounts[i] = 1000 * i * 10 ** 15; + } + + Fraction[] memory prices = adapter.price(pair, WETH, USDC, amounts); + + for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) { + Fraction memory reducedPrice0 = Fraction(prices[i].numerator / 10**18, prices[i].denominator / 10**18); + Fraction memory reducedPrice1 = Fraction(prices[i + 1].numerator / 10**18, prices[i + 1].denominator / 10**18); + assertEq(reducedPrice0.compareFractions(reducedPrice1), 0); + assertGt(prices[i].denominator, 0); + assertGt(prices[i + 1].denominator, 0); + } + } /// @dev Since TwapRelayer's calculateAmountOut function is internal, and using quoteSell would /// revert the transaction if calculateAmountOut is not enough, /// we need a threshold to cover this internal amount, applied to function testSwapFuzzIntegral(uint256 specifiedAmount, bool isBuy) public { - // Fails at times | FAIL. Reason: revert: TR03; - // - // OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); @@ -113,7 +114,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { if (side == OrderSide.Buy) { assertEq( specifiedAmount, - WETH.balanceOf(address(this)) + weth_balance_before + WETH.balanceOf(address(this)) - weth_balance_before ); assertEq( @@ -128,23 +129,34 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { assertEq( trade.calculatedAmount, - weth_balance_before + WETH.balanceOf(address(this)) + WETH.balanceOf(address(this)) - weth_balance_before ); } } } - + + function testSwapSellIncreasingIntegral() public { + executeIncreasingSwapsIntegral(OrderSide.Sell); + } + + function testSwapBuyIncreasing() public { + executeIncreasingSwapsIntegral(OrderSide.Buy); + } + function executeIncreasingSwapsIntegral(OrderSide side) internal { bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); + + uint256 amountConstant_ = side == OrderSide.Sell ? 1000 * 10**6 : 10**17; uint256[] memory amounts = new uint256[](TEST_ITERATIONS); - for (uint256 i = 0; i < TEST_ITERATIONS; i++) { - amounts[i] = 1000 * i * 10 ** 6; + 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 = 0; i < TEST_ITERATIONS; i++) { + for (uint256 i = 1; i < TEST_ITERATIONS; i++) { beforeSwap = vm.snapshot(); deal(address(USDC), address(this), amounts[i]); @@ -160,7 +172,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { trades[i + 1].calculatedAmount ); assertLe(trades[i].gasUsed, trades[i + 1].gasUsed); - assertEq(trades[i].price.compareFractions(trades[i + 1].price), 1); + assertEq(trades[i].price.compareFractions(trades[i + 1].price), 0); } } @@ -191,4 +203,19 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { assertEq(limits.length, 2); } + + function getMinLimits(IERC20 sellToken, IERC20 buyToken) public view returns (uint256[] memory limits) { + ( + , + , + uint256 limitMin0, + , + uint256 limitMin1 + , + ) = relayer.getPoolState(address(sellToken), address(buyToken)); + + limits = new uint256[](2); + limits[0] = limitMin0; + limits[1] = limitMin1; + } } \ No newline at end of file From 3d7d4f1a985a9ef7b3926b160ee60c752349b0b2 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 18:05:07 +0100 Subject: [PATCH 35/43] fix: Removed unused parameters from Contract --- evm/src/integral/IntegralSwapAdapter.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 425c786..85e9037 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -27,7 +27,7 @@ contract IntegralSwapAdapter is ISwapAdapter { /// 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, + bytes32, IERC20 _sellToken, IERC20 _buyToken, uint256[] memory _specifiedAmounts @@ -41,7 +41,7 @@ contract IntegralSwapAdapter is ISwapAdapter { /// @inheritdoc ISwapAdapter function swap( - bytes32 poolId, + bytes32, IERC20 sellToken, IERC20 buyToken, OrderSide side, @@ -64,7 +64,7 @@ contract IntegralSwapAdapter is ISwapAdapter { } /// @inheritdoc ISwapAdapter - function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + function getLimits(bytes32, IERC20 sellToken, IERC20 buyToken) external view override @@ -85,7 +85,7 @@ contract IntegralSwapAdapter is ISwapAdapter { } /// @inheritdoc ISwapAdapter - function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) + function getCapabilities(bytes32, IERC20, IERC20) external pure override From e8b3e14b3eeac0373f92b5e337aeaa85c9dc573c Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 18:26:07 +0100 Subject: [PATCH 36/43] Updated .gitignore --- evm/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/evm/.gitignore b/evm/.gitignore index 85198aa..409f575 100644 --- a/evm/.gitignore +++ b/evm/.gitignore @@ -1,6 +1,7 @@ # Compiler files cache/ out/ +coverage/ # Ignores development broadcast logs !/broadcast @@ -12,3 +13,7 @@ docs/ # Dotenv file .env + +# Others +.DS_STORE +lcov.info From 3d900e7500e6584debab24facffe605db358332a Mon Sep 17 00:00:00 2001 From: domenicodev Date: Mon, 8 Jan 2024 18:26:39 +0100 Subject: [PATCH 37/43] Updated .gitgnore in main folder --- .gitignore | 1 + .vscode/settings.json | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 485dee6..4045fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b00c8e4..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "solidity.formatter": "forge", - "solidity.compileUsingRemoteVersion": "v0.8.20", - "solidity.packageDefaultDependenciesContractsDirectory": "evm/src", - "solidity.packageDefaultDependenciesDirectory": "evm/lib", -} \ No newline at end of file From cf41c472c6ee146db2440b2a2a567aada40184b4 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Wed, 17 Jan 2024 15:24:18 +0100 Subject: [PATCH 38/43] fix: Initial code review fixes --- evm/src/integral/IntegralSwapAdapter.sol | 50 ++++++++++++++++-------- evm/test/IntegralSwapAdapter.t.sol | 19 --------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 85e9037..67ab0bb 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -8,6 +8,7 @@ import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/Safe /// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but it is not strictly necessary to be this long /// as the contract allows less durations, we use 1000 seconds (15 minutes) as a deadline uint256 constant SWAP_DEADLINE_SEC = 1000; +uint256 constant STANDARD_TOKEN_DECIMALS = 10**18; /// @title Integral Swap Adapter contract IntegralSwapAdapter is ISwapAdapter { @@ -33,12 +34,43 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256[] memory _specifiedAmounts ) external view override returns (Fraction[] memory _prices) { _prices = new Fraction[](_specifiedAmounts.length); + uint256 price = getPriceAt(address(_sellToken), address(_buyToken)); for (uint256 i = 0; i < _specifiedAmounts.length; i++) { - _prices[i] = getPriceAt(address(_sellToken), address(_buyToken)); + _prices[i] = price; } } + /// @notice Get swap price including fee + /// @param sellToken token to sell + /// @param buyToken token to buy + function getPriceAt(address sellToken, address buyToken) internal view returns(Fraction memory) { + uint256 priceWithoutFee = relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)); + ITwapFactory factory = ITwapFactory(relayer.factory()); + address pairAddress = factory.getPair(address(sellToken), address(buyToken)); + + // get swapFee formatted; swapFee is a constant + uint256 swapFeeFormatted = (STANDARD_TOKEN_DECIMALS - relayer.swapFee(pairAddress)); + + // get token decimals + uint256 sellTokenDecimals = 10**ERC20(sellToken).decimals(); + uint256 buyTokenDecimals = 10**ERC20(buyToken).decimals(); + + /** + * @dev + * Denominator works as a "standardizer" for the price rather than a reserve value + * as Integral takes prices from oracles and do not operate with reserves; + * it is therefore used to maintain integrity for the Fraction division, + * as numerator and denominator could have different token decimals(es. ETH(18)-USDC(6)). + * Both numerator and denominator are also multiplied by STANDARD_TOKEN_DECIMALS + * to ensure that precision losses are minimized or null. + */ + return Fraction( + priceWithoutFee * STANDARD_TOKEN_DECIMALS, + STANDARD_TOKEN_DECIMALS * sellTokenDecimals * swapFeeFormatted / buyTokenDecimals + ); + } + /// @inheritdoc ISwapAdapter function swap( bytes32, @@ -154,7 +186,7 @@ contract IntegralSwapAdapter is ISwapAdapter { to: msg.sender, submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), amountIn: amount, - amountOutMin: amountOut + amountOutMin: 0 })); return amountOut; @@ -190,20 +222,6 @@ contract IntegralSwapAdapter is ISwapAdapter { return amountIn; } - - /// @notice Get swap price including fee - /// @param sellToken token to sell - /// @param buyToken token to buy - function getPriceAt(address sellToken, address buyToken) internal view returns(Fraction memory) { - uint256 priceWithoutFee = relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)); - ITwapFactory factory = ITwapFactory(relayer.factory()); - address pairAddress = factory.getPair(address(sellToken), address(buyToken)); - - return Fraction( - priceWithoutFee * 10**18, - 10**(ERC20(sellToken).decimals()) * 10**18 * (10**18 - relayer.swapFee(pairAddress)) / 10**(ERC20(buyToken).decimals()) - ); - } } interface ITwapRelayer { diff --git a/evm/test/IntegralSwapAdapter.t.sol b/evm/test/IntegralSwapAdapter.t.sol index 65b8fd5..3d41885 100644 --- a/evm/test/IntegralSwapAdapter.t.sol +++ b/evm/test/IntegralSwapAdapter.t.sol @@ -49,25 +49,6 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes { assertGt(prices[i].denominator, 0); } } - - function testPriceKeepingIntegral() public { - bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); - uint256[] memory amounts = new uint256[](TEST_ITERATIONS); - - for (uint256 i = 0; i < TEST_ITERATIONS; i++) { - amounts[i] = 1000 * i * 10 ** 15; - } - - Fraction[] memory prices = adapter.price(pair, WETH, USDC, amounts); - - for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) { - Fraction memory reducedPrice0 = Fraction(prices[i].numerator / 10**18, prices[i].denominator / 10**18); - Fraction memory reducedPrice1 = Fraction(prices[i + 1].numerator / 10**18, prices[i + 1].denominator / 10**18); - assertEq(reducedPrice0.compareFractions(reducedPrice1), 0); - assertGt(prices[i].denominator, 0); - assertGt(prices[i + 1].denominator, 0); - } - } /// @dev Since TwapRelayer's calculateAmountOut function is internal, and using quoteSell would /// revert the transaction if calculateAmountOut is not enough, From 1cffd007ad034c6cb9267ad0020df817438584c6 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 18 Jan 2024 11:06:13 +0100 Subject: [PATCH 39/43] fix: Moved getPriceAt() to bottom --- evm/lib/forge-std | 2 +- evm/src/integral/IntegralSwapAdapter.sol | 60 ++++++++++++------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/evm/lib/forge-std b/evm/lib/forge-std index 155d547..f73c73d 160000 --- a/evm/lib/forge-std +++ b/evm/lib/forge-std @@ -1 +1 @@ -Subproject commit 155d547c449afa8715f538d69454b83944117811 +Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 67ab0bb..ea14885 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -41,36 +41,6 @@ contract IntegralSwapAdapter is ISwapAdapter { } } - /// @notice Get swap price including fee - /// @param sellToken token to sell - /// @param buyToken token to buy - function getPriceAt(address sellToken, address buyToken) internal view returns(Fraction memory) { - uint256 priceWithoutFee = relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)); - ITwapFactory factory = ITwapFactory(relayer.factory()); - address pairAddress = factory.getPair(address(sellToken), address(buyToken)); - - // get swapFee formatted; swapFee is a constant - uint256 swapFeeFormatted = (STANDARD_TOKEN_DECIMALS - relayer.swapFee(pairAddress)); - - // get token decimals - uint256 sellTokenDecimals = 10**ERC20(sellToken).decimals(); - uint256 buyTokenDecimals = 10**ERC20(buyToken).decimals(); - - /** - * @dev - * Denominator works as a "standardizer" for the price rather than a reserve value - * as Integral takes prices from oracles and do not operate with reserves; - * it is therefore used to maintain integrity for the Fraction division, - * as numerator and denominator could have different token decimals(es. ETH(18)-USDC(6)). - * Both numerator and denominator are also multiplied by STANDARD_TOKEN_DECIMALS - * to ensure that precision losses are minimized or null. - */ - return Fraction( - priceWithoutFee * STANDARD_TOKEN_DECIMALS, - STANDARD_TOKEN_DECIMALS * sellTokenDecimals * swapFeeFormatted / buyTokenDecimals - ); - } - /// @inheritdoc ISwapAdapter function swap( bytes32, @@ -222,6 +192,36 @@ contract IntegralSwapAdapter is ISwapAdapter { return amountIn; } + + /// @notice Get swap price including fee + /// @param sellToken token to sell + /// @param buyToken token to buy + function getPriceAt(address sellToken, address buyToken) internal view returns(Fraction memory) { + uint256 priceWithoutFee = relayer.getPriceByTokenAddresses(address(sellToken), address(buyToken)); + ITwapFactory factory = ITwapFactory(relayer.factory()); + address pairAddress = factory.getPair(address(sellToken), address(buyToken)); + + // get swapFee formatted; swapFee is a constant + uint256 swapFeeFormatted = (STANDARD_TOKEN_DECIMALS - relayer.swapFee(pairAddress)); + + // get token decimals + uint256 sellTokenDecimals = 10**ERC20(sellToken).decimals(); + uint256 buyTokenDecimals = 10**ERC20(buyToken).decimals(); + + /** + * @dev + * Denominator works as a "standardizer" for the price rather than a reserve value + * as Integral takes prices from oracles and do not operate with reserves; + * it is therefore used to maintain integrity for the Fraction division, + * as numerator and denominator could have different token decimals(es. ETH(18)-USDC(6)). + * Both numerator and denominator are also multiplied by STANDARD_TOKEN_DECIMALS + * to ensure that precision losses are minimized or null. + */ + return Fraction( + priceWithoutFee * STANDARD_TOKEN_DECIMALS, + STANDARD_TOKEN_DECIMALS * sellTokenDecimals * swapFeeFormatted / buyTokenDecimals + ); + } } interface ITwapRelayer { From 14ce015ac4726e006f2c07be9c8ae5c8ea731725 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Fri, 19 Jan 2024 11:54:11 +0100 Subject: [PATCH 40/43] feat: Added comment about minLimits as requested --- evm/src/integral/IntegralSwapAdapter.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index ea14885..840607d 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -84,6 +84,11 @@ contract IntegralSwapAdapter is ISwapAdapter { limits = new uint256[](2); limits[0] = limitMax0; limits[1] = limitMax1; + /** + * @dev minLimits in integral are the args: 2(for sellToken, the one before limitMax0) + * and 4(for buyToken, the one before limitMax1) of the function relayer.getPoolState(sellToken, buyToken); + * an implementation of them can be found in the test of this adapter + */ } /// @inheritdoc ISwapAdapter From 8001e22674efc0084f653cc0a315c86a71235c52 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 25 Jan 2024 11:51:04 +0100 Subject: [PATCH 41/43] fix: Set amountOutMin --- evm/src/integral/IntegralSwapAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index 840607d..a394d26 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -161,7 +161,7 @@ contract IntegralSwapAdapter is ISwapAdapter { to: msg.sender, submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), amountIn: amount, - amountOutMin: 0 + amountOutMin: amountOut })); return amountOut; From 7dca13f9e0dd7b1d1ee2b6b73b51aab50d5f9dca Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 25 Jan 2024 11:53:40 +0100 Subject: [PATCH 42/43] fix: fixed price type in price function --- evm/src/integral/IntegralSwapAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/integral/IntegralSwapAdapter.sol b/evm/src/integral/IntegralSwapAdapter.sol index a394d26..4797132 100644 --- a/evm/src/integral/IntegralSwapAdapter.sol +++ b/evm/src/integral/IntegralSwapAdapter.sol @@ -34,7 +34,7 @@ contract IntegralSwapAdapter is ISwapAdapter { uint256[] memory _specifiedAmounts ) external view override returns (Fraction[] memory _prices) { _prices = new Fraction[](_specifiedAmounts.length); - uint256 price = getPriceAt(address(_sellToken), address(_buyToken)); + Fraction memory price = getPriceAt(address(_sellToken), address(_buyToken)); for (uint256 i = 0; i < _specifiedAmounts.length; i++) { _prices[i] = price; From f4f56ad42dbf5ab0a5eb6eb2e55eaf4c4794d269 Mon Sep 17 00:00:00 2001 From: domenicodev Date: Thu, 25 Jan 2024 12:39:52 +0100 Subject: [PATCH 43/43] fix: Solved merge conflicts --- .gitignore | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4045fd6..2c65d74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,13 @@ +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Substreams spkg files are build artifacts +*.spkg + +.env +.vscode .idea -.vscode/ +*.log