Skip to content

Commit

Permalink
Merge pull request #154 from rabbitholegg/mmackz-camelot-add-v3
Browse files Browse the repository at this point in the history
feat(camelot): add support for swaps on camelot V3
  • Loading branch information
mmackz authored Jan 4, 2024
2 parents edc7aef + 8a1233a commit 6cce259
Show file tree
Hide file tree
Showing 9 changed files with 584 additions and 249 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-snakes-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rabbitholegg/questdk-plugin-camelot": minor
---

add support for swaps on camelot V3
2 changes: 2 additions & 0 deletions packages/camelot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ Camelot uses a few different methods to swap, which we will capture with this pl

V2 Mode - This is the native swap of the camelot dapp

V3 Mode - The latest native swap contracts

Aggregator Mode - This routes the swap through either paraswap, or open ocean.
306 changes: 138 additions & 168 deletions packages/camelot/src/Camelot.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { GreaterThanOrEqual, apply } from '@rabbitholegg/questdk/filter'
import { describe, expect, test } from 'vitest'
import {
CAMELOT_ROUTER,
CAMELOT_V2_ROUTER,
CAMELOT_V3_ROUTER,
DEFAULT_TOKEN_LIST,
ETH_ADDRESS,
PARASWAP_ROUTER,
} from './contract-addresses'
import {
CAMELOT_V2_ABI,
CAMELOT_V3_EXACT_INPUT_ABI,
CAMELOT_V3_EXACT_OUTPUT_ABI,
PARASWAP_ABI,
} from './abi'
import { ARBITRUM_CHAIN_ID } from './chain-ids'
import { parseEther, getAddress } from 'viem'
import { swap } from './Camelot'
import { Tokens } from './utils'
import { CAMELOT_ABI, PARASWAP_ABI } from './abi'
import { failingTestCases, passingTestCases } from './test-setup'

describe('Given the camelot plugin', () => {
describe('should return a valid action filter', () => {
test('for a swap using ERC-20 token as tokenIn', async () => {
const filter = await swap({
chainId: ARBITRUM_CHAIN_ID,
contractAddress: CAMELOT_ROUTER,
tokenIn: Tokens.USDT,
tokenOut: Tokens.WETH,
amountIn: GreaterThanOrEqual(1000000n),
Expand All @@ -28,12 +32,16 @@ describe('Given the camelot plugin', () => {
expect(filter).to.deep.equal({
chainId: 42161,
to: {
$or: [CAMELOT_ROUTER.toLowerCase(), PARASWAP_ROUTER.toLowerCase()],
$or: [
CAMELOT_V2_ROUTER.toLowerCase(),
CAMELOT_V3_ROUTER.toLowerCase(),
PARASWAP_ROUTER.toLowerCase(),
],
},
input: {
$abi: [...CAMELOT_ABI, ...PARASWAP_ABI],
$or: [
{
$abi: CAMELOT_V2_ABI,
path: {
$and: [
{
Expand All @@ -52,181 +60,154 @@ describe('Given the camelot plugin', () => {
},
},
{
data: {
fromToken: Tokens.USDT,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
toToken: Tokens.WETH,
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
{
data: {
fromToken: Tokens.USDT,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
$or: [
{
$abiAbstract: CAMELOT_V3_EXACT_OUTPUT_ABI,
params: {
$or: [
{
tokenIn: Tokens.USDT,
tokenOut: Tokens.WETH,
},
{
path: {
$and: [
{
$regex:
'^0x82af49447d8a07e3bd95bd0d56f35241523fbab1',
},
{
$regex:
'fd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9$',
},
],
},
},
],
amountInMaximum: {
$gte: '1000000',
},
amountOut: {
$gte: '500000000000000',
},
},
},
path: {
$last: {
to: Tokens.WETH,
{
$abiAbstract: CAMELOT_V3_EXACT_INPUT_ABI,
params: {
$or: [
{
tokenIn: Tokens.USDT,
tokenOut: Tokens.WETH,
},
{
path: {
$and: [
{
$regex:
'^0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9',
},
{
$regex:
'82af49447d8a07e3bd95bd0d56f35241523fbab1$',
},
],
},
},
],
amountIn: {
$gte: '1000000',
},
amountOutMinimum: {
$gte: '500000000000000',
},
},
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
],
},
{
data: {
fromToken: Tokens.USDT,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
$abi: PARASWAP_ABI,
$or: [
{
data: {
fromToken: Tokens.USDT,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
toToken: Tokens.WETH,
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
path: {
$last: {
{
data: {
fromToken: Tokens.USDT,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
path: {
$last: {
to: Tokens.WETH,
},
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
{
data: {
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
assets: {
$and: [{ $first: Tokens.USDT }, { $last: Tokens.WETH }],
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
],
},
})
})
test('for a swap using ETH as tokenIn', async () => {
const filter = await swap({
chainId: ARBITRUM_CHAIN_ID,
contractAddress: CAMELOT_ROUTER,
tokenIn: Tokens.ETH,
tokenOut: Tokens.USDT,
amountIn: GreaterThanOrEqual(1000000n),
amountOut: GreaterThanOrEqual(parseEther('0.0005')),
recipient: '0x67ef327038b25ff762a0606bc92c4a0a6e767048',
})
expect(filter).to.deep.equal({
chainId: 42161,
to: {
$or: [CAMELOT_ROUTER.toLowerCase(), PARASWAP_ROUTER.toLowerCase()],
},
value: {
$gte: '1000000',
},
input: {
$abi: [...CAMELOT_ABI, ...PARASWAP_ABI],
$or: [
{
to: '0x67ef327038b25ff762a0606bc92c4a0a6e767048',
path: {
$and: [
{
$first: Tokens.WETH,
},
{
$last: Tokens.USDT,
},
],
},
amountOutMin: {
$gte: '500000000000000',
},
},
{
data: {
fromToken: ETH_ADDRESS,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
toToken: Tokens.USDT,
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
{
data: {
fromToken: ETH_ADDRESS,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
path: {
$last: {
to: Tokens.USDT,
},
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
{
data: {
fromToken: ETH_ADDRESS,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
path: {
$last: {
{
data: {
fromToken: Tokens.USDT,
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
path: {
$last: {
to: Tokens.USDT,
path: {
$last: {
to: Tokens.WETH,
},
},
},
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
{
data: {
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
assets: {
$and: [{ $first: Tokens.ETH }, { $last: Tokens.USDT }],
{
data: {
assets: {
$and: [
{
$first: Tokens.USDT,
},
{
$last: Tokens.WETH,
},
],
},
fromAmount: {
$gte: '1000000',
},
toAmount: {
$gte: '500000000000000',
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
},
partner: '0x353D2d14Bb674892910685520Ac040f560CcBC06',
},
],
},
],
},
})
})
})

describe('should pass filter when all parameters are valid', () => {
passingTestCases.forEach((testCase) => {
const { transaction, params, description } = testCase
Expand All @@ -236,6 +217,7 @@ describe('Given the camelot plugin', () => {
})
})
})

describe('should not pass filter when parameters are invalid', () => {
failingTestCases.forEach((testCase) => {
const { transaction, params, description } = testCase
Expand All @@ -244,20 +226,8 @@ describe('Given the camelot plugin', () => {
expect(apply(transaction, filter)).to.be.false
})
})
test('should throw error when contract address is incorrect', async () => {
try {
const { transaction, params } = passingTestCases[0]
params.contractAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564'
const filter = await swap({ ...params })
apply(transaction, filter)
throw new Error('Expected bridge function to throw, but it did not.')
} catch (err) {
if (err instanceof Error) {
expect(err.message).toBe('Invalid Contract Address')
}
}
})
})

describe('all supported tokens addresses are properly checksummed', () => {
test('should have all addresses properly checksummed', () => {
const notChecksummed = DEFAULT_TOKEN_LIST.filter(
Expand Down
Loading

0 comments on commit 6cce259

Please sign in to comment.