Fix bytecode hash checking for v3 (#2824)

### Description

PR adjusts the bytecode checking for V3 mailbox's

### Drive-by changes

Adds a violation when a contract is not proxied, but be expected to be


### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure
implications, e.g. changes that would prohibit deploying older commits
using this infra tooling?

No
-->

### Testing

<!--
What kind of testing have these changes undergone?

Manual
-->
typechain11
Nam Chu Hoai 1 year ago committed by GitHub
parent 671e544467
commit bbc024abd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 40
      .github/workflows/node.yml
  2. 8
      typescript/infra/config/environments/mainnet3/core.ts
  3. 2
      typescript/infra/config/environments/mainnet3/owners.ts
  4. 18
      typescript/infra/config/routingIsm.ts
  5. 4
      typescript/infra/fork.sh
  6. 10
      typescript/sdk/src/consts/bytecode.ts
  7. 87
      typescript/sdk/src/core/HyperlaneCoreChecker.ts
  8. 2
      typescript/sdk/src/core/types.ts
  9. 28
      typescript/sdk/src/gas/HyperlaneIgpChecker.ts
  10. 43
      typescript/sdk/src/router/HyperlaneRouterChecker.ts

@ -115,26 +115,26 @@ jobs:
- name: infra
run: yarn workspace @hyperlane-xyz/infra run test
# test-env:
# runs-on: ubuntu-latest
# needs: [yarn-build]
# strategy:
# matrix:
# environment: [testnet4, mainnet3]
# module: [ism, core, igp, ica, helloworld]
# steps:
# - uses: actions/checkout@v3
# - uses: actions/cache@v3
# with:
# path: ./*
# key: ${{ github.sha }}
# - name: Install Foundry
# uses: onbjerg/foundry-toolchain@v1
# - name: Test ${{ matrix.environment }} ${{ matrix.module }} deployment (check, deploy, govern, check again)
# run: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }}
test-env:
runs-on: ubuntu-latest
needs: [yarn-build]
strategy:
matrix:
environment: [testnet4, mainnet3]
module: [ism, core, helloworld]
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: ./*
key: ${{ github.sha }}
- name: Install Foundry
uses: onbjerg/foundry-toolchain@v1
- name: Test ${{ matrix.environment }} ${{ matrix.module }} deployment (check, deploy, govern, check again)
run: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }}
test-sol:
env:

@ -15,7 +15,7 @@ import { Contexts } from '../../contexts';
import { routingIsm } from '../../routingIsm';
import { igp } from './igp';
import { owners } from './owners';
import { owners, safes } from './owners';
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
const defaultIsm = routingIsm('mainnet3', local, Contexts.Hyperlane);
@ -47,5 +47,11 @@ export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
defaultIsm,
defaultHook,
requiredHook,
ownerOverrides: {
proxyAdmin:
local === 'arbitrum'
? `0xAC98b0cD1B64EA4fe133C6D2EDaf842cE5cF4b01`
: safes[local] ?? owner,
},
};
});

@ -1,7 +1,7 @@
import { ChainMap } from '@hyperlane-xyz/sdk';
import { Address, objMap } from '@hyperlane-xyz/utils';
const safes: ChainMap<Address> = {
export const safes: ChainMap<Address> = {
celo: '0x1DE69322B55AC7E0999F8e7738a1428C8b130E4d',
ethereum: '0x12C5AB61Fe17dF9c65739DBa73dF294708f78d23',
avalanche: '0xDF9B28B76877f1b1B4B8a11526Eb7D8D7C49f4f3',

@ -41,15 +41,15 @@ export const routingIsm = (
local: ChainName,
context: Contexts,
): RoutingIsmConfig | string => {
const aggregationIsms: ChainMap<AggregationIsmConfig> = chains[
environment
].reduce(
(acc, chain) => ({
...acc,
[chain]: aggregationIsm(chain, context),
}),
{},
);
const aggregationIsms: ChainMap<AggregationIsmConfig> = chains[environment]
.filter((_) => _ !== local)
.reduce(
(acc, chain) => ({
...acc,
[chain]: aggregationIsm(chain, context),
}),
{},
);
return {
type: IsmType.ROUTING,

@ -6,12 +6,12 @@ if [ -z "$ENVIRONMENT" ]; then
exit 1
fi
if [ "$ENVIRONMENT" == "testnet3" ]; then
if [ "$ENVIRONMENT" == "testnet4" ]; then
FORK_CHAIN="goerli"
RPC_URL="https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161"
elif [ "$ENVIRONMENT" == "mainnet3" ]; then
FORK_CHAIN="arbitrum"
RPC_URL="https://rpc.ankr.com/arbitrum"
RPC_URL="https://arb1.arbitrum.io/rpc"
else
echo "Unknown environment $ENVIRONMENT"
exit 1

@ -1,8 +1,10 @@
export enum BytecodeHash {
MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH = '0x29b7294ab3ad2e8587e5cce0e2289ce65e12a2ea2f1e7ab34a05e7737616f457',
MAILBOX_WITHOUT_LOCAL_DOMAIN_NONZERO_PAUSE_BYTE_CODE_HASH = '0x4e73e34c0982b93eebb4ac4889e9e4e1611f7c24feacf016c3a13e389f146d9c',
TRANSPARENT_PROXY_BYTECODE_HASH = '0x4dde3d0906b6492bf1d4947f667afe8d53c8899f1d8788cabafd082938dceb2d',
PROXY_ADMIN_BYTECODE_HASH = '0x7c378e9d49408861ca754fe684b9f7d1ea525bddf095ee0463902df701453ba0',
V3_MAILBOX_BYTECODE_HASH = '0x6e853444a6e38bb1d7ac7608b92a70cee83153c891c70ed882b2432134bb23a0', // without optimizer
OPT_V3_MAILBOX_BYTECODE_HASH = '0x7cc944e10fa5597f10265bdac4a808e705711451ee7f117ebf9a97193b796136', // with optimizer
TRANSPARENT_PROXY_BYTECODE_HASH = '0x320bda003dfa31828115be5c01b9f3e7eecaf2532afdb89d2b53559f2e7ab86d',
PROXY_ADMIN_BYTECODE_HASH = '0x13855ae57da3aadecb9259cecece16e1f434b8850fe95531f422e4e262f3f200', // without optimizer
OPT_PROXY_ADMIN_BYTECODE_HASH = '0x30aa3b1506a94c0fe2749af099851623685d9a24a65e2e8b3746c272499979d1', // with optimizer
V2_PROXY_ADMIN_BYTECODE_HASH = '0x7c378e9d49408861ca754fe684b9f7d1ea525bddf095ee0463902df701453ba0', // reused from v2
INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xe995bcd732f4861606036357edb2a4d4c3e9b8d7e599fe548790ac1cf26888f8',
OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xd2c5b00ac2d058117491d581d63c3c4fcf6aeb2667c6cc0c7caed359c9eebea1',
OVERHEAD_IGP_BYTECODE_HASH = '0x3cfed1f24f1e9b28a76d5a8c61696a04f7bc474404b823a2fcc210ea52346252',

@ -1,4 +1,4 @@
import { utils as ethersUtils } from 'ethers';
import { ethers, utils as ethersUtils } from 'ethers';
import { Address, assert, eqAddress } from '@hyperlane-xyz/utils';
@ -55,12 +55,11 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
async checkDomainOwnership(chain: ChainName): Promise<void> {
const config = this.configMap[chain];
let ownableOverrides: Record<string, Address> = {};
const ownableOverrides: Record<string, Address> =
config.ownerOverrides || {};
if (config.upgrade) {
const proxyOwner = await this.app.getContracts(chain).proxyAdmin.owner();
ownableOverrides = {
proxyAdmin: proxyOwner,
};
ownableOverrides.proxyAdmin = proxyOwner;
}
return this.checkOwnership(chain, config.owner, ownableOverrides);
}
@ -103,39 +102,71 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker<
mailbox.address,
);
await this.checkBytecode(
chain,
'Mailbox implementation',
implementation,
[
BytecodeHash.MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH,
BytecodeHash.MAILBOX_WITHOUT_LOCAL_DOMAIN_NONZERO_PAUSE_BYTE_CODE_HASH,
],
(bytecode) =>
// 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
bytecode.replaceAll(
ethersUtils.defaultAbiCoder
.encode(['uint32'], [localDomain])
.slice(2),
(match, offset) => (offset > 8000 ? match : ''),
),
);
if (implementation === ethers.constants.AddressZero) {
const violation: MailboxViolation = {
type: CoreViolationType.Mailbox,
subType: MailboxViolationType.NotProxied,
contract: mailbox,
chain,
actual: implementation,
expected: 'non-zero address',
};
this.addViolation(violation);
} else {
await this.checkBytecode(
chain,
'Mailbox implementation',
implementation,
[
BytecodeHash.V3_MAILBOX_BYTECODE_HASH,
BytecodeHash.OPT_V3_MAILBOX_BYTECODE_HASH,
],
(bytecode) =>
// 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
bytecode
.replaceAll(
ethersUtils.defaultAbiCoder
.encode(['uint32'], [localDomain])
.slice(2),
(match, offset) => (offset > 8000 ? match : ''),
)
// We persist the block number in the bytecode now too, so we have to strip it
.replaceAll(
/(00000000000000000000000000000000000000000000000000000000[a-f0-9]{0,22})81565/g,
(match, _offset) => (match.length % 2 === 0 ? '' : '0'),
)
.replaceAll(
/(0000000000000000000000000000000000000000000000000000[a-f0-9]{0,22})6118123373/g,
(match, _offset) => (match.length % 2 === 0 ? '' : '0'),
)
.replaceAll(
/(f167f00000000000000000000000000000000000000000000000000000[a-f0-9]{0,22})338989898/g,
(match, _offset) => (match.length % 2 === 0 ? '' : '0'),
),
);
}
await this.checkBytecode(
chain,
'Mailbox proxy',
contracts.mailbox.address,
[BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH],
[
BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH,
BytecodeHash.OPT_PROXY_ADMIN_BYTECODE_HASH,
],
);
await this.checkBytecode(
chain,
'ProxyAdmin',
contracts.proxyAdmin.address,
[BytecodeHash.PROXY_ADMIN_BYTECODE_HASH],
[
BytecodeHash.PROXY_ADMIN_BYTECODE_HASH,
BytecodeHash.V2_PROXY_ADMIN_BYTECODE_HASH,
],
);
}

@ -12,6 +12,7 @@ export type CoreConfig = {
defaultHook: HookConfig;
requiredHook: HookConfig;
owner: Address;
ownerOverrides?: Record<string, string>;
remove?: boolean;
upgrade?: UpgradeConfig;
};
@ -24,6 +25,7 @@ export enum CoreViolationType {
export enum MailboxViolationType {
DefaultIsm = 'DefaultIsm',
NotProxied = 'NotProxied',
}
export interface MailboxViolation extends CheckerViolation {

@ -1,4 +1,4 @@
import { BigNumber, ethers, utils as ethersUtils } from 'ethers';
import { BigNumber, ethers } from 'ethers';
import { Address, eqAddress } from '@hyperlane-xyz/utils';
@ -54,25 +54,25 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker<
chain,
'InterchainGasPaymaster implementation',
implementation,
[
BytecodeHash.INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH,
BytecodeHash.OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH,
],
[BytecodeHash.INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH],
);
await this.checkBytecode(
chain,
'InterchainGasPaymaster',
'InterchainGasPaymaster proxy',
contracts.interchainGasPaymaster.address,
[BytecodeHash.OVERHEAD_IGP_BYTECODE_HASH],
[BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH],
(bytecode) =>
// Remove the address of the wrapped IGP from the bytecode
bytecode.replaceAll(
ethersUtils.defaultAbiCoder
.encode(['address'], [contracts.interchainGasPaymaster.address])
.slice(2),
'',
),
bytecode
// We persist the block number in the bytecode now too, so we have to strip it
.replaceAll(
/(00000000000000000000000000000000000000000000000000000000[a-f0-9]{0,22})81565/g,
(match, _offset) => (match.length % 2 === 0 ? '' : '0'),
)
.replaceAll(
/(0000000000000000000000000000000000000000000000000000[a-f0-9]{0,22})6118123373/g,
(match, _offset) => (match.length % 2 === 0 ? '' : '0'),
),
);
}

@ -1,6 +1,8 @@
import { ConnectionClientViolation } from '..';
import { ethers } from 'ethers';
import { zeroAddress } from 'viem';
import { IMailbox__factory } from '@hyperlane-xyz/core';
import { addressToBytes32, eqAddress } from '@hyperlane-xyz/utils';
import { HyperlaneFactories } from '../contracts/types';
@ -48,9 +50,9 @@ export class HyperlaneRouterChecker<
const checkMailboxClientProperty = async (
property: keyof (MailboxClientConfig & OwnableConfig),
actual: string,
violationType: ClientViolationType,
) => {
const actual = await router[property]();
const value = this.configMap[chain][property];
// If the value is an object, it's an ISM config
@ -105,12 +107,45 @@ export class HyperlaneRouterChecker<
}
};
await checkMailboxClientProperty('mailbox', ClientViolationType.Mailbox);
await checkMailboxClientProperty('hook', ClientViolationType.Hook);
const mailboxAddr = await router.mailbox();
await checkMailboxClientProperty(
'interchainSecurityModule',
'mailbox',
mailboxAddr,
ClientViolationType.Mailbox,
);
await checkMailboxClientProperty(
'hook',
await router.hook(),
ClientViolationType.Hook,
);
const mailbox = IMailbox__factory.connect(
mailboxAddr,
this.multiProvider.getProvider(chain),
);
const ism = await mailbox.recipientIsm(router.address);
if (
!this.configMap[chain].interchainSecurityModule ||
this.configMap[chain].interchainSecurityModule === zeroAddress
) {
const defaultIsm = await mailbox.defaultIsm();
if (!eqAddress(defaultIsm, ism)) {
this.addViolation({
chain,
type: ClientViolationType.InterchainSecurityModule,
contract: router,
actual: ism,
expected: defaultIsm,
});
}
} else {
await checkMailboxClientProperty(
'interchainSecurityModule',
ism,
ClientViolationType.InterchainSecurityModule,
);
}
}
async checkEnrolledRouters(chain: ChainName): Promise<void> {

Loading…
Cancel
Save