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

🗞️ Standalone CLI: Moving wiring logic into a flow #1010

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/brown-kings-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@layerzerolabs/ua-devtools-evm-hardhat": patch
"@layerzerolabs/devtools": patch
---

Move the wiring logic into a flow
1 change: 1 addition & 0 deletions packages/devtools/src/flows/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './config.execute'
export * from './config.load'
export * from './sign.and.send'
export * from './wire'
91 changes: 91 additions & 0 deletions packages/devtools/src/flows/wire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { createLogger, printJson, pluralizeNoun, type Logger } from '@layerzerolabs/io-devtools'

import type { ConfigExecuteFlow } from './config.execute'
import type { SignAndSendFlow } from './sign.and.send'
import type { OmniGraph } from '@/omnigraph'
import { formatOmniTransaction, type OmniTransaction, type SignAndSendResult } from '@/transactions'
import { printRecords } from '@layerzerolabs/io-devtools/swag'

export interface CreateWireFlowArgs<TOmniGraph extends OmniGraph> {
logger?: Logger
executeConfig: ConfigExecuteFlow<TOmniGraph>
signAndSend: SignAndSendFlow
}

export interface WireFlowArgs<TOmniGraph extends OmniGraph> {
graph: TOmniGraph
assert?: boolean
dryRun?: boolean
}

export const createWireFlow =
<TOmniGraph extends OmniGraph>({
logger = createLogger(),
executeConfig,
signAndSend,
}: CreateWireFlowArgs<TOmniGraph>) =>
async ({ graph, assert = false, dryRun = false }: WireFlowArgs<TOmniGraph>): Promise<SignAndSendResult> => {
if (assert) {
logger.info(`Running in assertion mode`)
} else if (dryRun) {
logger.info(`Running in dry run mode`)
}

// At this point we are ready to create the list of transactions
logger.verbose(`Creating a list of wiring transactions`)

// We'll get the list of OmniTransactions using the config execution flow
const transactions: OmniTransaction[] = await executeConfig({ graph })

// Flood users with debug output
logger.verbose(`Created a list of wiring transactions`)
logger.debug(`Following transactions are necessary:\n\n${printJson(transactions)}`)

// If there are no transactions that need to be executed, we'll just exit
if (transactions.length === 0) {
logger.info(`The OApp is wired, no action is necessary`)

return [[], [], []]
}

// If we are in a dry run mode, we print our the results and exit
if (dryRun) {
printRecords(transactions.map(formatOmniTransaction))

return [[], [], transactions]
}

// If we are in an assertion mode, we make sure there are no pending transactions
if (assert) {
// Let the user know something's about to go down
logger.error(`The OApp is not fully wired, following transactions are necessary:`)

// Print the outstanding transactions
printRecords(transactions.map(formatOmniTransaction))

// Mark the process as failed (if not already marked)
//
// TODO This is a bit ugly since we might not be running this in a standalone process
// so we might want to move the assert functionality outside of this flow
process.exitCode = process.exitCode || 1

// Return the list of pending transactions
return [[], [], transactions]
}

// Tell the user about the transactions
logger.info(
pluralizeNoun(
transactions.length,
`There is 1 transaction required to configure the OApp`,
`There are ${transactions.length} transactions required to configure the OApp`
)
)

// Now sign & send the transactions
const signAndSendResult: SignAndSendResult = await signAndSend({
transactions,
})

return signAndSendResult
}
108 changes: 30 additions & 78 deletions packages/ua-devtools-evm-hardhat/src/tasks/oapp/wire/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { task } from 'hardhat/config'
import type { ActionType } from 'hardhat/types'
import { SUBTASK_LZ_OAPP_CONFIG_LOAD, SUBTASK_LZ_OAPP_WIRE_CONFIGURE, TASK_LZ_OAPP_WIRE } from '@/constants/tasks'
import { createLogger, setDefaultLogLevel, printJson, pluralizeNoun } from '@layerzerolabs/io-devtools'
import { createLogger, printJson, setDefaultLogLevel } from '@layerzerolabs/io-devtools'
import { OAppOmniGraph } from '@layerzerolabs/ua-devtools'
import {
types,
SUBTASK_LZ_SIGN_AND_SEND,
createGnosisSignerFactory,
createSignerFactory,
formatOmniTransaction,
} from '@layerzerolabs/devtools-evm-hardhat'
import { OmniTransaction } from '@layerzerolabs/devtools'
import { printLogo, printRecords } from '@layerzerolabs/io-devtools/swag'
import { createWireFlow } from '@layerzerolabs/devtools'
import { printLogo } from '@layerzerolabs/io-devtools/swag'
import type { SignAndSendResult } from '@layerzerolabs/devtools'
import type { SubtaskConfigureTaskArgs } from './types'
import type { SignAndSendTaskArgs } from '@layerzerolabs/devtools-evm-hardhat/tasks'

import './subtask.configure'
import { OAppOmniGraphHardhatSchema } from '@/oapp'
import { SubtaskLoadConfigTaskArgs } from '@/tasks/oapp/types'
import type { SubtaskConfigureTaskArgs, SubtaskLoadConfigTaskArgs } from '@/tasks/oapp/types'
import type { SignerDefinition } from '@layerzerolabs/devtools-evm'
import type { SignAndSendTaskArgs } from '@layerzerolabs/devtools-evm-hardhat/tasks'

import './subtask.configure'

interface TaskArgs {
oappConfig: string
Expand Down Expand Up @@ -74,90 +73,43 @@ const action: ActionType<TaskArgs> = async (
// And we'll create a logger for ourselves
const logger = createLogger()

if (assert) {
logger.info(`Running in assertion mode`)
} else if (dryRun) {
logger.info(`Running in dry run mode`)
}

// Now we can load and validate the config
//
// To maintain compatibility with the legacy hardhat-based CLI,
// we use a subtask to do this
logger.debug(`Using ${loadConfigSubtask} subtask to load the config`)
const graph: OAppOmniGraph = await hre.run(loadConfigSubtask, {
configPath: oappConfigPath,
schema: OAppOmniGraphHardhatSchema,
task: TASK_LZ_OAPP_WIRE,
} satisfies SubtaskLoadConfigTaskArgs)

// At this point we are ready to create the list of transactions
logger.verbose(`Creating a list of wiring transactions`)

// We'll get the list of OmniTransactions using a subtask to allow for developers
// to use this as a hook and extend the configuration
logger.debug(`Using ${configureSubtask} subtask to get the configuration`)
const transactions: OmniTransaction[] = await hre.run(configureSubtask, {
graph,
} satisfies SubtaskConfigureTaskArgs)

// Flood users with debug output
logger.verbose(`Created a list of wiring transactions`)
logger.debug(`Following transactions are necessary:\n\n${printJson(transactions)}`)

// If there are no transactions that need to be executed, we'll just exit
if (transactions.length === 0) {
logger.info(`The OApp is wired, no action is necessary`)

return [[], [], []]
} else if (assert) {
// If we are in assertion mode, we'll print out the transactions and exit with code 1
// if there is anything left to configure
logger.error(`The OApp is not fully wired, following transactions are necessary:`)

// Print the outstanding transactions
printRecords(transactions.map(formatOmniTransaction))

// And set the exit code to failure
process.exitCode = process.exitCode || 1

return [[], [], transactions]
}

// If we are in dry run mode, we'll just print the transactions and exit
if (dryRun) {
printRecords(transactions.map(formatOmniTransaction))

return [[], [], transactions]
}

// Tell the user about the transactions
logger.info(
pluralizeNoun(
transactions.length,
`There is 1 transaction required to configure the OApp`,
`There are ${transactions.length} transactions required to configure the OApp`
)
)

// Now let's create the signer
logger.debug(
signer == null ? `Creating a default signer` : `Creating a signer based on definition: ${printJson(signer)}`
)
const createSigner = safe ? createGnosisSignerFactory(signer) : createSignerFactory(signer)

// Now sign & send the transactions
logger.debug(`Using ${signAndSendSubtask} subtask to sign & send the transactions`)
const signAndSendResult: SignAndSendResult = await hre.run(signAndSendSubtask, {
transactions,
ci,
createSigner,
} satisfies SignAndSendTaskArgs)

// Mark the process as unsuccessful if there were any errors (only if it has not yet been marked as such)
const [, failed] = signAndSendResult
if (failed.length !== 0) {
process.exitCode = process.exitCode || 1
}

return signAndSendResult
// Then create the wire flow
const wireFlow = createWireFlow({
logger,
// We use hardhat subtasks to provide the option to override certain behaviors on a more granular level
executeConfig: ({ graph }) => hre.run(configureSubtask, { graph } satisfies SubtaskConfigureTaskArgs),
signAndSend: ({ transactions }) =>
hre.run(signAndSendSubtask, {
ci,
logger,
createSigner,
transactions,
} satisfies SignAndSendTaskArgs),
})

// And run the wire flow
return wireFlow({
graph,
assert,
dryRun,
})
}

task(TASK_LZ_OAPP_WIRE, 'Wire LayerZero OApp', action)
Expand Down
10 changes: 10 additions & 0 deletions tests/ua-devtools-evm-hardhat-test/test/task/oapp/wire.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ describe(`task ${TASK_LZ_OAPP_WIRE}`, () => {
// Helper matcher object that checks for OmniTransaction objects
const expectTransaction = { data: expect.any(String), point: expectOmniPoint, description: expect.any(String) }
const expectTransactionWithReceipt = { receipt: expect.any(Object), transaction: expectTransaction }
const expectLogger = expect.objectContaining({
info: expect.any(Function),
warn: expect.any(Function),
error: expect.any(Function),
debug: expect.any(Function),
verbose: expect.any(Function),
})

const CONFIGS_BASE_DIR = relative(cwd(), join(__dirname, '__data__', 'configs'))
const configPathFixture = (fileName: string): string => {
Expand Down Expand Up @@ -283,6 +290,7 @@ describe(`task ${TASK_LZ_OAPP_WIRE}`, () => {
transactions: expect.any(Array),
ci: false,
createSigner,
logger: expectLogger,
},
{},
undefined
Expand Down Expand Up @@ -313,6 +321,7 @@ describe(`task ${TASK_LZ_OAPP_WIRE}`, () => {
transactions: expect.any(Array),
ci: false,
createSigner,
expectLogger,
},
{},
undefined
Expand Down Expand Up @@ -342,6 +351,7 @@ describe(`task ${TASK_LZ_OAPP_WIRE}`, () => {
transactions: expect.any(Array),
ci: false,
createSigner,
expectLogger,
},
{},
undefined
Expand Down
Loading