Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connectors: Implement Odos connector #158

Merged
merged 4 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading