Skip to content

Commit

Permalink
Merge pull request #145 from rabbitholegg/mmackz-connext-fix
Browse files Browse the repository at this point in the history
feat(connext): add support for ETH transactions on "ANY TOKEN" quests
  • Loading branch information
mmackz authored Dec 27, 2023
2 parents 9bacc66 + ec6af59 commit 5fcc7c8
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 188 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-lamps-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rabbitholegg/questdk-plugin-connext": minor
---

combine filters for xcall and multisend
192 changes: 73 additions & 119 deletions packages/connext/src/Connext.test.ts
Original file line number Diff line number Diff line change
@@ -1,135 +1,89 @@
import { getDeployedMultisendContract } from '@connext/nxtp-txservice'
import { MultisendAbi } from '@connext/nxtp-utils'
import { GreaterThanOrEqual, apply } from '@rabbitholegg/questdk/filter'
import type { TransactionEIP1559 } from 'viem'
import { apply } from '@rabbitholegg/questdk/filter'
import { describe, expect, test } from 'vitest'
import { bridge } from './Connext.js'
import {
bridge,
getSupportedChainIds,
getSupportedTokenAddresses,
} from './Connext.js'
import { MultisendAbi } from '@connext/nxtp-utils'
import { XCALL_ABI_FRAGMENTS } from './abi.js'
import { passingTestCases, failingTestCases } from './test-transactions.js'

describe('Connext', () => {
const ETH = '0x0000000000000000000000000000000000000000'
const OP_WETH = '0x4200000000000000000000000000000000000006'

describe('Bridge', () => {
const USDC = '0x7F5c764cBc14f9669B88837ca1490cCa17c31607'

test('should return a valid bridge action filter', async () => {
const filter = await bridge({
sourceChainId: 10,
destinationChainId: 137,
tokenAddress: USDC,
amount: GreaterThanOrEqual(100000n),
})

expect(filter).to.deep.equal({
chainId: 10,
to: '0x8f7492DE823025b4CfaAB1D34c58963F2af5DEDA',
input: {
$abi: XCALL_ABI_FRAGMENTS,
_destination: 1886350457,
_asset: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
_amount: {
$gte: '100000',
describe('Given the Connext plugin', () => {
describe('When handling the bridge action', () => {
describe('should return a valid action filter', () => {
test('when doing a valid bridge action', async () => {
const { params } = passingTestCases[0]
const filter = await bridge(params)
expect(filter).to.deep.equal({
chainId: 10,
to: {
$or: [
'0x8f7492de823025b4cfaab1d34c58963f2af5deda',
'0xb0eef3e1de973d045c3858e072c540299585252d',
],
},
from: '0xd59a74e615c9d55422ed8c5ce64cb50fda0bb58d',
input: {
$or: [
{
$abi: MultisendAbi,
transactions: {
$regex: 'd59a74e615c9d55422ed8c5ce64cb50fda0bb58d',
},
},
{
$abi: XCALL_ABI_FRAGMENTS,
_destination: 6778479,
_asset: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
_amount: {
$gte: '2000000000000000000',
},
_delegate: '0xd59a74e615c9d55422ed8c5ce64cb50fda0bb58d',
},
],
},
},
})
})
})
})

test('should use the WETH wrapper multisend contract when bridging ETH', async () => {
const filter = await bridge({
sourceChainId: 10,
destinationChainId: 137,
tokenAddress: ETH,
amount: GreaterThanOrEqual(100000n),
})

const multiSendContract = getDeployedMultisendContract(10)

expect(filter).to.deep.equal({
chainId: 10,
to: multiSendContract?.address,
value: {
$gte: '100000',
},
input: {
$abi: MultisendAbi,
transactions: {
$regex: OP_WETH.slice(2),
},
},
describe('should pass filter with valid transactions', () => {
passingTestCases.forEach((testCase) => {
const { transaction, description, params } = testCase
test(description, async () => {
const filter = await bridge(params)
expect(apply(transaction, filter)).to.be.true
})
})
})
})

describe('Apply filter', () => {
test('transaction should pass filter', async () => {
const transaction = {
blockHash:
'0x80ca121924779dc65f575409be05e7c2cbaf718858e6e20d66d130cf1acec4f3',
blockNumber: '0x6606396',
from: '0xd59a74e615c9d55422ed8c5ce64cb50fda0bb58d',
gas: '0x73766',
gasPrice: '0xc9',
maxFeePerGas: '0x17a',
maxPriorityFeePerGas: '0x71',
hash: '0x22d3715ca5ae0bd0d87f9341fafc7a330fd6962e13bf318a6a541c93e4e6bc04',
input:
'0x93f18ac50000000000000000000000000000000000000000000000000000000000676e6f000000000000000000000000642c27a96dffb6f21443a89b789a3194ff8399fa000000000000000000000000da10009cbd5d07dd0cecc66161fc93d7c9000da1000000000000000000000000d59a74e615c9d55422ed8c5ce64cb50fda0bb58d000000000000000000000000000000000000000000000000204d764a78eac238000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000050b91c62117dc0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000d59a74e615c9d55422ed8c5ce64cb50fda0bb58d',
nonce: '0x3',
to: '0x8f7492de823025b4cfaab1d34c58963f2af5deda',
transactionIndex: '0x3',
value: '0x0',
type: '0x2',
accessList: [],
chainId: 10,
v: '0x0',
r: '0x4fcc9247c0b29a50b9db146191e93fbd503be520998f62c9445aa2eeb944d613',
s: '0x78e1ace158ae1166687db900743fc7db36cc80e42a43a800577895c5cb2b95b3',
}

const filter = await bridge({
sourceChainId: 10,
destinationChainId: 100, // xDAI Chain
tokenAddress: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
amount: GreaterThanOrEqual(2000000000000000000n),
describe('should not pass filter with invalid transactions', () => {
failingTestCases.forEach((testCase) => {
const { transaction, description, params } = testCase
test(description, async () => {
const filter = await bridge(params)
expect(apply(transaction, filter)).to.be.false
})
})

expect(apply(transaction, filter)).to.be.true
})

test('ETH bridge should pass filter', async () => {
const transaction: TransactionEIP1559 = {
blockHash:
'0xfdb722e4a99e3422490bc12d15fafab54ebb7e2e83ff08e9fe20d70045e94889',
blockNumber: BigInt('0x67812f1'),
from: '0xa4c8bb4658bc44bac430699c8b7b13dab28e0f4e',
gas: BigInt('0x8820a'),
maxFeePerGas: BigInt('0x11e2'),
maxPriorityFeePerGas: BigInt('0x112d'),
hash: '0xb8e2c0baf137b64553c91f286bde62cc37275d0b9f9d3e6c0041c6be79de45af',
input:
'0x8d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000026b004200000000000000000000000000000000000006000000000000000000000000000000000000000000000000008af8a1fa5fcc180000000000000000000000000000000000000000000000000000000000000004d0e30db000420000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044095ea7b30000000000000000000000008f7492de823025b4cfaab1d34c58963f2af5deda000000000000000000000000000000000000000000000000008af8a1fa5fcc18008f7492de823025b4cfaab1d34c58963f2af5deda0000000000000000000000000000000000000000000000000026aa1a3465338400000000000000000000000000000000000000000000000000000000000001248aac16ba0000000000000000000000000000000000000000000000000000000000657468000000000000000000000000268682b7d9992ae7e2ca4a8bcc9d9655fb06056f0000000000000000000000004200000000000000000000000000000000000006000000000000000000000000a4c8bb4658bc44bac430699c8b7b13dab28e0f4e000000000000000000000000000000000000000000000000008af8a1fa5fcc18000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a4c8bb4658bc44bac430699c8b7b13dab28e0f4e000000000000000000000000000000000000000000',
nonce: 45,
to: '0xb0eef3e1de973d045c3858e072c540299585252d',
transactionIndex: 6,
value: BigInt('0xb1a2bc2ec4ff9c'),
type: 'eip1559',
accessList: [],
typeHex: '0x2',
chainId: 10,
v: BigInt('0x1'),
r: '0x11d6fd962cf4090c1464404492a36ef7323c6173908883c87fbc695219e6d026',
s: '0x739b7240f8dd466881a66971b476f07c2fcce9a6f519d3c06f95133ad983092e',
}

const filter = await bridge({
sourceChainId: 10,
destinationChainId: 137,
tokenAddress: ETH,
amount: GreaterThanOrEqual(1000000000000000n),
describe('should return a valid list of tokens for each supported chain', async () => {
const chainIdArray = await getSupportedChainIds()
chainIdArray.forEach((chainId) => {
test(`for chainId: ${chainId}`, async () => {
const tokens = await getSupportedTokenAddresses(chainId)
const addressRegex = /^0x[a-fA-F0-9]{40}$/
expect(tokens).to.be.an('array')
expect(tokens).to.have.length.greaterThan(0)
expect(tokens).to.have.length.lessThan(100)
tokens.forEach((token) => {
expect(token).to.match(
addressRegex,
`Token address ${token} is not a valid Ethereum address`,
)
})
})
})

expect(apply(transaction, filter)).to.be.true
})
})
})
112 changes: 43 additions & 69 deletions packages/connext/src/Connext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import {
type TransactionFilter,
compressJson,
} from '@rabbitholegg/questdk'
import { type Address } from 'viem'
import { type Address, zeroAddress } from 'viem'
import { XCALL_ABI_FRAGMENTS } from './abi.js'
import { ConnextContract } from './contract-addresses.js'

let _chainDataCache: Map<string, ChainData> | null = null

const ETH_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000'
const ETH_TOKEN_ADDRESS = zeroAddress

const _getChainData = async () => {
if (!_chainDataCache) {
Expand All @@ -28,82 +28,56 @@ const _getChainData = async () => {
return _chainDataCache
}

export const getWETHAddress = async (chainId: number) => {
const chains = await _getChainData()
const domainId = chainIdToDomain(chainId)
const chainData = chains?.get(String(domainId))
const assets = Object.keys(chainData?.assetId || {})

let wethAddress
for (const address of assets) {
if (chainData?.assetId[address].symbol === 'WETH') {
wethAddress = address
break
}
}
return wethAddress
}

export const bridge = async (
bridge: BridgeActionParams,
): Promise<TransactionFilter> => {
const {
sourceChainId,
destinationChainId,
contractAddress,
tokenAddress,
amount,
recipient,
} = bridge

const defaultContractAddress = ConnextContract[sourceChainId]
const destinationDomain = chainIdToDomain(destinationChainId)
const requiresWrapperMultisend = tokenAddress === ETH_TOKEN_ADDRESS

/*
Connext uses a MultiSend to wrap when briding ETH.
https://github.com/connext/monorepo/issues/2905
https://github.com/connext/monorepo/issues/4218
Contract addresses: https://github.com/search?q=repo%3Aconnext%2Fmonorepo%20MultiSend.json&type=code
*/

if (requiresWrapperMultisend) {
const multiSendContract = getDeployedMultisendContract(sourceChainId)

if (!multiSendContract) {
throw new Error(
`No multisend contract deployed on chain ${sourceChainId}`,
)
}

const wethAddress = await getWETHAddress(sourceChainId)

if (!wethAddress) {
throw new Error(`No WETH address found on chain ${sourceChainId}`)
}
const { sourceChainId, destinationChainId, tokenAddress, amount, recipient } =
bridge

const xcallContractAddress = ConnextContract[sourceChainId]
const destinationDomain = destinationChainId
? chainIdToDomain(destinationChainId)
: undefined
const multiSendContractAddress =
getDeployedMultisendContract(sourceChainId)?.address
const ethUsedIn = tokenAddress === ETH_TOKEN_ADDRESS

if (!xcallContractAddress) {
throw new Error(`No xcall contract deployed on chain ${sourceChainId}`)
}

return compressJson({
chainId: sourceChainId,
to: multiSendContract.address,
value: amount,
input: {
$abi: MultisendAbi,
transactions: {
$regex: wethAddress.slice(2),
},
},
})
if (!multiSendContractAddress) {
throw new Error(`No multisend contract deployed on chain ${sourceChainId}`)
}

return compressJson({
chainId: sourceChainId,
to: contractAddress || defaultContractAddress,
to: {
$or: [
xcallContractAddress.toLowerCase(),
multiSendContractAddress.toLowerCase(),
],
},
from: recipient,
value: ethUsedIn ? amount : undefined,
input: {
$abi: XCALL_ABI_FRAGMENTS,
_destination: Number(destinationDomain),
_asset: tokenAddress,
_amount: amount,
_to: recipient,
$or: [
{
$abi: MultisendAbi,
transactions: {
$regex: recipient?.toLowerCase().slice(2),
},
},
{
$abi: XCALL_ABI_FRAGMENTS,
_destination: destinationDomain
? Number(destinationDomain)
: undefined,
_asset: tokenAddress,
_amount: amount,
_delegate: recipient,
},
],
},
})
}
Expand Down
12 changes: 12 additions & 0 deletions packages/connext/src/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Connext Plugin

## Overview
Connext is a cross chain bridge protocol on Ethereum which services many popular L2 chains. The bridge transaction will use one of two method depending on wether ETH or tokens were bridged. For ETH, it will use `multiSend` and for tokens it will use `xCall`

## Limitations
- There should be no limitations with the current bridge plugin. Everything should work as expected.

## Sample Transactions
- [multiSend](https://etherscan.io/tx/0xb8e2c0baf137b64553c91f286bde62cc37275d0b9f9d3e6c0041c6be79de45af)
- [xCall](https://optimistic.etherscan.io/tx/0x22d3715ca5ae0bd0d87f9341fafc7a330fd6962e13bf318a6a541c93e4e6bc04)

Loading

0 comments on commit 5fcc7c8

Please sign in to comment.