UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

313 lines 15.7 kB
import { Mailbox__factory, Ownable__factory, } from '@hyperlane-xyz/core'; import { eqAddress, rootLogger, } from '@hyperlane-xyz/utils'; import { attachContractsMap, serializeContractsMap, transferOwnershipTransactions, } from '../contracts/contracts.js'; import { CoreConfigSchema, } from '../core/types.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { proxyFactoryFactories, } from '../deploy/contracts.js'; import { proxyAdminUpdateTxs } from '../deploy/proxy.js'; import { createDefaultProxyFactoryFactories } from '../deploy/proxyFactoryUtils.js'; import { getEvmHookUpdateTransactions } from '../hook/updates.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { IsmType } from '../ism/types.js'; import { isStaticDeploymentSupported } from '../ism/utils.js'; import { extractIsmAndHookFactoryAddresses } from '../utils/ism.js'; import { HyperlaneModule, } from './AbstractHyperlaneModule.js'; import { EvmCoreReader } from './EvmCoreReader.js'; import { EvmIcaModule } from './EvmIcaModule.js'; import { HyperlaneCoreDeployer } from './HyperlaneCoreDeployer.js'; export class EvmCoreModule extends HyperlaneModule { multiProvider; logger = rootLogger.child({ module: 'EvmCoreModule' }); coreReader; evmIcaModule; chainName; chainId; domainId; constructor(multiProvider, args) { super(args); this.multiProvider = multiProvider; this.coreReader = new EvmCoreReader(multiProvider, args.chain); this.chainName = multiProvider.getChainName(args.chain); this.chainId = multiProvider.getEvmChainId(args.chain); this.domainId = multiProvider.getDomainId(args.chain); if (args.config.interchainAccountRouter) { this.evmIcaModule = new EvmIcaModule(multiProvider, { chain: args.chain, addresses: { interchainAccountRouter: args.addresses.interchainAccountRouter, }, config: args.config.interchainAccountRouter, }); } } /** * Reads the core configuration from the mailbox address specified in the SDK arguments. * @returns The core config. */ async read() { return this.coreReader.deriveCoreConfig({ mailbox: this.args.addresses.mailbox, interchainAccountRouter: this.args.addresses.interchainAccountRouter, }); } /** * Updates the core contracts with the provided configuration. * * @param expectedConfig - The configuration for the core contracts to be updated. * @returns An array of Ethereum transactions that were executed to update the contract. */ async update(expectedConfig) { CoreConfigSchema.parse(expectedConfig); const actualConfig = await this.read(); const transactions = [ ...(await this.createDefaultIsmUpdateTxs(actualConfig, expectedConfig)), ]; const proxyAdminAddress = expectedConfig.proxyAdmin?.address ?? actualConfig.proxyAdmin?.address ?? this.args.addresses.proxyAdmin; if (expectedConfig.requiredHook) { transactions.push(...(await this.createHookUpdateTxs(proxyAdminAddress, 'requiredHook', actualConfig.requiredHook, expectedConfig.requiredHook))); } if (expectedConfig.defaultHook) { transactions.push(...(await this.createHookUpdateTxs(proxyAdminAddress, 'defaultHook', actualConfig.defaultHook, expectedConfig.defaultHook))); } if (expectedConfig.interchainAccountRouter && this.evmIcaModule) { transactions.push(...(await this.evmIcaModule.update(expectedConfig.interchainAccountRouter))); } transactions.push(...this.createMailboxOwnerUpdateTxs(actualConfig, expectedConfig), ...proxyAdminUpdateTxs(this.chainId, this.args.addresses.mailbox, actualConfig, expectedConfig)); return transactions; } async createHookUpdateTxs(proxyAdminAddress, setHookFunctionName, actualConfig, expectedConfig) { return getEvmHookUpdateTransactions(this.args.addresses.mailbox, { actualConfig: actualConfig, expectedConfig: expectedConfig, evmChainName: this.chainName, hookAndIsmFactories: extractIsmAndHookFactoryAddresses(this.args.addresses), setHookFunctionCallEncoder: (newHookAddress) => { if (setHookFunctionName === 'requiredHook') { return Mailbox__factory.createInterface().encodeFunctionData('setRequiredHook', [newHookAddress]); } return Mailbox__factory.createInterface().encodeFunctionData('setDefaultHook', [newHookAddress]); }, logger: this.logger, mailbox: this.args.addresses.mailbox, multiProvider: this.multiProvider, proxyAdminAddress, }); } /** * Create a transaction to update an existing ISM config, or deploy a new ISM and return a tx to setDefaultIsm * * @param actualConfig - The on-chain router configuration, including the ISM configuration, and address. * @param expectedConfig - The expected token router configuration, including the ISM configuration. * @returns Transaction that need to be executed to update the ISM configuration. */ async createDefaultIsmUpdateTxs(actualConfig, expectedConfig) { const updateTransactions = []; const actualDefaultIsmConfig = actualConfig.defaultIsm; // Try to update (may also deploy) Ism with the expected config const { deployedIsm, ismUpdateTxs } = await this.deployOrUpdateIsm(actualDefaultIsmConfig, expectedConfig.defaultIsm); if (ismUpdateTxs.length) { updateTransactions.push(...ismUpdateTxs); } const newIsmDeployed = !eqAddress(actualDefaultIsmConfig.address, deployedIsm); if (newIsmDeployed) { const { mailbox } = this.serialize(); const contractToUpdate = Mailbox__factory.connect(mailbox, this.multiProvider.getProvider(this.domainId)); updateTransactions.push({ annotation: `Setting default ISM for Mailbox ${mailbox} to ${deployedIsm}`, chainId: this.chainId, to: contractToUpdate.address, data: contractToUpdate.interface.encodeFunctionData('setDefaultIsm', [ deployedIsm, ]), }); } return updateTransactions; } /** * Updates or deploys the ISM using the provided configuration. * * @returns Object with deployedIsm address, and update Transactions */ async deployOrUpdateIsm(actualDefaultIsmConfig, expectDefaultIsmConfig) { const { mailbox } = this.serialize(); const ismModule = new EvmIsmModule(this.multiProvider, { chain: this.args.chain, config: expectDefaultIsmConfig, addresses: { mailbox, ...extractIsmAndHookFactoryAddresses(this.serialize()), deployedIsm: actualDefaultIsmConfig.address, }, }); this.logger.info(`Comparing target ISM config with ${this.args.chain} chain`); const ismUpdateTxs = await ismModule.update(expectDefaultIsmConfig); const { deployedIsm } = ismModule.serialize(); return { deployedIsm, ismUpdateTxs }; } /** * Create a transaction to transfer ownership of an existing mailbox with a given config. * * @param actualConfig - The on-chain core configuration. * @param expectedConfig - The expected token core configuration. * @returns Ethereum transaction that need to be executed to update the owner. */ createMailboxOwnerUpdateTxs(actualConfig, expectedConfig) { return transferOwnershipTransactions(this.chainId, this.args.addresses.mailbox, actualConfig, expectedConfig, 'Mailbox'); } /** * Deploys the Core contracts. * @remark Most of the contract owners is the Deployer with some being the Proxy Admin. * @returns The created EvmCoreModule instance. */ static async create(params) { const { chain, config, multiProvider, contractVerifier } = params; const addresses = await EvmCoreModule.deploy({ config, multiProvider, chain, contractVerifier, }); // Create CoreModule and deploy the Core contracts const module = new EvmCoreModule(multiProvider, { addresses, chain, config, }); return module; } /** * Deploys the core Hyperlane contracts. * @returns The deployed core contract addresses. */ static async deploy(params) { const { config, multiProvider, chain, contractVerifier } = params; const { name: chainName, technicalStack } = multiProvider.getChainMetadata(chain); const ismFactoryFactories = await this.getIsmFactoryFactories(technicalStack, { chainName, config, multiProvider, contractVerifier, }); const ismFactory = new HyperlaneIsmFactory(attachContractsMap({ [chainName]: ismFactoryFactories }, proxyFactoryFactories), multiProvider); const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory, contractVerifier); // Deploy proxyAdmin const proxyAdmin = await coreDeployer.deployContract(chainName, 'proxyAdmin', []); // Deploy Mailbox const mailbox = await this.deployMailbox({ config, coreDeployer, proxyAdmin: proxyAdmin.address, multiProvider, chain, }); // Deploy ICA ISM and Router const { interchainAccountRouter } = (await EvmIcaModule.create({ chain: chainName, multiProvider: multiProvider, config: { mailbox: mailbox.address, owner: await multiProvider.getSigner(chain).getAddress(), commitmentIsm: { type: IsmType.OFFCHAIN_LOOKUP, urls: ['https://commitment-read-ism.hyperlane.xyz'], owner: await multiProvider.getSigner(chain).getAddress(), }, }, contractVerifier, })).serialize(); // Deploy Validator announce const validatorAnnounce = (await coreDeployer.deployValidatorAnnounce(chainName, mailbox.address)).address; // Deploy timelock controller if config.upgrade is set let timelockController; if (config.upgrade) { timelockController = (await coreDeployer.deployTimelock(chainName, config.upgrade.timelock)).address; } // Deploy Test Recipient const testRecipient = (await coreDeployer.deployTestRecipient(chainName, await mailbox.defaultIsm())).address; // Obtain addresses of every contract created by the deployer // and extract only the merkleTreeHook and interchainGasPaymaster const serializedContracts = serializeContractsMap(coreDeployer.deployedContracts); const { merkleTreeHook, interchainGasPaymaster } = serializedContracts[chainName]; // Update the ProxyAdmin owner of the Mailbox if the config defines a different owner from the current signer const currentProxyOwner = await proxyAdmin.owner(); if (config?.proxyAdmin?.owner && !eqAddress(config.proxyAdmin.owner, currentProxyOwner)) { await multiProvider.sendTransaction(chainName, { annotation: `Transferring ownership of ProxyAdmin to the configured address ${config.proxyAdmin.owner}`, to: proxyAdmin.address, data: Ownable__factory.createInterface().encodeFunctionData('transferOwnership(address)', [config.proxyAdmin.owner]), }); } // Set Core & extra addresses return { ...ismFactoryFactories, proxyAdmin: proxyAdmin.address, mailbox: mailbox.address, interchainAccountRouter, validatorAnnounce, timelockController, testRecipient, merkleTreeHook, interchainGasPaymaster, }; } /** * Deploys the ISM factories for a given chain. * @returns The deployed ISM factories addresses. */ static async deployIsmFactories(params) { const { chainName, config, multiProvider, contractVerifier } = params; const proxyFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider, contractVerifier); const ismFactoriesFactory = await proxyFactoryDeployer.deploy({ [chainName]: config, }); return serializeContractsMap(ismFactoriesFactory)[chainName]; } /** * Deploys a Mailbox and its default ISM, hook, and required hook contracts with a given configuration. * @returns The deployed Mailbox contract instance. */ static async deployMailbox(params) { const { config, proxyAdmin, coreDeployer: deployer, multiProvider, chain, } = params; const chainName = multiProvider.getChainName(chain); const domain = multiProvider.getDomainId(chainName); const mailbox = await deployer.deployProxiedContract(chainName, 'mailbox', 'mailbox', proxyAdmin, [domain]); // @todo refactor when 1) IsmModule is ready const deployedDefaultIsm = await deployer.deployIsm(chainName, config.defaultIsm, mailbox.address); // @todo refactor when 1) HookModule is ready, and 2) Hooks Config can handle strings const deployedDefaultHook = await deployer.deployHook(chainName, config.defaultHook, { mailbox: mailbox.address, proxyAdmin, }); // @todo refactor when 1) HookModule is ready, and 2) Hooks Config can handle strings const deployedRequiredHook = await deployer.deployHook(chainName, config.requiredHook, { mailbox: mailbox.address, proxyAdmin, }); // Initialize Mailbox await multiProvider.handleTx(chain, mailbox.initialize(config.owner, deployedDefaultIsm, deployedDefaultHook.address, deployedRequiredHook.address, multiProvider.getTransactionOverrides(chain))); return mailbox; } /** * Retrieves the ISM factory factories based on the provided technicalStack and parameters. * * @param technicalStack - The technicalStack to determine if static address set deployment should be skipped. * @param params - An object containing the parameters needed for ISM factory deployment. * @param params.chainName - The name of the chain for which the ISM factories are being deployed. * @param params.config - The core configuration to be used during deployment. * @param params.multiProvider - The multi-provider instance for interacting with the blockchain. * @param params.contractVerifier - An optional contract verifier for validating contracts during deployment. * @returns A promise that resolves to the addresses of the deployed ISM factory factories. */ static async getIsmFactoryFactories(technicalStack, params) { // Check if we should skip static address set deployment if (!isStaticDeploymentSupported(technicalStack)) { return createDefaultProxyFactoryFactories(); } return EvmCoreModule.deployIsmFactories(params); } } //# sourceMappingURL=EvmCoreModule.js.map