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 3e2aab363e..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,11 +1,19 @@ +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 = | { type: 'start' } // | { type: 'return' } + | { type: 'dialog'; payload: Dialog } export type UiDriverStepResultFor = // TStep extends { type: 'start' } @@ -13,6 +21,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..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 @@ -1,12 +1,104 @@ import { stepGeneratorForCctp } from './UiDriverCctp' -import { expectStartStep } from './UiDriverTestUtils' +import { nextStep, expectStep } 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 }) - const step1 = await (await generator.next()).value - expectStartStep(step1) + 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, [false]) + expectStep(step3).hasType('return') + + const step4 = await nextStep(generator) + expectStep(step4).doesNotExist() +}) + +it(`successfully returns steps for context: + + isDepositMode=false + isSmartContractWallet=false +`, async () => { + const generator = stepGeneratorForCctp({ + isDepositMode: false, + isSmartContractWallet: false + }) + + const step1 = await nextStep(generator) + expectStep(step1).hasType('start') + + const step2 = await nextStep(generator) + 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() +}) + +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() }) 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..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,5 +1,15 @@ import { step, UiDriverStepGenerator } from './UiDriver' +import { + stepGeneratorForDialog, + stepGeneratorForDialogToCheckScwDestinationAddress +} from './UiDriverCommon' + +export const stepGeneratorForCctp: UiDriverStepGenerator = async function* ( + context +) { + const deposit = context.isDepositMode -export const stepGeneratorForCctp: UiDriverStepGenerator = async function* () { 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') + } + } 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..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,6 +1,33 @@ -import { UiDriverStep } from './UiDriver' +import { UiDriverStep, UiDriverStepResultFor } from './UiDriver' -export function expectStartStep(step: UiDriverStep | void) { - expect(step).toBeDefined() - expect(step!.type).toEqual('start') +export async function nextStep( + generator: AsyncGenerator>, + nextStepInputs: [] | [UiDriverStepResultFor] = [] +) { + return (await generator.next(...nextStepInputs)).value +} + +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 + }, + + doesNotExist() { + expect(step).toBeUndefined() + } + } }