Add ImplementationDeployer and ImplementationUpgrader (#31)
* Add implmentation deployer and upgrader * Comments * Publish optics-providerpull/40/head
parent
bb5d09448b
commit
fa860d5ae6
@ -0,0 +1,118 @@ |
||||
import * as proxyUtils from '../proxyUtils'; |
||||
import { CoreDeploy } from './CoreDeploy'; |
||||
import { writeDeployOutput } from './index'; |
||||
import * as contracts from '@optics-xyz/ts-interface/dist/optics-core'; |
||||
import { log, warn } from '../utils'; |
||||
|
||||
export class ImplementationDeployer { |
||||
private _deploys: CoreDeploy[]; |
||||
|
||||
constructor(deploys: CoreDeploy[]) { |
||||
this._deploys = deploys; |
||||
} |
||||
|
||||
deployHomeImplementations(): Promise<void> { |
||||
return this._deployImplementations(this._deployHomeImplementation) |
||||
} |
||||
|
||||
deployReplicaImplementations(): Promise<void> { |
||||
return this._deployImplementations(this._deployReplicaImplementation) |
||||
} |
||||
|
||||
writeDeploys(dir: string): void { |
||||
writeDeployOutput(this._deploys, dir); |
||||
} |
||||
|
||||
/** |
||||
* Deploys a Home implementation on the chain of the given deploy and updates |
||||
* the deploy instance with the new contract. |
||||
* |
||||
* @param deploy - The deploy instance |
||||
*/ |
||||
private async _deployHomeImplementation(deploy: CoreDeploy) { |
||||
const isTestDeploy: boolean = deploy.test; |
||||
if (isTestDeploy) warn('deploying test Home'); |
||||
const homeFactory = isTestDeploy |
||||
? contracts.TestHome__factory |
||||
: contracts.Home__factory; |
||||
const implementation = await proxyUtils.deployImplementation<contracts.Home>( |
||||
'Home', |
||||
deploy, |
||||
new homeFactory(deploy.deployer), |
||||
deploy.chain.domain |
||||
); |
||||
|
||||
deploy.contracts.home = proxyUtils.overrideBeaconProxyImplementation<contracts.Home>( |
||||
implementation, |
||||
deploy, |
||||
new homeFactory(deploy.deployer), |
||||
deploy.contracts.home! |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Deploys a Replica implementation on the chain of the given deploy and updates |
||||
* the deploy instance with the new contracts. |
||||
* |
||||
* @param deploy - The deploy instance |
||||
*/ |
||||
private async _deployReplicaImplementation(deploy: CoreDeploy) { |
||||
const isTestDeploy: boolean = deploy.test; |
||||
if (isTestDeploy) warn('deploying test Replica'); |
||||
const replicaFactory = isTestDeploy |
||||
? contracts.TestReplica__factory |
||||
: contracts.Replica__factory; |
||||
const implementation = await proxyUtils.deployImplementation<contracts.Replica>( |
||||
'Replica', |
||||
deploy, |
||||
new replicaFactory(deploy.deployer), |
||||
deploy.chain.domain, |
||||
deploy.config.processGas, |
||||
deploy.config.reserveGas, |
||||
); |
||||
|
||||
for (const domain in deploy.contracts.replicas) { |
||||
deploy.contracts.replicas[domain] = proxyUtils.overrideBeaconProxyImplementation<contracts.Replica>( |
||||
implementation, |
||||
deploy, |
||||
new replicaFactory(deploy.deployer), |
||||
deploy.contracts.replicas[domain] |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Deploy a new contract implementation to each chain in the deploys |
||||
* array. |
||||
* |
||||
* @dev The first chain in the array will be the governing chain |
||||
* |
||||
* @param deploys - An array of chain deploys |
||||
* @param deployImplementation - A function that deploys a new implementation |
||||
*/ |
||||
private async _deployImplementations(deployImplementation: (d: CoreDeploy) => void) { |
||||
if (this._deploys.length == 0) { |
||||
throw new Error('Must pass at least one deploy config'); |
||||
} |
||||
|
||||
// there exists any chain marked test
|
||||
const isTestDeploy: boolean = this._deploys.filter((c) => c.test).length > 0; |
||||
|
||||
log(isTestDeploy, `Beginning ${this._deploys.length} Chain deploy process`); |
||||
log(isTestDeploy, `Deploy env is ${this._deploys[0].config.environment}`); |
||||
log(isTestDeploy, `${this._deploys[0].chain.name} is governing`); |
||||
|
||||
log(isTestDeploy, 'awaiting provider ready'); |
||||
await Promise.all([ |
||||
this._deploys.map(async (deploy) => { |
||||
await deploy.ready(); |
||||
}), |
||||
]); |
||||
log(isTestDeploy, 'done readying'); |
||||
|
||||
// Do it sequentially
|
||||
for (const deploy of this._deploys) { |
||||
await deployImplementation(deploy) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
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 { Call, CallBatch } from 'optics-multi-provider-community/dist/optics/govern'; |
||||
|
||||
export class ImplementationUpgrader { |
||||
private _deploys: CoreDeploy[]; |
||||
private _context: OpticsContext; |
||||
private _violations: InvariantViolation[]; |
||||
private _checked: boolean; |
||||
|
||||
constructor(deploys: CoreDeploy[], context: OpticsContext) { |
||||
this._deploys = deploys; |
||||
this._context = context; |
||||
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; |
||||
} |
||||
|
||||
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); |
||||
expect(matches).to.have.lengthOf(count[i]); |
||||
}) |
||||
const unmatched = this._violations.filter((v: InvariantViolation) => names.indexOf(v.name) === -1); |
||||
expect(unmatched).to.be.empty; |
||||
this._checked = true; |
||||
} |
||||
|
||||
async createCallBatch(): Promise<CallBatch> { |
||||
if (!this._checked) |
||||
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( |
||||
violation.beaconProxy.beacon.address, |
||||
violation.expectedImplementationAddress |
||||
); |
||||
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; |
||||
} |
||||
} |
||||
|
||||
export function expectCalls(batch: CallBatch, domains: number[], count: number[]) { |
||||
expect(domains).to.have.lengthOf(count.length); |
||||
domains.forEach((domain: number, i: number) => { |
||||
expect(batch.calls.get(domain)).to.have.lengthOf(count[i]); |
||||
}) |
||||
} |
@ -1,103 +0,0 @@ |
||||
import * as proxyUtils from './proxyUtils'; |
||||
import { CoreDeploy } from './core/CoreDeploy'; |
||||
import { writeDeployOutput } from './core'; |
||||
import * as contracts from '@optics-xyz/ts-interface/dist/optics-core'; |
||||
import { log, warn } from './utils'; |
||||
|
||||
/** |
||||
* Deploys a Home implementation on the chain of the given deploy and updates |
||||
* the deploy instance with the new contract. |
||||
* |
||||
* @param deploy - The deploy instance |
||||
*/ |
||||
export async function deployHomeImplementation(deploy: CoreDeploy) { |
||||
const isTestDeploy: boolean = deploy.test; |
||||
if (isTestDeploy) warn('deploying test Home'); |
||||
const homeFactory = isTestDeploy |
||||
? contracts.TestHome__factory |
||||
: contracts.Home__factory; |
||||
const implementation = await proxyUtils.deployImplementation<contracts.Home>( |
||||
'Home', |
||||
deploy, |
||||
new homeFactory(deploy.deployer), |
||||
deploy.chain.domain |
||||
); |
||||
|
||||
deploy.contracts.home = proxyUtils.overrideBeaconProxyImplementation<contracts.Home>( |
||||
implementation, |
||||
deploy, |
||||
new homeFactory(deploy.deployer), |
||||
deploy.contracts.home! |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Deploys a Replica implementation on the chain of the given deploy and updates |
||||
* the deploy instance with the new contracts. |
||||
* |
||||
* @param deploy - The deploy instance |
||||
*/ |
||||
export async function deployReplicaImplementation(deploy: CoreDeploy) { |
||||
const isTestDeploy: boolean = deploy.test; |
||||
if (isTestDeploy) warn('deploying test Replica'); |
||||
const replicaFactory = isTestDeploy |
||||
? contracts.TestReplica__factory |
||||
: contracts.Replica__factory; |
||||
const implementation = await proxyUtils.deployImplementation<contracts.Replica>( |
||||
'Replica', |
||||
deploy, |
||||
new replicaFactory(deploy.deployer), |
||||
deploy.chain.domain, |
||||
deploy.config.processGas, |
||||
deploy.config.reserveGas, |
||||
); |
||||
|
||||
for (const domain in deploy.contracts.replicas) { |
||||
deploy.contracts.replicas[domain] = proxyUtils.overrideBeaconProxyImplementation<contracts.Replica>( |
||||
implementation, |
||||
deploy, |
||||
new replicaFactory(deploy.deployer), |
||||
deploy.contracts.replicas[domain] |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Deploy a new contract implementation to each chain in the deploys |
||||
* array. |
||||
* |
||||
* @dev The first chain in the array will be the governing chain |
||||
* |
||||
* @param deploys - An array of chain deploys |
||||
* @param deployImplementation - A function that deploys a new implementation |
||||
*/ |
||||
export async function deployImplementations(dir: string, deploys: CoreDeploy[], deployImplementation: (d: CoreDeploy) => void) { |
||||
if (deploys.length == 0) { |
||||
throw new Error('Must pass at least one deploy config'); |
||||
} |
||||
|
||||
// there exists any chain marked test
|
||||
const isTestDeploy: boolean = deploys.filter((c) => c.test).length > 0; |
||||
|
||||
log(isTestDeploy, `Beginning ${deploys.length} Chain deploy process`); |
||||
log(isTestDeploy, `Deploy env is ${deploys[0].config.environment}`); |
||||
log(isTestDeploy, `${deploys[0].chain.name} is governing`); |
||||
|
||||
log(isTestDeploy, 'awaiting provider ready'); |
||||
await Promise.all([ |
||||
deploys.map(async (deploy) => { |
||||
await deploy.ready(); |
||||
}), |
||||
]); |
||||
log(isTestDeploy, 'done readying'); |
||||
|
||||
// Do it sequentially
|
||||
for (const deploy of deploys) { |
||||
await deployImplementation(deploy) |
||||
} |
||||
|
||||
// write config outputs again, should write under a different dir
|
||||
if (!isTestDeploy) { |
||||
writeDeployOutput(deploys, dir); |
||||
} |
||||
} |
Loading…
Reference in new issue