Skip to content

Commit

Permalink
Tasks: Implement KyberSwap v2 swapper (#154)
Browse files Browse the repository at this point in the history
Co-authored-by: Facu Spagnuolo <[email protected]>
  • Loading branch information
PedroAraoz and facuspagnuolo authored May 27, 2024
1 parent 1976b99 commit 1265133
Show file tree
Hide file tree
Showing 30 changed files with 1,049 additions and 38 deletions.
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 KyberSwap V2 connector interface
*/
interface IKyberSwapV2Connector {
/**
* @dev The token in is the same as the token out
*/
error KyberSwapV2SwapSameToken(address token);

/**
* @dev The amount out is lower than the minimum amount out
*/
error KyberSwapV2BadAmountOut(uint256 amountOut, uint256 minAmountOut);

/**
* @dev The post token in balance is lower than the previous token in balance minus the amount in
*/
error KyberSwapV2BadPostTokenInBalance(uint256 postBalanceIn, uint256 preBalanceIn, uint256 amountIn);

/**
* @dev Tells the reference to KyberSwap aggregation router v2
*/
function kyberSwapV2Router() external view returns (address);

/**
* @dev Executes a token swap in KyberSwap 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 KyberSwap 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/kyberswap/KyberSwapV2Connector.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/kyberswap/IKyberSwapV2Connector.sol';

/**
* @title KyberSwapV2Connector
* @dev Interfaces with KyberSwap V2 to swap tokens
*/
contract KyberSwapV2Connector is IKyberSwapV2Connector {
// Reference to KyberSwap aggregation router v2
address public immutable override kyberSwapV2Router;

/**
* @dev Creates a new KyberSwapV2Connector contract
* @param _kyberSwapV2Router KyberSwap aggregation router v2 reference
*/
constructor(address _kyberSwapV2Router) {
kyberSwapV2Router = _kyberSwapV2Router;
}

/**
* @dev Executes a token swap in KyberSwap 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 KyberSwap aggregation router
*/
function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes memory data)
external
override
returns (uint256 amountOut)
{
if (tokenIn == tokenOut) revert KyberSwapV2SwapSameToken(tokenIn);

uint256 preBalanceIn = IERC20(tokenIn).balanceOf(address(this));
uint256 preBalanceOut = IERC20(tokenOut).balanceOf(address(this));

ERC20Helpers.approve(tokenIn, kyberSwapV2Router, amountIn);
Address.functionCall(kyberSwapV2Router, data, 'KYBER_SWAP_V2_SWAP_FAILED');

uint256 postBalanceIn = IERC20(tokenIn).balanceOf(address(this));
bool isPostBalanceInUnexpected = postBalanceIn < preBalanceIn - amountIn;
if (isPostBalanceInUnexpected) revert KyberSwapV2BadPostTokenInBalance(postBalanceIn, preBalanceIn, amountIn);

uint256 postBalanceOut = IERC20(tokenOut).balanceOf(address(this));
amountOut = postBalanceOut - preBalanceOut;
if (amountOut < minAmountOut) revert KyberSwapV2BadAmountOut(amountOut, minAmountOut);
}
}
4 changes: 2 additions & 2 deletions packages/connectors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"lint:solidity": "solhint 'contracts/**/*.sol' --config ../../node_modules/solhint-config-mimic/index.js",
"lint:typescript": "eslint . --ext .ts",
"test": "hardhat test",
"test:mainnet": "yarn test --fork mainnet --block-number 17525323 --chain-id 1",
"test:mainnet": "yarn test --fork mainnet --block-number 19932950 --chain-id 1",
"test:polygon": "yarn test --fork polygon --block-number 44153231 --chain-id 137",
"test:optimism": "yarn test --fork optimism --block-number 105914596 --chain-id 10",
"test:arbitrum": "yarn test --fork arbitrum --block-number 212259071 --chain-id 42161",
Expand All @@ -24,7 +24,7 @@
"test:bsc": "yarn test --fork bsc --block-number 27925272 --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 14589312 --chain-id 8453",
"test:base": "yarn test --fork base --block-number 14845449 --chain-id 8453",
"prepare": "yarn build"
},
"dependencies": {
Expand Down
5 changes: 3 additions & 2 deletions packages/connectors/src/1inch-v5.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import axios, { AxiosError } from 'axios'
import { BigNumber, Contract } from 'ethers'

const ONE_INCH_URL = 'https://api.1inch.io/v5.0'
const ONE_INCH_URL = 'https://api.1inch.dev/swap/v5.2'
const ONE_INCH_API_KEY = process.env.ONE_INCH_API_KEY

export type SwapResponse = { data: { tx: { data: string } } }

Expand Down Expand Up @@ -32,7 +33,7 @@ async function getSwap(
): Promise<SwapResponse> {
return axios.get(`${ONE_INCH_URL}/${chainId}/swap`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${ONE_INCH_API_KEY}`,
Accept: 'application/json',
},
params: {
Expand Down
68 changes: 68 additions & 0 deletions packages/connectors/src/kyberswap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import axios, { AxiosError } from 'axios'
import { BigNumber, Contract } from 'ethers'

const KYBER_SWAP_URL = 'https://aggregator-api.kyberswap.com'
export type SwapResponse = { data: { data: { data: string } } }

const CHAINS: { [key: number]: string } = {
1: 'ethereum',
8453: 'base',
}

export async function getKyberSwapSwapData(
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.data.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 chain = CHAINS[chainId]
const response = await axios.get(`${KYBER_SWAP_URL}/${chain}/api/v1/routes`, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
params: {
tokenIn: tokenIn.address,
tokenOut: tokenOut.address,
amountIn: amountIn.toString(),
saveGas: true,
},
})

// The value is in ranges [0, 2000], 10 means 0.1%
const slippageTolerance = Math.floor(slippage < 1 ? slippage * 10000 : slippage)
return await axios.post(
`${KYBER_SWAP_URL}/${chain}/api/v1/route/build`,
{
routeSummary: response.data.data.routeSummary,
slippageTolerance,
sender: sender.address,
recipient: sender.address,
},
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
}
)
}
16 changes: 0 additions & 16 deletions packages/connectors/test/1inch/OneInchV5Connector.behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,22 +84,6 @@ export function itBehavesLikeOneInchV5Connector(
})

if (WBTC !== ZERO_ADDRESS) {
context('USDC-WBTC', () => {
const amountIn = toUSDC(10e3)

it('swaps correctly USDC-WBTC', async function () {
const previousBalance = await wbtc.balanceOf(this.connector.address)
await usdc.connect(whale).transfer(this.connector.address, amountIn)

const data = await loadOrGet1inchSwapData(CHAIN, this.connector, usdc, wbtc, amountIn, SLIPPAGE)
await this.connector.connect(whale).execute(USDC, WBTC, amountIn, 0, data)

const currentBalance = await wbtc.balanceOf(this.connector.address)
const expectedMinAmountOut = await getExpectedMinAmountOut(USDC, WBTC, amountIn, SLIPPAGE)
expect(currentBalance.sub(previousBalance)).to.be.at.least(expectedMinAmountOut)
})
})

context('WBTC-USDC', () => {
const amountIn = toWBTC(1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('BalancerV2PoolConnector', function () {
})

context('when the min amount out is not enough', () => {
const minAmountOut = fp(1000)
const minAmountOut = fp(2000)

it('reverts', async () => {
await expect(connector.join(poolId, tokenIn, joinAmount, minAmountOut)).to.be.revertedWith('BAL#208')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const CHAINLINK_USDC_ETH = '0x986b5E1e1755e3C2440e960477f25201B0a8bbD4'
const CHAINLINK_WBTC_ETH = '0xdeb288F737066589598e9214E782fa5A8eD689e8'

describe('BalancerV2SwapConnector', () => {
const SLIPPAGE = 0.06
const SLIPPAGE = 0.09
const WETH_USDC_POOL_ID = '0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019'
const WETH_WBTC_POOL_ID = '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e'

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"tokenIn": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"tokenOut": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
"amountIn": "10000000000",
"slippage": 0.015,
"data": "0x12aa3caf000000000000000000000000e37e799d5077682fa0a244d46e5649f71457bd09000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000e37e799d5077682fa0a244d46e5649f71457bd09000000000000000000000000f42ec71a4440f5e9871c643696dd6dc9a38911f800000000000000000000000000000000000000000000000000000002540be4000000000000000000000000000000000000000000000000000000000000ddb859000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002540000000000000000000000000000000000000000000002360002080001be00a007e5c0d200000000000000000000000000000000000000000000019a0000ca0000b051204dece678ceceb27446b35c672dc7d61f30bad69ea0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800443df02124000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000217507ae7cc9d125d3b0020d6bdbf78f939e0a03fb07f59a73314e73794be0e57ac1b4e5122e0438eb3703bf871e31ce639bd351109c88666eaf939e0a03fb07f59a73314e73794be0e57ac1b4e0044a64833a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e37e799d5077682fa0a244d46e5649f71457bd0900a0f2fa6b662260fac5e5542a773aa44fbcfedf7c193bc2c5990000000000000000000000000000000000000000000000000000000000e118b800000000000000000000000000001cac80a06c4eca272260fac5e5542a773aa44fbcfedf7c193bc2c5991111111254eeb25477b68fb85ed929f73a9605820000000000000000000000008bb21a3d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"tokenOut": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"amountIn": "10000000000",
"slippage": 0.015,
"data": "0xe449022e00000000000000000000000000000000000000000000000000000002540be4000000000000000000000000000000000000000000000000004b88924a476a67cd0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640cfee7c08"
"data": "0xe449022e00000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000024405ef5d36b90c10000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56408bb21a3d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amountIn": "100000000",
"slippage": 0.015,
"data": "0xe449022e0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000695097f210000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000099ac8ca7087fa4a2a1fb6357269965a2014abc35cfee7c08"
"data": "0xe449022e0000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000f99780dd4000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000004585fe77225b41b697c938b018e2ac67ac5a20c080000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56408bb21a3d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"amountIn": "1000000000000000000",
"slippage": 0.015,
"data": "0xe449022e0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006a1d73080000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000180000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f5640cfee7c08"
"data": "0xe449022e0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000dd28edaa0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000180000000000000000000000088e6a0c2ddd26feeb64f039a2c41296fcb3f56408bb21a3d"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"tokenOut": "0x4200000000000000000000000000000000000006",
"amountIn": "10000000000",
"data": "0x4dcebcba000000000000000000000000000000000000000000000000000000006647a7a600000000000000000000000034b40ba116d5dec75548a9e9a8f15411461e8c7000000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f5bf7000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000002540be4000000000000000000000000000000000000000000000000002ce1a31db374011a00000000000000000000000034b40ba116d5dec75548a9e9a8f15411461e8c70000000000000000000000000000000000000000000000000000000000000000085543df12d753b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000410503ae0427d1300d5d9092c65def74ef08b144804dd062976cde63a2813ece05729017fa0ccc9ac2b9587a5a347d189aac529cf55b65185a9585e2f4f50b640d1c00000000000000000000000000000000000000000000000000000000000000"
"data": "0x4dcebcba00000000000000000000000000000000000000000000000000000000664f699a000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c00000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f76ba000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000024454cd9c93eef31000000000000000000000000faaddc93baf78e89dcf37ba67943e1be8f37bb8c0000000000000000000000000000000000000000000000000000000000000000311627ff2adae9c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041342f31efd4fa1a26eb099aaefeab0c13627cfb74f5dc0b1fda580d1cdc3df8a2775e6e0db3ea045debbac8bb307602500d90ca0ed6c0f02f39f31262ebf3db661b00000000000000000000000000000000000000000000000000000000000000"
}
Loading

0 comments on commit 1265133

Please sign in to comment.