PI SDK support, Multiprovider refactor, Type loosening (#1804)

Breaking change to SDK types and Multiprovider interface

- Add support for PI chains in SDK classes
- Modify ChainMetadata schema
- Re-implement Multiprovider to use ChainMetadata
- Remove ChainConnection
- Remove strict chain typing throughout SDK
pull/1873/head v1.2.0
J M Rossy 2 years ago committed by GitHub
parent 0274c4f5ea
commit 31a102a0a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      solidity/package.json
  2. 2
      typescript/helloworld
  3. 8
      typescript/infra/package.json
  4. 7
      typescript/sdk/package.json
  5. 22
      typescript/sdk/src/HyperlaneApp.ts
  6. 44
      typescript/sdk/src/consts/chainConnectionConfigs.ts
  7. 134
      typescript/sdk/src/consts/chainMetadata.ts
  8. 20
      typescript/sdk/src/consts/chains.ts
  9. 4
      typescript/sdk/src/consts/environments/index.ts
  10. 5
      typescript/sdk/src/core/HyperlaneCore.test.ts
  11. 67
      typescript/sdk/src/core/HyperlaneCore.ts
  12. 20
      typescript/sdk/src/core/TestCoreApp.ts
  13. 22
      typescript/sdk/src/core/TestCoreDeployer.ts
  14. 376
      typescript/sdk/src/core/message.ts
  15. 23
      typescript/sdk/src/core/testHyperlaneDeploy.hardhat-test.ts
  16. 33
      typescript/sdk/src/deploy/HyperlaneAppChecker.ts
  17. 137
      typescript/sdk/src/deploy/HyperlaneDeployer.ts
  18. 43
      typescript/sdk/src/deploy/core/HyperlaneCoreChecker.ts
  19. 98
      typescript/sdk/src/deploy/core/HyperlaneCoreDeployer.ts
  20. 92
      typescript/sdk/src/deploy/middleware/LiquidityLayerApp.ts
  21. 40
      typescript/sdk/src/deploy/middleware/LiquidityLayerRouterDeployer.ts
  22. 28
      typescript/sdk/src/deploy/middleware/deploy.ts
  23. 26
      typescript/sdk/src/deploy/router/GasRouterDeployer.ts
  24. 19
      typescript/sdk/src/deploy/router/HyperlaneRouterChecker.ts
  25. 59
      typescript/sdk/src/deploy/router/HyperlaneRouterDeployer.ts
  26. 11
      typescript/sdk/src/deploy/types.ts
  27. 39
      typescript/sdk/src/deploy/utils.ts
  28. 30
      typescript/sdk/src/deploy/verify/ContractVerifier.ts
  29. 15
      typescript/sdk/src/domains.ts
  30. 33
      typescript/sdk/src/events.ts
  31. 13
      typescript/sdk/src/gas/calculator.hardhat-test.ts
  32. 43
      typescript/sdk/src/gas/calculator.test.ts
  33. 108
      typescript/sdk/src/gas/calculator.ts
  34. 22
      typescript/sdk/src/gas/token-prices.ts
  35. 182
      typescript/sdk/src/index.ts
  36. 27
      typescript/sdk/src/middleware/accounts.hardhat-test.ts
  37. 31
      typescript/sdk/src/middleware/liquidity-layer.hardhat-test.ts
  38. 24
      typescript/sdk/src/middleware/queries.hardhat-test.ts
  39. 86
      typescript/sdk/src/providers/ChainConnection.ts
  40. 570
      typescript/sdk/src/providers/MultiProvider.ts
  41. 19
      typescript/sdk/src/router.ts
  42. 66
      typescript/sdk/src/test/envSubsetDeployer/app.ts
  43. 17
      typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts
  44. 12
      typescript/sdk/src/test/envSubsetDeployer/deploy-single-chain.ts
  45. 39
      typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts
  46. 12
      typescript/sdk/src/test/envSubsetDeployer/utils.ts
  47. 11
      typescript/sdk/src/test/testUtils.ts
  48. 36
      typescript/sdk/src/types.ts
  49. 51
      typescript/sdk/src/utils/MultiGeneric.ts
  50. 13
      typescript/sdk/src/utils/wagmi.ts
  51. 2
      typescript/token
  52. 2
      typescript/utils/package.json
  53. 65
      yarn.lock

@ -1,9 +1,9 @@
{
"name": "@hyperlane-xyz/core",
"description": "Core solidity contracts for Hyperlane",
"version": "1.1.3",
"version": "1.2.0",
"dependencies": {
"@hyperlane-xyz/utils": "1.1.3",
"@hyperlane-xyz/utils": "1.2.0",
"@openzeppelin/contracts": "^4.8.0",
"@openzeppelin/contracts-upgradeable": "^4.8.0"
},

@ -1 +1 @@
Subproject commit 264839c0bfa3d4a4c3d4a2714e356fd7275a347b
Subproject commit b5087155c1ee3a6c5f9033150668e02481de7383

@ -1,7 +1,7 @@
{
"name": "@hyperlane-xyz/infra",
"description": "Infrastructure utilities for the Hyperlane Network",
"version": "1.1.3",
"version": "1.1.4",
"dependencies": {
"@arbitrum/sdk": "^3.0.0",
"@aws-sdk/client-iam": "^3.74.0",
@ -15,9 +15,9 @@
"@gnosis.pm/safe-ethers-lib": "^1.4.0",
"@gnosis.pm/safe-service-client": "^1.2.0",
"@hyperlane-xyz/celo-ethers-provider": "^0.1.1",
"@hyperlane-xyz/helloworld": "1.1.3",
"@hyperlane-xyz/sdk": "1.1.3",
"@hyperlane-xyz/utils": "1.1.3",
"@hyperlane-xyz/helloworld": "1.1.4",
"@hyperlane-xyz/sdk": "1.1.4",
"@hyperlane-xyz/utils": "1.1.4",
"@nomiclabs/hardhat-etherscan": "^3.0.3",
"@safe-global/safe-core-sdk": "3.2.0",
"@safe-global/safe-ethers-lib": "^1.7.0",

@ -1,11 +1,10 @@
{
"name": "@hyperlane-xyz/sdk",
"description": "The official SDK for the Hyperlane Network",
"version": "1.1.3",
"version": "1.2.0",
"dependencies": {
"@hyperlane-xyz/celo-ethers-provider": "^0.1.1",
"@hyperlane-xyz/core": "1.1.3",
"@hyperlane-xyz/utils": "1.1.3",
"@hyperlane-xyz/core": "1.2.0",
"@hyperlane-xyz/utils": "1.2.0",
"@wagmi/chains": "^0.2.6",
"coingecko-api": "^1.0.10",
"cross-fetch": "^3.1.5",

@ -5,36 +5,28 @@ import {
serializeContracts,
} from './contracts';
import { MultiProvider } from './providers/MultiProvider';
import { ChainMap, ChainName, Connection } from './types';
import { ChainMap, ChainName } from './types';
import { MultiGeneric } from './utils/MultiGeneric';
import { objMap } from './utils/objects';
export class HyperlaneApp<
Contracts extends HyperlaneContracts,
Chain extends ChainName = ChainName,
> extends MultiGeneric<Chain, Contracts> {
> extends MultiGeneric<Contracts> {
constructor(
public readonly contractsMap: ChainMap<Chain, Contracts>,
public readonly multiProvider: MultiProvider<Chain>,
public readonly contractsMap: ChainMap<Contracts>,
public readonly multiProvider: MultiProvider,
) {
const connectedContractsMap = objMap(contractsMap, (chain, contracts) =>
connectContracts(
contracts,
multiProvider.getChainConnection(chain).getConnection(),
),
connectContracts(contracts, multiProvider.getSignerOrProvider(chain)),
);
super(connectedContractsMap);
}
getContracts(chain: Chain): Contracts {
getContracts(chain: ChainName): Contracts {
return this.get(chain);
}
getAddresses(chain: Chain): HyperlaneAddresses {
getAddresses(chain: ChainName): HyperlaneAddresses {
return serializeContracts(this.get(chain));
}
connectToChain(chain: Chain, connection: Connection): void {
this.set(chain, connectContracts(this.get(chain), connection));
}
}

@ -1,44 +0,0 @@
import { ethers } from 'ethers';
import { StaticCeloJsonRpcProvider } from '@hyperlane-xyz/celo-ethers-provider';
import { ChainMap, ChainName, IChainConnection } from '../types';
import { objMap } from '../utils/objects';
import { chainMetadata, test1, test2, test3 } from './chainMetadata';
import { Chains, TestChains } from './chains';
function testChainConnection(id: number) {
return {
id,
provider: new ethers.providers.JsonRpcProvider(
'http://localhost:8545',
31337,
),
confirmations: 1,
};
}
export const chainConnectionConfigs: ChainMap<ChainName, IChainConnection> =
objMap(chainMetadata, (chainName, metadata) => {
if (TestChains.includes(chainName)) return testChainConnection(metadata.id);
const providerClass =
chainName === Chains.alfajores || chainName === Chains.celo
? StaticCeloJsonRpcProvider
: ethers.providers.JsonRpcProvider;
return {
id: metadata.id,
provider: new providerClass(metadata.publicRpcUrls[0].http, metadata.id),
confirmations: metadata.blocks.confirmations,
blockExplorerUrl: metadata.blockExplorers[0].url,
blockExplorerApiUrl: metadata.blockExplorers[0].apiUrl,
};
});
export const testChainConnectionConfigs = {
test1: testChainConnection(test1.id),
test2: testChainConnection(test2.id),
test3: testChainConnection(test3.id),
};

@ -1,9 +1,11 @@
import type { Chain as WagmiChain } from '@wagmi/chains';
import type { providers } from 'ethers';
import { ChainName } from '../types';
import { objMap } from '../utils/objects';
import { chainMetadataToWagmiChain } from '../utils/wagmi';
import { ChainName, Chains, Mainnets, Testnets } from './chains';
import { Chains, Mainnets, Testnets } from './chains';
export enum ExplorerFamily {
Etherscan = 'etherscan',
@ -16,14 +18,16 @@ export enum ExplorerFamily {
* for Hyperlane-supported chains
*/
export interface ChainMetadata {
id: number;
chainId: number;
/** Hyperlane domain, only required if differs from id above */
domainId?: number;
name: ChainName;
/** Human-readable name */
displayName: string;
displayName?: string;
/** Shorter human-readable name */
displayNameShort?: string;
/** Default currency/token used by chain */
nativeToken: {
nativeToken?: {
name: string;
symbol: string;
decimals: number;
@ -35,25 +39,28 @@ export interface ChainMetadata {
pagination?: RpcPagination;
}>;
/** Collection of block explorers */
blockExplorers: Array<{
blockExplorers?: Array<{
name: string;
url: string;
family: ExplorerFamily;
family?: ExplorerFamily;
apiUrl?: string;
}>;
blocks: {
// Number of blocks to wait before considering a transaction confirmed
blocks?: {
/** Number of blocks to wait before considering a transaction confirmed */
confirmations: number;
// TODO consider merging with confirmations, requires agent code changes
// Number of blocks before a transaction has a near-zero chance of reverting
reorgPeriod: number;
// Rough estimate of time per block in seconds
estimateBlockTime: number;
// TODO consider merging with confirmations, requires agent code changes */
/** Number of blocks before a transaction has a near-zero chance of reverting */
reorgPeriod?: number;
/** Rough estimate of time per block in seconds */
estimateBlockTime?: number;
};
// The CoinGecko API sometimes expects IDs that do not match ChainNames
transactionOverrides?: Partial<providers.TransactionRequest>;
/** The CoinGecko API sometimes expects IDs that do not match ChainNames */
gasCurrencyCoinGeckoId?: string;
// URL of the gnosis safe transaction service.
/** URL of the gnosis safe transaction service */
gnosisSafeTransactionServiceUrl?: string;
/** Is chain a testnet or a mainnet */
isTestnet?: boolean;
}
export interface RpcPagination {
@ -64,31 +71,31 @@ export interface RpcPagination {
/**
* Common native currencies
*/
const avaxToken = {
export const avaxToken = {
decimals: 18,
name: 'Avalanche',
symbol: 'AVAX',
};
const bnbToken = {
export const bnbToken = {
decimals: 18,
name: 'BNB',
symbol: 'BNB',
};
const celoToken = {
export const celoToken = {
decimals: 18,
name: 'CELO',
symbol: 'CELO',
};
const etherToken = { name: 'Ether', symbol: 'ETH', decimals: 18 };
const maticToken = { name: 'MATIC', symbol: 'MATIC', decimals: 18 };
const xDaiToken = { name: 'xDai', symbol: 'xDai', decimals: 18 };
export const etherToken = { name: 'Ether', symbol: 'ETH', decimals: 18 };
export const maticToken = { name: 'MATIC', symbol: 'MATIC', decimals: 18 };
export const xDaiToken = { name: 'xDai', symbol: 'xDai', decimals: 18 };
/**
* Chain metadata
*/
export const alfajores: ChainMetadata = {
id: 44787,
chainId: 44787,
name: Chains.alfajores,
displayName: 'Alfajores',
nativeToken: celoToken,
@ -97,7 +104,7 @@ export const alfajores: ChainMetadata = {
{
name: 'CeloScan',
url: 'https://alfajores.celoscan.io',
apiUrl: 'https://api-alfajores.celoscan.io/',
apiUrl: 'https://api-alfajores.celoscan.io',
family: ExplorerFamily.Etherscan,
},
{
@ -111,10 +118,11 @@ export const alfajores: ChainMetadata = {
reorgPeriod: 0,
estimateBlockTime: 5,
},
isTestnet: true,
};
export const arbitrum: ChainMetadata = {
id: 42161,
chainId: 42161,
name: Chains.arbitrum,
displayName: 'Arbitrum',
nativeToken: etherToken,
@ -138,7 +146,7 @@ export const arbitrum: ChainMetadata = {
};
export const arbitrumgoerli: ChainMetadata = {
id: 421613,
chainId: 421613,
name: Chains.arbitrumgoerli,
displayName: 'Arbitrum Goerli',
displayNameShort: 'Arb. Goerli',
@ -147,7 +155,7 @@ export const arbitrumgoerli: ChainMetadata = {
blockExplorers: [
{
name: 'Arbiscan',
url: 'https://goerli.arbiscan.io/',
url: 'https://goerli.arbiscan.io',
apiUrl: 'https://api-goerli.arbiscan.io',
family: ExplorerFamily.Etherscan,
},
@ -157,10 +165,11 @@ export const arbitrumgoerli: ChainMetadata = {
reorgPeriod: 1,
estimateBlockTime: 3,
},
isTestnet: true,
};
export const avalanche: ChainMetadata = {
id: 43114,
chainId: 43114,
name: Chains.avalanche,
displayName: 'Avalanche',
nativeToken: avaxToken,
@ -192,7 +201,7 @@ export const avalanche: ChainMetadata = {
};
export const bsc: ChainMetadata = {
id: 56,
chainId: 56,
name: Chains.bsc,
displayName: 'Binance Smart Chain',
displayNameShort: 'Binance',
@ -219,7 +228,7 @@ export const bsc: ChainMetadata = {
};
export const bsctestnet: ChainMetadata = {
id: 97,
chainId: 97,
name: Chains.bsctestnet,
displayName: 'BSC Testnet',
nativeToken: bnbToken,
@ -237,10 +246,11 @@ export const bsctestnet: ChainMetadata = {
reorgPeriod: 9,
estimateBlockTime: 3,
},
isTestnet: true,
};
export const celo: ChainMetadata = {
id: 42220,
chainId: 42220,
name: Chains.celo,
displayName: 'Celo',
nativeToken: celoToken,
@ -268,7 +278,7 @@ export const celo: ChainMetadata = {
};
export const ethereum: ChainMetadata = {
id: 1,
chainId: 1,
name: Chains.ethereum,
displayName: 'Ethereum',
nativeToken: etherToken,
@ -295,7 +305,7 @@ export const ethereum: ChainMetadata = {
};
export const fuji: ChainMetadata = {
id: 43113,
chainId: 43113,
name: Chains.fuji,
displayName: 'Fuji',
nativeToken: avaxToken,
@ -313,10 +323,11 @@ export const fuji: ChainMetadata = {
reorgPeriod: 3,
estimateBlockTime: 2,
},
isTestnet: true,
};
export const goerli: ChainMetadata = {
id: 5,
chainId: 5,
name: Chains.goerli,
displayName: 'Goerli',
nativeToken: etherToken,
@ -338,10 +349,11 @@ export const goerli: ChainMetadata = {
reorgPeriod: 2,
estimateBlockTime: 13,
},
isTestnet: true,
};
export const moonbasealpha: ChainMetadata = {
id: 1287,
chainId: 1287,
name: Chains.moonbasealpha,
displayName: 'Moonbase Alpha',
displayNameShort: 'Moonbase',
@ -364,10 +376,11 @@ export const moonbasealpha: ChainMetadata = {
reorgPeriod: 1,
estimateBlockTime: 12,
},
isTestnet: true,
};
export const moonbeam: ChainMetadata = {
id: 1284,
chainId: 1284,
name: Chains.moonbeam,
displayName: 'Moonbeam',
nativeToken: {
@ -394,7 +407,7 @@ export const moonbeam: ChainMetadata = {
};
export const mumbai: ChainMetadata = {
id: 80001,
chainId: 80001,
name: Chains.mumbai,
displayName: 'Mumbai',
nativeToken: maticToken,
@ -424,10 +437,11 @@ export const mumbai: ChainMetadata = {
reorgPeriod: 32,
estimateBlockTime: 5,
},
isTestnet: true,
};
export const optimism: ChainMetadata = {
id: 10,
chainId: 10,
name: Chains.optimism,
displayName: 'Optimism',
nativeToken: etherToken,
@ -451,7 +465,7 @@ export const optimism: ChainMetadata = {
};
export const optimismgoerli: ChainMetadata = {
id: 420,
chainId: 420,
name: Chains.optimismgoerli,
displayName: 'Optimism Goerli',
displayNameShort: 'Opt. Goerli',
@ -470,10 +484,11 @@ export const optimismgoerli: ChainMetadata = {
reorgPeriod: 1,
estimateBlockTime: 3,
},
isTestnet: true,
};
export const polygon: ChainMetadata = {
id: 137,
chainId: 137,
name: Chains.polygon,
displayName: 'Polygon',
nativeToken: etherToken,
@ -507,7 +522,7 @@ export const polygon: ChainMetadata = {
};
export const gnosis: ChainMetadata = {
id: 100,
chainId: 100,
name: Chains.gnosis,
displayName: 'Gnosis',
nativeToken: xDaiToken,
@ -538,7 +553,7 @@ export const gnosis: ChainMetadata = {
};
export const test1: ChainMetadata = {
id: 13371,
chainId: 13371,
name: Chains.test1,
displayName: 'Test 1',
nativeToken: etherToken,
@ -549,10 +564,11 @@ export const test1: ChainMetadata = {
reorgPeriod: 0,
estimateBlockTime: 3,
},
isTestnet: true,
};
export const test2: ChainMetadata = {
id: 13372,
chainId: 13372,
name: Chains.test2,
displayName: 'Test 2',
nativeToken: etherToken,
@ -563,10 +579,11 @@ export const test2: ChainMetadata = {
reorgPeriod: 1,
estimateBlockTime: 3,
},
isTestnet: true,
};
export const test3: ChainMetadata = {
id: 13373,
chainId: 13373,
name: Chains.test3,
displayName: 'Test 3',
nativeToken: etherToken,
@ -577,6 +594,7 @@ export const test3: ChainMetadata = {
reorgPeriod: 2,
estimateBlockTime: 3,
},
isTestnet: true,
};
/**
@ -617,7 +635,7 @@ export const wagmiChainMetadata: Record<ChainName, WagmiChain> = objMap(
export const chainIdToMetadata = Object.values(chainMetadata).reduce<
Record<number, ChainMetadata>
>((result, chain) => {
result[chain.id] = chain;
result[chain.chainId] = chain;
return result;
}, {});
@ -627,31 +645,3 @@ export const mainnetChainsMetadata: Array<ChainMetadata> = Mainnets.map(
export const testnetChainsMetadata: Array<ChainMetadata> = Testnets.map(
(chainName) => chainMetadata[chainName],
);
/**
* @deprecated use ChainMetadata
*/
export type PartialChainMetadata = {
id: number;
finalityBlocks: number;
nativeTokenDecimals?: number;
paginate?: RpcPagination;
// The CoinGecko API expects, in some cases, IDs that do not match
// ChainNames.
gasCurrencyCoinGeckoId?: string;
// URL of the gnosis safe transaction service.
gnosisSafeTransactionServiceUrl?: string;
};
/**
* @deprecated use chainMetadata
*/
export const partialChainMetadata: Record<ChainName, PartialChainMetadata> =
objMap(chainMetadata, (_, metadata) => ({
id: metadata.id,
finalityBlocks: metadata.blocks.confirmations,
nativeTokenDecimals: metadata.nativeToken.decimals,
paginate: metadata.publicRpcUrls[0]?.pagination,
gasCurrencyCoinGeckoId: metadata.gasCurrencyCoinGeckoId,
gnosisSafeTransactionServiceUrl: metadata.gnosisSafeTransactionServiceUrl,
}));

@ -25,7 +25,7 @@ export enum Chains {
test3 = 'test3',
}
export type ChainName = keyof typeof Chains;
export type CoreChainName = keyof typeof Chains;
export enum DeprecatedChains {
arbitrumkovan = 'arbitrumkovan',
@ -38,7 +38,7 @@ export enum DeprecatedChains {
export const AllDeprecatedChains = Object.keys(DeprecatedChains) as string[];
export const Mainnets = [
export const Mainnets: Array<CoreChainName> = [
Chains.arbitrum,
Chains.avalanche,
Chains.bsc,
@ -48,9 +48,9 @@ export const Mainnets = [
Chains.optimism,
Chains.polygon,
Chains.gnosis,
] as Array<ChainName>;
];
export const Testnets = [
export const Testnets: Array<CoreChainName> = [
Chains.alfajores,
Chains.arbitrumgoerli,
Chains.bsctestnet,
@ -59,12 +59,16 @@ export const Testnets = [
Chains.moonbasealpha,
Chains.mumbai,
Chains.optimismgoerli,
] as Array<ChainName>;
];
export const TestChains = [
export const TestChains: Array<CoreChainName> = [
Chains.test1,
Chains.test2,
Chains.test3,
] as Array<ChainName>;
];
export const AllChains = Object.keys(Chains) as Array<ChainName>;
export const AllChains: Array<CoreChainName> = [
...Mainnets,
...TestChains,
...TestChains,
];

@ -1,6 +1,6 @@
import { types } from '@hyperlane-xyz/utils';
import { LooseChainMap } from '../../types';
import { ChainMap } from '../../types';
import { objMap } from '../../utils/objects';
import mainnet from './mainnet.json';
@ -9,7 +9,7 @@ import testnet from './testnet.json';
export const environments = { test, testnet, mainnet };
type HyperlaneCoreAddressMap = LooseChainMap<{
type HyperlaneCoreAddressMap = ChainMap<{
mailbox: types.Address;
multisigIsm: types.Address;
interchainGasPaymaster: types.Address;

@ -1,4 +1,3 @@
import { chainConnectionConfigs } from '../consts/chainConnectionConfigs';
import { MultiProvider } from '../providers/MultiProvider';
import { HyperlaneCore } from './HyperlaneCore';
@ -6,11 +5,11 @@ import { HyperlaneCore } from './HyperlaneCore';
describe('HyperlaneCore', () => {
describe('fromEnvironment', () => {
it('creates an object for mainnet', async () => {
const multiProvider = new MultiProvider(chainConnectionConfigs);
const multiProvider = new MultiProvider();
HyperlaneCore.fromEnvironment('mainnet', multiProvider);
});
it('creates an object for testnet', async () => {
const multiProvider = new MultiProvider(chainConnectionConfigs);
const multiProvider = new MultiProvider();
HyperlaneCore.fromEnvironment('testnet', multiProvider);
});
});

@ -6,8 +6,6 @@ import { types, utils } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { environments } from '../consts/environments';
import { buildContracts } from '../contracts';
import { DomainIdToChainName } from '../domains';
import { ChainConnection } from '../providers/ChainConnection';
import { MultiProvider } from '../providers/MultiProvider';
import { ConnectionClientConfig } from '../router';
import { ChainMap, ChainName } from '../types';
@ -21,8 +19,8 @@ export type CoreEnvironmentChain<E extends CoreEnvironment> = Extract<
ChainName
>;
export type CoreContractsMap<Chain extends ChainName> = {
[local in Chain]: CoreContracts;
export type CoreContractsMap = {
[chain: ChainName]: CoreContracts;
};
export type DispatchedMessage = {
@ -31,51 +29,39 @@ export type DispatchedMessage = {
parsed: types.ParsedMessage;
};
export class HyperlaneCore<
Chain extends ChainName = ChainName,
> extends HyperlaneApp<CoreContracts, Chain> {
constructor(
contractsMap: CoreContractsMap<Chain>,
multiProvider: MultiProvider<Chain>,
) {
export class HyperlaneCore extends HyperlaneApp<CoreContracts> {
constructor(contractsMap: CoreContractsMap, multiProvider: MultiProvider) {
super(contractsMap, multiProvider);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
static fromEnvironment<
Env extends CoreEnvironment,
Chain extends ChainName = ChainName,
>(env: Env, multiProvider: MultiProvider<Chain>) {
static fromEnvironment<Env extends CoreEnvironment>(
env: Env,
multiProvider: MultiProvider,
): HyperlaneCore {
const envConfig = environments[env];
if (!envConfig) {
throw new Error(`No default env config found for ${env}`);
}
type EnvChain = keyof typeof envConfig;
type IntersectionChain = EnvChain & Chain;
const envChains = Object.keys(envConfig) as IntersectionChain[];
const envChains = Object.keys(envConfig);
const { intersection, multiProvider: intersectionProvider } =
multiProvider.intersect<IntersectionChain>(envChains);
multiProvider.intersect(envChains, true);
const intersectionConfig = pick(
envConfig as ChainMap<Chain, any>,
intersection,
);
const intersectionConfig = pick(envConfig, intersection);
const contractsMap = buildContracts(
intersectionConfig,
coreFactories,
) as CoreContractsMap<IntersectionChain>;
) as CoreContractsMap;
return new HyperlaneCore(contractsMap, intersectionProvider);
}
// override type to be derived from chain key
getContracts<Local extends Chain>(chain: Local): CoreContracts {
getContracts(chain: ChainName): CoreContracts {
return super.getContracts(chain);
}
getConnectionClientConfig(chain: Chain): ConnectionClientConfig {
getConnectionClientConfig(chain: ChainName): ConnectionClientConfig {
const contracts = this.getContracts(chain);
return {
mailbox: contracts.mailbox.address,
@ -85,15 +71,15 @@ export class HyperlaneCore<
};
}
getConnectionClientConfigMap(): ChainMap<Chain, ConnectionClientConfig> {
getConnectionClientConfigMap(): ChainMap<ConnectionClientConfig> {
return objMap(this.contractsMap, (chain) =>
this.getConnectionClientConfig(chain),
);
}
extendWithConnectionClientConfig<T>(
configMap: ChainMap<Chain, T>,
): ChainMap<Chain, T & ConnectionClientConfig> {
configMap: ChainMap<T>,
): ChainMap<T & ConnectionClientConfig> {
const connectionClientConfigMap = this.getConnectionClientConfigMap();
return objMap(configMap, (chain, config) => {
return {
@ -104,23 +90,21 @@ export class HyperlaneCore<
}
protected getDestination(message: DispatchedMessage): {
destinationChain: ChainName;
mailbox: Mailbox;
chainConnection: ChainConnection;
} {
const destinationChain = DomainIdToChainName[
message.parsed.destination
] as Chain;
const destinationChain = this.multiProvider.getChainName(
message.parsed.destination,
);
const mailbox = this.getContracts(destinationChain).mailbox.contract;
const chainConnection =
this.multiProvider.getChainConnection(destinationChain);
return { mailbox, chainConnection };
return { destinationChain, mailbox };
}
protected waitForProcessReceipt(
message: DispatchedMessage,
): Promise<ethers.ContractReceipt> {
const id = utils.messageId(message.message);
const { mailbox, chainConnection } = this.getDestination(message);
const { destinationChain, mailbox } = this.getDestination(message);
const filter = mailbox.filters.ProcessId(id);
return new Promise<ethers.ContractReceipt>((resolve, reject) => {
@ -128,8 +112,9 @@ export class HyperlaneCore<
if (id !== emittedId) {
reject(`Expected message id ${id} but got ${emittedId}`);
}
// @ts-ignore
resolve(chainConnection.handleTx(event.getTransaction()));
resolve(
this.multiProvider.handleTx(destinationChain, event.getTransaction()),
);
});
});
}

@ -3,10 +3,8 @@ import { ethers } from 'ethers';
import { TestMailbox } from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../consts/chainMetadata';
import { DomainIdToChainName } from '../domains';
import { ProxiedContract } from '../proxy';
import { ChainName, TestChainNames } from '../types';
import { ChainName } from '../types';
import { HyperlaneCore } from './HyperlaneCore';
import { CoreContracts } from './contracts';
@ -21,15 +19,13 @@ export type TestCoreContracts = CoreContracts & {
mailbox: ProxiedContract<TestMailbox, MockProxyAddresses>;
};
export class TestCoreApp<
TestChain extends TestChainNames = TestChainNames,
> extends HyperlaneCore<TestChain> {
getContracts<Local extends TestChain>(chain: Local): TestCoreContracts {
export class TestCoreApp extends HyperlaneCore {
getContracts(chain: ChainName): TestCoreContracts {
return super.getContracts(chain) as TestCoreContracts;
}
async processMessages(): Promise<
Map<TestChain, Map<TestChain, ethers.providers.TransactionResponse[]>>
Map<ChainName, Map<ChainName, ethers.providers.TransactionResponse[]>>
> {
const responses = new Map();
for (const origin of this.chains()) {
@ -43,8 +39,8 @@ export class TestCoreApp<
return responses;
}
async processOutboundMessages<Local extends TestChain>(
origin: Local,
async processOutboundMessages(
origin: ChainName,
): Promise<Map<ChainName, ethers.providers.TransactionResponse[]>> {
const responses = new Map<ChainName, any>();
const contracts = this.getContracts(origin);
@ -54,10 +50,10 @@ export class TestCoreApp<
const dispatches = await outbox.queryFilter(dispatchFilter);
for (const dispatch of dispatches) {
const destination = dispatch.args.destination;
if (destination === chainMetadata[origin].id) {
if (destination === this.multiProvider.getDomainId(origin)) {
throw new Error('Dispatched message to local domain');
}
const destinationChain = DomainIdToChainName[destination] as TestChain;
const destinationChain = this.multiProvider.getChainName(destination);
const inbox = this.getContracts(destinationChain).mailbox.contract;
const id = utils.messageId(dispatch.args.message);
const delivered = await inbox.delivered(id);

@ -9,7 +9,7 @@ import {
import { HyperlaneCoreDeployer } from '../deploy/core/HyperlaneCoreDeployer';
import { CoreConfig } from '../deploy/core/types';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, TestChainNames } from '../types';
import { ChainMap, ChainName } from '../types';
import { TestCoreApp } from './TestCoreApp';
import { coreFactories } from './contracts';
@ -31,29 +31,23 @@ const testCoreFactories = {
testIsm: new TestIsm__factory(),
};
export class TestCoreDeployer<
TestChain extends TestChainNames = TestChainNames,
> extends HyperlaneCoreDeployer<TestChain> {
export class TestCoreDeployer extends HyperlaneCoreDeployer {
constructor(
public readonly multiProvider: MultiProvider<TestChain>,
configMap?: ChainMap<TestChain, CoreConfig>,
public readonly multiProvider: MultiProvider,
configMap?: ChainMap<CoreConfig>,
) {
// Note that the multisig module configs are unused.
const configs =
configMap ??
({
const configs = configMap ?? {
test1: testMultisigIsmConfig,
test2: testMultisigIsmConfig,
test3: testMultisigIsmConfig,
} as ChainMap<TestChain, CoreConfig>); // cast so param can be optional
};
super(multiProvider, configs, testCoreFactories);
}
// deploy a test ISM in place of a multisig ISM
async deployMultisigIsm<LocalChain extends TestChain>(
chain: LocalChain,
): Promise<MultisigIsm> {
async deployMultisigIsm(chain: ChainName): Promise<MultisigIsm> {
const testIsm = await this.deployContractFromFactory(
chain,
testCoreFactories.testIsm,
@ -69,7 +63,7 @@ export class TestCoreDeployer<
return [];
}
async deployApp(): Promise<TestCoreApp<TestChain>> {
async deployApp(): Promise<TestCoreApp> {
return new TestCoreApp(await this.deploy(), this.multiProvider);
}
}

@ -1,376 +0,0 @@
import { utils as ethersUtils, providers } from 'ethers';
import { Mailbox, Mailbox__factory } from '@hyperlane-xyz/core';
import { types, utils } from '@hyperlane-xyz/utils';
import { ChainNameToDomainId, DomainIdToChainName } from '../domains';
import { Annotated, findAnnotatedSingleEvent } from '../events';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainName, NameOrDomain } from '../types';
import { delay } from '../utils/time';
import { HyperlaneCore } from './HyperlaneCore';
import {
AnnotatedDispatch,
AnnotatedLifecycleEvent,
AnnotatedProcess,
DispatchEvent,
ProcessEvent,
} from './events';
export const resolveDomain = (nameOrDomain: NameOrDomain): ChainName =>
typeof nameOrDomain === 'number'
? DomainIdToChainName[nameOrDomain]
: nameOrDomain;
export const resolveId = (nameOrDomain: NameOrDomain): number =>
typeof nameOrDomain === 'string'
? ChainNameToDomainId[nameOrDomain]
: nameOrDomain;
export const resolveChains = (
message: types.ParsedMessage,
): { origin: ChainName; destination: ChainName } => {
return {
origin: resolveDomain(message.origin),
destination: resolveDomain(message.destination),
};
};
export type HyperlaneStatus = {
status: MessageStatus;
events: AnnotatedLifecycleEvent[];
};
export enum MessageStatus {
Dispatched = 0,
Included = 1,
Relayed = 2,
Processed = 3,
}
export type EventCache = {
process?: AnnotatedProcess;
};
// TODO: move HyperlaneMessage into HyperlaneCore app
/**
* A deserialized Hyperlane message.
*/
export class HyperlaneMessage {
readonly dispatch: AnnotatedDispatch;
readonly message: types.ParsedMessage;
readonly outbox: Mailbox;
readonly inbox: Mailbox;
readonly multiProvider: MultiProvider;
readonly core: HyperlaneCore;
protected cache: EventCache;
constructor(
multiProvider: MultiProvider,
core: HyperlaneCore,
dispatch: AnnotatedDispatch,
) {
this.multiProvider = multiProvider;
this.core = core;
this.message = utils.parseMessage(dispatch.event.args.message);
this.dispatch = dispatch;
const messageChains = resolveChains(this.message);
this.outbox = core.getContracts(messageChains.origin).mailbox.contract;
this.inbox = core.getContracts(messageChains.destination).mailbox.contract;
this.cache = {};
}
/**
* The receipt of the TX that dispatched this message
*/
get receipt(): providers.TransactionReceipt {
return this.dispatch.receipt;
}
/**
* Instantiate one or more messages from a receipt.
*
* @param core the {@link HyperlaneCore} object to use
* @param nameOrDomain the domain on which the receipt was logged
* @param receipt the receipt
* @returns an array of {@link HyperlaneMessage} objects
*/
static fromReceipt(
multiProvider: MultiProvider,
core: HyperlaneCore,
nameOrDomain: NameOrDomain,
receipt: providers.TransactionReceipt,
): HyperlaneMessage[] {
const messages: HyperlaneMessage[] = [];
const outbox = new Mailbox__factory().interface;
const chain = resolveDomain(nameOrDomain);
const provider = multiProvider.getChainConnection(chain).provider!;
for (const log of receipt.logs) {
try {
const parsed = outbox.parseLog(log);
if (parsed.name === 'Dispatch') {
const dispatch = {
...parsed,
getBlock: () => provider.getBlock(log.blockHash),
getTransaction: () => provider.getTransaction(log.transactionHash),
getTransactionReceipt: () =>
provider.getTransactionReceipt(log.transactionHash),
} as unknown as DispatchEvent;
const annotated = new Annotated<DispatchEvent>(
resolveId(nameOrDomain),
receipt,
dispatch,
true,
);
annotated.event.blockNumber = annotated.receipt.blockNumber;
const message = new HyperlaneMessage(multiProvider, core, annotated);
messages.push(message);
}
} catch (e) {
continue;
}
}
return messages;
}
/**
* Instantiate EXACTLY one message from a receipt.
*
* @param core the {@link HyperlaneCore} object to use
* @param nameOrDomain the domain on which the receipt was logged
* @param receipt the receipt
* @returns an array of {@link HyperlaneMessage} objects
* @throws if there is not EXACTLY 1 dispatch in the receipt
*/
static singleFromReceipt(
multiProvider: MultiProvider,
core: HyperlaneCore,
nameOrDomain: NameOrDomain,
receipt: providers.TransactionReceipt,
): HyperlaneMessage {
const messages: HyperlaneMessage[] = HyperlaneMessage.fromReceipt(
multiProvider,
core,
nameOrDomain,
receipt,
);
if (messages.length !== 1) {
throw new Error('Expected single Dispatch in transaction');
}
return messages[0];
}
/**
* Instantiate one or more messages from a tx hash.
*
* @param core the {@link HyperlaneCore} object to use
* @param nameOrDomain the domain on which the receipt was logged
* @param receipt the receipt
* @returns an array of {@link HyperlaneMessage} objects
* @throws if there is no receipt for the TX
*/
static async fromTransactionHash(
multiProvider: MultiProvider,
core: HyperlaneCore,
nameOrDomain: NameOrDomain,
transactionHash: string,
): Promise<HyperlaneMessage[]> {
const provider = multiProvider.getChainConnection(
resolveDomain(nameOrDomain),
).provider!;
const receipt = await provider.getTransactionReceipt(transactionHash);
if (!receipt) {
throw new Error(`No receipt for ${transactionHash} on ${nameOrDomain}`);
}
return HyperlaneMessage.fromReceipt(
multiProvider,
core,
nameOrDomain,
receipt,
);
}
/**
* Instantiate EXACTLY one message from a transaction has.
*
* @param core the {@link HyperlaneCore} object to use
* @param nameOrDomain the domain on which the receipt was logged
* @param receipt the receipt
* @returns an array of {@link HyperlaneMessage} objects
* @throws if there is no receipt for the TX, or if not EXACTLY 1 dispatch in
* the receipt
*/
static async singleFromTransactionHash(
multiProvider: MultiProvider,
core: HyperlaneCore,
nameOrDomain: NameOrDomain,
transactionHash: string,
): Promise<HyperlaneMessage> {
const provider = multiProvider.getChainConnection(
resolveDomain(nameOrDomain),
).provider!;
const receipt = await provider.getTransactionReceipt(transactionHash);
if (!receipt) {
throw new Error(`No receipt for ${transactionHash} on ${nameOrDomain}`);
}
return HyperlaneMessage.singleFromReceipt(
multiProvider,
core,
nameOrDomain,
receipt,
);
}
/**
* Get the Inbox `Process` event associated with this message (if any)
*
* @returns An {@link AnnotatedProcess} (if any)
*/
async getProcess(): Promise<AnnotatedProcess | undefined> {
// if we have already gotten the event,
// return it without re-querying
if (this.cache.process) {
return this.cache.process;
}
// if not, attempt to query the event
const processFilter = this.inbox.filters.Process(this.id);
const processLogs = await findAnnotatedSingleEvent<ProcessEvent>(
this.multiProvider,
this.destinationName,
this.inbox,
processFilter,
);
if (processLogs.length === 1) {
// if event is returned, store it to the object
this.cache.process = processLogs[0];
} else if (processLogs.length > 1) {
throw new Error('multiple inbox process for same message');
}
// return the process or undefined if it doesn't exist
return this.cache.process;
}
/**
* Get all lifecycle events associated with this message
*
* @returns An array of {@link AnnotatedLifecycleEvent} objects
*/
async events(): Promise<HyperlaneStatus> {
const events: AnnotatedLifecycleEvent[] = [this.dispatch];
// attempt to get Inbox process
const process = await this.getProcess();
if (!process) {
// NOTE: when this is the status, you may way to
// query confirmAt() to check if challenge period
// on the Inbox has elapsed or not
return {
status: MessageStatus.Relayed, // the message was sent, included in an Checkpoint, then relayed to the Inbox
events,
};
}
events.push(process);
return {
status: MessageStatus.Processed, // the message was processed
events,
};
}
/**
* Checks whether the message has been delivered.
*
* @returns true if processed, else false.
*/
async delivered(): Promise<boolean> {
return this.inbox.delivered(this.id);
}
/**
* Returns a promise that resolves when the message has been delivered.
*
* WARNING: May never resolve. Oftern takes hours to resolve.
*
* @param opts Polling options.
*/
async wait(opts?: { pollTime?: number }): Promise<void> {
const interval = opts?.pollTime ?? 5000;
// sad spider face
for (;;) {
if (await this.delivered()) {
return;
}
await delay(interval);
}
}
/**
* The domain from which the message was sent.
*/
get origin(): number {
return this.message.origin;
}
get originName(): ChainName {
return resolveDomain(this.origin);
}
/**
* The identifier for the sender of this message
*/
get sender(): string {
return this.message.sender;
}
/**
* The destination domain for this message
*/
get destination(): number {
return this.message.destination;
}
get destinationName(): ChainName {
return resolveDomain(this.destination);
}
/**
* The identifer for the recipient for this message
*/
get recipient(): string {
return this.message.recipient;
}
/**
* The message body
*/
get body(): string {
return this.message.body;
}
/**
* The keccak256 hash of the message body
*/
get bodyHash(): string {
return ethersUtils.keccak256(this.body);
}
/**
* The hash of the transaction that dispatched this message
*/
get transactionHash(): string {
return this.dispatch.event.transactionHash;
}
/**
* The messageId committed to the tree in the Mailbox contract.
*/
get id(): string {
return utils.messageId(this.dispatch.event.args.message);
}
}

@ -7,16 +7,14 @@ import { ethers } from 'hardhat';
import { TestMailbox, TestRecipient__factory } from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../consts/chainMetadata';
import { getTestMultiProvider } from '../deploy/utils';
import { Chains } from '../consts/chains';
import { MultiProvider } from '../providers/MultiProvider';
import { TestCoreApp } from './TestCoreApp';
import { TestCoreDeployer } from './TestCoreDeployer';
const localChain = 'test1';
const localDomain = chainMetadata[localChain].id;
const remoteChain = 'test2';
const remoteDomain = chainMetadata[remoteChain].id;
const localChain = Chains.test1;
const remoteChain = Chains.test2;
const message = '0xdeadbeef';
describe('TestCoreDeployer', async () => {
@ -28,7 +26,7 @@ describe('TestCoreDeployer', async () => {
beforeEach(async () => {
const [signer] = await ethers.getSigners();
const multiProvider = getTestMultiProvider(signer);
const multiProvider = MultiProvider.createTestMultiProvider({ signer });
const deployer = new TestCoreDeployer(multiProvider);
testCoreApp = await deployer.deployApp();
@ -36,18 +34,19 @@ describe('TestCoreDeployer', async () => {
localMailbox = testCoreApp.getContracts(localChain).mailbox.contract;
const dispatchResponse = localMailbox.dispatch(
remoteDomain,
multiProvider.getDomainId(remoteChain),
utils.addressToBytes32(recipient.address),
message,
);
await expect(dispatchResponse).to.emit(localMailbox, 'Dispatch');
dispatchReceipt = await testCoreApp.multiProvider
.getChainConnection(localChain)
.handleTx(dispatchResponse);
dispatchReceipt = await testCoreApp.multiProvider.handleTx(
localChain,
dispatchResponse,
);
remoteMailbox = testCoreApp.getContracts(remoteChain).mailbox.contract;
await expect(
remoteMailbox.dispatch(
localDomain,
multiProvider.getDomainId(localChain),
utils.addressToBytes32(recipient.address),
message,
),

@ -1,8 +1,8 @@
import { keccak256 } from 'ethers/lib/utils';
import { Ownable } from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils';
import type { types } from '@hyperlane-xyz/utils';
import { utils } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../HyperlaneApp';
import { MultiProvider } from '../providers/MultiProvider';
@ -19,19 +19,18 @@ import {
} from './types';
export abstract class HyperlaneAppChecker<
Chain extends ChainName,
App extends HyperlaneApp<any, Chain>,
App extends HyperlaneApp<any>,
Config,
> {
readonly multiProvider: MultiProvider<Chain>;
readonly multiProvider: MultiProvider;
readonly app: App;
readonly configMap: ChainMap<Chain, Config>;
readonly configMap: ChainMap<Config>;
readonly violations: CheckerViolation[];
constructor(
multiProvider: MultiProvider<Chain>,
multiProvider: MultiProvider,
app: App,
configMap: ChainMap<Chain, Config>,
configMap: ChainMap<Config>,
) {
this.multiProvider = multiProvider;
this.app = app;
@ -39,15 +38,15 @@ export abstract class HyperlaneAppChecker<
this.configMap = configMap;
}
abstract checkChain(chain: Chain): Promise<void>;
abstract checkChain(chain: ChainName): Promise<void>;
async check(): Promise<void[]> {
Object.keys(this.configMap)
.filter((_) => !this.app.chains().includes(_ as Chain))
.filter((_) => !this.app.chains().includes(_))
.forEach((chain: string) =>
this.addViolation({
type: ViolationType.NotDeployed,
chain: chain as Chain,
chain,
expected: '',
actual: '',
}),
@ -63,14 +62,14 @@ export abstract class HyperlaneAppChecker<
}
async checkProxiedContract(
chain: Chain,
chain: ChainName,
name: string,
proxiedAddress: TransparentProxyAddresses,
proxyAdminAddress?: types.Address,
): Promise<void> {
const dc = this.multiProvider.getChainConnection(chain);
const provider = this.multiProvider.getProvider(chain);
const implementation = await proxyImplementation(
dc.provider,
provider,
proxiedAddress.proxy,
);
if (implementation !== proxiedAddress.implementation) {
@ -79,7 +78,7 @@ export abstract class HyperlaneAppChecker<
);
}
if (proxyAdminAddress) {
const admin = await proxyAdmin(dc.provider, proxiedAddress.proxy);
const admin = await proxyAdmin(provider, proxiedAddress.proxy);
utils.assert(admin === proxyAdminAddress, 'Proxy admin mismatch');
}
}
@ -92,13 +91,13 @@ export abstract class HyperlaneAppChecker<
// This method checks whether the bytecode of a contract matches the expected bytecode. It forces the deployer to explicitly acknowledge a change in bytecode. The violations can be remediated by updating the expected bytecode hash.
async checkBytecode(
chain: Chain,
chain: ChainName,
name: string,
address: string,
expectedBytecodeHash: string,
modifyBytecodePriorToHash: (bytecode: string) => string = (_) => _,
): Promise<void> {
const provider = this.multiProvider.getChainProvider(chain);
const provider = this.multiProvider.getProvider(chain);
const bytecode = await provider.getCode(address);
const bytecodeHash = keccak256(
modifyBytecodePriorToHash(this.removeBytecodeMetadata(bytecode)),
@ -115,7 +114,7 @@ export abstract class HyperlaneAppChecker<
}
async checkOwnership(
chain: Chain,
chain: ChainName,
owner: types.Address,
ownables: Ownable[],
): Promise<void> {

@ -41,20 +41,19 @@ export interface DeployOptions {
export const CREATE2FACTORY_ADDRESS =
'0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a';
export abstract class HyperlaneDeployer<
Chain extends ChainName,
Config,
Contracts extends HyperlaneContracts,
Factories extends HyperlaneFactories,
> {
public deployedContracts: Partial<Record<Chain, Partial<Contracts>>> = {};
verificationInputs: ChainMap<Chain, ContractVerificationInput[]>;
public deployedContracts: ChainMap<Contracts> = {};
public verificationInputs: ChainMap<ContractVerificationInput[]>;
protected logger: Debugger;
constructor(
protected readonly multiProvider: MultiProvider<Chain>,
protected readonly configMap: ChainMap<Chain, Config>,
protected readonly multiProvider: MultiProvider,
protected readonly configMap: ChainMap<Config>,
protected readonly factories: Factories,
protected readonly options?: DeployerOptions,
) {
@ -62,35 +61,28 @@ export abstract class HyperlaneDeployer<
this.logger = options?.logger || debug('hyperlane:AppDeployer');
}
abstract deployContracts(chain: Chain, config: Config): Promise<Contracts>;
abstract deployContracts(
chain: ChainName,
config: Config,
): Promise<Contracts>;
async deploy(
partialDeployment: Partial<Record<Chain, Partial<Contracts>>> = this
.deployedContracts,
): Promise<Record<Chain, Contracts>> {
objMap(
partialDeployment as ChainMap<Chain, Contracts>,
(chain, contracts) => {
this.logger(
`Recovering contracts for ${chain} from partial deployment`,
);
const chainConnection = this.multiProvider.getChainConnection(chain);
this.deployedContracts[chain] = connectContracts(
contracts,
chainConnection.signer!,
);
},
);
const configChains = Object.keys(this.configMap) as Chain[];
partialDeployment: ChainMap<Contracts> = this.deployedContracts,
): Promise<ChainMap<Contracts>> {
objMap(partialDeployment, (chain, contracts) => {
this.logger(`Recovering contracts for ${chain} from partial deployment`);
const signer = this.multiProvider.getSigner(chain);
this.deployedContracts[chain] = connectContracts(contracts, signer);
});
const configChains = Object.keys(this.configMap);
const targetChains = this.multiProvider.intersect(
configChains,
false,
true,
).intersection;
this.logger(`Start deploy to ${targetChains}`);
for (const chain of targetChains) {
const chainConnection = this.multiProvider.getChainConnection(chain);
const signerUrl = await chainConnection.getAddressUrl();
const signerUrl = await this.multiProvider.getExplorerAddressUrl(chain);
this.logger(`Deploying to ${chain} from ${signerUrl} ...`);
this.deployedContracts[chain] = await this.deployContracts(
chain,
@ -107,16 +99,15 @@ export abstract class HyperlaneDeployer<
),
);
}
return this.deployedContracts as ChainMap<Chain, Contracts>;
return this.deployedContracts;
}
protected async runIfOwner<T>(
chain: Chain,
chain: ChainName,
ownable: Ownable,
fn: () => Promise<T>,
): Promise<T | undefined> {
const dc = this.multiProvider.getChainConnection(chain);
const address = await dc.signer!.getAddress();
const address = await this.multiProvider.getSignerAddress(chain);
const owner = await ownable.owner();
const logObj = { owner, signer: address };
if (address === owner) {
@ -129,7 +120,7 @@ export abstract class HyperlaneDeployer<
}
protected async deployContractFromFactory<F extends ethers.ContractFactory>(
chain: Chain,
chain: ChainName,
factory: F,
contractName: string,
constructorArgs: Parameters<F['deploy']>,
@ -141,19 +132,12 @@ export abstract class HyperlaneDeployer<
return cachedContract as ReturnType<F['deploy']>;
}
const chainConnection = this.multiProvider.getChainConnection(chain);
const signer = chainConnection.signer;
if (!signer) {
throw new Error(`No signer for ${chain}`);
}
const provider = this.multiProvider.getProvider(chain);
const signer = this.multiProvider.getSigner(chain);
const overrides = this.multiProvider.getTransactionOverrides(chain);
this.logger(`Deploy ${contractName} on ${chain}`);
if (
deployOpts &&
deployOpts.create2Salt &&
(await chainConnection.provider.getCode(CREATE2FACTORY_ADDRESS)) != '0x'
) {
const factoryCode = await provider.getCode(CREATE2FACTORY_ADDRESS);
if (deployOpts && deployOpts.create2Salt && factoryCode != '0x') {
this.logger(`Deploying with CREATE2 factory`);
const create2Factory = Create2Factory__factory.connect(
@ -176,20 +160,17 @@ export abstract class HyperlaneDeployer<
salt,
);
if ((await chainConnection.provider.getCode(contractAddr)) === '0x') {
const contractCode = await provider.getCode(contractAddr);
if (contractCode === '0x') {
const deployTx = deployOpts.initCalldata
? await create2Factory.deployAndInit(
bytecode,
salt,
deployOpts.initCalldata,
chainConnection.overrides,
overrides,
)
: await create2Factory.deploy(
bytecode,
salt,
chainConnection.overrides,
);
await chainConnection.handleTx(deployTx);
: await create2Factory.deploy(bytecode, salt, overrides);
await this.multiProvider.handleTx(chain, deployTx);
} else {
this.logger(
`Found contract deployed at CREATE2 address, skipping contract deploy`,
@ -209,9 +190,9 @@ export abstract class HyperlaneDeployer<
} else {
const contract = await factory
.connect(signer)
.deploy(...constructorArgs, chainConnection.overrides);
.deploy(...constructorArgs, overrides);
await chainConnection.handleTx(contract.deployTransaction);
await this.multiProvider.handleTx(chain, contract.deployTransaction);
if (deployOpts?.initCalldata) {
this.logger(`Initialize ${contractName} on ${chain}`);
@ -219,7 +200,7 @@ export abstract class HyperlaneDeployer<
to: contract.address,
data: deployOpts.initCalldata,
});
await chainConnection.handleTx(initTx);
await this.multiProvider.handleTx(chain, initTx);
}
const verificationInput = getContractVerificationInput(
@ -234,7 +215,7 @@ export abstract class HyperlaneDeployer<
}
async deployContract<K extends keyof Factories>(
chain: Chain,
chain: ChainName,
contractName: K,
args: Parameters<Factories[K]['deploy']>,
deployOpts?: DeployOptions,
@ -251,7 +232,7 @@ export abstract class HyperlaneDeployer<
}
protected async deployProxy<C extends ethers.Contract>(
chain: Chain,
chain: ChainName,
implementation: C,
proxyAdmin: ProxyAdmin,
initArgs: Parameters<C['initialize']>,
@ -262,12 +243,13 @@ export abstract class HyperlaneDeployer<
initArgs,
);
let proxy: TransparentUpgradeableProxy;
const chainConnection = this.multiProvider.getChainConnection(chain);
const provider = this.multiProvider.getProvider(chain);
const overrides = this.multiProvider.getTransactionOverrides(chain);
this.logger(`Deploying transparent upgradable proxy`);
if (
deployOpts &&
deployOpts.create2Salt &&
(await chainConnection.provider.getCode(CREATE2FACTORY_ADDRESS)) != '0x'
(await provider.getCode(CREATE2FACTORY_ADDRESS)) != '0x'
) {
// To get consistent addresses with Create2, we need to use
// consistent constructor arguments.
@ -289,9 +271,7 @@ export abstract class HyperlaneDeployer<
// Note this requires the proxy contracts to ensure admin power has been
// transferred to the canonical proxy admin at some point in the future.
const proxyAdminOwner = await proxyAdmin.owner();
const deployer = await this.multiProvider
.getChainSigner(chain)
.getAddress();
const deployer = await this.multiProvider.getSignerAddress(chain);
let deployerOwnedProxyAdmin = proxyAdmin;
if (proxyAdminOwner.toLowerCase() !== deployer.toLowerCase()) {
deployerOwnedProxyAdmin = await this.deployContractFromFactory(
@ -322,9 +302,9 @@ export abstract class HyperlaneDeployer<
proxy.address,
implementation.address,
initData,
chainConnection.overrides,
overrides,
);
await chainConnection.handleTx(upgradeAndCallTx);
await this.multiProvider.handleTx(chain, upgradeAndCallTx);
// Change the proxy admin from deployerOwnedProxyAdmin to proxyAdmin if necessary.
await this.changeProxyAdmin(
chain,
@ -355,12 +335,12 @@ export abstract class HyperlaneDeployer<
}
private cacheContract<K extends keyof Factories>(
chain: Chain,
chain: ChainName,
contractName: K,
contract: HyperlaneContract,
) {
if (!this.deployedContracts[chain]) {
this.deployedContracts[chain] = {};
this.deployedContracts[chain] = {} as Contracts;
}
// @ts-ignore
this.deployedContracts[chain][contractName] = contract;
@ -374,7 +354,7 @@ export abstract class HyperlaneDeployer<
K extends keyof Factories,
C extends Awaited<ReturnType<Factories[K]['deploy']>>,
>(
chain: Chain,
chain: ChainName,
contractName: K,
constructorArgs: Parameters<Factories[K]['deploy']>,
proxyAdmin: ProxyAdmin,
@ -410,7 +390,7 @@ export abstract class HyperlaneDeployer<
* if the admin is not already the `desiredProxyAdmin`.
*/
async changeProxyAdmin(
chain: Chain,
chain: ChainName,
proxyAddress: types.Address,
currentProxyAdmin: ProxyAdmin,
desiredProxyAdmin: ProxyAdmin,
@ -425,26 +405,23 @@ export abstract class HyperlaneDeployer<
this.logger(
`Transferring proxy admin from ${currentProxyAdmin} to ${desiredProxyAdmin}`,
);
const chainConnection = this.multiProvider.getChainConnection(chain);
const overrides = this.multiProvider.getTransactionOverrides(chain);
const changeAdminTx = await currentProxyAdmin.changeProxyAdmin(
proxyAddress,
desiredProxyAdmin.address,
chainConnection.overrides,
overrides,
);
await chainConnection.handleTx(changeAdminTx);
await this.multiProvider.handleTx(chain, changeAdminTx);
}
mergeWithExistingVerificationInputs(
existingInputsMap: ChainMap<Chain, ContractVerificationInput[]>,
): ChainMap<Chain, ContractVerificationInput[]> {
const allChains = new Set<Chain>();
Object.keys(existingInputsMap).forEach((_) => allChains.add(_ as Chain));
Object.keys(this.verificationInputs).forEach((_) =>
allChains.add(_ as Chain),
);
existingInputsMap: ChainMap<ContractVerificationInput[]>,
): ChainMap<ContractVerificationInput[]> {
const allChains = new Set<ChainName>();
Object.keys(existingInputsMap).forEach((_) => allChains.add(_));
Object.keys(this.verificationInputs).forEach((_) => allChains.add(_));
// @ts-ignore
const ret: ChainMap<Chain, ContractVerificationInput[]> = {};
const ret: ChainMap<ContractVerificationInput[]> = {};
for (const chain of allChains) {
const existingInputs = existingInputsMap[chain] || [];
const newInputs = this.verificationInputs[chain] || [];

@ -1,10 +1,8 @@
import { defaultAbiCoder } from 'ethers/lib/utils';
import { utils as ethersUtils } from 'ethers';
import { utils } from '@hyperlane-xyz/utils';
import { eqAddress } from '@hyperlane-xyz/utils/dist/src/utils';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { ChainNameToDomainId } from '../../domains';
import { ChainName } from '../../types';
import { HyperlaneAppChecker } from '../HyperlaneAppChecker';
@ -31,10 +29,12 @@ const INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH =
'0xcee48ab556ae2ff12b6458fa92e5e31f4a07f7852a0ed06e43a7f06f3c4c6d76';
const OVERHEAD_IGP_BYTECODE_HASH =
'0x3cfed1f24f1e9b28a76d5a8c61696a04f7bc474404b823a2fcc210ea52346252';
export class HyperlaneCoreChecker<
Chain extends ChainName,
> extends HyperlaneAppChecker<Chain, HyperlaneCore<Chain>, CoreConfig> {
async checkChain(chain: Chain): Promise<void> {
export class HyperlaneCoreChecker extends HyperlaneAppChecker<
HyperlaneCore,
CoreConfig
> {
async checkChain(chain: ChainName): Promise<void> {
const config = this.configMap[chain];
// skip chains that are configured to be removed
if (config.remove) {
@ -49,7 +49,7 @@ export class HyperlaneCoreChecker<
await this.checkValidatorAnnounce(chain);
}
async checkDomainOwnership(chain: Chain): Promise<void> {
async checkDomainOwnership(chain: ChainName): Promise<void> {
const config = this.configMap[chain];
if (config.owner) {
const contracts = this.app.getContracts(chain);
@ -62,11 +62,11 @@ export class HyperlaneCoreChecker<
}
}
async checkMailbox(chain: Chain): Promise<void> {
async checkMailbox(chain: ChainName): Promise<void> {
const contracts = this.app.getContracts(chain);
const mailbox = contracts.mailbox.contract;
const localDomain = await mailbox.localDomain();
utils.assert(localDomain === ChainNameToDomainId[chain]);
utils.assert(localDomain === this.multiProvider.getDomainId(chain));
const actualIsm = await mailbox.defaultIsm();
const expectedIsm = contracts.multisigIsm.address;
@ -83,7 +83,7 @@ export class HyperlaneCoreChecker<
}
}
async checkBytecodes(chain: Chain): Promise<void> {
async checkBytecodes(chain: ChainName): Promise<void> {
const contracts = this.app.getContracts(chain);
const mailbox = contracts.mailbox.contract;
const localDomain = await mailbox.localDomain();
@ -100,7 +100,9 @@ export class HyperlaneCoreChecker<
// localDomain in the bytecode should be not be removed which
// are just done via an offset guard
_.replaceAll(
defaultAbiCoder.encode(['uint32'], [localDomain]).slice(2),
ethersUtils.defaultAbiCoder
.encode(['uint32'], [localDomain])
.slice(2),
(match, offset) => (offset > 8000 ? match : ''),
),
);
@ -144,7 +146,7 @@ export class HyperlaneCoreChecker<
(_) =>
// Remove the address of the wrapped ISM from the bytecode
_.replaceAll(
defaultAbiCoder
ethersUtils.defaultAbiCoder
.encode(
['address'],
[contracts.interchainGasPaymaster.addresses.proxy],
@ -155,7 +157,7 @@ export class HyperlaneCoreChecker<
);
}
async checkProxiedContracts(chain: Chain): Promise<void> {
async checkProxiedContracts(chain: ChainName): Promise<void> {
const contracts = this.app.getContracts(chain);
await this.checkProxiedContract(
chain,
@ -177,14 +179,14 @@ export class HyperlaneCoreChecker<
);
}
async checkValidatorAnnounce(chain: Chain): Promise<void> {
async checkValidatorAnnounce(chain: ChainName): Promise<void> {
const expectedValidators = this.configMap[chain].multisigIsm.validators;
const validatorAnnounce = this.app.getContracts(chain).validatorAnnounce;
const announcedValidators =
await validatorAnnounce.getAnnouncedValidators();
expectedValidators.map((validator) => {
const matches = announcedValidators.filter((x) =>
eqAddress(x, validator),
utils.eqAddress(x, validator),
);
if (matches.length == 0) {
const violation: ValidatorAnnounceViolation = {
@ -199,7 +201,7 @@ export class HyperlaneCoreChecker<
});
}
async checkMultisigIsm(local: Chain): Promise<void> {
async checkMultisigIsm(local: ChainName): Promise<void> {
await Promise.all(
this.app
.remoteChains(local)
@ -207,12 +209,15 @@ export class HyperlaneCoreChecker<
);
}
async checkMultisigIsmForRemote(local: Chain, remote: Chain): Promise<void> {
async checkMultisigIsmForRemote(
local: ChainName,
remote: ChainName,
): Promise<void> {
const coreContracts = this.app.getContracts(local);
const multisigIsm = coreContracts.multisigIsm;
const config = this.configMap[remote];
const remoteDomain = ChainNameToDomainId[remote];
const remoteDomain = this.multiProvider.getDomainId(remote);
const multisigIsmConfig = config.multisigIsm;
const expectedValidators = multisigIsmConfig.validators;
const actualValidators = await multisigIsm.validators(remoteDomain);

@ -13,10 +13,8 @@ import {
} from '@hyperlane-xyz/core';
import type { types } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../../consts/chainMetadata';
import multisigIsmVerifyCosts from '../../consts/multisigIsmVerifyCosts.json';
import { CoreContracts, coreFactories } from '../../core/contracts';
import { ChainNameToDomainId } from '../../domains';
import { MultiProvider } from '../../providers/MultiProvider';
import { ProxiedContract, TransparentProxyAddresses } from '../../proxy';
import { ChainMap, ChainName } from '../../types';
@ -25,20 +23,17 @@ import { DeployOptions, HyperlaneDeployer } from '../HyperlaneDeployer';
import { CoreConfig } from './types';
export class HyperlaneCoreDeployer<
Chain extends ChainName,
> extends HyperlaneDeployer<
Chain,
export class HyperlaneCoreDeployer extends HyperlaneDeployer<
CoreConfig,
CoreContracts,
typeof coreFactories
> {
startingBlockNumbers: ChainMap<Chain, number | undefined>;
gasOverhead: ChainMap<Chain, OverheadIgp.DomainConfigStruct>;
startingBlockNumbers: ChainMap<number | undefined>;
gasOverhead: ChainMap<OverheadIgp.DomainConfigStruct>;
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, CoreConfig>,
multiProvider: MultiProvider,
configMap: ChainMap<CoreConfig>,
factoriesOverride = coreFactories,
) {
super(multiProvider, configMap, factoriesOverride, {
@ -54,15 +49,15 @@ export class HyperlaneCoreDeployer<
`Unknown verification cost for ${threshold} of ${validators.length}`,
);
return {
domain: ChainNameToDomainId[chain],
domain: multiProvider.getDomainId(chain),
gasOverhead: verifyCost,
};
});
this.startingBlockNumbers = objMap(configMap, () => undefined);
}
async deployInterchainGasPaymaster<LocalChain extends Chain>(
chain: LocalChain,
async deployInterchainGasPaymaster(
chain: ChainName,
proxyAdmin: ProxyAdmin,
deployOpts?: DeployOptions,
): Promise<
@ -78,13 +73,12 @@ export class HyperlaneCoreDeployer<
);
}
async deployDefaultIsmInterchainGasPaymaster<LocalChain extends Chain>(
chain: LocalChain,
async deployDefaultIsmInterchainGasPaymaster(
chain: ChainName,
interchainGasPaymasterAddress: types.Address,
deployOpts?: DeployOptions,
): Promise<OverheadIgp> {
const chainSigner = this.multiProvider.getChainSigner(chain);
const deployer = await chainSigner.getAddress();
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',
@ -100,11 +94,10 @@ export class HyperlaneCoreDeployer<
},
);
const configChains = Object.keys(this.configMap) as Chain[];
const chainConnection = this.multiProvider.getChainConnection(chain);
const configChains = Object.keys(this.configMap);
const remotes = this.multiProvider
.intersect(configChains, false)
.multiProvider.remoteChains(chain);
.multiProvider.getRemoteChains(chain);
// Only set gas overhead configs if they differ from what's on chain
const configs: OverheadIgp.DomainConfigStruct[] = [];
@ -120,11 +113,12 @@ export class HyperlaneCoreDeployer<
}
if (configs.length > 0) {
await this.runIfOwner(chain, defaultIsmInterchainGasPaymaster, async () =>
chainConnection.handleTx(
await this.runIfOwner(chain, defaultIsmInterchainGasPaymaster, () =>
this.multiProvider.handleTx(
chain,
defaultIsmInterchainGasPaymaster.setDestinationGasOverheads(
configs,
chainConnection.overrides,
this.multiProvider.getTransactionOverrides(chain),
),
),
);
@ -133,13 +127,13 @@ export class HyperlaneCoreDeployer<
return defaultIsmInterchainGasPaymaster;
}
async deployMailbox<LocalChain extends Chain>(
chain: LocalChain,
async deployMailbox(
chain: ChainName,
defaultIsmAddress: types.Address,
proxyAdmin: ProxyAdmin,
deployOpts?: DeployOptions,
): Promise<ProxiedContract<Mailbox, TransparentProxyAddresses>> {
const domain = chainMetadata[chain].id;
const domain = this.multiProvider.getDomainId(chain);
const owner = this.configMap[chain].owner;
const mailbox = await this.deployProxiedContract(
@ -153,8 +147,8 @@ export class HyperlaneCoreDeployer<
return mailbox;
}
async deployValidatorAnnounce<LocalChain extends Chain>(
chain: LocalChain,
async deployValidatorAnnounce(
chain: ChainName,
mailboxAddress: string,
deployOpts?: DeployOptions,
): Promise<ValidatorAnnounce> {
@ -167,18 +161,17 @@ export class HyperlaneCoreDeployer<
return validatorAnnounce;
}
async deployMultisigIsm<LocalChain extends Chain>(
chain: LocalChain,
): Promise<MultisigIsm> {
async deployMultisigIsm(chain: ChainName): Promise<MultisigIsm> {
const multisigIsm = await this.deployContract(chain, 'multisigIsm', []);
const configChains = Object.keys(this.configMap) as Chain[];
const chainConnection = this.multiProvider.getChainConnection(chain);
const configChains = Object.keys(this.configMap);
const remotes = this.multiProvider
.intersect(configChains, false)
.multiProvider.remoteChains(chain);
.multiProvider.getRemoteChains(chain);
const overrides = this.multiProvider.getTransactionOverrides(chain);
await super.runIfOwner(chain, multisigIsm, async () => {
// TODO: Remove extraneous validators
const remoteDomains = remotes.map((chain) => ChainNameToDomainId[chain]);
const remoteDomains = this.multiProvider.getDomainIds(remotes);
const actualValidators = await Promise.all(
remoteDomains.map((id) => multisigIsm.validators(id)),
);
@ -199,11 +192,15 @@ export class HyperlaneCoreDeployer<
this.logger(
`Enroll ${chainsToEnrollValidators} validators on ${chain}`,
);
await chainConnection.handleTx(
await this.multiProvider.handleTx(
chain,
multisigIsm.enrollValidators(
chainsToEnrollValidators.map((c) => ChainNameToDomainId[c]),
chainsToEnrollValidators.map((c) =>
this.multiProvider.getDomainId(c),
),
validatorsToEnroll.filter((validators) => validators.length > 0),
chainConnection.overrides,
overrides,
),
);
}
@ -221,13 +218,14 @@ export class HyperlaneCoreDeployer<
this.logger(
`Set remote (${chainsToSetThreshold}) thresholds on ${chain}`,
);
await chainConnection.handleTx(
await this.multiProvider.handleTx(
chain,
multisigIsm.setThresholds(
chainsToSetThreshold.map((c) => ChainNameToDomainId[c]),
chainsToSetThreshold.map((c) => this.multiProvider.getDomainId(c)),
chainsToSetThreshold.map(
(c) => this.configMap[c].multisigIsm.threshold,
),
chainConnection.overrides,
overrides,
),
);
}
@ -236,8 +234,8 @@ export class HyperlaneCoreDeployer<
return multisigIsm;
}
async deployContracts<LocalChain extends Chain>(
chain: LocalChain,
async deployContracts(
chain: ChainName,
config: CoreConfig,
): Promise<CoreContracts> {
if (config.remove) {
@ -245,8 +243,7 @@ export class HyperlaneCoreDeployer<
return undefined as any;
}
const dc = this.multiProvider.getChainConnection(chain);
const provider = dc.provider!;
const provider = this.multiProvider.getProvider(chain);
const startingBlockNumber = await provider.getBlockNumber();
this.startingBlockNumbers[chain] = startingBlockNumber;
const multisigIsm = await this.deployMultisigIsm(chain);
@ -289,18 +286,21 @@ export class HyperlaneCoreDeployer<
}
async transferOwnershipOfContracts(
chain: Chain,
chain: ChainName,
ownables: Ownable[],
): Promise<ethers.ContractReceipt[]> {
const owner = this.configMap[chain].owner;
const chainConnection = this.multiProvider.getChainConnection(chain);
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, () =>
chainConnection.handleTx(
ownable.transferOwnership(owner, chainConnection.overrides),
this.multiProvider.handleTx(
chain,
ownable.transferOwnership(
owner,
this.multiProvider.getTransactionOverrides(chain),
),
),
);
if (receipt) receipts.push(receipt);

@ -11,7 +11,6 @@ import { utils } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../../HyperlaneApp';
import { Chains } from '../../consts/chains';
import { ChainNameToDomainId, DomainIdToChainName } from '../../domains';
import { LiquidityLayerContracts } from '../../middleware';
import { MultiProvider } from '../../providers/MultiProvider';
import { ChainMap, ChainName } from '../../types';
@ -38,9 +37,9 @@ const PortalBridgedTokenTopic = PortalAdapterInterface.getEventTopic(
PortalAdapterInterface.getEvent('BridgedToken'),
);
interface CircleBridgeMessage<Chain> {
chain: Chain;
remoteChain: Chain;
interface CircleBridgeMessage {
chain: ChainName;
remoteChain: ChainName;
txHash: string;
message: string;
nonce: number;
@ -48,58 +47,56 @@ interface CircleBridgeMessage<Chain> {
nonceHash: string;
}
interface PortalBridgeMessage<Chain> {
origin: Chain;
interface PortalBridgeMessage {
origin: ChainName;
nonce: number;
portalSequence: number;
destination: Chain;
destination: ChainName;
}
export class LiquidityLayerApp<
Chain extends ChainName = ChainName,
> extends HyperlaneApp<LiquidityLayerContracts, Chain> {
export class LiquidityLayerApp extends HyperlaneApp<LiquidityLayerContracts> {
constructor(
public readonly contractsMap: ChainMap<Chain, LiquidityLayerContracts>,
public readonly multiProvider: MultiProvider<Chain>,
public readonly config: ChainMap<Chain, BridgeAdapterConfig>,
public readonly contractsMap: ChainMap<LiquidityLayerContracts>,
public readonly multiProvider: MultiProvider,
public readonly config: ChainMap<BridgeAdapterConfig>,
) {
super(contractsMap, multiProvider);
}
async fetchCircleMessageTransactions(chain: Chain): Promise<string[]> {
const cc = this.multiProvider.getChainConnection(chain);
async fetchCircleMessageTransactions(chain: ChainName): Promise<string[]> {
const params = new URLSearchParams({
module: 'logs',
action: 'getLogs',
address: this.getContracts(chain).circleBridgeAdapter!.address,
topic0: BridgedTokenTopic,
});
const req = await fetch(`${cc.getApiUrl()}?${params}`);
const url = `${this.multiProvider.getExplorerApiUrl(chain)}?${params}`;
const req = await fetch(url);
const response = await req.json();
return response.result.map((_: any) => _.transactionHash).flat();
}
async fetchPortalBridgeTransactions(chain: Chain): Promise<string[]> {
const cc = this.multiProvider.getChainConnection(chain);
async fetchPortalBridgeTransactions(chain: ChainName): Promise<string[]> {
const params = new URLSearchParams({
module: 'logs',
action: 'getLogs',
address: this.getContracts(chain).portalAdapter!.address,
topic0: PortalBridgedTokenTopic,
});
const req = await fetch(`${cc.getApiUrl()}?${params}`);
const url = `${this.multiProvider.getExplorerApiUrl(chain)}?${params}`;
const req = await fetch(url);
const response = await req.json();
return response.result.map((_: any) => _.transactionHash).flat();
}
async parsePortalMessages(
chain: Chain,
chain: ChainName,
txHash: string,
): Promise<PortalBridgeMessage<Chain>[]> {
const connection = this.multiProvider.getChainConnection(chain);
const receipt = await connection.provider.getTransactionReceipt(txHash);
): Promise<PortalBridgeMessage[]> {
const provider = this.multiProvider.getProvider(chain);
const receipt = await provider.getTransactionReceipt(txHash);
const matchingLogs = receipt.logs
.map((_) => {
try {
@ -114,18 +111,17 @@ export class LiquidityLayerApp<
const event = matchingLogs.find((_) => _!.name === 'BridgedToken')!;
const portalSequence = event.args.portalSequence.toNumber();
const nonce = event.args.nonce.toNumber();
const destination = DomainIdToChainName[event.args.destination];
const destination = this.multiProvider.getChainName(event.args.destination);
return [
{ origin: chain, nonce, portalSequence, destination },
] as PortalBridgeMessage<Chain>[];
return [{ origin: chain, nonce, portalSequence, destination }];
}
async parseCircleMessages(
chain: Chain,
chain: ChainName,
txHash: string,
): Promise<CircleBridgeMessage<Chain>[]> {
const connection = this.multiProvider.getChainConnection(chain);
const receipt = await connection.provider.getTransactionReceipt(txHash);
): Promise<CircleBridgeMessage[]> {
const provider = this.multiProvider.getProvider(chain);
const receipt = await provider.getTransactionReceipt(txHash);
const matchingLogs = receipt.logs
.map((_) => {
try {
@ -147,12 +143,11 @@ export class LiquidityLayerApp<
.nonce;
const remoteChain = chain === Chains.fuji ? Chains.goerli : Chains.fuji;
const domain = this.config[chain].circle!.circleDomainMapping.find(
(_) => _.hyperlaneDomain === ChainNameToDomainId[chain],
(_) => _.hyperlaneDomain === this.multiProvider.getDomainId(chain),
)!.circleDomain;
return [
{
chain,
// @ts-ignore
remoteChain,
txHash,
message,
@ -167,13 +162,13 @@ export class LiquidityLayerApp<
}
async attemptPortalTransferCompletion(
message: PortalBridgeMessage<Chain>,
message: PortalBridgeMessage,
): Promise<void> {
const destinationPortalAdapter = this.getContracts(message.destination)
.portalAdapter!;
const transferId = await destinationPortalAdapter.transferId(
ChainNameToDomainId[message.origin],
this.multiProvider.getDomainId(message.origin),
message.nonce,
);
@ -190,7 +185,8 @@ export class LiquidityLayerApp<
const wormholeOriginDomain = this.config[
message.destination
].portal!.wormholeDomainMapping.find(
(_) => _.hyperlaneDomain === ChainNameToDomainId[message.origin],
(_) =>
_.hyperlaneDomain === this.multiProvider.getDomainId(message.origin),
)?.wormholeDomain;
const emitter = utils.strip0x(
utils.addressToBytes32(
@ -207,27 +203,24 @@ export class LiquidityLayerApp<
return;
}
const connection = this.multiProvider.getChainConnection(
message.destination,
);
console.debug(
`Complete portal transfer for nonce ${message.nonce} on ${message.destination}`,
);
await connection.handleTx(
await this.multiProvider.handleTx(
message.destination,
destinationPortalAdapter.completeTransfer(
utils.ensure0x(Buffer.from(vaa.vaaBytes, 'base64').toString('hex')),
),
);
}
async attemptCircleAttestationSubmission(
message: CircleBridgeMessage<Chain>,
message: CircleBridgeMessage,
): Promise<void> {
const connection = this.multiProvider.getChainConnection(
message.remoteChain,
);
const signer = this.multiProvider.getSigner(message.remoteChain);
const transmitter = ICircleMessageTransmitter__factory.connect(
this.config[message.remoteChain].circle!.messageTransmitterAddress,
connection.signer!,
signer,
);
const alreadyProcessed = await transmitter.usedNonces(message.nonceHash);
@ -256,7 +249,12 @@ export class LiquidityLayerApp<
attestations.attestation,
);
console.log(`Submitted attestations in ${await connection.getTxUrl(tx)}`);
await connection.handleTx(tx);
console.log(
`Submitted attestations in ${this.multiProvider.getExplorerTxUrl(
message.remoteChain,
tx,
)}`,
);
await this.multiProvider.handleTx(message.remoteChain, tx);
}
}

@ -9,7 +9,6 @@ import {
import { utils } from '@hyperlane-xyz/utils';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { ChainNameToDomainId } from '../../domains';
import {
LiquidityLayerContracts,
LiquidityLayerFactories,
@ -54,25 +53,22 @@ export type BridgeAdapterConfig = {
export type LiquidityLayerConfig = RouterConfig & BridgeAdapterConfig;
export class LiquidityLayerDeployer<
Chain extends ChainName,
> extends MiddlewareRouterDeployer<
Chain,
export class LiquidityLayerDeployer extends MiddlewareRouterDeployer<
LiquidityLayerConfig,
LiquidityLayerContracts,
LiquidityLayerFactories
> {
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, LiquidityLayerConfig>,
protected core: HyperlaneCore<Chain>,
multiProvider: MultiProvider,
configMap: ChainMap<LiquidityLayerConfig>,
protected core: HyperlaneCore,
protected create2salt = 'LiquidityLayerDeployerSalt',
) {
super(multiProvider, configMap, liquidityLayerFactories, {});
}
async enrollRemoteRouters(
contractsMap: ChainMap<Chain, LiquidityLayerContracts>,
contractsMap: ChainMap<LiquidityLayerContracts>,
): Promise<void> {
this.logger(`Enroll LiquidityLayerRouters with each other`);
await super.enrollRemoteRouters(contractsMap);
@ -101,7 +97,7 @@ export class LiquidityLayerDeployer<
// Custom contract deployment logic can go here
// If no custom logic is needed, call deployContract for the router
async deployContracts(
chain: Chain,
chain: ChainName,
config: LiquidityLayerConfig,
): Promise<LiquidityLayerContracts> {
const initCalldata = this.getInitArgs(
@ -139,18 +135,16 @@ export class LiquidityLayerDeployer<
}
async deployPortalAdapter(
chain: Chain,
chain: ChainName,
adapterConfig: PortalAdapterConfig,
owner: string,
router: LiquidityLayerRouter,
): Promise<PortalAdapter> {
const cc = this.multiProvider.getChainConnection(chain);
const initCalldata =
PortalAdapter__factory.createInterface().encodeFunctionData(
'initialize',
[
ChainNameToDomainId[chain],
this.multiProvider.getDomainId(chain),
owner,
adapterConfig.portalBridgeAddress,
router.address,
@ -177,7 +171,8 @@ export class LiquidityLayerDeployer<
this.logger(
`Set wormhole domain ${wormholeDomain} for hyperlane domain ${hyperlaneDomain}`,
);
await cc.handleTx(
await this.multiProvider.handleTx(
chain,
portalAdapter.addDomain(hyperlaneDomain, wormholeDomain),
);
}
@ -189,7 +184,8 @@ export class LiquidityLayerDeployer<
)
) {
this.logger('Set Portal as LiquidityLayerAdapter on Router');
await cc.handleTx(
await this.multiProvider.handleTx(
chain,
router.setLiquidityLayerAdapter(
adapterConfig.type,
portalAdapter.address,
@ -201,12 +197,11 @@ export class LiquidityLayerDeployer<
}
async deployCircleBridgeAdapter(
chain: Chain,
chain: ChainName,
adapterConfig: CircleBridgeAdapterConfig,
owner: string,
router: LiquidityLayerRouter,
): Promise<CircleBridgeAdapter> {
const cc = this.multiProvider.getChainConnection(chain);
const initCalldata =
CircleBridgeAdapter__factory.createInterface().encodeFunctionData(
'initialize',
@ -234,7 +229,8 @@ export class LiquidityLayerDeployer<
)
) {
this.logger(`Set USDC token contract`);
await cc.handleTx(
await this.multiProvider.handleTx(
chain,
circleBridgeAdapter.addToken(adapterConfig.usdcAddress, 'USDC'),
);
}
@ -252,7 +248,8 @@ export class LiquidityLayerDeployer<
this.logger(
`Set circle domain ${circleDomain} for hyperlane domain ${hyperlaneDomain}`,
);
await cc.handleTx(
await this.multiProvider.handleTx(
chain,
circleBridgeAdapter.addDomain(hyperlaneDomain, circleDomain),
);
}
@ -264,7 +261,8 @@ export class LiquidityLayerDeployer<
)
) {
this.logger('Set Circle as LiquidityLayerAdapter on Router');
await cc.handleTx(
await this.multiProvider.handleTx(
chain,
router.setLiquidityLayerAdapter(
adapterConfig.type,
circleBridgeAdapter.address,

@ -23,12 +23,10 @@ import { RouterConfig } from '../router/types';
export type InterchainAccountConfig = RouterConfig;
export abstract class MiddlewareRouterDeployer<
Chain extends ChainName,
MiddlewareRouterConfig extends RouterConfig,
MiddlewareRouterContracts extends RouterContracts,
MiddlewareFactories extends RouterFactories,
> extends HyperlaneRouterDeployer<
Chain,
MiddlewareRouterConfig,
MiddlewareRouterContracts,
MiddlewareFactories
@ -46,18 +44,15 @@ export abstract class MiddlewareRouterDeployer<
}
}
export class InterchainAccountDeployer<
Chain extends ChainName,
> extends MiddlewareRouterDeployer<
Chain,
export class InterchainAccountDeployer extends MiddlewareRouterDeployer<
InterchainAccountConfig,
InterchainAccountContracts,
InterchainAccountFactories
> {
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, InterchainAccountConfig>,
protected core: HyperlaneCore<Chain>,
multiProvider: MultiProvider,
configMap: ChainMap<InterchainAccountConfig>,
protected core: HyperlaneCore,
protected create2salt = 'asdasdsd',
) {
super(multiProvider, configMap, interchainAccountFactories, {});
@ -66,7 +61,7 @@ export class InterchainAccountDeployer<
// Custom contract deployment logic can go here
// If no custom logic is needed, call deployContract for the router
async deployContracts(
chain: Chain,
chain: ChainName,
config: InterchainAccountConfig,
): Promise<InterchainAccountContracts> {
const initCalldata = this.getInitArgs(
@ -85,18 +80,15 @@ export class InterchainAccountDeployer<
export type InterchainQueryConfig = RouterConfig;
export class InterchainQueryDeployer<
Chain extends ChainName,
> extends MiddlewareRouterDeployer<
Chain,
export class InterchainQueryDeployer extends MiddlewareRouterDeployer<
InterchainQueryConfig,
InterchainQueryContracts,
InterchainQueryFactories
> {
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, InterchainQueryConfig>,
protected core: HyperlaneCore<Chain>,
multiProvider: MultiProvider,
configMap: ChainMap<InterchainQueryConfig>,
protected core: HyperlaneCore,
// TODO replace salt with 'hyperlane' before next redeploy
protected create2salt = 'asdasdsd',
) {
@ -106,7 +98,7 @@ export class InterchainQueryDeployer<
// Custom contract deployment logic can go here
// If no custom logic is needed, call deployContract for the router
async deployContracts(
chain: Chain,
chain: ChainName,
config: InterchainQueryConfig,
): Promise<InterchainQueryContracts> {
const initCalldata = this.getInitArgs(

@ -2,24 +2,22 @@ import { debug } from 'debug';
import { GasRouter } from '@hyperlane-xyz/core';
import { DomainIdToChainName } from '../../domains';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterContracts, RouterFactories } from '../../router';
import { ChainMap, ChainName } from '../../types';
import { ChainMap } from '../../types';
import { DeployerOptions } from '../HyperlaneDeployer';
import { HyperlaneRouterDeployer } from './HyperlaneRouterDeployer';
import { GasRouterConfig } from './types';
export abstract class GasRouterDeployer<
Chain extends ChainName,
Config extends GasRouterConfig,
Contracts extends RouterContracts<GasRouter>,
Factories extends RouterFactories<GasRouter>,
> extends HyperlaneRouterDeployer<Chain, Config, Contracts, Factories> {
> extends HyperlaneRouterDeployer<Config, Contracts, Factories> {
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, Config>,
multiProvider: MultiProvider,
configMap: ChainMap<Config>,
factories: Factories,
options?: DeployerOptions,
) {
@ -29,18 +27,14 @@ export abstract class GasRouterDeployer<
});
}
async enrollRemoteRouters(
contractsMap: ChainMap<Chain, Contracts>,
): Promise<void> {
async enrollRemoteRouters(contractsMap: ChainMap<Contracts>): Promise<void> {
await super.enrollRemoteRouters(contractsMap);
this.logger(`Setting enrolled router destination gas...`);
for (const [chain, contracts] of Object.entries<Contracts>(contractsMap)) {
const local = chain as Chain;
const remoteDomains = await contracts.router.domains();
const remoteChains = remoteDomains.map(
(domain) => DomainIdToChainName[domain] as Chain,
const remoteChains = remoteDomains.map((domain) =>
this.multiProvider.getChainName(domain),
);
const currentConfigs = await Promise.all(
remoteDomains.map((domain) => contracts.router.destinationGas(domain)),
@ -55,9 +49,9 @@ export abstract class GasRouterDeployer<
continue;
}
this.logger(`Set destination gas on ${local} for ${remoteChains}`);
const chainConnection = this.multiProvider.getChainConnection(local);
await chainConnection.handleTx(
this.logger(`Set destination gas on ${chain} for ${remoteChains}`);
await this.multiProvider.handleTx(
chain,
contracts.router.setDestinationGas(remoteConfigs),
);
}

@ -11,24 +11,23 @@ import { HyperlaneAppChecker } from '../HyperlaneAppChecker';
import { RouterConfig } from './types';
export class HyperlaneRouterChecker<
Chain extends ChainName,
App extends HyperlaneApp<Contracts, Chain>,
App extends HyperlaneApp<Contracts>,
Config extends RouterConfig,
Contracts extends RouterContracts,
> extends HyperlaneAppChecker<Chain, App, Config> {
checkOwnership(chain: Chain): Promise<void> {
> extends HyperlaneAppChecker<App, Config> {
checkOwnership(chain: ChainName): Promise<void> {
const owner = this.configMap[chain].owner;
const ownables = this.ownables(chain);
return super.checkOwnership(chain, owner, ownables);
}
async checkChain(chain: Chain): Promise<void> {
async checkChain(chain: ChainName): Promise<void> {
await this.checkHyperlaneConnectionClient(chain);
await this.checkEnrolledRouters(chain);
await this.checkOwnership(chain);
}
async checkHyperlaneConnectionClient(chain: Chain): Promise<void> {
async checkHyperlaneConnectionClient(chain: ChainName): Promise<void> {
const router = this.app.getContracts(chain).router;
const mailbox = await router.mailbox();
const igp = await router.interchainGasPaymaster();
@ -48,20 +47,20 @@ export class HyperlaneRouterChecker<
);
}
async checkEnrolledRouters(chain: Chain): Promise<void> {
async checkEnrolledRouters(chain: ChainName): Promise<void> {
const router = this.app.getContracts(chain).router;
await Promise.all(
this.app.remoteChains(chain).map(async (remoteChain) => {
const remoteRouter = this.app.getContracts(remoteChain).router;
const remoteChainId = this.multiProvider.getChainId(remoteChain);
const address = await router.routers(remoteChainId);
const remoteDomainId = this.multiProvider.getDomainId(remoteChain);
const address = await router.routers(remoteDomainId);
utils.assert(address === utils.addressToBytes32(remoteRouter.address));
}),
);
}
ownables(chain: Chain): Ownable[] {
ownables(chain: ChainName): Ownable[] {
return [this.app.getContracts(chain).router];
}
}

@ -2,24 +2,22 @@ import { debug } from 'debug';
import { utils } from '@hyperlane-xyz/utils';
import { DomainIdToChainName } from '../../domains';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterContracts, RouterFactories } from '../../router';
import { ChainMap, ChainName } from '../../types';
import { ChainMap } from '../../types';
import { objMap, promiseObjAll } from '../../utils/objects';
import { DeployerOptions, HyperlaneDeployer } from '../HyperlaneDeployer';
import { RouterConfig } from './types';
export abstract class HyperlaneRouterDeployer<
Chain extends ChainName,
Config extends RouterConfig,
Contracts extends RouterContracts,
Factories extends RouterFactories,
> extends HyperlaneDeployer<Chain, Config, Contracts, Factories> {
> extends HyperlaneDeployer<Config, Contracts, Factories> {
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, Config>,
multiProvider: MultiProvider,
configMap: ChainMap<Config>,
factories: Factories,
options?: DeployerOptions,
) {
@ -29,25 +27,26 @@ export abstract class HyperlaneRouterDeployer<
});
}
async initConnectionClient(
contractsMap: ChainMap<Chain, Contracts>,
): Promise<void> {
async initConnectionClient(contractsMap: ChainMap<Contracts>): Promise<void> {
this.logger(`Initializing connection clients (if not already)...`);
await promiseObjAll(
objMap(contractsMap, async (local, contracts) => {
const chainConnection = this.multiProvider.getChainConnection(local);
// set mailbox if not already set (and configured)
const mailbox = this.configMap[local].mailbox;
if (mailbox !== (await contracts.router.mailbox())) {
this.logger(`Set mailbox on ${local}`);
await chainConnection.handleTx(contracts.router.setMailbox(mailbox));
await this.multiProvider.handleTx(
local,
contracts.router.setMailbox(mailbox),
);
}
// set interchain gas paymaster if not already set (and configured)
const igp = this.configMap[local].interchainGasPaymaster;
if (igp !== (await contracts.router.interchainGasPaymaster())) {
this.logger(`Set interchain gas paymaster on ${local}`);
await chainConnection.handleTx(
await this.multiProvider.handleTx(
local,
contracts.router.setInterchainGasPaymaster(igp),
);
}
@ -58,7 +57,8 @@ export abstract class HyperlaneRouterDeployer<
ism !== (await contracts.router.interchainSecurityModule())
) {
this.logger(`Set interchain security module on ${local}`);
await chainConnection.handleTx(
await this.multiProvider.handleTx(
local,
contracts.router.setInterchainSecurityModule(ism),
);
}
@ -67,7 +67,7 @@ export abstract class HyperlaneRouterDeployer<
}
async enrollRemoteRouters(
contractsMap: ChainMap<Chain, RouterContracts>,
contractsMap: ChainMap<RouterContracts>,
): Promise<void> {
this.logger(
`Enrolling deployed routers with each other (if not already)...`,
@ -77,16 +77,14 @@ export abstract class HyperlaneRouterDeployer<
for (const [chain, contracts] of Object.entries<RouterContracts>(
contractsMap,
)) {
const local = chain as Chain;
const chainConnection = this.multiProvider.getChainConnection(local);
// only enroll chains which are deployed
const deployedRemoteChains = this.multiProvider
.remoteChains(local)
.getRemoteChains(chain)
.filter((c) => deployedChains.includes(c));
const enrollEntries = await Promise.all(
deployedRemoteChains.map(async (remote) => {
const remoteDomain = this.multiProvider.getChainId(remote);
const remoteDomain = this.multiProvider.getDomainId(remote);
const current = await contracts.router.routers(remoteDomain);
const expected = utils.addressToBytes32(
contractsMap[remote].router.address,
@ -105,38 +103,37 @@ export abstract class HyperlaneRouterDeployer<
return;
}
await super.runIfOwner(local, contracts.router, async () => {
const chains = domains.map((id) => DomainIdToChainName[id] || id);
await super.runIfOwner(chain, contracts.router, async () => {
const chains = domains.map((id) => this.multiProvider.getChainName(id));
this.logger(
`Enrolling remote routers (${chains.join(', ')}) on ${local}`,
`Enrolling remote routers (${chains.join(', ')}) on ${chain}`,
);
await chainConnection.handleTx(
await this.multiProvider.handleTx(
chain,
contracts.router.enrollRemoteRouters(
domains,
addresses,
chainConnection.overrides,
this.multiProvider.getTransactionOverrides(chain),
),
);
});
}
}
async transferOwnership(
contractsMap: ChainMap<Chain, Contracts>,
): Promise<void> {
async transferOwnership(contractsMap: ChainMap<Contracts>): Promise<void> {
this.logger(`Transferring ownership of routers...`);
await promiseObjAll(
objMap(contractsMap, async (chain, contracts) => {
const chainConnection = this.multiProvider.getChainConnection(chain);
const owner = this.configMap[chain].owner;
const currentOwner = await contracts.router.owner();
if (owner != currentOwner) {
this.logger(`Transfer ownership of ${chain}'s router to ${owner}`);
await super.runIfOwner(chain, contracts.router, async () => {
await chainConnection.handleTx(
await this.multiProvider.handleTx(
chain,
contracts.router.transferOwnership(
owner,
chainConnection.overrides,
this.multiProvider.getTransactionOverrides(chain),
),
);
});
@ -146,8 +143,8 @@ export abstract class HyperlaneRouterDeployer<
}
async deploy(
partialDeployment?: Partial<Record<Chain, Contracts>>,
): Promise<ChainMap<Chain, Contracts>> {
partialDeployment?: ChainMap<Contracts>,
): Promise<ChainMap<Contracts>> {
const contractsMap = await super.deploy(partialDeployment);
await this.enrollRemoteRouters(contractsMap);

@ -1,8 +1,8 @@
import { Contract } from 'ethers';
import type { Contract } from 'ethers';
import { Ownable } from '@hyperlane-xyz/core';
import type { Ownable } from '@hyperlane-xyz/core';
import type { ChainMap, ChainName, IChainConnection } from '../types';
import type { ChainName } from '../types';
export interface CheckerViolation {
chain: ChainName;
@ -12,11 +12,6 @@ export interface CheckerViolation {
contract?: Contract;
}
export type EnvironmentConfig<Chain extends ChainName> = ChainMap<
Chain,
IChainConnection
>;
export enum ViolationType {
Owner = 'Owner',
NotDeployed = 'NotDeployed',

@ -1,43 +1,12 @@
import { Signer, providers } from 'ethers';
import { types } from '@hyperlane-xyz/utils';
import { testChainConnectionConfigs } from '../consts/chainConnectionConfigs';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, ChainName, TestChainNames } from '../types';
import { ChainMap } from '../types';
import { objMap } from '../utils/objects';
import { EnvironmentConfig } from './types';
export function getTestMultiProvider<Chain extends TestChainNames>(
signerOrProvider: Signer | providers.Provider,
configs: EnvironmentConfig<Chain> = testChainConnectionConfigs,
): MultiProvider<Chain> {
let signer: Signer | undefined;
let provider: providers.Provider;
if (Signer.isSigner(signerOrProvider) && signerOrProvider.provider) {
signer = signerOrProvider;
provider = signerOrProvider.provider;
} else if (providers.Provider.isProvider(signerOrProvider)) {
provider = signerOrProvider;
} else {
throw new Error('signerOrProvider is invalid');
}
const chainProviders = objMap(configs, (_, config) => ({
signer,
provider,
id: config.id,
confirmations: config.confirmations,
overrides: config.overrides,
}));
return new MultiProvider(chainProviders);
}
export function getChainToOwnerMap<Chain extends ChainName>(
configMap: ChainMap<Chain, any>,
export function getChainToOwnerMap(
configMap: ChainMap<any>,
owner: types.Address,
): ChainMap<Chain, { owner: string }> {
): ChainMap<{ owner: string }> {
return objMap(configMap, () => {
return {
owner,

@ -28,16 +28,13 @@ enum ExplorerApiErrors {
PROXY_FAILED = 'A corresponding implementation contract was unfortunately not detected for the proxy address.',
}
export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
Chain,
VerificationInput
> {
export class ContractVerifier extends MultiGeneric<VerificationInput> {
protected logger: Debugger;
constructor(
verificationInputs: ChainMap<Chain, VerificationInput>,
protected readonly multiProvider: MultiProvider<Chain>,
protected readonly apiKeys: ChainMap<Chain, string>,
verificationInputs: ChainMap<VerificationInput>,
protected readonly multiProvider: MultiProvider,
protected readonly apiKeys: ChainMap<string>,
protected readonly flattenedSource: string, // flattened source code from eg `hardhat flatten`
protected readonly compilerOptions: CompilerOptions,
) {
@ -51,7 +48,10 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
);
}
async verifyChain(chain: Chain, inputs: VerificationInput): Promise<void> {
async verifyChain(
chain: ChainName,
inputs: VerificationInput,
): Promise<void> {
this.logger(`Verifying ${chain}...`);
for (const input of inputs) {
await this.verifyContract(chain, input);
@ -59,12 +59,11 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
}
private async submitForm(
chain: Chain,
chain: ChainName,
action: ExplorerApiActions,
options?: Record<string, string>,
): Promise<any> {
const chainConnection = this.multiProvider.getChainConnection(chain);
const apiUrl = chainConnection.getApiUrl();
const apiUrl = this.multiProvider.getExplorerApiUrl(chain);
const params = new URLSearchParams({
apikey: this.apiKeys[chain],
@ -114,7 +113,7 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
}
async verifyContract(
chain: Chain,
chain: ChainName,
input: ContractVerificationInput,
): Promise<void> {
if (input.address === ethers.constants.AddressZero) {
@ -143,9 +142,10 @@ export class ContractVerifier<Chain extends ChainName> extends MultiGeneric<
data,
);
const addressUrl = await this.multiProvider
.getChainConnection(chain)
.getAddressUrl(input.address);
const addressUrl = await this.multiProvider.getExplorerAddressUrl(
chain,
input.address,
);
// poll for verified status
if (guid) {

@ -1,15 +0,0 @@
import { chainMetadata } from './consts/chainMetadata';
import { AllChains } from './consts/chains';
import { ChainName, LooseChainMap } from './types';
export const DomainIdToChainName = Object.fromEntries(
AllChains.map((chain) => {
if (!chainMetadata[chain])
throw new Error(`Chain metadata for ${chain} could not be found`);
return [chainMetadata[chain].id, chain];
}),
) as Record<number, ChainName>;
export const ChainNameToDomainId = Object.fromEntries(
AllChains.map((chain) => [chain, chainMetadata[chain].id]),
) as LooseChainMap<number>;

@ -5,7 +5,6 @@ import type {
TypedEventFilter,
} from '@hyperlane-xyz/core/dist/common';
import { chainMetadata } from './consts/chainMetadata';
import { MultiProvider } from './providers/MultiProvider';
import { ChainName } from './types';
@ -80,7 +79,7 @@ export interface TSContract<T extends TypedEvent> {
}
export async function queryAnnotatedEvents<T extends TypedEvent>(
multiprovider: MultiProvider<any>,
multiprovider: MultiProvider,
chain: ChainName,
contract: TSContract<T>,
filter: TypedEventFilter<T>,
@ -95,11 +94,11 @@ export async function queryAnnotatedEvents<T extends TypedEvent>(
startBlock,
endBlock,
);
return Annotated.fromEvents(chainMetadata[chain].id, events);
return Annotated.fromEvents(multiprovider.getChainId(chain), events);
}
export async function findAnnotatedSingleEvent<T extends TypedEvent>(
multiprovider: MultiProvider<any>,
multiprovider: MultiProvider,
chain: ChainName,
contract: TSContract<T>,
filter: TypedEventFilter<T>,
@ -112,18 +111,19 @@ export async function findAnnotatedSingleEvent<T extends TypedEvent>(
filter,
startBlock,
);
return Annotated.fromEvents(chainMetadata[chain].id, events);
return Annotated.fromEvents(multiprovider.getChainId(chain), events);
}
export async function getEvents<T extends TypedEvent>(
multiprovider: MultiProvider<any>,
multiprovider: MultiProvider,
chain: ChainName,
contract: TSContract<T>,
filter: TypedEventFilter<T>,
startBlock?: number,
endBlock?: number,
): Promise<Array<T>> {
const mustPaginate = !!chainMetadata[chain].publicRpcUrls[0].pagination;
const metadata = multiprovider.getChainMetadata(chain);
const mustPaginate = !!metadata.publicRpcUrls[0].pagination;
if (mustPaginate) {
return getPaginatedEvents(
multiprovider,
@ -138,13 +138,14 @@ export async function getEvents<T extends TypedEvent>(
}
export async function findEvent<T extends TypedEvent>(
multiprovider: MultiProvider<any>,
multiprovider: MultiProvider,
chain: ChainName,
contract: TSContract<T>,
filter: TypedEventFilter<T>,
startBlock?: number,
): Promise<Array<T>> {
const mustPaginate = !!chainMetadata[chain].publicRpcUrls[0].pagination;
const metadata = multiprovider.getChainMetadata(chain);
const mustPaginate = !!metadata.publicRpcUrls[0].pagination;
if (mustPaginate) {
return findFromPaginatedEvents(
multiprovider,
@ -158,14 +159,15 @@ export async function findEvent<T extends TypedEvent>(
}
async function getPaginatedEvents<T extends TypedEvent>(
multiprovider: MultiProvider<any>,
multiprovider: MultiProvider,
chain: ChainName,
contract: TSContract<T>,
filter: TypedEventFilter<T>,
startBlock?: number,
endBlock?: number,
): Promise<Array<T>> {
const pagination = chainMetadata[chain].publicRpcUrls[0].pagination;
const metadata = multiprovider.getChainMetadata(chain);
const pagination = metadata.publicRpcUrls[0].pagination;
if (!pagination) {
throw new Error('Domain need not be paginated');
}
@ -178,7 +180,7 @@ async function getPaginatedEvents<T extends TypedEvent>(
// or current block number
let lastBlock;
if (!endBlock) {
const provider = multiprovider.getChainConnection(chain).provider!;
const provider = multiprovider.getProvider(chain);
lastBlock = await provider.getBlockNumber();
} else {
lastBlock = endBlock;
@ -201,14 +203,15 @@ async function getPaginatedEvents<T extends TypedEvent>(
}
async function findFromPaginatedEvents<T extends TypedEvent>(
multiprovider: MultiProvider<any>,
multiprovider: MultiProvider,
chain: ChainName,
contract: TSContract<T>,
filter: TypedEventFilter<T>,
startBlock?: number,
endBlock?: number,
): Promise<Array<T>> {
const pagination = chainMetadata[chain].publicRpcUrls[0].pagination;
const metadata = multiprovider.getChainMetadata(chain);
const pagination = metadata.publicRpcUrls[0].pagination;
if (!pagination) {
throw new Error('Domain need not be paginated');
}
@ -221,7 +224,7 @@ async function findFromPaginatedEvents<T extends TypedEvent>(
// or current block number
let lastBlock;
if (!endBlock) {
const provider = multiprovider.getChainConnection(chain).provider!;
const provider = multiprovider.getProvider(chain);
lastBlock = await provider.getBlockNumber();
} else {
lastBlock = endBlock;

@ -5,31 +5,30 @@ 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 { getTestMultiProvider } from '../deploy/utils';
import { MultiProvider } from '../providers/MultiProvider';
import { TestChainNames } from '../types';
import { InterchainGasCalculator } from './calculator';
describe('InterchainGasCalculator', async () => {
const localChain = 'test1';
const remoteChain = 'test2';
const localChain = Chains.test1;
const remoteChain = Chains.test2;
const expectedDefaultQuote = BigNumber.from('1');
const testGasAmount = BigNumber.from('100000');
let signer: SignerWithAddress;
let multiProvider: MultiProvider<TestChainNames>;
let multiProvider: MultiProvider;
let calculator: InterchainGasCalculator<TestChainNames>;
let calculator: InterchainGasCalculator;
let igp: types.Address;
before(async () => {
[signer] = await ethers.getSigners();
multiProvider = getTestMultiProvider(signer);
multiProvider = MultiProvider.createTestMultiProvider({ signer });
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();

@ -2,7 +2,6 @@ import { expect } from 'chai';
import { BigNumber } from 'ethers';
import sinon from 'sinon';
import { chainMetadata } from '../consts/chainMetadata';
import { Chains } from '../consts/chains';
import { HyperlaneCore } from '../core/HyperlaneCore';
import { CoreContracts } from '../core/contracts';
@ -17,48 +16,36 @@ const SUGGESTED_GAS_PRICE = 10;
const INBOX_PROCESS_OVERHEAD_GAS = 100_000;
// Exposes protected methods so they can be stubbed.
class TestInterchainGasCalculator<
Chain extends ChainName,
> extends InterchainGasCalculator<Chain> {
estimateGasForProcess<Destination extends Chain>(
origin: Exclude<Chain, Destination>,
destination: Destination,
class TestInterchainGasCalculator extends InterchainGasCalculator {
estimateGasForProcess(
origin: ChainName,
destination: ChainName,
): Promise<BigNumber> {
return super.estimateGasForProcess(origin, destination);
}
estimateGasForHandle<LocalChain extends Chain>(
message: ParsedMessage<Chain, LocalChain>,
): Promise<BigNumber> {
estimateGasForHandle(message: ParsedMessage): Promise<BigNumber> {
return super.estimateGasForHandle(message);
}
convertBetweenTokens(
fromChain: Chain,
toChain: Chain,
fromChain: ChainName,
toChain: ChainName,
fromAmount: BigNumber,
): Promise<BigNumber> {
return super.convertBetweenTokens(fromChain, toChain, fromAmount);
}
tokenDecimals(chain: Chain): number {
tokenDecimals(chain: ChainName): number {
return super.tokenDecimals(chain);
}
getGasPrice(chain: Chain): Promise<BigNumber> {
getGasPrice(chain: ChainName): Promise<BigNumber> {
return super.getGasPrice(chain);
}
}
describe('InterchainGasCalculator', () => {
const provider = new MockProvider();
// TODO: fix types to not require the <any> here.
// This is because InterchainGasCalculator isn't very strongly typed,
// which is because ParsedMessage isn't very strongly typed. This results
// in InterchainGasCalculator expecting a multiprovider with providers for
// every chain.
const multiProvider = new MultiProvider({
test1: { id: chainMetadata.test1.id, provider },
test2: { id: chainMetadata.test2.id, provider },
test3: { id: chainMetadata.test3.id, provider },
});
const core: HyperlaneCore<TestChainNames> = HyperlaneCore.fromEnvironment(
const multiProvider = MultiProvider.createTestMultiProvider({ provider });
const core: HyperlaneCore = HyperlaneCore.fromEnvironment(
'test',
multiProvider,
);
@ -66,7 +53,7 @@ describe('InterchainGasCalculator', () => {
const destination = Chains.test2;
let tokenPriceGetter: MockTokenPriceGetter;
let calculator: TestInterchainGasCalculator<TestChainNames>;
let calculator: TestInterchainGasCalculator;
beforeEach(() => {
tokenPriceGetter = new MockTokenPriceGetter();
@ -200,9 +187,7 @@ describe('InterchainGasCalculator', () => {
});
it('considers when the origin token decimals < the destination token decimals', async () => {
sinon
.stub(calculator, 'tokenDecimals')
.callsFake((chain: TestChainNames) => {
sinon.stub(calculator, 'tokenDecimals').callsFake((chain: ChainName) => {
if (chain === origin) {
return 16;
}

@ -4,15 +4,9 @@ import { BigNumber, FixedNumber, ethers } from 'ethers';
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core';
import { types, utils } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../consts/chainMetadata';
import {
CoreEnvironment,
CoreEnvironmentChain,
HyperlaneCore,
} from '../core/HyperlaneCore';
import { ChainNameToDomainId } from '../domains';
import { CoreEnvironment, HyperlaneCore } from '../core/HyperlaneCore';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainName, Remotes } from '../types';
import { ChainName } from '../types';
import { convertDecimalValue, mulBigAndFixed } from '../utils/number';
import { CoinGeckoTokenPriceGetter, TokenPriceGetter } from './token-prices';
@ -70,23 +64,20 @@ export interface InterchainGasCalculatorConfig {
tokenPriceGetter?: TokenPriceGetter;
}
export type ParsedMessage<
Chain extends ChainName,
Destination extends Chain,
> = {
origin: Exclude<Chain, Destination>;
export interface ParsedMessage {
origin: ChainName;
sender: string;
destination: Destination;
destination: ChainName;
recipient: string;
body: string;
};
}
/**
* Calculates interchain gas payments.
*/
export class InterchainGasCalculator<Chain extends ChainName> {
private core: HyperlaneCore<Chain>;
private multiProvider: MultiProvider<Chain>;
export class InterchainGasCalculator {
private core: HyperlaneCore;
private multiProvider: MultiProvider;
private tokenPriceGetter: TokenPriceGetter;
@ -95,16 +86,16 @@ export class InterchainGasCalculator<Chain extends ChainName> {
static fromEnvironment<Env extends CoreEnvironment>(
env: Env,
multiProvider: MultiProvider<CoreEnvironmentChain<Env>>,
multiProvider: MultiProvider,
config?: InterchainGasCalculatorConfig,
): InterchainGasCalculator<CoreEnvironmentChain<Env>> {
): InterchainGasCalculator {
const core = HyperlaneCore.fromEnvironment(env, multiProvider);
return new InterchainGasCalculator(multiProvider, core, config);
}
constructor(
multiProvider: MultiProvider<Chain>,
core: HyperlaneCore<Chain>,
multiProvider: MultiProvider,
core: HyperlaneCore,
config?: InterchainGasCalculatorConfig,
) {
this.multiProvider = multiProvider;
@ -139,9 +130,9 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* function.
* @returns The amount of native tokens required to pay for interchain gas.
*/
async quoteGasPaymentForDefaultIsmIgp<Destination extends Chain>(
origin: Exclude<Chain, Destination>,
destination: Destination,
async quoteGasPaymentForDefaultIsmIgp(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
): Promise<BigNumber> {
const igpAddress =
@ -168,9 +159,9 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* gas cost.
* @returns The amount of native tokens required to pay for interchain gas.
*/
async quoteGasPayment<Destination extends Chain>(
origin: Exclude<Chain, Destination>,
destination: Destination,
async quoteGasPayment(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
): Promise<BigNumber> {
const igpAddress = this.core.getContracts(origin).interchainGasPaymaster;
@ -196,18 +187,19 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* function.
* @returns The amount of native tokens required to pay for interchain gas.
*/
async quoteGasPaymentForIGP<Destination extends Chain>(
origin: Exclude<Chain, Destination>,
destination: Destination,
async quoteGasPaymentForIGP(
origin: ChainName,
destination: ChainName,
gasAmount: BigNumber,
interchainGasPaymasterAddress: types.Address,
): Promise<BigNumber> {
const originProvider = this.multiProvider.getChainProvider(origin);
const originProvider = this.multiProvider.getProvider(origin);
const igp = InterchainGasPaymaster__factory.connect(
interchainGasPaymasterAddress,
originProvider,
);
return igp.quoteGasPayment(ChainNameToDomainId[destination], gasAmount);
const domainId = this.multiProvider.getDomainId(destination);
return igp.quoteGasPayment(domainId, gasAmount);
}
/**
@ -222,9 +214,9 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* @returns An estimated amount of origin chain tokens to cover gas costs on the
* destination chain.
*/
async estimatePaymentForGas<Destination extends Chain>(
origin: Exclude<Chain, Destination>,
destination: Destination,
async estimatePaymentForGas(
origin: ChainName,
destination: ChainName,
gas: BigNumber,
): Promise<BigNumber> {
const destinationGasPrice = await this.getGasPrice(destination);
@ -258,9 +250,9 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* @returns An estimated amount of origin chain tokens to cover gas costs of the
* message on the destination chain.
*/
async estimatePaymentForHandleGas<Destination extends Chain>(
origin: Exclude<Chain, Destination>,
destination: Destination,
async estimatePaymentForHandleGas(
origin: ChainName,
destination: ChainName,
handleGas: BigNumber,
): Promise<BigNumber> {
const destinationGas = handleGas.add(
@ -281,8 +273,8 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* @returns An estimated amount of origin chain tokens to cover gas costs of the
* message on the destination chain.
*/
protected async estimatePaymentForMessage<Destination extends Chain>(
message: ParsedMessage<Chain, Destination>,
protected async estimatePaymentForMessage(
message: ParsedMessage,
): Promise<BigNumber> {
const handleGas = await this.estimateGasForHandle(message);
return this.estimatePaymentForHandleGas(
@ -303,8 +295,8 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* `fromAmount` of `fromChain` native tokens.
*/
protected async convertBetweenTokens(
fromChain: Chain,
toChain: Chain,
fromChain: ChainName,
toChain: ChainName,
value: BigNumber,
): Promise<BigNumber> {
// Does not factor in differing token decimals.
@ -328,8 +320,8 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* @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: Chain): Promise<BigNumber> {
const provider = this.multiProvider.getChainConnection(chain).provider!;
protected async getGasPrice(chain: ChainName): Promise<BigNumber> {
const provider = this.multiProvider.getProvider(chain);
if (provider == undefined) {
throw new Error(`Missing provider for ${chain}`);
}
@ -341,8 +333,11 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* @param chain The chain.
* @returns The number of decimals of `chain`'s native token.
*/
protected tokenDecimals(chain: Chain): number {
return chainMetadata[chain].nativeToken.decimals ?? DEFAULT_TOKEN_DECIMALS;
protected tokenDecimals(chain: ChainName): number {
return (
this.multiProvider.tryGetChainMetadata(chain)?.nativeToken?.decimals ??
DEFAULT_TOKEN_DECIMALS
);
}
/**
@ -359,11 +354,10 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* @returns The estimated gas required by the message's recipient handle function
* on the destination chain.
*/
protected async estimateGasForHandle<LocalChain extends Chain>(
message: ParsedMessage<Chain, LocalChain>,
protected async estimateGasForHandle(
message: ParsedMessage,
): Promise<BigNumber> {
const provider = this.multiProvider.getChainConnection(message.destination)
.provider!;
const provider = this.multiProvider.getProvider(message.destination);
const mailbox = this.core.getContracts(message.destination).mailbox
.contract;
@ -378,7 +372,7 @@ export class InterchainGasCalculator<Chain extends ChainName> {
to: utils.bytes32ToAddress(message.recipient),
from: mailbox.address,
data: handlerInterface.encodeFunctionData('handle', [
chainMetadata[message.origin].id,
this.multiProvider.getChainId(message.origin),
utils.addressToBytes32(message.sender),
message.body,
]),
@ -398,13 +392,15 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* operations within Inbox.sol, including intrinsic gas. Does not include any gas
* consumed within a message's recipient `handle` function.
*/
protected async estimateGasForProcess<Destination extends Chain>(
origin: Remotes<Chain, Destination>,
destination: Destination,
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(ChainNameToDomainId[origin]);
const threshold = await module.threshold(
this.multiProvider.getDomainId(origin),
);
return BigNumber.from(threshold)
.mul(GAS_OVERHEAD_PER_SIGNATURE)
.add(GAS_OVERHEAD_BASE);

@ -2,9 +2,12 @@ import CoinGecko from 'coingecko-api';
import { warn } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../consts/chainMetadata';
import { Mainnets } from '../consts/chains';
import { ChainName } from '../types';
import {
ChainMetadata,
chainMetadata as defaultChainMetadata,
} from '../consts/chainMetadata';
import { CoreChainName, Mainnets } from '../consts/chains';
import { ChainMap, ChainName } from '../types';
export interface TokenPriceGetter {
getTokenPrice(chain: ChainName): Promise<number>;
@ -69,10 +72,16 @@ class TokenPriceCache {
export class CoinGeckoTokenPriceGetter implements TokenPriceGetter {
protected coinGecko: CoinGeckoInterface;
protected cache: TokenPriceCache;
protected metadata: ChainMap<ChainMetadata>;
constructor(coinGecko: CoinGeckoInterface, expirySeconds?: number) {
constructor(
coinGecko: CoinGeckoInterface,
expirySeconds?: number,
chainMetadata = defaultChainMetadata,
) {
this.coinGecko = coinGecko;
this.cache = new TokenPriceCache(expirySeconds);
this.metadata = chainMetadata;
}
async getTokenPrice(chain: ChainName): Promise<number> {
@ -89,7 +98,8 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter {
}
private async getTokenPrices(chains: ChainName[]): Promise<number[]> {
const isMainnet = chains.map((c) => Mainnets.includes(c));
// TODO improve PI support here?
const isMainnet = chains.map((c) => Mainnets.includes(c as CoreChainName));
const allMainnets = isMainnet.every((v) => v === true);
const allTestnets = isMainnet.every((v) => v === false);
if (allTestnets) {
@ -119,7 +129,7 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter {
// The CoinGecko API expects, in some cases, IDs that do not match
// ChainNames.
const ids = chains.map(
(chain) => chainMetadata[chain].gasCurrencyCoinGeckoId || chain,
(chain) => this.metadata[chain].gasCurrencyCoinGeckoId || chain,
);
const response = await this.coinGecko.simple.price({
ids,

@ -1,122 +1,52 @@
export {
Chains,
ChainName,
DeprecatedChains,
AllChains,
Mainnets,
AllDeprecatedChains,
} from './consts/chains';
export {
chainIdToMetadata,
ChainMetadata,
RpcPagination,
ExplorerFamily,
chainMetadata,
chainIdToMetadata,
ExplorerFamily,
mainnetChainsMetadata,
RpcPagination,
testnetChainsMetadata,
wagmiChainMetadata,
PartialChainMetadata,
partialChainMetadata,
} from './consts/chainMetadata';
export {
chainConnectionConfigs,
testChainConnectionConfigs,
} from './consts/chainConnectionConfigs';
AllChains,
AllDeprecatedChains,
Chains,
CoreChainName,
DeprecatedChains,
Mainnets,
} from './consts/chains';
export {
environments as coreEnvironments,
hyperlaneCoreAddresses,
} from './consts/environments';
export {
ChainMap,
CompleteChainMap,
Connection,
NameOrDomain,
RemoteChainMap,
Remotes,
TestChainNames,
IChainConnection,
} from './types';
export { ChainNameToDomainId, DomainIdToChainName } from './domains';
export { HyperlaneApp } from './HyperlaneApp';
export {
buildContracts,
connectContracts,
HyperlaneAddresses,
HyperlaneContracts,
HyperlaneFactories,
buildContracts,
connectContracts,
serializeContracts,
} from './contracts';
export {
Annotated,
getEvents,
queryAnnotatedEvents,
TSContract,
} from './events';
export {
TransparentProxyAddresses,
ProxiedContract,
ProxyAddresses,
} from './proxy';
export {
Router,
RouterContracts,
RouterFactories,
RouterApp,
GasRouterApp,
} from './router';
export { ChainConnection } from './providers/ChainConnection';
export { MultiProvider } from './providers/MultiProvider';
export { RetryJsonRpcProvider, RetryProvider } from './providers/RetryProvider';
export {
HyperlaneCore,
CoreContractsMap,
DispatchedMessage,
} from './core/HyperlaneCore';
export {
ConnectionClientContracts,
CoreContracts,
coreFactories,
ConnectionClientContracts,
} from './core/contracts';
export {
HyperlaneLifecyleEvent,
AnnotatedDispatch,
AnnotatedLifecycleEvent,
HyperlaneLifecyleEvent,
} from './core/events';
export {
HyperlaneMessage,
HyperlaneStatus,
MessageStatus,
resolveDomain,
resolveId,
resolveChains,
} from './core/message';
CoreContractsMap,
DispatchedMessage,
HyperlaneCore,
} from './core/HyperlaneCore';
export { TestCoreApp, TestCoreContracts } from './core/TestCoreApp';
export { TestCoreDeployer } from './core/TestCoreDeployer';
export { InterchainGasCalculator, ParsedMessage } from './gas/calculator';
export {
CoinGeckoTokenPriceGetter,
TokenPriceGetter,
} from './gas/token-prices';
export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker';
export {
CheckerViolation,
EnvironmentConfig,
OwnerViolation,
ViolationType,
} from './deploy/types';
export { HyperlaneCoreDeployer } from './deploy/core/HyperlaneCoreDeployer';
export { HyperlaneCoreChecker } from './deploy/core/HyperlaneCoreChecker';
export { HyperlaneCoreDeployer } from './deploy/core/HyperlaneCoreDeployer';
export {
CoreConfig,
CoreViolationType,
@ -124,40 +54,78 @@ export {
MultisigIsmViolation,
MultisigIsmViolationType,
} from './deploy/core/types';
export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker';
export { HyperlaneDeployer } from './deploy/HyperlaneDeployer';
export { ProxyViolation } from './deploy/proxy';
export { HyperlaneRouterDeployer } from './deploy/router/HyperlaneRouterDeployer';
export { GasRouterDeployer } from './deploy/router/GasRouterDeployer';
export { HyperlaneRouterChecker } from './deploy/router/HyperlaneRouterChecker';
export {
InterchainAccountDeployer,
InterchainQueryDeployer,
} from './deploy/middleware/deploy';
export { LiquidityLayerApp } from './deploy/middleware/LiquidityLayerApp';
export {
LiquidityLayerDeployer,
BridgeAdapterType,
BridgeAdapterConfig,
BridgeAdapterType,
CircleBridgeAdapterConfig,
LiquidityLayerDeployer,
PortalAdapterConfig,
} from './deploy/middleware/LiquidityLayerRouterDeployer';
export { LiquidityLayerApp } from './deploy/middleware/LiquidityLayerApp';
export {
LiquidityLayerContracts,
interchainAccountFactories,
interchainQueryFactories,
liquidityLayerFactories,
} from './middleware';
export { ProxyViolation } from './deploy/proxy';
export { GasRouterDeployer } from './deploy/router/GasRouterDeployer';
export { HyperlaneRouterChecker } from './deploy/router/HyperlaneRouterChecker';
export { HyperlaneRouterDeployer } from './deploy/router/HyperlaneRouterDeployer';
export { GasRouterConfig, RouterConfig } from './deploy/router/types';
export { getTestMultiProvider, getChainToOwnerMap } from './deploy/utils';
export {
CheckerViolation,
OwnerViolation,
ViolationType,
} from './deploy/types';
export { getChainToOwnerMap } from './deploy/utils';
export { ContractVerifier } from './deploy/verify/ContractVerifier';
export {
CompilerOptions,
ContractVerificationInput,
VerificationInput,
CompilerOptions,
} from './deploy/verify/types';
export * as verificationUtils from './deploy/verify/utils';
export {
Annotated,
getEvents,
queryAnnotatedEvents,
TSContract,
} from './events';
export { InterchainGasCalculator, ParsedMessage } from './gas/calculator';
export {
CoinGeckoTokenPriceGetter,
TokenPriceGetter,
} from './gas/token-prices';
export { HyperlaneApp } from './HyperlaneApp';
export {
interchainAccountFactories,
interchainQueryFactories,
LiquidityLayerContracts,
liquidityLayerFactories,
} from './middleware';
export { MultiProvider } from './providers/MultiProvider';
export { RetryJsonRpcProvider, RetryProvider } from './providers/RetryProvider';
export {
ProxiedContract,
ProxyAddresses,
TransparentProxyAddresses,
} from './proxy';
export {
GasRouterApp,
Router,
RouterApp,
RouterContracts,
RouterFactories,
} from './router';
export { getTestOwnerConfig } from './test/testUtils';
export {
ChainMap,
ChainName,
Connection,
NameOrDomain,
TestChainNames,
} from './types';
export { canonizeId, evmId } from './utils/ids';
export { MultiGeneric } from './utils/MultiGeneric';
export {
@ -166,6 +134,6 @@ export {
fixedToBig,
mulBigAndFixed,
} from './utils/number';
export { objMap, objMapEntries, promiseObjAll, pick } from './utils/objects';
export { objMap, objMapEntries, pick, promiseObjAll } from './utils/objects';
export { delay } from './utils/time';
export { chainMetadataToWagmiChain } from './utils/wagmi';

@ -8,42 +8,39 @@ import {
} from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils';
import { testChainConnectionConfigs } from '../consts/chainConnectionConfigs';
import { Chains } from '../consts/chains';
import { TestCoreApp } from '../core/TestCoreApp';
import { TestCoreDeployer } from '../core/TestCoreDeployer';
import { InterchainAccountDeployer } from '../deploy/middleware/deploy';
import { RouterConfig } from '../deploy/router/types';
import { getChainToOwnerMap, getTestMultiProvider } from '../deploy/utils';
import { ChainNameToDomainId } from '../domains';
import { InterchainAccountContracts } from '../middleware';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, TestChainNames } from '../types';
import { getTestOwnerConfig } from '../test/testUtils';
import { ChainMap } from '../types';
import { objMap, promiseObjAll } from '../utils/objects';
describe('InterchainAccountRouter', async () => {
const localChain = 'test1';
const remoteChain = 'test2';
const localDomain = ChainNameToDomainId[localChain];
const remoteDomain = ChainNameToDomainId[remoteChain];
const localChain = Chains.test1;
const remoteChain = Chains.test2;
let signer: SignerWithAddress;
let contracts: ChainMap<TestChainNames, InterchainAccountContracts>;
let contracts: ChainMap<InterchainAccountContracts>;
let local: InterchainAccountRouter;
let remote: InterchainAccountRouter;
let multiProvider: MultiProvider<TestChainNames>;
let multiProvider: MultiProvider;
let coreApp: TestCoreApp;
let config: ChainMap<TestChainNames, RouterConfig>;
let config: ChainMap<RouterConfig>;
before(async () => {
[signer] = await ethers.getSigners();
multiProvider = getTestMultiProvider(signer);
multiProvider = MultiProvider.createTestMultiProvider({ signer });
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
coreApp = new TestCoreApp(coreContractsMaps, multiProvider);
config = coreApp.extendWithConnectionClientConfig(
getChainToOwnerMap(testChainConnectionConfigs, signer.address),
getTestOwnerConfig(signer.address),
);
config.test1.interchainSecurityModule =
@ -83,11 +80,11 @@ describe('InterchainAccountRouter', async () => {
fooMessage,
]);
const icaAddress = await remote['getInterchainAccount(uint32,address)'](
localDomain,
multiProvider.getDomainId(localChain),
signer.address,
);
await local.dispatch(remoteDomain, [
await local.dispatch(multiProvider.getDomainId(remoteChain), [
{
_call: { to: utils.addressToBytes32(recipient.address), data },
value: 0,

@ -16,7 +16,8 @@ import {
} from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils';
import { testChainConnectionConfigs } from '../consts/chainConnectionConfigs';
import { chainMetadata } from '../consts/chainMetadata';
import { Chains } from '../consts/chains';
import { TestCoreApp } from '../core/TestCoreApp';
import { TestCoreDeployer } from '../core/TestCoreDeployer';
import { LiquidityLayerApp } from '../deploy/middleware/LiquidityLayerApp';
@ -27,25 +28,24 @@ import {
LiquidityLayerDeployer,
PortalAdapterConfig,
} from '../deploy/middleware/LiquidityLayerRouterDeployer';
import { getChainToOwnerMap, getTestMultiProvider } from '../deploy/utils';
import { ChainNameToDomainId } from '../domains';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, TestChainNames } from '../types';
import { getTestOwnerConfig } from '../test/testUtils';
import { ChainMap } from '../types';
import { objMap } from '../utils/objects';
describe('LiquidityLayerRouter', async () => {
const localChain = 'test1';
const remoteChain = 'test2';
const localDomain = ChainNameToDomainId[localChain];
const remoteDomain = ChainNameToDomainId[remoteChain];
const localChain = Chains.test1;
const remoteChain = Chains.test2;
const localDomain = chainMetadata[localChain].chainId;
const remoteDomain = chainMetadata[remoteChain].chainId;
let signer: SignerWithAddress;
let local: LiquidityLayerRouter;
let multiProvider: MultiProvider<TestChainNames>;
let multiProvider: MultiProvider;
let coreApp: TestCoreApp;
let liquidityLayerApp: LiquidityLayerApp<TestChainNames>;
let config: ChainMap<TestChainNames, LiquidityLayerConfig>;
let liquidityLayerApp: LiquidityLayerApp;
let config: ChainMap<LiquidityLayerConfig>;
let mockToken: MockToken;
let circleTokenMessenger: MockCircleTokenMessenger;
let portalBridge: MockPortalBridge;
@ -54,7 +54,7 @@ describe('LiquidityLayerRouter', async () => {
before(async () => {
[signer] = await ethers.getSigners();
multiProvider = getTestMultiProvider(signer);
multiProvider = MultiProvider.createTestMultiProvider({ signer });
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
@ -74,9 +74,7 @@ describe('LiquidityLayerRouter', async () => {
messageTransmitter = await messageTransmitterF.deploy(mockToken.address);
config = coreApp.extendWithConnectionClientConfig(
objMap(
getChainToOwnerMap(testChainConnectionConfigs, signer.address),
(_chain, conf) => ({
objMap(getTestOwnerConfig(signer.address), (_chain, conf) => ({
...conf,
circle: {
type: BridgeAdapterType.Circle,
@ -108,8 +106,7 @@ describe('LiquidityLayerRouter', async () => {
},
],
} as PortalAdapterConfig,
}),
),
})),
);
});

@ -9,40 +9,40 @@ import {
} from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils';
import { testChainConnectionConfigs } from '../consts/chainConnectionConfigs';
import { chainMetadata } from '../consts/chainMetadata';
import { Chains } from '../consts/chains';
import { TestCoreApp } from '../core/TestCoreApp';
import { TestCoreDeployer } from '../core/TestCoreDeployer';
import { InterchainQueryDeployer } from '../deploy/middleware/deploy';
import { RouterConfig } from '../deploy/router/types';
import { getChainToOwnerMap, getTestMultiProvider } from '../deploy/utils';
import { ChainNameToDomainId } from '../domains';
import { MultiProvider } from '../providers/MultiProvider';
import { ChainMap, TestChainNames } from '../types';
import { getTestOwnerConfig } from '../test/testUtils';
import { ChainMap } from '../types';
describe('InterchainQueryRouter', async () => {
const localChain = 'test1';
const remoteChain = 'test2';
const localDomain = ChainNameToDomainId[localChain];
const remoteDomain = ChainNameToDomainId[remoteChain];
const localChain = Chains.test1;
const remoteChain = Chains.test2;
const localDomain = chainMetadata[localChain].chainId;
const remoteDomain = chainMetadata[remoteChain].chainId;
let signer: SignerWithAddress;
let local: InterchainQueryRouter;
let remote: InterchainQueryRouter;
let multiProvider: MultiProvider<TestChainNames>;
let multiProvider: MultiProvider;
let coreApp: TestCoreApp;
let config: ChainMap<TestChainNames, RouterConfig>;
let config: ChainMap<RouterConfig>;
let testQuery: TestQuery;
before(async () => {
[signer] = await ethers.getSigners();
multiProvider = getTestMultiProvider(signer);
multiProvider = MultiProvider.createTestMultiProvider({ signer });
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
coreApp = new TestCoreApp(coreContractsMaps, multiProvider);
config = coreApp.extendWithConnectionClientConfig(
getChainToOwnerMap(testChainConnectionConfigs, signer.address),
getTestOwnerConfig(signer.address),
);
});

@ -1,86 +0,0 @@
import { Debugger, debug } from 'debug';
import { ethers } from 'ethers';
import { IChainConnection } from '../types';
export class ChainConnection {
id: number;
provider: ethers.providers.Provider;
signer?: ethers.Signer;
overrides: ethers.Overrides;
confirmations: number;
blockExplorerUrl: string;
blockExplorerApiUrl: string;
logger: Debugger;
constructor(dc: IChainConnection) {
this.id = dc.id;
this.provider = dc.provider;
this.signer = dc.signer;
this.overrides = dc.overrides ?? {};
this.confirmations = dc.confirmations ?? 0;
this.blockExplorerUrl = dc.blockExplorerUrl ?? 'UNKNOWN_EXPLORER';
this.blockExplorerApiUrl = dc.blockExplorerApiUrl ?? this.blockExplorerUrl;
this.logger = debug('hyperlane:ChainConnection');
}
getConnection = (): ethers.providers.Provider | ethers.Signer =>
this.signer ?? this.provider;
getAddress = (): Promise<string> | undefined => this.signer?.getAddress();
getTxUrl(response: ethers.providers.TransactionResponse): string {
return `${this.blockExplorerUrl}/tx/${response.hash}`;
}
async getAddressUrl(address?: string): Promise<string> {
return `${this.blockExplorerUrl}/address/${
address ?? (await this.signer!.getAddress())
}`;
}
getApiUrl(): string {
return this.blockExplorerApiUrl + '/api';
}
async handleTx(
tx: ethers.ContractTransaction | Promise<ethers.ContractTransaction>,
): Promise<ethers.ContractReceipt> {
const response = await tx;
this.logger(
`Pending ${this.getTxUrl(response)} (waiting ${
this.confirmations
} blocks for confirmation)`,
);
return response.wait(this.confirmations);
}
async estimateGas(
tx: ethers.PopulatedTransaction,
from?: string,
): Promise<ethers.BigNumber> {
let txFrom = from;
if (!txFrom) {
txFrom = await this.getAddress();
}
return this.provider.estimateGas({
...tx,
from: txFrom,
...this.overrides,
});
}
async sendTransaction(
tx: ethers.PopulatedTransaction,
): Promise<ethers.ContractReceipt> {
if (!this.signer) throw new Error('no signer found');
const from = await this.signer.getAddress();
const response = await this.signer.sendTransaction({
...tx,
from,
...this.overrides,
});
this.logger(`sent tx ${response.hash}`);
return this.handleTx(response);
}
}

@ -1,141 +1,361 @@
import { Signer, ethers } from 'ethers';
import { ChainMap, ChainName, IChainConnection, Remotes } from '../types';
import { MultiGeneric } from '../utils/MultiGeneric';
import { objMap, pick } from '../utils/objects';
import { ChainConnection } from './ChainConnection';
export class MultiProvider<
Chain extends ChainName = ChainName,
> extends MultiGeneric<Chain, ChainConnection> {
constructor(chainConnectionConfigs: ChainMap<Chain, IChainConnection>) {
super(
objMap(
chainConnectionConfigs,
(_, connection) => new ChainConnection(connection),
),
import { Debugger, debug } from 'debug';
import {
BigNumber,
ContractReceipt,
ContractTransaction,
PopulatedTransaction,
Signer,
providers,
} from 'ethers';
import { types } from '@hyperlane-xyz/utils';
import {
ChainMetadata,
chainMetadata as defaultChainMetadata,
} from '../consts/chainMetadata';
import { CoreChainName, TestChains } from '../consts/chains';
import { ChainMap, ChainName } from '../types';
import { pick } from '../utils/objects';
type Provider = providers.Provider;
interface MultiProviderOptions {
loggerName?: string;
}
export class MultiProvider {
public readonly metadata: ChainMap<ChainMetadata> = {};
private readonly providers: ChainMap<Provider> = {};
private signers: ChainMap<Signer> = {};
private useSharedSigner = false; // A single signer to be used for all chains
private readonly logger: Debugger;
/**
* Create a new MultiProvider with the given chainMetadata,
* or the SDK's default metadata if not provided
*/
constructor(
chainMetadata: ChainMap<ChainMetadata> = defaultChainMetadata,
options: MultiProviderOptions = {},
) {
this.metadata = chainMetadata;
// 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))
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);
}
this.logger = debug(options?.loggerName || 'hyperlane:MultiProvider');
}
/**
* Get the metadata for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
tryGetChainMetadata(chainNameOrId: ChainName | number): ChainMetadata | null {
let chainMetadata: ChainMetadata | undefined;
if (typeof chainNameOrId === 'string') {
chainMetadata = this.metadata[chainNameOrId];
} else if (typeof chainNameOrId === 'number') {
chainMetadata = Object.values(this.metadata).find(
(m) => m.chainId === chainNameOrId || m.domainId === chainNameOrId,
);
}
return chainMetadata || null;
}
/**
* Get the metadata for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainMetadata(chainNameOrId: ChainName | number): ChainMetadata {
const chainMetadata = this.tryGetChainMetadata(chainNameOrId);
if (!chainMetadata)
throw new Error(`No chain metadata set for ${chainNameOrId}`);
return chainMetadata;
}
/**
* Get chainConnection for a chain
* @throws if chain is invalid or has not been set
* Get the name for a given chain name, chain id, or domain id
*/
getChainConnection(chain: Chain): ChainConnection {
return this.get(chain);
tryGetChainName(chainNameOrId: ChainName | number): string | null {
return this.tryGetChainMetadata(chainNameOrId)?.name ?? null;
}
/**
* Get chainConnection for a chain
* @returns value or null if chain value has not been set
* Get the name for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
tryGetChainConnection(chain: Chain): ChainConnection | null {
return this.tryGet(chain);
getChainName(chainNameOrId: ChainName | number): string {
return this.getChainMetadata(chainNameOrId).name;
}
/**
* Set value for a chain
* @throws if chain is invalid or has not been set
* Get the names for all chains known to this MultiProvider
*/
setChainConnection(
chain: Chain,
chainConnectionConfig: IChainConnection,
): ChainConnection {
const connection = new ChainConnection(chainConnectionConfig);
return this.set(chain, connection);
getKnownChainNames(): string[] {
return Object.keys(this.metadata);
}
/**
* Get provider for a chain
* @throws if chain is invalid or has not been set
* Get the id for a given chain name, chain id, or domain id
*/
getChainProvider(chain: Chain): ethers.providers.Provider {
const chainConnection = this.get(chain);
if (!chainConnection.provider) {
throw new Error(`No provider set for chain ${chain}`);
tryGetChainId(chainNameOrId: ChainName | number): number | null {
return this.tryGetChainMetadata(chainNameOrId)?.chainId ?? null;
}
return chainConnection.provider;
/**
* Get the id for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getChainId(chainNameOrId: ChainName | number): number {
return this.getChainMetadata(chainNameOrId).chainId;
}
/**
* Get provider for a chain
* @returns value or null if chain value has not been set
* Get the ids for all chains known to this MultiProvider
*/
tryGetChainProvider(chain: Chain): ethers.providers.Provider | null {
return this.tryGet(chain)?.provider ?? null;
getKnownChainIds(): number[] {
return Object.values(this.metadata).map((c) => c.chainId);
}
/**
* Get signer for a chain
* @throws if chain is invalid or has not been set
* Get the domain id for a given chain name, chain id, or domain id
*/
getChainSigner(chain: Chain): ethers.Signer {
const chainConnection = this.get(chain);
if (!chainConnection.signer) {
throw new Error(`No signer set for chain ${chain}`);
tryGetDomainId(chainNameOrId: ChainName | number): number | null {
const metadata = this.tryGetChainMetadata(chainNameOrId);
return metadata?.domainId ?? metadata?.chainId ?? null;
}
return chainConnection.signer;
/**
* Get the domain id for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getDomainId(chainNameOrId: ChainName | number): number {
const metadata = this.getChainMetadata(chainNameOrId);
return metadata.domainId ?? metadata.chainId;
}
/**
* Get signer for a chain
* @returns value or null if chain value has not been set
* Get the domain ids for a list of chain names, chain ids, or domain ids
* @throws if any chain's metadata has not been set
*/
tryGetChainSigner(chain: Chain): ethers.Signer | null {
return this.tryGet(chain)?.signer ?? null;
getDomainIds(chainNamesOrIds: Array<ChainName | number>): number[] {
return chainNamesOrIds.map((c) => this.getDomainId(c));
}
/**
* Get the id for a given chain name
* Attempts to use SDK defaults first, otherwise queries network
* @throws if chain is invalid or has not been set
* Get the ids for all chains known to this MultiProvider
*/
getChainId(chain: Chain): number {
return this.getChainConnection(chain).id;
getKnownDomainIds(): number[] {
return this.getKnownChainNames().map(this.getDomainId);
}
/**
* Create a new MultiProvider which includes the provided chain connection config
* Get an Ethers provider for a given chain name, chain id, or domain id
*/
extendWithChain<New extends Remotes<ChainName, Chain>>(
chain: New,
chainConnectionConfig: IChainConnection,
): MultiProvider<New & Chain> {
const connection = new ChainConnection(chainConnectionConfig);
return new MultiProvider<New & Chain>({
...this.chainMap,
[chain]: connection,
});
tryGetProvider(chainNameOrId: ChainName | number): Provider | null {
const metadata = this.tryGetChainMetadata(chainNameOrId);
if (!metadata) return null;
const { name, chainId: id, publicRpcUrls } = metadata;
if (this.providers[name]) return this.providers[name];
if (TestChains.includes(name as CoreChainName)) {
this.providers[name] = new providers.JsonRpcProvider(
'http://localhost:8545',
31337,
);
} else if (publicRpcUrls.length && publicRpcUrls[0].http) {
this.providers[name] = new providers.JsonRpcProvider(
publicRpcUrls[0].http,
id,
);
} else {
return null;
}
return this.providers[name];
}
/**
* Get an Ethers provider for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
getProvider(chainNameOrId: ChainName | number): Provider {
const provider = this.tryGetProvider(chainNameOrId);
if (!provider)
throw new Error(`No chain metadata set for ${chainNameOrId}`);
return provider;
}
/**
* Sets an Ethers provider for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set
*/
setProvider(chainNameOrId: ChainName | number, provider: Provider): Provider {
const chainName = this.getChainName(chainNameOrId);
this.providers[chainName] = provider;
return provider;
}
/**
* Sets Ethers providers for a set of chains
* @throws if chain's metadata has not been set
*/
setProviders(providers: ChainMap<Provider>): void {
for (const chain of Object.keys(providers)) {
const chainName = this.getChainName(chain);
this.providers[chainName] = providers[chain];
}
}
/**
* Get an Ethers signer for a given chain name, chain id, or domain id
* If signer is not yet connected, it will be connected
*/
tryGetSigner(chainNameOrId: ChainName | number): Signer | null {
const chainName = this.tryGetChainName(chainNameOrId);
if (!chainName) return null;
// Otherwise check the chain-to-signer map
const signer = this.signers[chainName];
if (!signer) return null;
if (signer.provider) return signer;
// Auto-connect the signer for convenience
const provider = this.tryGetProvider(chainName);
return provider ? signer.connect(provider) : signer;
}
/**
* Get an Ethers signer for a given chain name, chain id, or domain id
* If signer is not yet connected, it will be connected
* @throws if chain's metadata or signer has not been set
*/
getSigner(chainNameOrId: ChainName | number): Signer {
const signer = this.tryGetSigner(chainNameOrId);
if (!signer) throw new Error(`No chain signer set for ${chainNameOrId}`);
return signer;
}
/**
* Get an Ethers signer for a given chain name, chain id, or domain id
* @throws if chain's metadata or signer has not been set
*/
async getSignerAddress(
chainNameOrId: ChainName | number,
): Promise<types.Address> {
const signer = this.getSigner(chainNameOrId);
const address = await signer.getAddress();
return address;
}
/**
* Sets an Ethers Signer for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set or shared signer has already been set
*/
setSigner(chainNameOrId: ChainName | number, signer: Signer): Signer {
if (this.useSharedSigner) {
throw new Error('MultiProvider already set to use a shared signer');
}
const chainName = this.getChainName(chainNameOrId);
this.signers[chainName] = signer;
if (signer.provider && !this.providers[chainName]) {
this.providers[chainName] = signer.provider;
}
return signer;
}
/**
* Sets Ethers Signers for a set of chains
* @throws if chain's metadata has not been set or shared signer has already been set
*/
setSigners(signers: ChainMap<Signer>): void {
if (this.useSharedSigner) {
throw new Error('MultiProvider already set to use a shared signer');
}
for (const chain of Object.keys(signers)) {
const chainName = this.getChainName(chain);
this.signers[chainName] = signers[chain];
}
}
/**
* Gets the Signer if it's been set, otherwise the provider
*/
tryGetSignerOrProvider(
chainNameOrId: ChainName | number,
): Signer | Provider | null {
return (
this.tryGetSigner(chainNameOrId) || this.tryGetProvider(chainNameOrId)
);
}
/**
* Gets the Signer if it's been set, otherwise the provider
* @throws if chain metadata has not been set
*/
getSignerOrProvider(chainNameOrId: ChainName | number): Signer | Provider {
return this.tryGetSigner(chainNameOrId) || this.getProvider(chainNameOrId);
}
/**
* Sets Ethers Signers to be used for all chains
* Any subsequent calls to getSigner will return given signer
* Setting sharedSigner to null clears all signers
*/
setSharedSigner(sharedSigner: Signer | null): Signer | null {
if (!sharedSigner) {
this.useSharedSigner = false;
this.signers = {};
return null;
}
this.useSharedSigner = true;
for (const chain of this.getKnownChainNames()) {
this.signers[chain] = sharedSigner;
}
return sharedSigner;
}
/**
* Create a new MultiProvider from the intersection
* of current's chains and the provided chain list
*/
intersect<IntersectionChain extends Chain>(
intersect(
chains: ChainName[],
throwIfNotSubset = false,
): {
intersection: IntersectionChain[];
multiProvider: MultiProvider<IntersectionChain>;
intersection: ChainName[];
multiProvider: MultiProvider;
} {
const ownChains = this.chains();
const intersection = [] as IntersectionChain[];
const ownChains = this.getKnownChainNames();
const intersection: ChainName[] = [];
for (const chain of chains) {
// @ts-ignore
if (ownChains.includes(chain)) {
// @ts-ignore
intersection.push(chain);
} else {
if (throwIfNotSubset) {
} else if (throwIfNotSubset) {
throw new Error(
`MultiProvider#intersect: chains specified ${chain}, but ownChains did not include it`,
);
}
}
}
if (!intersection.length) {
throw new Error(
@ -143,25 +363,185 @@ export class MultiProvider<
);
}
const intersectionChainMap = pick(this.chainMap, intersection);
const intersectionMetadata = pick(this.metadata, intersection);
const intersectionProviders = pick(this.providers, intersection);
const intersectionSigners = pick(this.signers, intersection);
const multiProvider = new MultiProvider(intersectionMetadata);
multiProvider.setProviders(intersectionProviders);
multiProvider.setSigners(intersectionSigners);
const multiProvider = new MultiProvider<IntersectionChain>({
...intersectionChainMap,
});
return { intersection, multiProvider };
}
rotateSigner(newSigner: Signer): void {
this.forEach((chain, dc) => {
this.setChainConnection(chain, {
...dc,
signer: newSigner.connect(dc.provider),
});
});
/**
* Get chain names excluding given chain name
*/
getRemoteChains(name: ChainName): ChainName[] {
return this.getKnownChainNames().filter((n) => n !== name);
}
/**
* Get an RPC URL for given chain
* @throws if chain's metadata has not been set
*/
getRpcUrl(chainNameOrId: ChainName | number): string {
const { publicRpcUrls } = this.getChainMetadata(chainNameOrId);
if (!publicRpcUrls?.length || !publicRpcUrls[0].http)
throw new Error(`No RPC URl configured for ${chainNameOrId}`);
return publicRpcUrls[0].http;
}
/**
* Get a block explorer URL for given chain
* @throws if chain's metadata has not been set
*/
getExplorerUrl(chainNameOrId: ChainName | number): string {
const explorers = this.getChainMetadata(chainNameOrId).blockExplorers;
if (!explorers?.length) return 'UNKNOWN_EXPLORER_URL';
else return explorers[0].url;
}
/**
* Get a block explorer API URL for given chain
* @throws if chain's metadata has not been set
*/
getExplorerApiUrl(chainNameOrId: ChainName | number): string {
const explorers = this.getChainMetadata(chainNameOrId).blockExplorers;
if (!explorers?.length) return 'UNKNOWN_EXPLORER_API_URL';
else return (explorers[0].apiUrl || explorers[0].url) + '/api';
}
/**
* Get a block explorer URL for given chain's tx
* @throws if chain's metadata has not been set
*/
getExplorerTxUrl(
chainNameOrId: ChainName | number,
response: { hash: string },
): string {
return `${this.getExplorerUrl(chainNameOrId)}/tx/${response.hash}`;
}
/**
* Get a block explorer URL for given chain's address
* @throws if chain's metadata has not been set
*/
async getExplorerAddressUrl(
chainNameOrId: ChainName | number,
address?: string,
): Promise<string> {
const base = `${this.getExplorerUrl(chainNameOrId)}/address`;
if (address) return `${base}/${address}`;
const signerAddress = await this.getSignerAddress(chainNameOrId);
return `${base}/${signerAddress}`;
}
/**
* Get a block explorer URL for given chain's address
* @throws if chain's metadata has not been set
*/
getTransactionOverrides(
chainNameOrId: ChainName | number,
): Partial<providers.TransactionRequest> {
return this.getChainMetadata(chainNameOrId)?.transactionOverrides ?? {};
}
/**
* Wait for given tx to be confirmed
* @throws if chain's metadata or signer has not been set or tx fails
*/
async handleTx(
chainNameOrId: ChainName | number,
tx: ContractTransaction | Promise<ContractTransaction>,
): Promise<ContractReceipt> {
const confirmations =
this.getChainMetadata(chainNameOrId).blocks?.confirmations || 1;
const response = await tx;
this.logger(
`Pending ${this.getExplorerTxUrl(
chainNameOrId,
response,
)} (waiting ${confirmations} blocks for confirmation)`,
);
return response.wait(confirmations);
}
// This doesn't work on hardhat providers so we skip for now
// ready() {
// return Promise.all(this.values().map((dc) => dc.provider!.ready));
// }
/**
* Populate a transaction's fields using signer address and overrides
* @throws if chain's metadata has not been set or tx fails
*/
async prepareTx(
chainNameOrId: ChainName | number,
tx: PopulatedTransaction,
from?: string,
): Promise<providers.TransactionRequest> {
const txFrom = from ? from : await this.getSignerAddress(chainNameOrId);
const overrides = this.getTransactionOverrides(chainNameOrId);
return {
...tx,
from: txFrom,
...overrides,
};
}
/**
* Estimate gas for given tx
* @throws if chain's metadata has not been set or tx fails
*/
async estimateGas(
chainNameOrId: ChainName | number,
tx: PopulatedTransaction,
from?: string,
): Promise<BigNumber> {
const txReq = await this.prepareTx(chainNameOrId, tx, from);
const provider = this.getProvider(chainNameOrId);
return provider.estimateGas(txReq);
}
/**
* Send a transaction and wait for confirmation
* @throws if chain's metadata or signer has not been set or tx fails
*/
async sendTransaction(
chainNameOrId: ChainName | number,
tx: PopulatedTransaction,
): Promise<ContractReceipt> {
const txReq = await this.prepareTx(chainNameOrId, tx);
const signer = this.getSigner(chainNameOrId);
const response = await signer.sendTransaction(txReq);
this.logger(`Sent tx ${response.hash}`);
return this.handleTx(chainNameOrId, response);
}
/**
* Run given function on all known chains
*/
mapKnownChains<Output>(fn: (n: ChainName) => Output): ChainMap<Output> {
const result: ChainMap<Output> = {};
for (const chain of this.getKnownChainNames()) {
result[chain] = fn(chain);
}
return result;
}
/**
* Creates a MultiProvider using the given signer for all test networks
*/
static createTestMultiProvider(
params: { signer?: Signer; provider?: Provider } = {},
): MultiProvider {
const { signer, provider } = params;
const chainMetadata = pick(defaultChainMetadata, TestChains);
const mp = new MultiProvider(chainMetadata);
if (signer) {
mp.setSharedSigner(signer);
}
const _provider = provider || signer?.provider;
if (_provider) {
const providerMap: ChainMap<Provider> = {};
TestChains.forEach((t) => (providerMap[t] = _provider));
mp.setProviders(providerMap);
}
return mp;
}
}

@ -5,7 +5,6 @@ import type { types } from '@hyperlane-xyz/utils';
import { HyperlaneApp } from './HyperlaneApp';
import { HyperlaneContracts, HyperlaneFactories } from './contracts';
import { ChainNameToDomainId } from './domains';
import { ChainMap, ChainName } from './types';
import { objMap, promiseObjAll } from './utils/objects';
@ -34,16 +33,15 @@ export { Router } from '@hyperlane-xyz/core';
export class RouterApp<
Contracts extends RouterContracts,
Chain extends ChainName = ChainName,
> extends HyperlaneApp<Contracts, Chain> {
getSecurityModules = (): Promise<ChainMap<Chain, string>> =>
> extends HyperlaneApp<Contracts> {
getSecurityModules = (): Promise<ChainMap<types.Address>> =>
promiseObjAll(
objMap(this.contractsMap, (_, contracts) =>
contracts.router.interchainSecurityModule(),
),
);
getOwners = (): Promise<ChainMap<Chain, string>> =>
getOwners = (): Promise<ChainMap<types.Address>> =>
promiseObjAll(
objMap(this.contractsMap, (_, contracts) => contracts.router.owner()),
);
@ -51,14 +49,13 @@ export class RouterApp<
export class GasRouterApp<
Contracts extends RouterContracts<GasRouter>,
Chain extends ChainName = ChainName,
> extends RouterApp<Contracts, Chain> {
async quoteGasPayment<Origin extends Chain>(
origin: Origin,
destination: Exclude<Chain, Origin>,
> extends RouterApp<Contracts> {
async quoteGasPayment(
origin: ChainName,
destination: ChainName,
): Promise<BigNumber> {
return this.getContracts(origin).router.quoteGasPayment(
ChainNameToDomainId[destination],
this.multiProvider.getDomainId(destination),
);
}
}

@ -1,7 +1,8 @@
import { TestRouter__factory } from '@hyperlane-xyz/core';
import { HyperlaneApp } from '../../HyperlaneApp';
import { chainConnectionConfigs } from '../../consts/chainConnectionConfigs';
import { chainMetadata } from '../../consts/chainMetadata';
import { Chains, TestChains } from '../../consts/chains';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { HyperlaneDeployer } from '../../deploy/HyperlaneDeployer';
import { HyperlaneRouterChecker } from '../../deploy/router/HyperlaneRouterChecker';
@ -10,34 +11,21 @@ import { RouterConfig } from '../../deploy/router/types';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterContracts, RouterFactories } from '../../router';
import { ChainMap, ChainName } from '../../types';
import { objMap, promiseObjAll } from '../../utils/objects';
import { objMap, pick, promiseObjAll } from '../../utils/objects';
export const fullEnvTestConfigs = {
test1: chainConnectionConfigs.test1,
test2: chainConnectionConfigs.test2,
test3: chainConnectionConfigs.test3,
};
export const subsetTestConfigs = {
test1: chainConnectionConfigs.test1,
test2: chainConnectionConfigs.test2,
};
export const fullTestEnvConfigs = pick(chainMetadata, TestChains);
export type SubsetChains = keyof typeof subsetTestConfigs;
export const subsetTestConfigs = pick(chainMetadata, [
Chains.test1,
Chains.test2,
]);
export const alfajoresChainConfig = {
alfajores: chainConnectionConfigs.alfajores,
};
export const alfajoresChainConfig = pick(chainMetadata, [Chains.alfajores]);
export class EnvSubsetApp<
Chain extends ChainName = ChainName,
> extends HyperlaneApp<RouterContracts, Chain> {}
export class EnvSubsetApp extends HyperlaneApp<RouterContracts> {}
export class EnvSubsetChecker<
Chain extends ChainName,
> extends HyperlaneRouterChecker<
Chain,
EnvSubsetApp<Chain>,
export class EnvSubsetChecker extends HyperlaneRouterChecker<
EnvSubsetApp,
RouterConfig,
RouterContracts
> {}
@ -46,50 +34,46 @@ export const envSubsetFactories: RouterFactories = {
router: new TestRouter__factory(),
};
export class EnvSubsetDeployer<
Chain extends ChainName,
> extends HyperlaneRouterDeployer<
Chain,
export class EnvSubsetDeployer extends HyperlaneRouterDeployer<
RouterConfig,
RouterContracts,
RouterFactories
> {
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, RouterConfig>,
protected core: HyperlaneCore<Chain>,
multiProvider: MultiProvider,
configMap: ChainMap<RouterConfig>,
protected core: HyperlaneCore,
) {
super(multiProvider, configMap, envSubsetFactories, {});
}
// Consider moving this up to HyperlaneRouterDeployer
async initRouter(
contractsMap: ChainMap<Chain, RouterContracts>,
): Promise<void> {
async initRouter(contractsMap: ChainMap<RouterContracts>): Promise<void> {
this.logger(`Calling initialize on routers...`);
await promiseObjAll(
objMap(contractsMap, async (chain, contracts) => {
const chainConnection = this.multiProvider.getChainConnection(chain);
const mailbox = this.configMap[chain].mailbox;
const igp = this.configMap[chain].interchainGasPaymaster;
await chainConnection.handleTx(
// @ts-ignore
contracts.router.initialize(mailbox, igp, chainConnection.overrides),
const overrides = this.multiProvider.getTransactionOverrides(chain);
await this.multiProvider.handleTx(
chain,
// @ts-ignore TestRouter does implement this, though Router type does not
contracts.router.initialize(mailbox, igp, overrides),
);
}),
);
}
async deploy(): Promise<ChainMap<Chain, RouterContracts>> {
async deploy(): Promise<ChainMap<RouterContracts>> {
const contractsMap = (await HyperlaneDeployer.prototype.deploy.apply(
this,
)) as Record<Chain, RouterContracts>;
)) as ChainMap<RouterContracts>;
await this.initRouter(contractsMap);
await this.enrollRemoteRouters(contractsMap);
return contractsMap;
}
async deployContracts(chain: Chain) {
async deployContracts(chain: ChainName) {
const router = await this.deployContract(chain, 'router', []);
return {
router,

@ -1,10 +1,9 @@
import { chainMetadata } from '../../consts/chainMetadata';
import { buildContracts } from '../../contracts';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { getChainToOwnerMap } from '../../deploy/utils';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterContracts } from '../../router';
import { ChainMap, ChainName } from '../../types';
import { ChainMap } from '../../types';
import {
EnvSubsetApp,
@ -12,7 +11,6 @@ import {
alfajoresChainConfig,
envSubsetFactories,
} from './app';
import { getAlfajoresProvider } from './utils';
// Copied from output of deploy-single-chain.ts script
const deploymentAddresses = {
@ -24,22 +22,13 @@ const deploymentAddresses = {
const ownerAddress = '0x35b74Ed5038bf0488Ff33bD9819b9D12D10A7560';
async function check() {
const provider = getAlfajoresProvider();
console.info('Preparing utilities');
const multiProvider = new MultiProvider({
alfajores: {
id: chainMetadata.alfajores.id,
provider,
confirmations: alfajoresChainConfig.alfajores.confirmations,
overrides: alfajoresChainConfig.alfajores.overrides,
},
});
const multiProvider = new MultiProvider();
const contractsMap = buildContracts(
deploymentAddresses,
envSubsetFactories,
) as ChainMap<ChainName, RouterContracts>;
) as ChainMap<RouterContracts>;
const app = new EnvSubsetApp(contractsMap, multiProvider);
const core = HyperlaneCore.fromEnvironment('testnet', multiProvider);
const config = core.extendWithConnectionClientConfig(

@ -1,4 +1,4 @@
import { chainMetadata } from '../../consts/chainMetadata';
import { Chains } from '../../consts/chains';
import { serializeContracts } from '../../contracts';
import { HyperlaneCore } from '../../core/HyperlaneCore';
import { getChainToOwnerMap } from '../../deploy/utils';
@ -11,14 +11,8 @@ async function main() {
const signer = getAlfajoresSigner();
console.info('Preparing utilities');
const multiProvider = new MultiProvider({
alfajores: {
id: chainMetadata.alfajores.id,
provider: signer.provider,
confirmations: alfajoresChainConfig.alfajores.confirmations,
overrides: alfajoresChainConfig.alfajores.overrides,
},
});
const multiProvider = new MultiProvider();
multiProvider.setSigner(Chains.alfajores, signer);
const core = HyperlaneCore.fromEnvironment('testnet', multiProvider);
const config = core.extendWithConnectionClientConfig(

@ -1,34 +1,33 @@
import '@nomiclabs/hardhat-waffle';
import { ethers } from 'hardhat';
import { ChainMetadata } from '../../consts/chainMetadata';
import { TestCoreApp } from '../../core/TestCoreApp';
import { TestCoreDeployer } from '../../core/TestCoreDeployer';
import { RouterConfig } from '../../deploy/router/types';
import { EnvironmentConfig } from '../../deploy/types';
import { getChainToOwnerMap, getTestMultiProvider } from '../../deploy/utils';
import { getChainToOwnerMap } from '../../deploy/utils';
import { MultiProvider } from '../../providers/MultiProvider';
import { RouterContracts } from '../../router';
import { ChainMap, TestChainNames } from '../../types';
import { ChainMap } from '../../types';
import {
EnvSubsetApp,
EnvSubsetChecker,
EnvSubsetDeployer,
SubsetChains,
fullEnvTestConfigs,
fullTestEnvConfigs,
subsetTestConfigs,
} from './app';
// Tests deploying the basic EnvSubsetApp to a local hardhat-based test env
describe('deploy app for full test env', async () => {
let multiProvider: MultiProvider<TestChainNames>;
let config: ChainMap<TestChainNames, RouterConfig>;
let deployer: EnvSubsetDeployer<TestChainNames>;
let contracts: Record<TestChainNames, RouterContracts>;
let app: EnvSubsetApp<TestChainNames>;
let multiProvider: MultiProvider;
let config: ChainMap<RouterConfig>;
let deployer: EnvSubsetDeployer;
let contracts: ChainMap<RouterContracts>;
let app: EnvSubsetApp;
before(async () => {
const testEnv = await initTestEnv(fullEnvTestConfigs);
const testEnv = await initTestEnv(fullTestEnvConfigs);
multiProvider = testEnv.multiProvider;
config = testEnv.config;
deployer = testEnv.deployer;
@ -51,11 +50,11 @@ describe('deploy app for full test env', async () => {
// Tests same as above but only a subset of the full test env
describe('deploy app to test env subset', async () => {
let multiProvider: MultiProvider<SubsetChains>;
let config: ChainMap<SubsetChains, RouterConfig>;
let deployer: EnvSubsetDeployer<SubsetChains>;
let contracts: Record<SubsetChains, RouterContracts>;
let app: EnvSubsetApp<SubsetChains>;
let multiProvider: MultiProvider;
let config: ChainMap<RouterConfig>;
let deployer: EnvSubsetDeployer;
let contracts: ChainMap<RouterContracts>;
let app: EnvSubsetApp;
before(async () => {
const testEnv = await initTestEnv(subsetTestConfigs);
@ -82,17 +81,15 @@ describe('deploy app to test env subset', async () => {
});
});
async function initTestEnv<Chain extends TestChainNames>(
environmentConfig: EnvironmentConfig<Chain>,
) {
async function initTestEnv(environmentConfig: ChainMap<ChainMetadata>) {
const [signer] = await ethers.getSigners();
const multiProvider = getTestMultiProvider(signer, environmentConfig);
const multiProvider = MultiProvider.createTestMultiProvider({ signer });
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
const core = new TestCoreApp(coreContractsMaps, multiProvider);
const config = core.extendWithConnectionClientConfig(
getChainToOwnerMap(fullEnvTestConfigs, signer.address),
getChainToOwnerMap(environmentConfig, signer.address),
);
const deployer = new EnvSubsetDeployer(multiProvider, config, core);
return { multiProvider, config, deployer };

@ -1,24 +1,14 @@
import { Wallet } from 'ethers';
import { StaticCeloJsonRpcProvider } from '@hyperlane-xyz/celo-ethers-provider';
import { utils } from '@hyperlane-xyz/utils';
export const ALFAJORES_FORNO = 'https://alfajores-forno.celo-testnet.org';
export const CELO_DERIVATION_PATH = "m/44'/52752'/0'/0/0";
export function getAlfajoresSigner() {
console.info('Getting signer');
const provider = getAlfajoresProvider();
const mnemonic = utils.safelyAccessEnvVar('MNEMONIC');
if (!mnemonic) throw new Error('No MNEMONIC provided in env');
const wallet = Wallet.fromMnemonic(mnemonic, CELO_DERIVATION_PATH).connect(
provider,
);
const wallet = Wallet.fromMnemonic(mnemonic, CELO_DERIVATION_PATH);
console.info('Signer and provider ready');
return wallet;
}
export function getAlfajoresProvider() {
console.info('Getting provider');
return new StaticCeloJsonRpcProvider(ALFAJORES_FORNO);
}

@ -1,6 +1,9 @@
import { ethers } from 'ethers';
import { types } from '@hyperlane-xyz/utils';
import { chainMetadata } from '../consts/chainMetadata';
import { TestChains } from '../consts/chains';
import {
CoinGeckoInterface,
CoinGeckoResponse,
@ -10,6 +13,12 @@ import {
} from '../gas/token-prices';
import { ChainMap, ChainName } from '../types';
export function getTestOwnerConfig(owner: types.Address) {
const config: ChainMap<{ owner: types.Address }> = {};
TestChains.forEach((t) => (config[t] = { owner }));
return config;
}
const MOCK_NETWORK = {
name: 'MockNetwork',
chainId: 1337,
@ -95,7 +104,7 @@ export class MockCoinGecko implements CoinGeckoInterface {
// A mock TokenPriceGetter intended to be used by tests when mocking token prices
export class MockTokenPriceGetter implements TokenPriceGetter {
private tokenPrices: Partial<ChainMap<ChainName, number>>;
private tokenPrices: Partial<ChainMap<number>>;
constructor() {
this.tokenPrices = {};

@ -1,40 +1,12 @@
import type { ethers } from 'ethers';
import type { ChainName } from './consts/chains';
// Re-export ChainName for convenience
export { ChainName };
// A full object map of all chains to a value type
export type CompleteChainMap<Value> = Record<ChainName, Value>;
export type LooseChainMap<Value> = Record<ChainName | string, Value>;
// A partial object map of some chains to a value type
export type PartialChainMap<Value> = Partial<CompleteChainMap<Value>>;
// A map of some specific subset of chains to a value type
export type ChainMap<Chain extends ChainName, Value> = Record<Chain, Value>;
// An alias for string to clarify type is a chain name
export type ChainName = string;
// A map of chain names to a value type
export type ChainMap<Value> = Record<string, Value>;
// The names of test chains, should be kept up to date if new are added
export type TestChainNames = 'test1' | 'test2' | 'test3';
export type NameOrDomain = ChainName | number;
export type Remotes<
Chain extends ChainName,
LocalChain extends Chain,
> = Exclude<Chain, LocalChain>;
export type RemoteChainMap<
Chain extends ChainName,
LocalChain extends Chain,
Value,
> = Record<Remotes<Chain, LocalChain>, Value>;
export type Connection = ethers.providers.Provider | ethers.Signer;
export interface IChainConnection {
id: number;
provider: ethers.providers.Provider;
signer?: ethers.Signer;
overrides?: ethers.Overrides;
confirmations?: number;
blockExplorerUrl?: string;
blockExplorerApiUrl?: string;
}

@ -1,14 +1,15 @@
import { AllDeprecatedChains } from '../consts/chains';
import { ChainMap, ChainName, Remotes } from '../types';
import { ChainMap, ChainName } from '../types';
export class MultiGeneric<Chain extends ChainName, Value> {
constructor(public readonly chainMap: ChainMap<Chain, Value>) {}
// Generalized map container for chain name to some value
export class MultiGeneric<Value> {
constructor(public readonly chainMap: ChainMap<Value>) {}
/**
* Get value for a chain
* @throws if chain is invalid or has not been set
*/
protected get(chain: Chain): Value {
protected get(chain: ChainName): Value {
const value = this.chainMap[chain] ?? null;
if (!value) {
throw new Error(`No chain value found for ${chain}`);
@ -20,7 +21,7 @@ export class MultiGeneric<Chain extends ChainName, Value> {
* Get value for a chain
* @returns value or null if chain value has not been set
*/
protected tryGet(chain: Chain): Value | null {
protected tryGet(chain: ChainName): Value | null {
return this.chainMap[chain] ?? null;
}
@ -28,52 +29,38 @@ export class MultiGeneric<Chain extends ChainName, Value> {
* Set value for a chain
* @throws if chain is invalid or has not been set
*/
protected set(chain: Chain, value: Value): Value {
protected set(chain: ChainName, value: Value): Value {
this.chainMap[chain] = value;
return value;
}
chains(): Chain[] {
chains(): ChainName[] {
return Object.keys(this.chainMap).filter(
(chain) => !AllDeprecatedChains.includes(chain),
) as Chain[];
);
}
forEach(fn: (n: Chain, dc: Value) => void): void {
forEach(fn: (n: ChainName, dc: Value) => void): void {
for (const chain of this.chains()) {
fn(chain, this.chainMap[chain]);
}
}
map<Output>(fn: (n: Chain, dc: Value) => Output): Record<Chain, Output> {
const entries: [Chain, Output][] = [];
const chains = this.chains();
for (const chain of chains) {
map<Output>(
fn: (n: ChainName, dc: Value) => Output,
): Record<ChainName, Output> {
const entries: [ChainName, Output][] = [];
for (const chain of this.chains()) {
entries.push([chain, fn(chain, this.chainMap[chain])]);
}
return Object.fromEntries(entries) as Record<Chain, Output>;
}
remoteChains<LocalChain extends Chain>(
name: LocalChain,
): Remotes<Chain, LocalChain>[] {
return this.chains().filter((key) => key !== name) as Remotes<
Chain,
LocalChain
>[];
return Object.fromEntries(entries);
}
extendWithChain<New extends Remotes<ChainName, Chain>>(
chain: New,
value: Value,
): MultiGeneric<New & Chain, Value> {
return new MultiGeneric<New & Chain, Value>({
...this.chainMap,
[chain]: value,
});
remoteChains(name: ChainName): ChainName[] {
return this.chains().filter((key) => key !== name);
}
knownChain(chain: ChainName): boolean {
return chain in this.chainMap;
return Object.keys(this.chainMap).includes(chain);
}
}

@ -1,19 +1,18 @@
import type { Chain as WagmiChain } from '@wagmi/chains';
import type { ChainMetadata } from '../consts/chainMetadata';
import { Testnets } from '../consts/chains';
import { ChainMetadata, etherToken } from '../consts/chainMetadata';
export function chainMetadataToWagmiChain(metadata: ChainMetadata): WagmiChain {
return {
id: metadata.id,
name: metadata.displayName,
id: metadata.chainId,
name: metadata.displayName || metadata.name,
network: metadata.name as string,
nativeCurrency: metadata.nativeToken,
nativeCurrency: metadata.nativeToken || etherToken,
rpcUrls: {
public: { http: [metadata.publicRpcUrls[0].http] },
default: { http: [metadata.publicRpcUrls[0].http] },
},
blockExplorers: metadata.blockExplorers.length
blockExplorers: metadata.blockExplorers?.length
? {
default: {
name: metadata.blockExplorers[0].name,
@ -21,6 +20,6 @@ export function chainMetadataToWagmiChain(metadata: ChainMetadata): WagmiChain {
},
}
: undefined,
testnet: Testnets.includes(metadata.name),
testnet: !!metadata.isTestnet,
};
}

@ -1 +1 @@
Subproject commit 17ae49843eccac5383484314f3afd174f645fe60
Subproject commit a39ad9cc76bbe1944823e8321bf8e29bf3736c51

@ -1,7 +1,7 @@
{
"name": "@hyperlane-xyz/utils",
"description": "General utilities for the Hyperlane network",
"version": "1.1.3",
"version": "1.2.0",
"dependencies": {
"ethers": "^5.7.2"
},

@ -3932,11 +3932,11 @@ __metadata:
languageName: node
linkType: hard
"@hyperlane-xyz/core@1.1.3, @hyperlane-xyz/core@workspace:solidity":
"@hyperlane-xyz/core@1.2.0, @hyperlane-xyz/core@workspace:solidity":
version: 0.0.0-use.local
resolution: "@hyperlane-xyz/core@workspace:solidity"
dependencies:
"@hyperlane-xyz/utils": 1.1.3
"@hyperlane-xyz/utils": 1.2.0
"@nomiclabs/hardhat-ethers": ^2.2.1
"@nomiclabs/hardhat-waffle": ^2.0.3
"@openzeppelin/contracts": ^4.8.0
@ -3959,11 +3959,22 @@ __metadata:
languageName: unknown
linkType: soft
"@hyperlane-xyz/helloworld@1.1.3, @hyperlane-xyz/helloworld@workspace:typescript/helloworld":
"@hyperlane-xyz/core@npm:1.1.4":
version: 1.1.4
resolution: "@hyperlane-xyz/core@npm:1.1.4"
dependencies:
"@hyperlane-xyz/utils": 1.1.4
"@openzeppelin/contracts": ^4.8.0
"@openzeppelin/contracts-upgradeable": ^4.8.0
checksum: 6e1e70ce7dc23c69ec2f3443cac5956241db834d745fcec6d613ac323004f9e9c279e68182b9995fb3eca572a0a99600de493bc6b7d8e1dc248203aaeaec2251
languageName: node
linkType: hard
"@hyperlane-xyz/helloworld@1.1.4, @hyperlane-xyz/helloworld@workspace:typescript/helloworld":
version: 0.0.0-use.local
resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld"
dependencies:
"@hyperlane-xyz/sdk": ^1.1.3
"@hyperlane-xyz/sdk": 1.1.4
"@nomiclabs/hardhat-ethers": ^2.2.1
"@nomiclabs/hardhat-waffle": ^2.0.3
"@openzeppelin/contracts-upgradeable": ^4.8.0
@ -3995,9 +4006,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "@hyperlane-xyz/hyperlane-token@workspace:typescript/token"
dependencies:
"@hyperlane-xyz/core": 1.1.3
"@hyperlane-xyz/sdk": 1.1.3
"@hyperlane-xyz/utils": 1.1.3
"@hyperlane-xyz/core": 1.1.4
"@hyperlane-xyz/sdk": 1.1.4
"@hyperlane-xyz/utils": 1.1.4
"@nomiclabs/hardhat-ethers": ^2.2.1
"@nomiclabs/hardhat-waffle": ^2.0.3
"@openzeppelin/contracts-upgradeable": ^4.8.0
@ -4041,9 +4052,9 @@ __metadata:
"@gnosis.pm/safe-ethers-lib": ^1.4.0
"@gnosis.pm/safe-service-client": ^1.2.0
"@hyperlane-xyz/celo-ethers-provider": ^0.1.1
"@hyperlane-xyz/helloworld": 1.1.3
"@hyperlane-xyz/sdk": 1.1.3
"@hyperlane-xyz/utils": 1.1.3
"@hyperlane-xyz/helloworld": 1.1.4
"@hyperlane-xyz/sdk": 1.1.4
"@hyperlane-xyz/utils": 1.1.4
"@nomiclabs/hardhat-ethers": ^2.2.1
"@nomiclabs/hardhat-etherscan": ^3.0.3
"@nomiclabs/hardhat-waffle": ^2.0.3
@ -4086,13 +4097,28 @@ __metadata:
languageName: unknown
linkType: soft
"@hyperlane-xyz/sdk@1.1.3, @hyperlane-xyz/sdk@^1.1.3, @hyperlane-xyz/sdk@workspace:typescript/sdk":
"@hyperlane-xyz/sdk@npm:1.1.4":
version: 1.1.4
resolution: "@hyperlane-xyz/sdk@npm:1.1.4"
dependencies:
"@hyperlane-xyz/celo-ethers-provider": ^0.1.1
"@hyperlane-xyz/core": 1.1.4
"@hyperlane-xyz/utils": 1.1.4
"@wagmi/chains": ^0.2.6
coingecko-api: ^1.0.10
cross-fetch: ^3.1.5
debug: ^4.3.4
ethers: ^5.7.2
checksum: 600d81648cdbdad2be4200bd772d49128b9bcd5de10d1f811008291ed268610a0bb1cac8061dfa30b5040dc20c7b8919b39ed206d7993f63c65abf3df663c125
languageName: node
linkType: hard
"@hyperlane-xyz/sdk@workspace:typescript/sdk":
version: 0.0.0-use.local
resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk"
dependencies:
"@hyperlane-xyz/celo-ethers-provider": ^0.1.1
"@hyperlane-xyz/core": 1.1.3
"@hyperlane-xyz/utils": 1.1.3
"@hyperlane-xyz/core": 1.2.0
"@hyperlane-xyz/utils": 1.2.0
"@nomiclabs/hardhat-ethers": ^2.2.1
"@nomiclabs/hardhat-waffle": ^2.0.3
"@types/coingecko-api": ^1.0.10
@ -4116,7 +4142,7 @@ __metadata:
languageName: unknown
linkType: soft
"@hyperlane-xyz/utils@1.1.3, @hyperlane-xyz/utils@workspace:typescript/utils":
"@hyperlane-xyz/utils@1.2.0, @hyperlane-xyz/utils@workspace:typescript/utils":
version: 0.0.0-use.local
resolution: "@hyperlane-xyz/utils@workspace:typescript/utils"
dependencies:
@ -4127,6 +4153,15 @@ __metadata:
languageName: unknown
linkType: soft
"@hyperlane-xyz/utils@npm:1.1.4":
version: 1.1.4
resolution: "@hyperlane-xyz/utils@npm:1.1.4"
dependencies:
ethers: ^5.7.2
checksum: 9860b34cea341e2b49eb341cb2bab57d33b8e818a577f7d50daa3417c7d6dc01311d99cb13e1232d84b0c3551cc4476734e1b9bc0083b4cb1e6c441c3a0f4798
languageName: node
linkType: hard
"@jridgewell/gen-mapping@npm:^0.3.0":
version: 0.3.1
resolution: "@jridgewell/gen-mapping@npm:0.3.1"

Loading…
Cancel
Save