Add abacus hardhat plugin (#205)

pull/233/head
Asa Oines 3 years ago committed by GitHub
parent 42a7292fe7
commit 6fc6c99d57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3705
      package-lock.json
  2. 1
      package.json
  3. 1
      solidity/abacus-xapps/hardhat.config.ts
  4. 2
      solidity/abacus-xapps/package.json
  5. 13
      solidity/abacus-xapps/test/bridge/EthHelper.test.ts
  6. 17
      solidity/abacus-xapps/test/bridge/bridge.test.ts
  7. 99
      solidity/abacus-xapps/test/bridge/lib/BridgeDeploy.ts
  8. 30
      solidity/abacus-xapps/test/governance/governanceRouter.test.ts
  9. 51
      solidity/abacus-xapps/test/governance/lib/GovernanceDeploy.ts
  10. 1
      typescript/hardhat/.gitignore
  11. 20
      typescript/hardhat/index.ts
  12. 29
      typescript/hardhat/package.json
  13. 192
      typescript/hardhat/src/TestAbacusDeploy.ts
  14. 19
      typescript/hardhat/src/TestDeploy.ts
  15. 30
      typescript/hardhat/src/TestRouterDeploy.ts
  16. 12
      typescript/hardhat/tsconfig.json

3705
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -8,6 +8,7 @@
"workspaces": [
"typescript/utils",
"solidity/abacus-core",
"typescript/hardhat",
"solidity/abacus-xapps",
"typescript/typechain",
"typescript/abacus-sdk",

@ -2,6 +2,7 @@ import 'solidity-coverage';
import '@typechain/hardhat';
import '@nomiclabs/hardhat-waffle';
import 'hardhat-gas-reporter';
import '@abacus-network/hardhat';
/**
* @type import('hardhat/config').HardhatUserConfig

@ -46,6 +46,8 @@
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/abacus-sol": "file:../abacus-core",
"@abacus-network/hardhat": "^0.0.5",
"@abacus-network/utils": "^0.0.5",
"@openzeppelin/contracts": "~3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0"

@ -1,4 +1,4 @@
import { ethers } from 'hardhat';
import { ethers, abacus } from 'hardhat';
import { BytesLike } from 'ethers';
import { expect } from 'chai';
import { utils } from '@abacus-network/utils';
@ -6,16 +6,14 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import * as types from './lib/types';
import { serializeMessage } from './lib/utils';
import { BridgeDeployment } from './lib/BridgeDeployment';
import { AbacusDeployment } from '@abacus-network/abacus-sol/test';
import { BridgeConfig, BridgeDeploy } from './lib/BridgeDeploy';
const localDomain = 1000;
const remoteDomain = 2000;
const domains = [localDomain, remoteDomain];
describe('EthHelper', async () => {
let abacus: AbacusDeployment;
let bridge: BridgeDeployment;
let bridge: BridgeDeploy;
let deployer: SignerWithAddress;
let deployerId: string;
@ -32,8 +30,9 @@ describe('EthHelper', async () => {
[deployer, recipient] = await ethers.getSigners();
deployerId = utils.addressToBytes32(deployer.address);
recipientId = utils.addressToBytes32(recipient.address);
abacus = await AbacusDeployment.fromDomains(domains, deployer);
bridge = await BridgeDeployment.fromAbacusDeployment(abacus, deployer);
await abacus.deploy(domains, deployer);
bridge = new BridgeDeploy(deployer);
await bridge.deploy(abacus);
const tokenId: types.TokenIdentifier = {
domain: localDomain,

@ -1,13 +1,12 @@
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { ethers, abacus } from 'hardhat';
import { BigNumber, BytesLike } from 'ethers';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { AbacusDeployment } from '@abacus-network/abacus-sol/test';
import * as types from './lib/types';
import { utils } from '@abacus-network/utils';
import * as types from './lib/types';
import { serializeMessage } from './lib/utils';
import { BridgeDeployment } from './lib/BridgeDeployment';
import { BridgeConfig, BridgeDeploy } from './lib/BridgeDeploy';
import { BridgeToken, BridgeToken__factory, IERC20 } from '../../typechain';
const localDomain = 1000;
@ -20,8 +19,7 @@ const testTokenId = {
};
describe('BridgeRouter', async () => {
let abacus: AbacusDeployment;
let bridge: BridgeDeployment;
let bridge: BridgeDeploy;
let deployer: SignerWithAddress;
let deployerId: BytesLike;
@ -34,16 +32,17 @@ describe('BridgeRouter', async () => {
// populate deployer signer
[deployer] = await ethers.getSigners();
deployerId = utils.addressToBytes32(deployer.address);
abacus = await AbacusDeployment.fromDomains(domains, deployer);
await abacus.deploy(domains, deployer);
// Enroll ourselves as a inbox so we can send messages directly to the
// local router.
await abacus
.connectionManager(localDomain)
.xAppConnectionManager(localDomain)
.enrollInbox(remoteDomain, deployer.address);
});
beforeEach(async () => {
bridge = await BridgeDeployment.fromAbacusDeployment(abacus, deployer);
bridge = new BridgeDeploy(deployer);
await bridge.deploy(abacus);
// Enroll ourselves as a router so we can send messages directly to the
// local router.
await bridge

@ -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);
}
}

@ -1,15 +1,14 @@
import { ethers } from 'hardhat';
import { ethers, abacus } from 'hardhat';
import { expect } from 'chai';
import { utils } from '@abacus-network/utils';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { core, AbacusDeployment } from '@abacus-network/abacus-sol/test';
import { GovernanceDeployment } from './lib/GovernanceDeployment';
import {
formatSetGovernor,
formatCall,
increaseTimestampBy,
} from './lib/utils';
import { GovernanceConfig, GovernanceDeploy } from './lib/GovernanceDeploy';
import {
TestSet,
TestSet__factory,
@ -30,23 +29,28 @@ describe('GovernanceRouter', async () => {
router: GovernanceRouter,
remote: GovernanceRouter,
testSet: TestSet,
abacus: AbacusDeployment,
governance: GovernanceDeployment;
governance: GovernanceDeploy;
before(async () => {
[governor, recoveryManager] = await ethers.getSigners();
const testSetFactory = new TestSet__factory(governor);
testSet = await testSetFactory.deploy();
abacus = await AbacusDeployment.fromDomains(domains, governor);
await abacus.deploy(domains, governor);
});
beforeEach(async () => {
governance = await GovernanceDeployment.fromAbacusDeployment(
abacus,
governor,
recoveryManager,
);
const config: GovernanceConfig = {
signer: governor,
timelock: recoveryTimelock,
recoveryManager: recoveryManager.address,
governor: {
domain: localDomain,
address: governor.address,
},
};
governance = new GovernanceDeploy(config);
await governance.deploy(abacus);
router = governance.router(localDomain);
remote = governance.router(remoteDomain);
});
@ -134,7 +138,7 @@ describe('GovernanceRouter', async () => {
it('governor can set local xAppConnectionManager', async () => {
expect(await router.xAppConnectionManager()).to.equal(
abacus.connectionManager(localDomain).address,
abacus.xAppConnectionManager(localDomain).address,
);
await router.setXAppConnectionManager(ethers.constants.AddressZero);
expect(await router.xAppConnectionManager()).to.equal(
@ -333,7 +337,7 @@ describe('GovernanceRouter', async () => {
it('recovery manager can set local xAppConnectionManager', async () => {
expect(await router.xAppConnectionManager()).to.equal(
abacus.connectionManager(localDomain).address,
abacus.xAppConnectionManager(localDomain).address,
);
await router.setXAppConnectionManager(ethers.constants.AddressZero);
expect(await router.xAppConnectionManager()).to.equal(

@ -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,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…
Cancel
Save