Add abacus hardhat plugin (#205)
parent
42a7292fe7
commit
6fc6c99d57
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,99 @@ |
||||
import { ethers } from 'ethers'; |
||||
import { types } from '@abacus-network/utils'; |
||||
import { TestAbacusDeploy, TestRouterDeploy } from '@abacus-network/hardhat'; |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
|
||||
import { |
||||
MockWeth__factory, |
||||
MockWeth, |
||||
BridgeToken, |
||||
BridgeToken__factory, |
||||
BridgeRouter, |
||||
BridgeRouter__factory, |
||||
ETHHelper, |
||||
ETHHelper__factory, |
||||
} from '../../../typechain'; |
||||
import { |
||||
UpgradeBeacon__factory, |
||||
UpgradeBeacon, |
||||
} from '@abacus-network/abacus-sol/typechain'; |
||||
|
||||
export type BridgeConfig = SignerWithAddress; |
||||
|
||||
export interface BridgeInstance { |
||||
router: BridgeRouter; |
||||
helper: ETHHelper; |
||||
beacon: UpgradeBeacon; |
||||
token: BridgeToken; |
||||
weth: MockWeth; |
||||
} |
||||
|
||||
export class BridgeDeploy extends TestRouterDeploy< |
||||
BridgeInstance, |
||||
BridgeConfig |
||||
> { |
||||
async deployInstance( |
||||
domain: types.Domain, |
||||
abacus: TestAbacusDeploy, |
||||
): Promise<BridgeInstance> { |
||||
const wethFactory = new MockWeth__factory(this.signer); |
||||
const weth = await wethFactory.deploy(); |
||||
await weth.initialize(); |
||||
|
||||
const tokenFactory = new BridgeToken__factory(this.signer); |
||||
const token = await tokenFactory.deploy(); |
||||
await token.initialize(); |
||||
|
||||
const beaconFactory = new UpgradeBeacon__factory(this.signer); |
||||
const beacon = await beaconFactory.deploy( |
||||
token.address, |
||||
this.signer.address, |
||||
); |
||||
|
||||
const routerFactory = new BridgeRouter__factory(this.signer); |
||||
const router = await routerFactory.deploy(); |
||||
await router.initialize( |
||||
beacon.address, |
||||
abacus.xAppConnectionManager(domain).address, |
||||
); |
||||
|
||||
const helperFactory = new ETHHelper__factory(this.signer); |
||||
const helper = await helperFactory.deploy(weth.address, router.address); |
||||
return { |
||||
beacon, |
||||
router, |
||||
helper, |
||||
token, |
||||
weth, |
||||
}; |
||||
} |
||||
|
||||
get signer(): SignerWithAddress { |
||||
return this.config; |
||||
} |
||||
|
||||
router(domain: types.Domain): BridgeRouter { |
||||
return this.instances[domain].router; |
||||
} |
||||
|
||||
weth(domain: types.Domain): MockWeth { |
||||
return this.instances[domain].weth; |
||||
} |
||||
|
||||
helper(domain: types.Domain): ETHHelper { |
||||
return this.instances[domain].helper; |
||||
} |
||||
|
||||
async bridgeToken( |
||||
local: types.Domain, |
||||
remote: types.Domain, |
||||
address: ethers.BytesLike, |
||||
): Promise<BridgeToken> { |
||||
const router = this.router(local); |
||||
const reprAddr = await router['getLocalAddress(uint32,bytes32)']( |
||||
remote, |
||||
address, |
||||
); |
||||
return BridgeToken__factory.connect(reprAddr, this.signer); |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
import { ethers } from 'ethers'; |
||||
import { types, utils } from '@abacus-network/utils'; |
||||
import { TestAbacusDeploy, TestRouterDeploy } from '@abacus-network/hardhat'; |
||||
|
||||
import { |
||||
GovernanceRouter__factory, |
||||
GovernanceRouter, |
||||
} from '../../../typechain'; |
||||
|
||||
export type Governor = { |
||||
domain: types.Domain; |
||||
address: types.Address; |
||||
}; |
||||
|
||||
export type GovernanceConfig = { |
||||
signer: ethers.Signer; |
||||
timelock: number; |
||||
governor: Governor; |
||||
recoveryManager: types.Address; |
||||
}; |
||||
|
||||
export class GovernanceDeploy extends TestRouterDeploy< |
||||
GovernanceRouter, |
||||
GovernanceConfig |
||||
> { |
||||
async deploy(abacus: TestAbacusDeploy) { |
||||
await super.deploy(abacus); |
||||
for (const domain of this.domains) { |
||||
if (domain == this.config.governor.domain) { |
||||
await this.router(domain).setGovernor(this.config.governor.address); |
||||
} else { |
||||
await this.router(domain).setGovernor(ethers.constants.AddressZero); |
||||
} |
||||
} |
||||
} |
||||
|
||||
async deployInstance( |
||||
domain: types.Domain, |
||||
abacus: TestAbacusDeploy, |
||||
): Promise<GovernanceRouter> { |
||||
const routerFactory = new GovernanceRouter__factory(this.config.signer); |
||||
const router = await routerFactory.deploy(this.config.timelock); |
||||
await router.initialize(abacus.xAppConnectionManager(domain).address); |
||||
await router.transferOwnership(this.config.recoveryManager); |
||||
return router; |
||||
} |
||||
|
||||
router(domain: types.Domain): GovernanceRouter { |
||||
return this.instances[domain]; |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
dist/ |
@ -0,0 +1,20 @@ |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import { extendEnvironment } from 'hardhat/config'; |
||||
import { lazyObject } from "hardhat/plugins"; |
||||
import { TestAbacusDeploy } from './src/TestAbacusDeploy' |
||||
export { TestAbacusDeploy } from './src/TestAbacusDeploy' |
||||
export { TestRouterDeploy } from './src/TestRouterDeploy' |
||||
|
||||
|
||||
import "hardhat/types/runtime"; |
||||
|
||||
declare module 'hardhat/types/runtime' { |
||||
interface HardhatRuntimeEnvironment { |
||||
abacus: TestAbacusDeploy; |
||||
} |
||||
} |
||||
|
||||
// HardhatRuntimeEnvironment
|
||||
extendEnvironment((hre) => { |
||||
hre.abacus = lazyObject(() => new TestAbacusDeploy({ signer: {} })); |
||||
}); |
@ -0,0 +1,29 @@ |
||||
{ |
||||
"prepublish": "npm run build", |
||||
"name": "@abacus-network/hardhat", |
||||
"version": "0.0.5", |
||||
"description": "Abacus hardhat tools", |
||||
"main": "dist/src/index.js", |
||||
"types": "dist/src/index.d.ts", |
||||
"scripts": { |
||||
"build": "tsc", |
||||
"check": "tsc --noEmit", |
||||
"prettier": "prettier --write ./src" |
||||
}, |
||||
"author": "Celo Labs Inc.", |
||||
"license": "MIT OR Apache-2.0", |
||||
"dependencies": { |
||||
"@abacus-network/abacus-sol": "^0.0.0", |
||||
"@abacus-network/utils": "^0.0.5", |
||||
"@nomiclabs/hardhat-ethers": "^2.0.5", |
||||
"@nomiclabs/hardhat-waffle": "^2.0.2", |
||||
"@typechain/hardhat": "^2.0.1", |
||||
"ethereum-waffle": "^3.2.2", |
||||
"ethers": "^5.4.7", |
||||
"hardhat": "^2.8.4", |
||||
"prettier": "^2.3.1", |
||||
"ts-node": "^10.1.0", |
||||
"typechain": "^5.0.0", |
||||
"typescript": "^4.3.2" |
||||
} |
||||
} |
@ -0,0 +1,192 @@ |
||||
import { ethers } from "ethers"; |
||||
import { types } from "@abacus-network/utils"; |
||||
import { |
||||
Outbox, |
||||
Outbox__factory, |
||||
ValidatorManager, |
||||
ValidatorManager__factory, |
||||
UpgradeBeaconController, |
||||
UpgradeBeaconController__factory, |
||||
XAppConnectionManager, |
||||
XAppConnectionManager__factory, |
||||
TestInbox, |
||||
TestInbox__factory, |
||||
} from "@abacus-network/abacus-sol/typechain"; |
||||
import { Validator } from "@abacus-network/abacus-sol/test/lib/core"; |
||||
import { TestDeploy } from "./TestDeploy"; |
||||
|
||||
export type TestAbacusConfig = { |
||||
signer: Record<types.Domain, ethers.Signer>; |
||||
}; |
||||
|
||||
export type TestAbacusInstance = { |
||||
validatorManager: ValidatorManager; |
||||
outbox: Outbox; |
||||
xAppConnectionManager: XAppConnectionManager; |
||||
upgradeBeaconController: UpgradeBeaconController; |
||||
inboxes: Record<types.Domain, TestInbox>; |
||||
}; |
||||
|
||||
const PROCESS_GAS = 850_000; |
||||
const RESERVE_GAS = 15_000; |
||||
|
||||
export class TestAbacusDeploy extends TestDeploy< |
||||
TestAbacusInstance, |
||||
TestAbacusConfig |
||||
> { |
||||
async deploy(domains: types.Domain[], signer: ethers.Signer) { |
||||
// Clear previous deploy to support multiple tests.
|
||||
for (const domain of this.domains) { |
||||
delete this.config.signer[domain]; |
||||
delete this.instances[domain]; |
||||
} |
||||
for (const domain of domains) { |
||||
this.config.signer[domain] = signer; |
||||
} |
||||
for (const domain of domains) { |
||||
this.instances[domain] = await this.deployInstance(domain); |
||||
} |
||||
} |
||||
|
||||
async deployInstance(domain: types.Domain): Promise<TestAbacusInstance> { |
||||
const signer = this.config.signer[domain]; |
||||
const signerAddress = await signer.getAddress(); |
||||
const validatorManagerFactory = new ValidatorManager__factory(signer); |
||||
const validatorManager = await validatorManagerFactory.deploy(); |
||||
await validatorManager.enrollValidator(domain, signerAddress); |
||||
await Promise.all( |
||||
this.remotes(domain).map(async (remote) => |
||||
validatorManager.enrollValidator(remote, signerAddress) |
||||
) |
||||
); |
||||
|
||||
const upgradeBeaconControllerFactory = new UpgradeBeaconController__factory( |
||||
signer |
||||
); |
||||
const upgradeBeaconController = |
||||
await upgradeBeaconControllerFactory.deploy(); |
||||
|
||||
const outboxFactory = new Outbox__factory(signer); |
||||
const outbox = await outboxFactory.deploy(domain); |
||||
await outbox.initialize(validatorManager.address); |
||||
|
||||
const xAppConnectionManagerFactory = new XAppConnectionManager__factory( |
||||
signer |
||||
); |
||||
const xAppConnectionManager = await xAppConnectionManagerFactory.deploy(); |
||||
await xAppConnectionManager.setOutbox(outbox.address); |
||||
|
||||
const inboxFactory = new TestInbox__factory(signer); |
||||
const inboxes: Record<types.Domain, TestInbox> = {}; |
||||
// this.remotes reads this.instances which has not yet been set.
|
||||
const remotes = Object.keys(this.config.signer).map((d) => parseInt(d)); |
||||
const deploys = remotes.map(async (remote) => { |
||||
const inbox = await inboxFactory.deploy(domain, PROCESS_GAS, RESERVE_GAS); |
||||
await inbox.initialize( |
||||
remote, |
||||
validatorManager.address, |
||||
ethers.constants.HashZero, |
||||
0 |
||||
); |
||||
await xAppConnectionManager.enrollInbox(remote, inbox.address); |
||||
inboxes[remote] = inbox; |
||||
}); |
||||
await Promise.all(deploys); |
||||
return { |
||||
outbox, |
||||
xAppConnectionManager, |
||||
validatorManager, |
||||
inboxes, |
||||
upgradeBeaconController, |
||||
}; |
||||
} |
||||
|
||||
async transferOwnership(domain: types.Domain, address: types.Address) { |
||||
await this.outbox(domain).transferOwnership(address); |
||||
await this.upgradeBeaconController(domain).transferOwnership(address); |
||||
await this.xAppConnectionManager(domain).transferOwnership(address); |
||||
await this.validatorManager(domain).transferOwnership(address); |
||||
for (const remote of this.remotes(domain)) { |
||||
await this.inbox(domain, remote).transferOwnership(address); |
||||
} |
||||
} |
||||
|
||||
outbox(domain: types.Domain): Outbox { |
||||
return this.instances[domain].outbox; |
||||
} |
||||
|
||||
upgradeBeaconController(domain: types.Domain): UpgradeBeaconController { |
||||
return this.instances[domain].upgradeBeaconController; |
||||
} |
||||
|
||||
inbox(local: types.Domain, remote: types.Domain): TestInbox { |
||||
return this.instances[local].inboxes[remote]; |
||||
} |
||||
|
||||
xAppConnectionManager(domain: types.Domain): XAppConnectionManager { |
||||
return this.instances[domain].xAppConnectionManager; |
||||
} |
||||
|
||||
validatorManager(domain: types.Domain): ValidatorManager { |
||||
return this.instances[domain].validatorManager; |
||||
} |
||||
|
||||
async processMessages() { |
||||
await Promise.all( |
||||
this.domains.map((d) => this.processMessagesFromDomain(d)) |
||||
); |
||||
} |
||||
|
||||
async processMessagesFromDomain(domain: types.Domain) { |
||||
const outbox = this.outbox(domain); |
||||
const [checkpointedRoot, checkpointedIndex] = |
||||
await outbox.latestCheckpoint(); |
||||
const latestIndex = await outbox.tree(); |
||||
if (latestIndex.eq(checkpointedIndex)) return; |
||||
|
||||
// Find the block number of the last checkpoint submitted on Outbox.
|
||||
const checkpointFilter = outbox.filters.Checkpoint(checkpointedRoot); |
||||
const checkpoints = await outbox.queryFilter(checkpointFilter); |
||||
if (!(checkpoints.length === 0 || checkpoints.length === 1)) |
||||
throw new Error("found multiple checkpoints"); |
||||
const fromBlock = checkpoints.length === 0 ? 0 : checkpoints[0].blockNumber; |
||||
|
||||
await outbox.checkpoint(); |
||||
const [root, index] = await outbox.latestCheckpoint(); |
||||
// If there have been no checkpoints since the last checkpoint, return.
|
||||
if ( |
||||
index.eq(0) || |
||||
(checkpoints.length == 1 && index.eq(checkpoints[0].args.index)) |
||||
) { |
||||
return; |
||||
} |
||||
// Update the Outbox and Inboxes to the latest roots.
|
||||
// This is technically not necessary given that we are not proving against
|
||||
// a root in the TestInbox.
|
||||
const validator = await Validator.fromSigner( |
||||
this.config.signer[domain], |
||||
domain |
||||
); |
||||
const { signature } = await validator.signCheckpoint( |
||||
root, |
||||
index.toNumber() |
||||
); |
||||
|
||||
for (const remote of this.remotes(domain)) { |
||||
const inbox = this.inbox(remote, domain); |
||||
await inbox.checkpoint(root, index, signature); |
||||
} |
||||
|
||||
// Find all messages dispatched on the outbox since the previous checkpoint.
|
||||
const dispatchFilter = outbox.filters.Dispatch(); |
||||
const dispatches = await outbox.queryFilter(dispatchFilter, fromBlock); |
||||
for (const dispatch of dispatches) { |
||||
const destination = dispatch.args.destinationAndNonce.shr(32).toNumber(); |
||||
if (destination !== domain) { |
||||
const inbox = this.inbox(destination, domain); |
||||
await inbox.setMessageProven(dispatch.args.message); |
||||
await inbox.testProcess(dispatch.args.message); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
import { types } from "@abacus-network/utils"; |
||||
|
||||
export class TestDeploy<T, V> { |
||||
public readonly config: V; |
||||
public readonly instances: Record<types.Domain, T>; |
||||
|
||||
constructor(config: V) { |
||||
this.config = config; |
||||
this.instances = {}; |
||||
} |
||||
|
||||
get domains(): types.Domain[] { |
||||
return Object.keys(this.instances).map((d) => parseInt(d)); |
||||
} |
||||
|
||||
remotes(domain: types.Domain): types.Domain[] { |
||||
return this.domains.filter((d) => d !== domain); |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
import { types, utils } from "@abacus-network/utils"; |
||||
import { TestDeploy } from "./TestDeploy"; |
||||
import { TestAbacusDeploy } from "./TestAbacusDeploy"; |
||||
|
||||
export interface Router { |
||||
address: types.Address; |
||||
enrollRemoteRouter(domain: types.Domain, router: types.Address): Promise<any>; |
||||
} |
||||
|
||||
export abstract class TestRouterDeploy<T, V> extends TestDeploy<T, V> { |
||||
async deploy(abacus: TestAbacusDeploy) { |
||||
for (const domain of abacus.domains) { |
||||
this.instances[domain] = await this.deployInstance(domain, abacus); |
||||
} |
||||
for (const local of this.domains) { |
||||
for (const remote of this.remotes(local)) { |
||||
await this.router(local).enrollRemoteRouter( |
||||
remote, |
||||
utils.addressToBytes32(this.router(remote).address) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
|
||||
abstract deployInstance( |
||||
domain: types.Domain, |
||||
abacus: TestAbacusDeploy |
||||
): Promise<T>; |
||||
abstract router(domain: types.Domain): Router; |
||||
} |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"outDir": "./dist/", |
||||
"rootDir": "./" |
||||
}, |
||||
"exclude": ["./node_modules/", "./dist/", "./tmp.ts"], |
||||
"extends": "../tsconfig.package.json", |
||||
"include": [ |
||||
"./index.ts", |
||||
"./src/*.ts" |
||||
] |
||||
} |
Loading…
Reference in new issue