UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

282 lines 14 kB
import { ProxyAdmin__factory } from '@hyperlane-xyz/core'; import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js'; import { AltVMDeployer, AltVMHookModule, AltVMIsmModule, AltVMWarpModule, } from '@hyperlane-xyz/deploy-sdk'; import { ProtocolType } from '@hyperlane-xyz/provider-sdk'; import { addressToBytes32, assert, isObjEmpty, mustGet, objFilter, objKeys, objMap, promiseObjAll, rootLogger, } from '@hyperlane-xyz/utils'; import { ExplorerLicenseType } from '../block-explorer/etherscan.js'; import { CCIPContractCache } from '../ccip/utils.js'; import { EvmHookModule } from '../hook/EvmHookModule.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { altVmChainLookup } from '../metadata/ChainMetadataManager.js'; import { EvmERC20WarpModule } from '../token/EvmERC20WarpModule.js'; import { gasOverhead } from '../token/config.js'; import { hypERC20factories } from '../token/contracts.js'; import { HypERC20Deployer, HypERC721Deployer } from '../token/deploy.js'; import { extractIsmAndHookFactoryAddresses } from '../utils/ism.js'; import { HyperlaneProxyFactoryDeployer } from './HyperlaneProxyFactoryDeployer.js'; import { ContractVerifier } from './verify/ContractVerifier.js'; export async function executeWarpDeploy(warpDeployConfig, multiProvider, altVmSigners, registryAddresses, apiKeys) { const contractVerifier = new ContractVerifier(multiProvider, apiKeys, coreBuildArtifact, ExplorerLicenseType.MIT); const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider, contractVerifier); // For each chain in WarpRouteConfig, deploy each Ism Factory, if it's not in the registry // Then return a modified config with the ism and/or hook address as a string const modifiedConfig = await resolveWarpIsmAndHook(warpDeployConfig, multiProvider, altVmSigners, registryAddresses, ismFactoryDeployer, contractVerifier); // Initialize with unsupported chains so that they are enrolled let deployedContracts = objMap(objFilter(warpDeployConfig, (_chain, config) => !!config.foreignDeployment), (chain, config) => { assert(config.foreignDeployment, `Expected foreignDeployment field to be defined on ${chain} after filtering`); return config.foreignDeployment; }); // get unique list of protocols const protocols = Array.from(new Set(Object.keys(modifiedConfig).map((chainName) => multiProvider.getProtocol(chainName)))); for (const protocol of protocols) { const protocolSpecificConfig = objFilter(modifiedConfig, (chainName, config) => multiProvider.getProtocol(chainName) === protocol && !config.foreignDeployment); if (isObjEmpty(protocolSpecificConfig)) { continue; } switch (protocol) { case ProtocolType.Ethereum: { const deployer = warpDeployConfig.isNft ? new HypERC721Deployer(multiProvider) : new HypERC20Deployer(multiProvider); // TODO: replace with EvmERC20WarpModule const evmContracts = await deployer.deploy(protocolSpecificConfig); deployedContracts = { ...deployedContracts, ...objMap(evmContracts, (_, contracts) => getRouter(contracts).address), }; break; } default: { const signersMap = objMap(protocolSpecificConfig, (chain, _) => mustGet(altVmSigners, chain)); const deployer = new AltVMDeployer(signersMap); deployedContracts = { ...deployedContracts, ...(await deployer.deploy(protocolSpecificConfig)), }; break; } } } return deployedContracts; } async function resolveWarpIsmAndHook(warpConfig, multiProvider, altVmSigners, registryAddresses, ismFactoryDeployer, contractVerifier) { return promiseObjAll(objMap(warpConfig, async (chain, config) => { const ccipContractCache = new CCIPContractCache(registryAddresses); const chainAddresses = registryAddresses[chain]; if (!chainAddresses) { throw `Registry factory addresses not found for ${chain}.`; } config.interchainSecurityModule = await createWarpIsm({ ccipContractCache, chain, chainAddresses, multiProvider, altVmSigners, contractVerifier, ismFactoryDeployer, warpConfig: config, }); // TODO write test config.hook = await createWarpHook({ ccipContractCache, chain, chainAddresses, multiProvider, altVmSigners, contractVerifier, ismFactoryDeployer, warpConfig: config, }); return config; })); } /** * Deploys the Warp ISM for a given config * * @returns The deployed ism address */ async function createWarpIsm({ ccipContractCache, chain, chainAddresses, multiProvider, altVmSigners, contractVerifier, warpConfig, }) { const { interchainSecurityModule } = warpConfig; if (!interchainSecurityModule || typeof interchainSecurityModule === 'string') { rootLogger.info(`Config Ism is ${!interchainSecurityModule ? 'empty' : interchainSecurityModule}, skipping deployment.`); return interchainSecurityModule; } rootLogger.info(`Loading registry factory addresses for ${chain}...`); rootLogger.info(`Creating ${interchainSecurityModule.type} ISM for token on ${chain} chain...`); rootLogger.info(`Finished creating ${interchainSecurityModule.type} ISM for token on ${chain} chain.`); const protocolType = multiProvider.getProtocol(chain); switch (protocolType) { case ProtocolType.Ethereum: { const evmIsmModule = await EvmIsmModule.create({ chain, mailbox: chainAddresses.mailbox, multiProvider: multiProvider, proxyFactoryFactories: extractIsmAndHookFactoryAddresses(chainAddresses), config: interchainSecurityModule, ccipContractCache, contractVerifier, }); const { deployedIsm } = evmIsmModule.serialize(); return deployedIsm; } default: { const signer = mustGet(altVmSigners, chain); const ismModule = await AltVMIsmModule.create({ chain, addresses: { mailbox: chainAddresses.mailbox, }, // FIXME: not all ISM types are supported yet config: interchainSecurityModule, chainLookup: altVmChainLookup(multiProvider), signer, }); const { deployedIsm } = ismModule.serialize(); return deployedIsm; } } } async function createWarpHook({ ccipContractCache, chain, chainAddresses, multiProvider, altVmSigners, contractVerifier, warpConfig, }) { const { hook } = warpConfig; if (!hook || typeof hook === 'string') { rootLogger.info(`Config Hook is ${!hook ? 'empty' : hook}, skipping deployment.`); return hook; } rootLogger.info(`Loading registry factory addresses for ${chain}...`); rootLogger.info(`Creating ${hook.type} Hook for token on ${chain} chain...`); const protocolType = multiProvider.getProtocol(chain); switch (protocolType) { case ProtocolType.Ethereum: { rootLogger.info(`Loading registry factory addresses for ${chain}...`); rootLogger.info(`Creating ${hook.type} Hook for token on ${chain} chain...`); // If config.proxyadmin.address exists, then use that. otherwise deploy a new proxyAdmin const proxyAdminAddress = warpConfig.proxyAdmin?.address ?? (await multiProvider.handleDeploy(chain, new ProxyAdmin__factory(), [])) .address; const evmHookModule = await EvmHookModule.create({ chain, multiProvider: multiProvider, coreAddresses: { mailbox: chainAddresses.mailbox, proxyAdmin: proxyAdminAddress, }, config: hook, ccipContractCache, contractVerifier, proxyFactoryFactories: extractIsmAndHookFactoryAddresses(chainAddresses), }); rootLogger.info(`Finished creating ${hook.type} Hook for token on ${chain} chain.`); const { deployedHook } = evmHookModule.serialize(); return deployedHook; } default: { const signer = mustGet(altVmSigners, chain); const hookModule = await AltVMHookModule.create({ chain, chainLookup: multiProvider, addresses: { deployedHook: '', mailbox: chainAddresses.mailbox, }, // FIXME: not all Hook types are supported yet config: hook, signer, }); const { deployedHook } = hookModule.serialize(); return deployedHook; } } } export async function enrollCrossChainRouters({ multiProvider, altVmSigners, registryAddresses, warpDeployConfig, }, deployedContracts) { rootLogger.info(`Start enrolling cross chain routers`); const resolvedConfigMap = objMap(warpDeployConfig, (_, config) => ({ gas: gasOverhead(config.type), ...config, })); const updateTransactions = {}; const supportedChains = Object.keys(objFilter(resolvedConfigMap, (_, config) => !config.foreignDeployment)); for (const currentChain of supportedChains) { const protocol = multiProvider.getProtocol(currentChain); const remoteRouters = Object.fromEntries(Object.entries(deployedContracts) .filter(([chain, _address]) => chain !== currentChain) .map(([chain, address]) => [ multiProvider.getDomainId(chain).toString(), { address: addressToBytes32(address), }, ])); const destinationGas = Object.fromEntries(Object.entries(deployedContracts) .filter(([chain, _address]) => chain !== currentChain) .map(([chain, _address]) => [ multiProvider.getDomainId(chain).toString(), resolvedConfigMap[chain].gas.toString(), ])); for (const domainId of Object.keys(remoteRouters)) { rootLogger.debug(`Creating enroll remote router transactions with remote domain id ${domainId} and address ${remoteRouters[domainId]} on chain ${currentChain}`); } switch (protocol) { case ProtocolType.Ethereum: { const { domainRoutingIsmFactory, staticMerkleRootMultisigIsmFactory, staticMessageIdMultisigIsmFactory, staticAggregationIsmFactory, staticAggregationHookFactory, staticMerkleRootWeightedMultisigIsmFactory, staticMessageIdWeightedMultisigIsmFactory, } = registryAddresses[currentChain]; const evmWarpModule = new EvmERC20WarpModule(multiProvider, { chain: currentChain, config: resolvedConfigMap[currentChain], addresses: { deployedTokenRoute: deployedContracts[currentChain], domainRoutingIsmFactory, staticMerkleRootMultisigIsmFactory, staticMessageIdMultisigIsmFactory, staticAggregationIsmFactory, staticAggregationHookFactory, staticMerkleRootWeightedMultisigIsmFactory, staticMessageIdWeightedMultisigIsmFactory, }, }); const actualConfig = await evmWarpModule.read(); const expectedConfig = { ...actualConfig, owner: resolvedConfigMap[currentChain].owner, remoteRouters, destinationGas, }; const transactions = await evmWarpModule.update(expectedConfig, { routingDestinations: Object.keys(remoteRouters).map((domain) => multiProvider.getDomainId(domain)), }); if (transactions.length) { updateTransactions[currentChain] = transactions; } break; } default: { const signer = mustGet(altVmSigners, currentChain); const warpModule = new AltVMWarpModule(altVmChainLookup(multiProvider), signer, { chain: currentChain, config: resolvedConfigMap[currentChain], addresses: { deployedTokenRoute: deployedContracts[currentChain], }, }); const actualConfig = await warpModule.read(); const expectedConfig = { ...actualConfig, owner: resolvedConfigMap[currentChain].owner, remoteRouters, destinationGas, }; const transactions = await warpModule.update(expectedConfig); if (transactions.length) { updateTransactions[currentChain] = transactions; } } } rootLogger.debug(`Created enroll router update transactions for chain ${currentChain}`); } return updateTransactions; } function getRouter(contracts) { for (const key of objKeys(hypERC20factories)) { if (contracts[key]) return contracts[key]; } throw new Error('No matching contract found.'); } //# sourceMappingURL=warp.js.map