UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

265 lines 12.8 kB
import { CCIPHook__factory, IL1CrossDomainMessenger__factory, StaticAggregationHook__factory, } from '@hyperlane-xyz/core'; import { ZERO_ADDRESS_HEX_32, addBufferToGasLimit, addressToBytes32, deepEquals, rootLogger, } from '@hyperlane-xyz/utils'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; import { HyperlaneIgpDeployer } from '../gas/HyperlaneIgpDeployer.js'; import { IsmType } from '../ism/types.js'; import { hookFactories } from './contracts.js'; import { HookType, } from './types.js'; export class HyperlaneHookDeployer extends HyperlaneDeployer { core; ismFactory; igpDeployer; constructor(multiProvider, core, ismFactory, contractVerifier, concurrentDeploy = false, igpDeployer = new HyperlaneIgpDeployer(multiProvider, contractVerifier, concurrentDeploy)) { super(multiProvider, hookFactories, { logger: rootLogger.child({ module: 'HookDeployer' }), contractVerifier, concurrentDeploy, }); this.core = core; this.ismFactory = ismFactory; this.igpDeployer = igpDeployer; } cacheAddressesMap(addressesMap) { this.igpDeployer.cacheAddressesMap(addressesMap); super.cacheAddressesMap(addressesMap); } async deployContracts(chain, config, coreAddresses = this.core[chain]) { if (typeof config === 'string') { throw new Error('Hook deployer should not receive address config'); } let hook; if (config.type === HookType.MERKLE_TREE || config.type === HookType.MAILBOX_DEFAULT) { const mailbox = coreAddresses.mailbox; if (!mailbox) { throw new Error(`Mailbox address is required for ${config.type}`); } hook = await this.deployContract(chain, config.type, [mailbox]); } else if (config.type === HookType.INTERCHAIN_GAS_PAYMASTER) { const { interchainGasPaymaster } = await this.deployIgp(chain, config, coreAddresses); hook = interchainGasPaymaster; } else if (config.type === HookType.AGGREGATION) { hook = (await this.deployAggregation(chain, config, coreAddresses)) .aggregationHook; // deploy from factory } else if (config.type === HookType.PROTOCOL_FEE) { hook = await this.deployProtocolFee(chain, config); } else if (config.type === HookType.OP_STACK) { hook = await this.deployOpStack(chain, config, coreAddresses); } else if (config.type === HookType.ROUTING || config.type === HookType.FALLBACK_ROUTING) { hook = await this.deployRouting(chain, config, coreAddresses); } else if (config.type === HookType.PAUSABLE) { hook = await this.deployContract(chain, config.type, []); await this.transferOwnershipOfContracts(chain, config, { [HookType.PAUSABLE]: hook, }); } else if (config.type === HookType.AMOUNT_ROUTING) { hook = await this.deployAmountRoutingHook(chain, config); } else if (config.type === HookType.CCIP) { hook = await this.deployCCIPHook(chain, config); } else { throw new Error(`Unsupported hook config: ${config}`); } const deployedContracts = { [config.type]: hook }; // partial this.addDeployedContracts(chain, deployedContracts); return deployedContracts; } async deployCCIPHook(chain, config) { const hook = this.ismFactory.ccipContractCache.getHook(chain, config.destinationChain); if (!hook) { this.logger.error(`CCIP Hook not found for ${chain} -> ${config.destinationChain}`); throw new Error(`CCIP Hook not found for ${chain} -> ${config.destinationChain}`); } return CCIPHook__factory.connect(hook, this.multiProvider.getSigner(chain)); } async deployProtocolFee(chain, config) { this.logger.debug('Deploying ProtocolFeeHook for %s', chain); return this.deployContract(chain, HookType.PROTOCOL_FEE, [ config.maxProtocolFee, config.protocolFee, config.beneficiary, config.owner, ]); } async deployIgp(chain, config, coreAddresses = this.core[chain]) { this.logger.debug('Deploying IGP as hook for %s', chain); if (coreAddresses.proxyAdmin) { this.igpDeployer.writeCache(chain, 'proxyAdmin', coreAddresses.proxyAdmin); } const igpContracts = await this.igpDeployer.deployContracts(chain, config); // bubbling up addresses and verification input artifacts this.addDeployedContracts(chain, igpContracts, this.igpDeployer.verificationInputs[chain]); return igpContracts; } async deployAggregation(chain, config, coreAddresses = this.core[chain]) { this.logger.debug('Deploying AggregationHook for %s', chain); const aggregatedHooks = []; let hooks = {}; for (const hookConfig of config.hooks) { if (typeof hookConfig === 'string') { aggregatedHooks.push(hookConfig); continue; } const subhooks = await this.deployContracts(chain, hookConfig, coreAddresses); aggregatedHooks.push(subhooks[hookConfig.type].address); hooks = { ...hooks, ...subhooks }; } this.logger.debug({ aggregationHook: config.hooks }, `Deploying aggregation hook of type ${config.hooks.map((h) => typeof h === 'string' ? h : h.type)}...`); const address = await this.ismFactory.deployStaticAddressSet(chain, this.ismFactory.getContracts(chain).staticAggregationHookFactory, aggregatedHooks, this.logger); hooks[HookType.AGGREGATION] = StaticAggregationHook__factory.connect(address, this.multiProvider.getSignerOrProvider(chain)); this.addDeployedContracts(chain, hooks); return hooks; } async deployOpStack(chain, config, coreAddresses = this.core[chain]) { this.logger.debug('Deploying OPStackHook for %s to %s', chain, config.destinationChain); const mailbox = coreAddresses.mailbox; if (!mailbox) { throw new Error(`Mailbox address is required for ${config.type}`); } // fetch l2 messenger address from l1 messenger const l1Messenger = IL1CrossDomainMessenger__factory.connect(config.nativeBridge, this.multiProvider.getSignerOrProvider(chain)); const l2Messenger = await l1Messenger.OTHER_MESSENGER(); // deploy opstack ism const ismConfig = { type: IsmType.OP_STACK, origin: chain, nativeBridge: l2Messenger, }; const opstackIsm = (await this.ismFactory.deploy({ destination: config.destinationChain, config: ismConfig, origin: chain, })); // deploy opstack hook const hook = await this.deployContract(chain, HookType.OP_STACK, [ mailbox, this.multiProvider.getDomainId(config.destinationChain), addressToBytes32(opstackIsm.address), config.nativeBridge, ]); const overrides = this.multiProvider.getTransactionOverrides(chain); // set authorized hook on opstack ism const authorizedHook = await opstackIsm.authorizedHook(); if (authorizedHook === addressToBytes32(hook.address)) { this.logger.debug('Authorized hook already set on ism %s', opstackIsm.address); return hook; } else if (authorizedHook !== ZERO_ADDRESS_HEX_32) { this.logger.debug('Authorized hook mismatch on ism %s, expected %s, got %s', opstackIsm.address, addressToBytes32(hook.address), authorizedHook); throw new Error('Authorized hook mismatch'); } // check if mismatch and redeploy hook this.logger.debug('Setting authorized hook %s on ism % on destination %s', hook.address, opstackIsm.address, config.destinationChain); await this.multiProvider.handleTx(config.destinationChain, opstackIsm.setAuthorizedHook(addressToBytes32(hook.address), overrides)); return hook; } async deployRouting(chain, config, coreAddresses = this.core[chain]) { const mailbox = coreAddresses?.mailbox; if (!mailbox) { throw new Error(`Mailbox address is required for ${config.type}`); } // we don't config owner as config.owner because there're post-deploy steps like // enrolling routing hooks which need ownership, and therefore we transferOwnership at the end const deployer = await this.multiProvider.getSigner(chain).getAddress(); let routingHook; switch (config.type) { case HookType.ROUTING: { this.logger.debug('Deploying DomainRoutingHook for %s', chain); routingHook = await this.deployContract(chain, HookType.ROUTING, [ mailbox, deployer, ]); break; } case HookType.FALLBACK_ROUTING: { this.logger.debug('Deploying FallbackDomainRoutingHook for %s', chain); let fallbackAddress; if (typeof config.fallback === 'string') { fallbackAddress = config.fallback; } else { const fallbackHook = await this.deployContracts(chain, config.fallback, coreAddresses); fallbackAddress = fallbackHook[config.fallback.type].address; } routingHook = await this.deployContract(chain, HookType.FALLBACK_ROUTING, [mailbox, deployer, fallbackAddress]); break; } default: throw new Error(`Unexpected hook type: ${config}`); } const routingConfigs = []; let prevHookConfig; let prevHookAddress; for (const [dest, hookConfig] of Object.entries(config.domains)) { this.logger.debug(`Deploying routing hook for ${dest}`); const destDomain = this.multiProvider.getDomainId(dest); if (deepEquals(prevHookConfig, hookConfig) && prevHookAddress) { this.logger.debug(`Reusing hook ${prevHookAddress} for ${dest}`); routingConfigs.push({ destination: destDomain, hook: prevHookAddress, }); continue; } if (typeof hookConfig === 'string') { routingConfigs.push({ destination: destDomain, hook: hookConfig, }); prevHookConfig = hookConfig; prevHookAddress = hookConfig; } else { const hook = await this.deployContracts(chain, hookConfig, coreAddresses); routingConfigs.push({ destination: destDomain, hook: hook[hookConfig.type].address, }); prevHookConfig = hookConfig; prevHookAddress = hook[hookConfig.type].address; } } const overrides = this.multiProvider.getTransactionOverrides(chain); await this.runIfOwner(chain, routingHook, async () => { this.logger.debug({ chain, routingHookAddress: routingHook.address, routingConfigs, }, 'Setting routing hooks'); const estimatedGas = await routingHook.estimateGas.setHooks(routingConfigs); return this.multiProvider.handleTx(chain, routingHook.setHooks(routingConfigs, { gasLimit: addBufferToGasLimit(estimatedGas), ...overrides, })); }); await this.transferOwnershipOfContracts(chain, config, { [config.type]: routingHook, }); return routingHook; } async deployAmountRoutingHook(chain, config) { const hooks = []; for (const hookConfig of [config.lowerHook, config.upperHook]) { if (typeof hookConfig === 'string') { hooks.push(hookConfig); continue; } const contracts = await this.deployContracts(chain, hookConfig.type, this.core[chain]); hooks.push(contracts[hookConfig.type].address); } const [lowerHook, upperHook] = hooks; // deploy routing hook const routingHook = await this.deployContract(chain, HookType.AMOUNT_ROUTING, [lowerHook, upperHook, config.threshold]); return routingHook; } } //# sourceMappingURL=HyperlaneHookDeployer.js.map