UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

264 lines 9.87 kB
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; import { BigNumber, ethers } from 'ethers'; import { exclude, objMap } from '@hyperlane-xyz/utils'; import { testChains } from '../consts/testChains.js'; import { HookType } from '../hook/types.js'; import { IsmType, ModuleType, ismTypeToModuleType, } from '../ism/types.js'; export function randomInt(max, min = 0) { return Math.floor(Math.random() * (max - min)) + min; } export function randomAddress() { return ethers.utils.hexlify(ethers.utils.randomBytes(20)).toLowerCase(); } export async function randomCosmosAddress(prefix) { const wallet = await DirectSecp256k1Wallet.fromKey(ethers.utils.randomBytes(32), prefix); const accounts = await wallet.getAccounts(); return accounts[0].address; } export function createRouterConfigMap(owner, coreContracts, igpContracts) { return objMap(coreContracts, (chain, contracts) => { return { owner, mailbox: contracts.mailbox.address, interchainGasPaymaster: igpContracts[chain].interchainGasPaymaster.address, }; }); } const nonZeroAddress = ethers.constants.AddressZero.replace('00', '01'); // dummy config as TestInbox and TestOutbox do not use deployed ISM export function testCoreConfig(chains, owner = nonZeroAddress) { const chainConfig = { owner, defaultIsm: { type: IsmType.TEST_ISM, }, defaultHook: { type: HookType.MERKLE_TREE, }, requiredHook: { type: HookType.PROTOCOL_FEE, maxProtocolFee: ethers.utils.parseUnits('1', 'gwei').toString(), // 1 gwei of native token protocolFee: BigNumber.from(1).toString(), // 1 wei beneficiary: nonZeroAddress, owner, }, }; return Object.fromEntries(chains.map((local) => [local, chainConfig])); } const TEST_ORACLE_CONFIG = { gasPrice: ethers.utils.parseUnits('1', 'gwei').toString(), tokenExchangeRate: ethers.utils.parseUnits('1', 10).toString(), tokenDecimals: 18, }; const TEST_OVERHEAD_COST = 60000; export function testIgpConfig(chains, owner = nonZeroAddress) { return Object.fromEntries(chains.map((local) => { const overhead = {}; const oracleConfig = {}; exclude(local, chains).map((remote) => { overhead[remote] = TEST_OVERHEAD_COST; oracleConfig[remote] = TEST_ORACLE_CONFIG; }); return [ local, { type: HookType.INTERCHAIN_GAS_PAYMASTER, owner, oracleKey: owner, beneficiary: owner, overhead, oracleConfig, }, ]; })); } export const hookTypes = Object.values(HookType); export const hookTypesToFilter = [ HookType.OP_STACK, HookType.ARB_L2_TO_L1, HookType.CUSTOM, HookType.CCIP, ]; export const DEFAULT_TOKEN_DECIMALS = 18; function randomHookType() { // OP_STACK filtering is temporary until we have a way to deploy the required contracts // ARB_L2_TO_L1 filtered out until we have a way to deploy the required contracts (arbL2ToL1.hardhat-test.ts has the same test for checking deployment) const filteredHookTypes = hookTypes.filter((type) => !hookTypesToFilter.includes(type)); return filteredHookTypes[Math.floor(Math.random() * filteredHookTypes.length)]; } function randomProtocolFee() { const maxProtocolFee = Math.random() * 100000000000000; const protocolFee = (Math.random() * maxProtocolFee) / 1000; return { maxProtocolFee: Math.floor(maxProtocolFee).toString(), protocolFee: Math.floor(protocolFee).toString(), }; } export function randomHookConfig(depth = 0, maxDepth = 2, providedHookType) { const hookType = providedHookType ?? randomHookType(); if (depth >= maxDepth) { if (hookType === HookType.AGGREGATION || hookType === HookType.ROUTING || hookType === HookType.FALLBACK_ROUTING) { return { type: HookType.MERKLE_TREE }; } } switch (hookType) { case HookType.MERKLE_TREE: case HookType.MAILBOX_DEFAULT: return { type: hookType }; case HookType.AGGREGATION: return { type: hookType, hooks: [ randomHookConfig(depth + 1, maxDepth), randomHookConfig(depth + 1, maxDepth), ], }; case HookType.INTERCHAIN_GAS_PAYMASTER: { const owner = randomAddress(); return { owner, type: hookType, beneficiary: randomAddress(), oracleKey: owner, overhead: Object.fromEntries(testChains.map((c) => [c, Math.floor(Math.random() * 100)])), oracleConfig: Object.fromEntries(testChains.map((c) => [ c, { tokenExchangeRate: randomInt(1234567891234).toString(), gasPrice: randomInt(1234567891234).toString(), tokenDecimals: DEFAULT_TOKEN_DECIMALS, }, ])), }; } case HookType.PROTOCOL_FEE: { const { maxProtocolFee, protocolFee } = randomProtocolFee(); return { owner: randomAddress(), type: hookType, maxProtocolFee, protocolFee, beneficiary: randomAddress(), }; } case HookType.OP_STACK: return { owner: randomAddress(), type: hookType, nativeBridge: randomAddress(), destinationChain: 'testChain', }; case HookType.ARB_L2_TO_L1: return { type: hookType, arbSys: randomAddress(), bridge: randomAddress(), destinationChain: 'testChain', }; case HookType.ROUTING: return { owner: randomAddress(), type: hookType, domains: Object.fromEntries(testChains.map((c) => [c, randomHookConfig(depth + 1, maxDepth)])), }; case HookType.FALLBACK_ROUTING: return { owner: randomAddress(), type: hookType, fallback: randomHookConfig(depth + 1, maxDepth), domains: Object.fromEntries(testChains.map((c) => [c, randomHookConfig(depth + 1, maxDepth)])), }; case HookType.PAUSABLE: return { owner: randomAddress(), type: hookType, paused: false, }; case HookType.AMOUNT_ROUTING: return { type: hookType, threshold: Math.floor(Math.random() * 100), lowerHook: randomHookConfig(depth + 1, maxDepth), upperHook: randomHookConfig(depth + 1, maxDepth), }; default: throw new Error(`Unsupported Hook type: ${hookType}`); } } export const randomMultisigIsmConfig = (m, n) => { const emptyArray = new Array(n).fill(0); const validators = emptyArray.map(() => randomAddress()); return { type: IsmType.MERKLE_ROOT_MULTISIG, validators, threshold: m, }; }; const ModuleTypes = [ ModuleType.AGGREGATION, ModuleType.MERKLE_ROOT_MULTISIG, ModuleType.ROUTING, ModuleType.NULL, ]; const NonNestedModuleTypes = [ModuleType.MERKLE_ROOT_MULTISIG, ModuleType.NULL]; function randomModuleType() { return ModuleTypes[randomInt(ModuleTypes.length)]; } function randomNonNestedModuleType() { return NonNestedModuleTypes[randomInt(NonNestedModuleTypes.length)]; } export const randomIsmConfig = (depth = 0, maxDepth = 2, providedIsmType) => { // Use input IsmType, otherwise randomize a config based on depth const moduleType = providedIsmType ? ismTypeToModuleType(providedIsmType) : depth === maxDepth ? randomNonNestedModuleType() : randomModuleType(); switch (moduleType) { case ModuleType.MERKLE_ROOT_MULTISIG: { const n = randomInt(5, 1); return randomMultisigIsmConfig(randomInt(n, 1), n); } case ModuleType.ROUTING: { const config = { type: IsmType.ROUTING, owner: randomAddress(), domains: Object.fromEntries(testChains.map((c) => [c, randomIsmConfig(depth + 1)])), }; return config; } case ModuleType.AGGREGATION: { const n = randomInt(2, 1); const moduleTypes = new Set(); const modules = new Array(n).fill(0).map(() => { let moduleConfig; let moduleType; // Ensure that we do not add the same module type more than once per level do { moduleConfig = randomIsmConfig(depth + 1, maxDepth); moduleType = moduleConfig.type; } while (moduleTypes.has(moduleType)); moduleTypes.add(moduleType); return moduleConfig; }); const config = { type: IsmType.AGGREGATION, threshold: randomInt(n, 1), modules, }; return config; } case ModuleType.NULL: { const config = { type: IsmType.TRUSTED_RELAYER, relayer: randomAddress(), }; return config; } default: throw new Error(`Unsupported ISM type: ${moduleType}`); } }; //# sourceMappingURL=testUtils.js.map