From ae10ec86a59cd33e6f0228bdcb6059fa41365391 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 17:28:47 +0700 Subject: [PATCH 01/12] initial commit --- .../TransferPanel/TransferPanel.tsx | 24 ++++++++- .../src/ui-driver/UiDriver.ts | 50 +++++++++++++++++++ .../src/ui-driver/UiDriverCctp.ts | 5 ++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts create mode 100644 packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts 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 7750358a3a..39ac004fda 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -84,6 +84,8 @@ import { useMainContentTabs } from '../MainContent/MainContent' import { useIsOftV2Transfer } from './hooks/useIsOftV2Transfer' import { OftV2TransferStarter } from '../../token-bridge-sdk/OftV2TransferStarter' import { highlightOftTransactionHistoryDisclaimer } from '../TransactionHistory/OftTransactionHistoryDisclaimer' +import { drive, UiDriverStepExecutor } from '../../ui-driver/UiDriver' +import { stepGeneratorForCctp } from '../../ui-driver/UiDriverCctp' const signerUndefinedError = 'Signer is undefined' const transferNotAllowedError = 'Transfer not allowed' @@ -401,6 +403,21 @@ export function TransferPanel() { return confirmed } + const stepExecutor: UiDriverStepExecutor = async step => { + switch (step.type) { + case 'start': { + setTransferring(true) + return + } + + case 'return': { + throw Error( + `[stepExecutor] "return" step should be handled outside the executor` + ) + } + } + } + const transferCctp = async () => { if (!selectedToken) { return @@ -414,12 +431,15 @@ export function TransferPanel() { const destinationAddress = latestDestinationAddress.current - setTransferring(true) - try { const { sourceChainProvider, destinationChainProvider, sourceChain } = networks + await drive(stepGeneratorForCctp, stepExecutor, { + isDepositMode, + isSmartContractWallet + }) + // show confirmation popup before cctp transfer if (isDepositMode) { const depositConfirmation = diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts new file mode 100644 index 0000000000..473ba6f1d8 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -0,0 +1,50 @@ +export type UiDriverContext = { + isDepositMode: boolean + isSmartContractWallet: boolean +} + +export type UiDriverStep = + | { type: 'start' } // + | { type: 'return' } + +export type UiDriverStepResultFor = // + TStep extends { type: 'start' } + ? void + : // + TStep extends { type: 'return' } + ? void + : // + never + +export type UiDriverStepGenerator = ( + context: UiDriverContext +) => AsyncGenerator> + +export type UiDriverStepExecutor = ( + step: TStep +) => Promise> + +export async function drive( + generator: UiDriverStepGenerator, + executor: UiDriverStepExecutor, + context: UiDriverContext +): Promise { + const flow = generator(context) + + let nextStep = await flow.next() + + while (!nextStep.done) { + const step = nextStep.value + + // handle special type for early return + if (step.type === 'return') { + return + } + + // execute current step and obtain the result + const result = await executor(step) + + // pass the result back into the generator + nextStep = await flow.next(result) + } +} diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts new file mode 100644 index 0000000000..232c726c2e --- /dev/null +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts @@ -0,0 +1,5 @@ +import { UiDriverStepGenerator } from './UiDriver' + +export const stepGeneratorForCctp: UiDriverStepGenerator = async function* () { + yield { type: 'start' } +} From 45994d515f72bbc01f13c7fe0d67d009311758af Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 17:29:20 +0700 Subject: [PATCH 02/12] log step in dev --- .../src/components/TransferPanel/TransferPanel.tsx | 4 ++++ 1 file changed, 4 insertions(+) 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 39ac004fda..c51d78291a 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -404,6 +404,10 @@ export function TransferPanel() { } const stepExecutor: UiDriverStepExecutor = async step => { + if (process.env.NODE_ENV === 'development') { + console.log(step) + } + switch (step.type) { case 'start': { setTransferring(true) From ea844735b6136025e165ffe9b9172319f1a9fa61 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 17:41:40 +0700 Subject: [PATCH 03/12] add test --- .../src/ui-driver/UiDriver.test.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 packages/arb-token-bridge-ui/src/ui-driver/UiDriver.test.ts diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.test.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.test.ts new file mode 100644 index 0000000000..c3878851d2 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.test.ts @@ -0,0 +1,51 @@ +import { + UiDriverContext, + UiDriverStepGenerator, + UiDriverStepExecutor, + drive +} from './UiDriver' + +it('successfully does step execution', async () => { + let counter = 0 + + const generator: UiDriverStepGenerator = async function* () { + yield { type: 'start' } + yield { type: 'start' } + yield { type: 'start' } + yield { type: 'start' } + } + + const executor: UiDriverStepExecutor = async function (step) { + if (step.type === 'start') { + counter += 1 + } + } + + // the real ui context is not needed for the test + await drive(generator, executor, {} as UiDriverContext) + + expect(counter).toEqual(4) +}) + +it('successfully does early return', async () => { + let counter = 0 + + const generator: UiDriverStepGenerator = async function* () { + yield { type: 'start' } + yield { type: 'start' } + yield { type: 'return' } + yield { type: 'start' } + yield { type: 'start' } + } + + const executor: UiDriverStepExecutor = async function (step) { + if (step.type === 'start') { + counter += 1 + } + } + + // the real ui context is not needed for the test + await drive(generator, executor, {} as UiDriverContext) + + expect(counter).toEqual(2) +}) From f629cee53dd10fe0563354121a54bca03f98551e Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 17:50:45 +0700 Subject: [PATCH 04/12] basic test --- .../src/ui-driver/UiDriverCctp.test.ts | 12 ++++++++++++ .../src/ui-driver/UiDriverTestUtils.ts | 6 ++++++ 2 files changed, 18 insertions(+) create mode 100644 packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts create mode 100644 packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts 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 new file mode 100644 index 0000000000..8063db737f --- /dev/null +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts @@ -0,0 +1,12 @@ +import { stepGeneratorForCctp } from './UiDriverCctp' +import { expectStartStep } from './UiDriverTestUtils' + +it('successfully returns steps', async () => { + const generator = stepGeneratorForCctp({ + isDepositMode: true, + isSmartContractWallet: false + }) + + const step1 = await (await generator.next()).value + expectStartStep(step1) +}) diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts new file mode 100644 index 0000000000..81169e0777 --- /dev/null +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts @@ -0,0 +1,6 @@ +import { UiDriverStep } from './UiDriver' + +export function expectStartStep(step: UiDriverStep | void) { + expect(step).toBeDefined() + expect(step!.type).toEqual('start') +} From 412ddc7325b13894149d9ae6836fcd2833f92ef2 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 23:08:33 +0700 Subject: [PATCH 05/12] add util --- .../arb-token-bridge-ui/src/ui-driver/UiDriver.ts | 12 ++++++++++++ .../src/ui-driver/UiDriverCctp.ts | 4 ++-- 2 files changed, 14 insertions(+), 2 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 473ba6f1d8..3e2aab363e 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -24,6 +24,18 @@ export type UiDriverStepExecutor = ( step: TStep ) => 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 +export async function* step( + step: TStep +): AsyncGenerator< + TStep, + UiDriverStepResultFor, + UiDriverStepResultFor +> { + return yield step +} + export async function drive( generator: UiDriverStepGenerator, executor: UiDriverStepExecutor, 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 232c726c2e..ec1ba295b6 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,5 @@ -import { UiDriverStepGenerator } from './UiDriver' +import { step, UiDriverStepGenerator } from './UiDriver' export const stepGeneratorForCctp: UiDriverStepGenerator = async function* () { - yield { type: 'start' } + yield* step({ type: 'start' }) } From 2af71a5f7c60f9501473b6b1934669edf097bc8d Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 23:25:53 +0700 Subject: [PATCH 06/12] add test util --- .../src/ui-driver/UiDriverTestUtils.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 81169e0777..b18d7b2cca 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts @@ -1,6 +1,12 @@ -import { UiDriverStep } from './UiDriver' +import { Dialog, UiDriverStep } from './UiDriver' export function expectStartStep(step: UiDriverStep | void) { expect(step).toBeDefined() expect(step!.type).toEqual('start') } + +export function expectDialogStep(step: UiDriverStep | void, dialog: Dialog) { + expect(step).toBeDefined() + expect(step!.type).toEqual('dialog') + expect((step as { payload: Dialog }).payload).toEqual(dialog) +} From 672d35006466f2c8a09da01bea005adb2eaf550d Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 23:27:36 +0700 Subject: [PATCH 07/12] add stuff --- .../src/ui-driver/UiDriver.ts | 8 ++++++ .../src/ui-driver/UiDriverCctp.test.ts | 28 +++++++++++++++++-- .../src/ui-driver/UiDriverCctp.ts | 14 +++++++++- 3 files changed, 47 insertions(+), 3 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 3e2aab363e..2cd41a8d98 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,7 @@ +export type Dialog = + | 'cctp_deposit' // + | 'cctp_withdrawal' + export type UiDriverContext = { isDepositMode: boolean isSmartContractWallet: boolean @@ -6,6 +10,7 @@ export type UiDriverContext = { export type UiDriverStep = | { type: 'start' } // | { type: 'return' } + | { type: 'dialog'; payload: Dialog } export type UiDriverStepResultFor = // TStep extends { type: 'start' } @@ -13,6 +18,9 @@ export type UiDriverStepResultFor = // : // TStep extends { type: 'return' } ? void + : // + TStep extends { type: 'dialog' } + ? boolean : // never 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 8063db737f..0cbe0c3970 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,7 +1,11 @@ import { stepGeneratorForCctp } from './UiDriverCctp' -import { expectStartStep } from './UiDriverTestUtils' +import { expectStartStep, expectDialogStep } from './UiDriverTestUtils' -it('successfully returns steps', async () => { +it(`successfully returns steps for context: + + isDepositMode=true + isSmartContractWallet=false +`, async () => { const generator = stepGeneratorForCctp({ isDepositMode: true, isSmartContractWallet: false @@ -9,4 +13,24 @@ it('successfully returns steps', async () => { const step1 = await (await generator.next()).value expectStartStep(step1) + + const step2 = await (await generator.next()).value + expectDialogStep(step2, 'cctp_deposit') +}) + +it(`successfully returns steps for context: + + isDepositMode=false + isSmartContractWallet=false +`, async () => { + const generator = stepGeneratorForCctp({ + isDepositMode: false, + isSmartContractWallet: false + }) + + const step1 = await (await generator.next()).value + expectStartStep(step1) + + const step2 = await (await generator.next()).value + expectDialogStep(step2, 'cctp_withdrawal') }) 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 ec1ba295b6..0ec2e20c4e 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,17 @@ import { step, UiDriverStepGenerator } from './UiDriver' -export const stepGeneratorForCctp: UiDriverStepGenerator = async function* () { +export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( + context +) { yield* step({ type: 'start' }) + + const userInput = yield* step({ + type: 'dialog', + payload: context.isDepositMode ? 'cctp_deposit' : 'cctp_withdrawal' + }) + + if (!userInput) { + yield* step({ type: 'return' }) + return + } } From 64bdd95497d1c237458aeb236b56a28b87d21780 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Tue, 4 Mar 2025 23:37:22 +0700 Subject: [PATCH 08/12] nicer tests --- .../src/ui-driver/UiDriverCctp.test.ts | 22 +++++++++++-------- .../src/ui-driver/UiDriverTestUtils.ts | 12 +++++++--- 2 files changed, 22 insertions(+), 12 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 0cbe0c3970..43612b30da 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,9 @@ import { stepGeneratorForCctp } from './UiDriverCctp' -import { expectStartStep, expectDialogStep } from './UiDriverTestUtils' +import { + nextStep, + expectStepStart, + expectStepDialog +} from './UiDriverTestUtils' it(`successfully returns steps for context: @@ -11,11 +15,11 @@ it(`successfully returns steps for context: isSmartContractWallet: false }) - const step1 = await (await generator.next()).value - expectStartStep(step1) + const step1 = await nextStep(generator) + expectStepStart(step1) - const step2 = await (await generator.next()).value - expectDialogStep(step2, 'cctp_deposit') + const step2 = await nextStep(generator) + expectStepDialog(step2, 'cctp_deposit') }) it(`successfully returns steps for context: @@ -28,9 +32,9 @@ it(`successfully returns steps for context: isSmartContractWallet: false }) - const step1 = await (await generator.next()).value - expectStartStep(step1) + const step1 = await nextStep(generator) + expectStepStart(step1) - const step2 = await (await generator.next()).value - expectDialogStep(step2, 'cctp_withdrawal') + const step2 = await nextStep(generator) + expectStepDialog(step2, 'cctp_withdrawal') }) 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 b18d7b2cca..d0d9b7ea99 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts @@ -1,11 +1,17 @@ -import { Dialog, UiDriverStep } from './UiDriver' +import { Dialog, UiDriverStep, UiDriverStepResultFor } from './UiDriver' -export function expectStartStep(step: UiDriverStep | void) { +export async function nextStep( + generator: AsyncGenerator> +) { + return (await generator.next()).value +} + +export function expectStepStart(step: UiDriverStep | void) { expect(step).toBeDefined() expect(step!.type).toEqual('start') } -export function expectDialogStep(step: UiDriverStep | void, dialog: Dialog) { +export function expectStepDialog(step: UiDriverStep | void, dialog: Dialog) { expect(step).toBeDefined() expect(step!.type).toEqual('dialog') expect((step as { payload: Dialog }).payload).toEqual(dialog) From f11811ad40b94ad6048aad518b7b2bdfe8f350fa Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 5 Mar 2025 17:36:57 +0700 Subject: [PATCH 09/12] add scw step --- .../TransferPanel/TransferPanel.tsx | 16 +++++++++++++++ .../src/ui-driver/UiDriver.ts | 3 +++ .../src/ui-driver/UiDriverCctp.ts | 20 +++++++++++++++++-- 3 files changed, 37 insertions(+), 2 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 6dfe3b4102..7f359fc1fa 100644 --- a/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx +++ b/packages/arb-token-bridge-ui/src/components/TransferPanel/TransferPanel.tsx @@ -412,6 +412,22 @@ export function TransferPanel() { `[stepExecutor] "return" step should be handled outside the executor` ) } + + case 'dialog': { + if (step.payload === 'cctp_deposit') { + return console.log('todo: cctp_deposit') + } + + if (step.payload === 'cctp_withdrawal') { + return console.log('todo: cctp_withdrawal') + } + + if (step.payload === 'scw_custom_destination_address_equal') { + return confirmCustomDestinationAddressForSCWallets() + } + + throw Error(`[stepExecutor] unhandled dialog: ${step.payload}`) + } } } 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 2cd41a8d98..b46f07b46c 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts @@ -1,10 +1,13 @@ export type Dialog = | 'cctp_deposit' // | 'cctp_withdrawal' + | 'scw_custom_destination_address_equal' export type UiDriverContext = { isDepositMode: boolean isSmartContractWallet: boolean + walletAddress?: string + destinationAddress?: string } 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 0ec2e20c4e..030ddfc299 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts @@ -1,17 +1,33 @@ import { step, UiDriverStepGenerator } from './UiDriver' +import { addressesEqual } from '../util/AddressUtils' export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( context ) { yield* step({ type: 'start' }) - const userInput = yield* step({ + const userInput1 = yield* step({ type: 'dialog', payload: context.isDepositMode ? 'cctp_deposit' : 'cctp_withdrawal' }) - if (!userInput) { + if (!userInput1) { yield* step({ type: 'return' }) return } + + if ( + context.isSmartContractWallet && + addressesEqual(context.walletAddress, context.destinationAddress) + ) { + const userInput2 = yield* step({ + type: 'dialog', + payload: 'scw_custom_destination_address_equal' + }) + + if (!userInput2) { + yield* step({ type: 'return' }) + return + } + } } From 1f4d72cff7f9f15ad15f882b70438e6f35cf4524 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 5 Mar 2025 18:22:01 +0700 Subject: [PATCH 10/12] nicer utils more assertions --- .../src/ui-driver/UiDriverCctp.test.ts | 26 ++++++++----- .../src/ui-driver/UiDriverTestUtils.ts | 37 +++++++++++++------ 2 files changed, 43 insertions(+), 20 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 43612b30da..6b6d32014a 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,9 +1,5 @@ import { stepGeneratorForCctp } from './UiDriverCctp' -import { - nextStep, - expectStepStart, - expectStepDialog -} from './UiDriverTestUtils' +import { nextStep, expectStep } from './UiDriverTestUtils' it(`successfully returns steps for context: @@ -16,10 +12,16 @@ it(`successfully returns steps for context: }) const step1 = await nextStep(generator) - expectStepStart(step1) + expectStep(step1).hasType('start') const step2 = await nextStep(generator) - expectStepDialog(step2, 'cctp_deposit') + expectStep(step2).hasType('dialog').hasPayload('cctp_deposit') + + const step3 = await nextStep(generator, [false]) + expectStep(step3).hasType('return') + + const step4 = await nextStep(generator) + expectStep(step4).doesNotExist() }) it(`successfully returns steps for context: @@ -33,8 +35,14 @@ it(`successfully returns steps for context: }) const step1 = await nextStep(generator) - expectStepStart(step1) + expectStep(step1).hasType('start') const step2 = await nextStep(generator) - expectStepDialog(step2, 'cctp_withdrawal') + expectStep(step2).hasType('dialog').hasPayload('cctp_withdrawal') + + const step3 = await nextStep(generator, [false]) + expectStep(step3).hasType('return') + + const step4 = await nextStep(generator) + expectStep(step4).doesNotExist() }) 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 d0d9b7ea99..98f2a0746a 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts @@ -1,18 +1,33 @@ -import { Dialog, UiDriverStep, UiDriverStepResultFor } from './UiDriver' +import { UiDriverStep, UiDriverStepResultFor } from './UiDriver' export async function nextStep( - generator: AsyncGenerator> + generator: AsyncGenerator>, + nextStepInputs: [] | [UiDriverStepResultFor] = [] ) { - return (await generator.next()).value + return (await generator.next(...nextStepInputs)).value } -export function expectStepStart(step: UiDriverStep | void) { - expect(step).toBeDefined() - expect(step!.type).toEqual('start') -} +export function expectStep(step: TStep | void) { + return { + hasType(expectedStepType: TStepType) { + expect(step).toBeDefined() + expect(step!.type).toBe(expectedStepType) + return expectStep(step as Extract) + }, + + hasPayload['payload']>( + expectedStepPayload: TExpected + ) { + if (!('payload' in step!)) { + throw new Error(`Step of type "${step!.type}" does not have a payload.`) + } + + expect(step.payload).toBe(expectedStepPayload) + return this + }, -export function expectStepDialog(step: UiDriverStep | void, dialog: Dialog) { - expect(step).toBeDefined() - expect(step!.type).toEqual('dialog') - expect((step as { payload: Dialog }).payload).toEqual(dialog) + doesNotExist() { + expect(step).toBeUndefined() + } + } } From 83d00163fe68f9319e364145f5e6538e7070b009 Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 5 Mar 2025 18:38:30 +0700 Subject: [PATCH 11/12] more tests --- .../src/ui-driver/UiDriverCctp.test.ts | 56 +++++++++++++++++++ 1 file changed, 56 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 6b6d32014a..20b6f80d74 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 @@ -46,3 +46,59 @@ it(`successfully returns steps for context: const step4 = await nextStep(generator) expectStep(step4).doesNotExist() }) + +it(`successfully returns steps for context: + + isDepositMode=false + isSmartContractWallet=false + walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 +`, async () => { + const generator = stepGeneratorForCctp({ + isDepositMode: true, + isSmartContractWallet: false, + walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' + }) + + const step1 = await nextStep(generator) + expectStep(step1).hasType('start') + + const step2 = await nextStep(generator) + expectStep(step2).hasType('dialog').hasPayload('cctp_deposit') + + const step3 = await nextStep(generator, [true]) + expectStep(step3).doesNotExist() +}) + +it(`successfully returns steps for context: + + isDepositMode=false + isSmartContractWallet=true + walletAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + destinationAddress=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 +`, async () => { + const generator = stepGeneratorForCctp({ + isDepositMode: true, + isSmartContractWallet: true, + walletAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', + destinationAddress: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' + }) + + const step1 = await nextStep(generator) + expectStep(step1).hasType('start') + + const step2 = await nextStep(generator) + expectStep(step2).hasType('dialog').hasPayload('cctp_deposit') + + const step3 = await nextStep(generator, [true]) + expectStep(step3) + .hasType('dialog') + .hasPayload('scw_custom_destination_address_equal') + + const step4 = await nextStep(generator, [false]) + expectStep(step4).hasType('return') + + const step5 = await nextStep(generator) + expectStep(step5).doesNotExist() +}) From 9b8371f9ce88c92063d523c8f48fb6239d5c184e Mon Sep 17 00:00:00 2001 From: spsjvc Date: Wed, 5 Mar 2025 18:58:31 +0700 Subject: [PATCH 12/12] compose generators --- .../src/ui-driver/UiDriverCctp.ts | 34 +++++-------------- .../src/ui-driver/UiDriverCommon.ts | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts 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 030ddfc299..a280a37413 100644 --- a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts @@ -1,33 +1,15 @@ import { step, UiDriverStepGenerator } from './UiDriver' -import { addressesEqual } from '../util/AddressUtils' +import { + stepGeneratorForDialog, + stepGeneratorForDialogToCheckScwDestinationAddress +} from './UiDriverCommon' export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( context ) { - yield* step({ type: 'start' }) - - const userInput1 = yield* step({ - type: 'dialog', - payload: context.isDepositMode ? 'cctp_deposit' : 'cctp_withdrawal' - }) - - if (!userInput1) { - yield* step({ type: 'return' }) - return - } + const deposit = context.isDepositMode - if ( - context.isSmartContractWallet && - addressesEqual(context.walletAddress, context.destinationAddress) - ) { - const userInput2 = yield* step({ - type: 'dialog', - payload: 'scw_custom_destination_address_equal' - }) - - if (!userInput2) { - yield* step({ type: 'return' }) - return - } - } + yield* step({ type: 'start' }) + yield* stepGeneratorForDialog(`cctp_${deposit ? 'deposit' : 'withdrawal'}`) + yield* stepGeneratorForDialogToCheckScwDestinationAddress(context) } diff --git a/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts new file mode 100644 index 0000000000..1bbc5ae7db --- /dev/null +++ b/packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts @@ -0,0 +1,34 @@ +import { + step, + UiDriverStep, + UiDriverStepResultFor, + UiDriverStepGenerator, + Dialog +} from './UiDriver' +import { addressesEqual } from '../util/AddressUtils' + +export type UiDriverStepGeneratorForDialog< + TStep extends UiDriverStep = UiDriverStep +> = ( + dialog: Dialog +) => AsyncGenerator> + +export const stepGeneratorForDialog: UiDriverStepGeneratorForDialog = + async function* (payload: Dialog) { + const userInput = yield* step({ type: 'dialog', payload }) + + if (!userInput) { + yield* step({ type: 'return' }) + return + } + } + +export const stepGeneratorForDialogToCheckScwDestinationAddress: UiDriverStepGenerator = + async function* (context) { + if ( + context.isSmartContractWallet && + addressesEqual(context.walletAddress, context.destinationAddress) + ) { + yield* stepGeneratorForDialog('scw_custom_destination_address_equal') + } + }