@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
128 lines • 6.9 kB
JavaScript
import { ethers } from 'ethers';
import { addBufferToGasLimit, eqAddress, rootLogger, } from '@hyperlane-xyz/utils';
import { TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM } from '../consts/igp.js';
import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js';
import { submitBatched } from '../deploy/utils.js';
import { igpFactories } from './contracts.js';
import { oracleConfigToOracleData, serializeDifference, } from './oracle/types.js';
export class HyperlaneIgpDeployer extends HyperlaneDeployer {
constructor(multiProvider, contractVerifier, concurrentDeploy = false) {
super(multiProvider, igpFactories, {
logger: rootLogger.child({ module: 'IgpDeployer' }),
contractVerifier,
concurrentDeploy,
});
}
async deployInterchainGasPaymaster(chain, proxyAdmin, storageGasOracle, config) {
const igp = await this.deployProxiedContract(chain, 'interchainGasPaymaster', 'interchainGasPaymaster', proxyAdmin.address, [], [await this.multiProvider.getSignerAddress(chain), config.beneficiary]);
const gasParamsToSet = [];
for (const [remote, newGasOverhead] of Object.entries(config.overhead)) {
// TODO: add back support for non-EVM remotes.
// Previously would check core metadata for non EVMs and fallback to multiprovider for custom EVMs
const remoteId = this.multiProvider.tryGetDomainId(remote);
if (remoteId === null) {
this.logger.warn(`Skipping overhead ${chain} -> ${remote}. Expected if the remote is a non-EVM chain.`);
continue;
}
const currentGasConfig = await igp.destinationGasConfigs(remoteId);
if (!eqAddress(currentGasConfig.gasOracle, storageGasOracle.address) ||
!currentGasConfig.gasOverhead.eq(newGasOverhead)) {
this.logger.debug(`Setting gas params for ${chain} -> ${remote}: gasOverhead = ${newGasOverhead} gasOracle = ${storageGasOracle.address}`);
gasParamsToSet.push({
remoteDomain: remoteId,
config: {
gasOverhead: newGasOverhead,
gasOracle: storageGasOracle.address,
},
});
}
}
if (gasParamsToSet.length > 0) {
await this.runIfOwner(chain, igp, async () => {
await submitBatched(chain, gasParamsToSet, async (batch) => {
const estimatedGas = await igp.estimateGas.setDestinationGasConfigs(batch);
await this.multiProvider.handleTx(chain, igp.setDestinationGasConfigs(batch, {
gasLimit: addBufferToGasLimit(estimatedGas),
...this.multiProvider.getTransactionOverrides(chain),
}));
}, this.logger, 'gas configs');
});
}
if (config.quoteSigners?.length) {
for (const signer of config.quoteSigners) {
this.logger.debug(`Adding quote signer ${signer} to IGP on ${chain}`);
await this.multiProvider.handleTx(chain, igp.addQuoteSigner(signer, this.multiProvider.getTransactionOverrides(chain)));
}
}
return igp;
}
async deployStorageGasOracle(chain, config) {
const gasOracle = await this.deployContract(chain, 'storageGasOracle', []);
if (!config.oracleConfig) {
this.logger.debug('No oracle config provided, skipping...');
return gasOracle;
}
this.logger.info(`Configuring gas oracle from ${chain}...`);
const configsToSet = [];
// For each remote, check if the gas oracle has the correct data
for (const [remote, desired] of Object.entries(config.oracleConfig)) {
// TODO: add back support for non-EVM remotes.
// Previously would check core metadata for non EVMs and fallback to multiprovider for custom EVMs
const remoteDomain = this.multiProvider.tryGetDomainId(remote);
if (remoteDomain === null) {
this.logger.warn(`Skipping gas oracle ${chain} -> ${remote}. Expected if the remote is a non-EVM chain.`);
continue;
}
const actual = await gasOracle.remoteGasData(remoteDomain);
const desiredData = oracleConfigToOracleData(desired);
if (!actual.gasPrice.eq(desired.gasPrice) ||
!actual.tokenExchangeRate.eq(desired.tokenExchangeRate)) {
this.logger.info(`${chain} -> ${remote}: ${serializeDifference(this.multiProvider.getProtocol(chain), actual, desiredData)}`);
configsToSet.push({
remoteDomain,
...desiredData,
});
}
const exampleRemoteGas = (config.overhead[remote] ?? 200_000) + 50_000;
const exampleRemoteGasCost = desiredData.tokenExchangeRate
.mul(desiredData.gasPrice)
.mul(exampleRemoteGas)
.div(TOKEN_EXCHANGE_RATE_SCALE_ETHEREUM);
this.logger.info(`${chain} -> ${remote}: ${exampleRemoteGas} remote gas cost: ${ethers.utils.formatEther(exampleRemoteGasCost)}`);
}
if (configsToSet.length > 0) {
await this.runIfOwner(chain, gasOracle, async () => {
await submitBatched(chain, configsToSet, async (batch) => {
const estimatedGas = await gasOracle.estimateGas.setRemoteGasDataConfigs(batch);
await this.multiProvider.handleTx(chain, gasOracle.setRemoteGasDataConfigs(batch, {
gasLimit: addBufferToGasLimit(estimatedGas),
...this.multiProvider.getTransactionOverrides(chain),
}));
}, this.logger, 'gas oracle configs');
});
}
return gasOracle;
}
async deployContracts(chain, config) {
// NB: To share ProxyAdmins with HyperlaneCore, ensure the ProxyAdmin
// is loaded into the contract cache.
const proxyAdmin = await this.deployContract(chain, 'proxyAdmin', []);
const storageGasOracle = await this.deployStorageGasOracle(chain, config);
const interchainGasPaymaster = await this.deployInterchainGasPaymaster(chain, proxyAdmin, storageGasOracle, config);
const contracts = {
proxyAdmin,
storageGasOracle,
interchainGasPaymaster,
};
const ownerConfig = {
...config,
ownerOverrides: {
...config.ownerOverrides,
storageGasOracle: config.oracleKey,
},
};
await this.transferOwnershipOfContracts(chain, ownerConfig, contracts);
return contracts;
}
}
//# sourceMappingURL=HyperlaneIgpDeployer.js.map