Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add ui driver for cctp (part 2) #2310

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
}
}
}

Expand Down
11 changes: 11 additions & 0 deletions packages/arb-token-bridge-ui/src/ui-driver/UiDriver.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
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 UiDriverStep> = //
TStep extends { type: 'start' }
? void
: //
TStep extends { type: 'return' }
? void
: //
TStep extends { type: 'dialog' }
? boolean
: //
never

Expand Down
100 changes: 96 additions & 4 deletions packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.test.ts
Original file line number Diff line number Diff line change
@@ -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()
})
12 changes: 11 additions & 1 deletion packages/arb-token-bridge-ui/src/ui-driver/UiDriverCctp.ts
Original file line number Diff line number Diff line change
@@ -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)
}
34 changes: 34 additions & 0 deletions packages/arb-token-bridge-ui/src/ui-driver/UiDriverCommon.ts
Original file line number Diff line number Diff line change
@@ -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<TStep, void, UiDriverStepResultFor<TStep>>

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')
}
}
35 changes: 31 additions & 4 deletions packages/arb-token-bridge-ui/src/ui-driver/UiDriverTestUtils.ts
Original file line number Diff line number Diff line change
@@ -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<TStep extends UiDriverStep>(
generator: AsyncGenerator<TStep, void, UiDriverStepResultFor<TStep>>,
nextStepInputs: [] | [UiDriverStepResultFor<TStep>] = []
) {
return (await generator.next(...nextStepInputs)).value
}

export function expectStep<TStep extends UiDriverStep>(step: TStep | void) {
return {
hasType<TStepType extends TStep['type']>(expectedStepType: TStepType) {
expect(step).toBeDefined()
expect(step!.type).toBe(expectedStepType)
return expectStep(step as Extract<TStep, { type: TStepType }>)
},

hasPayload<TExpected extends Extract<TStep, { payload: any }>['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()
}
}
}
Loading