UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

636 lines 33.1 kB
import { getArbitrumNetwork } from '@arbitrum/sdk'; import { BigNumber, ethers } from 'ethers'; import { ArbL2ToL1Ism__factory, CCIPHook__factory, DomainRoutingHook__factory, IL1CrossDomainMessenger__factory, IPostDispatchHook__factory, InterchainGasPaymaster__factory, OPStackIsm__factory, Ownable__factory, PausableHook__factory, ProtocolFee__factory, StaticAggregationHookFactory__factory, StaticAggregationHook__factory, StorageGasOracle__factory, } from '@hyperlane-xyz/core'; import { ZERO_ADDRESS_HEX_32, addressToBytes32, assert, deepEquals, eqAddress, isZeroishAddress, rootLogger, } from '@hyperlane-xyz/utils'; import { TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM } from '../consts/igp.js'; import { HyperlaneModule, } from '../core/AbstractHyperlaneModule.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { IsmType } from '../ism/types.js'; import { normalizeConfig } from '../utils/ism.js'; import { EvmHookReader } from './EvmHookReader.js'; import { hookFactories } from './contracts.js'; import { HookConfigSchema, HookType, HookTypeToContractNameMap, MUTABLE_HOOK_TYPE, } from './types.js'; class HookDeployer extends HyperlaneDeployer { cachingEnabled = false; deployContracts(_chain, _config) { throw new Error('Method not implemented.'); } } export class EvmHookModule extends HyperlaneModule { multiProvider; contractVerifier; logger = rootLogger.child({ module: 'EvmHookModule' }); reader; // "ISM" Factory has aggregation hook factories too hookFactory; deployer; // Adding these to reduce how often we need to grab from MultiProvider. chain; chainId; domainId; // Transaction overrides for the chain txOverrides; constructor(multiProvider, params, ccipContractCache, contractVerifier) { params.config = HookConfigSchema.parse(params.config); super(params); this.multiProvider = multiProvider; this.contractVerifier = contractVerifier; this.reader = new EvmHookReader(multiProvider, this.args.chain); this.hookFactory = HyperlaneIsmFactory.fromAddressesMap({ [this.args.chain]: params.addresses }, multiProvider, ccipContractCache, contractVerifier); this.deployer = new HookDeployer(multiProvider, hookFactories); this.chain = multiProvider.getChainName(this.args.chain); this.chainId = multiProvider.getEvmChainId(this.chain); this.domainId = multiProvider.getDomainId(this.chain); this.txOverrides = multiProvider.getTransactionOverrides(this.chain); } async read() { return typeof this.args.config === 'string' ? this.args.addresses.deployedHook : this.reader.deriveHookConfig(this.args.addresses.deployedHook); } async update(targetConfig) { // Nothing to do if its the default hook if (typeof targetConfig === 'string' && isZeroishAddress(targetConfig)) { return []; } // We need to normalize the current and target configs to compare. const normalizedTargetConfig = normalizeConfig(await this.reader.deriveHookConfig(targetConfig)); const normalizedCurrentConfig = normalizeConfig(await this.read()); // If configs match, no updates needed if (deepEquals(normalizedCurrentConfig, normalizedTargetConfig)) { return []; } // Update the module config to the target one as we are sure now that an update will be needed this.args.config = normalizedTargetConfig; // if the new config is an address just point the module to the new address if (typeof normalizedTargetConfig === 'string') { this.args.addresses.deployedHook = normalizedTargetConfig; return []; } // Conditions for deploying a new hook: // - If updating from an address/custom config to a proper hook config. // - If updating a proper hook config whose types are different. // - If it is not a mutable Hook. if (typeof normalizedCurrentConfig === 'string' || normalizedCurrentConfig.type !== normalizedTargetConfig.type || !MUTABLE_HOOK_TYPE.includes(normalizedTargetConfig.type)) { const contract = await this.deploy({ config: normalizedTargetConfig, }); this.args.addresses.deployedHook = contract.address; return []; } // MERKLE_TREE, AGGREGATION and OP_STACK hooks should already be handled before this call return this.updateMutableHook({ current: normalizedCurrentConfig, target: normalizedTargetConfig, }); } // manually write static create function static async create({ chain, config, proxyFactoryFactories, coreAddresses, multiProvider, ccipContractCache, contractVerifier, }) { const module = new EvmHookModule(multiProvider, { addresses: { ...proxyFactoryFactories, ...coreAddresses, deployedHook: ethers.constants.AddressZero, }, chain, config, }, ccipContractCache, contractVerifier); const deployedHook = await module.deploy({ config }); module.args.addresses.deployedHook = deployedHook.address; return module; } // Compute delta between current and target domain configurations async computeRoutingHooksToSet({ currentDomains, targetDomains, }) { const routingHookUpdates = []; // Iterate over the target domains and compare with the current configuration for (const [dest, targetDomainConfig] of Object.entries(targetDomains)) { const destDomain = this.multiProvider.tryGetDomainId(dest); if (!destDomain) { this.logger.warn(`Domain not found in MultiProvider: ${dest}`); continue; } // If the domain is not in the current config or the config has changed, deploy a new hook // TODO: in-place updates per domain as a future optimization if (!deepEquals(currentDomains[dest], targetDomainConfig)) { const domainHook = await this.deploy({ config: targetDomainConfig, }); routingHookUpdates.push({ destination: destDomain, hook: domainHook.address, }); } } return routingHookUpdates; } async updateMutableHook(configs) { const { current, target } = configs; let updateTxs; assert(current.type === target.type, `Mutable hook update requires both hook configs to be of the same type. Expected ${current.type}, got ${target.type}`); assert(MUTABLE_HOOK_TYPE.includes(current.type), 'Expected update config to be of mutable hook type'); // Checking both objects type fields to help typescript narrow the type down correctly if (current.type === HookType.INTERCHAIN_GAS_PAYMASTER && target.type === HookType.INTERCHAIN_GAS_PAYMASTER) { updateTxs = await this.updateIgpHook({ currentConfig: current, targetConfig: target, }); } else if (current.type === HookType.PROTOCOL_FEE && target.type === HookType.PROTOCOL_FEE) { updateTxs = await this.updateProtocolFeeHook({ currentConfig: current, targetConfig: target, }); } else if (current.type === HookType.PAUSABLE && target.type === HookType.PAUSABLE) { updateTxs = await this.updatePausableHook({ currentConfig: current, targetConfig: target, }); } else if ((current.type === HookType.ROUTING && target.type === HookType.ROUTING) || (current.type === HookType.FALLBACK_ROUTING && target.type === HookType.FALLBACK_ROUTING)) { updateTxs = await this.updateRoutingHook({ currentConfig: current, targetConfig: target, }); } else { throw new Error(`Unsupported hook type: ${target.type}`); } // Lastly, check if the resolved owner is different from the current owner const owner = await Ownable__factory.connect(this.args.addresses.deployedHook, this.multiProvider.getProvider(this.chain)).owner(); // Return an ownership transfer transaction if required if (!eqAddress(target.owner, owner)) { updateTxs.push({ annotation: 'Transferring ownership of ownable Hook...', chainId: this.chainId, to: this.args.addresses.deployedHook, data: Ownable__factory.createInterface().encodeFunctionData('transferOwnership(address)', [target.owner]), }); } return updateTxs; } async updatePausableHook({ currentConfig, targetConfig, }) { const updateTxs = []; if (currentConfig.paused !== targetConfig.paused) { // Have to encode separately otherwise tsc will complain // about being unable to infer types correctly const pausableInterface = PausableHook__factory.createInterface(); const data = targetConfig.paused ? pausableInterface.encodeFunctionData('pause') : pausableInterface.encodeFunctionData('unpause'); updateTxs.push({ annotation: `Updating paused state to ${targetConfig.paused}`, chainId: this.chainId, to: this.args.addresses.deployedHook, data, }); } return updateTxs; } async updateIgpHook({ currentConfig, targetConfig, }) { const updateTxs = []; const igpInterface = InterchainGasPaymaster__factory.createInterface(); // Update beneficiary if changed if (!eqAddress(currentConfig.beneficiary, targetConfig.beneficiary)) { updateTxs.push({ annotation: `Updating beneficiary from ${currentConfig.beneficiary} to ${targetConfig.beneficiary}`, chainId: this.chainId, to: this.args.addresses.deployedHook, data: igpInterface.encodeFunctionData('setBeneficiary(address)', [ targetConfig.beneficiary, ]), }); } // get gasOracleAddress using any remote domain in the current config let gasOracle; const domainKeys = Object.keys(currentConfig.oracleConfig); // If possible, reuse and reconfigure the gas oracle from the first remote we know. // Otherwise if there are no remotes in current config, deploy a new gas oracle with our target config. // We should be reusing the same oracle for all remotes, but if not, the updateIgpRemoteGasParams step will rectify this if (domainKeys.length > 0) { const domainId = this.multiProvider.getDomainId(domainKeys[0]); ({ gasOracle } = await InterchainGasPaymaster__factory.connect(this.args.addresses.deployedHook, this.multiProvider.getSignerOrProvider(this.chain))['destinationGasConfigs(uint32)'](domainId)); // update storage gas oracle // Note: this will only update the gas oracle for remotes that are in the target config updateTxs.push(...(await this.updateStorageGasOracle({ gasOracle, currentOracleConfig: currentConfig.oracleConfig, targetOracleConfig: targetConfig.oracleConfig, targetOverhead: targetConfig.overhead, // used to log example remote gas costs }))); } else { const newGasOracle = await this.deployStorageGasOracle({ config: targetConfig, }); gasOracle = newGasOracle.address; } // update igp remote gas params // Note: this will only update the gas params for remotes that are in the target config updateTxs.push(...(await this.updateIgpRemoteGasParams({ interchainGasPaymaster: this.args.addresses.deployedHook, gasOracle, currentOverheads: currentConfig.overhead, targetOverheads: targetConfig.overhead, }))); return updateTxs; } async updateIgpRemoteGasParams({ interchainGasPaymaster, gasOracle, currentOverheads, targetOverheads, }) { const gasParamsToSet = []; for (const [remote, gasOverhead] of Object.entries(targetOverheads)) { // Note: non-EVM remotes actually *are* supported, provided that the remote domain is in the MultiProvider. // Previously would check core metadata for non EVMs and fallback to multiprovider for custom EVMs const remoteDomain = this.multiProvider.tryGetDomainId(remote); if (!remoteDomain) { this.logger.warn(`Skipping overhead ${this.chain} -> ${remote}. Expected if the remote domain is not in the MultiProvider.`); continue; } // only update if the gas overhead has changed if (currentOverheads?.[remote] !== gasOverhead) { this.logger.debug(`Setting gas params for ${this.chain} -> ${remote}: gasOverhead = ${gasOverhead} gasOracle = ${gasOracle}`); gasParamsToSet.push({ remoteDomain, config: { gasOverhead, gasOracle, }, }); } } if (gasParamsToSet.length === 0) { return []; } return [ { annotation: `Updating overhead for domains ${Object.keys(targetOverheads).join(', ')}...`, chainId: this.chainId, to: interchainGasPaymaster, data: InterchainGasPaymaster__factory.createInterface().encodeFunctionData('setDestinationGasConfigs((uint32,(address,uint96))[])', [gasParamsToSet]), }, ]; } async updateStorageGasOracle({ gasOracle, currentOracleConfig, targetOracleConfig, targetOverhead, }) { this.logger.info(`Updating gas oracle configuration from ${this.chain}...`); const configsToSet = []; for (const [remote, target] of Object.entries(targetOracleConfig)) { // Note: non-EVM remotes actually *are* supported, provided that the remote domain is in the MultiProvider. // Previously would check core metadata for non EVMs and fallback to multiprovider for custom EVMs const current = currentOracleConfig?.[remote]; const remoteDomain = this.multiProvider.tryGetDomainId(remote); if (!remoteDomain) { this.logger.warn(`Skipping gas oracle update ${this.chain} -> ${remote}. Expected if the remote domain is not in the MultiProvider.`); continue; } // only update if the oracle config has changed if (!current || !deepEquals(current, target)) { configsToSet.push({ remoteDomain, ...target }); // Log an example remote gas cost const exampleRemoteGas = (targetOverhead[remote] ?? 200000) + 50000; const exampleRemoteGasCost = BigNumber.from(target.tokenExchangeRate) .mul(target.gasPrice) .mul(exampleRemoteGas) .div(TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM); this.logger.info(`${this.chain} -> ${remote}: ${exampleRemoteGas} remote gas cost: ${ethers.utils.formatEther(exampleRemoteGasCost)}`); } } if (configsToSet.length === 0) { return []; } return [ { annotation: `Updating gas oracle config for domains ${Object.keys(targetOracleConfig).join(', ')}...`, chainId: this.chainId, to: gasOracle, data: StorageGasOracle__factory.createInterface().encodeFunctionData('setRemoteGasDataConfigs((uint32,uint128,uint128)[])', [configsToSet]), }, ]; } async updateProtocolFeeHook({ currentConfig, targetConfig, }) { const updateTxs = []; const protocolFeeInterface = ProtocolFee__factory.createInterface(); // if maxProtocolFee has changed, deploy a new hook if (currentConfig.maxProtocolFee !== targetConfig.maxProtocolFee) { const hook = await this.deployProtocolFeeHook({ config: targetConfig }); this.args.addresses.deployedHook = hook.address; return []; } // Update protocol fee if changed if (currentConfig.protocolFee !== targetConfig.protocolFee) { updateTxs.push({ annotation: `Updating protocol fee from ${currentConfig.protocolFee} to ${targetConfig.protocolFee}`, chainId: this.chainId, to: this.args.addresses.deployedHook, data: protocolFeeInterface.encodeFunctionData('setProtocolFee(uint256)', [targetConfig.protocolFee]), }); } // Update beneficiary if changed if (currentConfig.beneficiary !== targetConfig.beneficiary) { updateTxs.push({ annotation: `Updating beneficiary from ${currentConfig.beneficiary} to ${targetConfig.beneficiary}`, chainId: this.chainId, to: this.args.addresses.deployedHook, data: protocolFeeInterface.encodeFunctionData('setBeneficiary(address)', [targetConfig.beneficiary]), }); } // Return the transactions to update the protocol fee hook return updateTxs; } // Updates a routing hook async updateRoutingHook({ currentConfig, targetConfig, }) { // Deploy a new fallback hook if the fallback config has changed if (targetConfig.type === HookType.FALLBACK_ROUTING && !deepEquals(targetConfig.fallback, currentConfig.fallback)) { const hook = await this.deploy({ config: targetConfig }); this.args.addresses.deployedHook = hook.address; return []; } const routingUpdates = await this.computeRoutingHooksToSet({ currentDomains: currentConfig.domains, targetDomains: targetConfig.domains, }); // Return if no updates are required if (routingUpdates.length === 0) { return []; } // Create tx for setting hooks return [ { annotation: 'Updating routing hooks...', chainId: this.chainId, to: this.args.addresses.deployedHook, data: DomainRoutingHook__factory.createInterface().encodeFunctionData('setHooks((uint32,address)[])', [routingUpdates]), }, ]; } async deploy({ config, }) { config = HookConfigSchema.parse(config); // If it's an address, just return a base Hook if (typeof config === 'string') { // TODO: https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3773 // we can remove the ts-ignore once we have a proper type for address Hooks // @ts-ignore return IPostDispatchHook__factory.connect(config, this.multiProvider.getSignerOrProvider(this.args.chain)); } this.logger.debug(`Deploying hook of type ${config.type}`); switch (config.type) { case HookType.MERKLE_TREE: case HookType.MAILBOX_DEFAULT: return this.deployer.deployContract(this.chain, config.type, [ this.args.addresses.mailbox, ]); case HookType.INTERCHAIN_GAS_PAYMASTER: return this.deployIgpHook({ config }); case HookType.AGGREGATION: return this.deployAggregationHook({ config }); case HookType.PROTOCOL_FEE: return this.deployProtocolFeeHook({ config }); case HookType.OP_STACK: return this.deployOpStackHook({ config }); case HookType.ARB_L2_TO_L1: return this.deployArbL1ToL1Hook({ config }); case HookType.ROUTING: case HookType.FALLBACK_ROUTING: return this.deployRoutingHook({ config }); case HookType.PAUSABLE: return this.deployPausableHook({ config }); case HookType.AMOUNT_ROUTING: return this.deployAmountRoutingHook({ config }); case HookType.CCIP: return this.deployCCIPHook({ config }); default: throw new Error(`Unsupported hook config: ${config}`); } } async deployProtocolFeeHook({ config, }) { this.logger.debug('Deploying ProtocolFeeHook...'); const deployer = new HookDeployer(this.multiProvider, hookFactories); return deployer.deployContract(this.chain, HookType.PROTOCOL_FEE, [ config.maxProtocolFee, config.protocolFee, config.beneficiary, config.owner, ]); } async deployPausableHook({ config, }) { this.logger.debug('Deploying PausableHook...'); const deployer = new HookDeployer(this.multiProvider, hookFactories); const hook = await deployer.deployContract(this.chain, HookType.PAUSABLE, []); // transfer ownership await this.multiProvider.handleTx(this.chain, hook.transferOwnership(config.owner, this.txOverrides)); return hook; } async deployAggregationHook({ config, }) { this.logger.debug('Deploying AggregationHook...'); // deploy subhooks const aggregatedHooks = []; for (const hookConfig of config.hooks) { const { address } = await this.deploy({ config: hookConfig }); aggregatedHooks.push(address); } // deploy aggregation hook this.logger.debug(`Deploying aggregation hook of type ${config.hooks.map((h) => typeof h === 'string' ? h : h.type)}...`); const signer = this.multiProvider.getSigner(this.chain); const factory = StaticAggregationHookFactory__factory.connect(this.args.addresses.staticAggregationHookFactory, signer); const address = await this.hookFactory.deployStaticAddressSet(this.chain, factory, aggregatedHooks, this.logger); // return aggregation hook return StaticAggregationHook__factory.connect(address, signer); } // NOTE: this deploys the ism too on the destination chain if it doesn't exist async deployOpStackHook({ config, }) { const chain = this.chain; const mailbox = this.args.addresses.mailbox; this.logger.debug('Deploying OPStackHook for %s to %s...', chain, config.destinationChain); // 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, }; // deploy opstack ism const opStackIsmAddress = (await EvmIsmModule.create({ chain: config.destinationChain, config: ismConfig, proxyFactoryFactories: this.args.addresses, mailbox: mailbox, multiProvider: this.multiProvider, contractVerifier: this.contractVerifier, })).serialize().deployedIsm; // connect to ISM const opstackIsm = OPStackIsm__factory.connect(opStackIsmAddress, this.multiProvider.getSignerOrProvider(config.destinationChain)); // deploy opstack hook const hook = await this.deployer.deployContract(chain, HookType.OP_STACK, [ mailbox, this.multiProvider.getDomainId(config.destinationChain), addressToBytes32(opstackIsm.address), config.nativeBridge, ]); // 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), this.multiProvider.getTransactionOverrides(config.destinationChain))); return hook; } // NOTE: this deploys the ism too on the destination chain if it doesn't exist async deployArbL1ToL1Hook({ config, }) { const chain = this.chain; const mailbox = this.args.addresses.mailbox; const destinationChainId = this.multiProvider.tryGetEvmChainId(config.destinationChain); if (!destinationChainId) { throw new Error(`Only ethereum chains supported for deploying Arbitrum L2 hook, given: ${config.destinationChain}`); } const bridge = config.bridge ?? getArbitrumNetwork(destinationChainId).ethBridge.bridge; const ismConfig = { type: IsmType.ARB_L2_TO_L1, bridge, }; const arbL2ToL1IsmAddress = (await EvmIsmModule.create({ chain: config.destinationChain, config: ismConfig, proxyFactoryFactories: this.args.addresses, mailbox: mailbox, multiProvider: this.multiProvider, contractVerifier: this.contractVerifier, })).serialize().deployedIsm; // connect to ISM const arbL2ToL1Ism = ArbL2ToL1Ism__factory.connect(arbL2ToL1IsmAddress, this.multiProvider.getSignerOrProvider(config.destinationChain)); const childHook = await this.deploy({ config: config.childHook }); // deploy arbL1ToL1 hook const hook = await this.deployer.deployContract(chain, HookType.ARB_L2_TO_L1, [ mailbox, this.multiProvider.getDomainId(config.destinationChain), addressToBytes32(arbL2ToL1IsmAddress), config.arbSys, childHook.address, ]); // set authorized hook on arbL2ToL1 ism const authorizedHook = await arbL2ToL1Ism.authorizedHook(); if (authorizedHook === addressToBytes32(hook.address)) { this.logger.debug('Authorized hook already set on ism %s', arbL2ToL1Ism.address); return hook; } else if (authorizedHook !== ethers.constants.HashZero) { this.logger.debug('Authorized hook mismatch on ism %s, expected %s, got %s', arbL2ToL1Ism.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, arbL2ToL1Ism.address, config.destinationChain); await this.multiProvider.handleTx(config.destinationChain, arbL2ToL1Ism.setAuthorizedHook(addressToBytes32(hook.address), this.multiProvider.getTransactionOverrides(config.destinationChain))); return hook; } async deployCCIPHook({ config, }) { const hook = this.hookFactory.ccipContractCache.getHook(this.chain, config.destinationChain); if (!hook) { this.logger.error(`CCIP Hook not found for ${this.chain} -> ${config.destinationChain}`); throw new Error(`CCIP Hook not found for ${this.chain} -> ${config.destinationChain}`); } return CCIPHook__factory.connect(hook, this.multiProvider.getSigner(this.chain)); } async deployRoutingHook({ config, }) { // originally set owner to deployer so we can set hooks const deployerAddress = await this.multiProvider.getSignerAddress(this.chain); let routingHook; if (config.type === HookType.FALLBACK_ROUTING) { // deploy fallback hook const fallbackHook = await this.deploy({ config: config.fallback }); // deploy routing hook with fallback routingHook = await this.deployer.deployContractWithName(this.chain, HookType.FALLBACK_ROUTING, HookTypeToContractNameMap[HookType.FALLBACK_ROUTING], [this.args.addresses.mailbox, deployerAddress, fallbackHook.address]); } else { // deploy routing hook routingHook = await this.deployer.deployContract(this.chain, HookType.ROUTING, [this.args.addresses.mailbox, deployerAddress]); } // compute the hooks that need to be set const hooksToSet = await this.computeRoutingHooksToSet({ currentDomains: {}, targetDomains: config.domains, }); // set hooks await this.multiProvider.handleTx(this.chain, routingHook.setHooks(hooksToSet, this.txOverrides)); // transfer ownership await this.multiProvider.handleTx(this.chain, routingHook.transferOwnership(config.owner, this.txOverrides)); // return a fully configured routing hook return routingHook; } async deployIgpHook({ config, }) { this.logger.debug('Deploying IGP as hook...'); // Deploy the StorageGasOracle const storageGasOracle = await this.deployStorageGasOracle({ config, }); // Deploy the InterchainGasPaymaster const interchainGasPaymaster = await this.deployInterchainGasPaymaster({ storageGasOracle, config, }); return interchainGasPaymaster; } async deployInterchainGasPaymaster({ storageGasOracle, config, }) { // Set the deployer as the owner of the IGP for configuration purposes const deployerAddress = await this.multiProvider.getSignerAddress(this.chain); // Deploy the InterchainGasPaymaster const igp = await this.deployer.deployProxiedContract(this.chain, HookType.INTERCHAIN_GAS_PAYMASTER, HookType.INTERCHAIN_GAS_PAYMASTER, this.args.addresses.proxyAdmin, [], [deployerAddress, config.beneficiary]); // Obtain the transactions to set the gas params for each remote const configureTxs = await this.updateIgpRemoteGasParams({ interchainGasPaymaster: igp.address, gasOracle: storageGasOracle.address, targetOverheads: config.overhead, }); // Set the gas params for each remote for (const tx of configureTxs) { await this.multiProvider.sendTransaction(this.chain, tx); } // Transfer igp to the configured owner await this.multiProvider.handleTx(this.chain, igp.transferOwnership(config.owner, this.txOverrides)); return igp; } async deployAmountRoutingHook({ config, }) { const hooks = []; for (const hookConfig of [config.lowerHook, config.upperHook]) { const { address } = await this.deploy({ config: hookConfig }); hooks.push(address); } const [lowerHook, upperHook] = hooks; // deploy routing hook const routingHook = await this.deployer.deployContract(this.chain, HookType.AMOUNT_ROUTING, [lowerHook, upperHook, config.threshold]); return routingHook; } async deployStorageGasOracle({ config, }) { // Deploy the StorageGasOracle, by default msg.sender is the owner const gasOracle = await this.deployer.deployContractFromFactory(this.chain, new StorageGasOracle__factory(), 'storageGasOracle', []); // Obtain the transactions to set the gas params for each remote const configureTxs = await this.updateStorageGasOracle({ gasOracle: gasOracle.address, targetOracleConfig: config.oracleConfig, targetOverhead: config.overhead, }); // Set the gas params for each remote for (const tx of configureTxs) { await this.multiProvider.sendTransaction(this.chain, tx); } // Transfer gas oracle to the configured owner await this.multiProvider.handleTx(this.chain, gasOracle.transferOwnership(config.oracleKey, this.txOverrides)); return gasOracle; } } //# sourceMappingURL=EvmHookModule.js.map