Check bytecodes against constants (#1595)

* Check bytecodes against constants

* Check ProxyAdmin

* Fix for polygon
pull/1811/head
Nam Chu Hoai 2 years ago committed by GitHub
parent 8105913dea
commit 38ef67005c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      typescript/infra/scripts/check-deploy.ts
  2. 39
      typescript/sdk/src/deploy/HyperlaneAppChecker.ts
  3. 87
      typescript/sdk/src/deploy/core/HyperlaneCoreChecker.ts
  4. 6
      typescript/sdk/src/deploy/types.ts

@ -25,6 +25,7 @@ async function check() {
console.table(coreChecker.violations, [ console.table(coreChecker.violations, [
'chain', 'chain',
'remote', 'remote',
'name',
'type', 'type',
'subType', 'subType',
'actual', 'actual',

@ -1,3 +1,5 @@
import { keccak256 } from 'ethers/lib/utils';
import { Ownable } from '@hyperlane-xyz/core'; import { Ownable } from '@hyperlane-xyz/core';
import { utils } from '@hyperlane-xyz/utils'; import { utils } from '@hyperlane-xyz/utils';
import type { types } from '@hyperlane-xyz/utils'; import type { types } from '@hyperlane-xyz/utils';
@ -9,7 +11,12 @@ import { ChainMap, ChainName } from '../types';
import { objMap } from '../utils/objects'; import { objMap } from '../utils/objects';
import { proxyAdmin, proxyImplementation, proxyViolation } from './proxy'; import { proxyAdmin, proxyImplementation, proxyViolation } from './proxy';
import { CheckerViolation, OwnerViolation, ViolationType } from './types'; import {
BytecodeMismatchViolation,
CheckerViolation,
OwnerViolation,
ViolationType,
} from './types';
export abstract class HyperlaneAppChecker< export abstract class HyperlaneAppChecker<
Chain extends ChainName, Chain extends ChainName,
@ -77,6 +84,36 @@ export abstract class HyperlaneAppChecker<
} }
} }
private removeBytecodeMetadata(bytecode: string): string {
// https://docs.soliditylang.org/en/v0.8.17/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
// Remove solc metadata from bytecode
return bytecode.substring(0, bytecode.length - 90);
}
// This method checks whether the bytecode of a contract matches the expected bytecode. It forces the deployer to explicitly acknowledge a change in bytecode. The violations can be remediated by updating the expected bytecode hash.
async checkBytecode(
chain: Chain,
name: string,
address: string,
expectedBytecodeHash: string,
modifyBytecodePriorToHash: (bytecode: string) => string = (_) => _,
): Promise<void> {
const provider = this.multiProvider.getChainProvider(chain);
const bytecode = await provider.getCode(address);
const bytecodeHash = keccak256(
modifyBytecodePriorToHash(this.removeBytecodeMetadata(bytecode)),
);
if (bytecodeHash !== expectedBytecodeHash) {
this.addViolation({
type: ViolationType.BytecodeMismatch,
chain,
expected: expectedBytecodeHash,
actual: bytecodeHash,
name,
} as BytecodeMismatchViolation);
}
}
async checkOwnership( async checkOwnership(
chain: Chain, chain: Chain,
owner: types.Address, owner: types.Address,

@ -1,3 +1,5 @@
import { defaultAbiCoder } from 'ethers/lib/utils';
import { utils } from '@hyperlane-xyz/utils'; import { utils } from '@hyperlane-xyz/utils';
import { eqAddress } from '@hyperlane-xyz/utils/dist/src/utils'; import { eqAddress } from '@hyperlane-xyz/utils/dist/src/utils';
@ -17,6 +19,18 @@ import {
ValidatorAnnounceViolation, ValidatorAnnounceViolation,
} from './types'; } from './types';
const MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH =
'0x29b7294ab3ad2e8587e5cce0e2289ce65e12a2ea2f1e7ab34a05e7737616f457';
const TRANSPARENT_PROXY_BYTECODE_HASH =
'0x4dde3d0906b6492bf1d4947f667afe8d53c8899f1d8788cabafd082938dceb2d';
const MULTISIG_ISM_BYTECODE_HASH =
'0x5565704ffa5b10fdf37d57abfddcf137101d5fb418ded21fa6c5f90262c57dc2';
const PROXY_ADMIN_BYTECODE_HASH =
'0x7c378e9d49408861ca754fe684b9f7d1ea525bddf095ee0463902df701453ba0';
const INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH =
'0xcee48ab556ae2ff12b6458fa92e5e31f4a07f7852a0ed06e43a7f06f3c4c6d76';
const OVERHEAD_IGP_BYTECODE_HASH =
'0x3cfed1f24f1e9b28a76d5a8c61696a04f7bc474404b823a2fcc210ea52346252';
export class HyperlaneCoreChecker< export class HyperlaneCoreChecker<
Chain extends ChainName, Chain extends ChainName,
> extends HyperlaneAppChecker<Chain, HyperlaneCore<Chain>, CoreConfig> { > extends HyperlaneAppChecker<Chain, HyperlaneCore<Chain>, CoreConfig> {
@ -31,6 +45,7 @@ export class HyperlaneCoreChecker<
await this.checkProxiedContracts(chain); await this.checkProxiedContracts(chain);
await this.checkMailbox(chain); await this.checkMailbox(chain);
await this.checkMultisigIsm(chain); await this.checkMultisigIsm(chain);
await this.checkBytecodes(chain);
await this.checkValidatorAnnounce(chain); await this.checkValidatorAnnounce(chain);
} }
@ -68,6 +83,78 @@ export class HyperlaneCoreChecker<
} }
} }
async checkBytecodes(chain: Chain): Promise<void> {
const contracts = this.app.getContracts(chain);
const mailbox = contracts.mailbox.contract;
const localDomain = await mailbox.localDomain();
await this.checkBytecode(
chain,
'Mailbox implementation',
contracts.mailbox.addresses.implementation,
MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH,
(_) =>
// This is obviously super janky but basically we are searching
// for the ocurrences of localDomain in the bytecode and remove
// that to compare, but some coincidental ocurrences of
// localDomain in the bytecode should be not be removed which
// are just done via an offset guard
_.replaceAll(
defaultAbiCoder.encode(['uint32'], [localDomain]).slice(2),
(match, offset) => (offset > 8000 ? match : ''),
),
);
await this.checkBytecode(
chain,
'Mailbox proxy',
contracts.mailbox.address,
TRANSPARENT_PROXY_BYTECODE_HASH,
);
await this.checkBytecode(
chain,
'InterchainGasPaymaster proxy',
contracts.interchainGasPaymaster.address,
TRANSPARENT_PROXY_BYTECODE_HASH,
);
await this.checkBytecode(
chain,
'ProxyAdmin',
contracts.proxyAdmin.address,
PROXY_ADMIN_BYTECODE_HASH,
);
await this.checkBytecode(
chain,
'MultisigIsm implementation',
contracts.multisigIsm.address,
MULTISIG_ISM_BYTECODE_HASH,
);
await this.checkBytecode(
chain,
'InterchainGasPaymaster implementation',
contracts.interchainGasPaymaster.addresses.implementation,
INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH,
);
await this.checkBytecode(
chain,
'OverheadIGP',
contracts.defaultIsmInterchainGasPaymaster.address,
OVERHEAD_IGP_BYTECODE_HASH,
(_) =>
// Remove the address of the wrapped ISM from the bytecode
_.replaceAll(
defaultAbiCoder
.encode(
['address'],
[contracts.interchainGasPaymaster.addresses.proxy],
)
.slice(2),
'',
),
);
}
async checkProxiedContracts(chain: Chain): Promise<void> { async checkProxiedContracts(chain: Chain): Promise<void> {
const contracts = this.app.getContracts(chain); const contracts = this.app.getContracts(chain);
await this.checkProxiedContract( await this.checkProxiedContract(

@ -20,6 +20,7 @@ export type EnvironmentConfig<Chain extends ChainName> = ChainMap<
export enum ViolationType { export enum ViolationType {
Owner = 'Owner', Owner = 'Owner',
NotDeployed = 'NotDeployed', NotDeployed = 'NotDeployed',
BytecodeMismatch = 'BytecodeMismatch',
} }
export interface OwnerViolation extends CheckerViolation { export interface OwnerViolation extends CheckerViolation {
@ -30,3 +31,8 @@ export interface OwnerViolation extends CheckerViolation {
export interface NotDeployedViolation extends CheckerViolation { export interface NotDeployedViolation extends CheckerViolation {
type: ViolationType.NotDeployed; type: ViolationType.NotDeployed;
} }
export interface BytecodeMismatchViolation extends CheckerViolation {
type: ViolationType.BytecodeMismatch;
name: string;
}

Loading…
Cancel
Save