feat(governor): Support handling proxyAdmin violation in HyperlaneAppGovernor (#4311)

### Description

- Support handling proxyAdmin violation, where the proxyAdmin of a proxy
contract is not the expected proxyAdmin, `HyperlaneAppGovernor`
- `HyperlaneCoreGovernor` and `ProxiedRouterGovernor` implementation
will use it

### Testing

Manual
pull/4314/head
Mohammed Hussan 3 months ago committed by GitHub
parent d0ce06280f
commit ff0171677d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .changeset/gorgeous-pans-look.md
  2. 30
      typescript/infra/src/govern/HyperlaneAppGovernor.ts
  3. 4
      typescript/infra/src/govern/HyperlaneCoreGovernor.ts
  4. 3
      typescript/infra/src/govern/ProxiedRouterGovernor.ts
  5. 28
      typescript/sdk/src/deploy/HyperlaneAppChecker.ts
  6. 4
      typescript/sdk/src/deploy/types.ts
  7. 2
      typescript/sdk/src/index.ts

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': patch
---
Update ProxyAdminViolation interface to include proxyAdmin and proxy contract fields

@ -11,6 +11,7 @@ import {
InterchainAccount,
OwnableConfig,
OwnerViolation,
ProxyAdminViolation,
} from '@hyperlane-xyz/sdk';
// @ts-ignore
import { canProposeSafeTransactions } from '@hyperlane-xyz/sdk';
@ -432,4 +433,33 @@ export abstract class HyperlaneAppGovernor<
},
};
}
async handleProxyAdminViolation(violation: ProxyAdminViolation) {
const code = await this.checker.multiProvider
.getProvider(violation.chain)
.getCode(violation.expectedProxyAdmin.address);
let call;
if (code !== '0x') {
// admin for proxy is ProxyAdmin contract
call = {
chain: violation.chain,
call: {
to: violation.actual,
data: violation.expectedProxyAdmin.interface.encodeFunctionData(
'changeProxyAdmin',
[violation.proxy.address, violation.expected],
),
value: BigNumber.from(0),
description: `Change proxyAdmin of transparent proxy ${violation.proxy.address} from ${violation.actual} to ${violation.expected}`,
},
};
} else {
throw new Error(
`Admin for proxy ${violation.proxy.address} is not a ProxyAdmin contract`,
);
}
return call;
}
}

@ -11,6 +11,7 @@ import {
MailboxViolation,
MailboxViolationType,
OwnerViolation,
ProxyAdminViolation,
ViolationType,
} from '@hyperlane-xyz/sdk';
@ -78,6 +79,9 @@ export class HyperlaneCoreGovernor extends HyperlaneAppGovernor<
console.warn('Ignoring ValidatorAnnounce violation');
return undefined;
}
case ViolationType.ProxyAdmin: {
return this.handleProxyAdminViolation(violation as ProxyAdminViolation);
}
default:
throw new Error(`Unsupported violation type ${violation.type}`);
}

@ -5,6 +5,7 @@ import {
ConnectionClientViolation,
ConnectionClientViolationType,
OwnerViolation,
ProxyAdminViolation,
RouterApp,
RouterConfig,
RouterViolation,
@ -26,6 +27,8 @@ export class ProxiedRouterGovernor<
return this.handleEnrolledRouterViolation(violation as RouterViolation);
case ViolationType.Owner:
return this.handleOwnerViolation(violation as OwnerViolation);
case ViolationType.ProxyAdmin:
return this.handleProxyAdminViolation(violation as ProxyAdminViolation);
default:
throw new Error(
`Unsupported violation type ${violation.type}: ${JSON.stringify(

@ -1,6 +1,10 @@
import { utils } from 'ethers';
import { Ownable, TimelockController__factory } from '@hyperlane-xyz/core';
import {
Ownable,
ProxyAdmin__factory,
TimelockController__factory,
} from '@hyperlane-xyz/core';
import {
Address,
ProtocolType,
@ -79,9 +83,9 @@ export abstract class HyperlaneAppChecker<
chain: ChainName,
proxyAdminAddress?: Address,
): Promise<void> {
const expectedAdmin =
const expectedProxyAdminAddress =
proxyAdminAddress ?? this.app.getContracts(chain).proxyAdmin.address;
if (!expectedAdmin) {
if (!expectedProxyAdminAddress) {
throw new Error(
`Checking proxied contracts for ${chain} with no admin provided`,
);
@ -89,18 +93,28 @@ export abstract class HyperlaneAppChecker<
const provider = this.multiProvider.getProvider(chain);
const contracts = this.app.getContracts(chain);
const expectedProxyAdminContract = ProxyAdmin__factory.connect(
expectedProxyAdminAddress,
provider,
);
await promiseObjAll(
objMap(contracts, async (name, contract) => {
if (await isProxy(provider, contract.address)) {
// Check the ProxiedContract's admin matches expectation
const actualAdmin = await proxyAdmin(provider, contract.address);
if (!eqAddress(actualAdmin, expectedAdmin)) {
const actualProxyAdminAddress = await proxyAdmin(
provider,
contract.address,
);
if (!eqAddress(actualProxyAdminAddress, expectedProxyAdminAddress)) {
this.addViolation({
type: ViolationType.ProxyAdmin,
chain,
name,
expected: expectedAdmin,
actual: actualAdmin,
expected: expectedProxyAdminAddress,
actual: actualProxyAdminAddress,
expectedProxyAdmin: expectedProxyAdminContract,
proxy: contract,
} as ProxyAdminViolation);
}
}

@ -4,7 +4,9 @@ import { z } from 'zod';
import type {
AccessControl,
Ownable,
ProxyAdmin,
TimelockController,
TransparentUpgradeableProxy,
} from '@hyperlane-xyz/core';
import { Address } from '@hyperlane-xyz/utils';
@ -41,6 +43,8 @@ export interface OwnerViolation extends CheckerViolation {
export interface ProxyAdminViolation extends CheckerViolation {
type: ViolationType.ProxyAdmin;
expectedProxyAdmin: ProxyAdmin;
proxy: TransparentUpgradeableProxy;
name: string;
}

@ -97,6 +97,7 @@ export {
CheckerViolation,
OwnableConfig,
OwnerViolation,
ProxyAdminViolation,
ViolationType,
} from './deploy/types.js';
export { ContractVerifier } from './deploy/verify/ContractVerifier.js';
@ -517,3 +518,4 @@ export {
ProxyFactoryFactoriesAddresses,
} from './deploy/schemas.js';
export { AnnotatedEV5Transaction } from './providers/ProviderType.js';
export { proxyAdmin } from './deploy/proxy.js';

Loading…
Cancel
Save