Skip to content

Commit

Permalink
Connectors: Implement Odos connector (#158)
Browse files Browse the repository at this point in the history
Co-authored-by: Facu Spagnuolo <facundo_spagnuolo@icloud.com>
  • Loading branch information
PedroAraoz and facuspagnuolo authored Aug 16, 2024
1 parent acc770e commit 561915e
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 14 deletions.
52 changes: 52 additions & 0 deletions packages/connectors/contracts/interfaces/odos/IOdosV2Connector.sol
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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);
}
70 changes: 70 additions & 0 deletions packages/connectors/contracts/odos/OdosV2Connector.sol
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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);
}
}
4 changes: 2 additions & 2 deletions packages/connectors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
72 changes: 72 additions & 0 deletions packages/connectors/src/odos.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
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<SwapResponse> {
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',
},
}
)
}
2 changes: 1 addition & 1 deletion packages/connectors/test/bebop/BebopConnector.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"tokenOut": "0x4200000000000000000000000000000000000006",
"amountIn": "10000000000",
"data": "0x4dcebcba00000000000000000000000000000000000000000000000000000000664f699a000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f76ba000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000024454cd9c93eef31000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c0000000000000000000000000000000000000000000000000000000000000000311627ff2adae9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041342f31efd4fa1a26eb099aaefeab0c13627cfb74f5dc0b1fda580d1cdc3df8a2775e6e0db3ea045debbac8bb307602500d90ca0ed6c0f02f39f31262ebf3db661b00000000000000000000000000000000000000000000000000000000000000"
"data": "0x4dcebcba0000000000000000000000000000000000000000000000000000000066be39ac000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000000000000000000000000000000003228f94fa6c000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000034397a5237db8463000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee00000000000000000000000000000000000000000000000000000000000000004dfde906a737cc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000410c6783a42dce9371a1908ea2b8802ca11ed41ea106cdf4de57574b76d46c752241acb1916a6dbe6bd447a1f730a61d012ffa0ee0d81abb22be5e82d4f754a16a1c00000000000000000000000000000000000000000000000000000000000000"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"tokenIn": "0x4200000000000000000000000000000000000006",
"tokenOut": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amountIn": "1000000000000000000",
"data": "0x4dcebcba00000000000000000000000000000000000000000000000000000000664f6a09000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f76bd0000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000e338a472000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c00000000000000000000000000000000000000000000000000000000000000001bcba9f46988af1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004192ff56a724a1d35c56047cf43dd03f101d1adb44d429787bd9d60e5a542f14f678389cb688bf7039da4df38d5df2f1ad7720ec48650a20d4d46f4e5ce02d300b1b00000000000000000000000000000000000000000000000000000000000000"
"data": "0x4dcebcba0000000000000000000000000000000000000000000000000000000066be39b5000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f000000000000000000000000000000000000000000000000000003228f94fa6e0000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000009ddc8149000000000000000000000000d42912755319665397ff090fbb63b1a31ae87cee0000000000000000000000000000000000000000000000000000000000000000d2cb1562705ace0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041d2fcd902f2b58f9ab902f0da2a515457847c72ff8104a4b7919bb969a7cae2840382511cc8d0746b18821cab9d90df528f0f65774ff0902d26ab23a99b8dda391c00000000000000000000000000000000000000000000000000000000000000"
}

This file was deleted.

Loading

0 comments on commit 561915e

Please sign in to comment.