diff --git a/typescript/infra/src/core/govern.ts b/typescript/infra/src/core/govern.ts index 3ebd5f74c..42cacbc60 100644 --- a/typescript/infra/src/core/govern.ts +++ b/typescript/infra/src/core/govern.ts @@ -1,11 +1,13 @@ import { prompts } from 'prompts'; -import { InterchainGasPaymaster } from '@hyperlane-xyz/core'; +import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core'; import { ChainMap, ChainName, CoreContracts, CoreViolationType, + DefaultIsmIgpViolation, + DefaultIsmIgpViolationType, EnrolledValidatorsViolation, HyperlaneCoreChecker, IgpBeneficiaryViolation, @@ -149,6 +151,12 @@ export class HyperlaneCoreGovernor { this.handleIgpViolation(violation as IgpViolation); break; } + case CoreViolationType.DefaultIsmInterchainGasPaymaster: { + this.handleDefaultIsmIgpViolation( + violation as DefaultIsmIgpViolation, + ); + break; + } default: throw new Error(`Unsupported violation type ${violation.type}`); } @@ -191,10 +199,7 @@ export class HyperlaneCoreGovernor { submitterAddress: types.Address, ): Promise => { try { - await multiProvider.estimateGas(chain, { - ...call, - from: submitterAddress, - }); + await multiProvider.estimateGas(chain, call, submitterAddress); return true; } catch (e) {} // eslint-disable-line no-empty return false; @@ -362,4 +367,41 @@ export class HyperlaneCoreGovernor { throw new Error(`Unsupported IgpViolationType: ${violation.subType}`); } } + + handleDefaultIsmIgpViolation(violation: DefaultIsmIgpViolation) { + switch (violation.subType) { + case DefaultIsmIgpViolationType.DestinationGasOverheads: { + const configs: OverheadIgp.DomainConfigStruct[] = Object.entries( + violation.expected, + ).map( + ([remote, gasOverhead]) => + ({ + domain: this.checker.multiProvider.getDomainId(remote), + gasOverhead: gasOverhead, + } as OverheadIgp.DomainConfigStruct), + ); + + this.pushCall(violation.chain, { + to: violation.contract.address, + data: violation.contract.interface.encodeFunctionData( + 'setDestinationGasOverheads', + [configs], + ), + description: `Setting ${Object.keys(violation.expected) + .map((remoteStr) => { + const remote = remoteStr as ChainName; + const remoteId = this.checker.multiProvider.getDomainId(remote); + const expected = violation.expected[remote]; + return `destination gas overhead for ${remote} (domain ID ${remoteId}) to ${expected}`; + }) + .join(', ')}`, + }); + break; + } + default: + throw new Error( + `Unsupported DefaultIsmIgpViolationType: ${violation.subType}`, + ); + } + } } diff --git a/typescript/sdk/src/core/HyperlaneCoreChecker.ts b/typescript/sdk/src/core/HyperlaneCoreChecker.ts index 5f91391de..15c3cf521 100644 --- a/typescript/sdk/src/core/HyperlaneCoreChecker.ts +++ b/typescript/sdk/src/core/HyperlaneCoreChecker.ts @@ -1,7 +1,8 @@ -import { utils as ethersUtils } from 'ethers'; +import { BigNumber, utils as ethersUtils } from 'ethers'; import { types, utils } from '@hyperlane-xyz/utils'; +import multisigIsmVerifyCosts from '../consts/multisigIsmVerifyCosts.json'; import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker'; import { ChainName } from '../types'; @@ -9,6 +10,8 @@ import { HyperlaneCore } from './HyperlaneCore'; import { CoreConfig, CoreViolationType, + DefaultIsmIgpDestinationGasOverheadsViolation, + DefaultIsmIgpViolationType, EnrolledValidatorsViolation, GasOracleContractType, IgpBeneficiaryViolation, @@ -51,6 +54,7 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker< await this.checkMultisigIsm(chain); await this.checkBytecodes(chain); await this.checkValidatorAnnounce(chain); + await this.checkDefaultIsmInterchainGasPaymaster(chain); await this.checkInterchainGasPaymaster(chain); } @@ -262,6 +266,48 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker< } } + async checkDefaultIsmInterchainGasPaymaster(local: ChainName): Promise { + const coreContracts = this.app.getContracts(local); + const defaultIsmIgp = coreContracts.defaultIsmInterchainGasPaymaster; + + // Construct the violation, updating the actual & expected + // objects as violations are found. + // A single violation is used so that only a single `setDestinationGasOverheads` + // call is generated to set multiple gas overheads. + const gasOverheadViolation: DefaultIsmIgpDestinationGasOverheadsViolation = + { + type: CoreViolationType.DefaultIsmInterchainGasPaymaster, + subType: DefaultIsmIgpViolationType.DestinationGasOverheads, + contract: defaultIsmIgp, + chain: local, + actual: {}, + expected: {}, + }; + + const remotes = this.app.remoteChains(local); + for (const remote of remotes) { + const { validators, threshold } = this.configMap[remote].multisigIsm; + const expectedOverhead = this.getExpectedOverheadGas( + threshold, + validators.length, + ); + + const remoteId = this.multiProvider.getDomainId(remote); + const existingOverhead = await defaultIsmIgp.destinationGasOverhead( + remoteId, + ); + if (!expectedOverhead.eq(existingOverhead)) { + const remoteChain = remote as ChainName; + gasOverheadViolation.actual[remoteChain] = existingOverhead; + gasOverheadViolation.expected[remoteChain] = expectedOverhead; + } + } + + if (Object.keys(gasOverheadViolation.actual).length > 0) { + this.addViolation(gasOverheadViolation); + } + } + async checkInterchainGasPaymaster(local: ChainName): Promise { const coreContracts = this.app.getContracts(local); const igp = coreContracts.interchainGasPaymaster.contract; @@ -279,7 +325,7 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker< expected: {}, }; - const remotes = this.multiProvider.getRemoteChains(local); + const remotes = this.app.remoteChains(local); for (const remote of remotes) { const remoteId = this.multiProvider.getDomainId(remote); const actualGasOracle = await igp.gasOracles(remoteId); @@ -327,4 +373,18 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker< throw Error(`Unsupported gas oracle type ${gasOracleType}`); } } + + private getExpectedOverheadGas( + threshold: number, + validatorSetCount: number, + ): BigNumber { + const expectedOverhead: number | undefined = + // @ts-ignore + multisigIsmVerifyCosts[`${validatorSetCount}`][`${threshold}`]; + if (!expectedOverhead) + throw new Error( + `Unknown verification cost for ${threshold} of ${validatorSetCount}`, + ); + return BigNumber.from(expectedOverhead); + } } diff --git a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts index 8f2615375..64e388183 100644 --- a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts @@ -78,7 +78,10 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< // Set the gas oracles - const remotes = this.multiProvider.getRemoteChains(chain); + const configChains = Object.keys(this.configMap); + const remotes = this.multiProvider + .intersect(configChains, false) + .multiProvider.getRemoteChains(chain); const gasOracleConfigsToSet: InterchainGasPaymaster.GasOracleConfigStruct[] = []; diff --git a/typescript/sdk/src/core/types.ts b/typescript/sdk/src/core/types.ts index 8aa337951..474a4519c 100644 --- a/typescript/sdk/src/core/types.ts +++ b/typescript/sdk/src/core/types.ts @@ -1,7 +1,10 @@ +import { BigNumber } from 'ethers'; + import { InterchainGasPaymaster, Mailbox, MultisigIsm, + OverheadIgp, } from '@hyperlane-xyz/core'; import type { types } from '@hyperlane-xyz/utils'; @@ -35,6 +38,7 @@ export enum CoreViolationType { ConnectionManager = 'ConnectionManager', ValidatorAnnounce = 'ValidatorAnnounce', InterchainGasPaymaster = 'InterchainGasPaymaster', + DefaultIsmInterchainGasPaymaster = 'DefaultIsmInterchainGasPaymaster', } export enum MultisigIsmViolationType { @@ -46,6 +50,10 @@ export enum MailboxViolationType { DefaultIsm = 'DefaultIsm', } +export enum DefaultIsmIgpViolationType { + DestinationGasOverheads = 'DestinationGasOverheads', +} + export enum IgpViolationType { Beneficiary = 'Beneficiary', GasOracles = 'GasOracles', @@ -106,3 +114,16 @@ export interface IgpGasOraclesViolation extends IgpViolation { actual: ChainMap; expected: ChainMap; } + +export interface DefaultIsmIgpViolation extends CheckerViolation { + type: CoreViolationType.DefaultIsmInterchainGasPaymaster; + contract: OverheadIgp; + subType: DefaultIsmIgpViolationType; +} + +export interface DefaultIsmIgpDestinationGasOverheadsViolation + extends DefaultIsmIgpViolation { + subType: DefaultIsmIgpViolationType.DestinationGasOverheads; + actual: ChainMap; + expected: ChainMap; +} diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index e5bf65a04..520ea83d0 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -55,6 +55,8 @@ export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer'; export { CoreConfig, CoreViolationType, + DefaultIsmIgpViolation, + DefaultIsmIgpViolationType, EnrolledValidatorsViolation, GasOracleContractType, IgpBeneficiaryViolation, diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index 9990aaaf2..17e24b744 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -550,7 +550,14 @@ export class MultiProvider { tx: PopulatedTransaction, from?: string, ): Promise { - const txReq = await this.prepareTx(chainNameOrId, tx, from); + const txReq = { + ...(await this.prepareTx(chainNameOrId, tx, from)), + // Reset any tx request params that may have an unintended effect on gas estimation + gasLimit: undefined, + gasPrice: undefined, + maxPriorityFeePerGas: undefined, + maxFeePerGas: undefined, + }; const provider = this.getProvider(chainNameOrId); return provider.estimateGas(txReq); }