Refactor IGP related code out of HyperlaneCore (#1907)

### Description

A number of changes intended to make the SDK more easily consumed by
hyperlane-deploy

- Splits the IGP related code out of HyperlaneCore* into HyperlaneIgp*
- Moves agent config building from infra to SDK so that it can be
accessed by hyperlane-deploy
- Modifies CoreConfig to specify a ChainMap<MultisigIsmConfig> (i.e.
specify everything that you need to know to deploy to a chain in that
chain's CoreConfig)
- Removes core.extendWithConnectionClientConfig 
- Moves common logic into HyperlaneAppGovernor to be shared between
HyperlaneCoreGovernor and HyperlaneIgpGovernor
- Adds TestRecipient contract addresses to the SDK addresses for
consumption by hyperlane-deploy
- Allow buildContracts to build when address keys are a superset of
factory keys (via filterContracts). This is useful as it allows us to
just throw all the addresses at a HyperlaneApp constructor without
needing to remove the ones that aren't relevant
- Removes InterchainGasCalculator, the logic we want to keep now lives
in HyperlaneIgp
- Allows chains to be added to a MultiProvider

### Drive-by changes

- Merges the infra govern script into the check script (to dedupe)
- Some minor renaming where "Core" was being used liberally
- Default to using whatever is configured in `owners.ts` in the router
configs for infra (for ICA, IQS, HelloWorld, and LL deployment)

### Related issues

- Fixes #[issue number here]

### Backward compatibility

_Are these changes backward compatible?_

No, existing consumers of the SDK may need to adjust.

_Are there any infrastructure implications, e.g. changes that would
prohibit deploying older commits using this infra tooling?_

None


### Testing

_What kind of testing have these changes undergone?_

Manual / Unit Tests
yorhodes-patch-1
Asa Oines 2 years ago committed by GitHub
parent e8d90775f7
commit 97d17570cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .github/workflows/node.yml
  2. 2
      rust/.gitignore
  3. 2
      typescript/helloworld
  4. 1
      typescript/infra/.gitignore
  5. 11
      typescript/infra/config/environments/mainnet2/agent.ts
  6. 183
      typescript/infra/config/environments/mainnet2/core.ts
  7. 5
      typescript/infra/config/environments/mainnet2/funding.ts
  8. 7
      typescript/infra/config/environments/mainnet2/helloworld.ts
  9. 44
      typescript/infra/config/environments/mainnet2/igp.ts
  10. 9
      typescript/infra/config/environments/mainnet2/index.ts
  11. 13
      typescript/infra/config/environments/mainnet2/owners.ts
  12. 9
      typescript/infra/config/environments/test/agent.ts
  13. 62
      typescript/infra/config/environments/test/core.ts
  14. 42
      typescript/infra/config/environments/test/igp.ts
  15. 4
      typescript/infra/config/environments/test/index.ts
  16. 17
      typescript/infra/config/environments/test/multisigIsm.ts
  17. 10
      typescript/infra/config/environments/test/owners.ts
  18. 11
      typescript/infra/config/environments/testnet3/agent.ts
  19. 161
      typescript/infra/config/environments/testnet3/core.ts
  20. 5
      typescript/infra/config/environments/testnet3/funding.ts
  21. 7
      typescript/infra/config/environments/testnet3/helloworld.ts
  22. 42
      typescript/infra/config/environments/testnet3/igp.ts
  23. 9
      typescript/infra/config/environments/testnet3/index.ts
  24. 5
      typescript/infra/config/environments/testnet3/middleware.ts
  25. 10
      typescript/infra/config/environments/testnet3/owners.ts
  26. 11
      typescript/infra/fork.sh
  27. 15
      typescript/infra/hardhat.config.ts
  28. 4
      typescript/infra/scripts/announce-validators.ts
  29. 80
      typescript/infra/scripts/check-deploy.ts
  30. 96
      typescript/infra/scripts/core.ts
  31. 138
      typescript/infra/scripts/deploy.ts
  32. 16
      typescript/infra/scripts/funding/fund-keys-from-deployer.ts
  33. 10
      typescript/infra/scripts/funding/reclaim-from-igp.ts
  34. 18
      typescript/infra/scripts/gas-oracle/update-storage-gas-oracle.ts
  35. 50
      typescript/infra/scripts/govern.ts
  36. 11
      typescript/infra/scripts/helloworld/check.ts
  37. 5
      typescript/infra/scripts/helloworld/deploy.ts
  38. 24
      typescript/infra/scripts/helloworld/kathy.ts
  39. 26
      typescript/infra/scripts/helloworld/utils.ts
  40. 10
      typescript/infra/scripts/list-validator-checkpoint-indices.ts
  41. 6
      typescript/infra/scripts/merge-sdk-contract-addresses.ts
  42. 4
      typescript/infra/scripts/middleware/deploy-accounts.ts
  43. 6
      typescript/infra/scripts/middleware/deploy-liquidity-layer.ts
  44. 4
      typescript/infra/scripts/middleware/deploy-queries.ts
  45. 6
      typescript/infra/scripts/testcontracts/deploy-testquerysender.ts
  46. 92
      typescript/infra/scripts/utils.ts
  47. 14
      typescript/infra/scripts/verify-validators.ts
  48. 12
      typescript/infra/src/agents/index.ts
  49. 56
      typescript/infra/src/config/agent.ts
  50. 17
      typescript/infra/src/config/chain.ts
  51. 16
      typescript/infra/src/config/environment.ts
  52. 6
      typescript/infra/src/config/funding.ts
  53. 6
      typescript/infra/src/config/helloworld.ts
  54. 2
      typescript/infra/src/config/index.ts
  55. 6
      typescript/infra/src/config/middleware.ts
  56. 107
      typescript/infra/src/core/deploy.ts
  57. 325
      typescript/infra/src/core/govern.ts
  58. 71
      typescript/infra/src/gas/deploy.ts
  59. 126
      typescript/infra/src/gas/govern.ts
  60. 219
      typescript/infra/src/govern/HyperlaneAppGovernor.ts
  61. 0
      typescript/infra/src/govern/multisend.ts
  62. 5
      typescript/infra/src/scraper/deploy.ts
  63. 6
      typescript/infra/src/testcontracts/testquerysender.ts
  64. 16
      typescript/infra/src/utils/utils.ts
  65. 8
      typescript/infra/test/core.test.ts
  66. 21
      typescript/sdk/src/HyperlaneApp.ts
  67. 97
      typescript/sdk/src/agents/types.ts
  68. 10
      typescript/sdk/src/consts/bytecode.ts
  69. 56
      typescript/sdk/src/consts/environments/index.ts
  70. 11
      typescript/sdk/src/consts/environments/mainnet.json
  71. 9
      typescript/sdk/src/consts/environments/testnet.json
  72. 173
      typescript/sdk/src/consts/multisigIsm.ts
  73. 39
      typescript/sdk/src/contracts.ts
  74. 82
      typescript/sdk/src/core/HyperlaneCore.ts
  75. 223
      typescript/sdk/src/core/HyperlaneCoreChecker.ts
  76. 241
      typescript/sdk/src/core/HyperlaneCoreDeployer.ts
  77. 29
      typescript/sdk/src/core/TestCoreDeployer.ts
  78. 28
      typescript/sdk/src/core/contracts.ts
  79. 63
      typescript/sdk/src/core/types.ts
  80. 27
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  81. 15
      typescript/sdk/src/deploy/utils.ts
  82. 129
      typescript/sdk/src/gas/HyperlaneIgp.ts
  83. 194
      typescript/sdk/src/gas/HyperlaneIgpChecker.ts
  84. 184
      typescript/sdk/src/gas/HyperlaneIgpDeployer.ts
  85. 78
      typescript/sdk/src/gas/calculator.hardhat-test.ts
  86. 267
      typescript/sdk/src/gas/calculator.test.ts
  87. 416
      typescript/sdk/src/gas/calculator.ts
  88. 37
      typescript/sdk/src/gas/contracts.ts
  89. 83
      typescript/sdk/src/gas/types.ts
  90. 105
      typescript/sdk/src/index.ts
  91. 8
      typescript/sdk/src/middleware/accounts.hardhat-test.ts
  92. 4
      typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts
  93. 18
      typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts
  94. 8
      typescript/sdk/src/middleware/queries.hardhat-test.ts
  95. 61
      typescript/sdk/src/providers/MultiProvider.ts
  96. 6
      typescript/sdk/src/proxy.ts
  97. 39
      typescript/sdk/src/router/app.ts
  98. 9
      typescript/sdk/src/test/envSubsetDeployer/app.ts
  99. 18
      typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts
  100. 12
      typescript/sdk/src/test/envSubsetDeployer/deploy-single-chain.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -115,6 +115,7 @@ jobs:
strategy:
matrix:
environment: [testnet3, mainnet2]
module: [core, igp]
steps:
- uses: actions/checkout@v3
@ -126,8 +127,8 @@ jobs:
- name: Install Foundry
uses: onbjerg/foundry-toolchain@v1
- name: Test ${{ matrix.environment }} deployment (check, deploy, govern, check again)
run: cd typescript/infra && ./fork.sh ${{ matrix.environment }}
- name: Test ${{ matrix.environment }} ${{ matrix.module }} deployment (check, deploy, govern, check again)
run: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }}
test-sol:
env:

2
rust/.gitignore vendored

@ -2,4 +2,4 @@ target
validatordb
relayerdb
kathydb
config/test_*
config/test_config.json

@ -1 +1 @@
Subproject commit 99bcdfca24e8c9a5f2d3cca8eb6da891ff3c715e
Subproject commit 3b443c79bcb7671ef3d49ba7d6765c9adb139789

@ -5,3 +5,4 @@ dist/
cache/
test/outputs
config/environments/test/core/
config/environments/test/igp/

@ -1,11 +1,8 @@
import { chainMetadata } from '@hyperlane-xyz/sdk';
import { AgentConnectionType, chainMetadata } from '@hyperlane-xyz/sdk';
import { ALL_KEY_ROLES, KEY_ROLE_ENUM } from '../../../src/agents/roles';
import { AgentConfig } from '../../../src/config';
import {
ConnectionType,
GasPaymentEnforcementPolicyType,
} from '../../../src/config/agent';
import { GasPaymentEnforcementPolicyType } from '../../../src/config/agent';
import { Contexts } from '../../contexts';
import { helloworldMatchingList, routerMatchingList } from '../../utils';
@ -36,7 +33,7 @@ export const hyperlane: AgentConfig = {
},
environmentChainNames: chainNames,
contextChainNames: chainNames,
connectionType: ConnectionType.HttpQuorum,
connectionType: AgentConnectionType.HttpQuorum,
validators,
relayer: {
default: {
@ -78,7 +75,7 @@ export const releaseCandidate: AgentConfig = {
},
environmentChainNames: chainNames,
contextChainNames: chainNames,
connectionType: ConnectionType.HttpFallback,
connectionType: AgentConnectionType.HttpFallback,
relayer: {
default: {
whitelist: releaseCandidateHelloworldMatchingList,

@ -1,177 +1,20 @@
import {
ChainMap,
CoreConfig,
GasOracleContractType,
defaultMultisigIsmConfigs,
objMap,
} from '@hyperlane-xyz/sdk';
import { MainnetChains, chainNames } from './chains';
import { chainNames } from './chains';
import { owners } from './owners';
function getGasOracles(local: MainnetChains) {
return Object.fromEntries(
chainNames
.filter((name) => name !== local)
.map((name) => [name, GasOracleContractType.StorageGasOracle]),
);
}
const KEY_FUNDER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba';
export const core: ChainMap<CoreConfig> = {
celo: {
owner: '0x1DE69322B55AC7E0999F8e7738a1428C8b130E4d',
multisigIsm: {
threshold: 4,
validators: [
'0x1f20274b1210046769d48174c2f0e7c25ca7d5c5', // abacus works 0
'0x3bc014bafa43f93d534aed34f750997cdffcf007', // dsrv
'0xd79d506d741fa735938f7b7847a926e34a6fe6b0', // everstake
'0xe4a258bc61e65914c2a477b2a8a433ab4ebdf44b', // zplabs
'0x6aea63b0be4679c1385c26a92a3ff8aa6a8379f2', // staked
'0xc0085e1a49bcc69e534272adb82c74c0e007e1ca', // zkv
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('celo'),
},
},
ethereum: {
owner: '0x12C5AB61Fe17dF9c65739DBa73dF294708f78d23',
multisigIsm: {
threshold: 4,
validators: [
'0x4c327ccb881a7542be77500b2833dc84c839e7b7', // abacus works 0
'0x84cb373148ef9112b277e68acf676fefa9a9a9a0', // dsrv
'0x0d860c2b28bec3af4fd3a5997283e460ff6f2789', // everstake
'0xd4c1211f0eefb97a846c4e6d6589832e52fc03db', // zp labs
'0x600c90404d5c9df885404d2cc5350c9b314ea3a2', // staked
'0x892DC66F5B2f8C438E03f6323394e34A9C24F2D6', // zkv
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('ethereum'),
},
},
avalanche: {
owner: '0xDF9B28B76877f1b1B4B8a11526Eb7D8D7C49f4f3',
multisigIsm: {
threshold: 4,
validators: [
'0xa7aa52623fe3d78c343008c95894be669e218b8d', // abacus works 0
'0xb6004433fb04f643e2d48ae765c0e7f890f0bc0c', // dsrv
'0xa07e213e0985b21a6128e6c22ab5fb73948b0cc2', // everstake
'0x73853ed9a5f6f2e4c521970a94d43469e3cdaea6', // zplabs
'0xbd2e136cda02ba627ca882e49b184cbe976081c8', // staked
'0x1418126f944a44dad9edbab32294a8c890e7a9e3', // zkv
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('avalanche'),
},
},
polygon: {
owner: '0x0D195469f76146F6ae3De8fc887e0f0DFBA691e7',
multisigIsm: {
threshold: 4,
validators: [
'0x59a001c3451e7f9f3b4759ea215382c1e9aa5fc1', // abacus works 0
'0x009fb042d28944017177920c1d40da02bfebf474', // dsrv
'0xba4b13e23705a5919c1901150d9697e8ffb3ea71', // everstake
'0x2faa4071b718972f9b4beec1d8cbaa4eb6cca6c6', // zp labs
'0x5ae9b0f833dfe09ef455562a1f603f1634504dd6', // staked
'0x6a163d312f7352a95c9b81dca15078d5bf77a442', // zkv
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('polygon'),
},
},
bsc: {
owner: '0xA0d3dcB9d61Fba32cc02Ad63983e101b29E2f28a',
multisigIsm: {
threshold: 4,
validators: [
'0xcc84b1eb711e5076b2755cf4ad1d2b42c458a45e', // abacus works 0
'0xefe34eae2bca1846b895d2d0762ec21796aa196a', // dsrv
'0x662674e80e189b0861d6835c287693f50ee0c2ff', // everstake
'0x8a0f59075af466841808c529624807656309c9da', // zp labs
'0xdd2ff046ccd748a456b4757a73d47f165469669f', // staked
'0x034c4924c30ec4aa1b7f3ad58548988f0971e1bf', // zkv
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('bsc'),
},
},
arbitrum: {
owner: '0xbA47E1b575980B7D1b1508cc48bE1Df4EE508111',
multisigIsm: {
threshold: 4,
validators: [
'0xbcb815f38d481a5eba4d7ac4c9e74d9d0fc2a7e7', // abacus works 0
'0xd839424e2e5ace0a81152298dc2b1e3bb3c7fb20', // dsrv
'0xb8085c954b75b7088bcce69e61d12fcef797cd8d', // everstake
'0x9856dcb10fd6e5407fa74b5ab1d3b96cc193e9b7', // zp labs
'0x505dff4e0827aa5065f5e001db888e0569d46490', // staked
'0x25c6779d4610f940bf2488732e10bcffb9d36f81', // ZKV
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('arbitrum'),
},
},
optimism: {
owner: '0xb523CFAf45AACF472859f8B793CB0BFDB16bD257',
multisigIsm: {
threshold: 4,
validators: [
'0x9f2296d5cfc6b5176adc7716c7596898ded13d35', // abacus works 0
'0x9c10bbe8efa03a8f49dfdb5c549258e3a8dca097', // dsrv
'0x62144d4a52a0a0335ea5bb84392ef9912461d9dd', // everstake
'0xaff4718d5d637466ad07441ee3b7c4af8e328dbd', // zp labs
'0xc64d1efeab8ae222bc889fe669f75d21b23005d9', // staked
'0xfa174eb2b4921bb652bc1ada3e8b00e7e280bf3c', // ZKV
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('optimism'),
},
},
moonbeam: {
owner: '0xF0cb1f968Df01fc789762fddBfA704AE0F952197',
multisigIsm: {
threshold: 3,
validators: [
'0x237243d32d10e3bdbbf8dbcccc98ad44c1c172ea', // abacus works 0
'0x9509c8cf0a06955f27342262af501b74874e98fb', // dsrv
'0xb7113c999e4d587b162dd1a28c73f3f51c6bdcdc', // everstake
'0x26725501597d47352a23cd26f122709f69ad53bc', // staked
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('moonbeam'),
},
},
gnosis: {
owner: '0x36b0AA0e7d04e7b825D7E409FEa3c9A3d57E4C22',
multisigIsm: {
threshold: 2,
validators: [
'0xd0529ec8df08d0d63c0f023786bfa81e4bb51fd6', // abacus works 0
'0x829d6ec129bc7187fb1ed161adcf7939fe0c515f', // abacus works 1
'0x00009f8935e94bfe52ab3441df3526ab7cc38db1', // abacus works 2
],
},
igp: {
beneficiary: KEY_FUNDER_ADDRESS,
gasOracles: getGasOracles('gnosis'),
},
},
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
return {
owner,
multisigIsm: Object.fromEntries(
Object.entries(defaultMultisigIsmConfigs).filter(
([chain]) => chain !== local && chainNames.includes(chain),
),
),
};
});

@ -1,5 +1,6 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { KEY_ROLE_ENUM } from '../../../src/agents/roles';
import { ConnectionType } from '../../../src/config/agent';
import { KeyFunderConfig } from '../../../src/config/funding';
import { Contexts } from '../../contexts';
@ -22,5 +23,5 @@ export const keyFunderConfig: KeyFunderConfig = {
[Contexts.Hyperlane]: [KEY_ROLE_ENUM.Relayer, KEY_ROLE_ENUM.Kathy],
[Contexts.ReleaseCandidate]: [KEY_ROLE_ENUM.Relayer, KEY_ROLE_ENUM.Kathy],
},
connectionType: ConnectionType.Http,
connectionType: AgentConnectionType.Http,
};

@ -1,5 +1,6 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { HelloWorldConfig } from '../../../src/config';
import { ConnectionType } from '../../../src/config/agent';
import { HelloWorldKathyRunMode } from '../../../src/config/helloworld';
import { Contexts } from '../../contexts';
@ -23,7 +24,7 @@ export const hyperlane: HelloWorldConfig = {
},
messageSendTimeout: 1000 * 60 * 8, // 8 min
messageReceiptTimeout: 1000 * 60 * 20, // 20 min
connectionType: ConnectionType.HttpFallback,
connectionType: AgentConnectionType.HttpFallback,
cyclesBetweenEthereumMessages: 3, // Skip 3 cycles of Ethereum, i.e. send/receive Ethereum messages every 32 hours.
},
};
@ -43,7 +44,7 @@ export const releaseCandidate: HelloWorldConfig = {
},
messageSendTimeout: 1000 * 60 * 8, // 8 min
messageReceiptTimeout: 1000 * 60 * 20, // 20 min
connectionType: ConnectionType.Http,
connectionType: AgentConnectionType.Http,
},
};

@ -0,0 +1,44 @@
import {
ChainMap,
GasOracleContractType,
OverheadIgpConfig,
defaultMultisigIsmConfigs,
multisigIsmVerificationCost,
objMap,
} from '@hyperlane-xyz/sdk';
import { utils } from '@hyperlane-xyz/utils';
import { MainnetChains, chainNames } from './chains';
import { owners } from './owners';
const KEY_FUNDER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba';
function getGasOracles(local: MainnetChains) {
return Object.fromEntries(
utils
.exclude(local, chainNames)
.map((name) => [name, GasOracleContractType.StorageGasOracle]),
);
}
export const igp: ChainMap<OverheadIgpConfig> = objMap(
owners,
(chain, owner) => {
return {
owner,
beneficiary: KEY_FUNDER_ADDRESS,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
utils
.exclude(chain, chainNames)
.map((remote) => [
remote,
multisigIsmVerificationCost(
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
),
]),
),
};
},
);

@ -1,7 +1,8 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { getMultiProviderForRole } from '../../../scripts/utils';
import { KEY_ROLE_ENUM } from '../../../src/agents/roles';
import { CoreEnvironmentConfig } from '../../../src/config';
import { ConnectionType } from '../../../src/config/agent';
import { Contexts } from '../../contexts';
import { agents } from './agent';
@ -10,7 +11,9 @@ import { core } from './core';
import { keyFunderConfig } from './funding';
import { storageGasOracleConfig } from './gas-oracle';
import { helloWorld } from './helloworld';
import { igp } from './igp';
import { infrastructure } from './infrastructure';
import { owners } from './owners';
export const environment: CoreEnvironmentConfig = {
environment: environmentName,
@ -18,7 +21,7 @@ export const environment: CoreEnvironmentConfig = {
getMultiProvider: (
context: Contexts = Contexts.Hyperlane,
role: KEY_ROLE_ENUM = KEY_ROLE_ENUM.Deployer,
connectionType?: ConnectionType,
connectionType?: AgentConnectionType,
) =>
getMultiProviderForRole(
mainnetConfigs,
@ -30,6 +33,8 @@ export const environment: CoreEnvironmentConfig = {
),
agents,
core,
igp,
owners,
infra: infrastructure,
helloWorld,
keyFunderConfig,

@ -0,0 +1,13 @@
import { ChainMap } from '@hyperlane-xyz/sdk';
export const owners: ChainMap<string> = {
celo: '0x1DE69322B55AC7E0999F8e7738a1428C8b130E4d',
ethereum: '0x12C5AB61Fe17dF9c65739DBa73dF294708f78d23',
avalanche: '0xDF9B28B76877f1b1B4B8a11526Eb7D8D7C49f4f3',
polygon: '0x0D195469f76146F6ae3De8fc887e0f0DFBA691e7',
bsc: '0xA0d3dcB9d61Fba32cc02Ad63983e101b29E2f28a',
arbitrum: '0xbA47E1b575980B7D1b1508cc48bE1Df4EE508111',
optimism: '0xb523CFAf45AACF472859f8B793CB0BFDB16bD257',
moonbeam: '0xF0cb1f968Df01fc789762fddBfA704AE0F952197',
gnosis: '0x36b0AA0e7d04e7b825D7E409FEa3c9A3d57E4C22',
};

@ -1,9 +1,8 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { ALL_KEY_ROLES } from '../../../src/agents/roles';
import { AgentConfig } from '../../../src/config';
import {
ConnectionType,
GasPaymentEnforcementPolicyType,
} from '../../../src/config/agent';
import { GasPaymentEnforcementPolicyType } from '../../../src/config/agent';
import { Contexts } from '../../contexts';
import { chainNames } from './chains';
@ -19,7 +18,7 @@ export const hyperlane: AgentConfig = {
},
environmentChainNames: chainNames,
contextChainNames: chainNames,
connectionType: ConnectionType.Http,
connectionType: AgentConnectionType.Http,
validators,
relayer: {
default: {

@ -1,55 +1,13 @@
import {
ChainMap,
CoreConfig,
GasOracleContractType,
} from '@hyperlane-xyz/sdk';
import { ChainMap, CoreConfig, objMap } from '@hyperlane-xyz/sdk';
import { TestChains, chainNames } from './chains';
import { multisigIsm } from './multisigIsm';
import { owners } from './owners';
function getGasOracles(local: TestChains) {
return Object.fromEntries(
chainNames
.filter((name) => name !== local)
.map((name) => [name, GasOracleContractType.StorageGasOracle]),
);
}
const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
export const core: ChainMap<CoreConfig> = {
// Owner is hardhat account 0
// Validators are hardhat accounts 1-3
test1: {
owner: OWNER_ADDRESS,
multisigIsm: {
validators: ['0x70997970c51812dc3a010c7d01b50e0d17dc79c8'],
threshold: 1,
},
igp: {
beneficiary: OWNER_ADDRESS,
gasOracles: getGasOracles('test1'),
},
},
test2: {
owner: OWNER_ADDRESS,
multisigIsm: {
validators: ['0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc'],
threshold: 1,
},
igp: {
beneficiary: OWNER_ADDRESS,
gasOracles: getGasOracles('test2'),
},
},
test3: {
owner: OWNER_ADDRESS,
multisigIsm: {
validators: ['0x90f79bf6eb2c4f870365e785982e1f101e93b906'],
threshold: 1,
},
igp: {
beneficiary: OWNER_ADDRESS,
gasOracles: getGasOracles('test3'),
},
},
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
return {
owner,
multisigIsm: Object.fromEntries(
Object.entries(multisigIsm).filter(([chain]) => chain !== local),
),
};
});

@ -0,0 +1,42 @@
import {
ChainMap,
GasOracleContractType,
OverheadIgpConfig,
multisigIsmVerificationCost,
objMap,
} from '@hyperlane-xyz/sdk';
import { utils } from '@hyperlane-xyz/utils';
import { TestChains, chainNames } from './chains';
import { multisigIsm } from './multisigIsm';
import { owners } from './owners';
function getGasOracles(local: TestChains) {
return Object.fromEntries(
utils
.exclude(local, chainNames)
.map((name) => [name, GasOracleContractType.StorageGasOracle]),
);
}
export const igp: ChainMap<OverheadIgpConfig> = objMap(
owners,
(chain, owner) => {
return {
owner,
beneficiary: owner,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
utils
.exclude(chain, chainNames)
.map((remote) => [
remote,
multisigIsmVerificationCost(
multisigIsm[remote].threshold,
multisigIsm[remote].validators.length,
),
]),
),
};
},
);

@ -8,13 +8,17 @@ import { agents } from './agent';
import { testConfigs } from './chains';
import { core } from './core';
import { storageGasOracleConfig } from './gas-oracle';
import { igp } from './igp';
import { infra } from './infra';
import { owners } from './owners';
export const environment: CoreEnvironmentConfig = {
environment: 'test',
chainMetadataConfigs: testConfigs,
agents,
core,
igp,
owners,
infra,
// NOTE: Does not work from hardhat.config.ts
getMultiProvider: async () => {

@ -0,0 +1,17 @@
import { ChainMap, MultisigIsmConfig } from '@hyperlane-xyz/sdk';
export const multisigIsm: ChainMap<MultisigIsmConfig> = {
// Validators are hardhat accounts 1-3
test1: {
validators: ['0x70997970c51812dc3a010c7d01b50e0d17dc79c8'],
threshold: 1,
},
test2: {
validators: ['0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc'],
threshold: 1,
},
test3: {
validators: ['0x90f79bf6eb2c4f870365e785982e1f101e93b906'],
threshold: 1,
},
};

@ -0,0 +1,10 @@
import { ChainMap } from '@hyperlane-xyz/sdk';
import { types } from '@hyperlane-xyz/utils';
import { chainNames } from './chains';
// Owner is hardhat account 0
const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
export const owners: ChainMap<types.Address> = Object.fromEntries(
chainNames.map((chain) => [chain, OWNER_ADDRESS]),
);

@ -1,11 +1,8 @@
import { chainMetadata } from '@hyperlane-xyz/sdk';
import { AgentConnectionType, chainMetadata } from '@hyperlane-xyz/sdk';
import { ALL_KEY_ROLES, KEY_ROLE_ENUM } from '../../../src/agents/roles';
import { AgentConfig } from '../../../src/config';
import {
ConnectionType,
GasPaymentEnforcementPolicyType,
} from '../../../src/config/agent';
import { GasPaymentEnforcementPolicyType } from '../../../src/config/agent';
import { Contexts } from '../../contexts';
import { helloworldMatchingList, routerMatchingList } from '../../utils';
@ -36,7 +33,7 @@ export const hyperlane: AgentConfig = {
},
environmentChainNames: chainNames,
contextChainNames: chainNames,
connectionType: ConnectionType.HttpFallback,
connectionType: AgentConnectionType.HttpFallback,
validators,
relayer: {
default: {
@ -81,7 +78,7 @@ export const releaseCandidate: AgentConfig = {
},
environmentChainNames: chainNames,
contextChainNames: chainNames,
connectionType: ConnectionType.HttpFallback,
connectionType: AgentConnectionType.HttpFallback,
relayer: {
default: {
whitelist: releaseCandidateHelloworldMatchingList,

@ -1,155 +1,20 @@
import {
ChainMap,
CoreConfig,
GasOracleContractType,
defaultMultisigIsmConfigs,
objMap,
} from '@hyperlane-xyz/sdk';
import { TestnetChains, chainNames } from './chains';
import { chainNames } from './chains';
import { owners } from './owners';
function getGasOracles(local: TestnetChains) {
return Object.fromEntries(
chainNames
.filter((name) => name !== local)
.map((name) => [name, GasOracleContractType.StorageGasOracle]),
);
}
const DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C';
export const core: ChainMap<CoreConfig> = {
alfajores: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0xe6072396568e73ce6803b12b7e04164e839f1e54',
'0x9f177f51289b22515f41f95872e1511391b8e105',
'0x15f77400845eb1c971ad08de050861d5508cad6c',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('alfajores'),
},
},
fuji: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0x9fa19ead5ec76e437948b35e227511b106293c40',
'0x227e7d6507762ece0c94678f8c103eff9d682476',
'0x2379e43740e4aa4fde48cf4f00a3106df1d8420d',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('fuji'),
},
},
mumbai: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0x0a664ea799447da6b15645cf8b9e82072a68343f',
'0x6ae6f12929a960aba24ba74ea310e3d37d0ac045',
'0x51f70c047cd73bc7873273707501568857a619c4',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('mumbai'),
},
},
bsctestnet: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0x23338c8714976dd4a57eaeff17cbd26d7e275c08',
'0x85a618d7450ebc37e0d682371f08dac94eec7a76',
'0x95b76562e4ba1791a27ba4236801271c9115b141',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('bsctestnet'),
},
},
goerli: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0xf43fbd072fd38e1121d4b3b0b8a35116bbb01ea9',
'0xa33020552a21f35e75bd385c6ab95c3dfa82d930',
'0x0bba4043ff242f8bf3f39bafa8930a84d644d947',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('goerli'),
},
},
sepolia: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0xbc748ee311f5f2d1975d61cdf531755ce8ce3066',
'0xc4233b2bfe5aec08964a94b403052abb3eafcf07',
'0x6b36286c19f5c10bdc139ea9ee7f82287303f61d',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('sepolia'),
},
},
moonbasealpha: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0x890c2aeac157c3f067f3e42b8afc797939c59a32',
'0x1b06d6fe69b972ed7420c83599d5a5c0fc185904',
'0xe70b85206a968a99a597581f0fa09c99e7681093',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('moonbasealpha'),
},
},
optimismgoerli: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0xbb8d77eefbecc55db6e5a19b0fc3dc290776f189',
'0x69792508b4ddaa3ca52241ccfcd1e0b119a1ee65',
'0x11ddb46c6b653e0cdd7ad5bee32ae316e18f8453',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('optimismgoerli'),
},
},
arbitrumgoerli: {
owner: DEPLOYER_ADDRESS,
multisigIsm: {
threshold: 2,
validators: [
'0xce798fa21e323f6b24d9838a10ffecdefdfc4f30',
'0xa792d39dca4426927e0f00c1618d61c9cb41779d',
'0xdf181fcc11dfac5d01467e4547101a856dd5aa04',
],
},
igp: {
beneficiary: DEPLOYER_ADDRESS,
gasOracles: getGasOracles('arbitrumgoerli'),
},
},
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
return {
owner,
multisigIsm: Object.fromEntries(
Object.entries(defaultMultisigIsmConfigs).filter(
([chain]) => chain !== local && chainNames.includes(chain),
),
),
};
});

@ -1,5 +1,6 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { KEY_ROLE_ENUM } from '../../../src/agents/roles';
import { ConnectionType } from '../../../src/config/agent';
import { KeyFunderConfig } from '../../../src/config/funding';
import { Contexts } from '../../contexts';
@ -22,5 +23,5 @@ export const keyFunderConfig: KeyFunderConfig = {
[Contexts.Hyperlane]: [KEY_ROLE_ENUM.Relayer, KEY_ROLE_ENUM.Kathy],
[Contexts.ReleaseCandidate]: [KEY_ROLE_ENUM.Relayer, KEY_ROLE_ENUM.Kathy],
},
connectionType: ConnectionType.Http,
connectionType: AgentConnectionType.Http,
};

@ -1,5 +1,6 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { HelloWorldConfig } from '../../../src/config';
import { ConnectionType } from '../../../src/config/agent';
import { HelloWorldKathyRunMode } from '../../../src/config/helloworld';
import { Contexts } from '../../contexts';
@ -23,7 +24,7 @@ export const hyperlane: HelloWorldConfig = {
},
messageSendTimeout: 1000 * 60 * 8, // 8 min
messageReceiptTimeout: 1000 * 60 * 20, // 20 min
connectionType: ConnectionType.HttpFallback,
connectionType: AgentConnectionType.HttpFallback,
},
};
@ -42,7 +43,7 @@ export const releaseCandidate: HelloWorldConfig = {
},
messageSendTimeout: 1000 * 60 * 8, // 8 min
messageReceiptTimeout: 1000 * 60 * 20, // 20 min
connectionType: ConnectionType.Http,
connectionType: AgentConnectionType.Http,
},
};

@ -0,0 +1,42 @@
import {
ChainMap,
GasOracleContractType,
OverheadIgpConfig,
defaultMultisigIsmConfigs,
multisigIsmVerificationCost,
objMap,
} from '@hyperlane-xyz/sdk';
import { utils } from '@hyperlane-xyz/utils';
import { TestnetChains, chainNames } from './chains';
import { owners } from './owners';
function getGasOracles(local: TestnetChains) {
return Object.fromEntries(
utils
.exclude(local, chainNames)
.map((name) => [name, GasOracleContractType.StorageGasOracle]),
);
}
export const igp: ChainMap<OverheadIgpConfig> = objMap(
owners,
(chain, owner) => {
return {
owner,
beneficiary: owner,
gasOracleType: getGasOracles(chain),
overhead: Object.fromEntries(
utils
.exclude(chain, chainNames)
.map((remote) => [
remote,
multisigIsmVerificationCost(
defaultMultisigIsmConfigs[remote].threshold,
defaultMultisigIsmConfigs[remote].validators.length,
),
]),
),
};
},
);

@ -1,7 +1,8 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { getMultiProviderForRole } from '../../../scripts/utils';
import { KEY_ROLE_ENUM } from '../../../src/agents/roles';
import { CoreEnvironmentConfig } from '../../../src/config';
import { ConnectionType } from '../../../src/config/agent';
import { Contexts } from '../../contexts';
import { agents } from './agent';
@ -10,8 +11,10 @@ import { core } from './core';
import { keyFunderConfig } from './funding';
import { storageGasOracleConfig } from './gas-oracle';
import { helloWorld } from './helloworld';
import { igp } from './igp';
import { infrastructure } from './infrastructure';
import { liquidityLayerRelayerConfig } from './middleware';
import { owners } from './owners';
export const environment: CoreEnvironmentConfig = {
environment: environmentName,
@ -19,7 +22,7 @@ export const environment: CoreEnvironmentConfig = {
getMultiProvider: (
context: Contexts = Contexts.Hyperlane,
role: KEY_ROLE_ENUM = KEY_ROLE_ENUM.Deployer,
connectionType?: ConnectionType,
connectionType?: AgentConnectionType,
) =>
getMultiProviderForRole(
testnetConfigs,
@ -31,8 +34,10 @@ export const environment: CoreEnvironmentConfig = {
),
agents,
core,
igp,
infra: infrastructure,
helloWorld,
owners,
keyFunderConfig,
liquidityLayerRelayerConfig,
storageGasOracleConfig,

@ -1,4 +1,5 @@
import { ConnectionType } from '../../../src/config/agent';
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware';
import { environment } from './chains';
@ -11,5 +12,5 @@ export const liquidityLayerRelayerConfig: LiquidityLayerRelayerConfig = {
namespace: environment,
prometheusPushGateway:
'http://prometheus-pushgateway.monitoring.svc.cluster.local:9091',
connectionType: ConnectionType.Http,
connectionType: AgentConnectionType.Http,
};

@ -0,0 +1,10 @@
import { ChainMap } from '@hyperlane-xyz/sdk';
import { types } from '@hyperlane-xyz/utils';
import { chainNames } from './chains';
const DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C';
export const owners: ChainMap<types.Address> = Object.fromEntries(
chainNames.map((chain) => [chain, DEPLOYER_ADDRESS]),
);

@ -1,7 +1,8 @@
ENVIRONMENT=$1
MODULE=$2
if [ -z "$ENVIRONMENT" ]; then
echo "Usage: fork.sh <environment>"
echo "Usage: fork.sh <environment> <module>"
exit 1
fi
@ -24,16 +25,16 @@ while ! cast bn; do
done
echo "=== Run checker against forked $ENVIRONMENT ==="
DEBUG=hyperlane:* yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT --fork $FORK_CHAIN
DEBUG=hyperlane:* yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $FORK_CHAIN -m $MODULE
echo "=== Run core deployer against forked $ENVIRONMENT ==="
DEBUG=hyperlane:* yarn ts-node ./scripts/core.ts -e $ENVIRONMENT --fork $FORK_CHAIN
DEBUG=hyperlane:* yarn ts-node ./scripts/core.ts -e $ENVIRONMENT -f $FORK_CHAIN -m $MODULE
echo "=== Run govern against forked $ENVIRONMENT ==="
DEBUG=hyperlane:* yarn ts-node ./scripts/govern.ts -e $ENVIRONMENT --fork $FORK_CHAIN
DEBUG=hyperlane:* yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $FORK_CHAIN --govern -m $MODULE
echo "=== Run checker against forked $ENVIRONMENT ==="
DEBUG=hyperlane:* yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT --fork $FORK_CHAIN
DEBUG=hyperlane:* yarn ts-node ./scripts/check-deploy.ts -e $ENVIRONMENT -f $FORK_CHAIN -m $MODULE
SUCCESS=$?

@ -4,7 +4,12 @@ import { task } from 'hardhat/config';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { TestSendReceiver__factory } from '@hyperlane-xyz/core';
import { ChainName, HyperlaneCore, MultiProvider } from '@hyperlane-xyz/sdk';
import {
ChainName,
HyperlaneCore,
HyperlaneIgp,
MultiProvider,
} from '@hyperlane-xyz/sdk';
import { sleep } from './src/utils/utils';
@ -44,6 +49,7 @@ task('kathy', 'Dispatches random hyperlane messages')
const [signer] = await hre.ethers.getSigners();
const multiProvider = MultiProvider.createTestMultiProvider({ signer });
const core = HyperlaneCore.fromEnvironment(environment, multiProvider);
const igps = HyperlaneIgp.fromEnvironment(environment, multiProvider);
const randomElement = <T>(list: T[]) =>
list[Math.floor(Math.random() * list.length)];
@ -60,15 +66,14 @@ task('kathy', 'Dispatches random hyperlane messages')
const local = core.chains()[0];
const remote: ChainName = randomElement(core.remoteChains(local));
const remoteId = multiProvider.getDomainId(remote);
const coreContracts = core.getContracts(local);
const mailbox = coreContracts.mailbox.contract;
const paymaster = coreContracts.interchainGasPaymaster;
const mailbox = core.getContracts(local).mailbox.contract;
const igp = igps.getContracts(local).interchainGasPaymaster.contract;
// Send a batch of messages to the destination chain to test
// the relayer submitting only greedily
for (let i = 0; i < 10; i++) {
await recipient.dispatchToSelf(
mailbox.address,
paymaster.address,
igp.address,
remoteId,
'0x1234',
{

@ -14,7 +14,7 @@ import { assertContext } from '../src/utils/utils';
import {
assertEnvironment,
getContextAgentConfig,
getCoreEnvironmentConfig,
getEnvironmentConfig,
} from './utils';
function getArgs() {
@ -45,7 +45,7 @@ function getArgs() {
async function main() {
const { environment, context, chain, location } = await getArgs();
const config = getCoreEnvironmentConfig(environment);
const config = await getEnvironmentConfig();
const multiProvider = await config.getMultiProvider();
// environments union doesn't work well with typescript
const core = HyperlaneCore.fromEnvironment(

@ -1,44 +1,65 @@
import { HyperlaneCore, HyperlaneCoreChecker } from '@hyperlane-xyz/sdk';
import {
HyperlaneCore,
HyperlaneCoreChecker,
HyperlaneIgp,
HyperlaneIgpChecker,
} from '@hyperlane-xyz/sdk';
import { deployEnvToSdkEnv } from '../src/config/environment';
import { useLocalProvider } from '../src/utils/fork';
import { HyperlaneCoreGovernor } from '../src/core/govern';
import { HyperlaneIgpGovernor } from '../src/gas/govern';
import { HyperlaneAppGovernor } from '../src/govern/HyperlaneAppGovernor';
import { impersonateAccount, useLocalProvider } from '../src/utils/fork';
import {
assertEnvironment,
getArgsWithFork,
getCoreEnvironmentConfig,
} from './utils';
import { getArgsWithModuleAndFork, getEnvironmentConfig } from './utils';
async function check() {
const argv = await getArgsWithFork().argv;
const environment = assertEnvironment(argv.environment);
const config = getCoreEnvironmentConfig(environment);
const { fork, govern, module, environment } = await getArgsWithModuleAndFork()
.boolean('govern')
.alias('g', 'govern').argv;
const config = await getEnvironmentConfig();
const multiProvider = await config.getMultiProvider();
// must rotate to forked provider before building core contracts
if (argv.fork) {
await useLocalProvider(multiProvider, argv.fork);
if (fork) {
await useLocalProvider(multiProvider, fork);
if (govern) {
const owner = config.core[fork].owner;
const signer = await impersonateAccount(owner);
multiProvider.setSigner(fork, signer);
}
}
// environments union doesn't work well with typescript
const core = HyperlaneCore.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
const coreChecker = new HyperlaneCoreChecker(
multiProvider,
core,
config.core,
);
let governor: HyperlaneAppGovernor<any, any>;
const env = deployEnvToSdkEnv[environment];
if (module === 'core') {
const core = HyperlaneCore.fromEnvironment(env, multiProvider);
const checker = new HyperlaneCoreChecker(multiProvider, core, config.core);
governor = new HyperlaneCoreGovernor(checker, config.owners);
} else if (module === 'igp') {
const igp = HyperlaneIgp.fromEnvironment(env, multiProvider);
const checker = new HyperlaneIgpChecker(multiProvider, igp, config.igp);
governor = new HyperlaneIgpGovernor(checker, config.owners);
} else {
throw new Error('Unknown module type');
}
if (argv.fork) {
await coreChecker.checkChain(argv.fork);
if (fork) {
await governor.checker.checkChain(fork);
if (govern) {
await governor.govern(false, fork);
}
} else {
await coreChecker.check();
await governor.checker.check();
if (govern) {
await governor.govern();
}
}
if (coreChecker.violations.length > 0) {
console.table(coreChecker.violations, [
if (!govern) {
const violations = governor.checker.violations;
if (violations.length > 0) {
console.table(violations, [
'chain',
'remote',
'name',
@ -48,10 +69,11 @@ async function check() {
'expected',
]);
throw new Error(
`Checking core deploy yielded ${coreChecker.violations.length} violations`,
`Checking ${module} deploy yielded ${violations.length} violations`,
);
} else {
console.info('CoreChecker found no violations');
console.info(`${module} Checker found no violations`);
}
}
}

@ -1,96 +0,0 @@
import {
buildContracts,
coreFactories,
serializeContracts,
} from '@hyperlane-xyz/sdk';
import { deployEnvToSdkEnv } from '../src/config/environment';
import { HyperlaneCoreInfraDeployer } from '../src/core/deploy';
import { impersonateAccount, useLocalProvider } from '../src/utils/fork';
import { readJSON, writeJSON } from '../src/utils/utils';
import {
assertEnvironment,
getArgsWithFork,
getCoreContractsSdkFilepath,
getCoreEnvironmentConfig,
getCoreRustDirectory,
getCoreVerificationDirectory,
} from './utils';
async function main() {
const argv = await getArgsWithFork().argv;
const environment = assertEnvironment(argv.environment);
const config = getCoreEnvironmentConfig(environment);
const multiProvider = await config.getMultiProvider();
if (argv.fork) {
await useLocalProvider(multiProvider, argv.fork);
// TODO: make this more generic
const deployerAddress =
environment === 'testnet3'
? '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'
: '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba';
const signer = await impersonateAccount(deployerAddress);
multiProvider.setSigner(argv.fork, signer);
}
const deployer = new HyperlaneCoreInfraDeployer(
multiProvider,
config.core,
environment,
);
if (environment !== 'test') {
try {
const addresses = readJSON(
getCoreContractsSdkFilepath(),
`${deployEnvToSdkEnv[environment]}.json`,
);
deployer.cacheContracts(buildContracts(addresses, coreFactories) as any);
} catch (e) {
console.info('Could not load partial core addresses, file may not exist');
}
}
if (argv.fork) {
await deployer.deployContracts(argv.fork, config.core[argv.fork]);
return;
}
try {
await deployer.deploy();
} catch (e) {
console.error(`Encountered error during deploy`);
console.error(e);
}
// Persist artifacts, irrespective of deploy success
writeJSON(
getCoreContractsSdkFilepath(),
`${deployEnvToSdkEnv[environment]}.json`,
serializeContracts(deployer.deployedContracts),
);
const verificationDir = getCoreVerificationDirectory(environment);
const verificationFile = 'verification.json';
let existingVerificationInputs = [];
try {
existingVerificationInputs = readJSON(verificationDir, verificationFile);
} catch (err) {
/* ignore error */
}
writeJSON(
getCoreVerificationDirectory(environment),
'verification.json',
deployer.mergeWithExistingVerificationInputs(existingVerificationInputs),
);
deployer.writeRustConfigs(getCoreRustDirectory());
}
main()
.then()
.catch(() => process.exit(1));

@ -0,0 +1,138 @@
import {
HyperlaneDeployer,
HyperlaneFactories,
buildAgentConfig,
buildContracts,
coreFactories,
serializeContracts,
} from '@hyperlane-xyz/sdk';
import { igpFactories } from '@hyperlane-xyz/sdk/dist/gas/contracts';
import { deployEnvToSdkEnv } from '../src/config/environment';
import { HyperlaneCoreInfraDeployer } from '../src/core/deploy';
import { HyperlaneIgpInfraDeployer } from '../src/gas/deploy';
import { impersonateAccount, useLocalProvider } from '../src/utils/fork';
import { readJSON, writeJSON, writeMergedJSON } from '../src/utils/utils';
import {
getAgentConfigDirectory,
getArgsWithModuleAndFork,
getContractAddressesSdkFilepath,
getEnvironmentConfig,
getVerificationDirectory,
} from './utils';
async function main() {
const { module, fork, environment } = await getArgsWithModuleAndFork().argv;
const config = await getEnvironmentConfig();
const multiProvider = await config.getMultiProvider();
if (fork) {
await useLocalProvider(multiProvider, fork);
// TODO: make this more generic
const deployerAddress =
environment === 'testnet3'
? '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'
: '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba';
const signer = await impersonateAccount(deployerAddress);
multiProvider.setSigner(fork, signer);
}
// Write agent config indexing from the latest block numbers.
// For non-net-new deployments, these changes will need to be
// reverted manually.
const chains = multiProvider.getKnownChainNames();
const startBlocks = Object.fromEntries(
await Promise.all(
chains.map(async (c) => [
c,
await multiProvider.getProvider(c).getBlockNumber,
]),
),
);
let factories: HyperlaneFactories;
let deployer: HyperlaneDeployer<any, any, any>;
if (module === 'core') {
factories = coreFactories;
deployer = new HyperlaneCoreInfraDeployer(
multiProvider,
config.core,
environment,
);
} else if (module === 'igp') {
factories = igpFactories;
deployer = new HyperlaneIgpInfraDeployer(
multiProvider,
config.igp,
environment,
);
} else {
throw new Error('Unknown module type');
}
if (environment !== 'test') {
try {
const addresses = readJSON(
getContractAddressesSdkFilepath(),
`${deployEnvToSdkEnv[environment]}.json`,
);
deployer.cacheContracts(buildContracts(addresses, factories) as any);
} catch (e) {
console.info('Could not load partial addresses, file may not exist');
}
}
if (fork) {
await deployer.deployContracts(fork, config.core[fork]);
return;
}
try {
await deployer.deploy();
} catch (e) {
console.error('Encountered error during deploy', e);
}
// Persist address artifacts, irrespective of deploy success
writeMergedJSON(
getContractAddressesSdkFilepath(),
`${deployEnvToSdkEnv[environment]}.json`,
serializeContracts(deployer.deployedContracts),
);
const verificationDir = getVerificationDirectory(environment, module);
const verificationFile = 'verification.json';
let existingVerificationInputs = [];
try {
existingVerificationInputs = readJSON(verificationDir, verificationFile);
} catch (err) {
/* ignore error */
}
writeJSON(
verificationDir,
verificationFile,
deployer.mergeWithExistingVerificationInputs(existingVerificationInputs),
);
const sdkEnv = deployEnvToSdkEnv[environment];
const addresses = readJSON(
getContractAddressesSdkFilepath(),
`${sdkEnv}.json`,
);
const agentConfig = await buildAgentConfig(
multiProvider.getKnownChainNames(),
multiProvider,
addresses,
startBlocks,
);
writeJSON(getAgentConfigDirectory(), `${sdkEnv}_config.json`, agentConfig);
}
main()
.then()
.catch(() => process.exit(1));

@ -4,11 +4,12 @@ import { Gauge, Registry } from 'prom-client';
import { format } from 'util';
import {
AgentConnectionType,
AllChains,
ChainName,
Chains,
CoreChainName,
HyperlaneCore,
HyperlaneIgp,
MultiProvider,
} from '@hyperlane-xyz/sdk';
import { ChainMap } from '@hyperlane-xyz/sdk/dist/types';
@ -23,7 +24,6 @@ import {
} from '../../src/agents/keys';
import { KEY_ROLE_ENUM } from '../../src/agents/roles';
import { DeployEnvironment } from '../../src/config';
import { ConnectionType } from '../../src/config/agent';
import { deployEnvToSdkEnv } from '../../src/config/environment';
import { ContextAndRoles, ContextAndRolesMap } from '../../src/config/funding';
import { submitMetrics } from '../../src/utils/metrics';
@ -197,10 +197,10 @@ async function main() {
.string('connection-type')
.describe('connection-type', 'The provider connection type to use for RPCs')
.default('connection-type', ConnectionType.Http)
.default('connection-type', AgentConnectionType.Http)
.choices('connection-type', [
ConnectionType.Http,
ConnectionType.HttpQuorum,
AgentConnectionType.Http,
AgentConnectionType.HttpQuorum,
])
.demandOption('connection-type')
@ -260,7 +260,7 @@ async function main() {
// Funds keys for a single context
class ContextFunder {
public readonly chains: ChainName[];
core: HyperlaneCore;
igp: HyperlaneIgp;
constructor(
public readonly environment: DeployEnvironment,
@ -275,7 +275,7 @@ class ContextFunder {
);
this.chains = Array.from(uniqueChains) as ChainName[];
this.core = HyperlaneCore.fromEnvironment(
this.igp = HyperlaneIgp.fromEnvironment(
deployEnvToSdkEnv[this.environment],
multiProvider,
);
@ -497,7 +497,7 @@ class ContextFunder {
const igpClaimThreshold = ethers.utils.parseEther(igpClaimThresholdEther);
const provider = this.multiProvider.getProvider(chain);
const igp = this.core.getContracts(chain).interchainGasPaymaster;
const igp = this.igp.getContracts(chain).interchainGasPaymaster;
const igpBalance = await provider.getBalance(igp.address);
log('Checking IGP balance', {

@ -1,6 +1,6 @@
import { BigNumber } from 'ethers';
import { HyperlaneCore, objMap, promiseObjAll } from '@hyperlane-xyz/sdk';
import { HyperlaneIgp, objMap, promiseObjAll } from '@hyperlane-xyz/sdk';
import { deployEnvToSdkEnv } from '../../src/config/environment';
import { getEnvironment, getEnvironmentConfig } from '../utils';
@ -10,14 +10,14 @@ const RECLAIM_BALANCE_THRESHOLD = BigNumber.from(10).pow(17);
async function main() {
const environment = await getEnvironment();
const coreConfig = await getEnvironmentConfig();
const multiProvider = await coreConfig.getMultiProvider();
const core: HyperlaneCore = HyperlaneCore.fromEnvironment(
const environmentConfig = await getEnvironmentConfig();
const multiProvider = await environmentConfig.getMultiProvider();
const igp = HyperlaneIgp.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
const paymasters = core.map(
const paymasters = igp.map(
(_, contracts) => contracts.interchainGasPaymaster,
);

@ -1,4 +1,4 @@
import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk';
import { ChainName, HyperlaneIgp } from '@hyperlane-xyz/sdk';
import { RemoteGasData, StorageGasOracleConfig } from '../../src/config';
import { deployEnvToSdkEnv } from '../../src/config/environment';
@ -32,14 +32,14 @@ async function main() {
throw Error(`No storage gas oracle config for environment ${environment}`);
}
const core = HyperlaneCore.fromEnvironment(
const igp = HyperlaneIgp.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
for (const chain of core.chains()) {
for (const chain of igp.chains()) {
await setStorageGasOracleValues(
core,
igp,
storageGasOracleConfig[chain],
chain,
args.dryRun,
@ -49,19 +49,19 @@ async function main() {
}
async function setStorageGasOracleValues(
core: HyperlaneCore,
igp: HyperlaneIgp,
localStorageGasOracleConfig: StorageGasOracleConfig,
local: ChainName,
dryRun: boolean,
) {
console.log(`Setting remote gas data on local chain ${local}...`);
const storageGasOracle = core.getContracts(local).storageGasOracle;
const storageGasOracle = igp.getContracts(local).storageGasOracle;
const configsToSet: RemoteGasDataConfig[] = [];
for (const remote in localStorageGasOracleConfig) {
const desiredGasData = localStorageGasOracleConfig[remote]!;
const remoteId = core.multiProvider.getDomainId(remote);
const remoteId = igp.multiProvider.getDomainId(remote);
const existingGasData: RemoteGasData = await storageGasOracle.remoteGasData(
remoteId,
@ -92,14 +92,14 @@ async function setStorageGasOracleValues(
console.log(`Updating ${configsToSet.length} configs on local ${local}:`);
console.log(
configsToSet
.map((config) => prettyRemoteGasDataConfig(core.multiProvider, config))
.map((config) => prettyRemoteGasDataConfig(igp.multiProvider, config))
.join('\n\t--\n'),
);
if (dryRun) {
console.log('Running in dry run mode, not sending tx');
} else {
await core.multiProvider.handleTx(
await igp.multiProvider.handleTx(
local,
storageGasOracle.setRemoteGasDataConfigs(configsToSet),
);

@ -1,50 +0,0 @@
import { HyperlaneCore, HyperlaneCoreChecker } from '@hyperlane-xyz/sdk';
import { deployEnvToSdkEnv } from '../src/config/environment';
import { HyperlaneCoreGovernor } from '../src/core/govern';
import { impersonateAccount, useLocalProvider } from '../src/utils/fork';
import {
assertEnvironment,
getArgsWithFork,
getCoreEnvironmentConfig,
} from './utils';
async function check() {
const argv = await getArgsWithFork().argv;
const environment = assertEnvironment(argv.environment);
const config = getCoreEnvironmentConfig(environment);
const multiProvider = await config.getMultiProvider();
// must rotate to forked provider before building core contracts
if (argv.fork) {
await useLocalProvider(multiProvider, argv.fork);
const owner = config.core[argv.fork].owner;
const signer = await impersonateAccount(owner);
multiProvider.setSigner(argv.fork, signer);
}
const core = HyperlaneCore.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
const coreChecker = new HyperlaneCoreChecker(
multiProvider,
core,
config.core,
);
const governor = new HyperlaneCoreGovernor(coreChecker);
if (argv.fork) {
await coreChecker.checkChain(argv.fork);
await governor.governChain(argv.fork, false);
} else {
await coreChecker.check();
await governor.govern();
}
}
check()
.then()
.catch(() => process.exit(1));

@ -2,9 +2,14 @@ import { HelloWorldChecker } from '@hyperlane-xyz/helloworld';
import { Contexts } from '../../config/contexts';
import { KEY_ROLE_ENUM } from '../../src/agents/roles';
import { getContext, getCoreEnvironmentConfig, getEnvironment } from '../utils';
import {
getContext,
getCoreEnvironmentConfig,
getEnvironment,
getRouterConfig,
} from '../utils';
import { getApp, getConfiguration } from './utils';
import { getApp } from './utils';
async function main() {
const environment = await getEnvironment();
@ -17,7 +22,7 @@ async function main() {
KEY_ROLE_ENUM.Deployer,
Contexts.Hyperlane, // Owner should always be from the hyperlane context
);
const configMap = await getConfiguration(environment, multiProvider);
const configMap = await getRouterConfig(environment, multiProvider, true);
const checker = new HelloWorldChecker(multiProvider, app, configMap);
await checker.check();
checker.expectEmpty();

@ -21,10 +21,9 @@ import {
getCoreEnvironmentConfig,
getEnvironment,
getEnvironmentDirectory,
getRouterConfig,
} from '../utils';
import { getConfiguration } from './utils';
async function main() {
const environment = await getEnvironment();
const context = await getContext();
@ -34,7 +33,7 @@ async function main() {
Contexts.Hyperlane,
KEY_ROLE_ENUM.Deployer,
);
const configMap = await getConfiguration(environment, multiProvider);
const configMap = await getRouterConfig(environment, multiProvider, true);
const core = HyperlaneCore.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,

@ -4,15 +4,15 @@ import { format } from 'util';
import { HelloWorldApp } from '@hyperlane-xyz/helloworld';
import {
AgentConnectionType,
ChainName,
DispatchedMessage,
HyperlaneCore,
InterchainGasCalculator,
HyperlaneIgp,
} from '@hyperlane-xyz/sdk';
import { debug, error, log, utils, warn } from '@hyperlane-xyz/utils';
import { KEY_ROLE_ENUM } from '../../src/agents/roles';
import { ConnectionType } from '../../src/config/agent';
import { deployEnvToSdkEnv } from '../../src/config/environment';
import { startMetricsServer } from '../../src/utils/metrics';
import { assertChain, diagonalize, sleep } from '../../src/utils/utils';
@ -104,11 +104,11 @@ function getKathyArgs() {
.string('connection-type')
.describe('connection-type', 'The provider connection type to use for RPCs')
.default('connection-type', ConnectionType.Http)
.default('connection-type', AgentConnectionType.Http)
.choices('connection-type', [
ConnectionType.Http,
ConnectionType.HttpQuorum,
ConnectionType.HttpFallback,
AgentConnectionType.Http,
AgentConnectionType.HttpQuorum,
AgentConnectionType.HttpFallback,
])
.demandOption('connection-type');
@ -150,8 +150,8 @@ async function main(): Promise<boolean> {
undefined,
connectionType,
);
const gasCalculator = InterchainGasCalculator.fromEnvironment(
deployEnvToSdkEnv[environment],
const igp = HyperlaneIgp.fromEnvironment(
deployEnvToSdkEnv[coreConfig.environment],
app.multiProvider,
);
const appChains = app.chains();
@ -323,9 +323,9 @@ async function main(): Promise<boolean> {
try {
await sendMessage(
app,
igp,
origin,
destination,
gasCalculator,
messageSendTimeout,
messageReceiptTimeout,
);
@ -356,19 +356,19 @@ async function main(): Promise<boolean> {
async function sendMessage(
app: HelloWorldApp,
igp: HyperlaneIgp,
origin: ChainName,
destination: ChainName,
gasCalc: InterchainGasCalculator,
messageSendTimeout: number,
messageReceiptTimeout: number,
) {
const startTime = Date.now();
const msg = 'Hello!';
const expectedHandleGas = BigNumber.from(100_000);
const expectedHandleGas = BigNumber.from(50_000);
const value = await utils.retryAsync(
() =>
gasCalc.quoteGasPaymentForDefaultIsmIgp(
igp.quoteGasPaymentForDefaultIsmIgp(
origin,
destination,
expectedHandleGas,

@ -4,45 +4,25 @@ import {
helloWorldFactories,
} from '@hyperlane-xyz/helloworld';
import {
AgentConnectionType,
ChainMap,
HyperlaneCore,
MultiProvider,
RouterConfig,
buildContracts,
} from '@hyperlane-xyz/sdk';
import { Contexts } from '../../config/contexts';
import { KEY_ROLE_ENUM } from '../../src/agents/roles';
import { CoreEnvironmentConfig, DeployEnvironment } from '../../src/config';
import { ConnectionType } from '../../src/config/agent';
import { CoreEnvironmentConfig } from '../../src/config';
import { deployEnvToSdkEnv } from '../../src/config/environment';
import { HelloWorldConfig } from '../../src/config/helloworld';
export async function getConfiguration(
environment: DeployEnvironment,
multiProvider: MultiProvider,
): Promise<ChainMap<RouterConfig>> {
const ownerMap: ChainMap<{ owner: string }> = {};
for (const chain of multiProvider.getKnownChainNames()) {
ownerMap[chain] = {
owner: await multiProvider.getSignerAddress(chain),
};
}
const core: HyperlaneCore = HyperlaneCore.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
return core.extendWithConnectionClientConfig(ownerMap);
}
export async function getApp(
coreConfig: CoreEnvironmentConfig,
context: Contexts,
keyRole: KEY_ROLE_ENUM,
keyContext: Contexts = context,
connectionType: ConnectionType = ConnectionType.Http,
connectionType: AgentConnectionType = AgentConnectionType.Http,
) {
const helloworldConfig = getHelloWorldConfig(coreConfig, context);
const contracts = buildContracts(

@ -4,7 +4,11 @@ import { S3Validator } from '../src/agents/aws/validator';
import { deployEnvToSdkEnv } from '../src/config/environment';
import { concurrentMap } from '../src/utils/utils';
import { getCoreEnvironmentConfig, getEnvironment } from './utils';
import {
getCoreEnvironmentConfig,
getEnvironment,
getValidatorsByChain,
} from './utils';
async function main() {
const environment = await getEnvironment();
@ -15,8 +19,8 @@ async function main() {
multiProvider,
);
const validators = Object.entries(config.core).flatMap(([chain, set]) =>
set.multisigIsm.validators.map((validator) => ({ chain, validator })),
const validators = Object.entries(getValidatorsByChain(config.core)).flatMap(
([chain, set]) => [...set].map((validator) => ({ chain, validator })),
);
const indices = await concurrentMap(

@ -9,7 +9,7 @@ import { deployEnvToSdkEnv } from '../src/config/environment';
import { readJSON, writeJSON } from '../src/utils/utils';
import {
getCoreContractsSdkFilepath,
getContractAddressesSdkFilepath,
getEnvironment,
getEnvironmentDirectory,
} from './utils';
@ -29,7 +29,7 @@ export function mergeWithSdkContractAddressArtifacts(
) {
const sdkEnvironment = deployEnvToSdkEnv[environment];
const coreAddresses: HyperlaneAddresses = readJSON(
getCoreContractsSdkFilepath(),
getContractAddressesSdkFilepath(),
`${sdkEnvironment}.json`,
);
@ -55,7 +55,7 @@ export function mergeWithSdkContractAddressArtifacts(
}
writeJSON(
getCoreContractsSdkFilepath(),
getContractAddressesSdkFilepath(),
`${sdkEnvironment}.json`,
coreAddresses,
);

@ -6,12 +6,12 @@ import {
} from '@hyperlane-xyz/sdk';
import { deployWithArtifacts } from '../../src/deploy';
import { getConfiguration } from '../helloworld/utils';
import { mergeWithSdkContractAddressArtifacts } from '../merge-sdk-contract-addresses';
import {
getCoreEnvironmentConfig,
getEnvironment,
getEnvironmentDirectory,
getRouterConfig,
} from '../utils';
// similar to hello world deploy script but uses freshly funded account for consistent addresses across chains
@ -26,7 +26,7 @@ async function main() {
);
// config gcp deployer key as owner
const configMap = await getConfiguration(environment, multiProvider);
const configMap = await getRouterConfig(environment, multiProvider);
const deployer = new InterchainAccountDeployer(
multiProvider,

@ -8,11 +8,11 @@ import {
import { bridgeAdapterConfigs } from '../../config/environments/testnet3/token-bridge';
import { deployWithArtifacts } from '../../src/deploy';
import { getConfiguration } from '../helloworld/utils';
import {
getCoreEnvironmentConfig,
getEnvironment,
getEnvironmentDirectory,
getRouterConfig,
} from '../utils';
async function main() {
@ -26,10 +26,10 @@ async function main() {
);
// config gcp deployer key as owner
const ownerConfigMap = await getConfiguration(environment, multiProvider);
const routerConfig = await getRouterConfig(environment, multiProvider);
const config = objMap(bridgeAdapterConfigs, (chain, conf) => ({
...conf,
...ownerConfigMap[chain],
...routerConfig[chain],
}));
const deployer = new LiquidityLayerDeployer(
multiProvider,

@ -6,12 +6,12 @@ import {
} from '@hyperlane-xyz/sdk';
import { deployWithArtifacts } from '../../src/deploy';
import { getConfiguration } from '../helloworld/utils';
import { mergeWithSdkContractAddressArtifacts } from '../merge-sdk-contract-addresses';
import {
getCoreEnvironmentConfig,
getEnvironment,
getEnvironmentDirectory,
getRouterConfig,
} from '../utils';
async function main() {
@ -25,7 +25,7 @@ async function main() {
);
// config gcp deployer key as owner
const configMap = await getConfiguration(environment, multiProvider);
const configMap = await getRouterConfig(environment, multiProvider);
const deployer = new InterchainQueryDeployer(
multiProvider,

@ -1,6 +1,6 @@
import path from 'path';
import { HyperlaneCore, objMap } from '@hyperlane-xyz/sdk';
import { HyperlaneIgp, objMap } from '@hyperlane-xyz/sdk';
import { deployEnvToSdkEnv } from '../../src/config/environment';
import { deployWithArtifacts } from '../../src/deploy';
@ -19,7 +19,7 @@ async function main() {
const environment = await getEnvironment();
const coreConfig = getCoreEnvironmentConfig(environment);
const multiProvider = await coreConfig.getMultiProvider();
const core = HyperlaneCore.fromEnvironment(
const igp = HyperlaneIgp.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
@ -36,7 +36,7 @@ async function main() {
const deployer = new TestQuerySenderDeployer(
multiProvider,
queryRouterAddresses,
core,
igp,
);
const dir = path.join(

@ -2,11 +2,16 @@ import path from 'path';
import yargs from 'yargs';
import {
AgentConnectionType,
AllChains,
ChainMap,
ChainMetadata,
ChainName,
CoreConfig,
HyperlaneCore,
HyperlaneIgp,
MultiProvider,
RouterConfig,
objMap,
promiseObjAll,
} from '@hyperlane-xyz/sdk';
@ -18,9 +23,8 @@ import { getCloudAgentKey } from '../src/agents/key-utils';
import { CloudAgentKey } from '../src/agents/keys';
import { KEY_ROLE_ENUM } from '../src/agents/roles';
import { CoreEnvironmentConfig, DeployEnvironment } from '../src/config';
import { ConnectionType } from '../src/config/agent';
import { fetchProvider } from '../src/config/chain';
import { EnvironmentNames } from '../src/config/environment';
import { EnvironmentNames, deployEnvToSdkEnv } from '../src/config/environment';
import { assertContext } from '../src/utils/utils';
export function getArgsWithContext() {
@ -31,8 +35,16 @@ export function getArgsWithContext() {
.alias('c', 'context');
}
export function getArgsWithFork() {
export function getArgsWithModule() {
return getArgs()
.string('module')
.choices('module', ['core', 'igp'])
.demandOption('module')
.alias('m', 'module');
}
export function getArgsWithModuleAndFork() {
return getArgsWithModule()
.string('fork')
.describe('fork', 'network to fork')
.alias('f', 'fork');
@ -60,10 +72,8 @@ export function assertEnvironment(env: string): DeployEnvironment {
);
}
export function getCoreEnvironmentConfig<Env extends DeployEnvironment>(
env: Env,
): CoreEnvironmentConfig {
return environments[env];
export function getCoreEnvironmentConfig(environment: DeployEnvironment) {
return environments[environment];
}
export async function getEnvironment() {
@ -117,8 +127,8 @@ async function getKeyForRole(
role: KEY_ROLE_ENUM,
index?: number,
): Promise<CloudAgentKey> {
const coreConfig = getCoreEnvironmentConfig(environment);
const agentConfig = await getAgentConfig(context, coreConfig);
const environmentConfig = environments[environment];
const agentConfig = await getAgentConfig(context, environmentConfig);
return getCloudAgentKey(agentConfig, role, chain, index);
}
@ -128,7 +138,7 @@ export async function getMultiProviderForRole(
context: Contexts,
role: KEY_ROLE_ENUM,
index?: number,
connectionType?: ConnectionType,
connectionType?: AgentConnectionType,
): Promise<MultiProvider> {
if (process.env.CI === 'true') {
return new MultiProvider(); // use default RPCs
@ -147,7 +157,7 @@ export async function getMultiProviderForRole(
return multiProvider;
}
export function getCoreContractsSdkFilepath() {
export function getContractAddressesSdkFilepath() {
return path.join('../sdk/src/consts/environments');
}
@ -155,15 +165,21 @@ export function getEnvironmentDirectory(environment: DeployEnvironment) {
return path.join('./config/environments/', environment);
}
export function getCoreDirectory(environment: DeployEnvironment) {
return path.join(getEnvironmentDirectory(environment), 'core');
export function getModuleDirectory(
environment: DeployEnvironment,
module: string,
) {
return path.join(getEnvironmentDirectory(environment), module);
}
export function getCoreVerificationDirectory(environment: DeployEnvironment) {
return path.join(getCoreDirectory(environment), 'verification');
export function getVerificationDirectory(
environment: DeployEnvironment,
module: string,
) {
return path.join(getModuleDirectory(environment, module), 'verification');
}
export function getCoreRustDirectory() {
export function getAgentConfigDirectory() {
return path.join('../../', 'rust', 'config');
}
@ -198,3 +214,47 @@ export async function assertCorrectKubeContext(
process.exit(1);
}
}
export async function getRouterConfig(
environment: DeployEnvironment,
multiProvider: MultiProvider,
useMultiProviderOwners = false,
): Promise<ChainMap<RouterConfig>> {
const core = HyperlaneCore.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
const igp = HyperlaneIgp.fromEnvironment(
deployEnvToSdkEnv[environment],
multiProvider,
);
const owners = getCoreEnvironmentConfig(environment).owners;
const config: ChainMap<RouterConfig> = {};
for (const chain of multiProvider.getKnownChainNames()) {
config[chain] = {
owner: useMultiProviderOwners
? await multiProvider.getSignerAddress(chain)
: owners[chain],
mailbox: core.getContracts(chain).mailbox.address,
interchainGasPaymaster:
igp.getContracts(chain).defaultIsmInterchainGasPaymaster.address,
};
}
return config;
}
export function getValidatorsByChain(
config: ChainMap<CoreConfig>,
): ChainMap<Set<string>> {
const validators: ChainMap<Set<string>> = {};
objMap(config, (local, coreConfig) => {
objMap(coreConfig.multisigIsm, (remote, multisigIsmConfig) => {
if (!validators[remote]) {
validators[remote] = new Set(multisigIsmConfig.validators);
} else {
multisigIsmConfig.validators.map((v) => validators[remote].add(v));
}
});
});
return validators;
}

@ -3,7 +3,11 @@ import { HyperlaneCore, objMap } from '@hyperlane-xyz/sdk';
import { CheckpointStatus, S3Validator } from '../src/agents/aws/validator';
import { deployEnvToSdkEnv } from '../src/config/environment';
import { getCoreEnvironmentConfig, getEnvironment } from './utils';
import {
getCoreEnvironmentConfig,
getEnvironment,
getValidatorsByChain,
} from './utils';
async function main() {
const environment = await getEnvironment();
@ -14,14 +18,12 @@ async function main() {
multiProvider,
);
objMap(config.core, async (chain, coreConfig) => {
objMap(getValidatorsByChain(config.core), async (chain, set) => {
const validatorAnnounce = core.getContracts(chain).validatorAnnounce;
const storageLocations =
await validatorAnnounce.getAnnouncedStorageLocations(
coreConfig.multisigIsm.validators,
);
await validatorAnnounce.getAnnouncedStorageLocations([...set]);
const validators = await Promise.all(
coreConfig.multisigIsm.validators.map((validator, i) => {
[...set].map((validator, i) => {
// Only use the latest announcement for now
if (storageLocations[i].length != 1) {
throw new Error('Only support single announcement');

@ -1,14 +1,10 @@
import { ChainName } from '@hyperlane-xyz/sdk';
import { AgentConnectionType, ChainName } from '@hyperlane-xyz/sdk';
import { utils } from '@hyperlane-xyz/utils';
import { Contexts } from '../../config/contexts';
import { AgentConfig, DeployEnvironment } from '../config';
import {
ChainAgentConfig,
CheckpointSyncerType,
ConnectionType,
TransactionSubmissionType,
} from '../config/agent';
import { ChainAgentConfig, CheckpointSyncerType } from '../config/agent';
import { TransactionSubmissionType } from '../config/agent';
import { fetchGCPSecret } from '../utils/gcloud';
import {
HelmCommand,
@ -41,7 +37,7 @@ async function helmValuesForChain(
let baseConnectionConfig: Record<string, string> = {
type: agentConfig.connectionType,
};
if (baseConnectionConfig.type == ConnectionType.HttpQuorum) {
if (baseConnectionConfig.type == AgentConnectionType.HttpQuorum) {
baseConnectionConfig = {
...baseConnectionConfig,
urls: '',

@ -1,7 +1,6 @@
import { BigNumberish } from 'ethers';
import { ChainMap, ChainName } from '@hyperlane-xyz/sdk';
import { types } from '@hyperlane-xyz/utils';
import { AgentConnectionType, ChainMap, ChainName } from '@hyperlane-xyz/sdk';
import { Contexts } from '../../config/contexts';
import {
@ -199,7 +198,7 @@ export interface AgentConfig {
context: Contexts;
docker: DockerConfig;
quorumProvider?: boolean;
connectionType: ConnectionType;
connectionType: AgentConnectionType;
index?: IndexingConfig;
aws?: AwsConfig;
// Names of all chains in the environment
@ -213,57 +212,6 @@ export interface AgentConfig {
rolesWithKeys: KEY_ROLE_ENUM[];
}
export type RustSigner = {
key: string;
type: string; // TODO
};
export enum ConnectionType {
Http = 'http',
Ws = 'ws',
HttpQuorum = 'httpQuorum',
HttpFallback = 'httpFallback',
}
export type RustConnection =
| {
type: ConnectionType.Http;
url: string;
}
| { type: ConnectionType.Ws; url: string }
| { type: ConnectionType.HttpQuorum; urls: string }
| { type: ConnectionType.HttpFallback; urls: string };
export type RustCoreAddresses = {
mailbox: types.Address;
interchainGasPaymaster: types.Address;
validatorAnnounce: types.Address;
};
export interface RustChainSetupBase {
name: ChainName;
domain: number;
signer?: RustSigner;
finalityBlocks: number;
addresses: RustCoreAddresses;
protocol: 'ethereum' | 'fuel';
connection?: RustConnection;
index?: { from: number };
}
export interface RustChainSetup extends RustChainSetupBase {
signer: RustSigner;
connection: RustConnection;
}
export type RustConfig = {
chains: Partial<ChainMap<RustChainSetupBase>>;
tracing?: {
level?: string;
fmt?: 'json';
};
};
// Helper to get chain-specific agent configurations
export class ChainAgentConfig {
constructor(

@ -1,11 +1,14 @@
import { FallbackProviderConfig } from '@ethersproject/providers';
import { ethers } from 'ethers';
import { ChainName, providerBuilder } from '@hyperlane-xyz/sdk';
import {
AgentConnectionType,
ChainName,
providerBuilder,
} from '@hyperlane-xyz/sdk';
import { getSecretRpcEndpoint } from '../agents';
import { ConnectionType } from './agent';
import { DeployEnvironment } from './environment';
export const defaultRetry = {
@ -16,20 +19,20 @@ export const defaultRetry = {
export async function fetchProvider(
environment: DeployEnvironment,
chainName: ChainName,
connectionType: ConnectionType = ConnectionType.Http,
connectionType: AgentConnectionType = AgentConnectionType.Http,
): Promise<ethers.providers.Provider> {
const single = connectionType === ConnectionType.Http;
const single = connectionType === AgentConnectionType.Http;
const rpcData = await getSecretRpcEndpoint(environment, chainName, !single);
switch (connectionType) {
case ConnectionType.Http: {
case AgentConnectionType.Http: {
return providerBuilder({ http: rpcData, retry: defaultRetry });
}
case ConnectionType.HttpQuorum: {
case AgentConnectionType.HttpQuorum: {
return new ethers.providers.FallbackProvider(
(rpcData as string[]).map((url) => providerBuilder({ http: url })), // disable retry for quorum
);
}
case ConnectionType.HttpFallback: {
case AgentConnectionType.HttpFallback: {
return new ethers.providers.FallbackProvider(
(rpcData as string[]).map((url, index) => {
const fallbackProviderConfig: FallbackProviderConfig = {

@ -1,17 +1,20 @@
import {
AgentConnectionType,
ChainMap,
ChainMetadata,
ChainName,
CoreConfig,
MultiProvider,
OverheadIgpConfig,
} from '@hyperlane-xyz/sdk';
import { CoreEnvironment } from '@hyperlane-xyz/sdk/dist/core/HyperlaneCore';
import { HyperlaneEnvironment } from '@hyperlane-xyz/sdk/dist/consts/environments';
import { types } from '@hyperlane-xyz/utils';
import { Contexts } from '../../config/contexts';
import { environments } from '../../config/environments';
import { KEY_ROLE_ENUM } from '../agents/roles';
import { AgentConfig, ConnectionType } from './agent';
import { AgentConfig } from './agent';
import { KeyFunderConfig } from './funding';
import { AllStorageGasOracleConfigs } from './gas-oracle';
import { HelloWorldConfig } from './helloworld';
@ -31,11 +34,13 @@ export type CoreEnvironmentConfig = {
// Each AgentConfig, keyed by the context
agents: Partial<Record<Contexts, AgentConfig>>;
core: ChainMap<CoreConfig>;
igp: ChainMap<OverheadIgpConfig>;
owners: ChainMap<types.Address>;
infra: InfrastructureConfig;
getMultiProvider: (
context?: Contexts,
role?: KEY_ROLE_ENUM,
connectionType?: ConnectionType,
connectionType?: AgentConnectionType,
) => Promise<MultiProvider>;
helloWorld?: Partial<Record<Contexts, HelloWorldConfig>>;
keyFunderConfig?: KeyFunderConfig;
@ -43,7 +48,10 @@ export type CoreEnvironmentConfig = {
storageGasOracleConfig?: AllStorageGasOracleConfigs;
};
export const deployEnvToSdkEnv: Record<DeployEnvironment, CoreEnvironment> = {
export const deployEnvToSdkEnv: Record<
DeployEnvironment,
HyperlaneEnvironment
> = {
mainnet2: 'mainnet',
testnet3: 'testnet',
test: 'test',

@ -1,7 +1,9 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { Contexts } from '../../config/contexts';
import { KEY_ROLE_ENUM } from '../agents/roles';
import { ConnectionType, DockerConfig } from './agent';
import { DockerConfig } from './agent';
export interface ContextAndRoles {
context: Contexts;
@ -18,5 +20,5 @@ export interface KeyFunderConfig {
contextsAndRolesToFund: ContextAndRolesMap;
cyclesBetweenEthereumMessages?: number;
prometheusPushGateway: string;
connectionType: ConnectionType.Http | ConnectionType.HttpQuorum;
connectionType: AgentConnectionType.Http | AgentConnectionType.HttpQuorum;
}

@ -1,6 +1,6 @@
import { ChainMap, ChainName } from '@hyperlane-xyz/sdk';
import { AgentConnectionType, ChainMap, ChainName } from '@hyperlane-xyz/sdk';
import { ConnectionType, DockerConfig } from './agent';
import { DockerConfig } from './agent';
export enum HelloWorldKathyRunMode {
// Sends messages between all pairwise chains
@ -29,7 +29,7 @@ export interface HelloWorldKathyConfig {
messageReceiptTimeout: number;
// Which type of provider to use
connectionType: Exclude<ConnectionType, ConnectionType.Ws>;
connectionType: Exclude<AgentConnectionType, AgentConnectionType.Ws>;
// How many cycles to skip between a cycles that send messages to/from Ethereum. Defaults to 0.
cyclesBetweenEthereumMessages?: number;
}

@ -1,4 +1,4 @@
export { AgentConfig, RustConfig, RustChainSetup } from './agent';
export { AgentConfig } from './agent';
export { CoreEnvironmentConfig, DeployEnvironment } from './environment';
export {
AllStorageGasOracleConfigs,

@ -1,8 +1,10 @@
import { ConnectionType, DockerConfig } from './agent';
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { DockerConfig } from './agent';
export interface LiquidityLayerRelayerConfig {
docker: DockerConfig;
namespace: string;
connectionType: ConnectionType.Http | ConnectionType.HttpQuorum;
connectionType: AgentConnectionType.Http | AgentConnectionType.HttpQuorum;
prometheusPushGateway: string;
}

@ -1,34 +1,18 @@
import { ethers } from 'ethers';
import {
InterchainGasPaymaster,
Mailbox,
OverheadIgp,
ProxyAdmin,
ValidatorAnnounce,
} from '@hyperlane-xyz/core';
import { Mailbox, ProxyAdmin, ValidatorAnnounce } from '@hyperlane-xyz/core';
import {
ChainMap,
ChainName,
CoreConfig,
GasOracleContracts,
HyperlaneCoreDeployer,
MultiProvider,
ProxiedContract,
TransparentProxyAddresses,
chainMetadata,
objMap,
} from '@hyperlane-xyz/sdk';
import { types } from '@hyperlane-xyz/utils';
import { DeployEnvironment, RustConfig } from '../config';
import {
ConnectionType,
RustChainSetupBase,
RustConnection,
} from '../config/agent';
import { deployEnvToSdkEnv } from '../config/environment';
import { writeJSON } from '../utils/utils';
import { DeployEnvironment } from '../config';
export class HyperlaneCoreInfraDeployer extends HyperlaneCoreDeployer {
environment: DeployEnvironment;
@ -42,44 +26,6 @@ export class HyperlaneCoreInfraDeployer extends HyperlaneCoreDeployer {
this.environment = environment;
}
async deployInterchainGasPaymaster(
chain: ChainName,
proxyAdmin: ProxyAdmin,
gasOracleContracts: GasOracleContracts,
): Promise<
ProxiedContract<InterchainGasPaymaster, TransparentProxyAddresses>
> {
const deployOpts = {
create2Salt: ethers.utils.solidityKeccak256(
['string', 'string', 'uint8'],
[this.environment, 'interchainGasPaymaster', 6],
),
};
return super.deployInterchainGasPaymaster(
chain,
proxyAdmin,
gasOracleContracts,
deployOpts,
);
}
async deployDefaultIsmInterchainGasPaymaster(
chain: ChainName,
interchainGasPaymasterAddress: types.Address,
): Promise<OverheadIgp> {
const deployOpts = {
create2Salt: ethers.utils.solidityKeccak256(
['string', 'string', 'uint8'],
[this.environment, 'defaultIsmInterchainGasPaymaster', 4],
),
};
return super.deployDefaultIsmInterchainGasPaymaster(
chain,
interchainGasPaymasterAddress,
deployOpts,
);
}
async deployMailbox(
chain: ChainName,
defaultIsmAddress: types.Address,
@ -111,53 +57,4 @@ export class HyperlaneCoreInfraDeployer extends HyperlaneCoreDeployer {
};
return super.deployValidatorAnnounce(chain, mailboxAddress, deployOpts);
}
writeRustConfigs(directory: string) {
const rustConfig: RustConfig = {
chains: {},
};
objMap(this.configMap, (chain) => {
const contracts = this.deployedContracts[chain];
const metadata = chainMetadata[chain];
// Don't write config for undeployed chains
if (
contracts == undefined ||
contracts.mailbox == undefined ||
contracts.interchainGasPaymaster == undefined ||
contracts.validatorAnnounce == undefined
) {
return;
}
const chainConfig: RustChainSetupBase = {
name: chain,
domain: metadata.chainId,
addresses: {
mailbox: contracts.mailbox.contract.address,
interchainGasPaymaster: contracts.interchainGasPaymaster.address,
validatorAnnounce: contracts.validatorAnnounce.address,
},
protocol: 'ethereum',
finalityBlocks: metadata.blocks!.reorgPeriod!,
connection: {
// not a valid connection but we want to fill in the HTTP type for
// them as a default and leave out the URL
type: ConnectionType.Http,
url: undefined,
} as any as RustConnection,
};
const startingBlockNumber = this.startingBlockNumbers[chain];
if (startingBlockNumber) {
chainConfig.index = { from: startingBlockNumber };
}
rustConfig.chains[chain] = chainConfig;
});
writeJSON(
directory,
`${deployEnvToSdkEnv[this.environment]}_config.json`,
rustConfig,
);
}
}

@ -1,149 +1,31 @@
import { prompts } from 'prompts';
import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core';
import {
ChainMap,
ChainName,
CoreContracts,
CoreConfig,
CoreViolationType,
DefaultIsmIgpViolation,
DefaultIsmIgpViolationType,
EnrolledValidatorsViolation,
HyperlaneCore,
HyperlaneCoreChecker,
IgpBeneficiaryViolation,
IgpGasOraclesViolation,
IgpViolation,
IgpViolationType,
MultisigIsmViolation,
MultisigIsmViolationType,
OwnerViolation,
ProxyKind,
ProxyViolation,
ViolationType,
objMap,
} from '@hyperlane-xyz/sdk';
import { ProxyKind } from '@hyperlane-xyz/sdk/dist/proxy';
import { types, utils } from '@hyperlane-xyz/utils';
import { canProposeSafeTransactions } from '../utils/safe';
import {
ManualMultiSend,
MultiSend,
SafeMultiSend,
SignerMultiSend,
} from './multisend';
enum SubmissionType {
MANUAL = 'MANUAL',
SIGNER = 'SIGNER',
SAFE = 'SAFE',
}
type AnnotatedCallData = types.CallData & {
submissionType?: SubmissionType;
description: string;
};
export class HyperlaneCoreGovernor {
readonly checker: HyperlaneCoreChecker;
private calls: ChainMap<AnnotatedCallData[]>;
private canPropose: ChainMap<Map<string, boolean>>;
constructor(checker: HyperlaneCoreChecker) {
this.checker = checker;
this.calls = objMap(this.checker.app.contractsMap, () => []);
this.canPropose = objMap(this.checker.app.contractsMap, () => new Map());
}
async govern(confirm = true) {
// 1. Produce calls from checker violations.
await this.mapViolationsToCalls();
// 2. For each call, infer how it should be submitted on-chain.
await this.inferCallSubmissionTypes();
// 3. Prompt the user to confirm that the count, description,
// and submission methods look correct before submitting.
for (const chain of Object.keys(this.calls)) {
await this.sendCalls(chain, confirm);
}
}
async governChain(chain: ChainName, confirm = true) {
// 1. Produce calls from checker violations.
await this.mapViolationsToCalls();
// 2. For each call, infer how it should be submitted on-chain.
await this.inferCallSubmissionTypes();
// 3. Prompt the user to confirm that the count, description,
// and submission methods look correct before submitting.
await this.sendCalls(chain, confirm);
}
protected async sendCalls(chain: ChainName, confirm: boolean) {
const calls = this.calls[chain];
console.log(`\nFound ${calls.length} transactions for ${chain}`);
const filterCalls = (submissionType: SubmissionType) =>
calls.filter((call) => call.submissionType == submissionType);
const summarizeCalls = async (
submissionType: SubmissionType,
calls: AnnotatedCallData[],
): Promise<boolean> => {
if (calls.length > 0) {
console.log(
`> ${calls.length} calls will be submitted via ${submissionType}`,
);
calls.map((c) =>
console.log(`> > ${c.description} (to: ${c.to} data: ${c.data})`),
);
const response =
!confirm ||
prompts.confirm({
type: 'confirm',
name: 'value',
message: 'Can you confirm?',
initial: false,
});
return response as unknown as boolean;
}
return false;
};
const sendCallsForType = async (
submissionType: SubmissionType,
multiSend: MultiSend,
) => {
const calls = filterCalls(submissionType);
if (calls.length > 0) {
const confirmed = await summarizeCalls(submissionType, calls);
if (confirmed) {
console.log(`Submitting calls on ${chain} via ${submissionType}`);
await multiSend.sendTransactions(
calls.map((call) => ({ to: call.to, data: call.data })),
);
} else {
console.log(
`Skipping submission of calls on ${chain} via ${submissionType}`,
);
}
}
};
await sendCallsForType(
SubmissionType.SIGNER,
new SignerMultiSend(this.checker.multiProvider, chain),
);
const owner = this.checker.configMap[chain!].owner!;
await sendCallsForType(
SubmissionType.SAFE,
new SafeMultiSend(this.checker.multiProvider, chain, owner),
);
await sendCallsForType(SubmissionType.MANUAL, new ManualMultiSend(chain));
}
AnnotatedCallData,
HyperlaneAppGovernor,
} from '../govern/HyperlaneAppGovernor';
protected pushCall(chain: ChainName, call: AnnotatedCallData) {
this.calls[chain].push(call);
export class HyperlaneCoreGovernor extends HyperlaneAppGovernor<
HyperlaneCore,
CoreConfig
> {
constructor(checker: HyperlaneCoreChecker, owners: ChainMap<types.Address>) {
super(checker, owners);
}
protected async mapViolationsToCalls() {
@ -161,96 +43,12 @@ export class HyperlaneCoreGovernor {
this.handleProxyViolation(violation as ProxyViolation);
break;
}
case CoreViolationType.InterchainGasPaymaster: {
this.handleIgpViolation(violation as IgpViolation);
break;
}
case CoreViolationType.DefaultIsmInterchainGasPaymaster: {
this.handleDefaultIsmIgpViolation(
violation as DefaultIsmIgpViolation,
);
break;
}
default:
throw new Error(`Unsupported violation type ${violation.type}`);
}
}
}
handleProxyViolation(violation: ProxyViolation) {
const contracts: CoreContracts =
this.checker.app.contractsMap[violation.chain];
const data = contracts.proxyAdmin.interface.encodeFunctionData('upgrade', [
violation.data.proxyAddresses.proxy,
violation.data.proxyAddresses.implementation,
]);
this.pushCall(violation.chain, {
to: contracts.proxyAdmin.address,
data,
description: `Upgrade proxy ${violation.data.proxyAddresses.proxy} to implementation ${violation.data.proxyAddresses.implementation}`,
});
}
protected async inferCallSubmissionTypes() {
for (const chain of Object.keys(this.calls)) {
for (const call of this.calls[chain]) {
call.submissionType = await this.inferCallSubmissionType(chain, call);
}
}
}
protected async inferCallSubmissionType(
chain: ChainName,
call: AnnotatedCallData,
): Promise<SubmissionType> {
const multiProvider = this.checker.multiProvider;
const signer = multiProvider.getSigner(chain);
const signerAddress = await signer.getAddress();
const canUseSubmissionType = async (
submitterAddress: types.Address,
): Promise<boolean> => {
try {
await multiProvider.estimateGas(chain, call, submitterAddress);
return true;
} catch (e) {} // eslint-disable-line no-empty
return false;
};
if (await canUseSubmissionType(signerAddress)) {
return SubmissionType.SIGNER;
}
// 2. Check if the call will succeed via Gnosis Safe.
const safeAddress = this.checker.configMap[chain!].owner;
if (!safeAddress) throw new Error(`Owner address not found for ${chain}`);
// 2a. Confirm that the signer is a Safe owner or delegate.
// This should implicitly check whether or not the owner is a gnosis
// safe.
if (!this.canPropose[chain].has(safeAddress)) {
this.canPropose[chain].set(
safeAddress,
await canProposeSafeTransactions(
signerAddress,
chain,
multiProvider,
safeAddress,
),
);
}
// 2b. Check if calling from the owner/safeAddress will succeed.
if (
this.canPropose[chain].get(safeAddress) &&
(await canUseSubmissionType(safeAddress))
) {
return SubmissionType.SAFE;
}
return SubmissionType.MANUAL;
}
// pushes calls which reconcile actual and expected sets on chain
protected pushSetReconcilationCalls<T>(reconcile: {
chain: ChainName;
@ -318,103 +116,4 @@ export class HyperlaneCoreGovernor {
);
}
}
handleOwnerViolation(violation: OwnerViolation) {
this.pushCall(violation.chain, {
to: violation.contract.address,
data: violation.contract.interface.encodeFunctionData(
'transferOwnership',
[violation.expected],
),
description: `Transfer ownership of ${violation.contract.address} to ${violation.expected}`,
});
}
handleIgpViolation(violation: IgpViolation) {
switch (violation.subType) {
case IgpViolationType.Beneficiary: {
const beneficiaryViolation = violation as IgpBeneficiaryViolation;
this.pushCall(beneficiaryViolation.chain, {
to: beneficiaryViolation.contract.address,
data: beneficiaryViolation.contract.interface.encodeFunctionData(
'setBeneficiary',
[beneficiaryViolation.expected],
),
description: `Set IGP beneficiary to ${beneficiaryViolation.expected}`,
});
break;
}
case IgpViolationType.GasOracles: {
const gasOraclesViolation = violation as IgpGasOraclesViolation;
const configs: InterchainGasPaymaster.GasOracleConfigStruct[] = [];
for (const [remote, expected] of Object.entries(
gasOraclesViolation.expected,
)) {
const remoteId = this.checker.multiProvider.getDomainId(remote);
configs.push({
remoteDomain: remoteId,
gasOracle: expected,
});
}
this.pushCall(gasOraclesViolation.chain, {
to: gasOraclesViolation.contract.address,
data: gasOraclesViolation.contract.interface.encodeFunctionData(
'setGasOracles',
[configs],
),
description: `Setting ${Object.keys(gasOraclesViolation.expected)
.map((remoteStr) => {
const remote = remoteStr as ChainName;
const remoteId = this.checker.multiProvider.getDomainId(remote);
const expected = gasOraclesViolation.expected[remote];
return `gas oracle for ${remote} (domain ID ${remoteId}) to ${expected}`;
})
.join(', ')}`,
});
break;
}
default:
throw new Error(`Unsupported IgpViolationType: ${violation.subType}`);
}
}
handleDefaultIsmIgpViolation(violation: DefaultIsmIgpViolation) {
switch (violation.subType) {
case DefaultIsmIgpViolationType.DestinationGasOverheads: {
const configs: OverheadIgp.DomainConfigStruct[] = Object.entries(
violation.expected,
).map(
([remote, gasOverhead]) =>
({
domain: this.checker.multiProvider.getDomainId(remote),
gasOverhead: gasOverhead,
} as OverheadIgp.DomainConfigStruct),
);
this.pushCall(violation.chain, {
to: violation.contract.address,
data: violation.contract.interface.encodeFunctionData(
'setDestinationGasOverheads',
[configs],
),
description: `Setting ${Object.keys(violation.expected)
.map((remoteStr) => {
const remote = remoteStr as ChainName;
const remoteId = this.checker.multiProvider.getDomainId(remote);
const expected = violation.expected[remote];
return `destination gas overhead for ${remote} (domain ID ${remoteId}) to ${expected}`;
})
.join(', ')}`,
});
break;
}
default:
throw new Error(
`Unsupported DefaultIsmIgpViolationType: ${violation.subType}`,
);
}
}
}

@ -0,0 +1,71 @@
import { ethers } from 'ethers';
import {
InterchainGasPaymaster,
OverheadIgp,
ProxyAdmin,
StorageGasOracle,
} from '@hyperlane-xyz/core';
import {
ChainMap,
ChainName,
HyperlaneIgpDeployer,
MultiProvider,
OverheadIgpConfig,
ProxiedContract,
TransparentProxyAddresses,
} from '@hyperlane-xyz/sdk';
import { types } from '@hyperlane-xyz/utils';
import { DeployEnvironment } from '../config';
export class HyperlaneIgpInfraDeployer extends HyperlaneIgpDeployer {
environment: DeployEnvironment;
constructor(
multiProvider: MultiProvider,
configMap: ChainMap<OverheadIgpConfig>,
environment: DeployEnvironment,
) {
super(multiProvider, configMap);
this.environment = environment;
}
async deployInterchainGasPaymaster(
chain: ChainName,
proxyAdmin: ProxyAdmin,
storageGasOracle: StorageGasOracle,
): Promise<
ProxiedContract<InterchainGasPaymaster, TransparentProxyAddresses>
> {
const deployOpts = {
create2Salt: ethers.utils.solidityKeccak256(
['string', 'string', 'uint8'],
[this.environment, 'interchainGasPaymaster', 6],
),
};
return super.deployInterchainGasPaymaster(
chain,
proxyAdmin,
storageGasOracle,
deployOpts,
);
}
async deployOverheadInterchainGasPaymaster(
chain: ChainName,
interchainGasPaymasterAddress: types.Address,
): Promise<OverheadIgp> {
const deployOpts = {
create2Salt: ethers.utils.solidityKeccak256(
['string', 'string', 'uint8'],
[this.environment, 'defaultIsmInterchainGasPaymaster', 4],
),
};
return super.deployOverheadInterchainGasPaymaster(
chain,
interchainGasPaymasterAddress,
deployOpts,
);
}
}

@ -0,0 +1,126 @@
import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core';
import {
ChainMap,
ChainName,
HyperlaneIgp,
HyperlaneIgpChecker,
IgpBeneficiaryViolation,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
OverheadIgpConfig,
ProxyKind,
ProxyViolation,
} from '@hyperlane-xyz/sdk';
import { types } from '@hyperlane-xyz/utils';
import { HyperlaneAppGovernor } from '../govern/HyperlaneAppGovernor';
export class HyperlaneIgpGovernor extends HyperlaneAppGovernor<
HyperlaneIgp,
OverheadIgpConfig
> {
constructor(checker: HyperlaneIgpChecker, owners: ChainMap<types.Address>) {
super(checker, owners);
}
protected async mapViolationsToCalls() {
for (const violation of this.checker.violations) {
switch (violation.type) {
case 'InterchainGasPaymaster': {
this.handleIgpViolation(violation as IgpViolation);
break;
}
case ProxyKind.Transparent: {
this.handleProxyViolation(violation as ProxyViolation);
break;
}
default:
throw new Error(`Unsupported violation type ${violation.type}`);
}
}
}
handleIgpViolation(violation: IgpViolation) {
switch (violation.subType) {
case IgpViolationType.Beneficiary: {
const beneficiaryViolation = violation as IgpBeneficiaryViolation;
this.pushCall(beneficiaryViolation.chain, {
to: beneficiaryViolation.contract.address,
data: beneficiaryViolation.contract.interface.encodeFunctionData(
'setBeneficiary',
[beneficiaryViolation.expected],
),
description: `Set IGP beneficiary to ${beneficiaryViolation.expected}`,
});
break;
}
case IgpViolationType.GasOracles: {
const gasOraclesViolation = violation as IgpGasOraclesViolation;
const configs: InterchainGasPaymaster.GasOracleConfigStruct[] = [];
for (const [remote, expected] of Object.entries(
gasOraclesViolation.expected,
)) {
const remoteId = this.checker.multiProvider.getDomainId(remote);
configs.push({
remoteDomain: remoteId,
gasOracle: expected,
});
}
this.pushCall(gasOraclesViolation.chain, {
to: gasOraclesViolation.contract.address,
data: gasOraclesViolation.contract.interface.encodeFunctionData(
'setGasOracles',
[configs],
),
description: `Setting ${Object.keys(gasOraclesViolation.expected)
.map((remoteStr) => {
const remote = remoteStr as ChainName;
const remoteId = this.checker.multiProvider.getDomainId(remote);
const expected = gasOraclesViolation.expected[remote];
return `gas oracle for ${remote} (domain ID ${remoteId}) to ${expected}`;
})
.join(', ')}`,
});
break;
}
case IgpViolationType.Overhead: {
const overheadViolation = violation as IgpOverheadViolation;
const configs: OverheadIgp.DomainConfigStruct[] = Object.entries(
violation.expected,
).map(
([remote, gasOverhead]) =>
({
domain: this.checker.multiProvider.getDomainId(remote),
gasOverhead: gasOverhead,
} as OverheadIgp.DomainConfigStruct),
);
this.pushCall(violation.chain, {
to: overheadViolation.contract.address,
data: overheadViolation.contract.interface.encodeFunctionData(
'setDestinationGasOverheads',
[configs],
),
description: `Setting ${Object.keys(violation.expected)
.map((remoteStr) => {
const remote = remoteStr as ChainName;
const remoteId = this.checker.multiProvider.getDomainId(remote);
const expected = violation.expected[remote];
return `destination gas overhead for ${remote} (domain ID ${remoteId}) to ${expected}`;
})
.join(', ')}`,
});
break;
}
default:
throw new Error(
`Unsupported IgpViolation subType: ${violation.subType}`,
);
}
}
}

@ -0,0 +1,219 @@
import { prompts } from 'prompts';
import {
ChainMap,
ChainName,
CoreContracts,
HyperlaneApp,
HyperlaneAppChecker,
OwnerViolation,
ProxyViolation,
objMap,
} from '@hyperlane-xyz/sdk';
import { types } from '@hyperlane-xyz/utils';
import { canProposeSafeTransactions } from '../utils/safe';
import {
ManualMultiSend,
MultiSend,
SafeMultiSend,
SignerMultiSend,
} from './multisend';
export enum SubmissionType {
MANUAL = 'MANUAL',
SIGNER = 'SIGNER',
SAFE = 'SAFE',
}
export type AnnotatedCallData = types.CallData & {
submissionType?: SubmissionType;
description: string;
};
export abstract class HyperlaneAppGovernor<
App extends HyperlaneApp<any>,
Config,
> {
readonly checker: HyperlaneAppChecker<App, Config>;
private owners: ChainMap<types.Address>;
private calls: ChainMap<AnnotatedCallData[]>;
private canPropose: ChainMap<Map<string, boolean>>;
constructor(
checker: HyperlaneAppChecker<App, Config>,
owners: ChainMap<types.Address>,
) {
this.checker = checker;
this.owners = owners;
this.calls = objMap(this.checker.app.contractsMap, () => []);
this.canPropose = objMap(this.checker.app.contractsMap, () => new Map());
}
async govern(confirm = true, chain?: ChainName) {
// 1. Produce calls from checker violations.
await this.mapViolationsToCalls();
// 2. For each call, infer how it should be submitted on-chain.
await this.inferCallSubmissionTypes();
// 3. Prompt the user to confirm that the count, description,
// and submission methods look correct before submitting.
const chains = chain ? [chain] : Object.keys(this.calls);
for (const chain of chains) {
await this.sendCalls(chain, confirm);
}
}
protected async sendCalls(chain: ChainName, confirm: boolean) {
const calls = this.calls[chain];
console.log(`\nFound ${calls.length} transactions for ${chain}`);
const filterCalls = (submissionType: SubmissionType) =>
calls.filter((call) => call.submissionType == submissionType);
const summarizeCalls = async (
submissionType: SubmissionType,
calls: AnnotatedCallData[],
): Promise<boolean> => {
if (calls.length > 0) {
console.log(
`> ${calls.length} calls will be submitted via ${submissionType}`,
);
calls.map((c) =>
console.log(`> > ${c.description} (to: ${c.to} data: ${c.data})`),
);
const response =
!confirm ||
prompts.confirm({
type: 'confirm',
name: 'value',
message: 'Can you confirm?',
initial: false,
});
return !!response;
}
return false;
};
const sendCallsForType = async (
submissionType: SubmissionType,
multiSend: MultiSend,
) => {
const calls = filterCalls(submissionType);
if (calls.length > 0) {
const confirmed = await summarizeCalls(submissionType, calls);
if (confirmed) {
console.log(`Submitting calls on ${chain} via ${submissionType}`);
await multiSend.sendTransactions(
calls.map((call) => ({ to: call.to, data: call.data })),
);
} else {
console.log(
`Skipping submission of calls on ${chain} via ${submissionType}`,
);
}
}
};
await sendCallsForType(
SubmissionType.SIGNER,
new SignerMultiSend(this.checker.multiProvider, chain),
);
await sendCallsForType(
SubmissionType.SAFE,
new SafeMultiSend(this.checker.multiProvider, chain, this.owners[chain]),
);
await sendCallsForType(SubmissionType.MANUAL, new ManualMultiSend(chain));
}
protected pushCall(chain: ChainName, call: AnnotatedCallData) {
this.calls[chain].push(call);
}
protected abstract mapViolationsToCalls(): Promise<void>;
handleProxyViolation(violation: ProxyViolation) {
const contracts: CoreContracts =
this.checker.app.contractsMap[violation.chain];
const data = contracts.proxyAdmin.interface.encodeFunctionData('upgrade', [
violation.data.proxyAddresses.proxy,
violation.data.proxyAddresses.implementation,
]);
this.pushCall(violation.chain, {
to: contracts.proxyAdmin.address,
data,
description: `Upgrade proxy ${violation.data.proxyAddresses.proxy} to implementation ${violation.data.proxyAddresses.implementation}`,
});
}
protected async inferCallSubmissionTypes() {
for (const chain of Object.keys(this.calls)) {
for (const call of this.calls[chain]) {
call.submissionType = await this.inferCallSubmissionType(chain, call);
}
}
}
protected async inferCallSubmissionType(
chain: ChainName,
call: AnnotatedCallData,
): Promise<SubmissionType> {
const multiProvider = this.checker.multiProvider;
const signer = multiProvider.getSigner(chain);
const signerAddress = await signer.getAddress();
const canUseSubmissionType = async (
submitterAddress: types.Address,
): Promise<boolean> => {
try {
await multiProvider.estimateGas(chain, call, submitterAddress);
return true;
} catch (e) {} // eslint-disable-line no-empty
return false;
};
if (await canUseSubmissionType(signerAddress)) {
return SubmissionType.SIGNER;
}
// 2. Check if the call will succeed via Gnosis Safe.
const safeAddress = this.owners[chain];
if (!safeAddress) throw new Error(`Owner address not found for ${chain}`);
// 2a. Confirm that the signer is a Safe owner or delegate.
// This should implicitly check whether or not the owner is a gnosis
// safe.
if (!this.canPropose[chain].has(safeAddress)) {
this.canPropose[chain].set(
safeAddress,
await canProposeSafeTransactions(
signerAddress,
chain,
multiProvider,
safeAddress,
),
);
}
// 2b. Check if calling from the owner/safeAddress will succeed.
if (
this.canPropose[chain].get(safeAddress) &&
(await canUseSubmissionType(safeAddress))
) {
return SubmissionType.SAFE;
}
return SubmissionType.MANUAL;
}
handleOwnerViolation(violation: OwnerViolation) {
this.pushCall(violation.chain, {
to: violation.contract.address,
data: violation.contract.interface.encodeFunctionData(
'transferOwnership',
[violation.expected],
),
description: `Transfer ownership of ${violation.contract.address} to ${violation.expected}`,
});
}
}

@ -1,5 +1,6 @@
import { AgentConnectionType } from '@hyperlane-xyz/sdk';
import { AgentConfig } from '../config';
import { ConnectionType } from '../config/agent';
import {
HelmCommand,
buildHelmChartDependencies,
@ -59,7 +60,7 @@ async function scraperHelmValues(agentConfig: AgentConfig) {
let baseConnectionConfig: Record<string, string> = {
type: agentConfig.connectionType,
};
if (baseConnectionConfig.type == ConnectionType.HttpQuorum) {
if (baseConnectionConfig.type == AgentConnectionType.HttpQuorum) {
baseConnectionConfig = {
...baseConnectionConfig,
urls: '',

@ -2,8 +2,8 @@ import { TestQuerySender, TestQuerySender__factory } from '@hyperlane-xyz/core';
import {
ChainMap,
ChainName,
HyperlaneCore,
HyperlaneDeployer,
HyperlaneIgp,
MultiProvider,
} from '@hyperlane-xyz/sdk';
@ -25,7 +25,7 @@ export class TestQuerySenderDeployer extends HyperlaneDeployer<
constructor(
multiProvider: MultiProvider,
queryRouters: ChainMap<TestQuerySenderConfig>,
protected core: HyperlaneCore,
protected igp: HyperlaneIgp,
) {
super(multiProvider, queryRouters, factories);
}
@ -35,7 +35,7 @@ export class TestQuerySenderDeployer extends HyperlaneDeployer<
'initialize',
[
config.queryRouterAddress,
this.core.getContracts(chain).interchainGasPaymaster.address,
this.igp.getContracts(chain).interchainGasPaymaster.address,
],
);
const TestQuerySender = await this.deployContract(

@ -5,7 +5,12 @@ import { ethers } from 'ethers';
import fs from 'fs';
import path from 'path';
import { AllChains, ChainName, CoreChainName } from '@hyperlane-xyz/sdk';
import {
AllChains,
ChainName,
CoreChainName,
objMerge,
} from '@hyperlane-xyz/sdk';
import { Contexts } from '../../config/contexts';
import { ALL_KEY_ROLES, KEY_ROLE_ENUM } from '../agents/roles';
@ -152,6 +157,15 @@ export function warn(text: string, padded = false) {
}
}
export function writeMergedJSON(directory: string, filename: string, obj: any) {
if (fs.existsSync(path.join(directory, filename))) {
const previous = readJSON(directory, filename);
writeJSON(directory, filename, objMerge(previous, obj));
} else {
writeJSON(directory, filename, obj);
}
}
export function writeJSON(directory: string, filename: string, obj: any) {
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });

@ -50,7 +50,7 @@ describe('core', async () => {
const base = './test/outputs/core';
writeJSON(base, 'contracts.json', serializeContracts(contracts));
writeJSON(base, 'verification.json', deployer.verificationInputs);
deployer.writeRustConfigs(base);
// deployer.writeRustConfigs(base);
});
describe('failure modes', async () => {
@ -113,13 +113,13 @@ describe('core', async () => {
it('deploys a new implementation if it has been removed from the artifacts', async () => {
// Copy the old addresses
const oldAddresses = {
...deployer.deployedContracts.test2!.interchainGasPaymaster!.addresses,
...deployer.deployedContracts.test2!.mailbox!.addresses,
};
// @ts-ignore
delete deployer.deployedContracts.test2!.interchainGasPaymaster!.addresses
delete deployer.deployedContracts.test2!.mailbox!.addresses
.implementation;
const result = await deployer.deploy();
const newAddresses = result.test2.interchainGasPaymaster.addresses;
const newAddresses = result.test2.mailbox.addresses;
// New implementation
expect(newAddresses.implementation).to.not.be.undefined;
expect(newAddresses.implementation).to.not.equal(

@ -1,13 +1,15 @@
import {
HyperlaneAddresses,
HyperlaneContracts,
HyperlaneFactories,
buildContracts,
connectContracts,
serializeContracts,
} from './contracts';
import { MultiProvider } from './providers/MultiProvider';
import { ChainMap, ChainName } from './types';
import { MultiGeneric } from './utils/MultiGeneric';
import { objMap } from './utils/objects';
import { objMap, pick } from './utils/objects';
export class HyperlaneApp<
Contracts extends HyperlaneContracts,
@ -22,6 +24,23 @@ export class HyperlaneApp<
super(connectedContractsMap);
}
static buildContracts<C extends HyperlaneContracts>(
addresses: ChainMap<HyperlaneAddresses>,
factories: HyperlaneFactories,
multiProvider: MultiProvider,
): { contracts: ChainMap<C>; intersectionProvider: MultiProvider } {
const chains = Object.keys(addresses);
const { intersection, multiProvider: intersectionProvider } =
multiProvider.intersect(chains, true);
const intersectionAddresses = pick(addresses, intersection);
const contracts = buildContracts(
intersectionAddresses,
factories,
) as ChainMap<C>;
return { contracts, intersectionProvider };
}
getContracts(chain: ChainName): Contracts {
return this.get(chain);
}

@ -0,0 +1,97 @@
import { types } from '@hyperlane-xyz/utils';
import { MultiProvider } from '../providers/MultiProvider';
import { getProxyAddress } from '../proxy';
import { ChainMap, ChainName } from '../types';
export type AgentSigner = {
key: string;
type: string; // TODO
};
export enum AgentConnectionType {
Http = 'http',
Ws = 'ws',
HttpQuorum = 'httpQuorum',
HttpFallback = 'httpFallback',
}
export type AgentConnection =
| {
type: AgentConnectionType.Http;
url: string;
}
| { type: AgentConnectionType.Ws; url: string }
| { type: AgentConnectionType.HttpQuorum; urls: string }
| { type: AgentConnectionType.HttpFallback; urls: string };
export type HyperlaneAgentAddresses = {
mailbox: types.Address;
interchainGasPaymaster: types.Address;
validatorAnnounce: types.Address;
};
export type AgentChainSetupBase = {
name: ChainName;
domain: number;
signer?: AgentSigner;
finalityBlocks: number;
addresses: HyperlaneAgentAddresses;
protocol: 'ethereum' | 'fuel';
connection?: AgentConnection;
index?: { from: number };
};
export interface AgentChainSetup extends AgentChainSetupBase {
signer: AgentSigner;
connection: AgentConnection;
}
export type AgentConfig = {
chains: Partial<ChainMap<AgentChainSetupBase>>;
tracing?: {
level?: string;
fmt?: 'json';
};
};
export function buildAgentConfig(
chains: ChainName[],
multiProvider: MultiProvider,
addresses: ChainMap<HyperlaneAgentAddresses>,
startBlocks: ChainMap<number>,
): AgentConfig {
const agentConfig: AgentConfig = {
chains: {},
};
for (const chain of [...chains].sort()) {
const metadata = multiProvider.getChainMetadata(chain);
const chainConfig: AgentChainSetupBase = {
name: chain,
domain: metadata.chainId,
addresses: {
mailbox: getProxyAddress(addresses[chain].mailbox),
interchainGasPaymaster: getProxyAddress(
addresses[chain].interchainGasPaymaster,
),
validatorAnnounce: getProxyAddress(addresses[chain].validatorAnnounce),
},
protocol: 'ethereum',
finalityBlocks: metadata.blocks?.reorgPeriod ?? 1,
connection: {
// not a valid connection but we want to fill in the HTTP type for
// them as a default and leave out the URL
type: AgentConnectionType.Http,
url: undefined,
} as any as AgentConnection,
};
chainConfig.index = {
from: startBlocks[chain],
};
agentConfig.chains[chain] = chainConfig;
}
return agentConfig;
}

@ -0,0 +1,10 @@
export enum BytecodeHash {
MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH = '0x29b7294ab3ad2e8587e5cce0e2289ce65e12a2ea2f1e7ab34a05e7737616f457',
MAILBOX_WITHOUT_LOCAL_DOMAIN_NONZERO_PAUSE_BYTE_CODE_HASH = '0x4e73e34c0982b93eebb4ac4889e9e4e1611f7c24feacf016c3a13e389f146d9c',
TRANSPARENT_PROXY_BYTECODE_HASH = '0x4dde3d0906b6492bf1d4947f667afe8d53c8899f1d8788cabafd082938dceb2d',
MULTISIG_ISM_BYTECODE_HASH = '0x5565704ffa5b10fdf37d57abfddcf137101d5fb418ded21fa6c5f90262c57dc2',
PROXY_ADMIN_BYTECODE_HASH = '0x7c378e9d49408861ca754fe684b9f7d1ea525bddf095ee0463902df701453ba0',
INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xe995bcd732f4861606036357edb2a4d4c3e9b8d7e599fe548790ac1cf26888f8',
OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xd2c5b00ac2d058117491d581d63c3c4fcf6aeb2667c6cc0c7caed359c9eebea1',
OVERHEAD_IGP_BYTECODE_HASH = '0x3cfed1f24f1e9b28a76d5a8c61696a04f7bc474404b823a2fcc210ea52346252',
}

@ -1,22 +1,23 @@
import { types } from '@hyperlane-xyz/utils';
import { ChainMap } from '../../types';
import { HyperlaneAgentAddresses } from '../../agents/types';
import { CoreAddresses } from '../../core/contracts';
import { IgpAddresses } from '../../gas/contracts';
import { ChainMap, ChainName } from '../../types';
import { objMap } from '../../utils/objects';
import mainnet from './mainnet.json';
import test from './test.json';
import testnet from './testnet.json';
export const environments = { test, testnet, mainnet };
export const hyperlaneEnvironments = { test, testnet, mainnet };
export type HyperlaneEnvironment = keyof typeof hyperlaneEnvironments;
export type HyperlaneEnvironmentChain<E extends HyperlaneEnvironment> = Extract<
keyof typeof hyperlaneEnvironments[E],
ChainName
>;
type HyperlaneCoreAddressMap = ChainMap<{
mailbox: types.Address;
multisigIsm: types.Address;
interchainGasPaymaster: types.Address;
interchainAccountRouter?: types.Address;
interchainQueryRouter?: types.Address;
create2Factory: types.Address;
}>;
// TODO: Add middleware addresses
export type HyperlaneContractAddresses = CoreAddresses & IgpAddresses;
// Export developer-relevant addresses
export const hyperlaneCoreAddresses = objMap(
@ -24,9 +25,32 @@ export const hyperlaneCoreAddresses = objMap(
(_chain, addresses) => ({
mailbox: addresses.mailbox.proxy,
multisigIsm: addresses.multisigIsm,
proxyAdmin: addresses.proxyAdmin,
validatorAnnounce: addresses.validatorAnnounce,
}),
) as ChainMap<CoreAddresses>;
export const hyperlaneContractAddresses = objMap(
{ ...testnet, ...mainnet },
(_chain, addresses) => ({
mailbox: addresses.mailbox.proxy,
multisigIsm: addresses.multisigIsm,
proxyAdmin: addresses.proxyAdmin,
validatorAnnounce: addresses.validatorAnnounce,
interchainGasPaymaster: addresses.interchainGasPaymaster.proxy,
storageGasOracle: addresses.storageGasOracle,
defaultIsmInterchainGasPaymaster:
addresses.defaultIsmInterchainGasPaymaster,
//interchainAccountRouter: undefined,
//interchainQueryRouter: undefined,
}),
) as ChainMap<HyperlaneContractAddresses>;
export const hyperlaneAgentAddresses = objMap(
{ ...testnet, ...mainnet },
(_chain, addresses) => ({
mailbox: addresses.mailbox.proxy,
validatorAnnounce: addresses.validatorAnnounce,
interchainGasPaymaster: addresses.interchainGasPaymaster.proxy,
interchainAccountRouter: undefined,
interchainQueryRouter: undefined,
create2Factory: addresses.create2Factory,
}),
) as HyperlaneCoreAddressMap;
) as ChainMap<HyperlaneAgentAddresses>;

@ -16,6 +16,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0x9bDE63104EE030d9De419EEd6bA7D14b86D6fE3f",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -36,6 +37,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0xec48E52D960E54a179f70907bF28b105813877ee",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -56,6 +58,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0xeE80ab5B563cB3825133f29502bA34eD3707cb8C",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -76,6 +79,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0x61A80297e77FC5395bd6Ff60EEacf7CD4f18d4a4",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -96,6 +100,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0x3a579C0bd04FC4C98A8D70EEABD9094e7be4B26D",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -116,6 +121,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0x32B92bd3e5045B67FDD8dbb7A58D25980836d04C",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -136,6 +142,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0xAab1D11E2063Bae5EB01fa946cA8d2FDe3db05D5",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -156,6 +163,7 @@
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0xf3b1F415740A26568C45b1c771A737E31C198F09",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5",
"interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784"
},
@ -175,6 +183,7 @@
},
"defaultIsmInterchainGasPaymaster": "0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc",
"multisigIsm": "0xC343A7054838FE9F249D7E3Ec1Fa6f1D108694b8",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a"
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35"
}
}

@ -16,6 +16,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0x4D06A1671A2a345B14B15cbD50027979A5D1d8C9",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -36,6 +37,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0xD713Db664509bd057aC2b378F4B65Db468F634A5",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -56,6 +58,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0xd71f1A64659beC0781b2aa21bc7a72F7290F6Bf3",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -76,6 +79,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0x34add51924C500b4428067E251168807b3f5faED",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -96,6 +100,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0x32B34F0D86b275b92e9289d9054Db5Ec32d2CC6C",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -116,6 +121,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0xec8875C7cE0a814A56654618D366641859F32C7A",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -136,6 +142,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0x47384E33E67007B7fE4326fb096Bdf9CbA7AB6E4",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -156,6 +163,7 @@
"defaultIsmInterchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1",
"multisigIsm": "0x47384E33E67007B7fE4326fb096Bdf9CbA7AB6E4",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"interchainAccountRouter": "0xc61Bbf8eAb0b748Ecb532A7ffC49Ab7ca6D3a39D",
"interchainQueryRouter": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818"
},
@ -175,6 +183,7 @@
},
"defaultIsmInterchainGasPaymaster": "0xF987d7edcb5890cB321437d8145E3D51131298b6",
"multisigIsm": "0xD3d062a5dcBA85ae863618d4c264d2358300c283",
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE",
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a"
}
}

@ -0,0 +1,173 @@
import { MultisigIsmConfig } from '../core/types';
import { ChainMap } from '../types';
export const defaultMultisigIsmConfigs: ChainMap<MultisigIsmConfig> = {
// ----------------- Mainnets -----------------
celo: {
threshold: 4,
validators: [
'0x1f20274b1210046769d48174c2f0e7c25ca7d5c5', // abacus
'0x3bc014bafa43f93d534aed34f750997cdffcf007', // dsrv
'0xd79d506d741fa735938f7b7847a926e34a6fe6b0', // everstake
'0xe4a258bc61e65914c2a477b2a8a433ab4ebdf44b', // zee prime
'0x6aea63b0be4679c1385c26a92a3ff8aa6a8379f2', // staked
'0xc0085e1a49bcc69e534272adb82c74c0e007e1ca', // zkv
],
},
ethereum: {
threshold: 4,
validators: [
'0x4c327ccb881a7542be77500b2833dc84c839e7b7', // abacus
'0x84cb373148ef9112b277e68acf676fefa9a9a9a0', // dsrv
'0x0d860c2b28bec3af4fd3a5997283e460ff6f2789', // everstake
'0xd4c1211f0eefb97a846c4e6d6589832e52fc03db', // zee prime
'0x600c90404d5c9df885404d2cc5350c9b314ea3a2', // staked
'0x892DC66F5B2f8C438E03f6323394e34A9C24F2D6', // zkv
],
},
avalanche: {
threshold: 4,
validators: [
'0xa7aa52623fe3d78c343008c95894be669e218b8d', // abacus
'0xb6004433fb04f643e2d48ae765c0e7f890f0bc0c', // dsrv
'0xa07e213e0985b21a6128e6c22ab5fb73948b0cc2', // everstake
'0x73853ed9a5f6f2e4c521970a94d43469e3cdaea6', // zee prime
'0xbd2e136cda02ba627ca882e49b184cbe976081c8', // staked
'0x1418126f944a44dad9edbab32294a8c890e7a9e3', // zkv
],
},
polygon: {
threshold: 4,
validators: [
'0x59a001c3451e7f9f3b4759ea215382c1e9aa5fc1', // abacus
'0x009fb042d28944017177920c1d40da02bfebf474', // dsrv
'0xba4b13e23705a5919c1901150d9697e8ffb3ea71', // everstake
'0x2faa4071b718972f9b4beec1d8cbaa4eb6cca6c6', // zee prime
'0x5ae9b0f833dfe09ef455562a1f603f1634504dd6', // staked
'0x6a163d312f7352a95c9b81dca15078d5bf77a442', // zkv
],
},
bsc: {
threshold: 4,
validators: [
'0xcc84b1eb711e5076b2755cf4ad1d2b42c458a45e', // abacus
'0xefe34eae2bca1846b895d2d0762ec21796aa196a', // dsrv
'0x662674e80e189b0861d6835c287693f50ee0c2ff', // everstake
'0x8a0f59075af466841808c529624807656309c9da', // zee prime
'0xdd2ff046ccd748a456b4757a73d47f165469669f', // staked
'0x034c4924c30ec4aa1b7f3ad58548988f0971e1bf', // zkv
],
},
arbitrum: {
threshold: 4,
validators: [
'0xbcb815f38d481a5eba4d7ac4c9e74d9d0fc2a7e7', // abacus
'0xd839424e2e5ace0a81152298dc2b1e3bb3c7fb20', // dsrv
'0xb8085c954b75b7088bcce69e61d12fcef797cd8d', // everstake
'0x9856dcb10fd6e5407fa74b5ab1d3b96cc193e9b7', // zee prime
'0x505dff4e0827aa5065f5e001db888e0569d46490', // staked
'0x25c6779d4610f940bf2488732e10bcffb9d36f81', // ZKV
],
},
optimism: {
threshold: 4,
validators: [
'0x9f2296d5cfc6b5176adc7716c7596898ded13d35', // abacus
'0x9c10bbe8efa03a8f49dfdb5c549258e3a8dca097', // dsrv
'0x62144d4a52a0a0335ea5bb84392ef9912461d9dd', // everstake
'0xaff4718d5d637466ad07441ee3b7c4af8e328dbd', // zee prime
'0xc64d1efeab8ae222bc889fe669f75d21b23005d9', // staked
'0xfa174eb2b4921bb652bc1ada3e8b00e7e280bf3c', // ZKV
],
},
moonbeam: {
threshold: 3,
validators: [
'0x237243d32d10e3bdbbf8dbcccc98ad44c1c172ea', // abacus
'0x9509c8cf0a06955f27342262af501b74874e98fb', // dsrv
'0xb7113c999e4d587b162dd1a28c73f3f51c6bdcdc', // everstake
'0x26725501597d47352a23cd26f122709f69ad53bc', // staked
],
},
gnosis: {
threshold: 2,
validators: [
'0xd0529ec8df08d0d63c0f023786bfa81e4bb51fd6', // abacus
'0x829d6ec129bc7187fb1ed161adcf7939fe0c515f', // abacus
'0x00009f8935e94bfe52ab3441df3526ab7cc38db1', // abacus
],
},
// ----------------- Testnets -----------------
alfajores: {
threshold: 2,
validators: [
'0xe6072396568e73ce6803b12b7e04164e839f1e54',
'0x9f177f51289b22515f41f95872e1511391b8e105',
'0x15f77400845eb1c971ad08de050861d5508cad6c',
],
},
fuji: {
threshold: 2,
validators: [
'0x9fa19ead5ec76e437948b35e227511b106293c40',
'0x227e7d6507762ece0c94678f8c103eff9d682476',
'0x2379e43740e4aa4fde48cf4f00a3106df1d8420d',
],
},
mumbai: {
threshold: 2,
validators: [
'0x0a664ea799447da6b15645cf8b9e82072a68343f',
'0x6ae6f12929a960aba24ba74ea310e3d37d0ac045',
'0x51f70c047cd73bc7873273707501568857a619c4',
],
},
bsctestnet: {
threshold: 2,
validators: [
'0x23338c8714976dd4a57eaeff17cbd26d7e275c08',
'0x85a618d7450ebc37e0d682371f08dac94eec7a76',
'0x95b76562e4ba1791a27ba4236801271c9115b141',
],
},
goerli: {
threshold: 2,
validators: [
'0xf43fbd072fd38e1121d4b3b0b8a35116bbb01ea9',
'0xa33020552a21f35e75bd385c6ab95c3dfa82d930',
'0x0bba4043ff242f8bf3f39bafa8930a84d644d947',
],
},
sepolia: {
threshold: 2,
validators: [
'0xbc748ee311f5f2d1975d61cdf531755ce8ce3066',
'0xc4233b2bfe5aec08964a94b403052abb3eafcf07',
'0x6b36286c19f5c10bdc139ea9ee7f82287303f61d',
],
},
moonbasealpha: {
threshold: 2,
validators: [
'0x890c2aeac157c3f067f3e42b8afc797939c59a32',
'0x1b06d6fe69b972ed7420c83599d5a5c0fc185904',
'0xe70b85206a968a99a597581f0fa09c99e7681093',
],
},
optimismgoerli: {
threshold: 2,
validators: [
'0xbb8d77eefbecc55db6e5a19b0fc3dc290776f189',
'0x69792508b4ddaa3ca52241ccfcd1e0b119a1ee65',
'0x11ddb46c6b653e0cdd7ad5bee32ae316e18f8453',
],
},
arbitrumgoerli: {
threshold: 2,
validators: [
'0xce798fa21e323f6b24d9838a10ffecdefdfc4f30',
'0xa792d39dca4426927e0f00c1618d61c9cb41779d',
'0xdf181fcc11dfac5d01467e4547101a856dd5aa04',
],
},
};

@ -5,7 +5,7 @@ import type { types } from '@hyperlane-xyz/utils';
import { MultiProvider } from './providers/MultiProvider';
import { ProxiedContract, ProxyAddresses, isProxyAddresses } from './proxy';
import { ChainMap, Connection } from './types';
import { objMap } from './utils/objects';
import { isObject, objMap } from './utils/objects';
export type HyperlaneFactories = {
[key: string]: ethers.ContractFactory;
@ -55,14 +55,50 @@ function getFactory(
return factories[key];
}
function isAddress(addressOrObject: any) {
return (
isProxyAddresses(addressOrObject) || typeof addressOrObject === 'string'
);
}
export function filterAddresses(
addressOrObject: HyperlaneAddresses,
contractNames: string[],
max_depth = 5,
): HyperlaneAddresses {
if (max_depth === 0) {
throw new Error('filterAddresses tried to go too deep');
}
const ret: HyperlaneAddresses = {};
for (const key of Object.keys(addressOrObject)) {
if (isAddress(addressOrObject[key]) && contractNames.includes(key)) {
ret[key] = addressOrObject[key];
} else if (isObject(addressOrObject[key])) {
const obj = filterAddresses(
addressOrObject[key] as HyperlaneAddresses,
contractNames,
max_depth - 1,
);
if (Object.keys(obj).length > 0) {
ret[key] = obj;
}
}
}
return ret;
}
export function buildContracts(
addressOrObject: HyperlaneAddresses,
factories: HyperlaneFactories,
filter = true,
max_depth = 5,
): HyperlaneContracts {
if (max_depth === 0) {
throw new Error('buildContracts tried to go too deep');
}
if (filter) {
addressOrObject = filterAddresses(addressOrObject, Object.keys(factories));
}
return objMap(addressOrObject, (key, address: any) => {
if (isProxyAddresses(address)) {
const contract = getFactory(key, factories).attach(address.proxy);
@ -73,6 +109,7 @@ export function buildContracts(
return buildContracts(
address as HyperlaneAddresses,
factories,
false,
max_depth - 1,
);
}

@ -4,24 +4,17 @@ import { Mailbox, Mailbox__factory } from '@hyperlane-xyz/core';
import { types, utils } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { environments } from '../consts/environments';
import { buildContracts } from '../contracts';
import {
HyperlaneEnvironment,
hyperlaneEnvironments,
} from '../consts/environments';
import { HyperlaneAddresses } from '../contracts';
import { MultiProvider } from '../providers/MultiProvider';
import { ConnectionClientConfig } from '../router/types';
import { ChainMap, ChainName } from '../types';
import { objMap, pick } from '../utils/objects';
import { CoreContracts, coreFactories } from './contracts';
export type CoreEnvironment = keyof typeof environments;
export type CoreEnvironmentChain<E extends CoreEnvironment> = Extract<
keyof typeof environments[E],
ChainName
>;
export type CoreContractsMap = {
[chain: ChainName]: CoreContracts;
};
export type CoreContractsMap = ChainMap<CoreContracts>;
export type DispatchedMessage = {
id: string;
@ -34,59 +27,32 @@ export class HyperlaneCore extends HyperlaneApp<CoreContracts> {
super(contractsMap, multiProvider);
}
static fromEnvironment<Env extends CoreEnvironment>(
env: Env,
static fromAddresses(
addresses: ChainMap<HyperlaneAddresses>,
multiProvider: MultiProvider,
): HyperlaneCore {
const envConfig = environments[env];
if (!envConfig) {
throw new Error(`No default env config found for ${env}`);
}
const envChains = Object.keys(envConfig);
const { intersection, multiProvider: intersectionProvider } =
multiProvider.intersect(envChains, true);
const intersectionConfig = pick(envConfig, intersection);
const contractsMap = buildContracts(
intersectionConfig,
const { contracts, intersectionProvider } =
this.buildContracts<CoreContracts>(
addresses,
coreFactories,
) as CoreContractsMap;
return new HyperlaneCore(contractsMap, intersectionProvider);
}
getContracts(chain: ChainName): CoreContracts {
return super.getContracts(chain);
multiProvider,
);
return new HyperlaneCore(contracts, intersectionProvider);
}
getConnectionClientConfig(chain: ChainName): ConnectionClientConfig {
const contracts = this.getContracts(chain);
return {
mailbox: contracts.mailbox.address,
// TODO allow these to be more easily changed
interchainGasPaymaster:
contracts.defaultIsmInterchainGasPaymaster.address,
};
static fromEnvironment<Env extends HyperlaneEnvironment>(
env: Env,
multiProvider: MultiProvider,
): HyperlaneCore {
const envAddresses = hyperlaneEnvironments[env];
if (!envAddresses) {
throw new Error(`No addresses found for ${env}`);
}
getConnectionClientConfigMap(): ChainMap<ConnectionClientConfig> {
return objMap(this.contractsMap, (chain) =>
this.getConnectionClientConfig(chain),
);
return HyperlaneCore.fromAddresses(envAddresses, multiProvider);
}
extendWithConnectionClientConfig<T>(
configMap: ChainMap<T>,
): ChainMap<T & ConnectionClientConfig> {
const connectionClientConfigMap = this.getConnectionClientConfigMap();
return objMap(configMap, (chain, config) => {
return {
...config,
...connectionClientConfigMap[chain],
};
});
getContracts(chain: ChainName): CoreContracts {
return super.getContracts(chain);
}
protected getDestination(message: DispatchedMessage): {

@ -1,8 +1,8 @@
import { BigNumber, utils as ethersUtils } from 'ethers';
import { utils as ethersUtils } from 'ethers';
import { types, utils } from '@hyperlane-xyz/utils';
import { utils } from '@hyperlane-xyz/utils';
import multisigIsmVerifyCosts from '../consts/multisigIsmVerifyCosts.json';
import { BytecodeHash } from '../consts/bytecode';
import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker';
import { ChainName } from '../types';
@ -10,13 +10,7 @@ import { HyperlaneCore } from './HyperlaneCore';
import {
CoreConfig,
CoreViolationType,
DefaultIsmIgpDestinationGasOverheadsViolation,
DefaultIsmIgpViolationType,
EnrolledValidatorsViolation,
GasOracleContractType,
IgpBeneficiaryViolation,
IgpGasOraclesViolation,
IgpViolationType,
MailboxViolation,
MailboxViolationType,
MultisigIsmViolationType,
@ -24,23 +18,6 @@ import {
ValidatorAnnounceViolation,
} from './types';
const MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH =
'0x29b7294ab3ad2e8587e5cce0e2289ce65e12a2ea2f1e7ab34a05e7737616f457';
const MAILBOX_WITHOUT_LOCAL_DOMAIN_NONZERO_PAUSE_BYTE_CODE_HASH =
'0x4e73e34c0982b93eebb4ac4889e9e4e1611f7c24feacf016c3a13e389f146d9c';
const TRANSPARENT_PROXY_BYTECODE_HASH =
'0x4dde3d0906b6492bf1d4947f667afe8d53c8899f1d8788cabafd082938dceb2d';
const MULTISIG_ISM_BYTECODE_HASH =
'0x5565704ffa5b10fdf37d57abfddcf137101d5fb418ded21fa6c5f90262c57dc2';
const PROXY_ADMIN_BYTECODE_HASH =
'0x7c378e9d49408861ca754fe684b9f7d1ea525bddf095ee0463902df701453ba0';
const INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH =
'0xe995bcd732f4861606036357edb2a4d4c3e9b8d7e599fe548790ac1cf26888f8';
const OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH =
'0xd2c5b00ac2d058117491d581d63c3c4fcf6aeb2667c6cc0c7caed359c9eebea1';
const OVERHEAD_IGP_BYTECODE_HASH =
'0x3cfed1f24f1e9b28a76d5a8c61696a04f7bc474404b823a2fcc210ea52346252';
export class HyperlaneCoreChecker extends HyperlaneAppChecker<
HyperlaneCore,
CoreConfig
@ -58,8 +35,6 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
await this.checkMultisigIsm(chain);
await this.checkBytecodes(chain);
await this.checkValidatorAnnounce(chain);
await this.checkDefaultIsmInterchainGasPaymaster(chain);
await this.checkInterchainGasPaymaster(chain);
}
async checkDomainOwnership(chain: ChainName): Promise<void> {
@ -70,8 +45,6 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
contracts.proxyAdmin,
contracts.mailbox.contract,
contracts.multisigIsm,
contracts.interchainGasPaymaster.contract,
contracts.defaultIsmInterchainGasPaymaster,
];
return this.checkOwnership(chain, config.owner, ownables);
}
@ -108,16 +81,16 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
'Mailbox implementation',
contracts.mailbox.addresses.implementation,
[
MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH,
MAILBOX_WITHOUT_LOCAL_DOMAIN_NONZERO_PAUSE_BYTE_CODE_HASH,
BytecodeHash.MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH,
BytecodeHash.MAILBOX_WITHOUT_LOCAL_DOMAIN_NONZERO_PAUSE_BYTE_CODE_HASH,
],
(_) =>
(bytecode) =>
// This is obviously super janky but basically we are searching
// for the ocurrences of localDomain in the bytecode and remove
// that to compare, but some coincidental ocurrences of
// localDomain in the bytecode should be not be removed which
// are just done via an offset guard
_.replaceAll(
bytecode.replaceAll(
ethersUtils.defaultAbiCoder
.encode(['uint32'], [localDomain])
.slice(2),
@ -129,52 +102,19 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
chain,
'Mailbox proxy',
contracts.mailbox.address,
[TRANSPARENT_PROXY_BYTECODE_HASH],
);
await this.checkBytecode(
chain,
'InterchainGasPaymaster proxy',
contracts.interchainGasPaymaster.address,
[TRANSPARENT_PROXY_BYTECODE_HASH],
[BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH],
);
await this.checkBytecode(
chain,
'ProxyAdmin',
contracts.proxyAdmin.address,
[PROXY_ADMIN_BYTECODE_HASH],
[BytecodeHash.PROXY_ADMIN_BYTECODE_HASH],
);
await this.checkBytecode(
chain,
'MultisigIsm implementation',
contracts.multisigIsm.address,
[MULTISIG_ISM_BYTECODE_HASH],
);
await this.checkBytecode(
chain,
'InterchainGasPaymaster implementation',
contracts.interchainGasPaymaster.addresses.implementation,
[
INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH,
OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH,
],
);
await this.checkBytecode(
chain,
'OverheadIGP',
contracts.defaultIsmInterchainGasPaymaster.address,
[OVERHEAD_IGP_BYTECODE_HASH],
(_) =>
// Remove the address of the wrapped ISM from the bytecode
_.replaceAll(
ethersUtils.defaultAbiCoder
.encode(
['address'],
[contracts.interchainGasPaymaster.addresses.proxy],
)
.slice(2),
'',
),
[BytecodeHash.MULTISIG_ISM_BYTECODE_HASH],
);
}
@ -186,20 +126,21 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
contracts.mailbox.addresses,
contracts.proxyAdmin.address,
);
await this.checkProxiedContract(
chain,
'InterchainGasPaymaster',
contracts.interchainGasPaymaster.addresses,
contracts.proxyAdmin.address,
);
}
async checkValidatorAnnounce(chain: ChainName): Promise<void> {
const expectedValidators = this.configMap[chain].multisigIsm.validators;
const expectedValidators = new Set<string>();
const remotes = Object.keys(this.configMap).filter((c) => c !== chain);
remotes.forEach((remote) =>
this.configMap[remote].multisigIsm[chain].validators.forEach(
expectedValidators.add,
expectedValidators,
),
);
const validatorAnnounce = this.app.getContracts(chain).validatorAnnounce;
const announcedValidators =
await validatorAnnounce.getAnnouncedValidators();
expectedValidators.map((validator) => {
[...expectedValidators].forEach((validator) => {
const matches = announcedValidators.filter((x) =>
utils.eqAddress(x, validator),
);
@ -230,10 +171,10 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
): Promise<void> {
const coreContracts = this.app.getContracts(local);
const multisigIsm = coreContracts.multisigIsm;
const config = this.configMap[remote];
const config = this.configMap[local];
const remoteDomain = this.multiProvider.getDomainId(remote);
const multisigIsmConfig = config.multisigIsm;
const multisigIsmConfig = config.multisigIsm[remote];
const expectedValidators = multisigIsmConfig.validators;
const actualValidators = await multisigIsm.validators(remoteDomain);
@ -275,126 +216,4 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
this.addViolation(violation);
}
}
async checkDefaultIsmInterchainGasPaymaster(local: ChainName): Promise<void> {
const coreContracts = this.app.getContracts(local);
const defaultIsmIgp = coreContracts.defaultIsmInterchainGasPaymaster;
// Construct the violation, updating the actual & expected
// objects as violations are found.
// A single violation is used so that only a single `setDestinationGasOverheads`
// call is generated to set multiple gas overheads.
const gasOverheadViolation: DefaultIsmIgpDestinationGasOverheadsViolation =
{
type: CoreViolationType.DefaultIsmInterchainGasPaymaster,
subType: DefaultIsmIgpViolationType.DestinationGasOverheads,
contract: defaultIsmIgp,
chain: local,
actual: {},
expected: {},
};
const remotes = this.app.remoteChains(local);
for (const remote of remotes) {
const { validators, threshold } = this.configMap[remote].multisigIsm;
const expectedOverhead = this.getExpectedOverheadGas(
threshold,
validators.length,
);
const remoteId = this.multiProvider.getDomainId(remote);
const existingOverhead = await defaultIsmIgp.destinationGasOverhead(
remoteId,
);
if (!expectedOverhead.eq(existingOverhead)) {
const remoteChain = remote as ChainName;
gasOverheadViolation.actual[remoteChain] = existingOverhead;
gasOverheadViolation.expected[remoteChain] = expectedOverhead;
}
}
if (Object.keys(gasOverheadViolation.actual).length > 0) {
this.addViolation(gasOverheadViolation);
}
}
async checkInterchainGasPaymaster(local: ChainName): Promise<void> {
const coreContracts = this.app.getContracts(local);
const igp = coreContracts.interchainGasPaymaster.contract;
// Construct the violation, updating the actual & expected
// objects as violations are found.
// A single violation is used so that only a single `setGasOracles`
// call is generated to set multiple gas oracles.
const gasOraclesViolation: IgpGasOraclesViolation = {
type: CoreViolationType.InterchainGasPaymaster,
subType: IgpViolationType.GasOracles,
contract: igp,
chain: local,
actual: {},
expected: {},
};
const remotes = this.app.remoteChains(local);
for (const remote of remotes) {
const remoteId = this.multiProvider.getDomainId(remote);
const actualGasOracle = await igp.gasOracles(remoteId);
const expectedGasOracle = this.getGasOracleAddress(local, remote);
if (!utils.eqAddress(actualGasOracle, expectedGasOracle)) {
const remoteChain = remote as ChainName;
gasOraclesViolation.actual[remoteChain] = actualGasOracle;
gasOraclesViolation.expected[remoteChain] = expectedGasOracle;
}
}
// Add the violation only if it's been populated with gas oracle inconsistencies
if (Object.keys(gasOraclesViolation.actual).length > 0) {
this.addViolation(gasOraclesViolation);
}
const actualBeneficiary = await igp.beneficiary();
const expectedBeneficiary = this.configMap[local].igp.beneficiary;
if (!utils.eqAddress(actualBeneficiary, expectedBeneficiary)) {
const violation: IgpBeneficiaryViolation = {
type: CoreViolationType.InterchainGasPaymaster,
subType: IgpViolationType.Beneficiary,
contract: igp,
chain: local,
actual: actualBeneficiary,
expected: expectedBeneficiary,
};
this.addViolation(violation);
}
}
getGasOracleAddress(local: ChainName, remote: ChainName): types.Address {
const config = this.configMap[local];
const gasOracleType = config.igp.gasOracles[remote];
if (!gasOracleType) {
throw Error(
`Expected gas oracle type for local ${local} and remote ${remote}`,
);
}
const coreContracts = this.app.getContracts(local);
switch (gasOracleType) {
case GasOracleContractType.StorageGasOracle:
return coreContracts.storageGasOracle.address;
default:
throw Error(`Unsupported gas oracle type ${gasOracleType}`);
}
}
private getExpectedOverheadGas(
threshold: number,
validatorSetCount: number,
): BigNumber {
const expectedOverhead: number | undefined =
// @ts-ignore
multisigIsmVerifyCosts[`${validatorSetCount}`][`${threshold}`];
if (!expectedOverhead)
throw new Error(
`Unknown verification cost for ${threshold} of ${validatorSetCount}`,
);
return BigNumber.from(expectedOverhead);
}
}

@ -2,27 +2,22 @@ import debug from 'debug';
import { ethers } from 'ethers';
import {
InterchainGasPaymaster,
LegacyMultisigIsm,
Mailbox,
OverheadIgp,
Ownable,
Ownable__factory,
ProxyAdmin,
StorageGasOracle,
ValidatorAnnounce,
} from '@hyperlane-xyz/core';
import { types, utils } from '@hyperlane-xyz/utils';
import { types } from '@hyperlane-xyz/utils';
import multisigIsmVerifyCosts from '../consts/multisigIsmVerifyCosts.json';
import { DeployOptions, HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { ProxiedContract, TransparentProxyAddresses } from '../proxy';
import { ChainMap, ChainName } from '../types';
import { objMap } from '../utils/objects';
import { CoreContracts, GasOracleContracts, coreFactories } from './contracts';
import { CoreConfig, GasOracleContractType } from './types';
import { CoreContracts, coreFactories } from './contracts';
import { CoreConfig } from './types';
export class HyperlaneCoreDeployer extends HyperlaneDeployer<
CoreConfig,
@ -30,7 +25,6 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
typeof coreFactories
> {
startingBlockNumbers: ChainMap<number | undefined>;
gasOverhead: ChainMap<OverheadIgp.DomainConfigStruct>;
constructor(
multiProvider: MultiProvider,
@ -40,154 +34,9 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
super(multiProvider, configMap, factoriesOverride, {
logger: debug('hyperlane:CoreDeployer'),
});
this.gasOverhead = objMap(configMap, (chain, config) => {
const { validators, threshold } = config.multisigIsm;
const verifyCost =
// @ts-ignore
multisigIsmVerifyCosts[`${validators.length}`][`${threshold}`];
if (!verifyCost)
throw new Error(
`Unknown verification cost for ${threshold} of ${validators.length}`,
);
return {
domain: multiProvider.getDomainId(chain),
gasOverhead: verifyCost,
};
});
this.startingBlockNumbers = objMap(configMap, () => undefined);
}
async deployInterchainGasPaymaster(
chain: ChainName,
proxyAdmin: ProxyAdmin,
gasOracleContracts: GasOracleContracts,
deployOpts?: DeployOptions,
): Promise<
ProxiedContract<InterchainGasPaymaster, TransparentProxyAddresses>
> {
const owner = this.configMap[chain].owner;
const beneficiary = this.configMap[chain].igp.beneficiary;
const igp = await this.deployProxiedContract(
chain,
'interchainGasPaymaster',
[beneficiary],
proxyAdmin,
[owner, beneficiary],
deployOpts,
);
// Set the gas oracles
const configChains = Object.keys(this.configMap);
const remotes = this.multiProvider
.intersect(configChains, false)
.multiProvider.getRemoteChains(chain);
const gasOracleConfigsToSet: InterchainGasPaymaster.GasOracleConfigStruct[] =
[];
for (const remote of remotes) {
const remoteId = this.multiProvider.getDomainId(remote);
const currentGasOracle = await igp.contract.gasOracles(remoteId);
const desiredGasOracle = this.getGasOracleAddress(
chain,
remote,
gasOracleContracts,
);
if (!utils.eqAddress(currentGasOracle, desiredGasOracle)) {
gasOracleConfigsToSet.push({
remoteDomain: remoteId,
gasOracle: desiredGasOracle,
});
}
}
if (gasOracleConfigsToSet.length > 0) {
await this.runIfOwner(chain, igp.contract, async () =>
this.multiProvider.handleTx(
chain,
igp.contract.setGasOracles(gasOracleConfigsToSet),
),
);
}
return igp;
}
async deployDefaultIsmInterchainGasPaymaster(
chain: ChainName,
interchainGasPaymasterAddress: types.Address,
deployOpts?: DeployOptions,
): Promise<OverheadIgp> {
const deployer = await this.multiProvider.getSignerAddress(chain);
// Transfer ownership to the deployer so the destination gas overheads can be set
const initCalldata = Ownable__factory.createInterface().encodeFunctionData(
'transferOwnership',
[deployer],
);
const defaultIsmInterchainGasPaymaster = await this.deployContract(
chain,
'defaultIsmInterchainGasPaymaster',
[interchainGasPaymasterAddress],
{
...deployOpts,
initCalldata,
},
);
const configChains = Object.keys(this.configMap);
const remotes = this.multiProvider
.intersect(configChains, false)
.multiProvider.getRemoteChains(chain);
// Only set gas overhead configs if they differ from what's on chain
const configs: OverheadIgp.DomainConfigStruct[] = [];
for (const remote of remotes) {
const gasOverhead = this.gasOverhead[remote];
const existingOverhead =
await defaultIsmInterchainGasPaymaster.destinationGasOverhead(
gasOverhead.domain,
);
if (!existingOverhead.eq(gasOverhead.gasOverhead)) {
configs.push(gasOverhead);
}
}
if (configs.length > 0) {
await this.runIfOwner(chain, defaultIsmInterchainGasPaymaster, () =>
this.multiProvider.handleTx(
chain,
defaultIsmInterchainGasPaymaster.setDestinationGasOverheads(
configs,
this.multiProvider.getTransactionOverrides(chain),
),
),
);
}
return defaultIsmInterchainGasPaymaster;
}
async deployGasOracleContracts(
chain: ChainName,
deployOpts?: DeployOptions,
): Promise<GasOracleContracts> {
const storageGasOracle = await this.deployStorageGasOracle(
chain,
deployOpts,
);
return {
storageGasOracle,
};
}
async deployStorageGasOracle(
chain: ChainName,
deployOpts?: DeployOptions,
): Promise<StorageGasOracle> {
return this.deployContract(chain, 'storageGasOracle', [], deployOpts);
}
async deployMailbox(
chain: ChainName,
defaultIsmAddress: types.Address,
@ -224,10 +73,7 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
async deployLegacyMultisigIsm(chain: ChainName): Promise<LegacyMultisigIsm> {
const multisigIsm = await this.deployContract(chain, 'multisigIsm', []);
const configChains = Object.keys(this.configMap);
const remotes = this.multiProvider
.intersect(configChains, false)
.multiProvider.getRemoteChains(chain);
const remotes = Object.keys(this.configMap[chain].multisigIsm);
const overrides = this.multiProvider.getTransactionOverrides(chain);
await super.runIfOwner(chain, multisigIsm, async () => {
@ -237,7 +83,7 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
remoteDomains.map((id) => multisigIsm.validators(id)),
);
const expectedValidators = remotes.map(
(chain) => this.configMap[chain].multisigIsm.validators,
(remote) => this.configMap[chain].multisigIsm[remote].validators,
);
const validatorsToEnroll = expectedValidators.map((validators, i) =>
validators.filter(
@ -248,12 +94,10 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
const chainsToEnrollValidators = remotes.filter(
(_, i) => validatorsToEnroll[i].length > 0,
);
if (chainsToEnrollValidators.length > 0) {
this.logger(
`Enroll ${chainsToEnrollValidators} validators on ${chain}`,
);
await this.multiProvider.handleTx(
chain,
multisigIsm.enrollValidators(
@ -265,12 +109,11 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
),
);
}
const actualThresholds = await Promise.all(
remoteDomains.map((id) => multisigIsm.threshold(id)),
);
const expectedThresholds = remotes.map(
(chain) => this.configMap[chain].multisigIsm.threshold,
(remote) => this.configMap[chain].multisigIsm[remote].threshold,
);
const chainsToSetThreshold = remotes.filter(
(_, i) => actualThresholds[i] !== expectedThresholds[i],
@ -284,14 +127,13 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
multisigIsm.setThresholds(
chainsToSetThreshold.map((c) => this.multiProvider.getDomainId(c)),
chainsToSetThreshold.map(
(c) => this.configMap[c].multisigIsm.threshold,
(remote) => this.configMap[chain].multisigIsm[remote].threshold,
),
overrides,
),
);
}
});
return multisigIsm;
}
@ -311,17 +153,6 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
const proxyAdmin = await this.deployContract(chain, 'proxyAdmin', []);
const gasOracleContracts = await this.deployGasOracleContracts(chain);
const interchainGasPaymaster = await this.deployInterchainGasPaymaster(
chain,
proxyAdmin,
gasOracleContracts,
);
const defaultIsmInterchainGasPaymaster =
await this.deployDefaultIsmInterchainGasPaymaster(
chain,
interchainGasPaymaster.address,
);
const mailbox = await this.deployMailbox(
chain,
multisigIsm.address,
@ -332,68 +163,14 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
mailbox.address,
);
// Ownership of the Mailbox and the interchainGasPaymaster is transferred upon initialization.
const ownables: Ownable[] = [
multisigIsm,
proxyAdmin,
defaultIsmInterchainGasPaymaster,
];
await this.transferOwnershipOfContracts(chain, ownables);
const ownables: Ownable[] = [multisigIsm, proxyAdmin];
await this.transferOwnershipOfContracts(chain, config.owner, ownables);
return {
...gasOracleContracts,
validatorAnnounce,
proxyAdmin,
mailbox,
interchainGasPaymaster,
defaultIsmInterchainGasPaymaster,
multisigIsm,
};
}
async transferOwnershipOfContracts(
chain: ChainName,
ownables: Ownable[],
): Promise<ethers.ContractReceipt[]> {
const owner = this.configMap[chain].owner;
const receipts: ethers.ContractReceipt[] = [];
for (const ownable of ownables) {
const currentOwner = await ownable.owner();
if (currentOwner.toLowerCase() !== owner.toLowerCase()) {
const receipt = await super.runIfOwner(chain, ownable, () =>
this.multiProvider.handleTx(
chain,
ownable.transferOwnership(
owner,
this.multiProvider.getTransactionOverrides(chain),
),
),
);
if (receipt) receipts.push(receipt);
}
}
return receipts.filter((x) => x !== undefined) as ethers.ContractReceipt[];
}
private getGasOracleAddress(
local: ChainName,
remote: ChainName,
gasOracleContracts: GasOracleContracts,
): types.Address {
const localConfig = this.configMap[local];
const gasOracleType = localConfig.igp.gasOracles[remote];
if (!gasOracleType) {
throw Error(
`Expected gas oracle type for local ${local} and remote ${remote}`,
);
}
switch (gasOracleType) {
case GasOracleContractType.StorageGasOracle: {
return gasOracleContracts.storageGasOracle.address;
}
default: {
throw Error(`Unsupported gas oracle type ${gasOracleType}`);
}
}
}
}

@ -7,32 +7,15 @@ import {
TestMailbox__factory,
} from '@hyperlane-xyz/core';
import { TestChains } from '../consts/chains';
import { MultiProvider } from '../providers/MultiProvider';
import { testCoreConfig } from '../test/testUtils';
import { ChainMap, ChainName } from '../types';
import { HyperlaneCoreDeployer } from './HyperlaneCoreDeployer';
import { TestCoreApp } from './TestCoreApp';
import { coreFactories } from './contracts';
import { CoreConfig, GasOracleContractType } from './types';
const nonZeroAddress = ethers.constants.AddressZero.replace('00', '01');
// dummy config as TestInbox and TestOutbox do not use deployed ISM
const testConfig: CoreConfig = {
owner: nonZeroAddress,
multisigIsm: {
validators: [nonZeroAddress],
threshold: 1,
},
igp: {
beneficiary: nonZeroAddress,
gasOracles: {
test1: GasOracleContractType.StorageGasOracle,
test2: GasOracleContractType.StorageGasOracle,
test3: GasOracleContractType.StorageGasOracle,
},
},
};
import { CoreConfig } from './types';
const testCoreFactories = {
...coreFactories,
@ -47,11 +30,7 @@ export class TestCoreDeployer extends HyperlaneCoreDeployer {
configMap?: ChainMap<CoreConfig>,
) {
// Note that the multisig module configs are unused.
const configs = configMap ?? {
test1: testConfig,
test2: testConfig,
test3: testConfig,
};
const configs = configMap ?? testCoreConfig(TestChains);
super(multiProvider, configs, testCoreFactories);
}

@ -1,39 +1,28 @@
import {
Create2Factory__factory,
InterchainAccountRouter__factory,
InterchainGasPaymaster,
InterchainGasPaymaster__factory,
InterchainQueryRouter__factory,
LegacyMultisigIsm,
LegacyMultisigIsm__factory,
Mailbox,
Mailbox__factory,
OverheadIgp,
OverheadIgp__factory,
ProxyAdmin,
ProxyAdmin__factory,
StorageGasOracle,
StorageGasOracle__factory,
ValidatorAnnounce,
ValidatorAnnounce__factory,
} from '@hyperlane-xyz/core';
import { types } from '@hyperlane-xyz/utils';
import { ProxiedContract, TransparentProxyAddresses } from '../proxy';
export type GasOracleContracts = {
storageGasOracle: StorageGasOracle;
export type CoreAddresses = {
mailbox: types.Address | TransparentProxyAddresses;
multisigIsm: types.Address;
proxyAdmin: types.Address;
validatorAnnounce: types.Address;
};
export type ConnectionClientContracts = {
interchainGasPaymaster: ProxiedContract<
InterchainGasPaymaster,
TransparentProxyAddresses
>;
defaultIsmInterchainGasPaymaster: OverheadIgp;
};
export type CoreContracts = GasOracleContracts &
ConnectionClientContracts & {
export type CoreContracts = {
mailbox: ProxiedContract<Mailbox, TransparentProxyAddresses>;
multisigIsm: LegacyMultisigIsm;
proxyAdmin: ProxyAdmin;
@ -46,9 +35,6 @@ export const coreFactories = {
validatorAnnounce: new ValidatorAnnounce__factory(),
create2Factory: new Create2Factory__factory(),
proxyAdmin: new ProxyAdmin__factory(),
interchainGasPaymaster: new InterchainGasPaymaster__factory(),
defaultIsmInterchainGasPaymaster: new OverheadIgp__factory(),
storageGasOracle: new StorageGasOracle__factory(),
multisigIsm: new LegacyMultisigIsm__factory(),
mailbox: new Mailbox__factory(),
};

@ -1,34 +1,17 @@
import { BigNumber } from 'ethers';
import {
InterchainGasPaymaster,
LegacyMultisigIsm,
Mailbox,
OverheadIgp,
} from '@hyperlane-xyz/core';
import { LegacyMultisigIsm, Mailbox } from '@hyperlane-xyz/core';
import type { types } from '@hyperlane-xyz/utils';
import type { CheckerViolation } from '../deploy/types';
import { ChainMap, ChainName } from '../types';
export enum GasOracleContractType {
StorageGasOracle = 'StorageGasOracle',
}
export type InterchainGasPaymasterConfig = {
beneficiary: types.Address;
gasOracles: ChainMap<GasOracleContractType>;
};
export type MultisigIsmConfig = {
validators: Array<types.Address>;
threshold: number;
};
export type CoreConfig = {
multisigIsm: MultisigIsmConfig;
multisigIsm: ChainMap<MultisigIsmConfig>;
owner: types.Address;
igp: InterchainGasPaymasterConfig;
remove?: boolean;
};
@ -37,8 +20,6 @@ export enum CoreViolationType {
Mailbox = 'Mailbox',
ConnectionManager = 'ConnectionManager',
ValidatorAnnounce = 'ValidatorAnnounce',
InterchainGasPaymaster = 'InterchainGasPaymaster',
DefaultIsmInterchainGasPaymaster = 'DefaultIsmInterchainGasPaymaster',
}
export enum MultisigIsmViolationType {
@ -50,15 +31,6 @@ export enum MailboxViolationType {
DefaultIsm = 'DefaultIsm',
}
export enum DefaultIsmIgpViolationType {
DestinationGasOverheads = 'DestinationGasOverheads',
}
export enum IgpViolationType {
Beneficiary = 'Beneficiary',
GasOracles = 'GasOracles',
}
export interface MailboxViolation extends CheckerViolation {
type: CoreViolationType.Mailbox;
contract: Mailbox;
@ -96,34 +68,3 @@ export interface ValidatorAnnounceViolation extends CheckerViolation {
actual: boolean;
expected: boolean;
}
export interface IgpViolation extends CheckerViolation {
type: CoreViolationType.InterchainGasPaymaster;
contract: InterchainGasPaymaster;
subType: IgpViolationType;
}
export interface IgpBeneficiaryViolation extends IgpViolation {
subType: IgpViolationType.Beneficiary;
actual: types.Address;
expected: types.Address;
}
export interface IgpGasOraclesViolation extends IgpViolation {
subType: IgpViolationType.GasOracles;
actual: ChainMap<types.Address>;
expected: ChainMap<types.Address>;
}
export interface DefaultIsmIgpViolation extends CheckerViolation {
type: CoreViolationType.DefaultIsmInterchainGasPaymaster;
contract: OverheadIgp;
subType: DefaultIsmIgpViolationType;
}
export interface DefaultIsmIgpDestinationGasOverheadsViolation
extends DefaultIsmIgpViolation {
subType: DefaultIsmIgpViolationType.DestinationGasOverheads;
actual: ChainMap<BigNumber>;
expected: ChainMap<BigNumber>;
}

@ -10,7 +10,7 @@ import {
TransparentUpgradeableProxy,
TransparentUpgradeableProxy__factory,
} from '@hyperlane-xyz/core';
import { types } from '@hyperlane-xyz/utils';
import { types, utils } from '@hyperlane-xyz/utils';
import {
HyperlaneContract,
@ -510,4 +510,29 @@ export abstract class HyperlaneDeployer<
}
return ret;
}
protected async transferOwnershipOfContracts(
chain: ChainName,
owner: types.Address,
ownables: Ownable[],
): Promise<ethers.ContractReceipt[]> {
const receipts: ethers.ContractReceipt[] = [];
for (const ownable of ownables) {
const currentOwner = await ownable.owner();
if (!utils.eqAddress(currentOwner, owner)) {
const receipt = await this.runIfOwner(chain, ownable, () =>
this.multiProvider.handleTx(
chain,
ownable.transferOwnership(
owner,
this.multiProvider.getTransactionOverrides(chain),
),
),
);
if (receipt) receipts.push(receipt);
}
}
return receipts.filter((x) => !!x) as ethers.ContractReceipt[];
}
}

@ -1,15 +0,0 @@
import { types } from '@hyperlane-xyz/utils';
import { ChainMap } from '../types';
import { objMap } from '../utils/objects';
export function getChainToOwnerMap(
configMap: ChainMap<any>,
owner: types.Address,
): ChainMap<{ owner: string }> {
return objMap(configMap, () => {
return {
owner,
};
});
}

@ -0,0 +1,129 @@
import { BigNumber } from 'ethers';
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core';
import { types } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import {
HyperlaneEnvironment,
hyperlaneEnvironments,
} from '../consts/environments';
import { HyperlaneAddresses } from '../contracts';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName } from '../types';
import { IgpContracts, igpFactories } from './contracts';
export type IgpContractsMap = ChainMap<IgpContracts>;
export class HyperlaneIgp extends HyperlaneApp<IgpContracts> {
constructor(contractsMap: IgpContractsMap, multiProvider: MultiProvider) {
super(contractsMap, multiProvider);
}
static fromAddresses(
addresses: ChainMap<HyperlaneAddresses>,
multiProvider: MultiProvider,
): HyperlaneIgp {
const { contracts, intersectionProvider } =
this.buildContracts<IgpContracts>(addresses, igpFactories, multiProvider);
return new HyperlaneIgp(contracts, intersectionProvider);
}
static fromEnvironment<Env extends HyperlaneEnvironment>(
env: Env,
multiProvider: MultiProvider,
): HyperlaneIgp {
const envAddresses = hyperlaneEnvironments[env];
if (!envAddresses) {
throw new Error(`No addresses found for ${env}`);
}
return HyperlaneIgp.fromAddresses(envAddresses, multiProvider);
}
getContracts(chain: ChainName): IgpContracts {
return super.getContracts(chain);
}
/**
* Calls the default ISM IGP's `quoteGasPayment` function to get the amount of native tokens
* required to pay for interchain gas.
* The default ISM IGP will add any gas overhead amounts related to the Mailbox
* and default ISM on the destination to the provided gasAmount.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`.
* The default IGP is expected to add any gas overhead related to the Mailbox
* or ISM, so this gas amount is only required to cover the usage of the `handle`
* function.
* @returns The amount of native tokens required to pay for interchain gas.
*/
quoteGasPayment(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
): Promise<BigNumber> {
const igp = this.getContracts(origin).interchainGasPaymaster;
return this.quoteGasPaymentForIgp(
origin,
destination,
gasAmount,
igp.address,
);
}
/**
* Calls the default ISM IGP's `quoteGasPayment` function to get the amount of native tokens
* required to pay for interchain gas.
* The default ISM IGP will add any gas overhead amounts related to the Mailbox
* and default ISM on the destination to the provided gasAmount.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`.
* The default IGP is expected to add any gas overhead related to the Mailbox
* or ISM, so this gas amount is only required to cover the usage of the `handle`
* function.
* @returns The amount of native tokens required to pay for interchain gas.
*/
quoteGasPaymentForDefaultIsmIgp(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
): Promise<BigNumber> {
const igp = this.getContracts(origin).defaultIsmInterchainGasPaymaster;
return this.quoteGasPaymentForIgp(
origin,
destination,
gasAmount,
igp.address,
);
}
/**
* Calls the origin's default IGP's `quoteGasPayment` function to get the
* amount of native tokens required to pay for interchain gas.
* The default IGP is expected to add any gas overhead related to the Mailbox
* and ISM to the provided gasAmount.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`.
* The default IGP is expected to add any gas overhead related to the Mailbox
* or ISM, so this gas amount is only required to cover the usage of the `handle`
* function.
* @returns The amount of native tokens required to pay for interchain gas.
*/
protected quoteGasPaymentForIgp(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
interchainGasPaymasterAddress: types.Address,
): Promise<BigNumber> {
const originProvider = this.multiProvider.getProvider(origin);
const igp = InterchainGasPaymaster__factory.connect(
interchainGasPaymasterAddress,
originProvider,
);
const domainId = this.multiProvider.getDomainId(destination);
return igp.quoteGasPayment(domainId, gasAmount);
}
}

@ -0,0 +1,194 @@
import { BigNumber, utils as ethersUtils } from 'ethers';
import { types, utils } from '@hyperlane-xyz/utils';
import { BytecodeHash } from '../consts/bytecode';
import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker';
import { ChainName } from '../types';
import { HyperlaneIgp } from './HyperlaneIgp';
import {
GasOracleContractType,
IgpBeneficiaryViolation,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolationType,
OverheadIgpConfig,
} from './types';
export class HyperlaneIgpChecker extends HyperlaneAppChecker<
HyperlaneIgp,
OverheadIgpConfig
> {
async checkChain(chain: ChainName): Promise<void> {
await this.checkDomainOwnership(chain);
await this.checkProxiedContracts(chain);
await this.checkBytecodes(chain);
await this.checkOverheadInterchainGasPaymaster(chain);
await this.checkInterchainGasPaymaster(chain);
}
async checkDomainOwnership(chain: ChainName): Promise<void> {
const config = this.configMap[chain];
if (config.owner) {
const contracts = this.app.getContracts(chain);
const ownables = [
contracts.proxyAdmin,
contracts.interchainGasPaymaster.contract,
contracts.defaultIsmInterchainGasPaymaster,
];
return this.checkOwnership(chain, config.owner, ownables);
}
}
async checkBytecodes(chain: ChainName): Promise<void> {
const contracts = this.app.getContracts(chain);
await this.checkBytecode(
chain,
'InterchainGasPaymaster proxy',
contracts.interchainGasPaymaster.address,
[BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH],
);
await this.checkBytecode(
chain,
'InterchainGasPaymaster implementation',
contracts.interchainGasPaymaster.addresses.implementation,
[
BytecodeHash.INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH,
BytecodeHash.OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH,
],
);
await this.checkBytecode(
chain,
'OverheadIGP',
contracts.defaultIsmInterchainGasPaymaster.address,
[BytecodeHash.OVERHEAD_IGP_BYTECODE_HASH],
(bytecode) =>
// Remove the address of the wrapped IGP from the bytecode
bytecode.replaceAll(
ethersUtils.defaultAbiCoder
.encode(
['address'],
[contracts.interchainGasPaymaster.addresses.proxy],
)
.slice(2),
'',
),
);
}
async checkProxiedContracts(chain: ChainName): Promise<void> {
const contracts = this.app.getContracts(chain);
await this.checkProxiedContract(
chain,
'InterchainGasPaymaster',
contracts.interchainGasPaymaster.addresses,
contracts.proxyAdmin.address,
);
}
async checkOverheadInterchainGasPaymaster(local: ChainName): Promise<void> {
const coreContracts = this.app.getContracts(local);
const defaultIsmIgp = coreContracts.defaultIsmInterchainGasPaymaster;
// Construct the violation, updating the actual & expected
// objects as violations are found.
// A single violation is used so that only a single `setDestinationGasOverheads`
// call is generated to set multiple gas overheads.
const overheadViolation: IgpOverheadViolation = {
type: 'InterchainGasPaymaster',
subType: IgpViolationType.Overhead,
contract: defaultIsmIgp,
chain: local,
actual: {},
expected: {},
};
const remotes = this.app.remoteChains(local);
for (const remote of remotes) {
const expectedOverhead = this.configMap[local].overhead[remote];
const remoteId = this.multiProvider.getDomainId(remote);
const existingOverhead = await defaultIsmIgp.destinationGasOverhead(
remoteId,
);
if (!existingOverhead.eq(expectedOverhead)) {
const remoteChain = remote as ChainName;
overheadViolation.actual[remoteChain] = existingOverhead;
overheadViolation.expected[remoteChain] =
BigNumber.from(expectedOverhead);
}
}
if (Object.keys(overheadViolation.actual).length > 0) {
this.addViolation(overheadViolation);
}
}
async checkInterchainGasPaymaster(local: ChainName): Promise<void> {
const coreContracts = this.app.getContracts(local);
const igp = coreContracts.interchainGasPaymaster.contract;
// Construct the violation, updating the actual & expected
// objects as violations are found.
// A single violation is used so that only a single `setGasOracles`
// call is generated to set multiple gas oracles.
const gasOraclesViolation: IgpGasOraclesViolation = {
type: 'InterchainGasPaymaster',
subType: IgpViolationType.GasOracles,
contract: igp,
chain: local,
actual: {},
expected: {},
};
const remotes = this.app.remoteChains(local);
for (const remote of remotes) {
const remoteId = this.multiProvider.getDomainId(remote);
const actualGasOracle = await igp.gasOracles(remoteId);
const expectedGasOracle = this.getGasOracleAddress(local, remote);
if (!utils.eqAddress(actualGasOracle, expectedGasOracle)) {
const remoteChain = remote as ChainName;
gasOraclesViolation.actual[remoteChain] = actualGasOracle;
gasOraclesViolation.expected[remoteChain] = expectedGasOracle;
}
}
// Add the violation only if it's been populated with gas oracle inconsistencies
if (Object.keys(gasOraclesViolation.actual).length > 0) {
this.addViolation(gasOraclesViolation);
}
const actualBeneficiary = await igp.beneficiary();
const expectedBeneficiary = this.configMap[local].beneficiary;
if (!utils.eqAddress(actualBeneficiary, expectedBeneficiary)) {
const violation: IgpBeneficiaryViolation = {
type: 'InterchainGasPaymaster',
subType: IgpViolationType.Beneficiary,
contract: igp,
chain: local,
actual: actualBeneficiary,
expected: expectedBeneficiary,
};
this.addViolation(violation);
}
}
getGasOracleAddress(local: ChainName, remote: ChainName): types.Address {
const config = this.configMap[local];
const gasOracleType = config.gasOracleType[remote];
if (!gasOracleType) {
throw Error(
`Expected gas oracle type for local ${local} and remote ${remote}`,
);
}
const coreContracts = this.app.getContracts(local);
switch (gasOracleType) {
case GasOracleContractType.StorageGasOracle:
return coreContracts.storageGasOracle.address;
default:
throw Error(`Unsupported gas oracle type ${gasOracleType}`);
}
}
}

@ -0,0 +1,184 @@
import debug from 'debug';
import {
InterchainGasPaymaster,
OverheadIgp,
Ownable,
Ownable__factory,
ProxyAdmin,
StorageGasOracle,
} from '@hyperlane-xyz/core';
import { types, utils } from '@hyperlane-xyz/utils';
import { DeployOptions, HyperlaneDeployer } from '../deploy/HyperlaneDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { ProxiedContract, TransparentProxyAddresses } from '../proxy';
import { ChainMap, ChainName } from '../types';
import { objMap } from '../utils/objects';
import { IgpContracts, igpFactories } from './contracts';
import { OverheadIgpConfig } from './types';
export class HyperlaneIgpDeployer extends HyperlaneDeployer<
OverheadIgpConfig,
IgpContracts,
typeof igpFactories
> {
startingBlockNumbers: ChainMap<number | undefined>;
constructor(
multiProvider: MultiProvider,
configMap: ChainMap<OverheadIgpConfig>,
factoriesOverride = igpFactories,
) {
super(multiProvider, configMap, factoriesOverride, {
logger: debug('hyperlane:IgpDeployer'),
});
this.startingBlockNumbers = objMap(configMap, () => undefined);
}
async deployInterchainGasPaymaster(
chain: ChainName,
proxyAdmin: ProxyAdmin,
storageGasOracle: StorageGasOracle,
deployOpts?: DeployOptions,
): Promise<
ProxiedContract<InterchainGasPaymaster, TransparentProxyAddresses>
> {
const owner = this.configMap[chain].owner;
const beneficiary = this.configMap[chain].beneficiary;
const igp = await this.deployProxiedContract(
chain,
'interchainGasPaymaster',
[beneficiary],
proxyAdmin,
[owner, beneficiary],
deployOpts,
);
// Set the gas oracles
const configChains = Object.keys(this.configMap);
const remotes = this.multiProvider
.intersect(configChains, false)
.multiProvider.getRemoteChains(chain);
const gasOracleConfigsToSet: InterchainGasPaymaster.GasOracleConfigStruct[] =
[];
for (const remote of remotes) {
const remoteId = this.multiProvider.getDomainId(remote);
const currentGasOracle = await igp.contract.gasOracles(remoteId);
if (!utils.eqAddress(currentGasOracle, storageGasOracle.address)) {
gasOracleConfigsToSet.push({
remoteDomain: remoteId,
gasOracle: storageGasOracle.address,
});
}
}
if (gasOracleConfigsToSet.length > 0) {
await this.runIfOwner(chain, igp.contract, async () =>
this.multiProvider.handleTx(
chain,
igp.contract.setGasOracles(gasOracleConfigsToSet),
),
);
}
return igp;
}
async deployOverheadInterchainGasPaymaster(
chain: ChainName,
interchainGasPaymasterAddress: types.Address,
deployOpts?: DeployOptions,
): Promise<OverheadIgp> {
const deployer = await this.multiProvider.getSignerAddress(chain);
// Transfer ownership to the deployer so the destination gas overheads can be set
const initCalldata = Ownable__factory.createInterface().encodeFunctionData(
'transferOwnership',
[deployer],
);
const overheadInterchainGasPaymaster = await this.deployContract(
chain,
'defaultIsmInterchainGasPaymaster',
[interchainGasPaymasterAddress],
{
...deployOpts,
initCalldata,
},
);
const configChains = Object.keys(this.configMap);
const remotes = this.multiProvider
.intersect(configChains, false)
.multiProvider.getRemoteChains(chain);
// Only set gas overhead configs if they differ from what's on chain
const configs: OverheadIgp.DomainConfigStruct[] = [];
for (const remote of remotes) {
const remoteDomain = this.multiProvider.getDomainId(remote);
const gasOverhead = this.configMap[chain].overhead[remote];
const existingOverhead =
await overheadInterchainGasPaymaster.destinationGasOverhead(
remoteDomain,
);
if (!existingOverhead.eq(gasOverhead)) {
configs.push({ domain: remoteDomain, gasOverhead });
}
}
if (configs.length > 0) {
await this.runIfOwner(chain, overheadInterchainGasPaymaster, () =>
this.multiProvider.handleTx(
chain,
overheadInterchainGasPaymaster.setDestinationGasOverheads(
configs,
this.multiProvider.getTransactionOverrides(chain),
),
),
);
}
return overheadInterchainGasPaymaster;
}
async deployStorageGasOracle(
chain: ChainName,
deployOpts?: DeployOptions,
): Promise<StorageGasOracle> {
return this.deployContract(chain, 'storageGasOracle', [], deployOpts);
}
async deployContracts(
chain: ChainName,
config: OverheadIgpConfig,
): Promise<IgpContracts> {
const provider = this.multiProvider.getProvider(chain);
const startingBlockNumber = await provider.getBlockNumber();
this.startingBlockNumbers[chain] = startingBlockNumber;
// NB: To share ProxyAdmins with HyperlaneCore, ensure the ProxyAdmin
// is loaded into the contract cache.
const proxyAdmin = await this.deployContract(chain, 'proxyAdmin', []);
const storageGasOracle = await this.deployStorageGasOracle(chain);
const interchainGasPaymaster = await this.deployInterchainGasPaymaster(
chain,
proxyAdmin,
storageGasOracle,
);
const overheadInterchainGasPaymaster =
await this.deployOverheadInterchainGasPaymaster(
chain,
interchainGasPaymaster.address,
);
// Ownership of the Mailbox and the interchainGasPaymaster is transferred upon initialization.
const ownables: Ownable[] = [overheadInterchainGasPaymaster];
await this.transferOwnershipOfContracts(chain, config.owner, ownables);
return {
proxyAdmin,
storageGasOracle,
interchainGasPaymaster,
defaultIsmInterchainGasPaymaster: overheadInterchainGasPaymaster,
};
}
}

@ -1,78 +0,0 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber } from 'ethers';
import { ethers } from 'hardhat';
import { types } from '@hyperlane-xyz/utils';
import { Chains } from '../consts/chains';
import { HyperlaneCore } from '../core/HyperlaneCore';
import { TestCoreDeployer } from '../core/TestCoreDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { InterchainGasCalculator } from './calculator';
describe('InterchainGasCalculator', async () => {
const localChain = Chains.test1;
const remoteChain = Chains.test2;
const testGasAmount = BigNumber.from('100000');
let signer: SignerWithAddress;
let multiProvider: MultiProvider;
let calculator: InterchainGasCalculator;
let igp: types.Address;
before(async () => {
[signer] = await ethers.getSigners();
multiProvider = MultiProvider.createTestMultiProvider({ signer });
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
const core = new HyperlaneCore(coreContractsMaps, multiProvider);
calculator = new InterchainGasCalculator(multiProvider, core);
igp = coreContractsMaps[localChain].interchainGasPaymaster.address;
});
describe('quoteGasPaymentForDefaultIsmIgp', () => {
it("calls the default ISM IGP's quoteGasPayment function", async () => {
const quote = await calculator.quoteGasPaymentForDefaultIsmIgp(
localChain,
remoteChain,
testGasAmount,
);
// (100,000 gas amount + 151,966 overhead) * 10 gas price
expect(quote).to.equal(BigNumber.from('2519660'));
});
});
describe('quoteGasPayment', () => {
it("calls the IGP's quoteGasPayment function", async () => {
const quote = await calculator.quoteGasPayment(
localChain,
remoteChain,
testGasAmount,
);
// 100,000 gas amount * 10 gas price
expect(quote).to.equal(BigNumber.from('1000000'));
});
});
describe('quoteGasPaymentForIGP', () => {
it("calls the provided IGP's quoteGasPayment", async () => {
const quote = await calculator.quoteGasPaymentForIGP(
localChain,
remoteChain,
testGasAmount,
igp,
);
// 100,000 gas amount * 10 gas price
expect(quote).to.equal(BigNumber.from('1000000'));
});
});
});

@ -1,267 +0,0 @@
import { expect } from 'chai';
import { BigNumber } from 'ethers';
import sinon from 'sinon';
import { Chains } from '../consts/chains';
import { HyperlaneCore } from '../core/HyperlaneCore';
import { CoreContracts } from '../core/contracts';
import { MultiProvider } from '../providers/MultiProvider';
import { MockProvider, MockTokenPriceGetter } from '../test/testUtils';
import { ChainName, TestChainNames } from '../types';
import { InterchainGasCalculator, ParsedMessage } from './calculator';
const HANDLE_GAS = 100_000;
const SUGGESTED_GAS_PRICE = 10;
const INBOX_PROCESS_OVERHEAD_GAS = 100_000;
// Exposes protected methods so they can be stubbed.
class TestInterchainGasCalculator extends InterchainGasCalculator {
estimateGasForProcess(
origin: ChainName,
destination: ChainName,
): Promise<BigNumber> {
return super.estimateGasForProcess(origin, destination);
}
estimateGasForHandle(message: ParsedMessage): Promise<BigNumber> {
return super.estimateGasForHandle(message);
}
convertBetweenTokens(
fromChain: ChainName,
toChain: ChainName,
fromAmount: BigNumber,
): Promise<BigNumber> {
return super.convertBetweenTokens(fromChain, toChain, fromAmount);
}
tokenDecimals(chain: ChainName): number {
return super.tokenDecimals(chain);
}
getGasPrice(chain: ChainName): Promise<BigNumber> {
return super.getGasPrice(chain);
}
}
describe('InterchainGasCalculator', () => {
const provider = new MockProvider();
const multiProvider = MultiProvider.createTestMultiProvider({ provider });
const core: HyperlaneCore = HyperlaneCore.fromEnvironment(
'test',
multiProvider,
);
const origin = Chains.test1;
const destination = Chains.test2;
let tokenPriceGetter: MockTokenPriceGetter;
let calculator: TestInterchainGasCalculator;
beforeEach(() => {
tokenPriceGetter = new MockTokenPriceGetter();
tokenPriceGetter.setTokenPrice(origin, 9.0909);
tokenPriceGetter.setTokenPrice(destination, 5.5);
calculator = new TestInterchainGasCalculator(multiProvider, core, {
tokenPriceGetter,
// A multiplier of 1 makes testing easier to reason about
paymentEstimateMultiplier: '1',
});
});
afterEach(() => {
sinon.restore();
provider.clearMethodResolveValues();
});
describe('estimatePaymentForGas', () => {
it('estimates origin token payment from a specified destination gas amount', async () => {
// Set destination gas price to 10 wei
provider.setMethodResolveValue(
'getGasPrice',
BigNumber.from(SUGGESTED_GAS_PRICE),
);
const estimatedPayment = await calculator.estimatePaymentForGas(
origin,
destination,
BigNumber.from(HANDLE_GAS),
);
// 100k gas * 10 gas price * ($5.5 per destination token / $9.0909 per origin token)
expect(estimatedPayment.toNumber()).to.equal(605_000);
});
});
describe('estimatePaymentForHandleGas', () => {
it('estimates origin token payment from a specified destination handle gas amount', async () => {
// Set destination gas price to 10 wei
provider.setMethodResolveValue(
'getGasPrice',
BigNumber.from(SUGGESTED_GAS_PRICE),
);
// Stub the inbox process overhead gas
sinon
.stub(calculator, 'estimateGasForProcess')
.returns(Promise.resolve(BigNumber.from(INBOX_PROCESS_OVERHEAD_GAS)));
const estimatedPayment = await calculator.estimatePaymentForHandleGas(
origin,
destination,
BigNumber.from(HANDLE_GAS),
);
// (100_000 dest handler gas + 100_000 process overhead gas)
// * 10 gas price * ($5.5 per destination token / $9.0909 per origin token)
expect(estimatedPayment.toNumber()).to.equal(1_210_000);
});
});
/*
describe('estimatePaymentForMessage', () => {
it('estimates origin token payment from a specified message', async () => {
// Set destination gas price to 10 wei
provider.setMethodResolveValue(
'getGasPrice',
BigNumber.from(SUGGESTED_GAS_PRICE),
);
// Set the estimated handle gas
sinon
.stub(calculator, 'estimateGasForHandle')
.returns(Promise.resolve(BigNumber.from(HANDLE_GAS)));
// Stub the inbox process overhead gas
sinon
.stub(calculator, 'estimateGasForProcess')
.returns(Promise.resolve(BigNumber.from(INBOX_PROCESS_OVERHEAD_GAS)));
const zeroAddressBytes32 = utils.addressToBytes32(
ethers.constants.AddressZero,
);
const message: ParsedMessage<TestChainNames, Chains.test2> = {
origin: origin,
sender: zeroAddressBytes32,
destination: destination,
recipient: zeroAddressBytes32,
body: '0x12345678',
};
const estimatedPayment = await calculator.estimatePaymentForMessage(
message,
);
// (100_000 dest handler gas + 100_000 process overhead gas)
// * 10 gas price * ($5.5 per destination token / $9.0909 per origin token)
expect(estimatedPayment.toNumber()).to.equal(1_210_000);
});
});
*/
describe('convertBetweenTokens', () => {
const destinationWei = BigNumber.from('1000');
it('converts using the USD value of origin and destination native tokens', async () => {
const originWei = await calculator.convertBetweenTokens(
destination,
origin,
destinationWei,
);
// 1000 * (5.5 / 9.0909)
expect(originWei.toNumber()).to.equal(605);
});
it('considers when the origin token decimals > the destination token decimals', async () => {
calculator.tokenDecimals = (chain: TestChainNames) => {
if (chain === origin) {
return 20;
}
return 18;
};
const originWei = await calculator.convertBetweenTokens(
destination,
origin,
destinationWei,
);
// 1000 * (5.5 / 9.0909) * 100
expect(originWei.toNumber()).to.equal(60500);
});
it('considers when the origin token decimals < the destination token decimals', async () => {
sinon.stub(calculator, 'tokenDecimals').callsFake((chain: ChainName) => {
if (chain === origin) {
return 16;
}
return 18;
});
const originWei = await calculator.convertBetweenTokens(
destination,
origin,
destinationWei,
);
// 1000 * (5.5 / 9.0909) / 100
expect(originWei.toNumber()).to.equal(6);
});
});
describe('getGasPrice', () => {
it('gets the gas price from the provider', async () => {
provider.setMethodResolveValue(
'getGasPrice',
BigNumber.from(SUGGESTED_GAS_PRICE),
);
expect((await calculator.getGasPrice(destination)).toNumber()).to.equal(
SUGGESTED_GAS_PRICE,
);
});
});
describe('estimateGasForProcess', () => {
let threshold: number;
// Mock the return value of MultisigIsm.threshold
// to return `threshold`. Because the mocking involves a closure,
// changing `threshold` will change the return value of MultisigIsm.threshold.
before(() => {
const getContractsStub = sinon.stub(core, 'getContracts');
let thresholdStub: sinon.SinonStub | undefined;
getContractsStub.callsFake((chain) => {
// Get the "real" return value of getContracts.
const contracts: CoreContracts =
getContractsStub.wrappedMethod.bind(core)(chain);
// Ethers contracts are frozen using Object.freeze, so we make a copy
// of the object so we can stub `threshold`.
const multisigIsm = Object.assign({}, contracts.multisigIsm);
// Because we are stubbing vaidatorManager.threshold when core.getContracts gets called,
// we must ensure we don't try to stub more than once or sinon will complain.
if (!thresholdStub) {
thresholdStub = sinon
.stub(multisigIsm, 'threshold')
.callsFake(() => Promise.resolve(threshold));
contracts.multisigIsm = multisigIsm;
}
return contracts;
});
});
it('scales the gas cost with the quorum threshold', async () => {
threshold = 2;
const gasWithThresholdLow = await calculator.estimateGasForProcess(
origin,
destination,
);
threshold = 3;
const gasWithThresholdHigh = await calculator.estimateGasForProcess(
origin,
destination,
);
expect(gasWithThresholdHigh.gt(gasWithThresholdLow)).to.be.true;
});
});
});

@ -1,416 +0,0 @@
import CoinGecko from 'coingecko-api';
import { BigNumber, FixedNumber, ethers } from 'ethers';
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core';
import { types, utils } from '@hyperlane-xyz/utils';
import { CoreEnvironment, HyperlaneCore } from '../core/HyperlaneCore';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainName } from '../types';
import { convertDecimalValue, mulBigAndFixed } from '../utils/number';
import { CoinGeckoTokenPriceGetter, TokenPriceGetter } from './token-prices';
/**
* A note on arithmetic:
* The ethers.BigNumber implementation behaves very similar to Solidity's
* number handling by not supporting decimals. To avoid adding another big
* number implementation as a dependency, we use ethers.FixedNumber, a
* fixed point implementation intended to model how Solidity's half-supported
* fixed point numbers work, see https://docs.soliditylang.org/en/v0.8.13/types.html#fixed-point-numbers).
*
* Generally, ceiling is used rather than floor here to err on the side of over-
* estimating amounts.
*/
// If a chain doesn't specify how many decimals their native token has, 18 is used.
const DEFAULT_TOKEN_DECIMALS = 18;
// Intrinsic gas for a transaction. Does not consider calldata costs or differences in
// intrinsic gas for different chains.
const GAS_INTRINSIC = 21_000;
// The gas used to process a message when the quorum size is zero.
// Includes intrinsic gas, mailbox overhead gas, all other gas that does not scale with the
// quorum size. Excludes the cost of the recipient's handle function.
const GAS_OVERHEAD_BASE = 155_000;
// The amount of gas used for each signature when a signed checkpoint
// is submitted for verification.
// Really observed to be about 6500, but rounding up for safety.
const GAS_OVERHEAD_PER_SIGNATURE = 7_500;
export interface InterchainGasCalculatorConfig {
/**
* A multiplier applied to the estimated origin token payment amount.
* This should be high enough to account for movements in token exchange
* rates and gas prices.
* Only used for gas payment estimates that are not quoted on-chain.
* @defaultValue 1.25
*/
paymentEstimateMultiplier?: string;
/**
* An amount of additional gas to add to the estimated gas of processing a message.
* Only used when estimating a payment from a message.
* Only used for gas payment estimates that are not quoted on-chain.
* @defaultValue 50,000
*/
messageGasEstimateBuffer?: string;
/**
* Used to get the native token prices of the origin and destination chains.
* Only used for gas payment estimates that are not quoted on-chain.
* @defaultValue An instance of DefaultTokenPriceGetter.
*/
tokenPriceGetter?: TokenPriceGetter;
}
export interface ParsedMessage {
origin: ChainName;
sender: string;
destination: ChainName;
recipient: string;
body: string;
}
/**
* Calculates interchain gas payments.
*/
export class InterchainGasCalculator {
private core: HyperlaneCore;
private multiProvider: MultiProvider;
private tokenPriceGetter: TokenPriceGetter;
private paymentEstimateMultiplier: ethers.FixedNumber;
private messageGasEstimateBuffer: ethers.BigNumber;
static fromEnvironment<Env extends CoreEnvironment>(
env: Env,
multiProvider: MultiProvider,
config?: InterchainGasCalculatorConfig,
): InterchainGasCalculator {
const core = HyperlaneCore.fromEnvironment(env, multiProvider);
return new InterchainGasCalculator(multiProvider, core, config);
}
constructor(
multiProvider: MultiProvider,
core: HyperlaneCore,
config?: InterchainGasCalculatorConfig,
) {
this.multiProvider = multiProvider;
this.core = core;
if (config?.tokenPriceGetter) {
this.tokenPriceGetter = config.tokenPriceGetter;
} else {
const coinGecko = new CoinGecko();
this.tokenPriceGetter = new CoinGeckoTokenPriceGetter(coinGecko);
}
this.paymentEstimateMultiplier = FixedNumber.from(
config?.paymentEstimateMultiplier ?? '1.25',
);
this.messageGasEstimateBuffer = BigNumber.from(
config?.messageGasEstimateBuffer ?? 50_000,
);
}
/**
* Only intended for IGPs that quote gas payments on-chain.
* Calls the default ISM IGP's `quoteGasPayment` function to get the amount of native tokens
* required to pay for interchain gas.
* The default ISM IGP will add any gas overhead amounts related to the Mailbox
* and default ISM on the destination to the provided gasAmount.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`.
* The default IGP is expected to add any gas overhead related to the Mailbox
* or ISM, so this gas amount is only required to cover the usage of the `handle`
* function.
* @returns The amount of native tokens required to pay for interchain gas.
*/
async quoteGasPaymentForDefaultIsmIgp(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
): Promise<BigNumber> {
const igpAddress =
this.core.getContracts(origin).defaultIsmInterchainGasPaymaster;
return this.quoteGasPaymentForIGP(
origin,
destination,
gasAmount,
igpAddress.address,
);
}
/**
* Only intended for IGPs that quote gas payments on-chain.
* Calls the "base" IGP's `quoteGasPayment` function to get the amount of native tokens
* required to pay for interchain gas.
* This IGP will not apply any overhead gas to the provided gasAmount.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`.
* This is expected to be the total amount of gas that a transaction would use
* on the destination chain. This should consider intrinsic transaction gas,
* Mailbox overhead gas costs, ISM gas costs, and the recipient's handle function
* gas cost.
* @returns The amount of native tokens required to pay for interchain gas.
*/
async quoteGasPayment(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
): Promise<BigNumber> {
const igpAddress = this.core.getContracts(origin).interchainGasPaymaster;
return this.quoteGasPaymentForIGP(
origin,
destination,
gasAmount,
igpAddress.address,
);
}
/**
* Only intended for IGPs that quote gas payments on-chain.
* Calls the origin's default IGP's `quoteGasPayment` function to get the
* amount of native tokens required to pay for interchain gas.
* The default IGP is expected to add any gas overhead related to the Mailbox
* and ISM to the provided gasAmount.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`.
* The default IGP is expected to add any gas overhead related to the Mailbox
* or ISM, so this gas amount is only required to cover the usage of the `handle`
* function.
* @returns The amount of native tokens required to pay for interchain gas.
*/
async quoteGasPaymentForIGP(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
interchainGasPaymasterAddress: types.Address,
): Promise<BigNumber> {
const originProvider = this.multiProvider.getProvider(origin);
const igp = InterchainGasPaymaster__factory.connect(
interchainGasPaymasterAddress,
originProvider,
);
const domainId = this.multiProvider.getDomainId(destination);
return igp.quoteGasPayment(domainId, gasAmount);
}
/**
* Only intended for IGPs that do *not* quote gas payments on-chain.
* Given an amount of gas to consume on the destination chain, calculates the
* estimated payment denominated in the native token of the origin chain.
* Considers the exchange rate between the native tokens of the origin and
* destination chains and the suggested gas price of the destination chain.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param gas The amount of gas to pay for on the destination chain.
* @returns An estimated amount of origin chain tokens to cover gas costs on the
* destination chain.
*/
async estimatePaymentForGas(
origin: ChainName,
destination: ChainName,
gas: BigNumber,
): Promise<BigNumber> {
const destinationGasPrice = await this.getGasPrice(destination);
const destinationGasCost = gas.mul(destinationGasPrice);
const originGasCost = await this.convertBetweenTokens(
destination,
origin,
destinationGasCost,
);
// Applies a multiplier
return mulBigAndFixed(
originGasCost,
this.paymentEstimateMultiplier,
true, // ceil
);
}
/**
* Only intended for IGPs that do *not* quote gas payments on-chain.
* Given an amount of gas the message's recipient `handle` function is expected
* to use, calculates the estimated payment denominated in the native
* token of the origin chain. Considers the exchange rate between the native
* tokens of the origin and destination chains, the suggested gas price on
* the destination chain, gas costs incurred by a relayer when submitting a signed
* checkpoint to the destination chain, and the overhead gas cost in Inbox of processing
* a message.
* @param origin The name of the origin chain.
* @param destination The name of the destination chain.
* @param handleGas The amount of gas the recipient `handle` function
* is estimated to use.
* @returns An estimated amount of origin chain tokens to cover gas costs of the
* message on the destination chain.
*/
async estimatePaymentForHandleGas(
origin: ChainName,
destination: ChainName,
handleGas: BigNumber,
): Promise<BigNumber> {
const destinationGas = handleGas.add(
await this.estimateGasForProcess(origin, destination),
);
return this.estimatePaymentForGas(origin, destination, destinationGas);
}
/**
* Only intended for IGPs that do *not* quote gas payments on-chain.
* Calculates the estimated payment to process the message on its destination chain,
* denominated in the native token of the origin chain. The gas used by the message's
* recipient handler function is estimated in an eth_estimateGas call to the
* destination chain, and is then used to calculate the payment using
* Currently made private as it does not work properly for Arbitrum.
* {@link estimatePaymentForHandleGasAmount}.
* @param message The parsed message to estimate payment for.
* @returns An estimated amount of origin chain tokens to cover gas costs of the
* message on the destination chain.
*/
protected async estimatePaymentForMessage(
message: ParsedMessage,
): Promise<BigNumber> {
const handleGas = await this.estimateGasForHandle(message);
return this.estimatePaymentForHandleGas(
message.origin,
message.destination,
handleGas,
);
}
/**
* Using the exchange rates provided by tokenPriceGetter, returns the amount of
* `toChain` native tokens equivalent in value to the provided `fromAmount` of
* `fromChain` native tokens. Accounts for differences in the decimals of the tokens.
* @param fromChain The chain whose native token is being converted from.
* @param toChain The chain whose native token is being converted into.
* @param fromAmount The amount of `fromChain` native tokens to convert from.
* @returns The amount of `toChain` native tokens whose value is equivalent to
* `fromAmount` of `fromChain` native tokens.
*/
protected async convertBetweenTokens(
fromChain: ChainName,
toChain: ChainName,
value: BigNumber,
): Promise<BigNumber> {
// Does not factor in differing token decimals.
const exchangeRate = await this.tokenPriceGetter.getTokenExchangeRate(
fromChain,
toChain,
);
// 1/100th of a cent
const PRECISION = 1000;
return convertDecimalValue(
value.mul(Math.round(exchangeRate * PRECISION)).div(PRECISION),
this.tokenDecimals(fromChain),
this.tokenDecimals(toChain),
);
}
/**
* Gets a suggested gas price for a chain.
* @param chainName The name of the chain to get the gas price for
* @returns The suggested gas price in wei on the destination chain.
*/
protected async getGasPrice(chain: ChainName): Promise<BigNumber> {
const provider = this.multiProvider.getProvider(chain);
if (provider == undefined) {
throw new Error(`Missing provider for ${chain}`);
}
return provider.getGasPrice();
}
/**
* Gets the number of decimals of the provided chain's native token.
* @param chain The chain.
* @returns The number of decimals of `chain`'s native token.
*/
protected tokenDecimals(chain: ChainName): number {
return (
this.multiProvider.tryGetChainMetadata(chain)?.nativeToken?.decimals ??
DEFAULT_TOKEN_DECIMALS
);
}
/**
* Estimates the amount of gas used by message's recipient `handle` function
* on its destination chain. This does not assume the Inbox of the destination
* chain has a checkpoint that the message is included in, and does not
* consider intrinsic gas or any "overhead" gas incurred by Inbox.process.
* The estimated gas returned is the sum of:
* 1. The estimated gas consumption of a direct call to the `handle`
* function of the recipient address using the correct parameters and
* setting the `from` address of the transaction to the address of the inbox.
* 2. A buffer to account for inaccuracies in the above estimation.
* @param message The message to estimate recipient `handle` gas usage for.
* @returns The estimated gas required by the message's recipient handle function
* on the destination chain.
*/
protected async estimateGasForHandle(
message: ParsedMessage,
): Promise<BigNumber> {
const provider = this.multiProvider.getProvider(message.destination);
const mailbox = this.core.getContracts(message.destination).mailbox
.contract;
const handlerInterface = new ethers.utils.Interface([
'function handle(uint32,bytes32,bytes)',
]);
// Estimates a direct call to the `handle` function of the recipient
// with the `from` address set to the inbox.
// This includes intrinsic gas.
const directHandleCallGas = await provider.estimateGas({
to: utils.bytes32ToAddress(message.recipient),
from: mailbox.address,
data: handlerInterface.encodeFunctionData('handle', [
this.multiProvider.getChainId(message.origin),
utils.addressToBytes32(message.sender),
message.body,
]),
});
// Subtract intrinsic gas, which is included in directHandleCallGas.
// Note the "real" intrinsic gas will always be higher than this.intrinsicGas
// due to calldata costs, but this is desired because subtracting the lower bound
// this.intrinsicGas will result in a more generous final estimate.
return directHandleCallGas
.add(this.messageGasEstimateBuffer)
.sub(this.intrinsicGas());
}
/**
* @returns A generous estimation of the gas consumption of all process
* operations within Inbox.sol, including intrinsic gas. Does not include any gas
* consumed within a message's recipient `handle` function.
*/
protected async estimateGasForProcess(
origin: ChainName,
destination: ChainName,
): Promise<BigNumber> {
// TODO: Check the recipient module
const module = this.core.getContracts(destination).multisigIsm;
const threshold = await module.threshold(
this.multiProvider.getDomainId(origin),
);
return BigNumber.from(threshold)
.mul(GAS_OVERHEAD_PER_SIGNATURE)
.add(GAS_OVERHEAD_BASE);
}
/**
* @returns The intrinsic gas of a basic transaction. Note this does not consider calldata
* costs or potentially different intrinsic gas costs for different chains.
*/
protected intrinsicGas(): BigNumber {
return BigNumber.from(GAS_INTRINSIC);
}
}

@ -0,0 +1,37 @@
import {
InterchainGasPaymaster,
InterchainGasPaymaster__factory,
OverheadIgp,
OverheadIgp__factory,
ProxyAdmin,
ProxyAdmin__factory,
StorageGasOracle,
StorageGasOracle__factory,
} from '@hyperlane-xyz/core';
import { types } from '@hyperlane-xyz/utils';
import { ProxiedContract, TransparentProxyAddresses } from '../proxy';
export type IgpAddresses = {
proxyAdmin: types.Address;
interchainGasPaymaster: types.Address | TransparentProxyAddresses;
defaultIsmInterchainGasPaymaster: types.Address;
storageGasOracle: types.Address;
};
export type IgpContracts = {
proxyAdmin: ProxyAdmin;
interchainGasPaymaster: ProxiedContract<
InterchainGasPaymaster,
TransparentProxyAddresses
>;
defaultIsmInterchainGasPaymaster: OverheadIgp;
storageGasOracle: StorageGasOracle;
};
export const igpFactories = {
proxyAdmin: new ProxyAdmin__factory(),
interchainGasPaymaster: new InterchainGasPaymaster__factory(),
defaultIsmInterchainGasPaymaster: new OverheadIgp__factory(),
storageGasOracle: new StorageGasOracle__factory(),
};

@ -1,70 +1,53 @@
import { LegacyMultisigIsm, Mailbox } from '@hyperlane-xyz/core';
import { BigNumber } from 'ethers';
import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core';
import type { types } from '@hyperlane-xyz/utils';
import type { CheckerViolation } from '../deploy/types';
import { ChainName } from '../types';
import { ChainMap } from '../types';
export type MultisigIsmConfig = {
validators: Array<types.Address>;
threshold: number;
};
export enum GasOracleContractType {
StorageGasOracle = 'StorageGasOracle',
}
export type CoreConfig = {
multisigIsm: MultisigIsmConfig;
export type IgpConfig = {
owner: types.Address;
remove?: boolean;
beneficiary: types.Address;
gasOracleType: ChainMap<GasOracleContractType>;
};
export enum CoreViolationType {
MultisigIsm = 'MultisigIsm',
Mailbox = 'Mailbox',
ConnectionManager = 'ConnectionManager',
ValidatorAnnounce = 'ValidatorAnnounce',
}
export enum MultisigIsmViolationType {
EnrolledValidators = 'EnrolledValidators',
Threshold = 'Threshold',
}
export type OverheadIgpConfig = IgpConfig & {
overhead: ChainMap<number>;
};
export enum MailboxViolationType {
DefaultIsm = 'DefaultIsm',
export enum IgpViolationType {
Beneficiary = 'Beneficiary',
GasOracles = 'GasOracles',
Overhead = 'Overhead',
}
export interface MailboxViolation extends CheckerViolation {
type: CoreViolationType.Mailbox;
contract: Mailbox;
mailboxType: MailboxViolationType;
export interface IgpViolation extends CheckerViolation {
type: 'InterchainGasPaymaster';
subType: IgpViolationType;
}
export interface MailboxMultisigIsmViolation extends MailboxViolation {
export interface IgpBeneficiaryViolation extends IgpViolation {
subType: IgpViolationType.Beneficiary;
contract: InterchainGasPaymaster;
actual: types.Address;
expected: types.Address;
}
export interface MultisigIsmViolation extends CheckerViolation {
type: CoreViolationType.MultisigIsm;
contract: LegacyMultisigIsm;
subType: MultisigIsmViolationType;
remote: ChainName;
}
export interface EnrolledValidatorsViolation extends MultisigIsmViolation {
subType: MultisigIsmViolationType.EnrolledValidators;
actual: Set<types.Address>;
expected: Set<types.Address>;
}
export interface ThresholdViolation extends MultisigIsmViolation {
subType: MultisigIsmViolationType.Threshold;
actual: number;
expected: number;
export interface IgpGasOraclesViolation extends IgpViolation {
subType: IgpViolationType.GasOracles;
contract: InterchainGasPaymaster;
actual: ChainMap<types.Address>;
expected: ChainMap<types.Address>;
}
export interface ValidatorAnnounceViolation extends CheckerViolation {
type: CoreViolationType.ValidatorAnnounce;
chain: ChainName;
validator: types.Address;
actual: boolean;
expected: boolean;
export interface IgpOverheadViolation extends IgpViolation {
subType: IgpViolationType.Overhead;
contract: OverheadIgp;
actual: ChainMap<BigNumber>;
expected: ChainMap<BigNumber>;
}

@ -1,3 +1,12 @@
export {
AgentChainSetup,
AgentConfig,
AgentConnection,
AgentConnectionType,
AgentSigner,
buildAgentConfig,
HyperlaneAgentAddresses,
} from './agents/types';
export {
chainIdToMetadata,
ChainMetadata,
@ -21,24 +30,24 @@ export {
Testnets,
} from './consts/chains';
export {
environments as coreEnvironments,
hyperlaneAgentAddresses,
HyperlaneContractAddresses,
hyperlaneContractAddresses,
hyperlaneCoreAddresses,
hyperlaneEnvironments,
} from './consts/environments';
export { defaultMultisigIsmConfigs } from './consts/multisigIsm';
export {
buildContracts,
connectContracts,
connectContractsMap,
filterAddresses,
HyperlaneAddresses,
HyperlaneContracts,
HyperlaneFactories,
serializeContracts,
} from './contracts';
export {
ConnectionClientContracts,
CoreContracts,
coreFactories,
GasOracleContracts,
} from './core/contracts';
export { CoreContracts, coreFactories } from './core/contracts';
export {
AnnotatedDispatch,
AnnotatedLifecycleEvent,
@ -49,49 +58,26 @@ export {
DispatchedMessage,
HyperlaneCore,
} from './core/HyperlaneCore';
export { TestCoreApp, TestCoreContracts } from './core/TestCoreApp';
export { TestCoreDeployer } from './core/TestCoreDeployer';
export { HyperlaneCoreChecker } from './core/HyperlaneCoreChecker';
export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer';
export { TestCoreApp, TestCoreContracts } from './core/TestCoreApp';
export { TestCoreDeployer } from './core/TestCoreDeployer';
export {
CoreConfig,
CoreViolationType,
DefaultIsmIgpViolation,
DefaultIsmIgpViolationType,
EnrolledValidatorsViolation,
GasOracleContractType,
IgpBeneficiaryViolation,
IgpGasOraclesViolation,
IgpViolation,
IgpViolationType,
MultisigIsmConfig,
MultisigIsmViolation,
MultisigIsmViolationType,
} from './core/types';
export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker';
export { HyperlaneDeployer } from './deploy/HyperlaneDeployer';
export {
InterchainAccountDeployer,
InterchainQueryDeployer,
} from './middleware/deploy';
export { LiquidityLayerApp } from './middleware/liquidity-layer/LiquidityLayerApp';
export {
BridgeAdapterConfig,
BridgeAdapterType,
CircleBridgeAdapterConfig,
LiquidityLayerDeployer,
PortalAdapterConfig,
} from './middleware/liquidity-layer/LiquidityLayerRouterDeployer';
export { ProxyViolation } from './deploy/proxy';
export { GasRouterDeployer } from './router/GasRouterDeployer';
export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker';
export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer';
export { GasRouterConfig, RouterConfig } from './router/types';
export {
CheckerViolation,
OwnerViolation,
ViolationType,
} from './deploy/types';
export { getChainToOwnerMap } from './deploy/utils';
export { ContractVerifier } from './deploy/verify/ContractVerifier';
export {
CompilerOptions,
@ -105,30 +91,61 @@ export {
queryAnnotatedEvents,
TSContract,
} from './events';
export { InterchainGasCalculator, ParsedMessage } from './gas/calculator';
export { HyperlaneIgp } from './gas/HyperlaneIgp';
export { HyperlaneIgpChecker } from './gas/HyperlaneIgpChecker';
export { HyperlaneIgpDeployer } from './gas/HyperlaneIgpDeployer';
export { CoinGeckoTokenPriceGetter } from './gas/token-prices';
export {
CoinGeckoTokenPriceGetter,
TokenPriceGetter,
} from './gas/token-prices';
GasOracleContractType,
IgpBeneficiaryViolation,
IgpConfig,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
OverheadIgpConfig,
} from './gas/types';
export { HyperlaneApp } from './HyperlaneApp';
export {
InterchainAccountDeployer,
interchainAccountFactories,
InterchainQueryDeployer,
interchainQueryFactories,
} from './middleware/deploy';
export {
LiquidityLayerContracts,
liquidityLayerFactories,
} from './middleware/liquidity-layer/contracts';
export { LiquidityLayerApp } from './middleware/liquidity-layer/LiquidityLayerApp';
export {
BridgeAdapterConfig,
BridgeAdapterType,
CircleBridgeAdapterConfig,
LiquidityLayerDeployer,
PortalAdapterConfig,
} from './middleware/liquidity-layer/LiquidityLayerRouterDeployer';
export { MultiProvider, providerBuilder } from './providers/MultiProvider';
export { RetryJsonRpcProvider, RetryProvider } from './providers/RetryProvider';
export {
ProxiedContract,
ProxyAddresses,
ProxyKind,
TransparentProxyAddresses,
} from './proxy';
export { GasRouterDeployer } from './router/GasRouterDeployer';
export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker';
export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer';
export { GasRouterApp, Router, RouterApp } from './router/RouterApps';
export { RouterContracts, RouterFactories } from './router/types';
export { getTestOwnerConfig } from './test/testUtils';
export {
GasRouterConfig,
RouterConfig,
RouterContracts,
RouterFactories,
} from './router/types';
export {
createRouterConfigMap,
deployTestIgpsAndGetRouterConfig,
} from './test/testUtils';
export {
ChainMap,
ChainName,
@ -137,6 +154,7 @@ export {
TestChainNames,
} from './types';
export { canonizeId, evmId } from './utils/ids';
export { multisigIsmVerificationCost } from './utils/ism';
export { MultiGeneric } from './utils/MultiGeneric';
export {
bigToFixed,
@ -144,6 +162,13 @@ export {
fixedToBig,
mulBigAndFixed,
} from './utils/number';
export { objMap, objMapEntries, pick, promiseObjAll } from './utils/objects';
export {
objFilter,
objMap,
objMapEntries,
objMerge,
pick,
promiseObjAll,
} from './utils/objects';
export { delay } from './utils/time';
export { chainMetadataToWagmiChain } from './utils/wagmi';

@ -12,7 +12,7 @@ import { TestCoreApp } from '../core/TestCoreApp';
import { TestCoreDeployer } from '../core/TestCoreDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { RouterConfig } from '../router/types';
import { getTestOwnerConfig } from '../test/testUtils';
import { deployTestIgpsAndGetRouterConfig } from '../test/testUtils';
import { ChainMap } from '../types';
import { objMap, promiseObjAll } from '../utils/objects';
@ -41,8 +41,10 @@ describe('InterchainAccounts', async () => {
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
coreApp = new TestCoreApp(coreContractsMaps, multiProvider);
config = coreApp.extendWithConnectionClientConfig(
getTestOwnerConfig(signer.address),
config = await deployTestIgpsAndGetRouterConfig(
multiProvider,
signer.address,
coreContractsMaps,
);
config.test1.interchainSecurityModule =

@ -76,7 +76,7 @@ export class LiquidityLayerDeployer extends MiddlewareRouterDeployer<
objMap(contractsMap, (_chain, contracts) => ({
router: contracts.circleBridgeAdapter,
})),
(_): _ is { router: CircleBridgeAdapter } => !!_.router,
(chain, _): _ is { router: CircleBridgeAdapter } => !!_.router,
),
);
@ -86,7 +86,7 @@ export class LiquidityLayerDeployer extends MiddlewareRouterDeployer<
objMap(contractsMap, (_chain, contracts) => ({
router: contracts.portalAdapter,
})),
(_): _ is { router: PortalAdapter } => !!_.router,
(chain, _): _ is { router: PortalAdapter } => !!_.router,
),
);
}

@ -21,7 +21,7 @@ import { Chains } from '../../consts/chains';
import { TestCoreApp } from '../../core/TestCoreApp';
import { TestCoreDeployer } from '../../core/TestCoreDeployer';
import { MultiProvider } from '../../providers/MultiProvider';
import { getTestOwnerConfig } from '../../test/testUtils';
import { deployTestIgpsAndGetRouterConfig } from '../../test/testUtils';
import { ChainMap } from '../../types';
import { objMap } from '../../utils/objects';
@ -73,10 +73,14 @@ describe('LiquidityLayerRouter', async () => {
signer,
);
messageTransmitter = await messageTransmitterF.deploy(mockToken.address);
config = coreApp.extendWithConnectionClientConfig(
objMap(getTestOwnerConfig(signer.address), (_chain, conf) => ({
...conf,
const routerConfig = await deployTestIgpsAndGetRouterConfig(
multiProvider,
signer.address,
coreContractsMaps,
);
config = objMap(routerConfig, (chain, config) => {
return {
...config,
circle: {
type: BridgeAdapterType.Circle,
tokenMessengerAddress: circleTokenMessenger.address,
@ -107,8 +111,8 @@ describe('LiquidityLayerRouter', async () => {
},
],
} as PortalAdapterConfig,
})),
);
};
});
});
beforeEach(async () => {

@ -15,7 +15,7 @@ import { TestCoreApp } from '../core/TestCoreApp';
import { TestCoreDeployer } from '../core/TestCoreDeployer';
import { MultiProvider } from '../providers/MultiProvider';
import { RouterConfig } from '../router/types';
import { getTestOwnerConfig } from '../test/testUtils';
import { deployTestIgpsAndGetRouterConfig } from '../test/testUtils';
import { ChainMap } from '../types';
import { InterchainQueryDeployer } from './deploy';
@ -42,8 +42,10 @@ describe('InterchainQueryRouter', async () => {
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
coreApp = new TestCoreApp(coreContractsMaps, multiProvider);
config = coreApp.extendWithConnectionClientConfig(
getTestOwnerConfig(signer.address),
config = await deployTestIgpsAndGetRouterConfig(
multiProvider,
signer.address,
coreContractsMaps,
);
});

@ -8,7 +8,7 @@ import {
providers,
} from 'ethers';
import { types } from '@hyperlane-xyz/utils';
import { types, utils } from '@hyperlane-xyz/utils';
import {
ChainMetadata,
@ -57,37 +57,45 @@ export class MultiProvider {
options: MultiProviderOptions = {},
) {
Object.entries(chainMetadata).forEach(([key, cm]) => {
if (!isValidChainMetadata(cm))
throw new Error(`Invalid chain metadata for ${cm.chainId}`);
if (key !== cm.name)
throw new Error(
`Chain name mismatch: Key was ${key}, but name is ${cm.name}`,
);
this.addChain(cm);
});
this.logger = debug(options?.loggerName || 'hyperlane:MultiProvider');
}
this.metadata = chainMetadata;
/**
* Add a chain to the multiprovider
* @throws if chain's name or domain/chain ID collide
*/
addChain(metadata: ChainMetadata): void {
if (!isValidChainMetadata(metadata))
throw new Error(`Invalid chain metadata for ${metadata.name}`);
// Ensure no two chains have overlapping names/domainIds/chainIds
const chainNames = new Set<string>();
const chainIds = new Set<number>();
const domainIds = new Set<number>();
for (const chain of Object.values(chainMetadata)) {
const { name, chainId, domainId } = chain;
if (chainNames.has(name))
for (const chainMetadata of Object.values(this.metadata)) {
const { name, chainId, domainId } = chainMetadata;
if (name == metadata.name)
throw new Error(`Duplicate chain name: ${name}`);
if (chainIds.has(chainId))
throw new Error(`Duplicate chain id: ${chainId}`);
if (domainIds.has(chainId))
throw new Error(`Overlapping chain/domain id: ${chainId}`);
if (domainId && domainIds.has(domainId))
throw new Error(`Duplicate domain id: ${domainId}`);
if (domainId && chainIds.has(domainId))
throw new Error(`Overlapping chain/domain id: ${domainId}`);
chainNames.add(name);
chainIds.add(chainId);
if (domainId) domainIds.add(domainId);
// Chain and Domain Ids should be globally unique
const idCollision =
chainId == metadata.chainId ||
domainId == metadata.chainId ||
(metadata.domainId &&
(chainId == metadata.domainId || domainId === metadata.domainId));
if (idCollision)
throw new Error(
`Chain/Domain id collision: ${name} and ${metadata.name}`,
);
}
this.metadata[metadata.name] = metadata;
if (this.useSharedSigner) {
const signers = Object.values(this.signers);
if (signers.length > 0) {
this.setSharedSigner(signers[0]);
}
}
this.logger = debug(options?.loggerName || 'hyperlane:MultiProvider');
}
/**
@ -414,7 +422,7 @@ export class MultiProvider {
* Get chain names excluding given chain name
*/
getRemoteChains(name: ChainName): ChainName[] {
return this.getKnownChainNames().filter((n) => n !== name);
return utils.exclude(name, this.getKnownChainNames());
}
/**
@ -624,9 +632,10 @@ export class MultiProvider {
*/
static createTestMultiProvider(
params: { signer?: Signer; provider?: Provider } = {},
chains: ChainName[] = TestChains,
): MultiProvider {
const { signer, provider } = params;
const chainMetadata = pick(defaultChainMetadata, TestChains);
const chainMetadata = pick(defaultChainMetadata, chains);
const mp = new MultiProvider(chainMetadata);
if (signer) {
mp.setSharedSigner(signer);
@ -634,7 +643,7 @@ export class MultiProvider {
const _provider = provider || signer?.provider;
if (_provider) {
const providerMap: ChainMap<Provider> = {};
TestChains.forEach((t) => (providerMap[t] = _provider));
chains.forEach((t) => (providerMap[t] = _provider));
mp.setProviders(providerMap);
}
return mp;

@ -29,6 +29,12 @@ export function isProxyAddresses(
);
}
export function getProxyAddress(
address: types.Address | ProxyAddresses<any>,
): string {
return isProxyAddresses(address) ? address.proxy : address;
}
export type TransparentProxyAddresses = ProxyAddresses<ProxyKind.Transparent>;
export class ProxiedContract<

@ -0,0 +1,39 @@
import { BigNumber } from 'ethers';
import { GasRouter } from '@hyperlane-xyz/core';
import { types } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { ChainMap, ChainName } from '../types';
import { objMap, promiseObjAll } from '../utils/objects';
import { RouterContracts } from './types';
export class RouterApp<
Contracts extends RouterContracts,
> extends HyperlaneApp<Contracts> {
getSecurityModules = (): Promise<ChainMap<types.Address>> =>
promiseObjAll(
objMap(this.contractsMap, (_, contracts) =>
contracts.router.interchainSecurityModule(),
),
);
getOwners = (): Promise<ChainMap<types.Address>> =>
promiseObjAll(
objMap(this.contractsMap, (_, contracts) => contracts.router.owner()),
);
}
export class GasRouterApp<
Contracts extends RouterContracts<GasRouter>,
> extends RouterApp<Contracts> {
async quoteGasPayment(
origin: ChainName,
destination: ChainName,
): Promise<BigNumber> {
return this.getContracts(origin).router.quoteGasPayment(
this.multiProvider.getDomainId(destination),
);
}
}

@ -2,7 +2,7 @@ import { TestRouter__factory } from '@hyperlane-xyz/core';
import { HyperlaneApp } from '../../HyperlaneApp';
import { chainMetadata } from '../../consts/chainMetadata';
import { Chains, TestChains } from '../../consts/chains';
import { Chains } from '../../consts/chains';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { HyperlaneDeployer } from '../../deploy/HyperlaneDeployer';
import { MultiProvider } from '../../providers/MultiProvider';
@ -16,13 +16,6 @@ import {
import { ChainMap, ChainName } from '../../types';
import { objMap, pick, promiseObjAll } from '../../utils/objects';
export const fullTestEnvConfigs = pick(chainMetadata, TestChains);
export const subsetTestConfigs = pick(chainMetadata, [
Chains.test1,
Chains.test2,
]);
export const alfajoresChainConfig = pick(chainMetadata, [Chains.alfajores]);
export class EnvSubsetApp extends HyperlaneApp<RouterContracts> {}

@ -1,16 +1,12 @@
import { buildContracts } from '../../contracts';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { getChainToOwnerMap } from '../../deploy/utils';
import { HyperlaneIgp } from '../../gas/HyperlaneIgp';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterContracts } from '../../router/types';
import { ChainMap } from '../../types';
import { createRouterConfigMap } from '../testUtils';
import {
EnvSubsetApp,
EnvSubsetChecker,
alfajoresChainConfig,
envSubsetFactories,
} from './app';
import { EnvSubsetApp, EnvSubsetChecker, envSubsetFactories } from './app';
// Copied from output of deploy-single-chain.ts script
const deploymentAddresses = {
@ -31,9 +27,13 @@ async function check() {
) as ChainMap<RouterContracts>;
const app = new EnvSubsetApp(contractsMap, multiProvider);
const core = HyperlaneCore.fromEnvironment('testnet', multiProvider);
const config = core.extendWithConnectionClientConfig(
getChainToOwnerMap(alfajoresChainConfig, ownerAddress),
const igp = HyperlaneIgp.fromEnvironment('testnet', multiProvider);
const config = createRouterConfigMap(
ownerAddress,
core.contractsMap,
igp.contractsMap,
);
const envSubsetChecker = new EnvSubsetChecker(multiProvider, app, config);
console.info('Starting check');

@ -1,10 +1,11 @@
import { Chains } from '../../consts/chains';
import { serializeContracts } from '../../contracts';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { getChainToOwnerMap } from '../../deploy/utils';
import { HyperlaneIgp } from '../../gas/HyperlaneIgp';
import { MultiProvider } from '../../providers/MultiProvider';
import { createRouterConfigMap } from '../testUtils';
import { EnvSubsetDeployer, alfajoresChainConfig } from './app';
import { EnvSubsetDeployer } from './app';
import { getAlfajoresSigner } from './utils';
async function main() {
@ -15,8 +16,11 @@ async function main() {
multiProvider.setSigner(Chains.alfajores, signer);
const core = HyperlaneCore.fromEnvironment('testnet', multiProvider);
const config = core.extendWithConnectionClientConfig(
getChainToOwnerMap(alfajoresChainConfig, signer.address),
const igp = HyperlaneIgp.fromEnvironment('testnet', multiProvider);
const config = createRouterConfigMap(
signer.address,
core.contractsMap,
igp.contractsMap,
);
console.info('Starting deployment');

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save