chore(sdk): add further zod support to SDK (#3834)

### Description

* adds further zod support to SDK, namely for areas relating to
`transactions`

### Drive-by changes

* No

### Related issues

- Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3740

### Backward compatibility

- Yes

### Testing

- None
pull/3884/head
Noah Bayindirli 🥂 6 months ago committed by GitHub
parent ee28c0ebcc
commit 4bf7301eab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/yellow-donkeys-fetch.md
  2. 12
      solidity/contracts/test/ERC20Test.sol
  3. 2
      typescript/cli/src/submit/submit.ts
  4. 8
      typescript/infra/src/govern/HyperlaneAppGovernor.ts
  5. 200
      typescript/sdk/src/index.ts
  6. 13
      typescript/sdk/src/middleware/account/InterchainAccount.ts
  7. 4
      typescript/sdk/src/middleware/account/accounts.hardhat-test.ts
  8. 13
      typescript/sdk/src/middleware/account/schemas.ts
  9. 14
      typescript/sdk/src/middleware/account/types.ts
  10. 72
      typescript/sdk/src/providers/transactions/schemas.test.ts
  11. 12
      typescript/sdk/src/providers/transactions/schemas.ts
  12. 6
      typescript/sdk/src/providers/transactions/submitter/TxSubmitterTypes.ts
  13. 11
      typescript/sdk/src/providers/transactions/submitter/builder/schemas.ts
  14. 5
      typescript/sdk/src/providers/transactions/submitter/builder/types.ts
  15. 2
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts
  16. 4
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.ts
  17. 12
      typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.ts
  18. 61
      typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.test.ts
  19. 12
      typescript/sdk/src/providers/transactions/submitter/ethersV5/schemas.ts
  20. 13
      typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts
  21. 22
      typescript/sdk/src/providers/transactions/submitter/schemas.ts
  22. 5
      typescript/sdk/src/providers/transactions/submitter/types.ts
  23. 2
      typescript/sdk/src/providers/transactions/transformer/TxTransformerTypes.ts
  24. 13
      typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.ts
  25. 4
      typescript/sdk/src/providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.ts
  26. 56
      typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.test.ts
  27. 8
      typescript/sdk/src/providers/transactions/transformer/ethersV5/schemas.ts
  28. 7
      typescript/sdk/src/providers/transactions/transformer/ethersV5/types.ts
  29. 11
      typescript/sdk/src/providers/transactions/transformer/schemas.ts
  30. 5
      typescript/sdk/src/providers/transactions/transformer/types.ts
  31. 4
      typescript/sdk/src/providers/transactions/types.ts
  32. 2
      typescript/sdk/src/utils/fork.ts

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Adds further zod schema validation support throughout the SDK, namely for /transactions.

@ -67,14 +67,14 @@ contract XERC20Test is ERC20Test, IXERC20 {
}
function setLimits(
address _bridge,
uint256 _mintingLimit,
uint256 _burningLimit
) external {
require(false);
address /* _bridge */,
uint256 /* _mintingLimit */,
uint256 /* _burningLimit */
) external pure {
require(false, "setLimits(): not implemented");
}
function owner() external returns (address) {
function owner() external pure returns (address) {
return address(0x0);
}
}

@ -75,7 +75,7 @@ async function getTransformer<TProtocol extends ProtocolType>(
transformerMetadata: TransformerMetadata,
): Promise<TxTransformerInterface<TProtocol>> {
switch (transformerMetadata.type) {
case TxTransformerType.ICA:
case TxTransformerType.INTERCHAIN_ACCOUNT:
return new EV5InterchainAccountTxTransformer(
multiProvider,
transformerMetadata.props,

@ -197,7 +197,13 @@ export abstract class HyperlaneAppGovernor<
const callRemote = await this.interchainAccount.getCallRemote({
chain: origin,
destination: chain,
innerCalls: [call],
innerCalls: [
{
to: call.to,
data: call.data,
value: call.value?.toString() || '0',
},
],
config: accountConfig,
});
if (!callRemote.to || !callRemote.data) {

@ -24,6 +24,14 @@ export {
testCosmosChain,
testSealevelChain,
} from './consts/testChains.js';
export {
AddressesMap,
HyperlaneAddresses,
HyperlaneAddressesMap,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from './contracts/types.js';
export {
attachContracts,
attachContractsMap,
@ -38,13 +46,14 @@ export {
serializeContractsMap,
} from './contracts/contracts.js';
export {
AddressesMap,
HyperlaneAddresses,
HyperlaneAddressesMap,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from './contracts/types.js';
CoreConfig,
CoreViolationType,
DispatchedMessage,
MailboxMultisigIsmViolation,
MailboxViolation,
MailboxViolationType,
ValidatorAnnounceViolation,
} from './core/types.js';
export { HyperlaneCore } from './core/HyperlaneCore.js';
export { HyperlaneCoreChecker } from './core/HyperlaneCoreChecker.js';
export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer.js';
@ -55,10 +64,10 @@ export {
TestRecipientConfig,
TestRecipientDeployer,
} from './core/TestRecipientDeployer.js';
export { ICoreAdapter } from './core/adapters/types.js';
export { CosmWasmCoreAdapter } from './core/adapters/CosmWasmCoreAdapter.js';
export { EvmCoreAdapter } from './core/adapters/EvmCoreAdapter.js';
export { SealevelCoreAdapter } from './core/adapters/SealevelCoreAdapter.js';
export { ICoreAdapter } from './core/adapters/types.js';
export {
CoreAddresses,
CoreFactories,
@ -66,28 +75,19 @@ export {
} from './core/contracts.js';
export { HyperlaneLifecyleEvent } from './core/events.js';
export { EvmCoreReader } from './core/EvmCoreReader.js';
export {
CoreConfig,
CoreViolationType,
DispatchedMessage,
MailboxMultisigIsmViolation,
MailboxViolation,
MailboxViolationType,
ValidatorAnnounceViolation,
} from './core/types.js';
export { CoreConfigSchema } from './core/schemas.js';
export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker.js';
export {
DeployerOptions,
HyperlaneDeployer,
} from './deploy/HyperlaneDeployer.js';
export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer.js';
export {
CheckerViolation,
OwnableConfig,
OwnerViolation,
ViolationType,
} from './deploy/types.js';
export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker.js';
export {
DeployerOptions,
HyperlaneDeployer,
} from './deploy/HyperlaneDeployer.js';
export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer.js';
export { ContractVerifier } from './deploy/verify/ContractVerifier.js';
export { PostDeploymentContractVerifier } from './deploy/verify/PostDeploymentContractVerifier.js';
export {
@ -98,6 +98,14 @@ export {
VerificationInput,
} from './deploy/verify/types.js';
export * as verificationUtils from './deploy/verify/utils.js';
export {
IgpBeneficiaryViolation,
IgpConfig,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
} from './gas/types.js';
export { HyperlaneIgp } from './gas/HyperlaneIgp.js';
export { HyperlaneIgpChecker } from './gas/HyperlaneIgpChecker.js';
export { HyperlaneIgpDeployer } from './gas/HyperlaneIgpDeployer.js';
@ -112,16 +120,6 @@ export {
export { IgpFactories, igpFactories } from './gas/contracts.js';
export { StorageGasOracleConfig } from './gas/oracle/types.js';
export { CoinGeckoTokenPriceGetter } from './gas/token-prices.js';
export {
IgpBeneficiaryViolation,
IgpConfig,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
} from './gas/types.js';
export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer.js';
export { EvmHookReader } from './hook/EvmHookReader.js';
export {
AggregationHookConfig,
DomainRoutingHookConfig,
@ -135,12 +133,8 @@ export {
ProtocolFeeHookConfig,
} from './hook/types.js';
export { HookConfigSchema } from './hook/schemas.js';
export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js';
export {
buildAggregationIsmConfigs,
buildMultisigIsmConfigs,
} from './ism/multisig.js';
export { EvmIsmReader } from './ism/EvmIsmReader.js';
export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer.js';
export { EvmHookReader } from './hook/EvmHookReader.js';
export {
AggregationIsmConfig,
DeployedIsm,
@ -154,7 +148,30 @@ export {
RoutingIsmConfig,
TrustedRelayerIsmConfig,
} from './ism/types.js';
export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js';
export {
buildAggregationIsmConfigs,
buildMultisigIsmConfigs,
} from './ism/multisig.js';
export { EvmIsmReader } from './ism/EvmIsmReader.js';
export { collectValidators, moduleCanCertainlyVerify } from './ism/utils.js';
export { ZHash } from './metadata/customZodTypes.js';
export {
BlockExplorer,
ChainMetadata,
ChainMetadataSchema,
ChainMetadataSchemaObject,
ChainTechnicalStack,
ExplorerFamily,
ExplorerFamilyValue,
NativeToken,
RpcUrl,
RpcUrlSchema,
getChainIdNumber,
getDomainId,
getReorgPeriod,
isValidChainMetadata,
} from './metadata/chainMetadataTypes.js';
export {
ChainMetadataManager,
ChainMetadataManagerOptions,
@ -180,23 +197,6 @@ export {
ValidatorConfig,
buildAgentConfig,
} from './metadata/agentConfig.js';
export {
BlockExplorer,
ChainMetadata,
ChainMetadataSchema,
ChainMetadataSchemaObject,
ChainTechnicalStack,
ExplorerFamily,
ExplorerFamilyValue,
NativeToken,
RpcUrl,
RpcUrlSchema,
getChainIdNumber,
getDomainId,
getReorgPeriod,
isValidChainMetadata,
} from './metadata/chainMetadataTypes.js';
export { ZHash } from './metadata/customZodTypes.js';
export {
HyperlaneDeploymentArtifacts,
HyperlaneDeploymentArtifactsSchema,
@ -206,6 +206,14 @@ export {
WarpRouteConfig,
WarpRouteConfigSchema,
} from './metadata/warpRouteConfig.js';
export {
AccountConfigSchema,
GetCallRemoteSettingsSchema,
} from './middleware/account/schemas.js';
export {
AccountConfig,
GetCallRemoteSettings,
} from './middleware/account/types.js';
export { InterchainAccount } from './middleware/account/InterchainAccount.js';
export { InterchainAccountChecker } from './middleware/account/InterchainAccountChecker.js';
export {
@ -216,7 +224,6 @@ export {
InterchainAccountFactories,
interchainAccountFactories,
} from './middleware/account/contracts.js';
export { AccountConfig } from './middleware/account/types.js';
export { LiquidityLayerApp } from './middleware/liquidity-layer/LiquidityLayerApp.js';
export {
BridgeAdapterConfig,
@ -270,15 +277,6 @@ export {
ViemTransaction,
ViemTransactionReceipt,
} from './providers/ProviderType.js';
export { HyperlaneEtherscanProvider } from './providers/SmartProvider/HyperlaneEtherscanProvider.js';
export { HyperlaneJsonRpcProvider } from './providers/SmartProvider/HyperlaneJsonRpcProvider.js';
export {
AllProviderMethods,
IProviderMethods,
ProviderMethod,
excludeProviderMethods,
} from './providers/SmartProvider/ProviderMethods.js';
export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js';
export {
ChainMetadataWithRpcConnectionInfo,
ProviderErrorResult,
@ -289,6 +287,15 @@ export {
ProviderTimeoutResult,
SmartProviderOptions,
} from './providers/SmartProvider/types.js';
export { HyperlaneEtherscanProvider } from './providers/SmartProvider/HyperlaneEtherscanProvider.js';
export { HyperlaneJsonRpcProvider } from './providers/SmartProvider/HyperlaneJsonRpcProvider.js';
export {
AllProviderMethods,
IProviderMethods,
ProviderMethod,
excludeProviderMethods,
} from './providers/SmartProvider/ProviderMethods.js';
export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js';
export {
ProviderBuilderFn,
ProviderBuilderMap,
@ -301,22 +308,59 @@ export {
defaultViemProviderBuilder,
protocolToDefaultProviderBuilder,
} from './providers/providerBuilders.js';
export { TxSubmitterInterface } from './providers/transactions/submitter/TxSubmitterInterface.js';
export { PopulatedTransactionSchema } from './providers/transactions/schemas.js';
export {
CallData,
PopulatedTransaction,
} from './providers/transactions/types.js';
export { TxSubmitterType } from './providers/transactions/submitter/TxSubmitterTypes.js';
export { SubmitterMetadataSchema } from './providers/transactions/submitter/schemas.js';
export { SubmitterMetadata } from './providers/transactions/submitter/types.js';
export { TxSubmitterInterface } from './providers/transactions/submitter/TxSubmitterInterface.js';
export {
EV5GnosisSafeTxSubmitterPropsSchema,
EV5ImpersonatedAccountTxSubmitterPropsSchema,
} from './providers/transactions/submitter/ethersV5/schemas.js';
export {
EV5GnosisSafeTxSubmitterProps,
EV5ImpersonatedAccountTxSubmitterProps,
} from './providers/transactions/submitter/ethersV5/EV5TxSubmitterTypes.js';
} from './providers/transactions/submitter/ethersV5/types.js';
export { SubmissionStrategySchema } from './providers/transactions/submitter/builder/schemas.js';
export { SubmissionStrategy } from './providers/transactions/submitter/builder/types.js';
export { TxSubmitterBuilder } from './providers/transactions/submitter/builder/TxSubmitterBuilder.js';
export { EV5GnosisSafeTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.js';
export { EV5ImpersonatedAccountTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.js';
export { EV5JsonRpcTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js';
export { EV5TxSubmitterInterface } from './providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.js';
export { TxTransformerInterface } from './providers/transactions/transformer/TxTransformerInterface.js';
export { TxTransformerType } from './providers/transactions/transformer/TxTransformerTypes.js';
export { EV5InterchainAccountTxTransformerProps } from './providers/transactions/transformer/ethersV5/EV5TxTransformerTypes.js';
export { TransformerMetadataSchema } from './providers/transactions/transformer/schemas.js';
export { TransformerMetadata } from './providers/transactions/transformer/types.js';
export { TxTransformerInterface } from './providers/transactions/transformer/TxTransformerInterface.js';
export { EV5InterchainAccountTxTransformerPropsSchema } from './providers/transactions/transformer/ethersV5/schemas.js';
export { EV5InterchainAccountTxTransformerProps } from './providers/transactions/transformer/ethersV5/types.js';
export { EV5InterchainAccountTxTransformer } from './providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.js';
export { EV5TxTransformerInterface } from './providers/transactions/transformer/ethersV5/EV5TxTransformerInterface.js';
export {
MailboxClientConfig as ConnectionClientConfig,
ClientViolation as ConnectionClientViolation,
ClientViolationType as ConnectionClientViolationType,
GasRouterConfig,
MailboxClientConfig,
ProxiedFactories,
ProxiedRouterConfig,
RouterAddress,
RouterConfig,
RouterViolation,
RouterViolationType,
proxiedFactories,
} from './router/types.js';
export { GasRouterDeployer } from './router/GasRouterDeployer.js';
export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker.js';
export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer.js';
@ -325,6 +369,7 @@ export {
MultiProtocolRouterApp,
} from './router/MultiProtocolRouterApps.js';
export { GasRouterApp, RouterApp } from './router/RouterApps.js';
export { IGasRouterAdapter, IRouterAdapter } from './router/adapters/types.js';
export {
EvmGasRouterAdapter,
EvmRouterAdapter,
@ -333,21 +378,6 @@ export {
SealevelGasRouterAdapter,
SealevelRouterAdapter,
} from './router/adapters/SealevelRouterAdapter.js';
export { IGasRouterAdapter, IRouterAdapter } from './router/adapters/types.js';
export {
MailboxClientConfig as ConnectionClientConfig,
ClientViolation as ConnectionClientViolation,
ClientViolationType as ConnectionClientViolationType,
GasRouterConfig,
MailboxClientConfig,
ProxiedFactories,
ProxiedRouterConfig,
RouterAddress,
RouterConfig,
RouterViolation,
RouterViolationType,
proxiedFactories,
} from './router/types.js';
export { IToken, TokenArgs, TokenConfigSchema } from './token/IToken.js';
export { Token } from './token/Token.js';
export { TokenAmount } from './token/TokenAmount.js';

@ -1,9 +1,8 @@
import { BigNumber, BytesLike, PopulatedTransaction } from 'ethers';
import { BigNumber, PopulatedTransaction } from 'ethers';
import { InterchainAccountRouter } from '@hyperlane-xyz/core';
import {
Address,
CallData,
addressToBytes32,
bytes32ToAddress,
} from '@hyperlane-xyz/utils';
@ -22,15 +21,7 @@ import {
InterchainAccountFactories,
interchainAccountFactories,
} from './contracts.js';
import { AccountConfig } from './types.js';
export interface GetCallRemoteSettings {
chain: ChainName;
destination: ChainName;
innerCalls: CallData[];
config: AccountConfig;
hookMetadata?: BytesLike;
}
import { AccountConfig, GetCallRemoteSettings } from './types.js';
export class InterchainAccount extends RouterApp<InterchainAccountFactories> {
constructor(

@ -1,6 +1,6 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js';
import { expect } from 'chai';
import { BigNumber, constants } from 'ethers';
import { constants } from 'ethers';
import hre from 'hardhat';
import {
@ -84,7 +84,7 @@ describe('InterchainAccounts', async () => {
const call = {
to: recipient.address,
data,
value: BigNumber.from('0'),
value: '0',
};
const quote = await local['quoteGasPayment(uint32)'](
multiProvider.getDomainId(remoteChain),

@ -1,6 +1,10 @@
import { z } from 'zod';
import { ZChainName, ZHash } from '../../metadata/customZodTypes.js';
import {
BigNumberSchema,
CallDataSchema,
} from '../../providers/transactions/schemas.js';
export const AccountConfigSchema = z.object({
origin: ZChainName,
@ -9,3 +13,12 @@ export const AccountConfigSchema = z.object({
routerOverride: ZHash.optional(),
ismOverride: ZHash.optional(),
});
/* For InterchainAccount::getCallRemote() */
export const GetCallRemoteSettingsSchema = z.object({
chain: ZChainName,
destination: ZChainName,
innerCalls: z.array(CallDataSchema),
config: AccountConfigSchema,
hookMetadata: BigNumberSchema.optional(),
});

@ -1,11 +1,7 @@
import { Address } from '@hyperlane-xyz/utils';
import { z } from 'zod';
import { ChainName } from '../../types.js';
import { AccountConfigSchema, GetCallRemoteSettingsSchema } from './schemas.js';
export type AccountConfig = {
origin: ChainName;
owner: Address;
localRouter?: Address;
routerOverride?: Address;
ismOverride?: Address;
};
export type AccountConfig = z.infer<typeof AccountConfigSchema>;
/* For InterchainAccount::getCallRemote() */
export type GetCallRemoteSettings = z.infer<typeof GetCallRemoteSettingsSchema>;

@ -0,0 +1,72 @@
import { expect } from 'chai';
import { Address } from '@hyperlane-xyz/utils';
import { CallDataSchema, PopulatedTransactionSchema } from './schemas.js';
import { CallData, PopulatedTransaction } 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 = {
to: ADDRESS_MOCK,
data: DATA_MOCK,
value: VALUE_MOCK,
};
const result = CallDataSchema.safeParse(validCallData);
expect(result.success).to.be.true;
});
it('should parse CallData without optional value', () => {
const validCallDataWithoutValue: CallData = {
to: ADDRESS_MOCK,
data: DATA_MOCK,
};
const result = CallDataSchema.safeParse(validCallDataWithoutValue);
expect(result.success).to.be.true;
});
it('should fail parsing invalid CallData', () => {
const invalidCallData: CallData = {
to: INVALID_ADDRESS,
data: DATA_MOCK,
value: VALUE_MOCK,
};
const result = CallDataSchema.safeParse(invalidCallData);
expect(result.success).to.be.false;
});
});
});

@ -1,7 +1,17 @@
import { z } from 'zod';
import { ZHash } from '../../metadata/customZodTypes.js';
export const BigNumberSchema = z.string();
export const PopulatedTransactionSchema = z.object({
to: z.string(),
to: ZHash,
data: z.string(),
chainId: z.number(),
});
export const CallDataSchema = z.object({
to: ZHash,
data: z.string(),
value: BigNumberSchema.optional(),
});

@ -1,5 +1,5 @@
export enum TxSubmitterType {
JSON_RPC = 'JSON RPC',
IMPERSONATED_ACCOUNT = 'Impersonated Account',
GNOSIS_SAFE = 'Gnosis Safe',
JSON_RPC = 'jsonRpc',
IMPERSONATED_ACCOUNT = 'impersonatedAccount',
GNOSIS_SAFE = 'gnosisSafe',
}

@ -0,0 +1,11 @@
import { z } from 'zod';
import { ZChainName } from '../../../../metadata/customZodTypes.js';
import { TransformerMetadataSchema } from '../../transformer/schemas.js';
import { SubmitterMetadataSchema } from '../schemas.js';
export const SubmissionStrategySchema = z.object({
chain: ZChainName,
submitter: SubmitterMetadataSchema,
transforms: z.array(TransformerMetadataSchema).optional(),
});

@ -0,0 +1,5 @@
import { z } from 'zod';
import { SubmissionStrategySchema } from './schemas.js';
export type SubmissionStrategy = z.infer<typeof SubmissionStrategySchema>;

@ -9,7 +9,7 @@ import { PopulatedTransaction } from '../../types.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5TxSubmitterInterface } from './EV5TxSubmitterInterface.js';
import { EV5GnosisSafeTxSubmitterProps } from './EV5TxSubmitterTypes.js';
import { EV5GnosisSafeTxSubmitterProps } from './types.js';
export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface {
public readonly txSubmitterType: TxSubmitterType =

@ -12,7 +12,7 @@ import { MultiProvider } from '../../../MultiProvider.js';
import { TxSubmitterType } from '../TxSubmitterTypes.js';
import { EV5JsonRpcTxSubmitter } from './EV5JsonRpcTxSubmitter.js';
import { EV5ImpersonatedAccountTxSubmitterProps } from './EV5TxSubmitterTypes.js';
import { EV5ImpersonatedAccountTxSubmitterProps } from './types.js';
export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter {
public readonly txSubmitterType: TxSubmitterType =
@ -35,7 +35,7 @@ export class EV5ImpersonatedAccountTxSubmitter extends EV5JsonRpcTxSubmitter {
const impersonatedAccount = await impersonateAccount(
this.props.userAddress,
);
super.multiProvider.setSharedSigner(impersonatedAccount);
this.multiProvider.setSharedSigner(impersonatedAccount);
const transactionReceipts = await super.submit(...txs);
await stopImpersonatingAccount(this.props.userAddress);
return transactionReceipts;

@ -1,12 +0,0 @@
import { Address } from '@hyperlane-xyz/utils';
import { ChainName } from '../../../../types.js';
export interface EV5GnosisSafeTxSubmitterProps {
chain: ChainName;
safeAddress: Address;
}
export interface EV5ImpersonatedAccountTxSubmitterProps {
userAddress: Address;
}

@ -0,0 +1,61 @@
import { expect } from 'chai';
import { Address } from '@hyperlane-xyz/utils';
import { ChainName } from '../../../../types.js';
import {
EV5GnosisSafeTxSubmitterPropsSchema,
EV5ImpersonatedAccountTxSubmitterPropsSchema,
} from './schemas.js';
import {
EV5GnosisSafeTxSubmitterProps,
EV5ImpersonatedAccountTxSubmitterProps,
} from './types.js';
describe('ethersV5 submitter props schemas', () => {
const CHAIN_MOCK: ChainName = 'ethereum';
const ADDRESS_MOCK: Address = '0x1234567890123456789012345678901234567890';
const INVALID_ADDRESS: Address = '0x1';
describe('EV5GnosisSafeTxSubmitterPropsSchema', () => {
it('should parse valid props', () => {
const validProps: EV5GnosisSafeTxSubmitterProps = {
chain: CHAIN_MOCK,
safeAddress: ADDRESS_MOCK,
};
const result = EV5GnosisSafeTxSubmitterPropsSchema.safeParse(validProps);
expect(result.success).to.be.true;
});
it('should fail parsing invalid props', () => {
const invalidProps = {
chain: CHAIN_MOCK,
};
const result =
EV5GnosisSafeTxSubmitterPropsSchema.safeParse(invalidProps);
expect(result.success).to.be.false;
});
});
describe('EV5ImpersonatedAccountTxSubmitterPropsSchema', () => {
it('should parse valid props', () => {
const validProps: EV5ImpersonatedAccountTxSubmitterProps = {
userAddress: '0x1234567890123456789012345678901234567890',
};
const result =
EV5ImpersonatedAccountTxSubmitterPropsSchema.safeParse(validProps);
expect(result.success).to.be.true;
});
it('should fail parsing invalid props', () => {
const invalidProps: EV5ImpersonatedAccountTxSubmitterProps = {
userAddress: INVALID_ADDRESS,
};
const result =
EV5ImpersonatedAccountTxSubmitterPropsSchema.safeParse(invalidProps);
expect(result.success).to.be.false;
});
});
});

@ -0,0 +1,12 @@
import { z } from 'zod';
import { ZChainName, ZHash } from '../../../../metadata/customZodTypes.js';
export const EV5GnosisSafeTxSubmitterPropsSchema = z.object({
chain: ZChainName,
safeAddress: ZHash,
});
export const EV5ImpersonatedAccountTxSubmitterPropsSchema = z.object({
userAddress: ZHash,
});

@ -0,0 +1,13 @@
import { z } from 'zod';
import {
EV5GnosisSafeTxSubmitterPropsSchema,
EV5ImpersonatedAccountTxSubmitterPropsSchema,
} from './schemas.js';
export type EV5GnosisSafeTxSubmitterProps = z.infer<
typeof EV5GnosisSafeTxSubmitterPropsSchema
>;
export type EV5ImpersonatedAccountTxSubmitterProps = z.infer<
typeof EV5ImpersonatedAccountTxSubmitterPropsSchema
>;

@ -0,0 +1,22 @@
import { z } from 'zod';
import { TxSubmitterType } from './TxSubmitterTypes.js';
import {
EV5GnosisSafeTxSubmitterPropsSchema,
EV5ImpersonatedAccountTxSubmitterPropsSchema,
} from './ethersV5/schemas.js';
export const SubmitterMetadataSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal(TxSubmitterType.JSON_RPC),
props: z.object({}).optional(),
}),
z.object({
type: z.literal(TxSubmitterType.IMPERSONATED_ACCOUNT),
props: EV5ImpersonatedAccountTxSubmitterPropsSchema,
}),
z.object({
type: z.literal(TxSubmitterType.GNOSIS_SAFE),
props: EV5GnosisSafeTxSubmitterPropsSchema,
}),
]);

@ -0,0 +1,5 @@
import { z } from 'zod';
import { SubmitterMetadataSchema } from './schemas.js';
export type SubmitterMetadata = z.infer<typeof SubmitterMetadataSchema>;

@ -1,3 +1,3 @@
export enum TxTransformerType {
ICA = 'Interchain Account',
INTERCHAIN_ACCOUNT = 'interchainAccount',
}

@ -1,7 +1,7 @@
import { ethers } from 'ethers';
import { Logger } from 'pino';
import { CallData, assert, objKeys, rootLogger } from '@hyperlane-xyz/utils';
import { assert, objKeys, rootLogger } from '@hyperlane-xyz/utils';
import {
InterchainAccount,
@ -9,16 +9,17 @@ import {
} from '../../../../middleware/account/InterchainAccount.js';
import { ChainName } from '../../../../types.js';
import { MultiProvider } from '../../../MultiProvider.js';
import { PopulatedTransaction } from '../../types.js';
import { CallData, PopulatedTransaction } from '../../types.js';
import { TxTransformerType } from '../TxTransformerTypes.js';
import { EV5TxTransformerInterface } from './EV5TxTransformerInterface.js';
import { EV5InterchainAccountTxTransformerProps } from './EV5TxTransformerTypes.js';
import { EV5InterchainAccountTxTransformerProps } from './types.js';
export class EV5InterchainAccountTxTransformer
implements EV5TxTransformerInterface
{
public readonly txTransformerType: TxTransformerType = TxTransformerType.ICA;
public readonly txTransformerType: TxTransformerType =
TxTransformerType.INTERCHAIN_ACCOUNT;
protected readonly logger: Logger = rootLogger.child({
module: 'ica-transformer',
});
@ -39,11 +40,11 @@ export class EV5InterchainAccountTxTransformer
const txChainsToInnerCalls: Record<ChainName, CallData[]> = txs.reduce(
(
txChainToInnerCalls: Record<ChainName, CallData[]>,
{ to, data, value, chainId }: PopulatedTransaction,
{ to, data, chainId }: PopulatedTransaction,
) => {
const txChain = this.multiProvider.getChainName(chainId);
txChainToInnerCalls[txChain] ||= [];
txChainToInnerCalls[txChain].push({ to, data, value });
txChainToInnerCalls[txChain].push({ to, data });
return txChainToInnerCalls;
},
{},

@ -1,4 +0,0 @@
import { GetCallRemoteSettings } from '../../../../middleware/account/InterchainAccount.js';
export interface EV5InterchainAccountTxTransformerProps
extends Omit<GetCallRemoteSettings, 'destination' | 'innerCalls'> {}

@ -0,0 +1,56 @@
import { expect } from 'chai';
import { Address } from '@hyperlane-xyz/utils';
import { ChainName } from '../../../../types.js';
import { EV5InterchainAccountTxTransformerPropsSchema } from './schemas.js';
import { EV5InterchainAccountTxTransformerProps } from './types.js';
describe('ethersV5 transformer props schemas', () => {
const CHAIN_MOCK: ChainName = 'ethereum';
const ORIGIN_MOCK: ChainName = 'arbitrum';
const ADDRESS_MOCK: Address = '0x1234567890123456789012345678901234567890';
const HOOK_METADATA_MOCK: string = '1243';
describe('EV5InterchainAccountTxTransformerProps', () => {
it('should parse valid props', () => {
const validProps: EV5InterchainAccountTxTransformerProps = {
chain: CHAIN_MOCK,
config: {
origin: ORIGIN_MOCK,
owner: ADDRESS_MOCK,
},
hookMetadata: HOOK_METADATA_MOCK,
};
const result =
EV5InterchainAccountTxTransformerPropsSchema.safeParse(validProps);
expect(result.success).to.be.true;
});
it('should fail parsing props when required fields are missing', () => {
const invalidProps = {
chain: CHAIN_MOCK,
};
const result =
EV5InterchainAccountTxTransformerPropsSchema.safeParse(invalidProps);
expect(result.success).to.be.false;
});
it('should parse props when extra fields are present', () => {
const validProps = {
chain: CHAIN_MOCK,
config: {
origin: ORIGIN_MOCK,
owner: ADDRESS_MOCK,
},
miscData: 1234,
nonsense: 'bleh',
ish: true,
};
const result =
EV5InterchainAccountTxTransformerPropsSchema.safeParse(validProps);
expect(result.success).to.be.true;
});
});
});

@ -0,0 +1,8 @@
import { GetCallRemoteSettingsSchema } from '../../../../middleware/account/schemas.js';
export const EV5InterchainAccountTxTransformerPropsSchema =
GetCallRemoteSettingsSchema.pick({
chain: true,
config: true,
hookMetadata: true,
});

@ -0,0 +1,7 @@
import { z } from 'zod';
import { EV5InterchainAccountTxTransformerPropsSchema } from './schemas.js';
export type EV5InterchainAccountTxTransformerProps = z.infer<
typeof EV5InterchainAccountTxTransformerPropsSchema
>;

@ -0,0 +1,11 @@
import { z } from 'zod';
import { TxTransformerType } from './TxTransformerTypes.js';
import { EV5InterchainAccountTxTransformerPropsSchema } from './ethersV5/schemas.js';
export const TransformerMetadataSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal(TxTransformerType.INTERCHAIN_ACCOUNT),
props: EV5InterchainAccountTxTransformerPropsSchema,
}),
]);

@ -0,0 +1,5 @@
import { z } from 'zod';
import { TransformerMetadataSchema } from './schemas.js';
export type TransformerMetadata = z.infer<typeof TransformerMetadataSchema>;

@ -1,7 +1,9 @@
import { ethers } from 'ethers';
import { z } from 'zod';
import { PopulatedTransactionSchema } from './schemas.js';
import { CallDataSchema, PopulatedTransactionSchema } from './schemas.js';
export type PopulatedTransaction = z.infer<typeof PopulatedTransactionSchema> &
ethers.PopulatedTransaction;
export type CallData = z.infer<typeof CallDataSchema>;

@ -93,7 +93,7 @@ export const stopImpersonatingAccount = async (
) => {
rootLogger.info(`Stopping account impersonation for address (${address})...`);
if (isValidAddressEvm(address))
if (!isValidAddressEvm(address))
throw new Error(
`Cannot stop account impersonation: invalid address format: ${address}`,
);

Loading…
Cancel
Save