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

test: Add claim step for CCTP #1877

Merged
merged 22 commits into from
Sep 24, 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
1 change: 1 addition & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ env:
jobs:
load-e2e-files:
name: "Load e2e files"
if: inputs.test_type.type != 'cctp'
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.e2eFiles }}
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/formatSpecfiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ switch (testType) {
break;
}
case "cctp": {
cctpFiles.forEach((spec) => {
tests.push({
...spec,
type: 'cctp',
})
})
// Running CCTP tests in parallel cause nonce issues, we're running the two tests sequentially
tests.push({
name: "cctp",
file: "tests/e2e/specs/**/*Cctp.cy.{js,jsx,ts,tsx}",
recordVideo: false,
type: "cctp",
});
break;
}
}
Expand Down
179 changes: 132 additions & 47 deletions packages/arb-token-bridge-ui/synpress.cctp.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigNumber, Wallet, utils } from 'ethers'
import { BigNumber, Contract, Wallet, utils } from 'ethers'
import { defineConfig } from 'cypress'
import { Provider, StaticJsonRpcProvider } from '@ethersproject/providers'
import synpressPlugins from '@synthetixio/synpress/plugins'
Expand All @@ -13,6 +13,9 @@ import {
import specFiles from './tests/e2e/cctp.json'
import { CommonAddress } from './src/util/CommonAddressUtils'
import { ERC20__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory'
import { TokenMessengerAbi } from './src/util/cctp/TokenMessengerAbi'
import { ChainDomain } from './src/pages/api/cctp/[type]'
import { Address } from 'wagmi'

export async function fundUsdc({
address, // wallet address where funding is required
Expand Down Expand Up @@ -42,9 +45,11 @@ export async function fundUsdc({

const shouldRecordVideo = process.env.CYPRESS_RECORD_VIDEO === 'true'

const tests = process.env.TEST_FILE
? [process.env.TEST_FILE]
: specFiles.map(file => file.file)
const tests =
process.env.TEST_FILE &&
specFiles.find(file => file.name === process.env.TEST_FILE)
? [process.env.TEST_FILE]
: specFiles.map(file => file.file)

const INFURA_KEY = process.env.NEXT_PUBLIC_INFURA_KEY
if (typeof INFURA_KEY === 'undefined') {
Expand Down Expand Up @@ -76,40 +81,44 @@ async function fundWallets() {
const userWalletAddress = userWallet.address
console.log(`Funding wallet ${userWalletAddress}`)

const fundEthHelper = (network: 'sepolia' | 'arbSepolia') => {
return () =>
fundEth({
address: userWalletAddress,
sourceWallet: localWallet,
...(network === 'sepolia'
? {
provider: sepoliaProvider,
amount: ethAmountSepolia,
networkType: 'parentChain'
}
: {
provider: arbSepoliaProvider,
amount: ethAmountArbSepolia,
networkType: 'childChain'
})
})
const fundEthHelper = (
network: 'sepolia' | 'arbSepolia',
amount: BigNumber
) => {
return fundEth({
address: userWalletAddress,
sourceWallet: localWallet,
...(network === 'sepolia'
? {
provider: sepoliaProvider,
amount,
networkType: 'parentChain'
}
: {
provider: arbSepoliaProvider,
amount,
networkType: 'childChain'
})
})
}
const fundUsdcHelper = (network: 'sepolia' | 'arbSepolia') => {
return () =>
fundUsdc({
address: userWalletAddress,
sourceWallet: localWallet,
amount: usdcAmount,
...(network === 'sepolia'
? {
provider: sepoliaProvider,
networkType: 'parentChain'
}
: {
provider: arbSepoliaProvider,
networkType: 'childChain'
})
})
const fundUsdcHelper = (
network: 'sepolia' | 'arbSepolia',
amount: BigNumber = usdcAmount
) => {
return fundUsdc({
address: userWalletAddress,
sourceWallet: localWallet,
amount,
...(network === 'sepolia'
? {
provider: sepoliaProvider,
networkType: 'parentChain'
}
: {
provider: arbSepoliaProvider,
networkType: 'childChain'
})
})
}

/**
Expand All @@ -119,21 +128,74 @@ async function fundWallets() {
const usdcAmount = utils.parseUnits('0.00063', 6)
const ethAmountSepolia = utils.parseEther('0.025')
const ethAmountArbSepolia = utils.parseEther('0.006')
const ethPromises: (() => Promise<void>)[] = []
const usdcPromises: (() => Promise<void>)[] = []

if (tests.some(testFile => testFile.includes('deposit'))) {
ethPromises.push(fundEthHelper('sepolia'))
usdcPromises.push(fundUsdcHelper('sepolia'))
// Add ETH and USDC on ArbSepolia, to generate tx on ArbSepolia
await Promise.all([
fundEthHelper('sepolia', ethAmountSepolia),
fundEthHelper('arbSepolia', utils.parseEther('0.01'))
])
await Promise.all([
fundUsdcHelper('sepolia'),
fundUsdcHelper('arbSepolia', utils.parseUnits('0.00029', 6))
])
}

if (tests.some(testFile => testFile.includes('withdraw'))) {
ethPromises.push(fundEthHelper('arbSepolia'))
usdcPromises.push(fundUsdcHelper('arbSepolia'))
// Add ETH and USDC on Sepolia, to generate tx on Sepolia
await Promise.all([
fundEthHelper('arbSepolia', ethAmountArbSepolia),
fundEthHelper('sepolia', utils.parseEther('0.01'))
])
await Promise.all([
fundUsdcHelper('arbSepolia'),
fundUsdcHelper('sepolia', utils.parseUnits('0.00025', 6))
])
}
}

async function createCctpTx(
type: 'deposit' | 'withdrawal',
destinationAddress: Address,
amount: string
) {
console.log(`Creating CCTP transaction for ${destinationAddress}`)
const provider = type === 'deposit' ? sepoliaProvider : arbSepoliaProvider
const usdcAddress =
type === 'deposit'
? CommonAddress.Sepolia.USDC
: CommonAddress.ArbitrumSepolia.USDC
const tokenMessengerContractAddress =
type === 'deposit'
? CommonAddress.Sepolia.tokenMessengerContractAddress
: CommonAddress.ArbitrumSepolia.tokenMessengerContractAddress

const signer = userWallet.connect(provider)
const usdcContract = ERC20__factory.connect(usdcAddress, signer)

const tx = await usdcContract.functions.approve(
tokenMessengerContractAddress,
utils.parseUnits(amount, 6)
)

await tx.wait()

const tokenMessenger = new Contract(
tokenMessengerContractAddress,
TokenMessengerAbi,
signer
)

await tokenMessenger.deployed()

const depositForBurnTx = await tokenMessenger.functions.depositForBurn(
utils.parseUnits(amount, 6),
type === 'deposit' ? ChainDomain.ArbitrumOne : ChainDomain.Ethereum,
utils.hexlify(utils.zeroPad(destinationAddress, 32)),
usdcAddress
)

await Promise.all(ethPromises.map(fn => fn()))
await Promise.all(usdcPromises.map(fn => fn()))
await depositForBurnTx.wait()
}

export default defineConfig({
Expand All @@ -144,12 +206,35 @@ export default defineConfig({

await fundWallets()

const customAddress = await getCustomDestinationAddress()
config.env.PRIVATE_KEY = userWallet.privateKey
config.env.PRIVATE_KEY_CCTP = process.env.PRIVATE_KEY_CCTP
config.env.SEPOLIA_INFURA_RPC_URL = sepoliaRpcUrl
config.env.ARB_SEPOLIA_INFURA_RPC_URL = arbSepoliaRpcUrl
config.env.CUSTOM_DESTINATION_ADDRESS =
await getCustomDestinationAddress()
config.env.CUSTOM_DESTINATION_ADDRESS = customAddress

/**
* Currently, we can't confirm transaction on Sepolia, we need to programmatically deposit on Sepolia
* And claim on ArbSepolia
*
* - Create one deposit transaction, claimed in withdrawCctp
* - Create one deposit transaction to custom address, claimed in withdrawCctp
* - Create one withdraw transaction, rejected in depositCctp
* - Create one withdraw transaction to custom address, rejected in depositCctp
*/
if (tests.some(testFile => testFile.includes('deposit'))) {
await createCctpTx(
'withdrawal',
userWallet.address as Address,
'0.00014'
)
await createCctpTx('withdrawal', customAddress as Address, '0.00015')
}

if (tests.some(testFile => testFile.includes('withdraw'))) {
await createCctpTx('deposit', userWallet.address as Address, '0.00012')
await createCctpTx('deposit', customAddress as Address, '0.00013')
}

setupCypressTasks(on, { requiresNetworkSetup: false })
synpressPlugins(on, config)
Expand Down
4 changes: 3 additions & 1 deletion packages/arb-token-bridge-ui/tests/e2e/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
findClaimButton,
selectTransactionsPanelTab,
confirmSpending,
closeTransactionHistoryPanel
closeTransactionHistoryPanel,
claimCctp
} from '../support/commands'
import { NetworkType, NetworkName } from '../support/common'

Expand Down Expand Up @@ -71,6 +72,7 @@ declare global {
findTransactionInTransactionHistory: typeof findTransactionInTransactionHistory
findClaimButton: typeof findClaimButton
confirmSpending: typeof confirmSpending
claimCctp: typeof claimCctp
}
}
}
11 changes: 11 additions & 0 deletions packages/arb-token-bridge-ui/tests/e2e/specs/depositCctp.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { zeroToLessThanOneETH } from '../../support/common'
import { CommonAddress } from '../../../src/util/CommonAddressUtils'
import { formatAmount } from 'packages/arb-token-bridge-ui/src/util/NumberUtils'

// common function for this cctp deposit
const confirmAndApproveCctpDeposit = () => {
Expand Down Expand Up @@ -104,6 +105,16 @@ describe('Deposit USDC through CCTP', () => {
// timeout: 60_000
// }
// })

// We have setup deposit transactions before running tests
cy.wait(40_000)
cy.rejectMetamaskTransaction()
})

it('should claim deposit', () => {
cy.claimCctp(0.00014, { accept: false })
cy.closeTransactionHistoryPanel()
cy.claimCctp(0.00015, { accept: false })
})

/**
Expand Down
28 changes: 27 additions & 1 deletion packages/arb-token-bridge-ui/tests/e2e/specs/withdrawCctp.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { CommonAddress } from 'packages/arb-token-bridge-ui/src/util/CommonAddressUtils'
import { formatAmount } from 'packages/arb-token-bridge-ui/src/util/NumberUtils'

// common function for this cctp withdrawal
export const confirmAndApproveCctpWithdrawal = () => {
Expand Down Expand Up @@ -77,11 +78,27 @@ describe('Withdraw USDC through CCTP', () => {
cy.confirmSpending(USDCAmountToSend.toString())
// eslint-disable-next-line
cy.wait(40_000)
cy.confirmMetamaskTransaction(undefined)
cy.confirmMetamaskTransaction({ gasConfig: 'aggressive' })
cy.findTransactionInTransactionHistory({
amount: USDCAmountToSend,
symbol: 'USDC'
})
cy.findClaimButton(
formatAmount(USDCAmountToSend, {
symbol: 'USDC'
}),
{ timeout: 120_000 }
).click()
cy.allowMetamaskToSwitchNetwork()
cy.rejectMetamaskTransaction()
cy.changeMetamaskNetwork('arbitrum-sepolia')
})

it('should claim deposit', () => {
cy.changeMetamaskNetwork('sepolia')
cy.claimCctp(0.00012, { accept: true })
cy.closeTransactionHistoryPanel()
cy.claimCctp(0.00013, { accept: true })
})

it('should initiate withdrawing USDC to custom destination address through CCTP successfully', () => {
Expand Down Expand Up @@ -113,5 +130,14 @@ describe('Withdraw USDC through CCTP', () => {
cy.findTransactionDetailsCustomDestinationAddress(
Cypress.env('CUSTOM_DESTINATION_ADDRESS')
)
cy.closeTransactionDetails()
cy.findClaimButton(
formatAmount(USDCAmountToSend, {
symbol: 'USDC'
}),
{ timeout: 120_000 }
).click()
cy.allowMetamaskToSwitchNetwork()
cy.rejectMetamaskTransaction()
})
})
Loading
Loading