From 98f7e21b99577161f6903b0016976e1b9726d3c7 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 30 Nov 2023 14:30:43 -0500 Subject: [PATCH 1/8] Previous yarn test --since failed to catch deps (#3007) --- package.json | 2 +- typescript/helloworld/src/test/helloworld.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6c826d5c6d..ab3bf3b78d 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "clean": "yarn workspaces foreach --all --parallel run clean", "prettier": "yarn workspaces foreach --since --parallel run prettier", "lint": "yarn workspaces foreach --since --parallel run lint", - "test": "yarn workspaces foreach --since --parallel run test", + "test": "yarn workspaces foreach --all --parallel run test", "coverage": "yarn workspaces foreach --since --parallel run coverage", "version:prepare": "yarn changeset version && yarn workspaces foreach --all --parallel run version:update && yarn install --no-immutable", "version:check": "yarn changeset status", diff --git a/typescript/helloworld/src/test/helloworld.test.ts b/typescript/helloworld/src/test/helloworld.test.ts index 5fdf43c6e6..5d9f1e062e 100644 --- a/typescript/helloworld/src/test/helloworld.test.ts +++ b/typescript/helloworld/src/test/helloworld.test.ts @@ -82,7 +82,7 @@ describe('HelloWorld', async () => { local.sendHelloWorld(remoteDomain, body, { value: 0, }), - ).to.be.revertedWith('StaticProtocolFee: insufficient protocol fee'); + ).to.be.revertedWith('ProtocolFee: insufficient protocol fee'); }); it('handles a message', async () => { From 852b8d806bf6c0f98ba470bfbd7800dc9269bcea Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 1 Dec 2023 11:04:37 +0000 Subject: [PATCH 2/8] Deploy new agent release (#3004) ### Description Deploying with new release ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .../infra/config/environments/mainnet3/agent.ts | 12 ++++++------ .../infra/config/environments/testnet4/agent.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index 7542f9b5e6..841b1c4086 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -43,14 +43,14 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, gasPaymentEnforcement, }, validators: { docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), @@ -59,7 +59,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, }, }; @@ -72,7 +72,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, // whitelist: releaseCandidateHelloworldMatchingList, gasPaymentEnforcement, @@ -84,7 +84,7 @@ const releaseCandidate: RootAgentConfig = { validators: { docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.ReleaseCandidate), @@ -108,7 +108,7 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, gasPaymentEnforcement: [ { diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 7692d08fe4..bf7bd85a7d 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -50,7 +50,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, blacklist: [ ...releaseCandidateHelloworldMatchingList, @@ -67,7 +67,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, chains: validatorChainConfig(Contexts.Hyperlane), }, @@ -75,7 +75,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, }, }; @@ -88,7 +88,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, whitelist: [...releaseCandidateHelloworldMatchingList], gasPaymentEnforcement, @@ -101,7 +101,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, chains: validatorChainConfig(Contexts.ReleaseCandidate), }, @@ -120,7 +120,7 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'c2288eb-20231129-115257', + tag: 'f44589e-20231130-114734', }, gasPaymentEnforcement, transactionGasLimit: 750000, From e21e020b89f8177bf17e53bb2f54f6a324b2b129 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 1 Dec 2023 11:16:21 +0000 Subject: [PATCH 3/8] Fix Kathy sending from PolygonZkEvm & require MultiProtocolProvider chains to match addresses in MultiProtocolApp (#3001) ### Description Fixes 2 issues: 1. Estimates gas in Kathy by explicitly specifying the `from` address due to this bug with PolygonZkEvm when using a non-zero value https://github.com/0xPolygonHermez/zkevm-node/issues/2869 2. Now that we've added neutron & mantapacific as mainnet chains but we don't have helloworld deployments on these chains, Kathy was trying to send to & from these chains. To fix, I changed the constructor of `MultiProtocolApp` to intersect the multiProvider to only work with the chains specified in `addresses` ### Drive-by changes n/a ### Related issues n/a ### Backward compatibility ye ### Testing Ran kathy locally successfully & sent from polygonzkevm --- .../src/multiProtocolApp/evmAdapter.ts | 10 +++++++++- typescript/infra/scripts/helloworld/utils.ts | 2 +- typescript/sdk/src/app/MultiProtocolApp.test.ts | 8 +++++++- typescript/sdk/src/app/MultiProtocolApp.ts | 17 +++++++++++++++++ .../src/router/MultiProtocolRouterApps.test.ts | 8 ++++++-- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts b/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts index 481c1eb34d..0d565f4657 100644 --- a/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts +++ b/typescript/helloworld/src/multiProtocolApp/evmAdapter.ts @@ -29,6 +29,7 @@ export class EvmHelloWorldAdapter destination: ChainName, message: string, value: string, + sender: Address, ): Promise { const contract = this.getConnectedContract(); const toDomain = this.multiProvider.getDomainId(destination); @@ -44,7 +45,14 @@ export class EvmHelloWorldAdapter const estimated = await contract.estimateGas.sendHelloWorld( toDomain, message, - { ...transactionOverrides, value: BigNumber.from(value).add(quote) }, + { + ...transactionOverrides, + // Some networks, like PolygonZkEvm, require a `from` address + // with funds to be specified when estimating gas for a transaction + // that provides non-zero `value`. + from: sender, + value: BigNumber.from(value).add(quote), + }, ); const gasLimit = estimated.mul(12).div(10); diff --git a/typescript/infra/scripts/helloworld/utils.ts b/typescript/infra/scripts/helloworld/utils.ts index 7c4ec54756..cfddb9ee37 100644 --- a/typescript/infra/scripts/helloworld/utils.ts +++ b/typescript/infra/scripts/helloworld/utils.ts @@ -128,7 +128,7 @@ export async function getHelloWorldMultiProtocolApp( }), ); const app = new HelloMultiProtocolApp( - multiProtocolProvider, + multiProtocolProvider.intersect(Object.keys(routersAndMailboxes)).result, routersAndMailboxes, ); diff --git a/typescript/sdk/src/app/MultiProtocolApp.test.ts b/typescript/sdk/src/app/MultiProtocolApp.test.ts index 68f695ba52..9c69f39fb0 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.test.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.test.ts @@ -26,7 +26,13 @@ describe('MultiProtocolApp', () => { describe('constructs', () => { const multiProvider = new MultiProtocolProvider(); it('creates an app class and gleans types from generic', async () => { - const app = new TestMultiProtocolApp(multiProvider, {}); + const addresses = { + ethereum: {}, + }; + const app = new TestMultiProtocolApp( + multiProvider.intersect(Object.keys(addresses)).result, + addresses, + ); expect(app).to.be.instanceOf(MultiProtocolApp); expect(app.adapter(Chains.ethereum).protocol).to.eql( ProtocolType.Ethereum, diff --git a/typescript/sdk/src/app/MultiProtocolApp.ts b/typescript/sdk/src/app/MultiProtocolApp.ts index daf841dcad..e9c99d3a8e 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.ts @@ -6,6 +6,7 @@ import { ProtocolType, objMap, promiseObjAll, + symmetricDifference, } from '@hyperlane-xyz/utils'; import { ChainMetadata } from '../metadata/chainMetadataTypes'; @@ -120,6 +121,22 @@ export abstract class MultiProtocolApp< public readonly addresses: ChainMap, public readonly logger = debug('hyperlane:MultiProtocolApp'), ) { + const multiProviderChains = multiProvider.getKnownChainNames(); + const addressesChains = Object.keys(addresses); + const setDifference = symmetricDifference( + new Set(multiProviderChains), + new Set(addressesChains), + ); + if (setDifference.size > 0) { + throw new Error( + `MultiProtocolProvider and addresses must have the same chains. Provider chains: ${multiProviderChains.join( + ', ', + )}. Addresses chains: ${addressesChains.join( + ', ', + )}. Difference: ${Array.from(setDifference)}`, + ); + } + super(multiProvider.metadata); } diff --git a/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts b/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts index c075da32f9..e00b49e989 100644 --- a/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts +++ b/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts @@ -12,9 +12,13 @@ describe('MultiProtocolRouterApp', () => { describe('constructs', () => { const multiProvider = new MultiProtocolProvider(); it('creates an app class', async () => { - const app = new MultiProtocolRouterApp(multiProvider, { + const addresses = { ethereum: { router: ethers.constants.AddressZero }, - }); + }; + const app = new MultiProtocolRouterApp( + multiProvider.intersect(Object.keys(addresses)).result, + addresses, + ); expect(app).to.be.instanceOf(MultiProtocolRouterApp); const ethAdapter = app.adapter(Chains.ethereum); expect(ethAdapter).to.be.instanceOf(EvmRouterAdapter); From 7297c8d6f2bf53a8e0b625796854143769699e8c Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 1 Dec 2023 12:15:34 +0000 Subject: [PATCH 4/8] Deploy kathy w/ PolygonZkEvm fix (#3011) ### Description Deploy Kathy with #3001 ### Drive-by changes Small fix to deploy kathy successfully - because the relayer chains doesn't include neutron and kathy will try to create a multiprovider for all chains the env, we need to use the environment chain names as the helm `hyperlane.chains` value ### Related issues ### Backward compatibility ### Testing --- .../infra/config/environments/mainnet3/helloworld.ts | 4 ++-- .../infra/config/environments/testnet4/helloworld.ts | 4 ++-- typescript/infra/src/helloworld/kathy.ts | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/helloworld.ts b/typescript/infra/config/environments/mainnet3/helloworld.ts index 4f0fdde31a..32dcaa5290 100644 --- a/typescript/infra/config/environments/mainnet3/helloworld.ts +++ b/typescript/infra/config/environments/mainnet3/helloworld.ts @@ -13,7 +13,7 @@ export const hyperlane: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'bbe8928-20231025-215311', + tag: 'e21e020-20231201-111649', }, chainsToSkip: [], runEnv: environment, @@ -34,7 +34,7 @@ export const releaseCandidate: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'bef2251-20231025-174850', + tag: 'e21e020-20231201-111649', }, chainsToSkip: [], runEnv: environment, diff --git a/typescript/infra/config/environments/testnet4/helloworld.ts b/typescript/infra/config/environments/testnet4/helloworld.ts index c7db766866..ab4985787e 100644 --- a/typescript/infra/config/environments/testnet4/helloworld.ts +++ b/typescript/infra/config/environments/testnet4/helloworld.ts @@ -13,7 +13,7 @@ export const hyperlaneHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '52a8416-20231024-025635', + tag: 'e21e020-20231201-111649', }, chainsToSkip: [], runEnv: environment, @@ -33,7 +33,7 @@ export const releaseCandidateHelloworld: HelloWorldConfig = { kathy: { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '52a8416-20231024-025635', + tag: 'e21e020-20231201-111649', }, chainsToSkip: [], runEnv: environment, diff --git a/typescript/infra/src/helloworld/kathy.ts b/typescript/infra/src/helloworld/kathy.ts index ae48434671..c5519682d5 100644 --- a/typescript/infra/src/helloworld/kathy.ts +++ b/typescript/infra/src/helloworld/kathy.ts @@ -38,8 +38,6 @@ export async function runHelloworldKathyHelmCommand( const values = getHelloworldKathyHelmValues(agentConfig, kathyConfig); - console.log('kathy values: ', values); - return execCmd( `helm ${helmCommand} ${getHelmReleaseName( agentConfig.context, @@ -77,9 +75,9 @@ function getHelloworldKathyHelmValues( context: agentConfig.context, // This is just used for fetching secrets, and is not actually // the list of chains that kathy will send to. Because Kathy - // will fetch secrets for all chains, regardless of skipping them or - // not, we pass in all chains - chains: agentConfig.contextChainNames.relayer, + // will fetch secrets for all chains in the environment, regardless + // of skipping them or not, we pass in all chains + chains: agentConfig.environmentChainNames, aws: agentConfig.aws !== undefined, chainsToSkip: kathyConfig.chainsToSkip, From 9fc0866b29bf2190ecb8bc0ba78c267987c4f809 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Fri, 1 Dec 2023 19:08:31 +0000 Subject: [PATCH 5/8] Fix base bridging and a race condition in key funder (#3012) ### Description Turns out bridging from L1 to Base has been broken the whole time :( it's because the version of @eth-optimism/sdk we were using was 1.8.0 (according to yarn.lock) which was released in late 2022 https://github.com/ethereum-optimism/optimism/releases/tag/%40eth-optimism%2Fsdk%401.8.0, far before Base was launched. So we'd always get this error in key funder because we rely on the optimism SDK knowing about Base's chain ID: ``` {"chain":"base","error":"Error: cannot get contract AddressManager for unknown L2 chain ID 8453, you must provide an address\n at getOEContract (/hyperlane-monorepo/node_modules/@eth-optimism/sdk/src/utils/contracts.ts:58:11)\n at getAllOEContracts (/hyperlane-monorepo/node_modules/@eth-optimism/sdk/src/utils/contracts.ts:121:46)\n at new CrossChainMessenger (/hyperlane-monorepo/node_modules/@eth-optimism/sdk/src/cross-chain-messenger.ts:170:39)\n at ContextFunder.bridgeToOptimism (/hyperlane-monorepo/typescript/infra/scripts/funding/fund-keys-from-deployer.ts:652:33)\n at ContextFunder.bridgeToL2 (/hyperlane-monorepo/typescript/infra/scripts/funding/fund-keys-from-deployer.ts:637:23)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at async ContextFunder.bridgeIfL2 (/hyperlane-monorepo/typescript/infra/scripts/funding/fund-keys-from-deployer.ts:492:9)\n at async gracefullyHandleError (/hyperlane-monorepo/typescript/infra/scripts/funding/fund-keys-from-deployer.ts:774:5)\n at async /hyperlane-monorepo/typescript/infra/scripts/funding/fund-keys-from-deployer.ts:394:29\n at async Promise.all (index 9)","level":"error","message":"Error bridging to L2"} ``` The fix was just to upgrade to a newer optimism SDK version A mystery to me is that we'd get that log ^ but sometimes the key funder pod would show as having ran successfully. My best guess here is there was a race condition in `fund` when we had a variable local to the function called `failureOccurred` that many promises which were `Promise.all`'d would read and write to it via `failureOccurred ||= await someFallibleFn()`. I changed the logic here to not have multiple concurrent promises contend for the variable Also made a small change to not include mantapacific in the list of relayer keys for the Hyperlane context. It's not ever used, and had a downstream effect of us trying to fund the Kathy key on mantapacific, which we don't want to actually do ### Drive-by changes n/a ### Related issues n/a ### Backward compatibility ye ### Testing ran locally --- .../config/environments/mainnet3/chains.ts | 15 +- typescript/infra/package.json | 2 +- .../funding/fund-keys-from-deployer.ts | 29 +- yarn.lock | 403 +++++------------- 4 files changed, 127 insertions(+), 322 deletions(-) diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index 4ab1e5a8ad..239c179fd3 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -65,14 +65,17 @@ export const ethereumChainNames = Object.keys( ethereumMainnetConfigs, ) as MainnetChains[]; +// Remove mantapacific, as it's not considered a "blessed" +// chain. It's not included in the scraper domains table, +// and we don't relay to mantapacific on the Hyperlane or RC contexts. +const hyperlaneContextRelayChains = ethereumChainNames.filter( + (chainName) => chainName !== chainMetadata.mantapacific.name, +); + // Hyperlane & RC context agent chain names. export const agentChainNames: AgentChainNames = { // Run validators for all chains. [Role.Validator]: supportedChainNames, - // Only run relayers for Ethereum chains at the moment. - [Role.Relayer]: ethereumChainNames, - // Remove mantapacific for now, as it's not included in the scraper domains table - [Role.Scraper]: ethereumChainNames.filter( - (chainName) => chainName !== chainMetadata.mantapacific.name, - ), + [Role.Relayer]: hyperlaneContextRelayChains, + [Role.Scraper]: hyperlaneContextRelayChains, }; diff --git a/typescript/infra/package.json b/typescript/infra/package.json index f388658f9f..416507e8a2 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -8,7 +8,7 @@ "@aws-sdk/client-kms": "3.48.0", "@aws-sdk/client-s3": "^3.74.0", "@cosmjs/amino": "^0.31.3", - "@eth-optimism/sdk": "^1.7.0", + "@eth-optimism/sdk": "^3.1.6", "@ethersproject/experimental": "^5.7.0", "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index a63e33a7de..773b1cf339 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -395,10 +395,10 @@ class ContextFunder { // Funds all the roles in this.rolesToFund // Returns whether a failure occurred. async fund(): Promise { - let failureOccurred = false; - const chainKeys = this.getChainKeys(); - const promises = Object.entries(chainKeys).map(async ([chain, keys]) => { + const chainKeyEntries = Object.entries(chainKeys); + const promises = chainKeyEntries.map(async ([chain, keys]) => { + let failureOccurred = false; if (keys.length > 0) { if (!this.skipIgpClaim) { failureOccurred ||= await gracefullyHandleError( @@ -418,14 +418,25 @@ class ContextFunder { const failure = await this.attemptToFundKey(key, chain); failureOccurred ||= failure; } + return failureOccurred; }); - try { - await Promise.all(promises); - } catch (e) { - error('Unhandled error when funding key', { error: format(e) }); - failureOccurred = true; - } + // A failure occurred if any of the promises rejected or + // if any of them resolved with true, indicating a failure + // somewhere along the way + const failureOccurred = (await Promise.allSettled(promises)).reduce( + (failureAgg, result, i) => { + if (result.status === 'rejected') { + error('Funding promise for chain rejected', { + chain: chainKeyEntries[i][0], + error: format(result.reason), + }); + return true; + } + return result.value || failureAgg; + }, + false, + ); return failureOccurred; } diff --git a/yarn.lock b/yarn.lock index 57ed1c4680..665fa346a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3191,33 +3191,14 @@ __metadata: languageName: node linkType: hard -"@eth-optimism/contracts-bedrock@npm:0.11.0": - version: 0.11.0 - resolution: "@eth-optimism/contracts-bedrock@npm:0.11.0" - dependencies: - "@eth-optimism/core-utils": "npm:^0.12.0" - "@openzeppelin/contracts": "npm:4.7.3" - "@openzeppelin/contracts-upgradeable": "npm:4.7.3" - ethers: "npm:^5.7.0" - hardhat: "npm:^2.9.6" - checksum: 26f2bf2fbaddc20c6f04f03855c9aecb569f0492c87be45e55a845659461332c7d2d0caa9fea8e3d3c6e4b612320388bfcca64c3bf106893417619addb6045fd - languageName: node - linkType: hard - -"@eth-optimism/contracts@npm:0.5.39": - version: 0.5.39 - resolution: "@eth-optimism/contracts@npm:0.5.39" - dependencies: - "@eth-optimism/core-utils": "npm:0.12.0" - "@ethersproject/abstract-provider": "npm:^5.7.0" - "@ethersproject/abstract-signer": "npm:^5.7.0" - peerDependencies: - ethers: ^5 - checksum: 941c254f5b101620afa38b23f315fbee9bae74be092f6571aa516662340b739bf504b38c4edadaafecf515d4bb0e4c84bf1b647f16b14f03c447e6c584025554 +"@eth-optimism/contracts-bedrock@npm:0.16.2": + version: 0.16.2 + resolution: "@eth-optimism/contracts-bedrock@npm:0.16.2" + checksum: 4708a5f0385e784c23bb40bc0c4321bac3ccc469ccba4491bb8ffbee267755bad5929215d1af923b372edb93f40bd8fb04d9a1e6caa4fb615776099d23688b9b languageName: node linkType: hard -"@eth-optimism/contracts@npm:^0.6.0": +"@eth-optimism/contracts@npm:0.6.0, @eth-optimism/contracts@npm:^0.6.0": version: 0.6.0 resolution: "@eth-optimism/contracts@npm:0.6.0" dependencies: @@ -3230,7 +3211,7 @@ __metadata: languageName: node linkType: hard -"@eth-optimism/core-utils@npm:0.12.0, @eth-optimism/core-utils@npm:^0.12.0": +"@eth-optimism/core-utils@npm:0.12.0": version: 0.12.0 resolution: "@eth-optimism/core-utils@npm:0.12.0" dependencies: @@ -3254,19 +3235,41 @@ __metadata: languageName: node linkType: hard -"@eth-optimism/sdk@npm:^1.7.0": - version: 1.8.0 - resolution: "@eth-optimism/sdk@npm:1.8.0" +"@eth-optimism/core-utils@npm:0.13.1": + version: 0.13.1 + resolution: "@eth-optimism/core-utils@npm:0.13.1" dependencies: - "@eth-optimism/contracts": "npm:0.5.39" - "@eth-optimism/contracts-bedrock": "npm:0.11.0" - "@eth-optimism/core-utils": "npm:0.12.0" + "@ethersproject/abi": "npm:^5.7.0" + "@ethersproject/abstract-provider": "npm:^5.7.0" + "@ethersproject/address": "npm:^5.7.0" + "@ethersproject/bignumber": "npm:^5.7.0" + "@ethersproject/bytes": "npm:^5.7.0" + "@ethersproject/constants": "npm:^5.7.0" + "@ethersproject/contracts": "npm:^5.7.0" + "@ethersproject/keccak256": "npm:^5.7.0" + "@ethersproject/properties": "npm:^5.7.0" + "@ethersproject/rlp": "npm:^5.7.0" + "@ethersproject/web": "npm:^5.7.1" + chai: "npm:^4.3.9" + ethers: "npm:^5.7.2" + node-fetch: "npm:^2.6.7" + checksum: 7d9a3b94d05c3becce24562003032d6d2ddc4396e6420152ee3ad287a614ca513c53d43ecaeba5e238abb8bd85c352a42854a0f949df19cfb0219fc441e2da09 + languageName: node + linkType: hard + +"@eth-optimism/sdk@npm:^3.1.6": + version: 3.1.6 + resolution: "@eth-optimism/sdk@npm:3.1.6" + dependencies: + "@eth-optimism/contracts": "npm:0.6.0" + "@eth-optimism/contracts-bedrock": "npm:0.16.2" + "@eth-optimism/core-utils": "npm:0.13.1" lodash: "npm:^4.17.21" - merkletreejs: "npm:^0.2.27" + merkletreejs: "npm:^0.3.11" rlp: "npm:^2.2.7" peerDependencies: ethers: ^5 - checksum: 179bb561d30caca0f17affc8012fb4fd1ab30a566651f197111f3ad450f7decbf7ff212ba526ed95f02528694ff095d6e814bf642075a4e99c0e6de8dc322751 + checksum: 39ab8b94c7a4c4333ed31046de5a429529486b287a8080f7ad9f1a2d4c51d5f09a545931501c1b930578baca58cfc072626c460570b4bb6ba304f08061cb6f72 languageName: node linkType: hard @@ -4068,7 +4071,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/web@npm:5.7.1": +"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.1": version: 5.7.1 resolution: "@ethersproject/web@npm:5.7.1" dependencies: @@ -4336,7 +4339,7 @@ __metadata: "@aws-sdk/client-kms": "npm:3.48.0" "@aws-sdk/client-s3": "npm:^3.74.0" "@cosmjs/amino": "npm:^0.31.3" - "@eth-optimism/sdk": "npm:^1.7.0" + "@eth-optimism/sdk": "npm:^3.1.6" "@ethersproject/experimental": "npm:^5.7.0" "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" @@ -4911,20 +4914,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-block@npm:^4.0.0": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-block@npm:4.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-tx": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - ethereum-cryptography: "npm:0.1.3" - checksum: cbdd37fddeeb3aa29dd750409fc4ce1b3ef5691d45d4dc0808706e99080e6f3f4ee4b95e3ce14fccfb91296513196cea84095c64eb740f81ef38f3d9ab0d2a21 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-blockchain@npm:7.0.2": version: 7.0.2 resolution: "@nomicfoundation/ethereumjs-blockchain@npm:7.0.2" @@ -4946,26 +4935,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-blockchain@npm:^6.0.0": - version: 6.0.0 - resolution: "@nomicfoundation/ethereumjs-blockchain@npm:6.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-ethash": "npm:^2.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - abstract-level: "npm:^1.0.3" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - level: "npm:^8.0.0" - lru-cache: "npm:^5.1.1" - memory-level: "npm:^1.0.0" - checksum: c380735f69182576694b3ffd5eff7a5e8e562efb5cbd96e5cf186c68a0592acfb0effe4551cdf4ad366f831d4b7dd28c3347bee8ca845ae2352b8e36cdcbb6cb - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-common@npm:4.0.2": version: 4.0.2 resolution: "@nomicfoundation/ethereumjs-common@npm:4.0.2" @@ -4976,16 +4945,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-common@npm:^3.0.0": - version: 3.0.0 - resolution: "@nomicfoundation/ethereumjs-common@npm:3.0.0" - dependencies: - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - crc-32: "npm:^1.2.0" - checksum: d7012b0d05fba75e6ad2f54e60f777231896377a0a369af22d4868e4e260fb7e2586ac8d51c35b56ea2c1fa2e62dfef8669b653ed3e3b7816cfc38f2acf26090 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-ethash@npm:3.0.2": version: 3.0.2 resolution: "@nomicfoundation/ethereumjs-ethash@npm:3.0.2" @@ -5000,20 +4959,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-ethash@npm:^2.0.0": - version: 2.0.0 - resolution: "@nomicfoundation/ethereumjs-ethash@npm:2.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - abstract-level: "npm:^1.0.3" - bigint-crypto-utils: "npm:^3.0.23" - ethereum-cryptography: "npm:0.1.3" - checksum: dcabac814b7496a19824f6048157a095713afcb0676f4dd3d6172e579b31789dccd0f4417847c1901af3092be0021cc3e98f7f770a81766f505da93ff5930b00 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-evm@npm:2.0.2": version: 2.0.2 resolution: "@nomicfoundation/ethereumjs-evm@npm:2.0.2" @@ -5030,22 +4975,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-evm@npm:^1.0.0": - version: 1.0.0 - resolution: "@nomicfoundation/ethereumjs-evm@npm:1.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - "@types/async-eventemitter": "npm:^0.2.1" - async-eventemitter: "npm:^0.2.4" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - mcl-wasm: "npm:^0.7.1" - rustbn.js: "npm:~0.2.0" - checksum: 5a86ded335d74e63564d0b012c2e7807a1f8c9a2aca65a152fab5eb8200b7d2f23bc9fc31e26a3403a76eaba37ccf1bae4be81310da5e4619f8e1d63f1d0e3fd - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-rlp@npm:5.0.2": version: 5.0.2 resolution: "@nomicfoundation/ethereumjs-rlp@npm:5.0.2" @@ -5055,15 +4984,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-rlp@npm:^4.0.0, @nomicfoundation/ethereumjs-rlp@npm:^4.0.0-beta.2": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-rlp@npm:4.0.0" - bin: - rlp: bin/rlp - checksum: 9e3e52876e408583cf3b19fd65979162eded9e1d02b699d4936a53bfba4155713084d0b37dbc938ec6a34e5bef61b9f2e3682684a86c6a29ea3900ac51113e92 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2": version: 2.0.2 resolution: "@nomicfoundation/ethereumjs-statemanager@npm:2.0.2" @@ -5078,21 +4998,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-statemanager@npm:^1.0.0": - version: 1.0.0 - resolution: "@nomicfoundation/ethereumjs-statemanager@npm:1.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - functional-red-black-tree: "npm:^1.0.1" - checksum: faf7629ec3b5b494a955c9ab7a2e775c01796ad6b2441d10562407056b6a6e06ff751ac03ea131c3eddedc12189f1ae382be0cf2854fdce17f28ce62eb3c2f42 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-trie@npm:6.0.2": version: 6.0.2 resolution: "@nomicfoundation/ethereumjs-trie@npm:6.0.2" @@ -5106,18 +5011,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-trie@npm:^5.0.0": - version: 5.0.0 - resolution: "@nomicfoundation/ethereumjs-trie@npm:5.0.0" - dependencies: - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - ethereum-cryptography: "npm:0.1.3" - readable-stream: "npm:^3.6.0" - checksum: 709cfbb7be2c64208a3e712c27f0bc0580fc723d12340b211353beeddb886f68059170d51529650a090b821b6a21dbf63dc67499f23877e377fbc97295254b12 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-tx@npm:5.0.2": version: 5.0.2 resolution: "@nomicfoundation/ethereumjs-tx@npm:5.0.2" @@ -5132,18 +5025,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-tx@npm:^4.0.0": - version: 4.0.0 - resolution: "@nomicfoundation/ethereumjs-tx@npm:4.0.0" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - ethereum-cryptography: "npm:0.1.3" - checksum: 0833c6a4bfe5ad77a2931710ebe79d972d8e89eb59c9066bb0c7347cd6135a66bf0901c55e8c84827c1edad9200446ef0e5260a6026630552c9828e7f4f123a1 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-util@npm:9.0.2": version: 9.0.2 resolution: "@nomicfoundation/ethereumjs-util@npm:9.0.2" @@ -5155,16 +5036,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-util@npm:^8.0.0": - version: 8.0.0 - resolution: "@nomicfoundation/ethereumjs-util@npm:8.0.0" - dependencies: - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0-beta.2" - ethereum-cryptography: "npm:0.1.3" - checksum: 8da64af39b83750347bf73fb30b169e1bf702cc26d6f94434a78521b569af9ebbd5c23d729e5e1ce38b22e354696af83f823bccc95f8a427c1baf722936d5404 - languageName: node - linkType: hard - "@nomicfoundation/ethereumjs-vm@npm:7.0.2": version: 7.0.2 resolution: "@nomicfoundation/ethereumjs-vm@npm:7.0.2" @@ -5186,30 +5057,6 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-vm@npm:^6.0.0": - version: 6.0.0 - resolution: "@nomicfoundation/ethereumjs-vm@npm:6.0.0" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-blockchain": "npm:^6.0.0" - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-evm": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-statemanager": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-tx": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - "@types/async-eventemitter": "npm:^0.2.1" - async-eventemitter: "npm:^0.2.4" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - functional-red-black-tree: "npm:^1.0.1" - mcl-wasm: "npm:^0.7.1" - rustbn.js: "npm:~0.2.0" - checksum: 16c34baef9a8561d6d580164f258f03d8e0573b76cabb2373835e30ee684aef70d121c4d3a73f3fa5e82081a8b81f643326d11861b18f36d9dcf8e7cf157be96 - languageName: node - linkType: hard - "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0": version: 0.1.0 resolution: "@nomicfoundation/solidity-analyzer-darwin-arm64@npm:0.1.0" @@ -5382,13 +5229,6 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts-upgradeable@npm:4.7.3": - version: 4.7.3 - resolution: "@openzeppelin/contracts-upgradeable@npm:4.7.3" - checksum: 7c72ffeca867478b5aa8e8c7adb3d1ce114cfdc797ed4f3cd074788cf4da25d620ffffd624ac7e9d1223eecffeea9f7b79200ff70dc464cc828c470ccd12ddf1 - languageName: node - linkType: hard - "@openzeppelin/contracts-upgradeable@npm:^4.9.3, @openzeppelin/contracts-upgradeable@npm:^v4.9.3": version: 4.9.3 resolution: "@openzeppelin/contracts-upgradeable@npm:4.9.3" @@ -5396,13 +5236,6 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts@npm:4.7.3": - version: 4.7.3 - resolution: "@openzeppelin/contracts@npm:4.7.3" - checksum: 3d16ed8943938373ecc331c2ab83c3e8d0d89aed0c2a109aaa61ca6524b4c31cb5a81185c6f93ce9ee2dda685a4328fd85bd217929ae598f4be813d5d4cd1b78 - languageName: node - linkType: hard - "@openzeppelin/contracts@npm:^4.9.3": version: 4.9.3 resolution: "@openzeppelin/contracts@npm:4.9.3" @@ -6015,13 +5848,6 @@ __metadata: languageName: node linkType: hard -"@types/async-eventemitter@npm:^0.2.1": - version: 0.2.1 - resolution: "@types/async-eventemitter@npm:0.2.1" - checksum: 52f6a9c6773edec9bc8449273de8b08fca45ebbf1907c755cd67be9aca4f26988aebb6d0e461aecf01463b76f0e1b427f149a8ce54d27cec191702488c676f48 - languageName: node - linkType: hard - "@types/bn.js@npm:^4.11.3": version: 4.11.6 resolution: "@types/bn.js@npm:4.11.6" @@ -6670,15 +6496,6 @@ __metadata: languageName: node linkType: hard -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: "npm:^5.0.0" - checksum: ed84af329f1828327798229578b4fe03a4dd2596ba304083ebd2252666bdc1d7647d66d0b18704477e1f8aa315f055944aa6e859afebd341f12d0a53c37b4b40 - languageName: node - linkType: hard - "abortcontroller-polyfill@npm:^1.7.3": version: 1.7.3 resolution: "abortcontroller-polyfill@npm:1.7.3" @@ -7892,6 +7709,21 @@ __metadata: languageName: node linkType: hard +"chai@npm:^4.3.9": + version: 4.3.10 + resolution: "chai@npm:4.3.10" + dependencies: + assertion-error: "npm:^1.1.0" + check-error: "npm:^1.0.3" + deep-eql: "npm:^4.1.3" + get-func-name: "npm:^2.0.2" + loupe: "npm:^2.3.6" + pathval: "npm:^1.1.1" + type-detect: "npm:^4.0.8" + checksum: 9e545fd60f5efee4f06f7ad62f7b1b142932b08fbb3454db69defd511e7c58771ce51843764212da1e129b2c9d1b029fbf5f98da030fe67a95a0853e8679524f + languageName: node + linkType: hard + "chalk@npm:^2.0.0, chalk@npm:^2.1.0, chalk@npm:^2.4.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -7941,6 +7773,15 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.3": + version: 1.0.3 + resolution: "check-error@npm:1.0.3" + dependencies: + get-func-name: "npm:^2.0.2" + checksum: e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399 + languageName: node + linkType: hard + "chokidar@npm:3.3.0": version: 3.3.0 resolution: "chokidar@npm:3.3.0" @@ -8549,10 +8390,10 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:^3.1.9-1": - version: 3.3.0 - resolution: "crypto-js@npm:3.3.0" - checksum: d7e11f3a387fb143be834e1a25ecf57ead6f5765e90fbf3aed9cead680cc38b1d241718768b7bfec448a843f569374ea5b5870ac7a8165e4bfa1915f0b00c89c +"crypto-js@npm:^4.2.0": + version: 4.2.0 + resolution: "crypto-js@npm:4.2.0" + checksum: c7bcc56a6e01c3c397e95aa4a74e4241321f04677f9a618a8f48a63b5781617248afb9adb0629824792e7ec20ca0d4241a49b6b2938ae6f973ec4efc5c53c924 languageName: node linkType: hard @@ -8721,6 +8562,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.3": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" + dependencies: + type-detect: "npm:^4.0.0" + checksum: 12ce93ae63de187e77b076d3d51bfc28b11f98910a22c18714cce112791195e86a94f97788180994614b14562a86c9763f67c69f785e4586f806b5df39bf9301 + languageName: node + linkType: hard + "deep-extend@npm:^0.6.0, deep-extend@npm:~0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -9734,13 +9584,6 @@ __metadata: languageName: node linkType: hard -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 49ff46c3a7facbad3decb31f597063e761785d7fdb3920d4989d7b08c97a61c2f51183e2f3a03130c9088df88d4b489b1b79ab632219901f184f85158508f4c8 - languageName: node - linkType: hard - "eventemitter3@npm:4.0.4": version: 4.0.4 resolution: "eventemitter3@npm:4.0.4" @@ -10466,6 +10309,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1": version: 1.1.2 resolution: "get-intrinsic@npm:1.1.2" @@ -10982,74 +10832,6 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:^2.9.6": - version: 2.12.4 - resolution: "hardhat@npm:2.12.4" - dependencies: - "@ethersproject/abi": "npm:^5.1.2" - "@metamask/eth-sig-util": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-blockchain": "npm:^6.0.0" - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-evm": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-statemanager": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-tx": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - "@nomicfoundation/ethereumjs-vm": "npm:^6.0.0" - "@nomicfoundation/solidity-analyzer": "npm:^0.1.0" - "@sentry/node": "npm:^5.18.1" - "@types/bn.js": "npm:^5.1.0" - "@types/lru-cache": "npm:^5.1.0" - abort-controller: "npm:^3.0.0" - adm-zip: "npm:^0.4.16" - aggregate-error: "npm:^3.0.0" - ansi-escapes: "npm:^4.3.0" - chalk: "npm:^2.4.2" - chokidar: "npm:^3.4.0" - ci-info: "npm:^2.0.0" - debug: "npm:^4.1.1" - enquirer: "npm:^2.3.0" - env-paths: "npm:^2.2.0" - ethereum-cryptography: "npm:^1.0.3" - ethereumjs-abi: "npm:^0.6.8" - find-up: "npm:^2.1.0" - fp-ts: "npm:1.19.3" - fs-extra: "npm:^7.0.1" - glob: "npm:7.2.0" - immutable: "npm:^4.0.0-rc.12" - io-ts: "npm:1.10.4" - keccak: "npm:^3.0.2" - lodash: "npm:^4.17.11" - mnemonist: "npm:^0.38.0" - mocha: "npm:^10.0.0" - p-map: "npm:^4.0.0" - qs: "npm:^6.7.0" - raw-body: "npm:^2.4.1" - resolve: "npm:1.17.0" - semver: "npm:^6.3.0" - solc: "npm:0.7.3" - source-map-support: "npm:^0.5.13" - stacktrace-parser: "npm:^0.1.10" - tsort: "npm:0.0.1" - undici: "npm:^5.4.0" - uuid: "npm:^8.3.2" - ws: "npm:^7.4.6" - peerDependencies: - ts-node: "*" - typescript: "*" - peerDependenciesMeta: - ts-node: - optional: true - typescript: - optional: true - bin: - hardhat: internal/cli/cli.js - checksum: c92dd697b105fc3a81de4a1061f44e95b2c06169afde4704d1cd26e038693c5c9869b9be219342cd1e243796b3119f583c776c65abfb2bf53ed0d3f3b27ed612 - languageName: node - linkType: hard - "has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -12669,6 +12451,15 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^2.3.6": + version: 2.3.7 + resolution: "loupe@npm:2.3.7" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 635c8f0914c2ce7ecfe4e239fbaf0ce1d2c00e4246fafcc4ed000bfdb1b8f89d05db1a220054175cca631ebf3894872a26fffba0124477fcb562f78762848fb1 + languageName: node + linkType: hard + "lowercase-keys@npm:^1.0.0": version: 1.0.1 resolution: "lowercase-keys@npm:1.0.1" @@ -12902,16 +12693,16 @@ __metadata: languageName: node linkType: hard -"merkletreejs@npm:^0.2.27": - version: 0.2.32 - resolution: "merkletreejs@npm:0.2.32" +"merkletreejs@npm:^0.3.11": + version: 0.3.11 + resolution: "merkletreejs@npm:0.3.11" dependencies: bignumber.js: "npm:^9.0.1" buffer-reverse: "npm:^1.0.1" - crypto-js: "npm:^3.1.9-1" + crypto-js: "npm:^4.2.0" treeify: "npm:^1.1.0" web3-utils: "npm:^1.3.4" - checksum: 00c53a7fe9e87150b262b249f75c9e925641a628388d327f1b5fb4de9d146c95e58c67abe6771d5217b2555ebc3a9cc433ce958003f85dde58bda535225e853f + checksum: a93520ef768648d1e4ebd175182bd3d304270b8eb0700df5be99395fb3ad8805407bdaf3231d13b1649e87de799aa06d71c616db047ad039025764eb23b02244 languageName: node linkType: hard @@ -14605,7 +14396,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.4.0, qs@npm:^6.7.0": +"qs@npm:^6.4.0": version: 6.10.5 resolution: "qs@npm:6.10.5" dependencies: From 7e620c9dfa5a8ca1a8130e04841a6d262f2e14fd Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:32:26 -0500 Subject: [PATCH 6/8] feat:configure hooks in the CLI (#2964) ### Description - enable configuring hooks in the CLI (merkle, igp, protocolFee, aggregation, routing) - use preset by default without prompting ### Drive-by changes ### Related issues ### Backward compatibility ### Testing --- .changeset/odd-keys-pretend.md | 7 + typescript/cli/examples/hook-config.yaml | 18 - typescript/cli/examples/hooks.yaml | 66 ++++ typescript/cli/src/commands/config.ts | 8 +- typescript/cli/src/config/hooks.ts | 372 +++++++++++++----- typescript/cli/src/config/ism.ts | 32 +- typescript/cli/src/deploy/core.ts | 68 ++-- typescript/cli/src/tests/hooks.test.ts | 92 +++++ .../cli/src/tests/hooks/safe-parse-fail.yaml | 44 +++ .../config/environments/mainnet3/core.ts | 4 +- .../infra/config/environments/test/core.ts | 4 +- .../config/environments/testnet4/core.ts | 4 +- typescript/sdk/src/hook/types.ts | 11 +- typescript/sdk/src/index.ts | 1 + typescript/sdk/src/test/testUtils.ts | 4 +- 15 files changed, 539 insertions(+), 196 deletions(-) create mode 100644 .changeset/odd-keys-pretend.md delete mode 100644 typescript/cli/examples/hook-config.yaml create mode 100644 typescript/cli/examples/hooks.yaml create mode 100644 typescript/cli/src/tests/hooks.test.ts create mode 100644 typescript/cli/src/tests/hooks/safe-parse-fail.yaml diff --git a/.changeset/odd-keys-pretend.md b/.changeset/odd-keys-pretend.md new file mode 100644 index 0000000000..9d4233bf8d --- /dev/null +++ b/.changeset/odd-keys-pretend.md @@ -0,0 +1,7 @@ +--- +'@hyperlane-xyz/cli': minor +'@hyperlane-xyz/infra': patch +'@hyperlane-xyz/sdk': patch +--- + +Allow CLI to accept hook as a config diff --git a/typescript/cli/examples/hook-config.yaml b/typescript/cli/examples/hook-config.yaml deleted file mode 100644 index 1b9b9e0936..0000000000 --- a/typescript/cli/examples/hook-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -anvil1: - required: - type: protocolFee - maxProtocolFee: '10000000000000000' - protocolFee: '10000000000' - beneficiary: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - owner: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - default: - type: merkleTreeHook -anvil2: - required: - type: protocolFee - maxProtocolFee: '10000000000000000' - protocolFee: '10000000000' - beneficiary: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - owner: '0xb1b4e269dD0D19d9D49f3a95bF6c2c15f13E7943' - default: - type: merkleTreeHook diff --git a/typescript/cli/examples/hooks.yaml b/typescript/cli/examples/hooks.yaml new file mode 100644 index 0000000000..9fc19433ab --- /dev/null +++ b/typescript/cli/examples/hooks.yaml @@ -0,0 +1,66 @@ +# A config to define the hooks for core contract deployments +# Ideally, use the `hyperlane config create hooks` command to generate this file +# but you we can refer to https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/hook/types.ts for the matching types + +# HooksConfig: +# required: HookConfig +# default: HookConfig + +# HookConfig: +# type: HookType +# ... hook-specific config + +# HookType: +# - merkleTreeHook +# - domainRoutingHook +# - interchainGasPaymaster +# - protocolFee +# - aggregationHook +# - opStack (not yet supported) + +anvil1: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' # in wei (string) + protocolFee: '200000000000000' # in wei (string) + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil2: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + overhead: + anvil2: 50000 # gas amount (number) + gasOracleType: + anvil2: StorageGasOracle +anvil2: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' + protocolFee: '200000000000000' + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil1: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + overhead: + anvil1: 50000 + gasOracleType: + anvil1: StorageGasOracle diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index 64bfec5b1b..74b3c45646 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -2,7 +2,7 @@ import { CommandModule } from 'yargs'; import { log, logGreen } from '../../logger.js'; import { createChainConfig, readChainConfigs } from '../config/chain.js'; -import { createHookConfig } from '../config/hooks.js'; +import { createHooksConfigMap } from '../config/hooks.js'; import { createIsmConfigMap, readIsmConfig } from '../config/ism.js'; import { createMultisigConfig, @@ -96,8 +96,8 @@ const createIsmConfigCommand: CommandModule = { }; const createHookConfigCommand: CommandModule = { - command: 'hook', - describe: 'Create a new Hook config', + command: 'hooks', + describe: 'Create a new hooks config (required & default)', builder: (yargs) => yargs.options({ output: outputFileOption('./configs/hooks.yaml'), @@ -108,7 +108,7 @@ const createHookConfigCommand: CommandModule = { const format: FileFormat = argv.format; const outPath: string = argv.output; const chainConfigPath: string = argv.chains; - await createHookConfig({ format, outPath, chainConfigPath }); + await createHooksConfigMap({ format, outPath, chainConfigPath }); process.exit(0); }, }; diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 4449e1948f..62f66e0fa6 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -1,5 +1,5 @@ import { confirm, input, select } from '@inquirer/prompts'; -import { BigNumber } from 'bignumber.js'; +import { BigNumber as BigNumberJs } from 'bignumber.js'; import { ethers } from 'ethers'; import { z } from 'zod'; @@ -7,12 +7,10 @@ import { ChainMap, ChainName, GasOracleContractType, - HookConfig, HookType, - IgpHookConfig, - MerkleTreeHookConfig, - MultisigIsmConfig, - ProtocolFeeHookConfig, + HooksConfig, + MultisigConfig, + chainMetadata, defaultMultisigConfigs, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; @@ -41,25 +39,56 @@ const MerkleTreeSchema = z.object({ type: z.literal(HookType.MERKLE_TREE), }); -const HookSchema = z.union([ProtocolFeeSchema, MerkleTreeSchema]); +const IGPSchema = z.object({ + type: z.literal(HookType.INTERCHAIN_GAS_PAYMASTER), + owner: z.string(), + beneficiary: z.string(), + overhead: z.record(z.number()), + gasOracleType: z.record(z.literal(GasOracleContractType.StorageGasOracle)), + oracleKey: z.string(), +}); + +const RoutingConfigSchema: z.ZodSchema = z.lazy(() => + z.object({ + type: z.literal(HookType.ROUTING), + owner: z.string(), + domains: z.record(HookConfigSchema), + }), +); + +const AggregationConfigSchema: z.ZodSchema = z.lazy(() => + z.object({ + type: z.literal(HookType.AGGREGATION), + hooks: z.array(HookConfigSchema), + }), +); + +const HookConfigSchema = z.union([ + ProtocolFeeSchema, + MerkleTreeSchema, + IGPSchema, + RoutingConfigSchema, + AggregationConfigSchema, +]); +export type HookConfig = z.infer; -const ConfigSchema = z.object({ - required: HookSchema, - default: HookSchema, +const HooksConfigSchema = z.object({ + required: HookConfigSchema, + default: HookConfigSchema, }); -const HookConfigMapSchema = z.object({}).catchall(ConfigSchema); -export type HookConfigMap = z.infer; +const HooksConfigMapSchema = z.record(HooksConfigSchema); +export type HooksConfigMap = z.infer; export function isValidHookConfigMap(config: any) { - return HookConfigMapSchema.safeParse(config).success; + return HooksConfigMapSchema.safeParse(config).success; } export function presetHookConfigs( owner: Address, local: ChainName, destinationChains: ChainName[], - ismConfig?: MultisigIsmConfig, -) { + multisigConfig?: MultisigConfig, +): HooksConfig { const gasOracleType = destinationChains.reduce< ChainMap >((acc, chain) => { @@ -69,14 +98,17 @@ export function presetHookConfigs( const overhead = destinationChains.reduce>((acc, chain) => { let validatorThreshold: number; let validatorCount: number; - if (ismConfig) { - validatorThreshold = ismConfig.threshold; - validatorCount = ismConfig.validators.length; + if (multisigConfig) { + validatorThreshold = multisigConfig.threshold; + validatorCount = multisigConfig.validators.length; } else if (local in defaultMultisigConfigs) { validatorThreshold = defaultMultisigConfigs[local].threshold; validatorCount = defaultMultisigConfigs[local].validators.length; } else { - throw new Error('Cannot estimate gas overhead for IGP hook'); + // default values + // fix here: https://github.com/hyperlane-xyz/issues/issues/773 + validatorThreshold = 2; + validatorCount = 3; } acc[chain] = multisigIsmVerificationCost( validatorThreshold, @@ -89,17 +121,17 @@ export function presetHookConfigs( return { required: { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), - protocolFee: ethers.utils.parseUnits('0', 'wei'), + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), + protocolFee: ethers.utils.parseUnits('0', 'wei').toString(), beneficiary: owner, owner: owner, - } as ProtocolFeeHookConfig, + }, default: { type: HookType.AGGREGATION, hooks: [ { type: HookType.MERKLE_TREE, - } as MerkleTreeHookConfig, + }, { type: HookType.INTERCHAIN_GAS_PAYMASTER, owner: owner, @@ -107,19 +139,19 @@ export function presetHookConfigs( gasOracleType, overhead, oracleKey: owner, - } as IgpHookConfig, + }, ], }, }; } -export function readHookConfig(filePath: string) { +export function readHooksConfigMap(filePath: string) { const config = readYamlOrJson(filePath); if (!config) { logRed(`No hook config found at ${filePath}`); return; } - const result = HookConfigMapSchema.safeParse(config); + const result = HooksConfigMapSchema.safeParse(config); if (!result.success) { const firstIssue = result.error.issues[0]; throw new Error( @@ -127,21 +159,15 @@ export function readHookConfig(filePath: string) { ); } const parsedConfig = result.data; - const defaultHook: ChainMap = objMap( + const hooks: ChainMap = objMap( parsedConfig, - (_, config) => - ({ - type: config.default.type, - } as HookConfig), + (_, config) => config as HooksConfig, ); - logGreen(`All hook configs in ${filePath} are valid`); - return defaultHook; + logGreen(`All hook configs in ${filePath} are valid for ${hooks}`); + return hooks; } -// TODO: read different hook configs -// export async function readProtocolFeeHookConfig(config: {type: HookType.PROTOCOL_FEE, ...}) { - -export async function createHookConfig({ +export async function createHooksConfigMap({ format, outPath, chainConfigPath, @@ -154,70 +180,15 @@ export async function createHookConfig({ const customChains = readChainConfigsIfExists(chainConfigPath); const chains = await runMultiChainSelectionStep(customChains); - const result: HookConfigMap = {}; + const result: HooksConfigMap = {}; for (const chain of chains) { for (const hookRequirements of ['required', 'default']) { log(`Setting ${hookRequirements} hook for chain ${chain}`); - const hookType = await select({ - message: 'Select hook type', - choices: [ - { value: 'merkle_tree', name: 'MerkleTreeHook' }, - { value: 'protocol_fee', name: 'StaticProtocolFee' }, - ], - pageSize: 5, - }); - if (hookType === 'merkle_tree') { - result[chain] = { - ...result[chain], - [hookRequirements]: { type: HookType.MERKLE_TREE }, - }; - } else if (hookType === 'protocol_fee') { - const owner = await input({ - message: 'Enter owner address', - }); - const ownerAddress = normalizeAddressEvm(owner); - let beneficiary; - let sameAsOwner = false; - sameAsOwner = await confirm({ - message: 'Use this same address for the beneficiary?', - }); - if (sameAsOwner) { - beneficiary = ownerAddress; - } else { - beneficiary = await input({ - message: 'Enter beneficiary address', - }); - } - const beneficiaryAddress = normalizeAddressEvm(beneficiary); - // TODO: input in gwei, wei, etc - const maxProtocolFee = toWei( - await input({ - message: 'Enter max protocol fee in (e.g. 1.0)', - }), - ); - const protocolFee = toWei( - await input({ - message: 'Enter protocol fee (e.g. 1.0)', - }), - ); - if (BigNumber(protocolFee).gt(maxProtocolFee)) { - errorRed('Protocol fee cannot be greater than max protocol fee'); - throw new Error('Invalid protocol fee'); - } - - result[chain] = { - ...result[chain], - [hookRequirements]: { - type: HookType.PROTOCOL_FEE, - maxProtocolFee: maxProtocolFee.toString(), - protocolFee: protocolFee.toString(), - beneficiary: beneficiaryAddress, - owner: ownerAddress, - }, - }; - } else { - throw new Error(`Invalid hook type: ${hookType}}`); - } + const remotes = chains.filter((c) => c !== chain); + result[chain] = { + ...result[chain], + [hookRequirements]: await createHookConfig(chain, remotes), + }; } if (isValidHookConfigMap(result)) { logGreen(`Hook config is valid, writing to file ${outPath}`); @@ -230,3 +201,208 @@ export async function createHookConfig({ } } } + +export async function createHookConfig( + chain: ChainName, + remotes: ChainName[], +): Promise { + let lastConfig: HookConfig; + const hookType = await select({ + message: 'Select hook type', + choices: [ + { + value: HookType.MERKLE_TREE, + name: HookType.MERKLE_TREE, + description: + 'Add messages to the incremental merkle tree on origin chain (needed for the merkleRootMultisigIsm on the remote chain)', + }, + { + value: HookType.PROTOCOL_FEE, + name: HookType.PROTOCOL_FEE, + description: 'Charge fees for each message dispatch from this chain', + }, + { + value: HookType.INTERCHAIN_GAS_PAYMASTER, + name: HookType.INTERCHAIN_GAS_PAYMASTER, + description: + 'Allow for payments for expected gas to be paid by the relayer while delivering on remote chain', + }, + { + value: HookType.AGGREGATION, + name: HookType.AGGREGATION, + description: + 'Aggregate multiple hooks into a single hook (e.g. merkle tree + IGP) which will be called in sequence', + }, + { + value: HookType.ROUTING, + name: HookType.ROUTING, + description: + 'Each destination domain can have its own hook configured via DomainRoutingHook', + }, + ], + pageSize: 10, + }); + if (hookType === HookType.MERKLE_TREE) { + lastConfig = { type: HookType.MERKLE_TREE }; + } else if (hookType === HookType.PROTOCOL_FEE) { + lastConfig = await createProtocolFeeConfig(chain); + } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { + lastConfig = await createIGPConfig(remotes); + } else if (hookType === HookType.AGGREGATION) { + lastConfig = await createAggregationConfig(chain, remotes); + } else if (hookType === HookType.ROUTING) { + lastConfig = await createRoutingConfig(chain, remotes); + } else { + throw new Error(`Invalid hook type: ${hookType}`); + } + return lastConfig; +} + +export async function createProtocolFeeConfig( + chain: ChainName, +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = normalizeAddressEvm(owner); + let beneficiary; + let sameAsOwner = false; + sameAsOwner = await confirm({ + message: 'Use this same address for the beneficiary?', + }); + if (sameAsOwner) { + beneficiary = ownerAddress; + } else { + beneficiary = await input({ + message: 'Enter beneficiary address', + }); + } + const beneficiaryAddress = normalizeAddressEvm(beneficiary); + // TODO: input in gwei, wei, etc + const maxProtocolFee = toWei( + await input({ + message: `Enter max protocol fee ${nativeTokenAndDecimals( + chain, + )} e.g. 1.0)`, + }), + ); + const protocolFee = toWei( + await input({ + message: `Enter protocol fee in ${nativeTokenAndDecimals( + chain, + )} e.g. 0.01)`, + }), + ); + if (BigNumberJs(protocolFee).gt(maxProtocolFee)) { + errorRed('Protocol fee cannot be greater than max protocol fee'); + throw new Error('Invalid protocol fee'); + } + + return { + type: HookType.PROTOCOL_FEE, + maxProtocolFee: maxProtocolFee.toString(), + protocolFee: protocolFee.toString(), + beneficiary: beneficiaryAddress, + owner: ownerAddress, + }; +} + +export async function createIGPConfig( + remotes: ChainName[], +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = normalizeAddressEvm(owner); + let beneficiary, oracleKey; + let sameAsOwner = false; + sameAsOwner = await confirm({ + message: 'Use this same address for the beneficiary and gasOracleKey?', + }); + if (sameAsOwner) { + beneficiary = ownerAddress; + oracleKey = ownerAddress; + } else { + beneficiary = await input({ + message: 'Enter beneficiary address', + }); + oracleKey = await input({ + message: 'Enter gasOracleKey address', + }); + } + const beneficiaryAddress = normalizeAddressEvm(beneficiary); + const oracleKeyAddress = normalizeAddressEvm(oracleKey); + const overheads: ChainMap = {}; + for (const chain of remotes) { + const overhead = parseInt( + await input({ + message: `Enter overhead for ${chain} (eg 75000)`, + }), + ); + overheads[chain] = overhead; + } + return { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: beneficiaryAddress, + owner: ownerAddress, + oracleKey: oracleKeyAddress, + overhead: overheads, + gasOracleType: objMap( + overheads, + () => GasOracleContractType.StorageGasOracle, + ), + }; +} + +export async function createAggregationConfig( + chain: ChainName, + remotes: ChainName[], +): Promise { + const hooksNum = parseInt( + await input({ + message: 'Enter the number of hooks to aggregate (number)', + }), + 10, + ); + const hooks: Array = []; + for (let i = 0; i < hooksNum; i++) { + logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`); + hooks.push(await createHookConfig(chain, remotes)); + } + return { + type: HookType.AGGREGATION, + hooks, + }; +} + +export async function createRoutingConfig( + origin: ChainName, + remotes: ChainName[], +): Promise { + const owner = await input({ + message: 'Enter owner address', + }); + const ownerAddress = owner; + + const domainsMap: ChainMap = {}; + for (const chain of remotes) { + await confirm({ + message: `You are about to configure hook for remote chain ${chain}. Continue?`, + }); + const config = await createHookConfig(origin, remotes); + domainsMap[chain] = config; + } + return { + type: HookType.ROUTING, + owner: ownerAddress, + domains: domainsMap, + }; +} + +function nativeTokenAndDecimals(chain: ChainName) { + return `10^${ + chainMetadata[chain].nativeToken?.decimals ?? '18' + } which you cannot exceed (in ${ + chainMetadata[chain].nativeToken?.symbol ?? 'eth' + }`; +} diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index ab75581d88..eb18006077 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -112,7 +112,7 @@ export async function createIsmConfigMap({ const result: ZodIsmConfigMap = {}; for (const chain of chains) { log(`Setting values for chain ${chain}`); - result[chain] = await createIsmConfig(chain, chainConfigPath); + result[chain] = await createIsmConfig(chain, chains); // TODO consider re-enabling. Disabling based on feedback from @nambrot for now. // repeat = await confirm({ @@ -132,8 +132,8 @@ export async function createIsmConfigMap({ } export async function createIsmConfig( - chain: ChainName, - chainConfigPath: string, + remote: ChainName, + origins: ChainName[], ): Promise { let lastConfig: ZodIsmConfig; const moduleType = await select({ @@ -177,9 +177,9 @@ export async function createIsmConfig( ) { lastConfig = await createMultisigConfig(moduleType); } else if (moduleType === IsmType.ROUTING) { - lastConfig = await createRoutingConfig(chain, chainConfigPath); + lastConfig = await createRoutingConfig(remote, origins); } else if (moduleType === IsmType.AGGREGATION) { - lastConfig = await createAggregationConfig(chain, chainConfigPath); + lastConfig = await createAggregationConfig(remote, origins); } else if (moduleType === IsmType.TEST_ISM) { lastConfig = { type: IsmType.TEST_ISM }; } else { @@ -208,8 +208,8 @@ export async function createMultisigConfig( } export async function createAggregationConfig( - chain: ChainName, - chainConfigPath: string, + remote: ChainName, + chains: ChainName[], ): Promise { const isms = parseInt( await input({ @@ -227,7 +227,7 @@ export async function createAggregationConfig( const modules: Array = []; for (let i = 0; i < isms; i++) { - modules.push(await createIsmConfig(chain, chainConfigPath)); + modules.push(await createIsmConfig(remote, chains)); } return { type: IsmType.AGGREGATION, @@ -237,27 +237,21 @@ export async function createAggregationConfig( } export async function createRoutingConfig( - chain: ChainName, - chainConfigPath: string, + remote: ChainName, + chains: ChainName[], ): Promise { const owner = await input({ message: 'Enter owner address', }); const ownerAddress = owner; - const customChains = readChainConfigsIfExists(chainConfigPath); - delete customChains[chain]; - const chains = await runMultiChainSelectionStep( - customChains, - `Select origin chains to be verified on ${chain}`, - [chain], - ); + const origins = chains.filter((chain) => chain !== remote); const domainsMap: ChainMap = {}; - for (const chain of chains) { + for (const chain of origins) { await confirm({ message: `You are about to configure ISM from source chain ${chain}. Continue?`, }); - const config = await createIsmConfig(chain, chainConfigPath); + const config = await createIsmConfig(chain, chains); domainsMap[chain] = config; } return { diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index b58a946901..bb9e5c7fef 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -7,7 +7,7 @@ import { CoreConfig, DeployedIsm, GasOracleContractType, - HookType, + HooksConfig, HyperlaneAddressesMap, HyperlaneContractsMap, HyperlaneCore, @@ -31,7 +31,7 @@ import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; import { log, logBlue, logGray, logGreen, logRed } from '../../logger.js'; import { runDeploymentArtifactStep } from '../config/artifacts.js'; -import { readHookConfig } from '../config/hooks.js'; +import { presetHookConfigs, readHooksConfigMap } from '../config/hooks.js'; import { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; @@ -95,8 +95,7 @@ export async function runCoreDeploy({ const multisigConfigs = isIsmConfig ? defaultMultisigConfigs : (result as ChainMap); - // TODO re-enable when hook config is actually used - await runHookStep(chains, hookConfigPath); + const hooksConfig = await runHookStep(chains, hookConfigPath); const deploymentParams: DeployParams = { chains, @@ -105,6 +104,7 @@ export async function runCoreDeploy({ artifacts, ismConfigs, multisigConfigs, + hooksConfig, outPath, skipConfirmation, }; @@ -194,24 +194,8 @@ async function runHookStep( _selectedChains: ChainName[], hookConfigPath?: string, ) { - if ('TODO: Skip this step for now as values are unused') return; - - // const presetConfigChains = Object.keys(presetHookConfigs); - - if (!hookConfigPath) { - logBlue( - '\n', - 'Hyperlane instances can take an Interchain Security Module (ISM).', - ); - hookConfigPath = await runFileSelectionStep( - './configs/', - 'Hook config', - 'hook', - ); - } - const configs = readHookConfig(hookConfigPath); - if (!configs) return; - log(`Found hook configs for chains: ${Object.keys(configs).join(', ')}`); + if (!hookConfigPath) return {}; + return readHooksConfigMap(hookConfigPath); } interface DeployParams { @@ -221,6 +205,7 @@ interface DeployParams { artifacts?: HyperlaneAddressesMap; ismConfigs?: ChainMap; multisigConfigs?: ChainMap; + hooksConfig?: ChainMap; outPath: string; skipConfirmation: boolean; } @@ -258,6 +243,7 @@ async function executeDeploy({ artifacts = {}, ismConfigs = {}, multisigConfigs = {}, + hooksConfig = {}, }: DeployParams) { logBlue('All systems ready, captain! Beginning deployment...'); @@ -320,7 +306,8 @@ async function executeDeploy({ owner, chains, defaultIsms, - multisigConfigs ?? defaultMultisigConfigs, // TODO: fix https://github.com/hyperlane-xyz/issues/issues/773 + hooksConfig, + multisigConfigs, ); const coreContracts = await coreDeployer.deploy(coreConfigs); artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); @@ -372,32 +359,23 @@ function buildCoreConfigMap( owner: Address, chains: ChainName[], defaultIsms: ChainMap
, - multisigConfig: ChainMap, + hooksConfig: ChainMap, + multisigConfigs: ChainMap, ): ChainMap { return chains.reduce>((config, chain) => { - const igpConfig = buildIgpConfigMap(owner, chains, multisigConfig); + const hooks = + hooksConfig[chain] ?? + presetHookConfigs( + owner, + chain, + chains.filter((c) => c !== chain), + multisigConfigs[chain], // if no multisig config, uses default 2/3 + ); config[chain] = { owner, defaultIsm: defaultIsms[chain], - defaultHook: { - type: HookType.AGGREGATION, - hooks: [ - { - type: HookType.MERKLE_TREE, - }, - { - type: HookType.INTERCHAIN_GAS_PAYMASTER, - ...igpConfig[chain], - }, - ], - }, - requiredHook: { - type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: ethers.utils.parseUnits('0', 'wei'), // 1 wei - beneficiary: owner, - owner, - }, + defaultHook: hooks.default, + requiredHook: hooks.required, }; return config; }, {}); @@ -419,7 +397,7 @@ function buildTestRecipientConfigMap( }, {}); } -function buildIgpConfigMap( +export function buildIgpConfigMap( owner: Address, chains: ChainName[], multisigConfigs: ChainMap, diff --git a/typescript/cli/src/tests/hooks.test.ts b/typescript/cli/src/tests/hooks.test.ts new file mode 100644 index 0000000000..2848c0c061 --- /dev/null +++ b/typescript/cli/src/tests/hooks.test.ts @@ -0,0 +1,92 @@ +import { expect } from 'chai'; + +import { + ChainMap, + GasOracleContractType, + HookType, + HooksConfig, +} from '@hyperlane-xyz/sdk'; + +import { readHooksConfigMap } from '../config/hooks.js'; + +describe('readHooksConfigMap', () => { + it('parses and validates example correctly', () => { + const hooks = readHooksConfigMap('examples/hooks.yaml'); + + const exampleHooksConfig: ChainMap = { + anvil1: { + required: { + type: HookType.PROTOCOL_FEE, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + maxProtocolFee: '1000000000000000000', + protocolFee: '200000000000000', + }, + default: { + type: HookType.ROUTING, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + domains: { + anvil2: { + type: HookType.AGGREGATION, + hooks: [ + { + type: HookType.MERKLE_TREE, + }, + { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + gasOracleType: { + anvil2: GasOracleContractType.StorageGasOracle, + }, + overhead: { anvil2: 50000 }, + }, + ], + }, + }, + }, + }, + anvil2: { + required: { + type: HookType.PROTOCOL_FEE, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + maxProtocolFee: '1000000000000000000', + protocolFee: '200000000000000', + }, + default: { + type: HookType.ROUTING, + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + domains: { + anvil1: { + type: HookType.AGGREGATION, + hooks: [ + { + type: HookType.MERKLE_TREE, + }, + { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + gasOracleType: { + anvil1: GasOracleContractType.StorageGasOracle, + }, + overhead: { anvil1: 50000 }, + }, + ], + }, + }, + }, + }, + }; + expect(hooks).to.deep.equal(exampleHooksConfig); + }); + + it('parsing failure, missing internal key "overhead"', () => { + expect(() => { + readHooksConfigMap('src/tests/hooks/safe-parse-fail.yaml'); + }).to.throw('Invalid hook config: anvil2,default => Invalid input'); + }); +}); diff --git a/typescript/cli/src/tests/hooks/safe-parse-fail.yaml b/typescript/cli/src/tests/hooks/safe-parse-fail.yaml new file mode 100644 index 0000000000..4a2a5cedbf --- /dev/null +++ b/typescript/cli/src/tests/hooks/safe-parse-fail.yaml @@ -0,0 +1,44 @@ +anvil1: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' + protocolFee: '200000000000000' + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil2: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + overhead: + anvil2: 50000 + gasOracleType: + anvil2: StorageGasOracle +anvil2: + required: + type: protocolFee + maxProtocolFee: '1000000000000000000' + protocolFee: '200000000000000' + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + default: + type: domainRoutingHook + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + domains: + anvil1: + type: aggregationHook + hooks: + - type: merkleTreeHook + - type: interchainGasPaymaster + beneficiary: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + owner: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + oracleKey: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' + gasOracleType: + anvil1: StorageGasOracle diff --git a/typescript/infra/config/environments/mainnet3/core.ts b/typescript/infra/config/environments/mainnet3/core.ts index 54405eb532..7fa6138334 100644 --- a/typescript/infra/config/environments/mainnet3/core.ts +++ b/typescript/infra/config/environments/mainnet3/core.ts @@ -36,8 +36,8 @@ export const core: ChainMap = objMap(owners, (local, owner) => { const requiredHook: ProtocolFeeHookConfig = { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(0), // 0 wei + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(0).toString(), // 0 wei beneficiary: owner, owner, }; diff --git a/typescript/infra/config/environments/test/core.ts b/typescript/infra/config/environments/test/core.ts index 0c2f476e42..397059c52c 100644 --- a/typescript/infra/config/environments/test/core.ts +++ b/typescript/infra/config/environments/test/core.ts @@ -57,8 +57,8 @@ export const core: ChainMap = objMap(owners, (local, owner) => { const requiredHook: ProtocolFeeHookConfig = { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(1), // 1 wei + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(1).toString(), // 1 wei beneficiary: owner, owner, }; diff --git a/typescript/infra/config/environments/testnet4/core.ts b/typescript/infra/config/environments/testnet4/core.ts index 6198c55799..f8747befa7 100644 --- a/typescript/infra/config/environments/testnet4/core.ts +++ b/typescript/infra/config/environments/testnet4/core.ts @@ -78,8 +78,8 @@ export const core: ChainMap = objMap(owners, (local, owner) => { const requiredHook: ProtocolFeeHookConfig = { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(1), // 1 wei + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(1).toString(), // 1 wei of native token beneficiary: owner, owner, }; diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index a0e60a88b7..0cbedfb7a7 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -1,5 +1,3 @@ -import { BigNumber } from 'ethers'; - import { Address } from '@hyperlane-xyz/utils'; import { IgpConfig } from '../gas/types'; @@ -30,8 +28,8 @@ export type IgpHookConfig = IgpConfig & { export type ProtocolFeeHookConfig = { type: HookType.PROTOCOL_FEE; - maxProtocolFee: BigNumber; - protocolFee: BigNumber; + maxProtocolFee: string; + protocolFee: string; beneficiary: Address; owner: Address; }; @@ -64,3 +62,8 @@ export type HookConfig = | OpStackHookConfig | DomainRoutingHookConfig | FallbackRoutingHookConfig; + +export type HooksConfig = { + required: HookConfig; + default: HookConfig; +}; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index f12ce8e06f..a5122765ce 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -116,6 +116,7 @@ export { FallbackRoutingHookConfig, HookConfig, HookType, + HooksConfig, IgpHookConfig, MerkleTreeHookConfig, OpStackHookConfig, diff --git a/typescript/sdk/src/test/testUtils.ts b/typescript/sdk/src/test/testUtils.ts index 0ce5da89dc..3d2c48e56e 100644 --- a/typescript/sdk/src/test/testUtils.ts +++ b/typescript/sdk/src/test/testUtils.ts @@ -58,8 +58,8 @@ export function testCoreConfig( }, requiredHook: { type: HookType.PROTOCOL_FEE, - maxProtocolFee: ethers.utils.parseUnits('1', 'gwei'), // 1 gwei of native token - protocolFee: BigNumber.from(1), // 1 wei + maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token + protocolFee: BigNumber.from(1).toString(), // 1 wei beneficiary: nonZeroAddress, owner, }, From eb83cf891cfc16fe4be3e43feb95997fb9525ca8 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 4 Dec 2023 13:02:27 -0500 Subject: [PATCH 7/8] Enable for-in lint rule (#3013) ### Description https://eslint.org/docs/latest/rules/guard-for-in ### Drive-by changes Fix lint error in SDK Sealevel adapter ### Backward compatibility Yes --- .eslintrc | 1 + typescript/sdk/src/ism/HyperlaneIsmFactory.ts | 2 +- typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc b/.eslintrc index c1ce429fda..0855bdf61b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,7 @@ "no-extra-boolean-cast": ["error"], "no-ex-assign": ["error"], "no-constant-condition": ["off"], + "guard-for-in": ["error"], "@typescript-eslint/ban-ts-comment": ["off"], "@typescript-eslint/explicit-module-boundary-types": ["off"], "@typescript-eslint/no-explicit-any": ["off"], diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index 3d36e8fde0..0f1dbba529 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -165,7 +165,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp { // ? this.getContracts(chain).defaultFallbackRoutingIsmFactory // : this.getContracts(chain).routingIsmFactory; const isms: ChainMap
= {}; - for (const origin in config.domains) { + for (const origin of Object.keys(config.domains)) { const ism = await this.deploy(chain, config.domains[origin], origin); isms[origin] = ism.address; } diff --git a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts index b6d3b00244..2d93c4d2f9 100644 --- a/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/SealevelTokenAdapter.ts @@ -302,7 +302,7 @@ export abstract class SealevelHypTokenAdapter return tx; } - async getIgpKeys() { + async getIgpKeys(): Promise { const tokenData = await this.getTokenAccountData(); if (!tokenData.interchain_gas_paymaster) return undefined; const igpConfig = tokenData.interchain_gas_paymaster; From d0c1f8f11e8485ce3bdf11cf2353b3b0150274f9 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Mon, 4 Dec 2023 13:36:32 -0500 Subject: [PATCH 8/8] Rename `hook-config.yaml` to `hooks.yaml` in `ci-test.sh` (#3016) ### Description - CI fails otherwise ### Drive-by changes ### Related issues ### Backward compatibility ### Testing Manual --- typescript/cli/ci-test.sh | 2 +- typescript/cli/src/config/hooks.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 3ae496f278..00e61f55c1 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -39,7 +39,7 @@ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --chains ./examples/anvil-chains.yaml \ --artifacts /tmp/empty-artifacts.json \ --ism ./examples/ism.yaml \ - --hook ./examples/hook-config.yaml \ + --hook ./examples/hooks.yaml \ --out /tmp \ --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --yes diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index 62f66e0fa6..6392d60f95 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -195,7 +195,7 @@ export async function createHooksConfigMap({ mergeYamlOrJson(outPath, result, format); } else { errorRed( - `Hook config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/hook-config.yaml for an example`, + `Hook config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/hooks.yaml for an example`, ); throw new Error('Invalid hook config'); }