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

feat: add pending transactions to strategy #5047

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion .changeset/polite-bulldogs-sit.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@hyperlane-xyz/cli": patch
'@hyperlane-xyz/cli': patch
---

Fix strategy flag propagation
6 changes: 6 additions & 0 deletions .changeset/popular-glasses-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---

Implement pending state on tx submitters
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"preserveSymlinks": true,
"preserveWatchOutput": true,
"pretty": false,
Expand Down
2 changes: 0 additions & 2 deletions typescript/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { relayerCommand } from './src/commands/relayer.js';
import { sendCommand } from './src/commands/send.js';
import { statusCommand } from './src/commands/status.js';
import { strategyCommand } from './src/commands/strategy.js';
import { submitCommand } from './src/commands/submit.js';
import { validatorCommand } from './src/commands/validator.js';
import { warpCommand } from './src/commands/warp.js';
import { contextMiddleware, signerMiddleware } from './src/context/context.js';
Expand Down Expand Up @@ -68,7 +67,6 @@ try {
.command(sendCommand)
.command(statusCommand)
.command(strategyCommand)
.command(submitCommand)
.command(validatorCommand)
.command(warpCommand)
.version(VERSION)
Expand Down
1 change: 1 addition & 0 deletions typescript/cli/src/commands/signCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const SIGN_COMMANDS = [
'status',
'submit',
'relayer',
'pending', // gnosis safe service auth
];

export function isSignCommand(argv: any): boolean {
Expand Down
90 changes: 87 additions & 3 deletions typescript/cli/src/commands/strategy.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { stringify as yamlStringify } from 'yaml';
import { CommandModule } from 'yargs';

import { objMap, promiseObjAll } from '@hyperlane-xyz/utils';

import {
createStrategyConfig,
readChainSubmissionStrategyConfig,
readSubmissionStrategyConfig,
} from '../config/strategy.js';
import { runSubmit } from '../config/submit.js';
import { CommandModuleWithWriteContext } from '../context/types.js';
import { log, logCommandHeader } from '../logger.js';
import { indentYamlOrJson } from '../utils/files.js';
import { readChainSubmissionStrategy } from '../deploy/warp.js';
import { log, logCommandHeader, logGray } from '../logger.js';
import { getSubmitterBuilder } from '../submit/submit.js';
import {
indentYamlOrJson,
logYamlIfUnderMaxLines,
writeYamlOrJson,
} from '../utils/files.js';
import { maskSensitiveData } from '../utils/output.js';

import {
DEFAULT_STRATEGY_CONFIG_PATH,
outputFileCommandOption,
strategyCommandOption,
transactionsCommandOption,
} from './options.js';

/**
Expand All @@ -23,7 +34,13 @@ export const strategyCommand: CommandModule = {
command: 'strategy',
describe: 'Manage Hyperlane deployment strategies',
builder: (yargs) =>
yargs.command(init).command(read).version(false).demandCommand(),
yargs
.command(init)
.command(read)
.command(pending)
.command(submit)
.version(false)
.demandCommand(),
handler: () => log('Command required'),
};

Expand Down Expand Up @@ -68,3 +85,70 @@ export const read: CommandModuleWithWriteContext<{
process.exit(0);
},
};

export const pending: CommandModuleWithWriteContext<{
strategy: string;
transactions: string;
}> = {
command: 'pending',
describe: 'Fetches strategy pending transactions',
builder: {
transactions: {
...transactionsCommandOption,
demandOption: false,
default: './generated/transactions.yaml',
},
strategy: {
...strategyCommandOption,
demandOption: true,
},
},
handler: async ({ context, transactions }) => {
logCommandHeader(`Hyperlane Strategy Pending`);

const chainStrategy = readChainSubmissionStrategy(context.strategyPath!);

const pending = await promiseObjAll(
objMap(chainStrategy, async (_, submissionStrategy) => {
const submitter = await getSubmitterBuilder({
submissionStrategy,
multiProvider: context.multiProvider,
});
return submitter.pending();
}),
);

logYamlIfUnderMaxLines(pending);

logGray(`Writing pending transactions to ${transactions}`);
writeYamlOrJson(transactions, pending);
},
};

export const submit: CommandModuleWithWriteContext<{
strategy: string;
transactions: string;
receipts: string;
}> = {
command: 'submit',
describe: 'Submit transactions',
builder: {
transactions: transactionsCommandOption,
strategy: strategyCommandOption,
receipts: outputFileCommandOption('./generated/transactions/receipts.yaml'),
},
handler: async ({
context,
transactions,
strategy: strategyUrl,
receipts,
}) => {
const submissionStrategy = readSubmissionStrategyConfig(strategyUrl);
await runSubmit({
context,
transactionsFilepath: transactions,
receiptsFilepath: receipts,
submissionStrategy,
});
},
};
69 changes: 0 additions & 69 deletions typescript/cli/src/commands/submit.ts

This file was deleted.

10 changes: 10 additions & 0 deletions typescript/cli/src/config/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { stringify as yamlStringify } from 'yaml';
import {
ChainSubmissionStrategy,
ChainSubmissionStrategySchema,
SubmissionStrategy,
SubmissionStrategySchema,
TxSubmitterType,
} from '@hyperlane-xyz/sdk';
import {
Expand All @@ -24,6 +26,14 @@ import {
} from '../utils/files.js';
import { maskSensitiveData } from '../utils/output.js';

export function readSubmissionStrategyConfig(
filePath: string,
): SubmissionStrategy {
log(`Reading submission strategy in ${filePath}`);
const strategyConfig = readYamlOrJson<SubmissionStrategy>(filePath);
return SubmissionStrategySchema.parse(strategyConfig);
}

/**
* Reads and validates a chain submission strategy configuration from a file
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ export interface TxSubmitterInterface<TProtocol extends ProtocolType> {
| ProtocolTypedReceipt<TProtocol>['receipt'][]
| void
>;

pending(): Promise<
Annotated<ProtocolTypedTransaction<TProtocol>['transaction']>[]
>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,10 @@ export class TxSubmitterBuilder<TProtocol extends ProtocolType>

return txReceipts;
}

public async pending(): Promise<
Annotated<ProtocolTypedTransaction<TProtocol>['transaction']>[]
> {
return this.currentSubmitter.pending();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import SafeApiKit from '@safe-global/api-kit';
import Safe from '@safe-global/protocol-kit';
import { SafeTransaction } from '@safe-global/safe-core-sdk-types';
import { BigNumber } from 'ethers';
import { Logger } from 'pino';

import { Address, assert, rootLogger } from '@hyperlane-xyz/utils';
Expand All @@ -24,8 +27,8 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
constructor(
public readonly multiProvider: MultiProvider,
public readonly props: EV5GnosisSafeTxSubmitterProps,
private safe: any,
private safeService: any,
private safe: Safe.default,
private safeService: SafeApiKit.default,
) {}

static async create(
Expand Down Expand Up @@ -74,6 +77,10 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
);
const submitterChainId = this.multiProvider.getChainId(this.props.chain);
assert(chainId, 'Invalid AnnotatedEV5Transaction: chainId is required');
// TODO: fix types
assert(to, 'Invalid AnnotatedEV5Transaction: to is required');
assert(data, 'Invalid AnnotatedEV5Transaction: data is required');

assert(
chainId === submitterChainId,
`Invalid AnnotatedEV5Transaction: Cannot submit tx for chain ID ${chainId} to submitter for chain ID ${submitterChainId}.`,
Expand All @@ -84,6 +91,24 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
});
}

public async pending(nonce?: number): Promise<AnnotatedEV5Transaction[]> {
const safeTransactions = await this.safeService.getPendingTransactions(
this.props.safeAddress,
nonce,
);
const chainId = this.multiProvider.getEvmChainId(this.props.chain);
const from = this.props.safeAddress;
return safeTransactions.results.map(
(tx): AnnotatedEV5Transaction => ({
chainId,
from,
to: tx.to,
data: tx.data,
value: BigNumber.from(tx.value),
}),
);
}

public async submit(...txs: AnnotatedEV5Transaction[]): Promise<any> {
return this.proposeIndividualTransactions(txs);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@ export class EV5JsonRpcTxSubmitter implements EV5TxSubmitterInterface {
}
return receipts;
}

public async pending(): Promise<AnnotatedEV5Transaction[]> {
// TODO: mempool?
return [];
}
}
Loading