From 561915e88a12815afc3b30ad584909d928880315 Mon Sep 17 00:00:00 2001 From: Pedro <22825402+PedroAraoz@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:39:59 -0300 Subject: [PATCH] Connectors: Implement Odos connector (#158) Co-authored-by: Facu Spagnuolo --- .../interfaces/odos/IOdosV2Connector.sol | 52 +++++++++++++ .../contracts/odos/OdosV2Connector.sol | 70 +++++++++++++++++ packages/connectors/package.json | 4 +- packages/connectors/src/odos.ts | 72 +++++++++++++++++ .../test/bebop/BebopConnector.base.ts | 2 +- .../{14845449 => 18341220}/USDC-WETH.json | 2 +- .../{14845449 => 18341220}/WETH-USDC.json | 2 +- .../fixtures/8453/14845449/WETH-USDC.json | 7 -- .../{14845449 => 18341220}/USDC-WETH.json | 4 +- .../fixtures/8453/18341220/WETH-USDC.json | 7 ++ .../fixtures/8453/18341220/USDC-WETH.json | 7 ++ .../fixtures/8453/18341220/WETH-USDC.json | 7 ++ .../connectors/test/helpers/odos/index.ts | 77 +++++++++++++++++++ .../test/odos/OdosV2Connector.base.ts | 24 ++++++ .../test/odos/OdosV2Connector.behavior.ts | 74 ++++++++++++++++++ 15 files changed, 397 insertions(+), 14 deletions(-) create mode 100644 packages/connectors/contracts/interfaces/odos/IOdosV2Connector.sol create mode 100644 packages/connectors/contracts/odos/OdosV2Connector.sol create mode 100644 packages/connectors/src/odos.ts rename packages/connectors/test/helpers/bebop/fixtures/8453/{14845449 => 18341220}/USDC-WETH.json (58%) rename packages/connectors/test/helpers/bebop/fixtures/8453/{14845449 => 18341220}/WETH-USDC.json (58%) delete mode 100644 packages/connectors/test/helpers/kyberswap/fixtures/8453/14845449/WETH-USDC.json rename packages/connectors/test/helpers/kyberswap/fixtures/8453/{14845449 => 18341220}/USDC-WETH.json (52%) create mode 100644 packages/connectors/test/helpers/kyberswap/fixtures/8453/18341220/WETH-USDC.json create mode 100644 packages/connectors/test/helpers/odos/fixtures/8453/18341220/USDC-WETH.json create mode 100644 packages/connectors/test/helpers/odos/fixtures/8453/18341220/WETH-USDC.json create mode 100644 packages/connectors/test/helpers/odos/index.ts create mode 100644 packages/connectors/test/odos/OdosV2Connector.base.ts create mode 100644 packages/connectors/test/odos/OdosV2Connector.behavior.ts diff --git a/packages/connectors/contracts/interfaces/odos/IOdosV2Connector.sol b/packages/connectors/contracts/interfaces/odos/IOdosV2Connector.sol new file mode 100644 index 0000000..4570d9c --- /dev/null +++ b/packages/connectors/contracts/interfaces/odos/IOdosV2Connector.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity >=0.8.0; + +/** + * @title Odos V2 connector interface + */ +interface IOdosV2Connector { + /** + * @dev The token in is the same as the token out + */ + error OdosV2SwapSameToken(address token); + + /** + * @dev The amount out is lower than the minimum amount out + */ + error OdosV2BadAmountOut(uint256 amountOut, uint256 minAmountOut); + + /** + * @dev The post token in balance is lower than the previous token in balance minus the amount in + */ + error OdosV2BadPostTokenInBalance(uint256 postBalanceIn, uint256 preBalanceIn, uint256 amountIn); + + /** + * @dev Tells the reference to Odos aggregation router v2 + */ + function odosV2Router() external view returns (address); + + /** + * @dev Executes a token swap in Odos V2 + * @param tokenIn Token to be sent + * @param tokenOut Token to be received + * @param amountIn Amount of token in to be swapped + * @param minAmountOut Minimum amount of token out willing to receive + * @param data Calldata to be sent to the Odos aggregation router + */ + function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes memory data) + external + returns (uint256 amountOut); +} diff --git a/packages/connectors/contracts/odos/OdosV2Connector.sol b/packages/connectors/contracts/odos/OdosV2Connector.sol new file mode 100644 index 0000000..63140f8 --- /dev/null +++ b/packages/connectors/contracts/odos/OdosV2Connector.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol'; +import '@openzeppelin/contracts/utils/Address.sol'; + +import '@mimic-fi/v3-helpers/contracts/utils/ERC20Helpers.sol'; + +import '../interfaces/odos/IOdosV2Connector.sol'; + +/** + * @title OdosV2Connector + * @dev Interfaces with Odos V2 to swap tokens + */ +contract OdosV2Connector is IOdosV2Connector { + // Reference to Odos aggregation router v2 + address public immutable override odosV2Router; + + /** + * @dev Creates a new OdosV2Connector contract + * @param _odosV2Router Odos aggregation router v2 reference + */ + constructor(address _odosV2Router) { + odosV2Router = _odosV2Router; + } + + /** + * @dev Executes a token swap in Odos V2 + * @param tokenIn Token to be sent + * @param tokenOut Token to be received + * @param amountIn Amount of token in to be swapped + * @param minAmountOut Minimum amount of token out willing to receive + * @param data Calldata to be sent to the Odos aggregation router + */ + function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes memory data) + external + override + returns (uint256 amountOut) + { + if (tokenIn == tokenOut) revert OdosV2SwapSameToken(tokenIn); + + uint256 preBalanceIn = IERC20(tokenIn).balanceOf(address(this)); + uint256 preBalanceOut = IERC20(tokenOut).balanceOf(address(this)); + + ERC20Helpers.approve(tokenIn, odosV2Router, amountIn); + Address.functionCall(odosV2Router, data, 'ODOS_V2_SWAP_FAILED'); + + uint256 postBalanceIn = IERC20(tokenIn).balanceOf(address(this)); + bool isPostBalanceInUnexpected = postBalanceIn < preBalanceIn - amountIn; + if (isPostBalanceInUnexpected) revert OdosV2BadPostTokenInBalance(postBalanceIn, preBalanceIn, amountIn); + + uint256 postBalanceOut = IERC20(tokenOut).balanceOf(address(this)); + amountOut = postBalanceOut - preBalanceOut; + if (amountOut < minAmountOut) revert OdosV2BadAmountOut(amountOut, minAmountOut); + } +} diff --git a/packages/connectors/package.json b/packages/connectors/package.json index a20f697..4f5fc28 100644 --- a/packages/connectors/package.json +++ b/packages/connectors/package.json @@ -21,10 +21,10 @@ "test:arbitrum": "yarn test --fork arbitrum --block-number 212259071 --chain-id 42161", "test:gnosis": "yarn test --fork gnosis --block-number 28580764 --chain-id 100", "test:avalanche": "yarn test --fork avalanche --block-number 31333905 --chain-id 43114", - "test:bsc": "yarn test --fork bsc --block-number 27925272 --chain-id 56", + "test:bsc": "yarn test --fork bsc --block-number 41410900 --chain-id 56", "test:fantom": "yarn test --fork fantom --block-number 61485606 --chain-id 250", "test:zkevm": "yarn test --fork zkevm --block-number 9014946 --chain-id 1101", - "test:base": "yarn test --fork base --block-number 14845449 --chain-id 8453", + "test:base": "yarn test --fork base --block-number 18341220 --chain-id 8453", "prepare": "yarn build" }, "dependencies": { diff --git a/packages/connectors/src/odos.ts b/packages/connectors/src/odos.ts new file mode 100644 index 0000000..aae1c89 --- /dev/null +++ b/packages/connectors/src/odos.ts @@ -0,0 +1,72 @@ +import axios, { AxiosError } from 'axios' +import { BigNumber, Contract } from 'ethers' + +const ODOS_URL = 'https://api.odos.xyz' +export type SwapResponse = { data: { transaction: { data: string } } } + +export async function getOdosSwapData( + chainId: number, + sender: Contract, + tokenIn: Contract, + tokenOut: Contract, + amountIn: BigNumber, + slippage: number +): Promise { + try { + const response = await getSwap(chainId, sender, tokenIn, tokenOut, amountIn, slippage) + return response.data.transaction.data + } catch (error) { + if (error instanceof AxiosError) throw Error(error.toString() + ' - ' + error.response?.data?.description) + else throw error + } +} + +async function getSwap( + chainId: number, + sender: Contract, + tokenIn: Contract, + tokenOut: Contract, + amountIn: BigNumber, + slippage: number +): Promise { + const response = await axios.post( + `${ODOS_URL}/sor/quote/v2`, + { + chainId, + inputTokens: [ + { + tokenAddress: tokenIn.address, + amount: amountIn.toString(), + }, + ], + outputTokens: [ + { + tokenAddress: tokenOut.address, + proportion: 1, + }, + ], + userAddr: sender.address, + slippageLimitPercent: slippage < 1 ? slippage * 100 : slippage, // The value is 0.5 -> 0.5% + }, + { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + } + ) + const pathId = response.data.pathId + return await axios.post( + `${ODOS_URL}/sor/assemble`, + { + userAddr: sender.address, + pathId: pathId, + }, + { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + } + ) +} diff --git a/packages/connectors/test/bebop/BebopConnector.base.ts b/packages/connectors/test/bebop/BebopConnector.base.ts index 31be5a6..afa0844 100644 --- a/packages/connectors/test/bebop/BebopConnector.base.ts +++ b/packages/connectors/test/bebop/BebopConnector.base.ts @@ -15,7 +15,7 @@ const BEBOP_SETTLEMENT = '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F' const CHAINLINK_ETH_USD = '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70' describe('BebopConnector', () => { - const SLIPPAGE = 0.015 + const SLIPPAGE = 0.02 before('create bebop connector', async function () { this.connector = await deploy('BebopConnector', [BEBOP_SETTLEMENT]) diff --git a/packages/connectors/test/helpers/bebop/fixtures/8453/14845449/USDC-WETH.json b/packages/connectors/test/helpers/bebop/fixtures/8453/18341220/USDC-WETH.json similarity index 58% rename from packages/connectors/test/helpers/bebop/fixtures/8453/14845449/USDC-WETH.json rename to packages/connectors/test/helpers/bebop/fixtures/8453/18341220/USDC-WETH.json index cccdf1f..db77845 100644 --- a/packages/connectors/test/helpers/bebop/fixtures/8453/14845449/USDC-WETH.json +++ b/packages/connectors/test/helpers/bebop/fixtures/8453/18341220/USDC-WETH.json @@ -2,5 +2,5 @@ "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "tokenOut": "0x4200000000000000000000000000000000000006", "amountIn": "10000000000", - "data": "0x4dcebcba00000000000000000000000000000000000000000000000000000000664f699a000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f76ba000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000024454cd9c93eef31000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c0000000000000000000000000000000000000000000000000000000000000000311627ff2adae9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041342f31efd4fa1a26eb099aaefeab0c13627cfb74f5dc0b1fda580d1cdc3df8a2775e6e0db3ea045debbac8bb307602500d90ca0ed6c0f02f39f31262ebf3db661b00000000000000000000000000000000000000000000000000000000000000" + "data": "0x4dcebcba0000000000000000000000000000000000000000000000000000000066be39ac000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000000000000000000000000000000003228f94fa6c000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000034397a5237db8463000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee00000000000000000000000000000000000000000000000000000000000000004dfde906a737cc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000410c6783a42dce9371a1908ea2b8802ca11ed41ea106cdf4de57574b76d46c752241acb1916a6dbe6bd447a1f730a61d012ffa0ee0d81abb22be5e82d4f754a16a1c00000000000000000000000000000000000000000000000000000000000000" } \ No newline at end of file diff --git a/packages/connectors/test/helpers/bebop/fixtures/8453/14845449/WETH-USDC.json b/packages/connectors/test/helpers/bebop/fixtures/8453/18341220/WETH-USDC.json similarity index 58% rename from packages/connectors/test/helpers/bebop/fixtures/8453/14845449/WETH-USDC.json rename to packages/connectors/test/helpers/bebop/fixtures/8453/18341220/WETH-USDC.json index 3a2ccfd..ae67921 100644 --- a/packages/connectors/test/helpers/bebop/fixtures/8453/14845449/WETH-USDC.json +++ b/packages/connectors/test/helpers/bebop/fixtures/8453/18341220/WETH-USDC.json @@ -2,5 +2,5 @@ "tokenIn": "0x4200000000000000000000000000000000000006", "tokenOut": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amountIn": "1000000000000000000", - "data": "0x4dcebcba00000000000000000000000000000000000000000000000000000000664f6a09000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f76bd0000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000e338a472000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c00000000000000000000000000000000000000000000000000000000000000001bcba9f46988af1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004192ff56a724a1d35c56047cf43dd03f101d1adb44d429787bd9d60e5a542f14f678389cb688bf7039da4df38d5df2f1ad7720ec48650a20d4d46f4e5ce02d300b1b00000000000000000000000000000000000000000000000000000000000000" + "data": "0x4dcebcba0000000000000000000000000000000000000000000000000000000066be39b5000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000000000000000000000000000000003228f94fa6e0000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000009ddc8149000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee0000000000000000000000000000000000000000000000000000000000000000d2cb1562705ace0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041d2fcd902f2b58f9ab902f0da2a515457847c72ff8104a4b7919bb969a7cae2840382511cc8d0746b18821cab9d90df528f0f65774ff0902d26ab23a99b8dda391c00000000000000000000000000000000000000000000000000000000000000" } \ No newline at end of file diff --git a/packages/connectors/test/helpers/kyberswap/fixtures/8453/14845449/WETH-USDC.json b/packages/connectors/test/helpers/kyberswap/fixtures/8453/14845449/WETH-USDC.json deleted file mode 100644 index 2fb11cd..0000000 --- a/packages/connectors/test/helpers/kyberswap/fixtures/8453/14845449/WETH-USDC.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "tokenIn": "0x4200000000000000000000000000000000000006", - "tokenOut": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", - "amountIn": "1000000000000000000", - "slippage": 0.015, - "data": "0xe21fd0e9000000000000000000000000000000000000000000000000000000000000002000000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e854000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000c001010000002902000000d0b53d9277642d899df5c87a3966a349a798f22400000000000000000de0b6b3a7640000010a4200000000000000000000000000000000000006833589fcd6edb6e08f4c7c32d4f71b54bda029135bf5b11053e734690269c6b9d438f8c9d48f528a000000000000000000000000664f6f2400000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ee3000000000000000000000000e33363cc0000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000005bf5b11053e734690269c6b9d438f8c9d48f528a0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000dfcaf06100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000000100000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e85400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ef7b22536f75726365223a22222c22416d6f756e74496e555344223a22333831342e363930373338373838323731222c22416d6f756e744f7574555344223a22333831312e313133363034353331303336222c22526566657272616c223a22222c22466c616773223a322c22496e74656772697479496e666f223a7b224b65794944223a2231222c225369676e6174757265223a22624d65762f55584b4c34654a466b7277547a37326e77316c39667058327a37444f6b46546e743975724f7a664d4f4e3856324c665248593963715755686e4c65524233684c394a33646e744132655a45734446494844436b2b5733507a333268366150377056724b4830436549362f37436a5736613963316e6e4b7479314a7433416269594238534a61736d66664a322f7761483964424b4f4935444777654c755657613837613863627969412b3934556b55572b6177557253356b33654c575951634d7639373270677a4336394d5771416a705749343677547343533668685a58304570794879654b74565133653271594f78346d306d61466650364d4f4344513541694832523338436e6336356b69365a61686e6953435938713030533452346161327a46743352476c4e43452f704943637352637538304c67324e6672452f6672536e6f766946762b6634504b3878465565673d3d227d7d0000000000000000000000000000000000" -} \ No newline at end of file diff --git a/packages/connectors/test/helpers/kyberswap/fixtures/8453/14845449/USDC-WETH.json b/packages/connectors/test/helpers/kyberswap/fixtures/8453/18341220/USDC-WETH.json similarity index 52% rename from packages/connectors/test/helpers/kyberswap/fixtures/8453/14845449/USDC-WETH.json rename to packages/connectors/test/helpers/kyberswap/fixtures/8453/18341220/USDC-WETH.json index ac27fe9..d4e5926 100644 --- a/packages/connectors/test/helpers/kyberswap/fixtures/8453/14845449/USDC-WETH.json +++ b/packages/connectors/test/helpers/kyberswap/fixtures/8453/18341220/USDC-WETH.json @@ -2,6 +2,6 @@ "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "tokenOut": "0x4200000000000000000000000000000000000006", "amountIn": "100000000000", - "slippage": 0.015, - "data": "0xe21fd0e9000000000000000000000000000000000000000000000000000000000000002000000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e854000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000c001010000002902000000b2cc224c1c9fee385f8ad6a55b4d94e92359dc590000000000000000000000174876e800010a833589fcd6edb6e08f4c7c32d4f71b54bda0291342000000000000000000000000000000000000065bf5b11053e734690269c6b9d438f8c9d48f528a000000000000000000000000664f6ec80000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000017d44c4c857500000000000000016b9b293a9f666f53000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000005bf5b11053e734690269c6b9d438f8c9d48f528a000000000000000000000000000000000000000000000000000000174876e8000000000000000000000000000000000000000000000000016626e9e3fbbaa88800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000000100000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e8540000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e37b22536f75726365223a22222c22416d6f756e74496e555344223a22313030303030222c22416d6f756e744f7574555344223a2239393934372e31313231323334313739222c22526566657272616c223a22222c22466c616773223a312c22496e74656772697479496e666f223a7b224b65794944223a2231222c225369676e6174757265223a22505038666d616b304377714e7876674370366e6b7371724a7237667674546c2f416e454565553777516c67537532424f734e4f55434a7a454c767264776841694878344b594655496b41744d6c644461353244365636756a5359574b437238425a4f3448526d3452584e74334674366f6d67356a735a4c41723365425a79664573756b416a79344f3766566e55557a41545a2b4d5975424e77692b544a4d434c734f367a4f36442f71365a54446d516d3234342b5136475149756a794f48733975444243594b59525338384732574c49672f615864336872336f5357624b43736133694b42797459776733386b5454757254466a734f42766d34746530555731673053654c434d7a7a6a4f64584969396379774277475a526a4b6d307643517741473877484d414b346e654a4579616f2b79692f484941315a7a546446466361336f776b6658474b76456c7a5450414b2b2f4c3447513d3d227d7d0000000000000000000000000000000000000000000000000000000000" + "slippage": 0.02, + "data": "0xe21fd0e9000000000000000000000000000000000000000000000000000000000000002000000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e854000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000c001010000002902000000b2cc224c1c9fee385f8ad6a55b4d94e92359dc590000000000000000000000174876e800010a833589fcd6edb6e08f4c7c32d4f71b54bda02913420000000000000000000000000000000000000601c1def3b91672704716159c9041aeca392ddffb00000000000000000000000066be3e5f00000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000224b6c58e16d00000000000000020b4b5dff35bc045e000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000001c1def3b91672704716159c9041aeca392ddffb000000000000000000000000000000000000000000000000000000174876e80000000000000000000000000000000000000000000000000200d4198e95f093a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000000100000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e8540000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002277b22536f75726365223a22222c22416d6f756e74496e555344223a2239393933302e3638303931333034323633222c22416d6f756e744f7574555344223a2239393832312e383034383232353231222c22526566657272616c223a22222c22466c616773223a302c22416d6f756e744f7574223a223337373037333335363535373839313637373130222c2254696d657374616d70223a313732333734323633392c22496e74656772697479496e666f223a7b224b65794944223a2231222c225369676e6174757265223a2242416f357175744e7248376c376175426a566b3661754966506f4d684a3455506765384b4d4f6b41304e504c58667a584b386372566b4176365a6435624749504856614f71646e41634334636c5964486832524647533741562f714f5a69464855747371686a5662745654757a6e315a31774f726b726650615a4b4166785a556153593163547473614c662b714338316f726e2f505963436d3756664f50724c4b76515a7444396d417334534a326a502f443277322b434b42634a786166514c54636b34464c756142624865423665303944736f786e3974746a56486b3446736675397a66744c684d4f4c6434534874712b4a63486b34654c78774c5430523151466f62467251624f6a52596f47307542495836574c32742b474b63564242726a547a5666694d336332714375632b326b456167396c6c4f4a576a67586a49515566756e6f436e4933654a4f37496e586b636a3033513d3d227d7d00000000000000000000000000000000000000000000000000" } \ No newline at end of file diff --git a/packages/connectors/test/helpers/kyberswap/fixtures/8453/18341220/WETH-USDC.json b/packages/connectors/test/helpers/kyberswap/fixtures/8453/18341220/WETH-USDC.json new file mode 100644 index 0000000..0efa308 --- /dev/null +++ b/packages/connectors/test/helpers/kyberswap/fixtures/8453/18341220/WETH-USDC.json @@ -0,0 +1,7 @@ +{ + "tokenIn": "0x4200000000000000000000000000000000000006", + "tokenOut": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "amountIn": "1000000000000000000", + "slippage": 0.02, + "data": "0xe21fd0e9000000000000000000000000000000000000000000000000000000000000002000000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e854000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000c00101000000290200000072ab388e2e2f6facef59e3c3fa2c4e29011c2d3800000000000000000de0b6b3a7640000010a4200000000000000000000000000000000000006833589fcd6edb6e08f4c7c32d4f71b54bda0291301c1def3b91672704716159c9041aeca392ddffb00000000000000000000000066be3e6b00000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a5b0000000000000000000000009e115a8b0000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000001c1def3b91672704716159c9041aeca392ddffb0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000009ae80bee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000000100000000000000000000000011ddd59c33c73c44733b4123a86ea5ce57f6e85400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021e7b22536f75726365223a22222c22416d6f756e74496e555344223a22323634372e32373831323437303631222c22416d6f756e744f7574555344223a22323634392e35343736373030333232353734222c22526566657272616c223a22222c22466c616773223a302c22416d6f756e744f7574223a2232363531393337343139222c2254696d657374616d70223a313732333734323635312c22496e74656772697479496e666f223a7b224b65794944223a2231222c225369676e6174757265223a225371437348305346583073492b6531637471726b38387746775845746a6250616e542b796244734a66775544624f5436762b78674938682b4e4457432b4154316369723657644a31627a32522b3078662f7362456e65585341716b51473863542f2f446d6435316e79326a44304a2b77543955586b78745a644655723445446144575877432b413063637a567171586f4f4d774b6937714363496f756a706138427751736861704c383162624535664c57546c4d467a58316254683331616845544f41452b3369664c4c41365445674d63684d5067585446526f5056696c56784c356453733279637231734e7a6c56554d67376c3339664631414f396662396d523563733043374a7072336e694a4565696d544b2f79735845355139334f61544575724832416b6731653074576270774e467568546135467734637a57644641776e703652427341592b514b72746e794f4f304a2b413d3d227d7d0000" +} \ No newline at end of file diff --git a/packages/connectors/test/helpers/odos/fixtures/8453/18341220/USDC-WETH.json b/packages/connectors/test/helpers/odos/fixtures/8453/18341220/USDC-WETH.json new file mode 100644 index 0000000..c2fda22 --- /dev/null +++ b/packages/connectors/test/helpers/odos/fixtures/8453/18341220/USDC-WETH.json @@ -0,0 +1,7 @@ +{ + "tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "tokenOut": "0x4200000000000000000000000000000000000006", + "amountIn": "100000000000", + "slippage": 0.02, + "data": "0x83bd37f90004000205174876e80009020ae2de351167c000051eb80001fb2139331532e3ee59777FBbcB14aF674f3fd67100000001FD6F7A6a5c21A3f503EBaE7a473639974379c35100000000230c062501018f2bc7330101020301011ee3cfe60a0100040201010179885d0a01000502010102cf6e4b0d0200060201080de1f5108aeedc800101282fd30d0200070201080de2228ddbdae38001027b6be6340200010802018000000501003d7b2d340300010902007ffffffa0113fc10310a04010a02000100afeae80a04010b02000100142e120a04010c0200010028f6750a04010d020001018e3cca0a04010e020001001679d00a04010f02000100954c910a04011002000100ed1fd40a04011102000102a8f6180a0401120200010035d0cb0a040113020001ff2dd27b0a04011402000126efd2c0260401150200000a0500160200051f9301c50a0401171800050468b08f0a04011918000557b388c10a04011a180005d7ac8e340a04011b1800040a04011c1800060a04011d1e00030101f0d20a04011f030003046876dc0a0401200300020a04012103000b7a348ad90a04012223010a0a0401242301ff000000000000000000000000000000000000000000000000000000000000007cb1b38591021309c64f451859d79312d8ca2789833589fcd6edb6e08f4c7c32d4f71b54bda02913b79dd08ea68a908a97220c76d19a6aa9cbde4376273fdfe6018230f188741d7f93d4ab589bd261970c1a09d5d0445047da3ab4994262b22404288a3bbb8b2da5db110ad625270061e81987ce342677c3f6fbfc099a05e85bb52c9b284532b89523bf8754616535324976f8dbcef19df0705b95ace86ebb485fb33b095c6e739be19364ab408cd8f102262bb6d0b53d9277642d899df5c87a3966a349a798f224b4cb800910b228ed3d0834cf79d697127bbb00e5fcd3960075c00af339a4e26afc76b949e5ff06ec482fe995c4a52bc79271ab29a53591363ee30a8957713f7716e0b0f65ec116912f834e49805480d2883e4ae0a817f2901500971b353b5dd89aa5218474cb6260be6f31965c239df6d6ef2ac2b5d4f020e2fbb92c87abc7909e3dc5ee8cc884f28bc2d93b72ab388e2e2f6facef59e3c3fa2c4e29011c2d38551a0e3d267bea87048f08cc94cc6035ad99221bb2cc224c1c9fee385f8ad6a55b4d94e92359dc594cfd5ba4b8e0475d9a3cfa863e0e18ccf9d3eb25f81e1a62cbfa487e807044618661bfd4802b49824c36388be6f416a29c8d8eee81c771ce6be14b18d9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca17d7b7bd4fc771555d8cccfe90d2efcf3c7106ddf6c0a374a483101e04ef5f7ac9bd15d9142bac95b78daa6d74fe0e23e5c95446cfadbadc63205cfc199f3e273efe77c69a21b135de18d21c7b3c33c993e8542e6ca0efffb9d57a270b76712b968a38f550c5725949a6f0c72e6c4a641f24049a917db0cbcdd367446122ba5afbc0eacc675ce9f5030f94a15b9feb72588d2800892a00d2abb4ca9071df846e4d69971ccd4a636c403a3c1b00c85e99bb9b56060d486753b99b1e0548d3505d8b797c673b58cad3236aa50979d5f3de3bd1eeb40e81137f22ab794bb100ec4b9d92c3916e99eaf152e14824da6facd100000000000000000000000000000000" +} \ No newline at end of file diff --git a/packages/connectors/test/helpers/odos/fixtures/8453/18341220/WETH-USDC.json b/packages/connectors/test/helpers/odos/fixtures/8453/18341220/WETH-USDC.json new file mode 100644 index 0000000..ab33594 --- /dev/null +++ b/packages/connectors/test/helpers/odos/fixtures/8453/18341220/WETH-USDC.json @@ -0,0 +1,7 @@ +{ + "tokenIn": "0x4200000000000000000000000000000000000006", + "tokenOut": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + "amountIn": "1000000000000000000", + "slippage": 0.02, + "data": "0x83bd37f900020004080de0b6b3a7640000049e036494051eb80001fb2139331532e3ee59777FBbcB14aF674f3fd67100000001FD6F7A6a5c21A3f503EBaE7a473639974379c35100000000140605160105c5da84340100010102018000019e01135567c70a020003020101032047ca0a0200040201012428a9750a0200050201010b11d3b90a03010602010103f3af220a03010702010112abd6db0a030108020101032d80200a03010902010139e176080a03010a020101e707a8e90a03010b0201012a80f7ca2603010c0201014e3864cb0a040e0e0201000a040e0f0201040000020a021111120008050000000d1301000d0301141500080ddf787348aed9000405030100101300ff0000b9977af7dfae32c1633cfe9366e1d763caffdacf42000000000000000000000000000000000000064c36388be6f416a29c8d8eee81c771ce6be14b18e58b73ff901325b8b2056b29712c50237242f520f6c0a374a483101e04ef5f7ac9bd15d9142bac95b4cb800910b228ed3d0834cf79d697127bbb00e5482fe995c4a52bc79271ab29a53591363ee30a8974cb6260be6f31965c239df6d6ef2ac2b5d4f020e2fbb92c87abc7909e3dc5ee8cc884f28bc2d93b72ab388e2e2f6facef59e3c3fa2c4e29011c2d38b2cc224c1c9fee385f8ad6a55b4d94e92359dc594cfd5ba4b8e0475d9a3cfa863e0e18ccf9d3eb254a3636608d7bc5776cb19eb72caa36ebb9ea683bc6dd3aef564a7e04edd4f0d423a0c58c1c295c64cdd367446122ba5afbc0eacc675ce9f5030f94a1418457ca08fa5ec77f811b105f2c585cd051ac10a4846201e94d2a5399774926f760a36d52ac22bfc1cba3fcea344f92d9239c08c0568f6f2f0ee452b79dd08ea68a908a97220c76d19a6aa9cbde4376bb8b2da5db110ad625270061e81987ce342677c3d9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca00000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/packages/connectors/test/helpers/odos/index.ts b/packages/connectors/test/helpers/odos/index.ts new file mode 100644 index 0000000..b99064e --- /dev/null +++ b/packages/connectors/test/helpers/odos/index.ts @@ -0,0 +1,77 @@ +import { currentBlockNumber } from '@mimic-fi/v3-helpers' +import { BigNumber, Contract } from 'ethers' +import fs from 'fs' +import hre from 'hardhat' +import { HardhatNetworkConfig } from 'hardhat/types' +import path from 'path' + +import { getOdosSwapData } from '../../../src/odos' + +type Fixture = { + tokenIn: string + tokenOut: string + amountIn: string + slippage: number + data: string +} + +export async function loadOrGetOdosSwapData( + chainId: number, + sender: Contract, + tokenIn: Contract, + tokenOut: Contract, + amountIn: BigNumber, + slippage: number +): Promise { + const config = hre.network.config as HardhatNetworkConfig + const blockNumber = config?.forking?.blockNumber?.toString() || (await currentBlockNumber()).toString() + + const fixture = await readFixture(chainId, tokenIn, tokenOut, blockNumber) + if (fixture) return fixture.data + + const data = await getOdosSwapData(chainId, sender, tokenIn, tokenOut, amountIn, slippage) + await saveFixture(chainId, tokenIn, tokenOut, amountIn, slippage, data, blockNumber) + return data +} + +async function readFixture( + chainId: number, + tokenIn: Contract, + tokenOut: Contract, + blockNumber: string +): Promise { + const swapPath = `${await tokenIn.symbol()}-${await tokenOut.symbol()}.json` + const fixturePath = path.join(__dirname, 'fixtures', chainId.toString(), blockNumber, swapPath) + if (!fs.existsSync(fixturePath)) return undefined + return JSON.parse(fs.readFileSync(fixturePath).toString()) +} + +async function saveFixture( + chainId: number, + tokenIn: Contract, + tokenOut: Contract, + amountIn: BigNumber, + slippage: number, + data: string, + blockNumber: string +): Promise { + const output = { + tokenIn: tokenIn.address, + tokenOut: tokenOut.address, + amountIn: amountIn.toString(), + slippage, + data, + } + + const fixturesPath = path.join(__dirname, 'fixtures') + if (!fs.existsSync(fixturesPath)) fs.mkdirSync(fixturesPath) + + const networkPath = path.join(fixturesPath, chainId.toString()) + if (!fs.existsSync(networkPath)) fs.mkdirSync(networkPath) + + const blockNumberPath = path.join(networkPath, blockNumber) + if (!fs.existsSync(blockNumberPath)) fs.mkdirSync(blockNumberPath) + + const swapPath = path.join(blockNumberPath, `${await tokenIn.symbol()}-${await tokenOut.symbol()}.json`) + fs.writeFileSync(swapPath, JSON.stringify(output, null, 2)) +} diff --git a/packages/connectors/test/odos/OdosV2Connector.base.ts b/packages/connectors/test/odos/OdosV2Connector.base.ts new file mode 100644 index 0000000..1bf7028 --- /dev/null +++ b/packages/connectors/test/odos/OdosV2Connector.base.ts @@ -0,0 +1,24 @@ +import { deploy } from '@mimic-fi/v3-helpers' + +import { itBehavesLikeOdosV2Connector } from './OdosV2Connector.behavior' + +/* eslint-disable no-secrets/no-secrets */ +const CHAIN = 8453 + +const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' +const WETH = '0x4200000000000000000000000000000000000006' +const WHALE = '0xec8d8D4b215727f3476FF0ab41c406FA99b4272C' + +const ODOS_ROUTER = '0x19cEeAd7105607Cd444F5ad10dd51356436095a1' + +const CHAINLINK_ETH_USD = '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70' + +describe('OdosV2Connector', () => { + const SLIPPAGE = 0.02 + + before('create Odos connector', async function () { + this.connector = await deploy('OdosV2Connector', [ODOS_ROUTER]) + }) + + itBehavesLikeOdosV2Connector(CHAIN, USDC, WETH, WHALE, SLIPPAGE, CHAINLINK_ETH_USD) +}) diff --git a/packages/connectors/test/odos/OdosV2Connector.behavior.ts b/packages/connectors/test/odos/OdosV2Connector.behavior.ts new file mode 100644 index 0000000..0ea7a72 --- /dev/null +++ b/packages/connectors/test/odos/OdosV2Connector.behavior.ts @@ -0,0 +1,74 @@ +import { deployProxy, fp, impersonate, instanceAt, pct, toUSDC, ZERO_ADDRESS } from '@mimic-fi/v3-helpers' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { expect } from 'chai' +import { BigNumber, Contract } from 'ethers' + +import { loadOrGetOdosSwapData } from '../helpers/odos' + +export function itBehavesLikeOdosV2Connector( + CHAIN: number, + USDC: string, + WETH: string, + WHALE: string, + SLIPPAGE: number, + CHAINLINK_ETH_USD: string +): void { + let weth: Contract, usdc: Contract, whale: SignerWithAddress, priceOracle: Contract + + before('load tokens and accounts', async function () { + weth = await instanceAt('IERC20Metadata', WETH) + usdc = await instanceAt('IERC20Metadata', USDC) + whale = await impersonate(WHALE, fp(100)) + }) + + before('create price oracle', async function () { + priceOracle = await deployProxy( + '@mimic-fi/v3-price-oracle/artifacts/contracts/PriceOracle.sol/PriceOracle', + [], + [ZERO_ADDRESS, ZERO_ADDRESS, USDC, [{ base: WETH, quote: USDC, feed: CHAINLINK_ETH_USD }]] + ) + }) + + const getExpectedMinAmountOut = async ( + tokenIn: string, + tokenOut: string, + amountIn: BigNumber, + slippage: number + ): Promise => { + const price = await priceOracle['getPrice(address,address)'](tokenIn, tokenOut) + const expectedAmountOut = price.mul(amountIn).div(fp(1)) + return expectedAmountOut.sub(pct(expectedAmountOut, slippage)) + } + + context('USDC-WETH', () => { + const amountIn = toUSDC(10e4) + + it('swaps correctly USDC-WETH', async function () { + const previousBalance = await weth.balanceOf(this.connector.address) + await usdc.connect(whale).transfer(this.connector.address, amountIn) + + const data = await loadOrGetOdosSwapData(CHAIN, this.connector, usdc, weth, amountIn, SLIPPAGE) + await this.connector.connect(whale).execute(USDC, WETH, amountIn, 0, data) + + const currentBalance = await weth.balanceOf(this.connector.address) + const expectedMinAmountOut = await getExpectedMinAmountOut(USDC, WETH, amountIn, SLIPPAGE) + expect(currentBalance.sub(previousBalance)).to.be.at.least(expectedMinAmountOut) + }) + }) + + context('WETH-USDC', () => { + const amountIn = fp(1) + + it('swaps correctly WETH-USDC', async function () { + const previousBalance = await usdc.balanceOf(this.connector.address) + await weth.connect(whale).transfer(this.connector.address, amountIn) + + const data = await loadOrGetOdosSwapData(CHAIN, this.connector, weth, usdc, amountIn, SLIPPAGE) + await this.connector.connect(whale).execute(WETH, USDC, amountIn, 0, data) + + const currentBalance = await usdc.balanceOf(this.connector.address) + const expectedMinAmountOut = await getExpectedMinAmountOut(WETH, USDC, amountIn, SLIPPAGE) + expect(currentBalance.sub(previousBalance)).to.be.at.least(expectedMinAmountOut) + }) + }) +}