-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Connectors: Implement Bebop swap connector (#150)
- Loading branch information
Showing
18 changed files
with
415 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// 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/utils/Address.sol'; | ||
|
||
import '@mimic-fi/v3-helpers/contracts/utils/ERC20Helpers.sol'; | ||
|
||
import '../interfaces/bebop/IBebopConnector.sol'; | ||
|
||
/** | ||
* @title BebopConnector | ||
* @dev Interfaces with Bebop to swap tokens | ||
*/ | ||
contract BebopConnector is IBebopConnector { | ||
// Reference to Bebop Settlement contract | ||
address public immutable override bebopSettlement; | ||
|
||
/** | ||
* @dev Creates a new BebopConnector contract | ||
* @param _bebopSettlement Address of Bebop Settlement contract | ||
*/ | ||
constructor(address _bebopSettlement) { | ||
bebopSettlement = _bebopSettlement; | ||
} | ||
|
||
/** | ||
* @dev Executes a token swap using Bebop | ||
* @param tokenIn Token being sent | ||
* @param tokenOut Token being received | ||
* @param amountIn Amount of tokenIn being swapped | ||
* @param minAmountOut Minimum amount of tokenOut willing to receive | ||
* @param data Calldata to be sent to the Bebop Settlement contract | ||
*/ | ||
function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes memory data) | ||
external | ||
returns (uint256 amountOut) | ||
{ | ||
if (tokenIn == tokenOut) revert BebopSwapSameToken(tokenIn); | ||
|
||
uint256 preBalanceIn = IERC20(tokenIn).balanceOf(address(this)); | ||
uint256 preBalanceOut = IERC20(tokenOut).balanceOf(address(this)); | ||
|
||
ERC20Helpers.approve(tokenIn, bebopSettlement, amountIn); | ||
Address.functionCall(bebopSettlement, data, 'BEBOP_SWAP_FAILED'); | ||
|
||
uint256 postBalanceIn = IERC20(tokenIn).balanceOf(address(this)); | ||
bool isPostBalanceInUnexpected = postBalanceIn < preBalanceIn - amountIn; | ||
if (isPostBalanceInUnexpected) revert BebopBadPostTokenInBalance(postBalanceIn, preBalanceIn, amountIn); | ||
|
||
uint256 postBalanceOut = IERC20(tokenOut).balanceOf(address(this)); | ||
amountOut = postBalanceOut - preBalanceOut; | ||
if (amountOut < minAmountOut) revert BebopBadAmountOut(amountOut, minAmountOut); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
packages/connectors/contracts/interfaces/bebop/IBebopConnector.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 Bebop connector interface | ||
*/ | ||
interface IBebopConnector { | ||
/** | ||
* @dev The token in is the same as the token out | ||
*/ | ||
error BebopSwapSameToken(address token); | ||
|
||
/** | ||
* @dev The amount out is lower than the minimum amount out | ||
*/ | ||
error BebopBadAmountOut(uint256 amountOut, uint256 minAmountOut); | ||
|
||
/** | ||
* @dev The post token in balance is lower than the previous token in balance minus the amount in | ||
*/ | ||
error BebopBadPostTokenInBalance(uint256 postBalanceIn, uint256 preBalanceIn, uint256 amountIn); | ||
|
||
/** | ||
* @dev Tells the reference to Bebop Settlement contract | ||
*/ | ||
function bebopSettlement() external view returns (address); | ||
|
||
/** | ||
* @dev Executes a token swap using Bebop | ||
* @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 Bebop Settlement contract | ||
*/ | ||
function execute(address tokenIn, address tokenOut, uint256 amountIn, uint256 minAmountOut, bytes memory data) | ||
external | ||
returns (uint256 amountOut); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import axios, { AxiosError } from 'axios' | ||
import { BigNumber, Contract } from 'ethers' | ||
|
||
const BEBOP_URL = 'https://api.bebop.xyz/pmm' | ||
|
||
const CHAIN_NAMES = { | ||
42161: 'arbitrum', | ||
8453: 'base', | ||
} | ||
|
||
export type SwapResponse = { data: { tx: { data: string } } } | ||
|
||
export async function getBebopSwapData( | ||
chainId: number, | ||
sender: Contract, | ||
tokenIn: Contract, | ||
tokenOut: Contract, | ||
amountIn: BigNumber | ||
): Promise<string> { | ||
try { | ||
const response = await getSwap(chainId, sender, tokenIn, tokenOut, amountIn) | ||
return response.data.tx.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 | ||
): Promise<SwapResponse> { | ||
const chainName = CHAIN_NAMES[chainId] | ||
if (!chainName) throw Error('Unsupported chain id') | ||
|
||
return axios.get(`${BEBOP_URL}/${chainName}/v3/quote`, { | ||
headers: { | ||
'Content-Type': 'application/json', | ||
Accept: 'application/json', | ||
}, | ||
params: { | ||
taker_address: sender.address, | ||
sell_tokens: tokenIn.address, | ||
buy_tokens: tokenOut.address, | ||
sell_amounts: amountIn.toString(), | ||
gasless: false, | ||
skip_validation: true, | ||
}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { deploy } from '@mimic-fi/v3-helpers' | ||
|
||
import { itBehavesLikeBebopConnector } from './BebopConnector.behavior' | ||
|
||
/* eslint-disable no-secrets/no-secrets */ | ||
|
||
const CHAIN = 42161 | ||
|
||
const USDC = '0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8' | ||
const WETH = '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' | ||
const WHALE = '0xEeBe760354F5dcBa195EDe0a3B93901441D0968F' | ||
|
||
const BEBOP_SETTLEMENT = '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F' | ||
|
||
const CHAINLINK_ETH_USD = '0x639fe6ab55c921f74e7fac1ee960c0b6293ba612' | ||
|
||
describe('BebopConnector', () => { | ||
const SLIPPAGE = 0.015 | ||
|
||
before('create bebop connector', async function () { | ||
this.connector = await deploy('BebopConnector', [BEBOP_SETTLEMENT]) | ||
}) | ||
|
||
itBehavesLikeBebopConnector(CHAIN, USDC, WETH, WHALE, SLIPPAGE, CHAINLINK_ETH_USD) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { deploy } from '@mimic-fi/v3-helpers' | ||
|
||
import { itBehavesLikeBebopConnector } from './BebopConnector.behavior' | ||
|
||
/* eslint-disable no-secrets/no-secrets */ | ||
|
||
const CHAIN = 8453 | ||
|
||
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' | ||
const WETH = '0x4200000000000000000000000000000000000006' | ||
const WHALE = '0xec8d8D4b215727f3476FF0ab41c406FA99b4272C' | ||
|
||
const BEBOP_SETTLEMENT = '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F' | ||
|
||
const CHAINLINK_ETH_USD = '0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70' | ||
|
||
describe('BebopConnector', () => { | ||
const SLIPPAGE = 0.015 | ||
|
||
before('create bebop connector', async function () { | ||
this.connector = await deploy('BebopConnector', [BEBOP_SETTLEMENT]) | ||
}) | ||
|
||
itBehavesLikeBebopConnector(CHAIN, USDC, WETH, WHALE, SLIPPAGE, CHAINLINK_ETH_USD) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
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 { loadOrGetBebopSwapData } from '../helpers/bebop' | ||
|
||
export function itBehavesLikeBebopConnector( | ||
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<BigNumber> => { | ||
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(10e3) | ||
|
||
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 minAmountOut = 0 | ||
const data = await loadOrGetBebopSwapData(CHAIN, this.connector, usdc, weth, amountIn) | ||
await this.connector.connect(whale).execute(USDC, WETH, amountIn, minAmountOut, 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 minAmountOut = 0 | ||
const data = await loadOrGetBebopSwapData(CHAIN, this.connector, weth, usdc, amountIn) | ||
await this.connector.connect(whale).execute(WETH, USDC, amountIn, minAmountOut, 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) | ||
}) | ||
}) | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/connectors/test/helpers/bebop/fixtures/42161/212259071/USDC-WETH.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"tokenIn": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", | ||
"tokenOut": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", | ||
"amountIn": "10000000000", | ||
"data": "0x4dcebcba000000000000000000000000000000000000000000000000000000006647a4370000000000000000000000004f0a4ce902616f0e16a7c31075aa5601779ad40500000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f4f3b000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc800000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000000000000000000000000000000000002540be4000000000000000000000000000000000000000000000000002d0f5f4319e062100000000000000000000000004f0a4ce902616f0e16a7c31075aa5601779ad40500000000000000000000000000000000000000000000000000000000000000007409e45e4bca4e8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000041791c1ef4b257ba835057f84321c9e9804a94a1352b8a3961ed1d656fdab3d77f0f8ba4428da58c1c91fcd28472f6e8fce0ba10f9068f1d727e62ce1dbb405cc11b00000000000000000000000000000000000000000000000000000000000000" | ||
} |
6 changes: 6 additions & 0 deletions
6
packages/connectors/test/helpers/bebop/fixtures/42161/212259071/WETH-USDC.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"tokenIn": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", | ||
"tokenOut": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", | ||
"amountIn": "1000000000000000000", | ||
"data": "0x4dcebcba000000000000000000000000000000000000000000000000000000006647a4390000000000000000000000004f0a4ce902616f0e16a7c31075aa5601779ad40500000000000000000000000051c72848c68a965f66fa7a88855f9f7784502a7f0000000000000000000000000000000000000000000000000000031ef93f4f3c00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000ff970a61a04b1ca14834a43f5de4533ebddb5cc80000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000b7761b950000000000000000000000004f0a4ce902616f0e16a7c31075aa5601779ad40500000000000000000000000000000000000000000000000000000000000000004087748dff5aa5e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000418ad1282832998747d3a68672463c5855f61e09a2485d7505c42d2fddaa998a39077cdf67c41cc05c97b49602b9c2412f0cfd8d94c3ff2869b9d146ea9843d9691b00000000000000000000000000000000000000000000000000000000000000" | ||
} |
Oops, something went wrong.