fix: reuse SDK tx type in submitters (#4642)

### Description

Updating the types in the submitters because they were using their own
types instead of the existing ones for some reason.

### Drive-by changes

move `createTransferOwnershipTx` from the abstract hyperlane module to
the evm module deployer, as it's EVM specific

### Related issues

na

### Backward compatibility

yes

### Testing

ci

---------

Signed-off-by: pbio <10051819+paulbalaji@users.noreply.github.com>
pull/4656/head
Paul Balaji 1 month ago committed by GitHub
parent 45eb3ae5e3
commit fcfe91113f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      .changeset/gorgeous-rivers-search.md
  2. 18
      typescript/cli/src/config/submit.ts
  3. 47
      typescript/sdk/src/core/AbstractHyperlaneModule.ts
  4. 3
      typescript/sdk/src/core/EvmCoreModule.ts
  5. 43
      typescript/sdk/src/deploy/EvmModuleDeployer.ts
  6. 10
      typescript/sdk/src/index.ts
  7. 31
      typescript/sdk/src/providers/transactions/schemas.test.ts
  8. 11
      typescript/sdk/src/providers/transactions/schemas.ts
  9. 6
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxBuilder.ts
  10. 9
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts
  11. 4
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts
  12. 4
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.ts
  13. 14
      typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts
  14. 14
      typescript/sdk/src/providers/transactions/types.ts
  15. 3
      typescript/sdk/src/token/EvmERC20WarpModule.ts

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': patch
'@hyperlane-xyz/sdk': patch
---
Reuse SDK transaction typings in tx submitters

@ -1,11 +1,9 @@
import { stringify as yamlStringify } from 'yaml';
import {
PopulatedTransactions,
PopulatedTransactionsSchema,
AnnotatedEV5Transaction,
SubmissionStrategy,
} from '@hyperlane-xyz/sdk';
import { PopulatedTransaction } from '@hyperlane-xyz/sdk';
import { MultiProvider } from '@hyperlane-xyz/sdk';
import { assert, errorToString } from '@hyperlane-xyz/utils';
@ -72,20 +70,20 @@ export async function runSubmit({
*/
function getChainFromTxs(
multiProvider: MultiProvider,
transactions: PopulatedTransactions,
transactions: AnnotatedEV5Transaction[],
) {
const firstTransaction = transactions[0];
assert(firstTransaction.chainId, 'Invalid transaction: chainId is required');
const sameChainIds = transactions.every(
(t: PopulatedTransaction) => t.chainId === firstTransaction.chainId,
(t: AnnotatedEV5Transaction) => t.chainId === firstTransaction.chainId,
);
assert(sameChainIds, 'Transactions must be submitted on the same chains');
return multiProvider.getChainName(firstTransaction.chainId);
}
function getTransactions(transactionsFilepath: string): PopulatedTransactions {
const transactionsFileContent = readYamlOrJson<any[]>(
transactionsFilepath.trim(),
);
return PopulatedTransactionsSchema.parse(transactionsFileContent);
function getTransactions(
transactionsFilepath: string,
): AnnotatedEV5Transaction[] {
return readYamlOrJson<AnnotatedEV5Transaction[]>(transactionsFilepath.trim());
}

@ -1,17 +1,8 @@
import { Logger } from 'pino';
import { Ownable__factory } from '@hyperlane-xyz/core';
import {
Address,
Annotated,
ProtocolType,
eqAddress,
} from '@hyperlane-xyz/utils';
import { Annotated, ProtocolType } from '@hyperlane-xyz/utils';
import {
AnnotatedEV5Transaction,
ProtocolTypedTransaction,
} from '../providers/ProviderType.js';
import { ProtocolTypedTransaction } from '../providers/ProviderType.js';
import { ChainNameOrId } from '../types.js';
export type HyperlaneModuleParams<
@ -43,40 +34,6 @@ export abstract class HyperlaneModule<
config: TConfig,
): Promise<Annotated<ProtocolTypedTransaction<TProtocol>['transaction'][]>>;
/**
* Transfers ownership of a contract to a new owner.
*
* @param actualOwner - The current owner of the contract.
* @param expectedOwner - The expected new owner of the contract.
* @param deployedAddress - The address of the deployed contract.
* @param chainId - The chain ID of the network the contract is deployed on.
* @returns An array of annotated EV5 transactions that need to be executed to update the owner.
*/
static createTransferOwnershipTx(params: {
actualOwner: Address;
expectedOwner: Address;
deployedAddress: Address;
chainId: number;
}): AnnotatedEV5Transaction[] {
const { actualOwner, expectedOwner, deployedAddress, chainId } = params;
const updateTransactions: AnnotatedEV5Transaction[] = [];
if (eqAddress(actualOwner, expectedOwner)) {
return [];
}
updateTransactions.push({
annotation: `Transferring ownership of ${deployedAddress} from current owner ${actualOwner} to new owner ${expectedOwner}`,
chainId,
to: deployedAddress,
data: Ownable__factory.createInterface().encodeFunctionData(
'transferOwnership(address)',
[expectedOwner],
),
});
return updateTransactions;
}
// /*
// Types and static methods can be challenging. Ensure each implementation includes a static create function.
// Currently, include TConfig to maintain the structure for ISM/Hook configurations.

@ -17,6 +17,7 @@ import {
} from '../contracts/types.js';
import { DeployedCoreAddresses } from '../core/schemas.js';
import { CoreConfig } from '../core/types.js';
import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js';
import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js';
import {
ProxyFactoryFactories,
@ -201,7 +202,7 @@ export class EvmCoreModule extends HyperlaneModule<
actualConfig: CoreConfig,
expectedConfig: CoreConfig,
): AnnotatedEV5Transaction[] {
return EvmCoreModule.createTransferOwnershipTx({
return EvmModuleDeployer.createTransferOwnershipTx({
actualOwner: actualConfig.owner,
expectedOwner: expectedConfig.owner,
deployedAddress: this.args.addresses.mailbox,

@ -2,15 +2,22 @@ import { ethers } from 'ethers';
import { Logger } from 'pino';
import {
Ownable__factory,
StaticAddressSetFactory,
StaticThresholdAddressSetFactory,
TransparentUpgradeableProxy__factory,
} from '@hyperlane-xyz/core';
import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js';
import { Address, addBufferToGasLimit, rootLogger } from '@hyperlane-xyz/utils';
import {
Address,
addBufferToGasLimit,
eqAddress,
rootLogger,
} from '@hyperlane-xyz/utils';
import { HyperlaneContracts, HyperlaneFactories } from '../contracts/types.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { AnnotatedEV5Transaction } from '../providers/ProviderType.js';
import { ChainMap, ChainName } from '../types.js';
import { isProxy, proxyConstructorArgs } from './proxy.js';
@ -315,4 +322,38 @@ export class EvmModuleDeployer<Factories extends HyperlaneFactories> {
return address;
}
/**
* Transfers ownership of a contract to a new owner.
*
* @param actualOwner - The current owner of the contract.
* @param expectedOwner - The expected new owner of the contract.
* @param deployedAddress - The address of the deployed contract.
* @param chainId - The chain ID of the network the contract is deployed on.
* @returns An array of annotated EV5 transactions that need to be executed to update the owner.
*/
public static createTransferOwnershipTx(params: {
actualOwner: Address;
expectedOwner: Address;
deployedAddress: Address;
chainId: number;
}): AnnotatedEV5Transaction[] {
const { actualOwner, expectedOwner, deployedAddress, chainId } = params;
const updateTransactions: AnnotatedEV5Transaction[] = [];
if (eqAddress(actualOwner, expectedOwner)) {
return [];
}
updateTransactions.push({
annotation: `Transferring ownership of ${deployedAddress} from current owner ${actualOwner} to new owner ${expectedOwner}`,
chainId,
to: deployedAddress,
data: Ownable__factory.createInterface().encodeFunctionData(
'transferOwnership(address)',
[expectedOwner],
),
});
return updateTransactions;
}
}

@ -312,15 +312,7 @@ export {
excludeProviderMethods,
} from './providers/SmartProvider/ProviderMethods.js';
export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js';
export {
PopulatedTransactionSchema,
PopulatedTransactionsSchema,
} from './providers/transactions/schemas.js';
export {
CallData,
PopulatedTransaction,
PopulatedTransactions,
} from './providers/transactions/types.js';
export { CallData } from './providers/transactions/types.js';
export { SubmitterMetadataSchema } from './providers/transactions/submitter/schemas.js';
export { TxSubmitterInterface } from './providers/transactions/submitter/TxSubmitterInterface.js';

@ -2,43 +2,16 @@ import { expect } from 'chai';
import { Address } from '@hyperlane-xyz/utils';
import { CallDataSchema, PopulatedTransactionSchema } from './schemas.js';
import { CallData, PopulatedTransaction } from './types.js';
import { CallDataSchema } from './schemas.js';
import { CallData } from './types.js';
describe('transactions schemas', () => {
const ADDRESS_MOCK: Address = '0x1234567890123456789012345678901234567890';
const DATA_MOCK: string = '0xabcdef';
const CHAIN_ID_MOCK: number = 1;
const VALUE_MOCK: string = '100';
const INVALID_ADDRESS: Address = '0x1';
describe('PopulatedTransactionSchema', () => {
it('should parse valid PopulatedTransaction', () => {
const validPopulatedTransaction: PopulatedTransaction = {
to: ADDRESS_MOCK,
data: DATA_MOCK,
chainId: CHAIN_ID_MOCK,
};
const result = PopulatedTransactionSchema.safeParse(
validPopulatedTransaction,
);
expect(result.success).to.be.true;
});
it('should fail parsing invalid PopulatedTransaction', () => {
const invalidPopulatedTransaction: PopulatedTransaction = {
to: INVALID_ADDRESS,
data: DATA_MOCK,
chainId: CHAIN_ID_MOCK,
};
const result = PopulatedTransactionSchema.safeParse(
invalidPopulatedTransaction,
);
expect(result.success).to.be.false;
});
});
describe('CallDataSchema', () => {
it('should parse valid CallData', () => {
const validCallData: CallData = {

@ -4,17 +4,6 @@ import { ZHash } from '../../metadata/customZodTypes.js';
export const BigNumberSchema = z.string();
export const PopulatedTransactionSchema = z.object({
to: ZHash,
data: z.string(),
chainId: z.number(),
});
export const PopulatedTransactionsSchema =
PopulatedTransactionSchema.array().refine((txs) => txs.length > 0, {
message: 'Populated Transactions cannot be empty',
});
export const CallDataSchema = z.object({
to: ZHash,
data: z.string(),

@ -6,7 +6,7 @@ import { assert } from '@hyperlane-xyz/utils';
// @ts-ignore
import { getSafe, getSafeService } from '../../../../utils/gnosisSafe.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransaction, PopulatedTransactions } from '../../types.js';
import { AnnotatedEV5Transaction } from '../../../ProviderType.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5GnosisSafeTxSubmitter } from './EV5GnosisSafeTxSubmitter.js';
@ -58,10 +58,10 @@ export class EV5GnosisSafeTxBuilder extends EV5GnosisSafeTxSubmitter {
*
* @param txs - An array of populated transactions
*/
public async submit(...txs: PopulatedTransactions): Promise<any> {
public async submit(...txs: AnnotatedEV5Transaction[]): Promise<any> {
const transactions: SafeTransactionData[] = await Promise.all(
txs.map(
async (tx: PopulatedTransaction) =>
async (tx: AnnotatedEV5Transaction) =>
(
await this.createSafeTransaction(tx)
).data,

@ -7,7 +7,7 @@ import { Address, assert, rootLogger } from '@hyperlane-xyz/utils';
// @ts-ignore
import { canProposeSafeTransactions, getSafe, getSafeService } from '../../../../utils/gnosisSafe.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransaction, PopulatedTransactions } from '../../types.js';
import { AnnotatedEV5Transaction } from '../../../ProviderType.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js';
@ -68,10 +68,11 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
data,
value,
chainId,
}: PopulatedTransaction): Promise<SafeTransaction> {
}: AnnotatedEV5Transaction): Promise<SafeTransaction> {
const nextNonce: number = await this.safeService.getNextNonce(
this.props.safeAddress,
);
assert(chainId, 'Invalid PopulatedTransaction: chainId is required');
const txChain = this.multiProvider.getChainName(chainId);
assert(
txChain === this.props.chain,
@ -83,11 +84,11 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
});
}
public async submit(...txs: PopulatedTransactions): Promise<any> {
public async submit(...txs: AnnotatedEV5Transaction[]): Promise<any> {
return this.proposeIndividualTransactions(txs);
}
private async proposeIndividualTransactions(txs: PopulatedTransactions) {
private async proposeIndividualTransactions(txs: AnnotatedEV5Transaction[]) {
const safeTransactions: SafeTransaction[] = [];
for (const tx of txs) {
const safeTransaction = await this.createSafeTransaction(tx);

@ -8,7 +8,7 @@ import {
stopImpersonatingAccount,
} from '../../../../utils/fork.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransactions } from '../../types.js';
import { AnnotatedEV5Transaction } from '../../../ProviderType.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5JsonRpcTxSubmitter } from './EV5JsonRpcTxSubmitter.js';
@ -30,7 +30,7 @@ export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter {
}
public async submit(
...txs: PopulatedTransactions
...txs: AnnotatedEV5Transaction[]
): Promise<TransactionReceipt[]> {
const impersonatedAccount = await impersonateAccount(
this.props.userAddress,

@ -5,7 +5,7 @@ import { Logger } from 'pino';
import { assert, rootLogger } from '@hyperlane-xyz/utils';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransactions } from '../../types.js';
import { AnnotatedEV5Transaction } from '../../../ProviderType.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js';
@ -20,7 +20,7 @@ export class EV5JsonRpcTxSubmitter implements EV5TxSubmitterInterface {
constructor(public readonly multiProvider: MultiProvider) {}
public async submit(
...txs: PopulatedTransactions
...txs: AnnotatedEV5Transaction[]
): Promise<TransactionReceipt[]> {
const receipts: TransactionReceipt[] = [];
for (const tx of txs) {

@ -9,11 +9,8 @@ import {
} from '../../../../middleware/account/InterchainAccount.js';
import { ChainName } from '../../../../types.js';
import { MultiProvider } from '../../../MultiProvider.js';
import {
CallData,
PopulatedTransaction,
PopulatedTransactions,
} from '../../types.js';
import { AnnotatedEV5Transaction } from '../../../ProviderType.js';
import { CallData } from '../../types.js';
import { TxTransformerType } from '../TxTransformerTypes.js';
import { EV5TxTransformerInterface } from './EV5TxTransformerInterface.js';
@ -39,13 +36,16 @@ export class EV5InterchainAccountTxTransformer
}
public async transform(
...txs: PopulatedTransactions
...txs: AnnotatedEV5Transaction[]
): Promise<ethers.PopulatedTransaction[]> {
const txChainsToInnerCalls: Record<ChainName, CallData[]> = txs.reduce(
(
txChainToInnerCalls: Record<ChainName, CallData[]>,
{ to, data, chainId }: PopulatedTransaction,
{ to, data, chainId }: AnnotatedEV5Transaction,
) => {
assert(chainId, 'Invalid PopulatedTransaction: chainId is required');
assert(to, 'Invalid PopulatedTransaction: to is required');
assert(data, 'Invalid PopulatedTransaction: data is required');
const txChain = this.multiProvider.getChainName(chainId);
txChainToInnerCalls[txChain] ||= [];
txChainToInnerCalls[txChain].push({ to, data });

@ -1,17 +1,5 @@
import { ethers } from 'ethers';
import { z } from 'zod';
import {
CallDataSchema,
PopulatedTransactionSchema,
PopulatedTransactionsSchema,
} from './schemas.js';
export type PopulatedTransaction = z.infer<typeof PopulatedTransactionSchema> &
ethers.PopulatedTransaction;
export type PopulatedTransactions = z.infer<
typeof PopulatedTransactionsSchema
> &
ethers.PopulatedTransaction[];
import { CallDataSchema } from './schemas.js';
export type CallData = z.infer<typeof CallDataSchema>;

@ -19,6 +19,7 @@ import {
HyperlaneModule,
HyperlaneModuleParams,
} from '../core/AbstractHyperlaneModule.js';
import { EvmModuleDeployer } from '../deploy/EvmModuleDeployer.js';
import { EvmIsmModule } from '../ism/EvmIsmModule.js';
import { DerivedIsmConfig } from '../ism/EvmIsmReader.js';
import { MultiProvider } from '../providers/MultiProvider.js';
@ -214,7 +215,7 @@ export class EvmERC20WarpModule extends HyperlaneModule<
actualConfig: TokenRouterConfig,
expectedConfig: TokenRouterConfig,
): AnnotatedEV5Transaction[] {
return EvmERC20WarpModule.createTransferOwnershipTx({
return EvmModuleDeployer.createTransferOwnershipTx({
actualOwner: actualConfig.owner,
expectedOwner: expectedConfig.owner,
deployedAddress: this.args.addresses.deployedTokenRoute,

Loading…
Cancel
Save