Add InvariantChecker (#36)

* Refactor checks

* comments

* Fix build

* Get running

* cleanup

* comments

* suggestion

* Update typescript/optics-deploy/src/core/checks.ts

Co-authored-by: Nam Chu Hoai <nambrot@googlemail.com>

* Comments

* Assert at the right spot

* comments

* Fix build

Co-authored-by: Nam Chu Hoai <nambrot@googlemail.com>
pull/45/head
Asa Oines 3 years ago committed by GitHub
parent 9961cf0d61
commit a48e4eab15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 20
      typescript/optics-deploy/scripts/dev/check-deploy.ts
  3. 2
      typescript/optics-deploy/scripts/dev/upgrade-replica.ts
  4. 121
      typescript/optics-deploy/src/bridge/checks.ts
  5. 13
      typescript/optics-deploy/src/bridge/index.ts
  6. 196
      typescript/optics-deploy/src/checks.ts
  7. 383
      typescript/optics-deploy/src/core/checks.ts
  8. 22
      typescript/optics-deploy/src/core/index.ts
  9. 50
      typescript/optics-deploy/src/core/upgrade.ts
  10. 13
      typescript/optics-deploy/src/verification/readDeployOutput.ts

3
.gitignore vendored

@ -5,6 +5,7 @@ typescript/optics-deploy/.env
**/.env
**/tsconfig.tsbuildinfo
**/tmp.*
**/*.swp
rust/vendor/
rust/tmp_db
@ -16,4 +17,4 @@ typescript/*/.env
typescript/*/node_modules
typescript/**/tsconfig.tsbuildinfo
**/**/tsconfig.tsbuildinfo
typescript/optics-provider/src/tmp.ts
typescript/optics-provider/src/tmp.ts

@ -1,11 +1,7 @@
import * as alfajores from '../../config/testnets/alfajores';
import { InvariantViolationCollector } from '../../src/checks';
import { checkCoreDeploys } from '../../src/core/checks';
import { CoreInvariantChecker } from '../../src/core/checks';
import { configPath, networks } from './agentConfig';
import { makeCoreDeploys } from '../../src/core/CoreDeploy';
const governorDomain = alfajores.chain.domain;
const coreDeploys = makeCoreDeploys(
configPath,
networks,
@ -14,17 +10,9 @@ const coreDeploys = makeCoreDeploys(
);
async function check() {
const invariantViolationCollector = new InvariantViolationCollector();
await checkCoreDeploys(
coreDeploys,
governorDomain,
invariantViolationCollector.handleViolation,
);
if (invariantViolationCollector.violations.length > 0) {
console.error(`Invariant violations were found`);
console.log(invariantViolationCollector.violations);
}
const checker = new CoreInvariantChecker(coreDeploys)
await checker.checkDeploys()
checker.expectEmpty()
}
check().then(console.log).catch(console.error);

@ -21,7 +21,7 @@ async function main() {
devCommunity.registerSigner('alfajores', new ethers.Wallet(process.env.ALFAJORES_DEPLOYER_KEY!))
const upgrader = new ImplementationUpgrader(deploys, devCommunity);
await upgrader.getInvariantViolations();
await upgrader.getViolations();
upgrader.expectViolations(['Replica'], [5]);
const batch = await upgrader.createCallBatch()

@ -1,70 +1,79 @@
import { expect } from 'chai';
import { BridgeDeploy as Deploy } from './BridgeDeploy';
import { BridgeDeploy } from './BridgeDeploy';
import TestBridgeDeploy from './TestBridgeDeploy';
import { assertBeaconProxy, checkVerificationInput } from '../checks';
import { VerificationInput, InvariantChecker } from '../checks';
import { BeaconProxy } from '../proxyUtils';
const emptyAddr = '0x' + '00'.repeat(32);
export async function checkBridgeDeploy(
deploy: Deploy | TestBridgeDeploy,
remotes: number[],
) {
assertBeaconProxy(deploy.contracts.bridgeToken!);
assertBeaconProxy(deploy.contracts.bridgeRouter!);
type AnyBridgeDeploy = BridgeDeploy | TestBridgeDeploy;
if (deploy.config.weth) {
expect(deploy.contracts.ethHelper).to.not.be.undefined;
} else {
expect(deploy.contracts.ethHelper).to.be.undefined;
export class BridgeInvariantChecker extends InvariantChecker<AnyBridgeDeploy> {
constructor(deploys: AnyBridgeDeploy[]) {
super(deploys)
}
const bridgeRouter = deploy.contracts.bridgeRouter?.proxy!;
await Promise.all(
remotes.map(async (remoteDomain) => {
const registeredRouter = await bridgeRouter.remotes(remoteDomain);
expect(registeredRouter).to.not.equal(emptyAddr);
}),
);
async checkDeploy(deploy: AnyBridgeDeploy): Promise<void> {
await this.checkBeaconProxies(deploy)
await this.checkBridgeRouter(deploy)
this.checkEthHelper(deploy)
this.checkVerificationInputs(deploy)
}
expect(await bridgeRouter.owner()).to.equal(
deploy.coreContractAddresses.governance.proxy,
);
async checkBeaconProxies(deploy: AnyBridgeDeploy): Promise<void> {
await this.checkBeaconProxyImplementation(
deploy.chain.domain,
'BridgeToken',
deploy.contracts.bridgeToken!,
);
await this.checkBeaconProxyImplementation(
deploy.chain.domain,
'BridgeRouter',
deploy.contracts.bridgeRouter!,
);
}
// check verification addresses
checkVerificationInput(
deploy,
'BridgeToken Implementation',
deploy.contracts.bridgeToken?.implementation.address!,
);
checkVerificationInput(
deploy,
'BridgeToken UpgradeBeacon',
deploy.contracts.bridgeToken?.beacon.address!,
);
checkVerificationInput(
deploy,
'BridgeToken Proxy',
deploy.contracts.bridgeToken?.proxy.address!,
);
checkVerificationInput(
deploy,
'BridgeRouter Implementation',
deploy.contracts.bridgeRouter?.implementation.address!,
);
checkVerificationInput(
deploy,
'BridgeRouter UpgradeBeacon',
deploy.contracts.bridgeRouter?.beacon.address!,
);
checkVerificationInput(
deploy,
'BridgeRouter Proxy',
deploy.contracts.bridgeRouter?.proxy.address!,
);
if (deploy.config.weth) {
expect(deploy.verificationInput[6].address).to.equal(
deploy.contracts.ethHelper?.address,
async checkBridgeRouter(deploy: AnyBridgeDeploy): Promise<void> {
const bridgeRouter = deploy.contracts.bridgeRouter?.proxy!;
const domains = this._deploys.map((d: AnyBridgeDeploy) => d.chain.domain)
const remoteDomains = domains.filter((d: number) => d !== deploy.chain.domain)
await Promise.all(
remoteDomains.map(async (remoteDomain) => {
const registeredRouter = await bridgeRouter.remotes(remoteDomain);
expect(registeredRouter).to.not.equal(emptyAddr);
}),
);
expect(await bridgeRouter.owner()).to.equal(
deploy.coreContractAddresses.governance.proxy,
);
}
checkEthHelper(deploy: AnyBridgeDeploy): void {
if (deploy.config.weth) {
expect(deploy.contracts.ethHelper).to.not.be.undefined;
} else {
expect(deploy.contracts.ethHelper).to.be.undefined;
}
}
getVerificationInputs(deploy: AnyBridgeDeploy): VerificationInput[] {
const inputs: VerificationInput[] = []
const addInputsForUpgradableContract = (contract: BeaconProxy<any>, name: string) => {
inputs.push([`${name} Implementation`, contract.implementation])
inputs.push([`${name} UpgradeBeacon`, contract.beacon])
inputs.push([`${name} Proxy`, contract.proxy])
}
expect(deploy.contracts.bridgeToken).to.not.be.undefined;
expect(deploy.contracts.bridgeRouter).to.not.be.undefined;
addInputsForUpgradableContract(deploy.contracts.bridgeToken!, 'BridgeToken')
addInputsForUpgradableContract(deploy.contracts.bridgeRouter!, 'BridgeRouter')
if (deploy.config.weth) {
expect(deploy.contracts.ethHelper).to.not.be.undefined;
inputs.push(['EthHelper', deploy.contracts.ethHelper!])
}
return inputs
}
}

@ -1,5 +1,5 @@
import * as proxyUtils from '../proxyUtils';
import { checkBridgeDeploy } from './checks';
import { BridgeInvariantChecker } from './checks';
import * as xAppContracts from 'optics-ts-interface/dist/optics-xapps';
import { toBytes32 } from '../utils';
import fs from 'fs';
@ -50,14 +50,9 @@ export async function deployBridges(deploys: Deploy[]) {
}),
);
await Promise.all(
deploys.map(async (local) => {
const remotes = deploys
.filter((remote) => remote.chain.domain != local.chain.domain)
.map((remote) => remote.chain.domain);
await checkBridgeDeploy(local, remotes);
}),
);
const checker = new BridgeInvariantChecker(deploys)
await checker.checkDeploys()
checker.expectEmpty()
if (!isTestDeploy) {
// output the Bridge deploy information to a subdirectory

@ -2,102 +2,142 @@ import { expect } from 'chai';
import { Contract, ethers } from 'ethers';
import { Deploy } from './deploy';
import { ProxyNames, BeaconProxy } from './proxyUtils';
import { UpgradeBeaconController } from 'optics-ts-interface/dist/optics-core';
export enum InvariantViolationType {
UpgradeBeacon
export enum ViolationType {
UpgradeBeacon = 'UpgradeBeacon',
VerificationInput = 'VerificationInput',
UpdaterManager = 'UpdaterManager',
HomeUpdater = 'HomeUpdater',
ReplicaUpdater = 'ReplicaUpdater',
}
interface UpgradeBeaconInvariantViolation {
export interface UpgradeBeaconViolation {
domain: number
name: ProxyNames
upgradeBeaconController: UpgradeBeaconController,
type: InvariantViolationType.UpgradeBeacon,
type: ViolationType.UpgradeBeacon,
beaconProxy: BeaconProxy<ethers.Contract>,
expectedImplementationAddress: string
actualImplementationAddress: string
expected: string
actual: string
}
export type InvariantViolation = UpgradeBeaconInvariantViolation
interface VerificationInputViolation {
domain: number
type: ViolationType.VerificationInput,
name: string
address: string
}
export type InvariantViolationHandler = (violation: InvariantViolation) => void
export interface UpdaterManagerViolation {
domain: number
type: ViolationType.UpdaterManager,
expected: string
actual: string
}
export const assertInvariantViolation = (violation: InvariantViolation) => {
switch (violation.type) {
case InvariantViolationType.UpgradeBeacon:
throw new Error(`Expected UpgradeBeacon at address at ${violation.beaconProxy.beacon.address} to point to implementation at ${violation.expectedImplementationAddress}, found ${violation.actualImplementationAddress}`)
break;
default:
break;
}
return violation
export interface HomeUpdaterViolation {
domain: number
type: ViolationType.HomeUpdater,
expected: string
actual: string
}
export class InvariantViolationCollector {
violations: InvariantViolation[];
export interface ReplicaUpdaterViolation {
domain: number
remoteDomain: number
type: ViolationType.ReplicaUpdater,
expected: string
actual: string
}
export type Violation = UpgradeBeaconViolation | VerificationInputViolation | HomeUpdaterViolation | ReplicaUpdaterViolation | UpdaterManagerViolation
export type VerificationInput = [string, Contract];
constructor() {
this.violations = []
export abstract class InvariantChecker<T extends Deploy<any>> {
readonly _deploys: T[]
readonly violations: Violation[];
abstract checkDeploy(deploy: T): Promise<void>;
abstract getVerificationInputs(deploy: T): VerificationInput[]
constructor(deploys: T[]) {
this._deploys = deploys;
this.violations = [];
}
// Declare method this way to retain scope
handleViolation = (v: InvariantViolation) => {
const duplicateIndex = this.violations.findIndex((m: InvariantViolation) =>
m.domain === v.domain &&
m.actualImplementationAddress === v.actualImplementationAddress &&
m.expectedImplementationAddress === v.expectedImplementationAddress
)
if (duplicateIndex === -1)
this.violations.push(v);
async checkDeploys(): Promise<void> {
await Promise.all(this._deploys.map(this.checkDeploy))
}
}
export function checkVerificationInput(
deploy: Deploy<any>,
name: string,
addr: string,
) {
const match = deploy.verificationInput.find(
(contract) => contract.name == name && contract.address === addr
)
expect(match).to.not.be.undefined;
}
addViolation(v: Violation) {
switch (v.type) {
case ViolationType.UpgradeBeacon:
const duplicateIndex = this.violations.findIndex((m: Violation) =>
m.type === ViolationType.UpgradeBeacon &&
m.domain === v.domain &&
m.actual === v.actual &&
m.expected === v.expected
)
if (duplicateIndex === -1) this.violations.push(v);
break;
default:
this.violations.push(v);
break;
}
}
export function assertBeaconProxy(beaconProxy: BeaconProxy<Contract>) {
expect(beaconProxy.beacon).to.not.be.undefined;
expect(beaconProxy.proxy).to.not.be.undefined;
expect(beaconProxy.implementation).to.not.be.undefined;
}
async checkBeaconProxyImplementation(
domain: number,
name: ProxyNames,
beaconProxy: BeaconProxy<Contract>,
) {
expect(beaconProxy.beacon).to.not.be.undefined;
expect(beaconProxy.proxy).to.not.be.undefined;
expect(beaconProxy.implementation).to.not.be.undefined;
export async function checkBeaconProxyImplementation(
domain: number,
name: ProxyNames,
upgradeBeaconController: UpgradeBeaconController,
beaconProxy: BeaconProxy<Contract>,
invariantViolationHandler: InvariantViolationHandler,
) {
assertBeaconProxy(beaconProxy)
// Assert that the implementation is actually set
const provider = beaconProxy.beacon.provider;
const storageValue = await provider.getStorageAt(
beaconProxy.beacon.address,
0,
);
const actualImplementationAddress = ethers.utils.getAddress(
storageValue.slice(26),
);
if (actualImplementationAddress != beaconProxy.implementation.address) {
invariantViolationHandler({
type: InvariantViolationType.UpgradeBeacon,
name,
domain,
upgradeBeaconController,
beaconProxy,
actualImplementationAddress,
expectedImplementationAddress: beaconProxy.implementation.address,
});
// Assert that the implementation is actually set
const provider = beaconProxy.beacon.provider;
const storageValue = await provider.getStorageAt(
beaconProxy.beacon.address,
0,
);
const actual = ethers.utils.getAddress(storageValue.slice(26));
const expected = beaconProxy.implementation.address;
if (actual != expected) {
const violation: UpgradeBeaconViolation = {
domain,
type: ViolationType.UpgradeBeacon,
name,
beaconProxy,
actual,
expected
}
this.addViolation(violation)
}
}
}
checkVerificationInput(deploy: T, name: string, address: string) {
const match = deploy.verificationInput.find(
(contract) => contract.name == name && contract.address === address
)
if (match === undefined) {
const violation: VerificationInputViolation = {
domain: deploy.chain.domain,
type: ViolationType.VerificationInput,
name,
address
}
this.addViolation(violation)
}
}
checkVerificationInputs(deploy: T) {
const inputs = this.getVerificationInputs(deploy)
inputs.map((input) => this.checkVerificationInput(deploy, input[0], input[1].address))
}
expectEmpty(): void {
expect(this.violations).to.be.empty;
}
}

@ -1,223 +1,202 @@
import { expect } from 'chai';
import { BeaconProxy } from '../proxyUtils';
import { CoreDeploy } from './CoreDeploy';
import {
assertInvariantViolation,
checkBeaconProxyImplementation,
checkVerificationInput,
InvariantViolationHandler,
} from '../checks';
import { VerificationInput, ViolationType, HomeUpdaterViolation, ReplicaUpdaterViolation, UpdaterManagerViolation, InvariantChecker } from '../checks';
const emptyAddr = '0x' + '00'.repeat(20);
export async function checkCoreDeploys(
deploys: CoreDeploy[],
governorDomain: number,
invariantViolationHandler: InvariantViolationHandler
) {
const checkDeploy = async (deploy: CoreDeploy) => {
const remoteDomains = deploys.filter(_ => _.chain.domain !== deploy.chain.domain).map(_ => _.chain.domain)
export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
console.info(`Checking core deploy on ${deploy.chain.name}`)
return checkCoreDeploy(deploy, remoteDomains, governorDomain, invariantViolationHandler)
constructor(deploys: CoreDeploy[]) {
super(deploys)
}
await Promise.all(deploys.map(checkDeploy))
}
async checkDeploy(deploy: CoreDeploy): Promise<void> {
this.checkContractsDefined(deploy)
await this.checkBeaconProxies(deploy)
await this.checkHome(deploy)
await this.checkReplicas(deploy)
await this.checkGovernance(deploy)
await this.checkXAppConnectionManager(deploy)
this.checkVerificationInputs(deploy)
}
export async function checkCoreDeploy(
deploy: CoreDeploy,
remoteDomains: number[],
governorDomain: number,
invariantViolationHandler: InvariantViolationHandler = assertInvariantViolation,
) {
// Home upgrade setup contracts are defined
await checkBeaconProxyImplementation(
deploy.chain.domain,
'Home',
deploy.contracts.upgradeBeaconController!,
deploy.contracts.home!,
invariantViolationHandler,
);
// updaterManager is set on Home
const updaterManager = await deploy.contracts.home?.proxy.updaterManager();
expect(updaterManager).to.equal(deploy.contracts.updaterManager?.address);
// GovernanceRouter upgrade setup contracts are defined
await checkBeaconProxyImplementation(
deploy.chain.domain,
'Governance',
deploy.contracts.upgradeBeaconController!,
deploy.contracts.governance!,
invariantViolationHandler,
);
for (const domain of remoteDomains) {
// Replica upgrade setup contracts are defined
await checkBeaconProxyImplementation(
deploy.chain.domain,
'Replica',
deploy.contracts.upgradeBeaconController!,
deploy.contracts.replicas[domain]!,
invariantViolationHandler,
);
// governanceRouter for remote domain is registered
const registeredRouter = await deploy.contracts.governance?.proxy.routers(
domain,
);
expect(registeredRouter).to.not.equal(emptyAddr);
// replica is enrolled in xAppConnectionManager
const enrolledReplica =
await deploy.contracts.xAppConnectionManager?.domainToReplica(domain);
expect(enrolledReplica).to.not.equal(emptyAddr);
//watchers have permission in xAppConnectionManager
/*
await Promise.all(
deploy.config.watchers.map(async (watcher) => {
const watcherPermissions =
await deploy.contracts.xAppConnectionManager?.watcherPermission(
watcher,
domain,
);
expect(watcherPermissions).to.be.true;
}),
);
*/
checkContractsDefined(deploy: CoreDeploy): void {
const contracts = deploy.contracts;
expect(contracts.home).to.not.be.undefined;
expect(contracts.governance).to.not.be.undefined;
expect(contracts.upgradeBeaconController).to.not.be.undefined;
expect(contracts.xAppConnectionManager).to.not.be.undefined;
expect(contracts.updaterManager).to.not.be.undefined;
for (const domain in contracts.replicas) {
expect(contracts.replicas[domain]).to.not.be.undefined;
}
}
if (remoteDomains.length > 0) {
// expect all replicas to have to same implementation and upgradeBeacon
const firstReplica = deploy.contracts.replicas[remoteDomains[0]]!;
const replicaImpl = firstReplica.implementation.address;
const replicaBeacon = firstReplica.beacon.address;
// check every other implementation/beacon matches the first
remoteDomains.slice(1).forEach((remoteDomain) => {
const replica = deploy.contracts.replicas[remoteDomain]!;
const implementation = replica.implementation.address;
const beacon = replica.beacon.address;
expect(implementation).to.equal(replicaImpl);
expect(beacon).to.equal(replicaBeacon);
});
async checkHome(deploy: CoreDeploy): Promise<void> {
// contracts are defined
const home = deploy.contracts.home!.proxy;
// updaterManager is set on Home
const actualManager = await home.updaterManager();
const expectedManager = deploy.contracts.updaterManager!.address;
if (actualManager !== expectedManager) {
const violation: UpdaterManagerViolation = {
domain: deploy.chain.domain,
type: ViolationType.UpdaterManager,
actual: actualManager,
expected: expectedManager,
}
this.addViolation(violation)
}
const actual = await home?.updater()!;
expect(actual).to.not.be.undefined;
const expected = deploy.config.updater;
if (actual !== expected) {
const violation: HomeUpdaterViolation = {
domain: deploy.chain.domain,
type: ViolationType.HomeUpdater,
actual,
expected,
}
this.addViolation(violation)
}
}
// contracts are defined
expect(deploy.contracts.updaterManager).to.not.be.undefined;
expect(deploy.contracts.upgradeBeaconController).to.not.be.undefined;
expect(deploy.contracts.xAppConnectionManager).to.not.be.undefined;
// governor is set on governor chain, empty on others
const gov = await deploy.contracts.governance?.proxy.governor();
const localDomain = await deploy.contracts.home?.proxy.localDomain();
if (governorDomain == localDomain) {
expect(gov).to.not.equal(emptyAddr);
} else {
expect(gov).to.equal(emptyAddr);
async checkReplicas(deploy: CoreDeploy): Promise<void> {
// Check if the Replicas on *remote* domains are set to the updater
// configured on our domain.
const domain = deploy.chain.domain
const addReplicaUpdaterViolations = async (remoteDeploy: CoreDeploy) => {
const replica = remoteDeploy.contracts.replicas[domain];
// Sanity check remote domain.
const actualRemoteDomain = await replica.proxy.remoteDomain();
expect(actualRemoteDomain).to.be.equal(remoteDeploy.chain.domain);
const actual = await replica.proxy.updater();
const expected = deploy.config.updater;
if (actual !== expected) {
const violation: ReplicaUpdaterViolation = {
domain: remoteDeploy.chain.domain,
remoteDomain: domain,
type: ViolationType.ReplicaUpdater,
actual,
expected,
}
this.addViolation(violation)
}
}
const remoteDeploys = this._deploys.filter((d) => d.chain.domain !== domain)
await Promise.all(remoteDeploys.map(addReplicaUpdaterViolations))
// Check that all replicas on this domain share the same implementation and
// UpgradeBeacon.
const replicas = Object.values(deploy.contracts.replicas)
const implementations = replicas.map((r) => r.implementation.address);
const identical = (a: any, b: any) => (a === b) ? a : false;
const upgradeBeacons = replicas.map((r) => r.beacon.address);
expect(implementations.reduce(identical)).to.not.be.false;
expect(upgradeBeacons.reduce(identical)).to.not.be.false;
}
// governor domain is correct
expect(await deploy.contracts.governance?.proxy.governorDomain()).to.equal(
governorDomain,
);
// Home is set on xAppConnectionManager
const xAppManagerHome = await deploy.contracts.xAppConnectionManager?.home();
const homeAddress = deploy.contracts.home?.proxy.address;
expect(xAppManagerHome).to.equal(homeAddress);
// governor has ownership over following contracts
const updaterManagerOwner = await deploy.contracts.updaterManager?.owner();
const xAppManagerOwner =
await deploy.contracts.xAppConnectionManager?.owner();
const beaconOwner = await deploy.contracts.upgradeBeaconController?.owner();
const homeOwner = await deploy.contracts.home?.proxy.owner();
const governorAddr = deploy.contracts.governance?.proxy.address;
expect(updaterManagerOwner).to.equal(governorAddr);
expect(xAppManagerOwner).to.equal(governorAddr);
expect(beaconOwner).to.equal(governorAddr);
expect(homeOwner).to.equal(governorAddr);
// This bit fails when the replicas don't yet have the owner() function.
/*
Object.entries(deploy.contracts.replicas).forEach(async ([domain, replica]) => {
const replicaOwner = await replica.proxy.owner()
expect(replicaOwner).to.equal(governorAddr)
})
*/
checkCoreVerificationInput(deploy, remoteDomains)
}
function checkCoreVerificationInput(
deploy: CoreDeploy,
remoteDomains: number[],
) {
// Checks that verification input is consistent with deployed contracts.
checkVerificationInput(
deploy,
'UpgradeBeaconController',
deploy.contracts.upgradeBeaconController?.address!,
);
checkVerificationInput(
deploy,
'XAppConnectionManager',
deploy.contracts.xAppConnectionManager?.address!,
);
checkVerificationInput(
deploy,
'UpdaterManager',
deploy.contracts.updaterManager?.address!,
);
checkVerificationInput(
deploy,
'Home Implementation',
deploy.contracts.home?.implementation.address!,
);
checkVerificationInput(
deploy,
'Home UpgradeBeacon',
deploy.contracts.home?.beacon.address!,
);
checkVerificationInput(
deploy,
'Home Proxy',
deploy.contracts.home?.proxy.address!,
);
checkVerificationInput(
deploy,
'Governance Implementation',
deploy.contracts.governance?.implementation.address!,
);
checkVerificationInput(
deploy,
'Governance UpgradeBeacon',
deploy.contracts.governance?.beacon.address!,
);
checkVerificationInput(
deploy,
'Governance Proxy',
deploy.contracts.governance?.proxy.address!,
);
if (remoteDomains.length > 0) {
checkVerificationInput(
deploy,
'Replica Implementation',
deploy.contracts.replicas[remoteDomains[0]]?.implementation.address!,
);
checkVerificationInput(
deploy,
'Replica UpgradeBeacon',
deploy.contracts.replicas[remoteDomains[0]]?.beacon.address!,
async checkGovernance(deploy: CoreDeploy): Promise<void> {
expect(deploy.contracts.governance).to.not.be.undefined;
// governanceRouter for each remote domain is registered
const registeredRouters = await Promise.all(
Object.keys(deploy.contracts.replicas)
.map(_ => deploy.contracts.governance?.proxy.routers(_))
)
registeredRouters.map(_ => expect(_).to.not.equal(emptyAddr))
// governor is set on governor chain, empty on others
// TODO: assert all governance routers have the same governor domain
const governorDomain = await deploy.contracts.governance?.proxy.governorDomain()
const gov = await deploy.contracts.governance?.proxy.governor();
const localDomain = await deploy.contracts.home?.proxy.localDomain();
if (governorDomain == localDomain) {
expect(gov).to.not.equal(emptyAddr);
} else {
expect(gov).to.equal(emptyAddr);
}
const owners = [
deploy.contracts.updaterManager?.owner()!,
deploy.contracts.xAppConnectionManager?.owner()!,
deploy.contracts.upgradeBeaconController?.owner()!,
deploy.contracts.home?.proxy.owner()!,
]
Object.values(deploy.contracts.replicas).map(_ => owners.push(_.proxy.owner()))
const expectedOwner = deploy.contracts.governance?.proxy.address;
const actualOwners = await Promise.all(owners)
actualOwners.map(_ => expect(_).to.equal(expectedOwner))
}
async checkXAppConnectionManager(deploy: CoreDeploy): Promise<void> {
expect(deploy.contracts.xAppConnectionManager).to.not.be.undefined;
for (const domain in deploy.contracts.replicas) {
// replica is enrolled in xAppConnectionManager
const enrolledReplica =
await deploy.contracts.xAppConnectionManager?.domainToReplica(domain);
expect(enrolledReplica).to.not.equal(emptyAddr);
//watchers have permission in xAppConnectionManager
await Promise.all(
deploy.config.watchers.map(async (watcher) => {
const watcherPermissions =
await deploy.contracts.xAppConnectionManager?.watcherPermission(
watcher,
domain,
);
expect(watcherPermissions).to.be.true;
}),
);
}
// Home is set on xAppConnectionManager
const xAppManagerHome = await deploy.contracts.xAppConnectionManager?.home();
const homeAddress = deploy.contracts.home?.proxy.address;
expect(xAppManagerHome).to.equal(homeAddress);
}
getVerificationInputs(deploy: CoreDeploy): VerificationInput[] {
const inputs: VerificationInput[] = [];
const contracts = deploy.contracts;
inputs.push(['UpgradeBeaconController', contracts.upgradeBeaconController!])
inputs.push(['XAppConnectionManager', contracts.xAppConnectionManager!])
inputs.push(['UpdaterManager', contracts.updaterManager!])
const addInputsForUpgradableContract = (contract: BeaconProxy<any>, name: string) => {
inputs.push([`${name} Implementation`, contract.implementation])
inputs.push([`${name} UpgradeBeacon`, contract.beacon])
inputs.push([`${name} Proxy`, contract.proxy])
}
addInputsForUpgradableContract(contracts.home!, 'Home')
addInputsForUpgradableContract(contracts.governance!, 'Governance')
for (const domain in contracts.replicas) {
addInputsForUpgradableContract(contracts.replicas[domain], 'Replica')
}
return inputs
}
async checkBeaconProxies(deploy: CoreDeploy): Promise<void> {
const domain = deploy.chain.domain;
const contracts = deploy.contracts;
// Home upgrade setup contracts are defined
await this.checkBeaconProxyImplementation(
domain,
'Home',
contracts.home!
);
const replicaProxies = deploy.verificationInput.filter(
(contract) => contract.name == 'Replica Proxy',
// GovernanceRouter upgrade setup contracts are defined
await this.checkBeaconProxyImplementation(
domain,
'Governance',
contracts.governance!
);
remoteDomains.forEach((domain) => {
const replicaProxy = replicaProxies.find((proxy) => {
return (proxy.address =
deploy.contracts.replicas[domain]?.proxy.address);
});
expect(replicaProxy).to.not.be.undefined;
});
await Promise.all(
Object.values(contracts.replicas).map(
_ => this.checkBeaconProxyImplementation(domain, 'Replica', _)
)
)
}
}

@ -5,7 +5,7 @@ import fs from 'fs';
import * as proxyUtils from '../proxyUtils';
import { CoreDeploy } from './CoreDeploy';
import * as contracts from 'optics-ts-interface/dist/optics-core';
import { checkCoreDeploy } from './checks';
import { CoreInvariantChecker } from './checks';
import { log, warn, toBytes32 } from '../utils';
export async function deployUpgradeBeaconController(deploy: CoreDeploy) {
@ -506,10 +506,9 @@ export async function deployTwoChains(gov: CoreDeploy, non: CoreDeploy) {
await Promise.all([relinquish(gov), relinquish(non)]);
// checks deploys are correct
const govDomain = gov.chain.domain;
const nonDomain = non.chain.domain;
await checkCoreDeploy(gov, [nonDomain], govDomain);
await checkCoreDeploy(non, [govDomain], govDomain);
const checker = new CoreInvariantChecker([gov, non])
await checker.checkDeploys()
checker.expectEmpty()
if (!isTestDeploy) {
writeDeployOutput([gov, non]);
@ -601,16 +600,9 @@ export async function deployNChains(deploys: CoreDeploy[]) {
}
// checks deploys are correct
const govDomain = deploys[0].chain.domain;
for (var i = 0; i < deploys.length; i++) {
const localDomain = deploys[i].chain.domain;
const remoteDomains = deploys
.map((deploy) => deploy.chain.domain)
.filter((domain) => {
return domain != localDomain;
});
await checkCoreDeploy(deploys[i], remoteDomains, govDomain);
}
const checker = new CoreInvariantChecker(deploys)
await checker.checkDeploys()
checker.expectEmpty()
// write config outputs again, should write under a different dir
if (!isTestDeploy) {

@ -2,41 +2,48 @@ import { expect } from 'chai';
import { ProxyNames } from '../proxyUtils';
import { OpticsContext } from 'optics-multi-provider-community';
import { CoreDeploy } from './CoreDeploy';
import { InvariantViolation, InvariantViolationCollector } from '../checks';
import { checkCoreDeploys } from './checks';
import { UpgradeBeaconViolation, Violation, ViolationType } from '../checks';
import { CoreInvariantChecker } from './checks';
import { Call, CallBatch } from 'optics-multi-provider-community/dist/optics/govern';
export class ImplementationUpgrader {
private _deploys: CoreDeploy[];
private _context: OpticsContext;
private _violations: InvariantViolation[];
private _checker: CoreInvariantChecker;
private _violations: UpgradeBeaconViolation[];
private _checked: boolean;
constructor(deploys: CoreDeploy[], context: OpticsContext) {
this._deploys = deploys;
this._context = context;
this._violations = [];
this._checker = new CoreInvariantChecker(this._deploys)
this._violations = []
this._checked = false;
}
async getInvariantViolations(): Promise<void> {
const governorDomain = await this._context.governorDomain()
const invariantViolationCollector = new InvariantViolationCollector()
await checkCoreDeploys(
this._deploys,
governorDomain,
invariantViolationCollector.handleViolation,
);
this._violations = invariantViolationCollector.violations;
async getViolations(): Promise<void> {
await this._checker.checkDeploys()
const other: Violation[] = []
for (const v of this._checker.violations) {
switch (v.type) {
case ViolationType.UpgradeBeacon:
this._violations.push(v)
break;
default:
other.push(v)
break;
}
}
expect(other).to.be.empty;
}
expectViolations(names: ProxyNames[], count: number[]) {
expect(names).to.have.lengthOf(count.length);
names.forEach((name: ProxyNames, i: number) => {
const matches = this._violations.filter((v: InvariantViolation) => v.name === name);
const matches = this._violations.filter((v) => v.name === name);
expect(matches).to.have.lengthOf(count[i]);
})
const unmatched = this._violations.filter((v: InvariantViolation) => names.indexOf(v.name) === -1);
const unmatched = this._violations.filter((v) => names.indexOf(v.name) === -1);
expect(unmatched).to.be.empty;
this._checked = true;
}
@ -46,17 +53,20 @@ export class ImplementationUpgrader {
throw new Error('Must check invariants match expectation');
const governorCore = await this._context.governorCore()
const governanceMessages = await governorCore.newGovernanceBatch()
const populate = this._violations.map(async (violation) => {
const upgrade = await violation.upgradeBeaconController.populateTransaction.upgrade(
for (const violation of this._violations) {
const deploys = this._deploys.filter((d) => d.chain.domain == violation.domain)
expect(deploys).to.have.lengthOf(1);
const ubc = deploys[0].contracts.upgradeBeaconController;
expect(ubc).to.not.be.undefined;
const upgrade = await ubc!.populateTransaction.upgrade(
violation.beaconProxy.beacon.address,
violation.expectedImplementationAddress
violation.expected
);
if (upgrade.to === undefined) {
throw new Error('Missing "to" field in populated transaction')
}
governanceMessages.push(violation.domain, upgrade as Call)
})
await Promise.all(populate);
}
return governanceMessages;
}
}

@ -137,20 +137,15 @@ export function parseFileFromDeploy(
fileSuffix: string,
): any {
const targetFileName = `${network}_${fileSuffix}.json`;
const filePath = `${path}/${targetFileName}`
const file = fs
.readdirSync(path, { withFileTypes: true })
.find((dirEntry: fs.Dirent) => dirEntry.name == targetFileName);
if (!file) {
if (!fs.existsSync(filePath)) {
throw new Error(
`No ${fileSuffix} files found for ${network} at ${path}/${targetFileName}`,
`No ${fileSuffix} files found for ${network} at ${filePath}`
);
}
const fileString: string = fs
.readFileSync(`${path}/${targetFileName}`)
.toString();
const fileString: string = fs.readFileSync(filePath).toString();
return JSON.parse(fileString);
}

Loading…
Cancel
Save