From 76d7e58c360364d60376503a38ab73e7f59566af Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 15 Apr 2025 11:29:14 +0200 Subject: [PATCH 01/25] rebased --- .../TransferPanel/TransferPanel.tsx | 72 +++++++++---------- .../src/ui-driver/UiDriver.ts | 16 ++++- .../src/ui-driver/UiDriverCctp.ts | 24 ++++++- .../src/ui-driver/UiDriverCommon.ts | 24 +++++++ 4 files changed, 96 insertions(+), 40 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index fe26a31b7b..eccd904473 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -369,6 +369,33 @@ export function TransferPanel() { case 'dialog': { return confirmDialog(step.payload) } + + case 'scw_tooltip': { + showDelayedSmartContractTxRequest() + return + } + + case 'tx': { + try { + const tx = await signer!.sendTransaction(step.payload) + const txReceipt = await tx.wait() + + return txReceipt + } catch (error) { + if (isUserRejectedError(error)) { + return + } + + captureSentryErrorWithExtraData({ + error, + originFunction: 'cctpTransferStarter.approveToken' + }) + + errorToast(`${(error as Error)?.message ?? error}`) + } + + return + } } } @@ -376,6 +403,9 @@ export function TransferPanel() { if (!selectedToken) { return } + if (!walletAddress) { + throw new Error(`walletAddress is undefined`) + } if (!signer) { throw new Error(signerUndefinedError) } @@ -390,10 +420,13 @@ export function TransferPanel() { networks const returnEarly = await drive(stepGeneratorForCctp, stepExecutor, { + amountBigNumber, isDepositMode, isSmartContractWallet, walletAddress, - destinationAddress + destinationAddress, + sourceChainProvider, + destinationChainProvider }) // this is only necessary while we are migrating to the ui driver @@ -409,43 +442,6 @@ export function TransferPanel() { destinationChainProvider }) - const isTokenApprovalRequired = - await cctpTransferStarter.requiresTokenApproval({ - amount: amountBigNumber, - owner: await signer.getAddress() - }) - - if (isTokenApprovalRequired) { - const userConfirmation = await confirmDialog('approve_token') - if (!userConfirmation) return false - - if (isSmartContractWallet) { - showDelayedSmartContractTxRequest() - } - try { - const tx = await cctpTransferStarter.approveToken({ - signer, - amount: amountBigNumber - }) - - await tx.wait() - } catch (error) { - if (isUserRejectedError(error)) { - return - } - captureSentryErrorWithExtraData({ - error, - originFunction: 'cctpTransferStarter.approveToken' - }) - errorToast( - `USDC approval transaction failed: ${ - (error as Error)?.message ?? error - }` - ) - return - } - } - let depositForBurnTx try { diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts index 69f86b9d2c..d612e12781 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -1,3 +1,5 @@ +import { BigNumber, providers } from 'ethers' + import { DialogType } from '../components/common/Dialog2' export type Dialog = Extract< @@ -5,19 +7,25 @@ export type Dialog = Extract< | 'confirm_cctp_deposit' | 'confirm_cctp_withdrawal' | 'scw_custom_destination_address' + | 'approve_token' > export type UiDriverContext = { + amountBigNumber: BigNumber isDepositMode: boolean isSmartContractWallet: boolean - walletAddress?: string + walletAddress: string destinationAddress?: string + sourceChainProvider: providers.Provider + destinationChainProvider: providers.Provider } export type UiDriverStep = | { type: 'start' } // | { type: 'return' } | { type: 'dialog'; payload: Dialog } + | { type: 'scw_tooltip' } + | { type: 'tx'; payload: providers.TransactionRequest } export type UiDriverStepResultFor = // TStep extends { type: 'start' } @@ -28,6 +36,12 @@ export type UiDriverStepResultFor = // : // TStep extends { type: 'dialog' } ? boolean + : // + TStep extends { type: 'scw_tooltip' } + ? void + : // + TStep extends { type: 'tx' } + ? providers.TransactionReceipt : // never diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts index 885470e4b0..ec8fde0da8 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts @@ -1,7 +1,10 @@ +import { CctpTransferStarter } from '@/token-bridge-sdk/CctpTransferStarter' + import { step, UiDriverStepGenerator } from './UiDriver' import { stepGeneratorForDialog, - stepGeneratorForSmartContractWalletDestinationDialog + stepGeneratorForSmartContractWalletDestinationDialog, + stepGeneratorForTransaction } from './UiDriverCommon' export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( @@ -13,4 +16,23 @@ export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( yield* step({ type: 'start' }) yield* stepGeneratorForDialog(dialog) yield* stepGeneratorForSmartContractWalletDestinationDialog(context) + + const cctpTransferStarter = new CctpTransferStarter({ + sourceChainProvider: context.sourceChainProvider, + destinationChainProvider: context.destinationChainProvider + }) + + const approval = await cctpTransferStarter.requiresTokenApproval({ + amount: context.amountBigNumber, + owner: context.walletAddress + }) + + if (approval) { + const txRequest = await cctpTransferStarter.approveTokenPrepareTxRequest({ + amount: context.amountBigNumber + }) + + yield* stepGeneratorForDialog('approve_token') + yield* stepGeneratorForTransaction(context, txRequest) + } } diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts index ef7e5dd1b1..e8301f4784 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts @@ -1,8 +1,11 @@ +import { providers } from 'ethers' + import { step, UiDriverStep, UiDriverStepResultFor, UiDriverStepGenerator, + UiDriverContext, Dialog } from './UiDriver' import { addressesEqual } from '../util/AddressUtils' @@ -31,3 +34,24 @@ export const stepGeneratorForSmartContractWalletDestinationDialog: UiDriverStepG yield* stepGeneratorForDialog('scw_custom_destination_address') } } + +export type UiDriverStepGeneratorForTransaction< + TStep extends UiDriverStep = UiDriverStep +> = ( + context: UiDriverContext, + txRequest: providers.TransactionRequest +) => AsyncGenerator< + TStep, + providers.TransactionReceipt, + UiDriverStepResultFor +> + +export const stepGeneratorForTransaction: UiDriverStepGeneratorForTransaction = + async function* (context, txRequest) { + if (context.isSmartContractWallet) { + yield* step({ type: 'scw_tooltip' }) + } + + // return the tx receipt + return yield* step({ type: 'tx', payload: txRequest }) + } From 3dcef6932f78f4155606cb5e230f13c4a7352766 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 15 Apr 2025 11:40:59 +0200 Subject: [PATCH 02/25] lil cleanup --- .../src/token-bridge-sdk/BridgeTransferStarter.ts | 15 ++++++++++++++- .../src/token-bridge-sdk/CctpTransferStarter.ts | 9 +++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts index 542285b679..b29302916e 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts @@ -1,4 +1,4 @@ -import { Provider } from '@ethersproject/providers' +import { Provider, TransactionRequest } from '@ethersproject/providers' import { BigNumber, ContractTransaction, Signer } from 'ethers' import { MergedTransaction } from '../state/app/state' import { @@ -96,6 +96,10 @@ export type RequiresTokenApprovalProps = { destinationAddress?: string } +export type ApproveTokenPrepareTxRequestProps = { + amount?: BigNumber +} + export type ApproveTokenProps = { signer: Signer amount?: BigNumber @@ -144,6 +148,15 @@ export abstract class BridgeTransferStarter { props: RequiresTokenApprovalProps ): Promise + // not marking this as abstract for now, as we need a dummy implementation for every class + // only cctp is going to override it for now, and we'll do the same for others one by one + // finally, once we have all implementations we'll mark it as abstract + public async approveTokenPrepareTxRequest( + props?: ApproveTokenPrepareTxRequestProps + ): Promise { + return {} as TransactionRequest + } + public abstract approveTokenEstimateGas( props: ApproveTokenProps ): Promise diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts index e0bb1e60a0..f510ede8ce 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts @@ -4,6 +4,7 @@ import { TransactionRequest } from '@ethersproject/providers' import { ERC20__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory' import { + ApproveTokenPrepareTxRequestProps, ApproveTokenProps, BridgeTransferStarter, RequiresTokenApprovalProps, @@ -50,9 +51,9 @@ export class CctpTransferStarter extends BridgeTransferStarter { return allowance.lt(amount) } - public async approveTokenPrepareTxRequest(params?: { - amount: BigNumber | undefined - }): Promise { + public async approveTokenPrepareTxRequest( + props?: ApproveTokenPrepareTxRequestProps + ): Promise { const { // usdcContractAddress, @@ -63,7 +64,7 @@ export class CctpTransferStarter extends BridgeTransferStarter { to: usdcContractAddress, data: ERC20__factory.createInterface().encodeFunctionData('approve', [ tokenMessengerContractAddress, - params?.amount ?? constants.MaxUint256 + props?.amount ?? constants.MaxUint256 ]), value: BigNumber.from(0) } From 8929a298b25a054a9564ddf52465ec94c377dc3f Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 15 Apr 2025 11:50:20 +0200 Subject: [PATCH 03/25] yay polymorphism --- .../src/components/TransferPanel/TransferPanel.tsx | 13 ++++++------- .../arb-token-bridge-ui/src/ui-driver/UiDriver.ts | 4 ++-- .../src/ui-driver/UiDriverCctp.ts | 13 +++---------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index eccd904473..e2041928e7 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -419,14 +419,18 @@ export function TransferPanel() { const { sourceChainProvider, destinationChainProvider, sourceChain } = networks + const cctpTransferStarter = new CctpTransferStarter({ + sourceChainProvider, + destinationChainProvider + }) + const returnEarly = await drive(stepGeneratorForCctp, stepExecutor, { amountBigNumber, isDepositMode, isSmartContractWallet, walletAddress, destinationAddress, - sourceChainProvider, - destinationChainProvider + transferStarter: cctpTransferStarter }) // this is only necessary while we are migrating to the ui driver @@ -437,11 +441,6 @@ export function TransferPanel() { return } - const cctpTransferStarter = new CctpTransferStarter({ - sourceChainProvider, - destinationChainProvider - }) - let depositForBurnTx try { diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts index d612e12781..28ad6ef42b 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -1,4 +1,5 @@ import { BigNumber, providers } from 'ethers' +import { BridgeTransferStarter } from '@/token-bridge-sdk/BridgeTransferStarter' import { DialogType } from '../components/common/Dialog2' @@ -16,8 +17,7 @@ export type UiDriverContext = { isSmartContractWallet: boolean walletAddress: string destinationAddress?: string - sourceChainProvider: providers.Provider - destinationChainProvider: providers.Provider + transferStarter: BridgeTransferStarter } export type UiDriverStep = diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts index ec8fde0da8..ebb824a7bd 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts @@ -1,5 +1,3 @@ -import { CctpTransferStarter } from '@/token-bridge-sdk/CctpTransferStarter' - import { step, UiDriverStepGenerator } from './UiDriver' import { stepGeneratorForDialog, @@ -17,22 +15,17 @@ export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( yield* stepGeneratorForDialog(dialog) yield* stepGeneratorForSmartContractWalletDestinationDialog(context) - const cctpTransferStarter = new CctpTransferStarter({ - sourceChainProvider: context.sourceChainProvider, - destinationChainProvider: context.destinationChainProvider - }) - - const approval = await cctpTransferStarter.requiresTokenApproval({ + const approval = await context.transferStarter.requiresTokenApproval({ amount: context.amountBigNumber, owner: context.walletAddress }) if (approval) { - const txRequest = await cctpTransferStarter.approveTokenPrepareTxRequest({ + const request = await context.transferStarter.approveTokenPrepareTxRequest({ amount: context.amountBigNumber }) yield* stepGeneratorForDialog('approve_token') - yield* stepGeneratorForTransaction(context, txRequest) + yield* stepGeneratorForTransaction(context, request) } } From 4df9bea7d3623e8fe5d46df096a206013497acec Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 15 Apr 2025 17:57:08 +0200 Subject: [PATCH 04/25] fix tests --- .../src/ui-driver/UiDriverCctp.test.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 5593d52205..5cc1ef5248 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -1,5 +1,6 @@ import { it } from 'vitest' +import { UiDriverContext } from './UiDriver' import { stepGeneratorForCctp } from './UiDriverCctp' import { nextStep, expectStep } from './UiDriverTestUtils' @@ -14,7 +15,7 @@ it(` const generator = stepGeneratorForCctp({ isDepositMode: true, isSmartContractWallet: false - }) + } as UiDriverContext) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -24,9 +25,6 @@ it(` const step3 = await nextStep(generator, [false]) expectStep(step3).hasType('return') - - const step4 = await nextStep(generator) - expectStep(step4).doesNotExist() }) it(` @@ -40,7 +38,7 @@ it(` const generator = stepGeneratorForCctp({ isDepositMode: false, isSmartContractWallet: false - }) + } as UiDriverContext) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -50,9 +48,6 @@ it(` const step3 = await nextStep(generator, [false]) expectStep(step3).hasType('return') - - const step4 = await nextStep(generator) - expectStep(step4).doesNotExist() }) it(` From 9d162ad357538f34a1c648198e48cc6ca582224b Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 15 Apr 2025 18:08:18 +0200 Subject: [PATCH 05/25] update tests --- .../src/ui-driver/UiDriverCctp.test.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 5cc1ef5248..8a21f1e1fc 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -1,4 +1,6 @@ import { it } from 'vitest' +import { BigNumber } from 'ethers' +import { BridgeTransferStarter } from '@/token-bridge-sdk/BridgeTransferStarter' import { UiDriverContext } from './UiDriver' import { stepGeneratorForCctp } from './UiDriverCctp' @@ -57,14 +59,22 @@ it(` walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + more context: + requires token approval + user actions: 1. user confirms "confirm_cctp_deposit" dialog + 2. user rejects "approve token" dialog `, async () => { const generator = stepGeneratorForCctp({ + amountBigNumber: BigNumber.from(1), isDepositMode: true, isSmartContractWallet: false, walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + transferStarter: { + requiresTokenApproval: () => true + } as unknown as BridgeTransferStarter }) const step1 = await nextStep(generator) @@ -74,7 +84,10 @@ it(` expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') const step3 = await nextStep(generator, [true]) - expectStep(step3).doesNotExist() + expectStep(step3).hasType('dialog').hasPayload('approve_token') + + const step4 = await nextStep(generator, [false]) + expectStep(step4).hasType('return') }) it(` From 6d5b55873762583da61d29bdbee07ebfc66cc604 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 15 Apr 2025 18:08:39 +0200 Subject: [PATCH 06/25] fix flow --- packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts index ebb824a7bd..38f06a8685 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts @@ -21,11 +21,12 @@ export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( }) if (approval) { + yield* stepGeneratorForDialog('approve_token') + const request = await context.transferStarter.approveTokenPrepareTxRequest({ amount: context.amountBigNumber }) - yield* stepGeneratorForDialog('approve_token') yield* stepGeneratorForTransaction(context, request) } } From fb8ad99c2a221dc975a3ea508c2791546d196a0d Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 15 Apr 2025 18:15:05 +0200 Subject: [PATCH 07/25] fix test util --- .../arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts index 69d3f5e07c..9980b3b5cd 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts @@ -13,7 +13,7 @@ export function expectStep(step: TStep | void) { return { hasType(expectedStepType: TStepType) { expect(step).toBeDefined() - expect(step!.type).toBe(expectedStepType) + expect(step!.type).toEqual(expectedStepType) return expectStep(step as Extract) }, @@ -24,7 +24,7 @@ export function expectStep(step: TStep | void) { throw new Error(`Step of type "${step!.type}" does not have a payload.`) } - expect(step.payload).toBe(expectedStepPayload) + expect(step.payload).toEqual(expectedStepPayload) return this }, From 4e40cd974330d15b69b5779e160b46148ceb9dca Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 16 Apr 2025 13:22:29 +0200 Subject: [PATCH 08/25] error handling for tx signing --- .../TransferPanel/TransferPanel.tsx | 21 +++++++++---------- .../src/ui-driver/UiDriver.ts | 6 +++++- .../src/ui-driver/UiDriverCommon.ts | 11 +++++++--- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index e2041928e7..0a2c952de6 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -380,21 +380,20 @@ export function TransferPanel() { const tx = await signer!.sendTransaction(step.payload) const txReceipt = await tx.wait() - return txReceipt + return { data: txReceipt } } catch (error) { - if (isUserRejectedError(error)) { - return + // capture error and show toast for anything that's not user rejecting error + if (!isUserRejectedError(error)) { + captureSentryErrorWithExtraData({ + error, + originFunction: 'stepExecutor.tx' + }) + + errorToast(`${(error as Error)?.message ?? error}`) } - captureSentryErrorWithExtraData({ - error, - originFunction: 'cctpTransferStarter.approveToken' - }) - - errorToast(`${(error as Error)?.message ?? error}`) + return { error: error as unknown as Error } } - - return } } } diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts index 28ad6ef42b..2a9dda3807 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -27,6 +27,10 @@ export type UiDriverStep = | { type: 'scw_tooltip' } | { type: 'tx'; payload: providers.TransactionRequest } +type Result = + | { data: T; error?: undefined } + | { data?: undefined; error: Error } + export type UiDriverStepResultFor = // TStep extends { type: 'start' } ? void @@ -41,7 +45,7 @@ export type UiDriverStepResultFor = // ? void : // TStep extends { type: 'tx' } - ? providers.TransactionReceipt + ? Result : // never diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts index e8301f4784..74a2121b70 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts @@ -42,7 +42,7 @@ export type UiDriverStepGeneratorForTransaction< txRequest: providers.TransactionRequest ) => AsyncGenerator< TStep, - providers.TransactionReceipt, + providers.TransactionReceipt | void, UiDriverStepResultFor > @@ -52,6 +52,11 @@ export const stepGeneratorForTransaction: UiDriverStepGeneratorForTransaction = yield* step({ type: 'scw_tooltip' }) } - // return the tx receipt - return yield* step({ type: 'tx', payload: txRequest }) + const { error, data } = yield* step({ type: 'tx', payload: txRequest }) + + if (typeof error === 'undefined') { + return data + } + + yield* step({ type: 'return' }) } From 7b77a198b92af095c70d1b8a8f28509bb33faf3c Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 16 Apr 2025 13:22:33 +0200 Subject: [PATCH 09/25] tests --- .../src/ui-driver/UiDriverCctp.test.ts | 109 ++++++++++++++++-- 1 file changed, 102 insertions(+), 7 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 8a21f1e1fc..58873e995c 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -1,5 +1,6 @@ import { it } from 'vitest' import { BigNumber } from 'ethers' +import { TransactionReceipt } from '@ethersproject/providers' import { BridgeTransferStarter } from '@/token-bridge-sdk/BridgeTransferStarter' import { UiDriverContext } from './UiDriver' @@ -64,7 +65,7 @@ it(` user actions: 1. user confirms "confirm_cctp_deposit" dialog - 2. user rejects "approve token" dialog + 2. user rejects "approve_token" dialog `, async () => { const generator = stepGeneratorForCctp({ amountBigNumber: BigNumber.from(1), @@ -90,6 +91,104 @@ it(` expectStep(step4).hasType('return') }) +it.only(` + context: + isDepositMode=true + isSmartContractWallet=false + walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + + more context: + requires token approval + + user actions: + 1. user confirms "confirm_cctp_deposit" dialog + 2. user confirms "approve_token" dialog + 3. token approval tx fails +`, async () => { + const mockTxRequest = { + to: '1', + data: '2', + value: 0 + } + + const generator = stepGeneratorForCctp({ + amountBigNumber: BigNumber.from(1), + isDepositMode: true, + isSmartContractWallet: false, + walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + transferStarter: { + requiresTokenApproval: () => true, + approveTokenPrepareTxRequest: () => mockTxRequest + } as unknown as BridgeTransferStarter + }) + + const step1 = await nextStep(generator) + expectStep(step1).hasType('start') + + const step2 = await nextStep(generator) + expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') + + const step3 = await nextStep(generator, [true]) + expectStep(step3).hasType('dialog').hasPayload('approve_token') + + const step4 = await nextStep(generator, [true]) + expectStep(step4).hasType('tx').hasPayload(mockTxRequest) + + const step5 = await nextStep(generator, [{ error: new Error() }]) + expectStep(step5).hasType('return') +}) + +it.only(` + context: + isDepositMode=true + isSmartContractWallet=false + walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + + more context: + requires token approval + + user actions: + 1. user confirms "confirm_cctp_deposit" dialog + 2. user confirms "approve_token" dialog + 3. token approval tx is successful +`, async () => { + const mockTxRequest = { + to: '2', + data: '3', + value: 1 + } + + const generator = stepGeneratorForCctp({ + amountBigNumber: BigNumber.from(1), + isDepositMode: true, + isSmartContractWallet: false, + walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + transferStarter: { + requiresTokenApproval: () => true, + approveTokenPrepareTxRequest: () => mockTxRequest + } as unknown as BridgeTransferStarter + }) + + const step1 = await nextStep(generator) + expectStep(step1).hasType('start') + + const step2 = await nextStep(generator) + expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') + + const step3 = await nextStep(generator, [true]) + expectStep(step3).hasType('dialog').hasPayload('approve_token') + + const step4 = await nextStep(generator, [true]) + expectStep(step4).hasType('tx').hasPayload(mockTxRequest) + + const step5 = await nextStep(generator, [{ data: {} as TransactionReceipt }]) + expectStep(step5).doesNotExist() +}) + it(` context: isDepositMode=true @@ -106,7 +205,7 @@ it(` isSmartContractWallet: true, walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' - }) + } as UiDriverContext) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -121,9 +220,6 @@ it(` const step4 = await nextStep(generator, [false]) expectStep(step4).hasType('return') - - const step5 = await nextStep(generator) - expectStep(step5).doesNotExist() }) it(` @@ -142,7 +238,7 @@ it(` isSmartContractWallet: true, walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' - }) + } as UiDriverContext) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -156,5 +252,4 @@ it(` .hasPayload('scw_custom_destination_address') const step4 = await nextStep(generator, [true]) - expectStep(step4).doesNotExist() }) From 55361914837c3de1a8e68564ddf4d84dd147a692 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 16 Apr 2025 13:53:41 +0200 Subject: [PATCH 10/25] use proper mock --- .../src/ui-driver/UiDriverCctp.test.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 58873e995c..5531a1089a 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -7,6 +7,12 @@ import { UiDriverContext } from './UiDriver' import { stepGeneratorForCctp } from './UiDriverCctp' import { nextStep, expectStep } from './UiDriverTestUtils' +const mockedApproveTokenTxRequest = { + to: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238', + data: '0x095ea7b30000000000000000000000009f3b8679c73c2fef8b59b4f3444d4e156fb70aa5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + value: BigNumber.from(0) +} + it(` context: isDepositMode=true @@ -106,12 +112,6 @@ it.only(` 2. user confirms "approve_token" dialog 3. token approval tx fails `, async () => { - const mockTxRequest = { - to: '1', - data: '2', - value: 0 - } - const generator = stepGeneratorForCctp({ amountBigNumber: BigNumber.from(1), isDepositMode: true, @@ -120,7 +120,7 @@ it.only(` destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', transferStarter: { requiresTokenApproval: () => true, - approveTokenPrepareTxRequest: () => mockTxRequest + approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest } as unknown as BridgeTransferStarter }) @@ -134,7 +134,7 @@ it.only(` expectStep(step3).hasType('dialog').hasPayload('approve_token') const step4 = await nextStep(generator, [true]) - expectStep(step4).hasType('tx').hasPayload(mockTxRequest) + expectStep(step4).hasType('tx').hasPayload(mockedApproveTokenTxRequest) const step5 = await nextStep(generator, [{ error: new Error() }]) expectStep(step5).hasType('return') @@ -155,12 +155,6 @@ it.only(` 2. user confirms "approve_token" dialog 3. token approval tx is successful `, async () => { - const mockTxRequest = { - to: '2', - data: '3', - value: 1 - } - const generator = stepGeneratorForCctp({ amountBigNumber: BigNumber.from(1), isDepositMode: true, @@ -169,7 +163,7 @@ it.only(` destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', transferStarter: { requiresTokenApproval: () => true, - approveTokenPrepareTxRequest: () => mockTxRequest + approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest } as unknown as BridgeTransferStarter }) @@ -183,7 +177,7 @@ it.only(` expectStep(step3).hasType('dialog').hasPayload('approve_token') const step4 = await nextStep(generator, [true]) - expectStep(step4).hasType('tx').hasPayload(mockTxRequest) + expectStep(step4).hasType('tx').hasPayload(mockedApproveTokenTxRequest) const step5 = await nextStep(generator, [{ data: {} as TransactionReceipt }]) expectStep(step5).doesNotExist() From affe08c990214954dcc38b127b295a5207935b9b Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 16 Apr 2025 13:58:48 +0200 Subject: [PATCH 11/25] wip util --- .../src/ui-driver/UiDriverCctp.test.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 5531a1089a..3c7c10eeca 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -13,6 +13,35 @@ const mockedApproveTokenTxRequest = { value: BigNumber.from(0) } +async function expectStepsForApproveToken( + generator: ReturnType, + options?: { + shouldDialogReject?: boolean + shouldTxError?: boolean + } +) { + const step1 = await nextStep(generator, [true]) + expectStep(step1).hasType('dialog').hasPayload('approve_token') + + if (options?.shouldDialogReject ?? false) { + const step2 = await nextStep(generator, [false]) + expectStep(step2).hasType('return') + return + } + + const step2 = await nextStep(generator, [true]) + expectStep(step2).hasType('tx').hasPayload(mockedApproveTokenTxRequest) + + if (options?.shouldTxError ?? false) { + const payload = { error: new Error() } + const step5 = await nextStep(generator, [payload]) + expectStep(step5).hasType('return') + } else { + const payload = { data: {} as TransactionReceipt } + const step5 = await nextStep(generator, [payload]) + } +} + it(` context: isDepositMode=true From e56173a1a337560ce5f3a213a779c342e97a1d02 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Thu, 22 May 2025 20:59:42 +0200 Subject: [PATCH 12/25] update tests --- .../src/ui-driver/UiDriverCctp.test.ts | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 3c7c10eeca..f2aba03a3f 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -126,7 +126,7 @@ it(` expectStep(step4).hasType('return') }) -it.only(` +it(` context: isDepositMode=true isSmartContractWallet=false @@ -169,7 +169,7 @@ it.only(` expectStep(step5).hasType('return') }) -it.only(` +it(` context: isDepositMode=true isSmartContractWallet=false @@ -252,16 +252,26 @@ it(` walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + more context: + requires token approval + user actions: 1. user confirms "confirm_cctp_deposit" dialog 2. user confirms "scw_custom_destination_address" dialog + 3. user confirms "approve_token" dialog + 4. token approval tx is successful `, async () => { const generator = stepGeneratorForCctp({ + amountBigNumber: BigNumber.from(1), isDepositMode: true, isSmartContractWallet: true, walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', - destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' - } as UiDriverContext) + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + transferStarter: { + requiresTokenApproval: () => true, + approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest + } as unknown as BridgeTransferStarter + }) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -275,4 +285,14 @@ it(` .hasPayload('scw_custom_destination_address') const step4 = await nextStep(generator, [true]) + expectStep(step4).hasType('dialog').hasPayload('approve_token') + + const step5 = await nextStep(generator, [true]) + expectStep(step5).hasType('scw_tooltip') + + const step6 = await nextStep(generator, [true]) + expectStep(step6).hasType('tx').hasPayload(mockedApproveTokenTxRequest) + + const step7 = await nextStep(generator, [{ data: {} as TransactionReceipt }]) + expectStep(step7).doesNotExist() }) From be2813b6531fae5d09cbd76569f1c9ff17d9c81d Mon Sep 17 00:00:00 2001 From: spsjvc Date: Thu, 22 May 2025 21:22:20 +0200 Subject: [PATCH 13/25] refactoring --- .../src/ui-driver/UiDriverCctp.test.ts | 102 +++++++++--------- 1 file changed, 48 insertions(+), 54 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index f2aba03a3f..89abcb0bd7 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -13,33 +13,42 @@ const mockedApproveTokenTxRequest = { value: BigNumber.from(0) } -async function expectStepsForApproveToken( +async function expectStepsForTokenApproval( generator: ReturnType, + context: UiDriverContext, options?: { - shouldDialogReject?: boolean + shouldUserRejectDialog?: boolean shouldTxError?: boolean } ) { - const step1 = await nextStep(generator, [true]) - expectStep(step1).hasType('dialog').hasPayload('approve_token') + const dialogStep = await nextStep(generator, [true]) + expectStep(dialogStep).hasType('dialog').hasPayload('approve_token') + + if (options?.shouldUserRejectDialog ?? false) { + const returnStep = await nextStep(generator, [false]) + expectStep(returnStep).hasType('return') - if (options?.shouldDialogReject ?? false) { - const step2 = await nextStep(generator, [false]) - expectStep(step2).hasType('return') return } - const step2 = await nextStep(generator, [true]) - expectStep(step2).hasType('tx').hasPayload(mockedApproveTokenTxRequest) + // confirm dialog + let variableStep = await nextStep(generator, [true]) + + if (context.isSmartContractWallet) { + expectStep(variableStep).hasType('scw_tooltip') + variableStep = await nextStep(generator) + } + + expectStep(variableStep).hasType('tx').hasPayload(mockedApproveTokenTxRequest) if (options?.shouldTxError ?? false) { - const payload = { error: new Error() } - const step5 = await nextStep(generator, [payload]) - expectStep(step5).hasType('return') - } else { - const payload = { data: {} as TransactionReceipt } - const step5 = await nextStep(generator, [payload]) + const returnStep = await nextStep(generator, [{ error: new Error() }]) + expectStep(returnStep).hasType('return') + + return } + + await nextStep(generator, [{ data: {} as TransactionReceipt }]) } it(` @@ -102,7 +111,7 @@ it(` 1. user confirms "confirm_cctp_deposit" dialog 2. user rejects "approve_token" dialog `, async () => { - const generator = stepGeneratorForCctp({ + const context: UiDriverContext = { amountBigNumber: BigNumber.from(1), isDepositMode: true, isSmartContractWallet: false, @@ -111,7 +120,9 @@ it(` transferStarter: { requiresTokenApproval: () => true } as unknown as BridgeTransferStarter - }) + } + + const generator = stepGeneratorForCctp(context) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -119,11 +130,9 @@ it(` const step2 = await nextStep(generator) expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') - const step3 = await nextStep(generator, [true]) - expectStep(step3).hasType('dialog').hasPayload('approve_token') - - const step4 = await nextStep(generator, [false]) - expectStep(step4).hasType('return') + await expectStepsForTokenApproval(generator, context, { + shouldUserRejectDialog: true + }) }) it(` @@ -141,7 +150,7 @@ it(` 2. user confirms "approve_token" dialog 3. token approval tx fails `, async () => { - const generator = stepGeneratorForCctp({ + const context: UiDriverContext = { amountBigNumber: BigNumber.from(1), isDepositMode: true, isSmartContractWallet: false, @@ -151,7 +160,9 @@ it(` requiresTokenApproval: () => true, approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest } as unknown as BridgeTransferStarter - }) + } + + const generator = stepGeneratorForCctp(context) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -159,14 +170,7 @@ it(` const step2 = await nextStep(generator) expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') - const step3 = await nextStep(generator, [true]) - expectStep(step3).hasType('dialog').hasPayload('approve_token') - - const step4 = await nextStep(generator, [true]) - expectStep(step4).hasType('tx').hasPayload(mockedApproveTokenTxRequest) - - const step5 = await nextStep(generator, [{ error: new Error() }]) - expectStep(step5).hasType('return') + await expectStepsForTokenApproval(generator, context, { shouldTxError: true }) }) it(` @@ -184,7 +188,7 @@ it(` 2. user confirms "approve_token" dialog 3. token approval tx is successful `, async () => { - const generator = stepGeneratorForCctp({ + const context: UiDriverContext = { amountBigNumber: BigNumber.from(1), isDepositMode: true, isSmartContractWallet: false, @@ -194,7 +198,9 @@ it(` requiresTokenApproval: () => true, approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest } as unknown as BridgeTransferStarter - }) + } + + const generator = stepGeneratorForCctp(context) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -202,14 +208,10 @@ it(` const step2 = await nextStep(generator) expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') - const step3 = await nextStep(generator, [true]) - expectStep(step3).hasType('dialog').hasPayload('approve_token') - - const step4 = await nextStep(generator, [true]) - expectStep(step4).hasType('tx').hasPayload(mockedApproveTokenTxRequest) + await expectStepsForTokenApproval(generator, context) - const step5 = await nextStep(generator, [{ data: {} as TransactionReceipt }]) - expectStep(step5).doesNotExist() + const step3 = await nextStep(generator) + expectStep(step3).doesNotExist() }) it(` @@ -261,7 +263,7 @@ it(` 3. user confirms "approve_token" dialog 4. token approval tx is successful `, async () => { - const generator = stepGeneratorForCctp({ + const context: UiDriverContext = { amountBigNumber: BigNumber.from(1), isDepositMode: true, isSmartContractWallet: true, @@ -271,7 +273,9 @@ it(` requiresTokenApproval: () => true, approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest } as unknown as BridgeTransferStarter - }) + } + + const generator = stepGeneratorForCctp(context) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -284,15 +288,5 @@ it(` .hasType('dialog') .hasPayload('scw_custom_destination_address') - const step4 = await nextStep(generator, [true]) - expectStep(step4).hasType('dialog').hasPayload('approve_token') - - const step5 = await nextStep(generator, [true]) - expectStep(step5).hasType('scw_tooltip') - - const step6 = await nextStep(generator, [true]) - expectStep(step6).hasType('tx').hasPayload(mockedApproveTokenTxRequest) - - const step7 = await nextStep(generator, [{ data: {} as TransactionReceipt }]) - expectStep(step7).doesNotExist() + await expectStepsForTokenApproval(generator, context) }) From 37284995d062296585d7ccb5f72e31e0772094fb Mon Sep 17 00:00:00 2001 From: spsjvc Date: Thu, 22 May 2025 21:32:04 +0200 Subject: [PATCH 14/25] refactoring --- .../src/ui-driver/UiDriverCctp.test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 89abcb0bd7..3127179868 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -59,10 +59,12 @@ it(` user actions: 1. user rejects "confirm_cctp_deposit" dialog `, async () => { - const generator = stepGeneratorForCctp({ + const context: UiDriverContext = { isDepositMode: true, isSmartContractWallet: false - } as UiDriverContext) + } as UiDriverContext + + const generator = stepGeneratorForCctp(context) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -82,10 +84,12 @@ it(` user actions: 1. user rejects "confirm_cctp_withdrawal" dialog `, async () => { - const generator = stepGeneratorForCctp({ + const context: UiDriverContext = { isDepositMode: false, isSmartContractWallet: false - } as UiDriverContext) + } as UiDriverContext + + const generator = stepGeneratorForCctp(context) const step1 = await nextStep(generator) expectStep(step1).hasType('start') @@ -225,12 +229,14 @@ it(` 1. user confirms "confirm_cctp_deposit" dialog 2. user rejects "scw_custom_destination_address" dialog `, async () => { - const generator = stepGeneratorForCctp({ + const context: UiDriverContext = { isDepositMode: true, isSmartContractWallet: true, walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' - } as UiDriverContext) + } as UiDriverContext + + const generator = stepGeneratorForCctp(context) const step1 = await nextStep(generator) expectStep(step1).hasType('start') From d21570556e24cca5dbdfbe00bfbe28d73f8e29f2 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 12:05:12 +0200 Subject: [PATCH 15/25] tiny change --- .../arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts index 74a2121b70..c8556ba4d4 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts @@ -54,9 +54,9 @@ export const stepGeneratorForTransaction: UiDriverStepGeneratorForTransaction = const { error, data } = yield* step({ type: 'tx', payload: txRequest }) - if (typeof error === 'undefined') { + if (typeof error !== 'undefined') { + yield* step({ type: 'return' }) + } else { return data } - - yield* step({ type: 'return' }) } From 4ab7ec1867aa5c36cd3a997afad24e42cd9f174f Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 12:09:15 +0200 Subject: [PATCH 16/25] oops --- .../src/components/TransferPanel/TransferPanel.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index dea8fb4870..4541bc8a4e 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -379,11 +379,8 @@ export function TransferPanel() { } catch (error) { // capture error and show toast for anything that's not user rejecting error if (!isUserRejectedError(error)) { - captureSentryErrorWithExtraData({ - error, - originFunction: 'stepExecutor.tx' - }) - + // todo: what to add here? + // handleError({ error }) errorToast(`${(error as Error)?.message ?? error}`) } From 52f54c1c86fb23a2abf156e274813f6442d73ba5 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 13:53:16 +0200 Subject: [PATCH 17/25] refactoring --- .../src/ui-driver/UiDriverCctp.test.ts | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 1d2bebf7c2..b9daadfe8b 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -21,12 +21,12 @@ async function expectStepsForTokenApproval( shouldTxError?: boolean } ) { - const dialogStep = await nextStep(generator, [true]) - expectStep(dialogStep).hasType('dialog').hasPayload('approve_token') + expectStep(await nextStep(generator, [true])) + .hasType('dialog') + .hasPayload('approve_token') if (options?.shouldUserRejectDialog ?? false) { - const returnStep = await nextStep(generator, [false]) - expectStep(returnStep).hasType('return') + expectStep(await nextStep(generator, [false])).hasType('return') return } @@ -42,8 +42,9 @@ async function expectStepsForTokenApproval( expectStep(variableStep).hasType('tx').hasPayload(mockedApproveTokenTxRequest) if (options?.shouldTxError ?? false) { - const returnStep = await nextStep(generator, [{ error: new Error() }]) - expectStep(returnStep).hasType('return') + expectStep(await nextStep(generator, [{ error: new Error() }])) + // + .hasType('return') return } @@ -122,11 +123,10 @@ it(` const generator = stepGeneratorForCctp(context) - const step1 = await nextStep(generator) - expectStep(step1).hasType('start') - - const step2 = await nextStep(generator) - expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') + expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + .hasType('dialog') + .hasPayload('confirm_cctp_deposit') await expectStepsForTokenApproval(generator, context, { shouldUserRejectDialog: true @@ -162,11 +162,10 @@ it(` const generator = stepGeneratorForCctp(context) - const step1 = await nextStep(generator) - expectStep(step1).hasType('start') - - const step2 = await nextStep(generator) - expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') + expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + .hasType('dialog') + .hasPayload('confirm_cctp_deposit') await expectStepsForTokenApproval(generator, context, { shouldTxError: true }) }) @@ -200,16 +199,14 @@ it(` const generator = stepGeneratorForCctp(context) - const step1 = await nextStep(generator) - expectStep(step1).hasType('start') - - const step2 = await nextStep(generator) - expectStep(step2).hasType('dialog').hasPayload('confirm_cctp_deposit') + expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + .hasType('dialog') + .hasPayload('confirm_cctp_deposit') await expectStepsForTokenApproval(generator, context) - const step3 = await nextStep(generator) - expectStep(step3).doesNotExist() + expectStep(await nextStep(generator)).doesNotExist() }) it(` From a7027ecf7fba6ff22d371602b3db7209931aacd4 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 14:09:26 +0200 Subject: [PATCH 18/25] refactoring --- .../src/ui-driver/UiDriverCctp.test.ts | 95 ++++++++----------- 1 file changed, 41 insertions(+), 54 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index b9daadfe8b..d82b2714f7 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -13,45 +13,6 @@ const mockedApproveTokenTxRequest = { value: BigNumber.from(0) } -async function expectStepsForTokenApproval( - generator: ReturnType, - context: UiDriverContext, - options?: { - shouldUserRejectDialog?: boolean - shouldTxError?: boolean - } -) { - expectStep(await nextStep(generator, [true])) - .hasType('dialog') - .hasPayload('approve_token') - - if (options?.shouldUserRejectDialog ?? false) { - expectStep(await nextStep(generator, [false])).hasType('return') - - return - } - - // confirm dialog - let variableStep = await nextStep(generator, [true]) - - if (context.isSmartContractWallet) { - expectStep(variableStep).hasType('scw_tooltip') - variableStep = await nextStep(generator) - } - - expectStep(variableStep).hasType('tx').hasPayload(mockedApproveTokenTxRequest) - - if (options?.shouldTxError ?? false) { - expectStep(await nextStep(generator, [{ error: new Error() }])) - // - .hasType('return') - - return - } - - await nextStep(generator, [{ data: {} as TransactionReceipt }]) -} - it(` context: isDepositMode=true @@ -127,10 +88,10 @@ it(` expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') - - await expectStepsForTokenApproval(generator, context, { - shouldUserRejectDialog: true - }) + expectStep(await nextStep(generator, [true])) + .hasType('dialog') + .hasPayload('approve_token') + expectStep(await nextStep(generator, [false])).hasType('return') }) it(` @@ -162,12 +123,21 @@ it(` const generator = stepGeneratorForCctp(context) - expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + // + .hasType('start') expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') - - await expectStepsForTokenApproval(generator, context, { shouldTxError: true }) + expectStep(await nextStep(generator, [true])) + .hasType('dialog') + .hasPayload('approve_token') + expectStep(await nextStep(generator, [true])) + .hasType('tx') + .hasPayload(mockedApproveTokenTxRequest) + expectStep(await nextStep(generator, [{ error: new Error() }])) + // + .hasType('return') }) it(` @@ -203,10 +173,15 @@ it(` expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') - - await expectStepsForTokenApproval(generator, context) - - expectStep(await nextStep(generator)).doesNotExist() + expectStep(await nextStep(generator, [true])) + .hasType('dialog') + .hasPayload('approve_token') + expectStep(await nextStep(generator, [true])) + .hasType('tx') + .hasPayload(mockedApproveTokenTxRequest) + expectStep(await nextStep(generator, [{ data: {} as TransactionReceipt }])) + // + .doesNotExist() }) it(` @@ -229,14 +204,18 @@ it(` const generator = stepGeneratorForCctp(context) - expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + // + .hasType('start') expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') expectStep(await nextStep(generator, [true])) .hasType('dialog') .hasPayload('scw_custom_destination_address') - expectStep(await nextStep(generator, [false])).hasType('return') + expectStep(await nextStep(generator, [false])) + // + .hasType('return') }) it(` @@ -276,6 +255,14 @@ it(` expectStep(await nextStep(generator, [true])) .hasType('dialog') .hasPayload('scw_custom_destination_address') - - await expectStepsForTokenApproval(generator, context) + expectStep(await nextStep(generator, [true])) + .hasType('dialog') + .hasPayload('approve_token') + expectStep(await nextStep(generator, [true])).hasType('scw_tooltip') + expectStep(await nextStep(generator)) + .hasType('tx') + .hasPayload(mockedApproveTokenTxRequest) + expectStep(await nextStep(generator, [{ data: {} as TransactionReceipt }])) + // + .doesNotExist() }) From 998bd097d23ed1ec6657d1130100b320ff26f4c3 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 14:10:38 +0200 Subject: [PATCH 19/25] formatting --- .../src/ui-driver/UiDriverCctp.test.ts | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index d82b2714f7..fd875409c6 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -28,11 +28,15 @@ it(` const generator = stepGeneratorForCctp(context) - expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + // + .hasType('start') expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') - expectStep(await nextStep(generator, [false])).hasType('return') + expectStep(await nextStep(generator, [false])) + // + .hasType('return') }) it(` @@ -50,11 +54,15 @@ it(` const generator = stepGeneratorForCctp(context) - expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + // + .hasType('start') expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_withdrawal') - expectStep(await nextStep(generator, [false])).hasType('return') + expectStep(await nextStep(generator, [false])) + // + .hasType('return') }) it(` @@ -84,14 +92,18 @@ it(` const generator = stepGeneratorForCctp(context) - expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + // + .hasType('start') expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') expectStep(await nextStep(generator, [true])) .hasType('dialog') .hasPayload('approve_token') - expectStep(await nextStep(generator, [false])).hasType('return') + expectStep(await nextStep(generator, [false])) + // + .hasType('return') }) it(` @@ -169,7 +181,9 @@ it(` const generator = stepGeneratorForCctp(context) - expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + // + .hasType('start') expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') @@ -248,7 +262,9 @@ it(` const generator = stepGeneratorForCctp(context) - expectStep(await nextStep(generator)).hasType('start') + expectStep(await nextStep(generator)) + // + .hasType('start') expectStep(await nextStep(generator)) .hasType('dialog') .hasPayload('confirm_cctp_deposit') @@ -258,7 +274,9 @@ it(` expectStep(await nextStep(generator, [true])) .hasType('dialog') .hasPayload('approve_token') - expectStep(await nextStep(generator, [true])).hasType('scw_tooltip') + expectStep(await nextStep(generator, [true])) + // + .hasType('scw_tooltip') expectStep(await nextStep(generator)) .hasType('tx') .hasPayload(mockedApproveTokenTxRequest) From 9a70b43e70279d495bccaa3ff20f16d272db64e5 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 14:15:29 +0200 Subject: [PATCH 20/25] update comments --- .../src/ui-driver/UiDriverCctp.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index fd875409c6..6d253623d8 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -72,8 +72,8 @@ it(` walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 - more context: - requires token approval + additional context: + 1. token requires approval user actions: 1. user confirms "confirm_cctp_deposit" dialog @@ -113,8 +113,8 @@ it(` walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 - more context: - requires token approval + additional context: + 1. token requires approval user actions: 1. user confirms "confirm_cctp_deposit" dialog @@ -159,8 +159,8 @@ it(` walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 - more context: - requires token approval + additional context: + 1. token requires approval user actions: 1. user confirms "confirm_cctp_deposit" dialog @@ -239,8 +239,8 @@ it(` walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 - more context: - requires token approval + additional context: + 1. token requires approval user actions: 1. user confirms "confirm_cctp_deposit" dialog From b00a5fa9f9a08da643b7c9b4078c1ff605e6ea79 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 14:20:20 +0200 Subject: [PATCH 21/25] add test --- .../src/ui-driver/UiDriverCctp.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 6d253623d8..82d6ef9632 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -198,6 +198,44 @@ it(` .doesNotExist() }) +it(` + context: + isDepositMode=true + isSmartContractWallet=false + walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + + additional context: + 1. token does not require approval + + user actions: + 1. user confirms "confirm_cctp_deposit" dialog +`, async () => { + const context: UiDriverContext = { + amountBigNumber: BigNumber.from(1), + isDepositMode: true, + isSmartContractWallet: false, + walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + transferStarter: { + requiresTokenApproval: () => false, + approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest + } as unknown as BridgeTransferStarter + } + + const generator = stepGeneratorForCctp(context) + + expectStep(await nextStep(generator)) + // + .hasType('start') + expectStep(await nextStep(generator)) + .hasType('dialog') + .hasPayload('confirm_cctp_deposit') + expectStep(await nextStep(generator, [true])) + // + .doesNotExist() +}) + it(` context: isDepositMode=true From 7aa89da153f58f5e6c2a87fe92708ca0f1ee63aa Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 14:38:57 +0200 Subject: [PATCH 22/25] add label --- .../components/TransferPanel/TransferPanel.tsx | 10 +++++++--- .../src/ui-driver/UiDriver.ts | 17 ++++++++++++++++- .../src/ui-driver/UiDriverCctp.test.ts | 18 ++++++++++++++---- .../src/ui-driver/UiDriverCctp.ts | 5 ++++- .../src/ui-driver/UiDriverCommon.ts | 7 ++++--- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx index 00d98696a0..26853b602c 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -400,15 +400,19 @@ export function TransferPanel() { case 'tx': { try { - const tx = await signer!.sendTransaction(step.payload) + const tx = await signer!.sendTransaction(step.payload.txRequest) const txReceipt = await tx.wait() return { data: txReceipt } } catch (error) { // capture error and show toast for anything that's not user rejecting error if (!isUserRejectedError(error)) { - // todo: what to add here? - // handleError({ error }) + handleError({ + error, + label: step.payload.txRequestLabel, + category: 'transaction_signing' + }) + errorToast(`${(error as Error)?.message ?? error}`) } diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts index 2a9dda3807..eb93340f62 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -25,7 +25,22 @@ export type UiDriverStep = | { type: 'return' } | { type: 'dialog'; payload: Dialog } | { type: 'scw_tooltip' } - | { type: 'tx'; payload: providers.TransactionRequest } + | { + type: 'tx' + payload: { + txRequest: providers.TransactionRequest + txRequestLabel: string + } + } + +export type UiDriverStepType = UiDriverStep['type'] + +export type UiDriverStepPayloadFor = + Extract extends { + payload: infer TPayload + } + ? TPayload + : never type Result = | { data: T; error?: undefined } diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 82d6ef9632..4878c4aec5 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -1,6 +1,9 @@ import { it } from 'vitest' import { BigNumber } from 'ethers' -import { TransactionReceipt } from '@ethersproject/providers' +import { + TransactionRequest, + TransactionReceipt +} from '@ethersproject/providers' import { BridgeTransferStarter } from '@/token-bridge-sdk/BridgeTransferStarter' import { UiDriverContext } from './UiDriver' @@ -13,6 +16,13 @@ const mockedApproveTokenTxRequest = { value: BigNumber.from(0) } +function approveTokenPayload(txRequest: TransactionRequest) { + return { + txRequest, + txRequestLabel: 'stepGeneratorForCctp.approveToken' + } +} + it(` context: isDepositMode=true @@ -146,7 +156,7 @@ it(` .hasPayload('approve_token') expectStep(await nextStep(generator, [true])) .hasType('tx') - .hasPayload(mockedApproveTokenTxRequest) + .hasPayload(approveTokenPayload(mockedApproveTokenTxRequest)) expectStep(await nextStep(generator, [{ error: new Error() }])) // .hasType('return') @@ -192,7 +202,7 @@ it(` .hasPayload('approve_token') expectStep(await nextStep(generator, [true])) .hasType('tx') - .hasPayload(mockedApproveTokenTxRequest) + .hasPayload(approveTokenPayload(mockedApproveTokenTxRequest)) expectStep(await nextStep(generator, [{ data: {} as TransactionReceipt }])) // .doesNotExist() @@ -317,7 +327,7 @@ it(` .hasType('scw_tooltip') expectStep(await nextStep(generator)) .hasType('tx') - .hasPayload(mockedApproveTokenTxRequest) + .hasPayload(approveTokenPayload(mockedApproveTokenTxRequest)) expectStep(await nextStep(generator, [{ data: {} as TransactionReceipt }])) // .doesNotExist() diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts index 38f06a8685..2a330e6423 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts @@ -27,6 +27,9 @@ export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( amount: context.amountBigNumber }) - yield* stepGeneratorForTransaction(context, request) + yield* stepGeneratorForTransaction(context, { + txRequest: request, + txRequestLabel: 'stepGeneratorForCctp.approveToken' + }) } } diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts index c8556ba4d4..1c3946bc1a 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts @@ -3,6 +3,7 @@ import { providers } from 'ethers' import { step, UiDriverStep, + UiDriverStepPayloadFor, UiDriverStepResultFor, UiDriverStepGenerator, UiDriverContext, @@ -39,7 +40,7 @@ export type UiDriverStepGeneratorForTransaction< TStep extends UiDriverStep = UiDriverStep > = ( context: UiDriverContext, - txRequest: providers.TransactionRequest + payload: UiDriverStepPayloadFor<'tx'> ) => AsyncGenerator< TStep, providers.TransactionReceipt | void, @@ -47,12 +48,12 @@ export type UiDriverStepGeneratorForTransaction< > export const stepGeneratorForTransaction: UiDriverStepGeneratorForTransaction = - async function* (context, txRequest) { + async function* (context, payload) { if (context.isSmartContractWallet) { yield* step({ type: 'scw_tooltip' }) } - const { error, data } = yield* step({ type: 'tx', payload: txRequest }) + const { error, data } = yield* step({ type: 'tx', payload }) if (typeof error !== 'undefined') { yield* step({ type: 'return' }) From ed00a61dcefe99d3e181e9a0563b665803ea86ad Mon Sep 17 00:00:00 2001 From: spsjvc Date: Fri, 23 May 2025 14:44:05 +0200 Subject: [PATCH 23/25] refactor to use type --- .../src/ui-driver/UiDriver.ts | 27 ++++++++----------- .../src/ui-driver/UiDriverCommon.ts | 4 +-- .../src/ui-driver/UiDriverTestUtils.ts | 4 +-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts index eb93340f62..5642c8aa8f 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -46,31 +46,26 @@ type Result = | { data: T; error?: undefined } | { data?: undefined; error: Error } -export type UiDriverStepResultFor = // - TStep extends { type: 'start' } +export type UiDriverStepResultFor = + TStepType extends 'start' ? void - : // - TStep extends { type: 'return' } + : TStepType extends 'return' ? void - : // - TStep extends { type: 'dialog' } + : TStepType extends 'dialog' ? boolean - : // - TStep extends { type: 'scw_tooltip' } + : TStepType extends 'scw_tooltip' ? void - : // - TStep extends { type: 'tx' } + : TStepType extends 'tx' ? Result - : // - never + : never export type UiDriverStepGenerator = ( context: UiDriverContext -) => AsyncGenerator> +) => AsyncGenerator> export type UiDriverStepExecutor = ( step: TStep -) => Promise> +) => Promise> // TypeScript doesn't to the greatest job with generators // This 2nd generator helps with types both for params and result when yielding a step @@ -78,8 +73,8 @@ export async function* step( step: TStep ): AsyncGenerator< TStep, - UiDriverStepResultFor, - UiDriverStepResultFor + UiDriverStepResultFor, + UiDriverStepResultFor > { return yield step } diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts index 1c3946bc1a..3273931bb6 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts @@ -15,7 +15,7 @@ export type UiDriverStepGeneratorForDialog< TStep extends UiDriverStep = UiDriverStep > = ( dialog: Dialog -) => AsyncGenerator> +) => AsyncGenerator> export const stepGeneratorForDialog: UiDriverStepGeneratorForDialog = async function* (payload: Dialog) { @@ -44,7 +44,7 @@ export type UiDriverStepGeneratorForTransaction< ) => AsyncGenerator< TStep, providers.TransactionReceipt | void, - UiDriverStepResultFor + UiDriverStepResultFor > export const stepGeneratorForTransaction: UiDriverStepGeneratorForTransaction = diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts index 9980b3b5cd..3daf7b5ef8 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts @@ -3,8 +3,8 @@ import { expect } from 'vitest' import { UiDriverStep, UiDriverStepResultFor } from './UiDriver' export async function nextStep( - generator: AsyncGenerator>, - nextStepInputs: [] | [UiDriverStepResultFor] = [] + generator: AsyncGenerator>, + nextStepInputs: [] | [UiDriverStepResultFor] = [] ) { return (await generator.next(...nextStepInputs)).value } From 53705b857c7156aa88f022ae316003e5837c834c Mon Sep 17 00:00:00 2001 From: spsjvc Date: Mon, 26 May 2025 17:20:45 +0200 Subject: [PATCH 24/25] fix --- .../src/token-bridge-sdk/BridgeTransferStarter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts index 2d1f7a1768..80dc0eb0ba 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/BridgeTransferStarter.ts @@ -155,6 +155,7 @@ export abstract class BridgeTransferStarter { // only cctp is going to override it for now, and we'll do the same for others one by one // finally, once we have all implementations we'll mark it as abstract public async approveTokenPrepareTxRequest( + // eslint-disable-next-line @typescript-eslint/no-unused-vars props?: ApproveTokenPrepareTxRequestProps ): Promise { return {} as TransactionRequest From 5430d72b8161843f94a7d71e21abe1c01570cb7a Mon Sep 17 00:00:00 2001 From: spsjvc Date: Mon, 26 May 2025 18:22:08 +0200 Subject: [PATCH 25/25] try things out --- .../src/ui-driver/UiDriverCctp.test.ts | 124 +++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts index 4878c4aec5..4eee4ca6cc 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -6,7 +6,7 @@ import { } from '@ethersproject/providers' import { BridgeTransferStarter } from '@/token-bridge-sdk/BridgeTransferStarter' -import { UiDriverContext } from './UiDriver' +import { UiDriverContext, UiDriverStep } from './UiDriver' import { stepGeneratorForCctp } from './UiDriverCctp' import { nextStep, expectStep } from './UiDriverTestUtils' @@ -23,6 +23,128 @@ function approveTokenPayload(txRequest: TransactionRequest) { } } +type UiDriverTestCaseStep = { + description: string + userInput?: any + expectedStep: UiDriverStep | undefined +} + +type UiDriverTestCase = { + name: string + context: UiDriverContext + sequence: UiDriverTestCaseStep[] +} + +const dialog = { + confirm: () => [true], + reject: () => [false] +} + +const testCases: UiDriverTestCase[] = [ + { + name: 'eoa :: deposit :: user rejects "confirm_cctp_deposit" dialog', + context: { + isDepositMode: true, + isSmartContractWallet: false + } as UiDriverContext, + sequence: [ + { + description: '"confirm_cctp_deposit" dialog is opened', + expectedStep: { + type: 'dialog', + payload: 'confirm_cctp_deposit' + } + }, + { + description: 'user rejects dialog', + userInput: dialog.reject(), + expectedStep: { + type: 'return' + } + } + ] + }, + { + name: 'scw :: deposit :: user confirms all dialogs and token approval succeeds', + context: { + amountBigNumber: BigNumber.from(1), + isDepositMode: true, + isSmartContractWallet: true, + walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + transferStarter: { + requiresTokenApproval: () => true, + approveTokenPrepareTxRequest: () => mockedApproveTokenTxRequest + } as unknown as BridgeTransferStarter + } as UiDriverContext, + sequence: [ + { + description: '"confirm_cctp_deposit" dialog is opened', + expectedStep: { type: 'dialog', payload: 'confirm_cctp_deposit' } + }, + { + description: 'user confirms deposit dialog', + userInput: dialog.confirm(), + expectedStep: { + type: 'dialog', + payload: 'scw_custom_destination_address' + } + }, + { + description: 'user confirms scw destination address dialog', + userInput: dialog.confirm(), + expectedStep: { type: 'dialog', payload: 'approve_token' } + }, + { + description: 'user confirms approve token dialog', + userInput: dialog.confirm(), + expectedStep: { type: 'scw_tooltip' } + }, + { + description: 'token approval transaction is prepared', + expectedStep: { + type: 'tx', + payload: approveTokenPayload(mockedApproveTokenTxRequest) + } + }, + { + description: 'token approval transaction succeeds', + userInput: [{ data: {} as TransactionReceipt }], + expectedStep: undefined + } + ] + } +] + +testCases.forEach(({ name, context, sequence }) => { + it(name, async () => { + const generator = stepGeneratorForCctp(context) + + expectStep(await nextStep(generator)) + // + .hasType('start') + + sequence.forEach(async ({ userInput, expectedStep }) => { + if (typeof expectedStep === 'undefined') { + expectStep(await nextStep(generator, userInput)) + // + .doesNotExist() + return + } + + if ('payload' in expectedStep) { + expectStep(await nextStep(generator, userInput)) + .hasType(expectedStep.type) + .hasPayload(expectedStep.payload) + } else { + expectStep(await nextStep(generator, userInput)) + // + .hasType(expectedStep.type) + } + }) + }) +}) + it(` context: isDepositMode=true