@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
636 lines • 33.1 kB
JavaScript
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