test: migrate tests and deploy process to ts (#400)
* refactor: migrate js lib to ts * refactor: rewrite extendEnvironment in ts * add: sample tests * add: extend hre * refactor: ts deploy and signer type * refactor: devDeploy for tests * document: isTestDeploy param, add common.test.ts * add: common tests * fix: common tests * clean: remove sample script * refactor: remove Signer type and use ethers.Signer * refactor: typescript directory * build: generate typechain * refactor: typescript code (#399) * refactor: optics-tests/optics-deploy * fix: install typechain * fix * fix: ts errors * add: merkle tests (#417) * add: merkle tests * clean: remove commented code * fix: merkle tests * test: add queue tests (#422) * Add queue tests * fix: add await to fix nonce bug * test: home ts (#401) * add: home tests * add: home tests * add: deploy home (broken) * refactor: add num confirmations to config * refactor: clean up around ts tests (#424) - add testChain.ts with utility functions for making test Chain and Deploy - refactor the Optics hardhat extension - improve typing of enums in Optics lib - remove most references to waffle (prefer ethers) - remove isTestDeploy from deployment args in favor of a test? on Deploys * refactor: clean test deploy, home tests passing * refactor: deployOptics * fix: white space * lint: fix white space * refactor: testCase vectors and imports * fix: rust test vector generation * fix: missing await Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> * test: add message test (#423) * test: add message test * fix: replace require with import * fix: import for queue.test.ts * enhance: add type check * fix: import conflicts * test: add upgrade tests (#443) * test: add upgrade tests * fix: missing arg * refactor: clean up code * remove: unused code * refactor: deploy proxy * fix: unsaved code * fix: ts conflicts * add: utils ts (#453) * draft * refactor: remove waffle from home tests * fix: types, remove waffle Co-authored-by: yoduyodu <wang7ong@gmail.com> * test: add XAppConnectionManager tests (#456) * test: add XAppConnectionManager tests * test: initial setup for XAppConnectionManager tests * test: most XAppConnectionManager tests passing * fix: failing test * test: SimpleMessage ts (#468) * test: add cross-chain test utils * test: simpleMessage, mostly passing * fix: prove and process test * test: add initial state test, clean up * test: recoverymanager ts (#473) * test: add recoveryManager initial tests * fix: set recoveryManager in ts deploy * fix: set governor * test: replica ts (#483) * test: add replica tests * debug: fix failing tests * WIP: test: governance router/upgrade ts (#470) * test: add governance router tests * debug: some tests * debug: Gov Router tests * refactor: upgradeUtils * fix: missing await * test: clean up ts tests (#484) * clean: imports * refactor: utils * refactor: remove logs during testing * fix: weird bug, ethers.getSigners messes up describe blocks * delete: solidity/optics-core/js * update: pre-commit script for ts * update: pre-commit and scripts * fix: test, update with main * clean: types * fix: bad recipient handle * add: todo for gov router test * add: add back verify deploy stuff in js for now * bug: fix the governance upgrade test (#490) * fix: gov router upgrade test * feature: use TS in both solidity packages * bug: install deps in typescript dir in tests * feature: enhanced github action install * chore: disable automerge, and move lint before test Co-authored-by: Tong Wang <wang7ong@gmail.com> Co-authored-by: James Prestwich <10149425+prestwich@users.noreply.github.com> Co-authored-by: James Prestwich <james@prestwi.ch>buddies-main-deployment
parent
a38f4f5153
commit
9c9fb87b03
@ -1,8 +1,6 @@ |
||||
node_modules |
||||
test_deploy.env |
||||
config.json |
||||
|
||||
typescript/tmp.ts |
||||
rust/tmp_db |
||||
rust/tmp.env |
||||
tmp.env |
||||
|
@ -0,0 +1,6 @@ |
||||
// SPDX-License-Identifier: MIT OR Apache-2.0 |
||||
pragma solidity >=0.6.11; |
||||
|
||||
contract BadRecipientHandle { |
||||
function handle(uint32, bytes32) external pure {} // solhint-disable-line no-empty-blocks |
||||
} |
@ -1,65 +0,0 @@ |
||||
require('hardhat-gas-reporter'); |
||||
require('solidity-coverage'); |
||||
require('@typechain/hardhat'); |
||||
require('@nomiclabs/hardhat-etherscan'); |
||||
const path = require('path'); |
||||
const envy = require('envy'); |
||||
require('./js'); |
||||
const {verifyLatestDeploy} = require("./js/verifyLatestDeploy"); |
||||
|
||||
/* |
||||
* envy loads variables from .env and |
||||
* creates an object with camelCase properties. |
||||
* Docs: https://www.npmjs.com/package/envy
|
||||
* */ |
||||
let env = {}; |
||||
try { |
||||
env = envy(); |
||||
} catch (e) { |
||||
// if envy doesn't find a .env file, we swallow the error and
|
||||
// return an empty object
|
||||
} |
||||
|
||||
task("verify-latest-deploy", "Verifies the source code of the latest contract deploy").setAction(verifyLatestDeploy); |
||||
|
||||
/** |
||||
* @type import('hardhat/config').HardhatUserConfig |
||||
*/ |
||||
module.exports = { |
||||
solidity: { |
||||
version: '0.7.6', |
||||
settings: { |
||||
optimizer: { |
||||
enabled: true, |
||||
runs: 999999, |
||||
}, |
||||
}, |
||||
}, |
||||
|
||||
gasReporter: { |
||||
currency: 'USD', |
||||
}, |
||||
|
||||
networks: { |
||||
localhost: { |
||||
url: 'http://localhost:8545', |
||||
}, |
||||
goerli: { |
||||
url: 'https://goerli.infura.io/v3/5c456d7844fa40a683e934df60534c60', |
||||
}, |
||||
kovan: { |
||||
url: 'https://kovan.infura.io/v3/5c456d7844fa40a683e934df60534c60', |
||||
}, |
||||
}, |
||||
typechain: { |
||||
outDir: '../../typescript/src/typechain/optics-core', |
||||
target: 'ethers-v5', |
||||
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
|
||||
}, |
||||
mocha: { |
||||
bail: true, |
||||
}, |
||||
etherscan: { |
||||
apiKey: env.etherscanApiKey |
||||
} |
||||
}; |
@ -0,0 +1,59 @@ |
||||
import "hardhat-gas-reporter"; |
||||
import "solidity-coverage"; |
||||
import "@typechain/hardhat"; |
||||
import "@nomiclabs/hardhat-etherscan"; |
||||
|
||||
import { task } from "hardhat/config"; |
||||
|
||||
import * as dotenv from "dotenv"; |
||||
|
||||
dotenv.config(); |
||||
|
||||
import { verifyLatestDeploy } from "./ts/verifyLatestDeploy"; |
||||
|
||||
task( |
||||
"verify-latest-deploy", |
||||
"Verifies the source code of the latest contract deploy" |
||||
).setAction(verifyLatestDeploy); |
||||
|
||||
/** |
||||
* @type import('hardhat/config').HardhatUserConfig |
||||
*/ |
||||
module.exports = { |
||||
solidity: { |
||||
version: "0.7.6", |
||||
settings: { |
||||
optimizer: { |
||||
enabled: true, |
||||
runs: 999999, |
||||
}, |
||||
}, |
||||
}, |
||||
|
||||
gasReporter: { |
||||
currency: "USD", |
||||
}, |
||||
|
||||
networks: { |
||||
localhost: { |
||||
url: "http://localhost:8545", |
||||
}, |
||||
goerli: { |
||||
url: "https://goerli.infura.io/v3/5c456d7844fa40a683e934df60534c60", |
||||
}, |
||||
kovan: { |
||||
url: "https://kovan.infura.io/v3/5c456d7844fa40a683e934df60534c60", |
||||
}, |
||||
}, |
||||
typechain: { |
||||
outDir: "../../typescript/typechain/optics-core", |
||||
target: "ethers-v5", |
||||
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
|
||||
}, |
||||
mocha: { |
||||
bail: true, |
||||
}, |
||||
etherscan: { |
||||
apiKey: process.env.etherscanApiKey, |
||||
}, |
||||
}; |
@ -1,20 +0,0 @@ |
||||
const utils = require('./deployOpticsUtils'); |
||||
|
||||
// TODO: #later explore bundling these deploys into a single transaction to a bespoke DeployHelper contract
|
||||
/* |
||||
* Deploy, initialize, and configure the entire |
||||
* suite of Optics contracts for a single chain |
||||
* specified by the config information |
||||
* |
||||
* @param local - a single ChainConfig for the local chain |
||||
* @param remotes - an array of ChainConfigs for each of the remote chains |
||||
* |
||||
* @return contracts - OpticsContracts type for the suite of Optics contract on this chain |
||||
*/ |
||||
async function deployOptics(local, remotes) { |
||||
return utils.devDeployOptics(local, remotes, false); |
||||
} |
||||
|
||||
module.exports = { |
||||
deployOptics, |
||||
}; |
@ -1,274 +0,0 @@ |
||||
/* |
||||
* Deploy the contracts for an upgradable TestGovernanceRouter contract (Implementation + UpgradeBeacon + Proxy) |
||||
* on the given domain |
||||
* |
||||
* @param localDomain - domain on which the Home contract will be deployed |
||||
* @param controller - ethers Contract of the UpgradeBeaconController contract |
||||
* @param XAappConnectionManagerAddress - address of the XAappConnectionManager contract for the TestGovernanceRouter |
||||
* @param isTestDeploy - boolean, true to deploy the test contract, false otherwise |
||||
* |
||||
* @return contracts - UpgradableContractSetup type for the GovernanceRouter contracts |
||||
*/ |
||||
async function devDeployGovernanceRouter( |
||||
localDomain, |
||||
recoveryTimelock, |
||||
controller, |
||||
xAppConnectionManagerAddress, |
||||
recoveryManagerAddress, |
||||
isTestDeploy, |
||||
) { |
||||
const contractStr = isTestDeploy |
||||
? 'TestGovernanceRouter' |
||||
: 'GovernanceRouter'; |
||||
const { contracts } = await optics.deployUpgradeSetupAndProxy( |
||||
contractStr, |
||||
[localDomain, recoveryTimelock], |
||||
[xAppConnectionManagerAddress, recoveryManagerAddress], |
||||
controller, |
||||
); |
||||
|
||||
return contracts; |
||||
} |
||||
|
||||
/* |
||||
* Deploy the XAppConnectionManager contract |
||||
* |
||||
* @return xAppConnectionManager - ethers Contract for the XAppConnectionManager contract |
||||
*/ |
||||
async function deployXAppConnectionManager() { |
||||
return optics.deployImplementation('XAppConnectionManager'); |
||||
} |
||||
|
||||
/* |
||||
* Deploy the UpdaterManager contract |
||||
* with the given initial updater |
||||
* |
||||
* @param updater - address of the Updater for this chain |
||||
* |
||||
* @return updaterManager - ethers Contract for the UpdaterManager contract |
||||
*/ |
||||
async function deployUpdaterManager(updater) { |
||||
return await optics.deployImplementation('UpdaterManager', [updater]); |
||||
} |
||||
|
||||
/* |
||||
* Deploy the contracts for an upgradable Home contract (Implementation + UpgradeBeacon + Proxy) |
||||
* on the given domain |
||||
* |
||||
* @param localDomain - domain on which the Home contract will be deployed |
||||
* @param controller - ethers Contract of the UpgradeBeaconController contract |
||||
* @param updaterManager - address of the UpdaterManager contract |
||||
* @param isTestDeploy - boolean, true to deploy the test contract, false otherwise |
||||
* |
||||
* @return contracts - UpgradableContractSetup type for the Home contracts |
||||
*/ |
||||
async function devDeployHome( |
||||
localDomain, |
||||
controller, |
||||
updaterManagerAddress, |
||||
isTestDeploy, |
||||
) { |
||||
const contractStr = isTestDeploy ? 'TestHome' : 'Home'; |
||||
const { contracts } = await optics.deployUpgradeSetupAndProxy( |
||||
contractStr, |
||||
[localDomain], |
||||
[updaterManagerAddress], |
||||
controller, |
||||
); |
||||
|
||||
return contracts; |
||||
} |
||||
|
||||
/* |
||||
* Deploy the TestReplica Implementation and UpgradeBeacon |
||||
* which will be used to spawn ReplicaProxies for each remote chain |
||||
* |
||||
* @param localDomain - domain that the TestReplica setup will be deployed on |
||||
* @param controller - ethers Contract for the UpgradeBeaconController |
||||
* @param isTestDeploy - boolean, true to deploy the test contract, false otherwise |
||||
* |
||||
* @return contracts - UpgradeSetup type |
||||
*/ |
||||
async function devDeployReplicaUpgradeSetup( |
||||
localDomain, |
||||
controller, |
||||
isTestDeploy, |
||||
) { |
||||
const contractStr = isTestDeploy ? 'TestReplica' : 'Replica'; |
||||
|
||||
const contracts = await optics.deployUpgradeSetup( |
||||
contractStr, |
||||
[localDomain], |
||||
controller, |
||||
); |
||||
|
||||
return contracts; |
||||
} |
||||
|
||||
/* |
||||
* Deploy the Replica Proxy which points to the given UpgradeBeacon |
||||
* and "listens" to the given remote chain |
||||
* |
||||
* @param upgradeBeaconAddress - address of the Replica Upgrade Beacon contract |
||||
* @param remote - ChainConfig for the remote chain that the Replica will receive updates from |
||||
* @param isTestDeploy - boolean, true to deploy the test contract, false otherwise |
||||
* |
||||
* @return contracts - UpgradableProxy type |
||||
*/ |
||||
async function devDeployReplicaProxy( |
||||
upgradeBeaconAddress, |
||||
remote, |
||||
isTestDeploy, |
||||
) { |
||||
const contractStr = isTestDeploy ? 'TestReplica' : 'Replica'; |
||||
|
||||
// Construct initialize args
|
||||
const { |
||||
domain, |
||||
updater, |
||||
currentRoot, |
||||
nextToProcessIndex, |
||||
optimisticSeconds, |
||||
} = remote; |
||||
const proxyInitializeArgs = [ |
||||
domain, |
||||
updater, |
||||
currentRoot, |
||||
optimisticSeconds, |
||||
nextToProcessIndex, |
||||
]; |
||||
|
||||
// Deploy Proxy Contract and initialize
|
||||
const { proxy, proxyWithImplementation } = |
||||
await optics.deployProxyWithImplementation( |
||||
upgradeBeaconAddress, |
||||
contractStr, |
||||
proxyInitializeArgs, |
||||
'initialize(uint32, address, bytes32, uint256, uint32)', |
||||
); |
||||
|
||||
const contracts = { |
||||
proxy, |
||||
proxyWithImplementation, |
||||
}; |
||||
|
||||
return contracts; |
||||
} |
||||
|
||||
/* |
||||
* Deploy, initialize, and configure the entire |
||||
* suite of Optics contracts for a single chain |
||||
* specified by the config information |
||||
* |
||||
* @param local - a single ChainConfig for the local chain |
||||
* @param remotes - an array of ChainConfigs for each of the remote chains |
||||
* @param isTestDeploy - boolean, true to deploy the test contracts, false otherwise |
||||
* |
||||
* @return contracts - OpticsContracts type for the suite of Optics contract on this chain |
||||
*/ |
||||
async function devDeployOptics(local, remotes, isTestDeploy) { |
||||
const { |
||||
domain, |
||||
recoveryTimelock, |
||||
recoveryManagerAddress, |
||||
updater: localUpdaterAddress, |
||||
} = local; |
||||
|
||||
// Deploy UpgradeBeaconController
|
||||
// Note: initial owner will be the signer that's deploying
|
||||
const upgradeBeaconController = await optics.deployUpgradeBeaconController(); |
||||
|
||||
const updaterManager = await deployUpdaterManager(localUpdaterAddress); |
||||
|
||||
// Deploy XAppConnectionManager
|
||||
// Note: initial owner will be the signer that's deploying
|
||||
const xAppConnectionManager = await deployXAppConnectionManager(); |
||||
|
||||
// Deploy Home and setHome on XAppConnectionManager
|
||||
const home = await devDeployHome( |
||||
domain, |
||||
upgradeBeaconController, |
||||
updaterManager.address, |
||||
isTestDeploy, |
||||
); |
||||
|
||||
await xAppConnectionManager.setHome(home.proxy.address); |
||||
await updaterManager.setHome(home.proxy.address); |
||||
|
||||
// Deploy GovernanceRouter
|
||||
// Note: initial governor will be the signer that's deploying
|
||||
const governanceRouter = await devDeployGovernanceRouter( |
||||
domain, |
||||
recoveryTimelock, |
||||
upgradeBeaconController, |
||||
xAppConnectionManager.address, |
||||
recoveryManagerAddress, |
||||
isTestDeploy, |
||||
); |
||||
|
||||
// Deploy Replica Upgrade Setup
|
||||
const replicaSetup = await devDeployReplicaUpgradeSetup( |
||||
domain, |
||||
upgradeBeaconController, |
||||
isTestDeploy, |
||||
); |
||||
|
||||
// Deploy Replica Proxies and enroll in XAppConnectionManager
|
||||
const replicaProxies = {}; |
||||
for (let remote of remotes) { |
||||
const { domain: remoteDomain, watchers } = remote; |
||||
|
||||
const replica = await devDeployReplicaProxy( |
||||
replicaSetup.upgradeBeacon.address, |
||||
remote, |
||||
isTestDeploy, |
||||
); |
||||
|
||||
replicaProxies[remoteDomain] = replica; |
||||
|
||||
// Enroll Replica Proxy on XAppConnectionManager
|
||||
await xAppConnectionManager.ownerEnrollReplica( |
||||
replica.proxy.address, |
||||
remoteDomain, |
||||
); |
||||
|
||||
// Add watcher permissions for Replica
|
||||
for (let watcher in watchers) { |
||||
await xAppConnectionManager.setWatcherPermission( |
||||
watcher, |
||||
remoteDomain, |
||||
true, |
||||
); |
||||
} |
||||
} |
||||
|
||||
// Delegate permissions to governance router
|
||||
await updaterManager.transferOwnership(governanceRouter.proxy.address); |
||||
await xAppConnectionManager.transferOwnership(governanceRouter.proxy.address); |
||||
await upgradeBeaconController.transferOwnership( |
||||
governanceRouter.proxy.address, |
||||
); |
||||
await home.proxyWithImplementation.transferOwnership( |
||||
governanceRouter.proxy.address, |
||||
); |
||||
|
||||
const contracts = { |
||||
upgradeBeaconController, |
||||
xAppConnectionManager, |
||||
governanceRouter, |
||||
updaterManager, |
||||
home, |
||||
replicaSetup, |
||||
replicaProxies, |
||||
}; |
||||
|
||||
return contracts; |
||||
} |
||||
|
||||
module.exports = { |
||||
devDeployGovernanceRouter, |
||||
devDeployReplicaUpgradeSetup, |
||||
devDeployReplicaProxy, |
||||
devDeployHome, |
||||
devDeployOptics, |
||||
}; |
@ -1,168 +0,0 @@ |
||||
async function deployImplementation(implementationName, deployArgs = []) { |
||||
const Implementation = await ethers.getContractFactory(implementationName); |
||||
const implementation = await Implementation.deploy(...deployArgs); |
||||
return implementation.deployed(); |
||||
} |
||||
|
||||
async function deployUpgradeBeaconController() { |
||||
const UpgradeBeaconController = await ethers.getContractFactory( |
||||
'UpgradeBeaconController', |
||||
); |
||||
const upgradeBeaconController = await UpgradeBeaconController.deploy(); |
||||
return upgradeBeaconController.deployed(); |
||||
} |
||||
|
||||
async function deployUpgradeBeacon( |
||||
implementationAddress, |
||||
upgradeBeaconControllerAddress, |
||||
) { |
||||
const UpgradeBeacon = await ethers.getContractFactory('UpgradeBeacon'); |
||||
const upgradeBeacon = await UpgradeBeacon.deploy( |
||||
implementationAddress, |
||||
upgradeBeaconControllerAddress, |
||||
); |
||||
return upgradeBeacon.deployed(); |
||||
} |
||||
|
||||
async function deployProxy(upgradeBeaconAddress, initializeData = '0x') { |
||||
const Proxy = await ethers.getContractFactory('UpgradeBeaconProxy'); |
||||
const proxy = await Proxy.deploy(upgradeBeaconAddress, initializeData); |
||||
return proxy.deployed(); |
||||
} |
||||
|
||||
async function getInitializeData( |
||||
implementationName, |
||||
initializeArgs, |
||||
initializeIdentifier = 'initialize', |
||||
) { |
||||
if (initializeArgs.length === 0) { |
||||
return '0x'; |
||||
} |
||||
|
||||
const Implementation = await ethers.getContractFactory(implementationName); |
||||
|
||||
const initializeFunction = |
||||
Implementation.interface.getFunction(initializeIdentifier); |
||||
|
||||
const initializeData = Implementation.interface.encodeFunctionData( |
||||
initializeFunction, |
||||
initializeArgs, |
||||
); |
||||
|
||||
return initializeData; |
||||
} |
||||
|
||||
async function deployProxyWithImplementation( |
||||
upgradeBeaconAddress, |
||||
implementationName, |
||||
initializeArgs = [], |
||||
initializeIdentifier = 'initialize', |
||||
) { |
||||
const initializeData = await getInitializeData( |
||||
implementationName, |
||||
initializeArgs, |
||||
initializeIdentifier, |
||||
); |
||||
|
||||
const proxy = await deployProxy(upgradeBeaconAddress, initializeData); |
||||
|
||||
// instantiate proxy with Proxy Contract address + Implementation interface
|
||||
const Implementation = await ethers.getContractFactory(implementationName); |
||||
const [signer] = await ethers.getSigners(); |
||||
const proxyWithImplementation = new ethers.Contract( |
||||
proxy.address, |
||||
Implementation.interface, |
||||
signer, |
||||
); |
||||
return { proxy, proxyWithImplementation }; |
||||
} |
||||
|
||||
async function deployUpgradeSetup( |
||||
implementationName, |
||||
implementationDeployArgs, |
||||
upgradeBeaconController, |
||||
) { |
||||
// Deploy Implementation
|
||||
const implementation = await deployImplementation( |
||||
implementationName, |
||||
implementationDeployArgs, |
||||
); |
||||
|
||||
// Deploy UpgradeBeacon
|
||||
const upgradeBeacon = await deployUpgradeBeacon( |
||||
implementation.address, |
||||
upgradeBeaconController.address, |
||||
); |
||||
|
||||
return { implementation, upgradeBeaconController, upgradeBeacon }; |
||||
} |
||||
|
||||
async function deployUpgradeSetupAndController( |
||||
implementationName, |
||||
implementationDeployArgs, |
||||
) { |
||||
// Deploy UpgradeBeaconController
|
||||
const upgradeBeaconController = await deployUpgradeBeaconController(); |
||||
|
||||
return deployUpgradeSetup( |
||||
implementationName, |
||||
implementationDeployArgs, |
||||
upgradeBeaconController, |
||||
); |
||||
} |
||||
|
||||
async function deployUpgradeSetupAndProxy( |
||||
implementationName, |
||||
constructorArgs = [], |
||||
initializeArgs = [], |
||||
upgradeBeaconController, |
||||
implementationInitializeFunctionIdentifier = 'initialize', |
||||
) { |
||||
let upgradeSetup; |
||||
if (upgradeBeaconController) { |
||||
upgradeSetup = await deployUpgradeSetup( |
||||
implementationName, |
||||
constructorArgs, |
||||
upgradeBeaconController, |
||||
); |
||||
} else { |
||||
upgradeSetup = await deployUpgradeSetupAndController( |
||||
implementationName, |
||||
constructorArgs, |
||||
); |
||||
upgradeBeaconController = upgradeSetup.upgradeBeaconController; |
||||
} |
||||
|
||||
const { implementation, upgradeBeacon } = upgradeSetup; |
||||
|
||||
// Construct initialize data
|
||||
// Deploy Proxy Contract and initialize
|
||||
const { proxy, proxyWithImplementation } = |
||||
await deployProxyWithImplementation( |
||||
upgradeBeacon.address, |
||||
implementationName, |
||||
initializeArgs, |
||||
implementationInitializeFunctionIdentifier, |
||||
); |
||||
|
||||
return { |
||||
contracts: { |
||||
implementation, |
||||
upgradeBeaconController, |
||||
upgradeBeacon, |
||||
proxy, |
||||
proxyWithImplementation, |
||||
}, |
||||
}; |
||||
} |
||||
|
||||
module.exports = { |
||||
deployUpgradeBeaconController, |
||||
deployUpgradeSetup, |
||||
deployImplementation, |
||||
deployUpgradeBeacon, |
||||
deployUpgradeSetupAndProxy, |
||||
deployProxy, |
||||
deployProxyWithImplementation, |
||||
getInitializeData, |
||||
}; |
@ -1,266 +0,0 @@ |
||||
require('@nomiclabs/hardhat-waffle'); |
||||
const { assert } = require('chai'); |
||||
const { extendEnvironment } = require('hardhat/config'); |
||||
|
||||
const { |
||||
deployUpgradeSetup, |
||||
deployUpgradeSetupAndProxy, |
||||
deployImplementation, |
||||
deployUpgradeBeaconController, |
||||
deployProxyWithImplementation, |
||||
getInitializeData, |
||||
} = require('./deployUpgradeSetup'); |
||||
const utils = require('./utils'); |
||||
const { deployOptics } = require('./deployOptics'); |
||||
const HomeAbi = require('../../../rust/optics-ethereum/abis/Home.abi.json'); |
||||
const ReplicaAbi = require('../../../rust/optics-ethereum/abis/Replica.abi.json'); |
||||
|
||||
extendEnvironment((hre) => { |
||||
let { ethers } = hre; |
||||
const State = { |
||||
UNINITIALIZED: 0, |
||||
ACTIVE: 1, |
||||
FAILED: 2, |
||||
}; |
||||
|
||||
const GovernanceMessage = { |
||||
CALL: 1, |
||||
TRANSFERGOVERNOR: 2, |
||||
SETROUTER: 3, |
||||
}; |
||||
|
||||
const MessageStatus = { |
||||
NONE: 0, |
||||
PENDING: 1, |
||||
PROCESSED: 2, |
||||
}; |
||||
|
||||
class Common extends ethers.Contract { |
||||
constructor(address, abi, providerOrSigner) { |
||||
super(address, abi, providerOrSigner); |
||||
} |
||||
|
||||
async submitDoubleUpdate(left, right) { |
||||
if (left.oldRoot !== right.oldRoot) { |
||||
throw new Error('Old roots do not match'); |
||||
} |
||||
return await this.doubleUpdate( |
||||
right.oldRoot, |
||||
[left.newRoot, right.newRoot], |
||||
left.signature, |
||||
right.signature, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Home extends Common { |
||||
constructor(address, providerOrSigner) { |
||||
super(address, HomeAbi, providerOrSigner); |
||||
} |
||||
|
||||
async submitSignedUpdate(update) { |
||||
return await this.update( |
||||
update.oldRoot, |
||||
update.newRoot, |
||||
update.signature, |
||||
); |
||||
} |
||||
|
||||
// Returns list of Dispatch events with given destination and sequence
|
||||
async dispatchByDestinationAndSequence(destination, sequence) { |
||||
const filter = this.filters.Dispatch( |
||||
null, |
||||
optics.destinationAndSequence(destination, sequence), |
||||
); |
||||
|
||||
return await this.queryFilter(filter); |
||||
} |
||||
} |
||||
|
||||
class Replica extends Common { |
||||
constructor(address, providerOrSigner) { |
||||
super(address, ReplicaAbi, providerOrSigner); |
||||
} |
||||
|
||||
async submitSignedUpdate(update) { |
||||
return await this.update( |
||||
update.oldRoot, |
||||
update.newRoot, |
||||
update.signature, |
||||
); |
||||
} |
||||
} |
||||
|
||||
class GovernanceRouter { |
||||
static formatTransferGovernor(newDomain, newAddress) { |
||||
return ethers.utils.solidityPack( |
||||
['bytes1', 'uint32', 'bytes32'], |
||||
[GovernanceMessage.TRANSFERGOVERNOR, newDomain, newAddress], |
||||
); |
||||
} |
||||
|
||||
static formatSetRouter(domain, address) { |
||||
return ethers.utils.solidityPack( |
||||
['bytes1', 'uint32', 'bytes32'], |
||||
[GovernanceMessage.SETROUTER, domain, address], |
||||
); |
||||
} |
||||
|
||||
static formatCalls(callsData) { |
||||
let callBody = '0x'; |
||||
const numCalls = callsData.length; |
||||
|
||||
for (let i = 0; i < numCalls; i++) { |
||||
const { to, data } = callsData[i]; |
||||
const dataLen = utils.getHexStringByteLength(data); |
||||
|
||||
if (!to || !data) { |
||||
throw new Error(`Missing data in Call ${i + 1}: \n ${callsData[i]}`); |
||||
} |
||||
|
||||
let hexBytes = ethers.utils.solidityPack( |
||||
['bytes32', 'uint256', 'bytes'], |
||||
[to, dataLen, data], |
||||
); |
||||
|
||||
// remove 0x before appending
|
||||
callBody += hexBytes.slice(2); |
||||
} |
||||
|
||||
return ethers.utils.solidityPack( |
||||
['bytes1', 'bytes1', 'bytes'], |
||||
[GovernanceMessage.CALL, numCalls, callBody], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class Updater { |
||||
constructor(signer, address, localDomain, disableWarn) { |
||||
if (!disableWarn) { |
||||
throw new Error('Please use `Updater.fromSigner()` to instantiate.'); |
||||
} |
||||
this.localDomain = localDomain ? localDomain : 0; |
||||
this.signer = signer; |
||||
this.address = address; |
||||
} |
||||
|
||||
static async fromSigner(signer, localDomain) { |
||||
return new Updater(signer, await signer.getAddress(), localDomain, true); |
||||
} |
||||
|
||||
domainHash() { |
||||
return optics.domainHash(this.localDomain); |
||||
} |
||||
|
||||
message(oldRoot, newRoot) { |
||||
return ethers.utils.concat([this.domainHash(), oldRoot, newRoot]); |
||||
} |
||||
|
||||
async signUpdate(oldRoot, newRoot) { |
||||
let message = this.message(oldRoot, newRoot); |
||||
let msgHash = ethers.utils.arrayify(ethers.utils.keccak256(message)); |
||||
let signature = await this.signer.signMessage(msgHash); |
||||
return { |
||||
origin: this.localDomain, |
||||
oldRoot, |
||||
newRoot, |
||||
signature, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
const formatMessage = ( |
||||
localDomain, |
||||
senderAddr, |
||||
sequence, |
||||
destinationDomain, |
||||
recipientAddr, |
||||
body, |
||||
) => { |
||||
senderAddr = optics.ethersAddressToBytes32(senderAddr); |
||||
recipientAddr = optics.ethersAddressToBytes32(recipientAddr); |
||||
|
||||
return ethers.utils.solidityPack( |
||||
['uint32', 'bytes32', 'uint32', 'uint32', 'bytes32', 'bytes'], |
||||
[ |
||||
localDomain, |
||||
senderAddr, |
||||
sequence, |
||||
destinationDomain, |
||||
recipientAddr, |
||||
body, |
||||
], |
||||
); |
||||
}; |
||||
|
||||
const messageToLeaf = (message) => { |
||||
return ethers.utils.solidityKeccak256(['bytes'], [message]); |
||||
}; |
||||
|
||||
const ethersAddressToBytes32 = (address) => { |
||||
return ethers.utils |
||||
.hexZeroPad(ethers.utils.hexStripZeros(address), 32) |
||||
.toLowerCase(); |
||||
}; |
||||
|
||||
const destinationAndSequence = (destination, sequence) => { |
||||
assert(destination < Math.pow(2, 32) - 1); |
||||
assert(sequence < Math.pow(2, 32) - 1); |
||||
|
||||
return ethers.BigNumber.from(destination) |
||||
.mul(ethers.BigNumber.from(2).pow(32)) |
||||
.add(ethers.BigNumber.from(sequence)); |
||||
}; |
||||
|
||||
const domainHash = (domain) => { |
||||
return ethers.utils.solidityKeccak256( |
||||
['uint32', 'string'], |
||||
[domain, 'OPTICS'], |
||||
); |
||||
}; |
||||
|
||||
const signedFailureNotification = async (signer, domain, updaterAddress) => { |
||||
const domainHash = optics.domainHash(domain); |
||||
const updaterBytes32 = optics.ethersAddressToBytes32(updaterAddress); |
||||
|
||||
const failureNotification = ethers.utils.solidityPack( |
||||
['bytes32', 'uint32', 'bytes32'], |
||||
[domainHash, domain, updaterBytes32], |
||||
); |
||||
const signature = await signer.signMessage( |
||||
ethers.utils.arrayify(ethers.utils.keccak256(failureNotification)), |
||||
); |
||||
|
||||
return { |
||||
failureNotification: { |
||||
domainHash, |
||||
domain, |
||||
updaterBytes32, |
||||
}, |
||||
signature, |
||||
}; |
||||
}; |
||||
|
||||
hre.optics = { |
||||
State, |
||||
MessageStatus, |
||||
Common, |
||||
Home, |
||||
Replica, |
||||
GovernanceRouter, |
||||
Updater, |
||||
formatMessage, |
||||
messageToLeaf, |
||||
ethersAddressToBytes32, |
||||
destinationAndSequence, |
||||
domainHash, |
||||
signedFailureNotification, |
||||
deployUpgradeSetupAndProxy, |
||||
deployImplementation, |
||||
deployUpgradeBeaconController, |
||||
deployUpgradeSetup, |
||||
deployOptics, |
||||
deployProxyWithImplementation, |
||||
getInitializeData, |
||||
}; |
||||
}); |
@ -1,60 +0,0 @@ |
||||
const fs = require('fs'); |
||||
|
||||
// TODO: deprecate this file & import from ../../typescript/src/readDeployOutput.ts
|
||||
|
||||
function getPathToLatestDeployConfig() { |
||||
const configPath = '../../rust/config'; |
||||
const defaultConfigName = 'default'; |
||||
|
||||
// get the names of all non-default config directories within the relative configPath
|
||||
let configFolders = fs |
||||
.readdirSync(configPath, { withFileTypes: true }) |
||||
.filter( |
||||
(dirEntry) => |
||||
dirEntry.isDirectory() && dirEntry.name != defaultConfigName, |
||||
) |
||||
.map((dirEntry) => dirEntry.name); |
||||
|
||||
// if no non-default config folders are found, return
|
||||
if (configFolders.length == 0) { |
||||
throw new Error('No config folders found'); |
||||
} |
||||
|
||||
// get path to newest generated config folder
|
||||
// (config folder names are UTC strings of the date they were generated - the greatest string is newest folder)
|
||||
const newestConfigFolder = configFolders.reduce((a, b) => { |
||||
return a > b ? a : b; |
||||
}); |
||||
|
||||
return `${configPath}/${newestConfigFolder}`; |
||||
} |
||||
|
||||
/* |
||||
* @notice |
||||
* - Determine the folder with the *most recent* contract deploy output |
||||
* - Get the file in that folder for (network, configTypeSuffix) |
||||
* - Parse contents of file as JSON & return them |
||||
* - Throw if the file is not found |
||||
* @param network target network to parse ("alfajores", "kovan"") |
||||
* @param fileSuffix target file suffix to parse ("config", "contracts", "verification") |
||||
* */ |
||||
function getOutputFromLatestDeploy(network, fileSuffix) { |
||||
const path = getPathToLatestDeployConfig(); |
||||
const targetFileName = `${network}_${fileSuffix}.json`; |
||||
|
||||
const file = fs |
||||
.readdirSync(path, { withFileTypes: true }) |
||||
.find((dirEntry) => dirEntry.name == targetFileName); |
||||
|
||||
if (!file) { |
||||
throw new Error( |
||||
`No verification inputs found for ${network} at ${path}/${targetFileName}`, |
||||
); |
||||
} |
||||
|
||||
return JSON.parse(fs.readFileSync(`${path}/${targetFileName}`)); |
||||
} |
||||
|
||||
module.exports = { |
||||
getOutputFromLatestDeploy, |
||||
}; |
@ -1,38 +0,0 @@ |
||||
/* |
||||
* ChainConfig { |
||||
* domain: int, |
||||
* updater: address, |
||||
* recoveryTimelock: int, |
||||
* recoveryManager: address, // NOTE: this may change if we add a multisig to the deploy setup
|
||||
* currentRoot: bytes32, |
||||
* nextToProcessIndex: int, |
||||
* optimisticSeconds: int, |
||||
* watchers?: [address], |
||||
* }; |
||||
* |
||||
* OpticsContracts { |
||||
* home: UpgradableContractSetup, |
||||
* governanceRouter: UpgradableContractSetup, |
||||
* replicaSetup: UpgradeSetup, |
||||
* replicaProxies: UpgradableProxy[], |
||||
* upgradeBeaconController: ethers Contract, |
||||
* xAppConnectionManager: ethers Contract, |
||||
* updaterManager: ethers Contract, |
||||
* }; |
||||
* |
||||
* UpgradeSetup { |
||||
* implementation: ethers Contract, |
||||
* upgradeBeaconController: ethers Contract, |
||||
* upgradeBeacon: ethers Contract, |
||||
* }; |
||||
* |
||||
* UpgradableProxy { |
||||
* proxy: ethers Contract, |
||||
* proxyWithImplementation: ethers Contract, |
||||
* }; |
||||
* |
||||
* UpgradableContractSetup { |
||||
* ...UpgradeSetup, |
||||
* ...UpgradableProxy, |
||||
* }; |
||||
*/ |
@ -0,0 +1,7 @@ |
||||
set -e |
||||
|
||||
npm run compile |
||||
cd ../../typescript/optics-tests |
||||
npm i |
||||
npm run test |
||||
cd ../.. |
@ -1,63 +0,0 @@ |
||||
const { expect } = require('chai'); |
||||
|
||||
describe('Upgrade', async () => { |
||||
let proxy, upgradeBeacon, upgradeBeaconController; |
||||
const a = 5; |
||||
const b = 10; |
||||
const stateVar = 17; |
||||
|
||||
before(async () => { |
||||
// SETUP CONTRACT SUITE
|
||||
const { contracts } = await optics.deployUpgradeSetupAndProxy( |
||||
'MysteryMathV1', |
||||
); |
||||
|
||||
proxy = contracts.proxyWithImplementation; |
||||
upgradeBeacon = contracts.upgradeBeacon; |
||||
upgradeBeaconController = contracts.upgradeBeaconController; |
||||
|
||||
// Set state of proxy
|
||||
await proxy.setState(stateVar); |
||||
}); |
||||
|
||||
it('Pre-Upgrade returns version 1', async () => { |
||||
const versionResult = await proxy.version(); |
||||
expect(versionResult).to.equal(1); |
||||
}); |
||||
|
||||
it('Pre-Upgrade returns the math from implementation v1', async () => { |
||||
const mathResult = await proxy.doMath(a, b); |
||||
expect(mathResult).to.equal(a + b); |
||||
}); |
||||
|
||||
it('Pre-Upgrade returns the expected state variable', async () => { |
||||
const stateResult = await proxy.getState(); |
||||
expect(stateResult).to.equal(stateVar); |
||||
}); |
||||
|
||||
it('Upgrades without problem', async () => { |
||||
// Deploy Implementation 2
|
||||
const implementation = await optics.deployImplementation('MysteryMathV2'); |
||||
|
||||
// Upgrade to implementation 2
|
||||
await upgradeBeaconController.upgrade( |
||||
upgradeBeacon.address, |
||||
implementation.address, |
||||
); |
||||
}); |
||||
|
||||
it('Post-Upgrade returns version 2', async () => { |
||||
const versionResult = await proxy.version(); |
||||
expect(versionResult).to.equal(2); |
||||
}); |
||||
|
||||
it('Post-Upgrade returns the math from implementation v2', async () => { |
||||
const mathResult = await proxy.doMath(a, b); |
||||
expect(mathResult).to.equal(a * b); |
||||
}); |
||||
|
||||
it('Post-Upgrade preserved the state variable', async () => { |
||||
const stateResult = await proxy.getState(); |
||||
expect(stateResult).to.equal(stateVar); |
||||
}); |
||||
}); |
@ -1,212 +0,0 @@ |
||||
const { waffle, ethers } = require('hardhat'); |
||||
const { provider } = waffle; |
||||
const { expect } = require('chai'); |
||||
const testUtils = require('../utils'); |
||||
const { domainsToTestConfigs } = require('./generateTestChainConfigs'); |
||||
const { |
||||
enqueueUpdateToReplica, |
||||
enqueueMessagesAndUpdateHome, |
||||
formatMessage, |
||||
formatCall, |
||||
} = require('./crossChainTestUtils'); |
||||
const { |
||||
deployMultipleChains, |
||||
getHome, |
||||
getReplica, |
||||
getGovernanceRouter, |
||||
} = require('./deployCrossChainTest'); |
||||
const { |
||||
testCases: proveAndProcessTestCases, |
||||
} = require('../../../../vectors/proveAndProcessTestCases.json'); |
||||
|
||||
/* |
||||
* Deploy the full Optics suite on two chains |
||||
* enqueue messages to Home |
||||
* sign and submit updates to Home |
||||
* relay updates to Replica |
||||
* confirm updates on Replica |
||||
* TODO prove and process messages on Replica |
||||
*/ |
||||
describe('SimpleCrossChainMessage', async () => { |
||||
const domains = [1000, 2000]; |
||||
const homeDomain = domains[0]; |
||||
const replicaDomain = domains[1]; |
||||
const walletProvider = new testUtils.WalletProvider(provider); |
||||
|
||||
let randomSigner, recoveryManager, chainDetails, firstRootEnqueuedToReplica; |
||||
let latestRoot = {}, |
||||
latestUpdate = {}; |
||||
|
||||
before(async () => { |
||||
[randomSigner, recoveryManager] = walletProvider.getWalletsPersistent(2); |
||||
|
||||
// generate TestChainConfigs for the given domains
|
||||
const configs = await domainsToTestConfigs( |
||||
domains, |
||||
recoveryManager.address, |
||||
); |
||||
|
||||
// deploy the entire Optics suite on each chain
|
||||
chainDetails = await deployMultipleChains(configs); |
||||
}); |
||||
|
||||
it('All Homes have correct initial state', async () => { |
||||
// governorHome has 0 updates
|
||||
const governorHome = getHome(chainDetails, homeDomain); |
||||
|
||||
let length = await governorHome.queueLength(); |
||||
expect(length).to.equal(0); |
||||
|
||||
let [suggestedCurrent, suggestedNew] = await governorHome.suggestUpdate(); |
||||
expect(suggestedCurrent).to.equal(ethers.utils.formatBytes32String(0)); |
||||
expect(suggestedNew).to.equal(ethers.utils.formatBytes32String(0)); |
||||
|
||||
// nonGovernorHome has 1 update
|
||||
const nonGovernorHome = getHome(chainDetails, replicaDomain); |
||||
|
||||
length = await nonGovernorHome.queueLength(); |
||||
expect(length).to.equal(1); |
||||
|
||||
[suggestedCurrent, suggestedNew] = await nonGovernorHome.suggestUpdate(); |
||||
const nullRoot = ethers.utils.formatBytes32String(0); |
||||
expect(suggestedCurrent).to.equal(nullRoot); |
||||
expect(suggestedNew).to.not.equal(nullRoot); |
||||
}); |
||||
|
||||
it('All Replicas have empty queue of pending updates', async () => { |
||||
for (let destinationDomain of domains) { |
||||
for (let remoteDomain of domains) { |
||||
if (destinationDomain !== remoteDomain) { |
||||
const replica = getReplica( |
||||
chainDetails, |
||||
destinationDomain, |
||||
remoteDomain, |
||||
); |
||||
|
||||
const length = await replica.queueLength(); |
||||
expect(length).to.equal(0); |
||||
|
||||
const [pending, confirmAt] = await replica.nextPending(); |
||||
expect(pending).to.equal(await replica.current()); |
||||
expect(confirmAt).to.equal(1); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
|
||||
it('Origin Home Accepts one valid update', async () => { |
||||
const messages = ['message'].map((message) => |
||||
formatMessage(message, replicaDomain, randomSigner.address), |
||||
); |
||||
const update = await enqueueMessagesAndUpdateHome( |
||||
chainDetails, |
||||
homeDomain, |
||||
messages, |
||||
); |
||||
|
||||
latestUpdate[homeDomain] = update; |
||||
latestRoot[homeDomain] = update.finalRoot; |
||||
}); |
||||
|
||||
it('Destination Replica Accepts the first update', async () => { |
||||
firstRootEnqueuedToReplica = await enqueueUpdateToReplica( |
||||
chainDetails, |
||||
latestUpdate[homeDomain], |
||||
homeDomain, |
||||
replicaDomain, |
||||
); |
||||
}); |
||||
|
||||
it('Origin Home Accepts an update with several batched messages', async () => { |
||||
const messages = ['message1', 'message2', 'message3'].map((message) => |
||||
formatMessage(message, replicaDomain, randomSigner.address), |
||||
); |
||||
const update = await enqueueMessagesAndUpdateHome( |
||||
chainDetails, |
||||
homeDomain, |
||||
messages, |
||||
); |
||||
|
||||
latestUpdate[homeDomain] = update; |
||||
latestRoot[homeDomain] = update.finalRoot; |
||||
}); |
||||
|
||||
it('Destination Replica Accepts the second update', async () => { |
||||
await enqueueUpdateToReplica( |
||||
chainDetails, |
||||
latestUpdate[homeDomain], |
||||
homeDomain, |
||||
replicaDomain, |
||||
); |
||||
}); |
||||
|
||||
it('Destination Replica shows first update as the next pending', async () => { |
||||
const replica = getReplica(chainDetails, replicaDomain, homeDomain); |
||||
const [pending] = await replica.nextPending(); |
||||
expect(pending).to.equal(firstRootEnqueuedToReplica); |
||||
}); |
||||
|
||||
it('Destination Replica Batch-confirms several ready updates', async () => { |
||||
const replica = getReplica(chainDetails, replicaDomain, homeDomain); |
||||
|
||||
// Increase time enough for both updates to be confirmable
|
||||
const optimisticSeconds = chainDetails[replicaDomain].optimisticSeconds; |
||||
await testUtils.increaseTimestampBy(provider, optimisticSeconds * 2); |
||||
|
||||
// Replica should be able to confirm updates
|
||||
expect(await replica.canConfirm()).to.be.true; |
||||
|
||||
await replica.confirm(); |
||||
|
||||
// after confirming, current root should be equal to the last submitted update
|
||||
const { finalRoot } = latestUpdate[homeDomain]; |
||||
expect(await replica.current()).to.equal(finalRoot); |
||||
}); |
||||
|
||||
it('Proves and processes a message on Replica', async () => { |
||||
// get governance routers
|
||||
const governorRouter = getGovernanceRouter(chainDetails, homeDomain); |
||||
const nonGovernorRouter = getGovernanceRouter(chainDetails, replicaDomain); |
||||
|
||||
const replica = getReplica(chainDetails, replicaDomain, homeDomain); |
||||
const TestRecipient = await optics.deployImplementation('TestRecipient'); |
||||
|
||||
// ensure `processed` has an initial value of false
|
||||
expect(await TestRecipient.processed()).to.be.false; |
||||
|
||||
// create Call message to test recipient that calls `processCall`
|
||||
const arg = true; |
||||
const call = await formatCall(TestRecipient, 'processCall', [arg]); |
||||
const callMessage = optics.GovernanceRouter.formatCalls([call]); |
||||
|
||||
// Create Optics message that is sent from the governor domain and governor
|
||||
// to the nonGovernorRouter on the nonGovernorDomain
|
||||
const sequence = await replica.nextToProcess(); |
||||
const opticsMessage = optics.formatMessage( |
||||
1000, |
||||
governorRouter.address, |
||||
sequence, |
||||
2000, |
||||
nonGovernorRouter.address, |
||||
callMessage, |
||||
); |
||||
|
||||
// get merkle proof
|
||||
const { path, index } = proveAndProcessTestCases[0]; |
||||
const leaf = optics.messageToLeaf(opticsMessage); |
||||
|
||||
// set root
|
||||
const proofRoot = await replica.testBranchRoot(leaf, path, index); |
||||
await replica.setCurrentRoot(proofRoot); |
||||
|
||||
// prove and process message
|
||||
await replica.proveAndProcess(opticsMessage, path, index); |
||||
|
||||
// expect call to have been processed
|
||||
expect(await TestRecipient.processed()).to.be.true; |
||||
expect(await replica.messages(leaf)).to.equal( |
||||
optics.MessageStatus.PROCESSED, |
||||
); |
||||
expect(await replica.nextToProcess()).to.equal(sequence + 1); |
||||
}); |
||||
}); |
@ -1,176 +0,0 @@ |
||||
const { devDeployOptics } = require('../../js/deployOpticsUtils'); |
||||
|
||||
/* |
||||
* Get the Home contract for the given domain |
||||
* |
||||
* @param chainDetails - ChainDetails type |
||||
* @param domain - domain where the Home contract is deployed; localDomain for the Home |
||||
* |
||||
* @return homeContract - ethers contract for interacting with the Home |
||||
*/ |
||||
function getHome(chainDetails, domain) { |
||||
return chainDetails[domain].contracts.home.proxyWithImplementation; |
||||
} |
||||
|
||||
/* |
||||
* Get the Replica contract that's deployed on replicaDomain (localDomain = replicaDomain) |
||||
* that listens to homeDomain (remoteDomain = homeDomain) |
||||
* |
||||
* @param chainDetails - ChainDetails type |
||||
* @param replicaDomain - localDomain for the Replica; domain where the Replica contract is deployed |
||||
* @param homeDomain - remoteDomain for the Replica; domain of the Home contract the Replica "listens" to |
||||
* |
||||
* @return replicaContract - ethers contract for interacting with the Replica |
||||
*/ |
||||
function getReplica(chainDetails, replicaDomain, homeDomain) { |
||||
return chainDetails[replicaDomain].contracts.replicaProxies[homeDomain] |
||||
.proxyWithImplementation; |
||||
} |
||||
|
||||
/* |
||||
* Get the Updater object that can sign updates for the given domain |
||||
* |
||||
* @param chainDetails - ChainDetails type |
||||
* @param domain - domain of the chain for which we want the Updater |
||||
* |
||||
* @return updaterObject - an optics.Updater type |
||||
*/ |
||||
function getUpdaterObject(chainDetails, domain) { |
||||
return chainDetails[domain].updaterObject; |
||||
} |
||||
|
||||
/* |
||||
* Get the GovernanceRouter contract |
||||
* |
||||
* @param chainDetails - ChainDetails type |
||||
* @param domain - the domain |
||||
* @return governanceRouterContract - ethers contract for interacting with the upgradeBeacon |
||||
*/ |
||||
function getGovernanceRouter(chainDetails, domain) { |
||||
return chainDetails[domain].contracts.governanceRouter |
||||
.proxyWithImplementation; |
||||
} |
||||
|
||||
/* |
||||
* Get the UpgradeBeaconController contract |
||||
* |
||||
* @param chainDetails - ChainDetails type |
||||
* @param domain - the domain |
||||
* @return upgradeBeaconControllerContract - ethers contract for interacting with the upgradeBeaconController |
||||
*/ |
||||
function getUpgradeBeaconController(chainDetails, domain) { |
||||
return chainDetails[domain].contracts.upgradeBeaconController; |
||||
} |
||||
|
||||
/* |
||||
* Get the UpdaterManager contract |
||||
* |
||||
* @param chainDetails - ChainDetails type |
||||
* @param domain - the domain |
||||
* @return updaterManagerContract - ethers contract for interacting with the updaterManager |
||||
*/ |
||||
function getUpdaterManager(chainDetails, domain) { |
||||
return chainDetails[domain].contracts.updaterManager; |
||||
} |
||||
|
||||
/* |
||||
* Deploy the entire suite of Optics contracts |
||||
* on each chain within the chainConfigs array |
||||
* including the upgradable Home, Replicas, and GovernanceRouter |
||||
* that have been deployed, initialized, and configured |
||||
* according to the deployOptics script |
||||
* |
||||
* @param chainConfigs - ChainConfig[] |
||||
* |
||||
* @return chainDetails - ChainDetails type |
||||
*/ |
||||
async function deployMultipleChains(chainConfigs) { |
||||
// for each domain, deploy the entire contract suite,
|
||||
// including one replica for each other domain
|
||||
const chainDetails = {}; |
||||
|
||||
let govRouters = []; |
||||
for (let config of chainConfigs) { |
||||
const { domain } = config; |
||||
|
||||
// for the given domain,
|
||||
// local is the single chainConfig for the chain at the given domain
|
||||
// remotes is an array of all other chains
|
||||
const { local, remotes } = separateLocalFromRemotes(chainConfigs, domain); |
||||
|
||||
// deploy contract suite for this chain
|
||||
// note: we will be working with a persistent set of contracts across each test
|
||||
const contracts = await devDeployOptics(local, remotes, true); |
||||
|
||||
const govRouter = contracts.governanceRouter.proxyWithImplementation; |
||||
govRouters.push(govRouter); |
||||
|
||||
chainDetails[domain] = { |
||||
...config, |
||||
contracts, |
||||
}; |
||||
} |
||||
|
||||
// set the governor to the governance router deployed on the first chain
|
||||
const governorDomain = await chainConfigs[0].domain; |
||||
const governor = await govRouters[0].governor(); |
||||
for (let i = 0; i < govRouters.length; i++) { |
||||
for (let j = 0; j < govRouters.length; j++) { |
||||
if (j !== i) { |
||||
// set routers on every governance router
|
||||
const routerDomain = chainConfigs[j].domain; |
||||
const routerAddress = govRouters[j].address; |
||||
govRouters[i].setRouterAddress(routerDomain, routerAddress); |
||||
} |
||||
} |
||||
if (i > 0) { |
||||
// transfer governorship to first governance router
|
||||
govRouters[i].transferGovernor(governorDomain, governor); |
||||
} |
||||
} |
||||
|
||||
return chainDetails; |
||||
} |
||||
|
||||
/* |
||||
* Given a full array of chainConfigs and a target localDomain, |
||||
* return an object where local is the domain specified by localDomain |
||||
* and remotes is an array of all other remote domains |
||||
* thus creating appropriate input parameters for the deployOptics script |
||||
* given an array of all Optics chains |
||||
* |
||||
* @param chainConfigs - ChainConfig[] |
||||
* @param localDomain - domain for the local contract suite |
||||
* |
||||
* @return { |
||||
* local - ChainConfig for the local domain |
||||
* remotes - ChainConfig[] for all other domains |
||||
* } |
||||
*/ |
||||
function separateLocalFromRemotes(chainConfigs, localDomain) { |
||||
let local; |
||||
const remotes = []; |
||||
|
||||
for (let config of chainConfigs) { |
||||
if (config.domain == localDomain) { |
||||
local = config; |
||||
} else { |
||||
remotes.push(config); |
||||
} |
||||
} |
||||
|
||||
return { |
||||
local, |
||||
remotes, |
||||
}; |
||||
} |
||||
|
||||
module.exports = { |
||||
deployMultipleChains, |
||||
getHome, |
||||
getReplica, |
||||
getGovernanceRouter, |
||||
getUpdaterObject, |
||||
getUpgradeBeaconController, |
||||
getUpdaterManager, |
||||
}; |
@ -1,55 +0,0 @@ |
||||
const { waffle } = require('hardhat'); |
||||
const { provider } = waffle; |
||||
|
||||
/* |
||||
* Given an array of domains, |
||||
* generate an array of ChainConfigs |
||||
* which can be used to deploy Optics to each domain |
||||
* for cross-chain tests |
||||
* |
||||
* @param domains - array of domains (integers) for chains we want to deploy Optics on |
||||
* |
||||
* @return configs - TestChainConfig[] |
||||
*/ |
||||
async function domainsToTestConfigs(domains, recoveryManagerAddress) { |
||||
let configs = domains.map((domain) => { |
||||
return { |
||||
domain, |
||||
recoveryTimelock: 1200, |
||||
recoveryManagerAddress, |
||||
currentRoot: |
||||
'0x0000000000000000000000000000000000000000000000000000000000000000', |
||||
nextToProcessIndex: 0, |
||||
optimisticSeconds: 3, |
||||
}; |
||||
}); |
||||
|
||||
const wallets = provider.getWallets(); |
||||
|
||||
if (wallets.length < domains.length) { |
||||
throw new Error('need more wallets to add updaters for all chains'); |
||||
} |
||||
|
||||
// add the domain + updater + initialization arguments to config
|
||||
for (let i = 0; i < configs.length; i++) { |
||||
let config = configs[i]; |
||||
const { domain } = config; |
||||
|
||||
const signer = wallets[i]; |
||||
|
||||
const updaterObject = await optics.Updater.fromSigner(signer, domain); |
||||
|
||||
configs[i] = { |
||||
...config, |
||||
updater: signer.address, |
||||
updaterObject, |
||||
signer, |
||||
}; |
||||
} |
||||
|
||||
return configs; |
||||
} |
||||
|
||||
module.exports = { |
||||
domainsToTestConfigs, |
||||
}; |
@ -1,26 +0,0 @@ |
||||
/* |
||||
* see also ../../js/types.js |
||||
* |
||||
* TestChainConfig { |
||||
* ...ChainConfig, |
||||
* updaterObject: optics.Updater type, |
||||
* signer: ethers Signer, |
||||
* contracts: OpticsContracts, |
||||
* }; |
||||
* |
||||
* ChainDetails { |
||||
* [domain]: TestChainConfig, |
||||
* }; |
||||
* |
||||
* Message { |
||||
* message: string, |
||||
* destinationDomain: int, |
||||
* recipientAddress: address, |
||||
* }; |
||||
* |
||||
* Update { |
||||
* startRoot: bytes32, |
||||
* finalRoot: bytes32, |
||||
* signature: hex, |
||||
* } |
||||
*/ |
@ -1,61 +0,0 @@ |
||||
const { provider, deployMockContract } = waffle; |
||||
const TestRecipient = require('../artifacts/contracts/test/TestRecipient.sol/TestRecipient.json'); |
||||
|
||||
const [opticsMessageSender] = provider.getWallets(); |
||||
|
||||
class MockRecipientObject { |
||||
constructor() { |
||||
const [opticsMessageRecipient] = provider.getWallets(); |
||||
this.mockRecipient = deployMockContract( |
||||
opticsMessageRecipient, |
||||
TestRecipient.abi, |
||||
); |
||||
} |
||||
|
||||
async getRecipient() { |
||||
return await this.mockRecipient; |
||||
} |
||||
} |
||||
|
||||
const increaseTimestampBy = async (provider, increaseTime) => { |
||||
await provider.send('evm_increaseTime', [increaseTime]); |
||||
await provider.send('evm_mine'); |
||||
}; |
||||
|
||||
class WalletProvider { |
||||
constructor(provider) { |
||||
this.provider = provider; |
||||
this.wallets = provider.getWallets(); |
||||
this.numUsedWallets = 0; |
||||
} |
||||
|
||||
_getWallets(numWallets) { |
||||
if (this.numUsedAccounts + numWallets > this.wallets.length) { |
||||
throw new Error('Out of wallets!'); |
||||
} |
||||
|
||||
return this.wallets.slice( |
||||
this.numUsedWallets, |
||||
this.numUsedWallets + numWallets, |
||||
); |
||||
} |
||||
|
||||
getWalletsPersistent(numWallets) { |
||||
const wallets = this._getWallets(numWallets); |
||||
this.numUsedWallets += numWallets; |
||||
return wallets; |
||||
} |
||||
|
||||
getWalletsEphemeral(numWallets) { |
||||
return this._getWallets(numWallets); |
||||
} |
||||
} |
||||
|
||||
const testUtils = { |
||||
increaseTimestampBy, |
||||
opticsMessageSender, |
||||
opticsMessageMockRecipient: new MockRecipientObject(), |
||||
WalletProvider, |
||||
}; |
||||
|
||||
module.exports = testUtils; |
@ -0,0 +1,71 @@ |
||||
import { HardhatRuntimeEnvironment } from "hardhat/types"; |
||||
|
||||
import { getOutputFromLatestDeploy } from "../../../typescript/optics-deploy/src/readDeployOutput"; |
||||
|
||||
const envError = (network: string) => |
||||
`pass --network tag to hardhat task (current network=${network})`; |
||||
|
||||
// list of networks supported by Etherscan
|
||||
const etherscanNetworks = ["mainnet", "kovan", "goerli", "ropsten", "rinkeby"]; |
||||
|
||||
/* |
||||
* Generate link to Etherscan for an address on the given network |
||||
* */ |
||||
function etherscanLink(network: string, address: string) { |
||||
const prefix = network == "mainnet" ? "" : `${network}.`; |
||||
return `https://${prefix}etherscan.io/address/${address}`; |
||||
} |
||||
|
||||
/* |
||||
* Parse the contract verification inputs |
||||
* that were output by the latest contract deploy |
||||
* for the network that hardhat is configured to |
||||
* and attempt to verify those contracts' source code on Etherscan |
||||
* */ |
||||
export async function verifyLatestDeploy(hre: HardhatRuntimeEnvironment) { |
||||
const network = hre.network.name; |
||||
|
||||
// assert that network from .env is supported by Etherscan
|
||||
if (!etherscanNetworks.includes(network)) { |
||||
throw new Error(`Network not supported by Etherscan; ${envError(network)}`); |
||||
} |
||||
console.log(`VERIFY ${network}`); |
||||
|
||||
// get the JSON verification inputs for the given network
|
||||
// from the latest contract deploy; throw if not found
|
||||
const verificationInputs = getOutputFromLatestDeploy(network, "verification"); |
||||
|
||||
// loop through each verification input for each contract in the file
|
||||
for (let verificationInput of verificationInputs) { |
||||
// attempt to verify contract on etherscan
|
||||
// (await one-by-one so that Etherscan doesn't rate limit)
|
||||
await verifyContract(network, verificationInput, hre); |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* Given one contract verification input, |
||||
* attempt to verify the contracts' source code on Etherscan |
||||
* */ |
||||
export async function verifyContract( |
||||
network: string, |
||||
verificationInput: any, |
||||
hre: HardhatRuntimeEnvironment |
||||
) { |
||||
const { name, address, constructorArguments } = verificationInput; |
||||
try { |
||||
console.log( |
||||
` Attempt to verify ${name} - ${etherscanLink(network, address)}` |
||||
); |
||||
await hre.run("verify:verify", { |
||||
network, |
||||
address, |
||||
constructorArguments, |
||||
}); |
||||
console.log(` SUCCESS verifying ${name}`); |
||||
} catch (e) { |
||||
console.log(` ERROR verifying ${name}`); |
||||
console.error(e); |
||||
} |
||||
console.log("\n\n"); // add space after each attempt
|
||||
} |
@ -0,0 +1,74 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */ |
||||
|
||||
/* Basic Options */ |
||||
// "incremental": true, /* Enable incremental compilation */ |
||||
"target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, |
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, |
||||
// "lib": [], /* Specify library files to be included in the compilation. */ |
||||
// "allowJs": true, /* Allow javascript files to be compiled. */ |
||||
// "checkJs": true, /* Report errors in .js files. */ |
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ |
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ |
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */ |
||||
// "outFile": "./", /* Concatenate and emit output to single file. */ |
||||
// "outDir": "./", /* Redirect output structure to the directory. */ |
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ |
||||
// "composite": true, /* Enable project compilation */ |
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ |
||||
// "removeComments": true, /* Do not emit comments to output. */ |
||||
// "noEmit": true, /* Do not emit outputs. */ |
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ |
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ |
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ |
||||
"resolveJsonModule": true, /* Allows for importing, extracting types from and generating . json files.*/ |
||||
|
||||
/* Strict Type-Checking Options */ |
||||
"strict": true /* Enable all strict type-checking options. */, |
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ |
||||
// "strictNullChecks": true, /* Enable strict null checks. */ |
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ |
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ |
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ |
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ |
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ |
||||
|
||||
/* Additional Checks */ |
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */ |
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */ |
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ |
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ |
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ |
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ |
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ |
||||
|
||||
/* Module Resolution Options */ |
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ |
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ |
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ |
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ |
||||
// "typeRoots": [], /* List of folders to include type definitions from. */ |
||||
// "types": [], /* Type declaration files to be included in compilation. */ |
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ |
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, |
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ |
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ |
||||
|
||||
/* Source Map Options */ |
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ |
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ |
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ |
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ |
||||
|
||||
/* Experimental Options */ |
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||
|
||||
/* Advanced Options */ |
||||
"skipLibCheck": true /* Skip type checking of declaration files. */, |
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, |
||||
"resolveJsonModule": true |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,74 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */ |
||||
|
||||
/* Basic Options */ |
||||
// "incremental": true, /* Enable incremental compilation */ |
||||
"target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, |
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, |
||||
// "lib": [], /* Specify library files to be included in the compilation. */ |
||||
// "allowJs": true, /* Allow javascript files to be compiled. */ |
||||
// "checkJs": true, /* Report errors in .js files. */ |
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ |
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ |
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */ |
||||
// "outFile": "./", /* Concatenate and emit output to single file. */ |
||||
// "outDir": "./", /* Redirect output structure to the directory. */ |
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ |
||||
// "composite": true, /* Enable project compilation */ |
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ |
||||
// "removeComments": true, /* Do not emit comments to output. */ |
||||
// "noEmit": true, /* Do not emit outputs. */ |
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ |
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ |
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ |
||||
"resolveJsonModule": true, /* Allows for importing, extracting types from and generating . json files.*/ |
||||
|
||||
/* Strict Type-Checking Options */ |
||||
"strict": true /* Enable all strict type-checking options. */, |
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ |
||||
// "strictNullChecks": true, /* Enable strict null checks. */ |
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ |
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ |
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ |
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ |
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ |
||||
|
||||
/* Additional Checks */ |
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */ |
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */ |
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ |
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ |
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ |
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ |
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ |
||||
|
||||
/* Module Resolution Options */ |
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ |
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ |
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ |
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ |
||||
// "typeRoots": [], /* List of folders to include type definitions from. */ |
||||
// "types": [], /* Type declaration files to be included in compilation. */ |
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ |
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, |
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ |
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ |
||||
|
||||
/* Source Map Options */ |
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ |
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ |
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ |
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ |
||||
|
||||
/* Experimental Options */ |
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||
|
||||
/* Advanced Options */ |
||||
"skipLibCheck": true /* Skip type checking of declaration files. */, |
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, |
||||
"resolveJsonModule": true |
||||
} |
||||
} |
@ -1 +0,0 @@ |
||||
src/typechain |
@ -0,0 +1 @@ |
||||
node_modules |
@ -0,0 +1,5 @@ |
||||
{ |
||||
"tabWidth": 2, |
||||
"singleQuote": true, |
||||
"trailingComma": "all" |
||||
} |
@ -0,0 +1,21 @@ |
||||
{ |
||||
"devDependencies": { |
||||
"ethers": "^5.3.1", |
||||
"prettier": "^2.3.1", |
||||
"typechain": "^5.0.0", |
||||
"typescript": "^4.3.2" |
||||
}, |
||||
"name": "optics-deploy", |
||||
"version": "1.0.0", |
||||
"description": "Optics deploy tools", |
||||
"main": "src/index.ts", |
||||
"scripts": { |
||||
"prettier": "prettier --write ./src", |
||||
"test": "echo \"Error: no test specified\" && exit 1" |
||||
}, |
||||
"author": "Celo Labs Inc.", |
||||
"license": "MIT OR Apache-2.0", |
||||
"dependencies": { |
||||
"@ethersproject/experimental": "^5.3.0" |
||||
} |
||||
} |
@ -0,0 +1,2 @@ |
||||
node_modules |
||||
cache |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"_format": "hh-sol-cache-2", |
||||
"files": {} |
||||
} |
@ -0,0 +1,9 @@ |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import './lib/index'; |
||||
|
||||
/** |
||||
* @type import('hardhat/config').HardhatUserConfig |
||||
*/ |
||||
module.exports = { |
||||
solidity: '0.7.3', |
||||
}; |
@ -0,0 +1,206 @@ |
||||
import '@nomiclabs/hardhat-waffle'; |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
import { assert } from 'chai'; |
||||
import { extendEnvironment } from 'hardhat/config'; |
||||
|
||||
import * as ethers from 'ethers'; |
||||
import * as types from './types'; |
||||
import { getHexStringByteLength } from './utils'; |
||||
|
||||
export class Updater { |
||||
localDomain: types.Domain; |
||||
signer: SignerWithAddress; |
||||
address: types.Address; |
||||
|
||||
constructor( |
||||
signer: SignerWithAddress, |
||||
address: types.Address, |
||||
localDomain: types.Domain, |
||||
disableWarn: boolean, |
||||
) { |
||||
if (!disableWarn) { |
||||
throw new Error('Please use `Updater.fromSigner()` to instantiate.'); |
||||
} |
||||
this.localDomain = localDomain ? localDomain : 0; |
||||
this.signer = signer; |
||||
this.address = address; |
||||
} |
||||
|
||||
static async fromSigner( |
||||
signer: SignerWithAddress, |
||||
localDomain: types.Domain, |
||||
) { |
||||
return new Updater(signer, await signer.getAddress(), localDomain, true); |
||||
} |
||||
|
||||
domainHash() { |
||||
return domainHash(this.localDomain); |
||||
} |
||||
|
||||
message(oldRoot: types.HexString, newRoot: types.HexString) { |
||||
return ethers.utils.concat([this.domainHash(), oldRoot, newRoot]); |
||||
} |
||||
|
||||
async signUpdate(oldRoot: types.HexString, newRoot: types.HexString) { |
||||
let message = this.message(oldRoot, newRoot); |
||||
let msgHash = ethers.utils.arrayify(ethers.utils.keccak256(message)); |
||||
let signature = await this.signer.signMessage(msgHash); |
||||
return { |
||||
origin: this.localDomain, |
||||
oldRoot, |
||||
newRoot, |
||||
signature, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
const formatMessage = ( |
||||
localDomain: types.Domain, |
||||
senderAddr: types.Address, |
||||
sequence: number, |
||||
destinationDomain: types.Domain, |
||||
recipientAddr: types.Address, |
||||
body: types.HexString, |
||||
): string => { |
||||
senderAddr = ethersAddressToBytes32(senderAddr); |
||||
recipientAddr = ethersAddressToBytes32(recipientAddr); |
||||
|
||||
return ethers.utils.solidityPack( |
||||
['uint32', 'bytes32', 'uint32', 'uint32', 'bytes32', 'bytes'], |
||||
[localDomain, senderAddr, sequence, destinationDomain, recipientAddr, body], |
||||
); |
||||
}; |
||||
|
||||
export enum OpticsState { |
||||
UNINITIALIZED = 0, |
||||
ACTIVE, |
||||
FAILED, |
||||
} |
||||
|
||||
export enum GovernanceMessage { |
||||
CALL = 1, |
||||
TRANSFERGOVERNOR = 2, |
||||
SETROUTER = 3, |
||||
} |
||||
|
||||
export enum MessageStatus { |
||||
NONE = 0, |
||||
PENDING, |
||||
PROCESSED, |
||||
} |
||||
|
||||
function formatTransferGovernor( |
||||
newDomain: types.Domain, |
||||
newAddress: types.Address, |
||||
): string { |
||||
return ethers.utils.solidityPack( |
||||
['bytes1', 'uint32', 'bytes32'], |
||||
[GovernanceMessage.TRANSFERGOVERNOR, newDomain, newAddress], |
||||
); |
||||
} |
||||
|
||||
function formatSetRouter(domain: types.Domain, address: types.Address): string { |
||||
return ethers.utils.solidityPack( |
||||
['bytes1', 'uint32', 'bytes32'], |
||||
[GovernanceMessage.SETROUTER, domain, address], |
||||
); |
||||
} |
||||
|
||||
function messageToLeaf(message: types.HexString): string { |
||||
return ethers.utils.solidityKeccak256(['bytes'], [message]); |
||||
} |
||||
|
||||
function ethersAddressToBytes32(address: types.Address): string { |
||||
return ethers.utils |
||||
.hexZeroPad(ethers.utils.hexStripZeros(address), 32) |
||||
.toLowerCase(); |
||||
} |
||||
|
||||
function destinationAndSequence( |
||||
destination: types.Domain, |
||||
sequence: number, |
||||
): ethers.BigNumber { |
||||
assert(destination < Math.pow(2, 32) - 1); |
||||
assert(sequence < Math.pow(2, 32) - 1); |
||||
|
||||
return ethers.BigNumber.from(destination) |
||||
.mul(ethers.BigNumber.from(2).pow(32)) |
||||
.add(ethers.BigNumber.from(sequence)); |
||||
} |
||||
|
||||
function domainHash(domain: Number): string { |
||||
return ethers.utils.solidityKeccak256( |
||||
['uint32', 'string'], |
||||
[domain, 'OPTICS'], |
||||
); |
||||
} |
||||
|
||||
async function signedFailureNotification( |
||||
signer: ethers.Signer, |
||||
domain: types.Domain, |
||||
updaterAddress: types.Address, |
||||
): Promise<types.SignedFailureNotification> { |
||||
const domainCommitment = domainHash(domain); |
||||
const updaterBytes32 = ethersAddressToBytes32(updaterAddress); |
||||
|
||||
const failureNotification = ethers.utils.solidityPack( |
||||
['bytes32', 'uint32', 'bytes32'], |
||||
[domainCommitment, domain, updaterBytes32], |
||||
); |
||||
const signature = await signer.signMessage( |
||||
ethers.utils.arrayify(ethers.utils.keccak256(failureNotification)), |
||||
); |
||||
|
||||
return { |
||||
failureNotification: { |
||||
domainCommitment, |
||||
domain, |
||||
updaterBytes32, |
||||
}, |
||||
signature, |
||||
}; |
||||
} |
||||
|
||||
function formatCalls(callsData: types.CallData[]): string { |
||||
let callBody = '0x'; |
||||
const numCalls = callsData.length; |
||||
|
||||
for (let i = 0; i < numCalls; i++) { |
||||
const { to, data } = callsData[i]; |
||||
const dataLen = getHexStringByteLength(data); |
||||
|
||||
if (!to || !data) { |
||||
throw new Error(`Missing data in Call ${i + 1}: \n ${callsData[i]}`); |
||||
} |
||||
|
||||
let hexBytes = ethers.utils.solidityPack( |
||||
['bytes32', 'uint256', 'bytes'], |
||||
[to, dataLen, data], |
||||
); |
||||
|
||||
// remove 0x before appending
|
||||
callBody += hexBytes.slice(2); |
||||
} |
||||
|
||||
return ethers.utils.solidityPack( |
||||
['bytes1', 'bytes1', 'bytes'], |
||||
[GovernanceMessage.CALL, numCalls, callBody], |
||||
); |
||||
} |
||||
|
||||
// HardhatRuntimeEnvironment
|
||||
extendEnvironment((hre) => { |
||||
hre.optics = { |
||||
formatMessage, |
||||
governance: { |
||||
formatTransferGovernor, |
||||
formatSetRouter, |
||||
formatCalls, |
||||
}, |
||||
messageToLeaf, |
||||
ethersAddressToBytes32, |
||||
destinationAndSequence, |
||||
domainHash, |
||||
signedFailureNotification, |
||||
}; |
||||
}); |
@ -0,0 +1,84 @@ |
||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
||||
import { BytesLike } from 'ethers'; |
||||
|
||||
export interface HardhatOpticsHelpers { |
||||
formatMessage: Function; |
||||
governance: { |
||||
formatTransferGovernor: Function; |
||||
formatSetRouter: Function; |
||||
formatCalls: Function; |
||||
}; |
||||
messageToLeaf: Function; |
||||
ethersAddressToBytes32: Function; |
||||
destinationAndSequence: Function; |
||||
domainHash: Function; |
||||
signedFailureNotification: Function; |
||||
} |
||||
|
||||
declare module 'hardhat/types/runtime' { |
||||
interface HardhatRuntimeEnvironment { |
||||
optics: HardhatOpticsHelpers; |
||||
} |
||||
} |
||||
|
||||
export type Domain = number; |
||||
export type Address = string; |
||||
export type AddressBytes32 = string; |
||||
export type HexString = string; |
||||
export type Signer = SignerWithAddress; |
||||
export type BytesArray = [ |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
BytesLike, |
||||
]; |
||||
|
||||
export type Update = { |
||||
oldRoot: string; |
||||
newRoot: string; |
||||
signature: string; |
||||
}; |
||||
|
||||
export type CallData = { |
||||
to: Address; |
||||
data: string; |
||||
}; |
||||
|
||||
export type FailureNotification = { |
||||
domainCommitment: string; |
||||
domain: number; |
||||
updaterBytes32: string; |
||||
}; |
||||
|
||||
export type SignedFailureNotification = { |
||||
failureNotification: FailureNotification; |
||||
signature: string; |
||||
}; |
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,31 @@ |
||||
{ |
||||
"devDependencies": { |
||||
"@nomiclabs/hardhat-ethers": "^2.0.2", |
||||
"@nomiclabs/hardhat-waffle": "^2.0.1", |
||||
"@typechain/ethers-v5": "^7.0.0", |
||||
"@types/chai": "^4.2.18", |
||||
"@types/mocha": "^8.2.2", |
||||
"chai": "^4.3.4", |
||||
"ethereum-waffle": "^3.3.0", |
||||
"ethers": "^5.3.1", |
||||
"hardhat": "^2.3.0", |
||||
"mkdirp": "^1.0.4", |
||||
"prettier": "2.3.0", |
||||
"ts-node": "^10.0.0", |
||||
"typechain": "^5.0.0", |
||||
"typescript": "^4.3.2" |
||||
}, |
||||
"dependencies": { |
||||
"@ethersproject/experimental": "^5.3.0", |
||||
"@types/node": "^15.6.1", |
||||
"ethers": "^5.2.0" |
||||
"@types/node": "^15.6.1" |
||||
}, |
||||
"name": "optics-ts", |
||||
"version": "1.0.0", |
||||
"description": "Optics typescript tools", |
||||
"main": "src/index.ts", |
||||
"scripts": { |
||||
"prettier": "npx prettier --write .", |
||||
"test": "echo \"Error: no test specified\" && exit 1" |
||||
"prettier": "prettier --write ./test ./lib", |
||||
"test": "hardhat test" |
||||
}, |
||||
"author": "Celo Labs Inc.", |
||||
"license": "MIT OR Apache-2.0" |
@ -0,0 +1,206 @@ |
||||
import { optics, ethers } from 'hardhat'; |
||||
import { expect } from 'chai'; |
||||
|
||||
import * as utils from './utils'; |
||||
import { getTestDeploy } from '../testChain'; |
||||
import { increaseTimestampBy } from '../utils'; |
||||
import { Updater, MessageStatus } from '../../lib'; |
||||
import { Update, Signer, BytesArray } from '../../lib/types'; |
||||
import { Deploy } from '../../../optics-deploy/src/chain'; |
||||
import { deployTwoChains } from '../../../optics-deploy/src/deployOptics'; |
||||
import { |
||||
TestRecipient__factory, |
||||
TestReplica, |
||||
} from '../../../typechain/optics-core'; |
||||
|
||||
import proveAndProcessTestCases from '../../../../vectors/proveAndProcess.json'; |
||||
|
||||
const domains = [1000, 2000]; |
||||
const localDomain = domains[0]; |
||||
const remoteDomain = domains[1]; |
||||
|
||||
/* |
||||
* Deploy the full Optics suite on two chains |
||||
* enqueue messages to Home |
||||
* sign and submit updates to Home |
||||
* relay updates to Replica |
||||
* confirm updates on Replica |
||||
* TODO prove and process messages on Replica |
||||
*/ |
||||
describe('SimpleCrossChainMessage', async () => { |
||||
// deploys[0] is the local deploy and governor chain
|
||||
// deploys[1] is the remote deploy
|
||||
let deploys: Deploy[] = []; |
||||
|
||||
let randomSigner: Signer, |
||||
firstRootEnqueuedToReplica: string, |
||||
updater: Updater, |
||||
latestRoot: string, |
||||
latestUpdate: Update; |
||||
|
||||
before(async () => { |
||||
[randomSigner] = await ethers.getSigners(); |
||||
updater = await Updater.fromSigner(randomSigner, localDomain); |
||||
|
||||
deploys.push(await getTestDeploy(localDomain, updater.address, [])); |
||||
deploys.push(await getTestDeploy(remoteDomain, updater.address, [])); |
||||
|
||||
await deployTwoChains(deploys[0], deploys[1]); |
||||
}); |
||||
|
||||
it('All Homes have correct initial state', async () => { |
||||
const nullRoot = '0x' + '00'.repeat(32); |
||||
|
||||
// governorHome has 1 updates
|
||||
const governorHome = deploys[0].contracts.home?.proxy!; |
||||
|
||||
let length = await governorHome.queueLength(); |
||||
expect(length).to.equal(1); |
||||
|
||||
let [suggestedCurrent, suggestedNew] = await governorHome.suggestUpdate(); |
||||
expect(suggestedCurrent).to.equal(nullRoot); |
||||
expect(suggestedNew).to.not.equal(nullRoot); |
||||
|
||||
// nonGovernorHome has 2 updates
|
||||
const nonGovernorHome = deploys[1].contracts.home?.proxy!; |
||||
|
||||
length = await nonGovernorHome.queueLength(); |
||||
expect(length).to.equal(2); |
||||
|
||||
[suggestedCurrent, suggestedNew] = await nonGovernorHome.suggestUpdate(); |
||||
expect(suggestedCurrent).to.equal(nullRoot); |
||||
expect(suggestedNew).to.not.equal(nullRoot); |
||||
}); |
||||
|
||||
it('All Replicas have empty queue of pending updates', async () => { |
||||
for (let deploy of deploys) { |
||||
const replicas = deploy.contracts.replicas; |
||||
for (let domain in replicas) { |
||||
const replica = replicas[domain].proxy; |
||||
|
||||
const length = await replica.queueLength(); |
||||
expect(length).to.equal(0); |
||||
|
||||
const [pending, confirmAt] = await replica.nextPending(); |
||||
expect(pending).to.equal(await replica.current()); |
||||
expect(confirmAt).to.equal(1); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
it('Origin Home Accepts one valid update', async () => { |
||||
const messages = ['message'].map((message) => |
||||
utils.formatMessage(message, remoteDomain, randomSigner.address), |
||||
); |
||||
const update = await utils.enqueueMessagesAndUpdateHome( |
||||
deploys[0].contracts.home?.proxy!, |
||||
messages, |
||||
updater, |
||||
); |
||||
|
||||
latestUpdate = update; |
||||
latestRoot = update.newRoot; |
||||
}); |
||||
|
||||
it('Destination Replica Accepts the first update', async () => { |
||||
firstRootEnqueuedToReplica = await utils.enqueueUpdateToReplica( |
||||
latestUpdate, |
||||
deploys[1].contracts.replicas[localDomain].proxy!, |
||||
); |
||||
}); |
||||
|
||||
it('Origin Home Accepts an update with several batched messages', async () => { |
||||
const messages = ['message1', 'message2', 'message3'].map((message) => |
||||
utils.formatMessage(message, remoteDomain, randomSigner.address), |
||||
); |
||||
const update = await utils.enqueueMessagesAndUpdateHome( |
||||
deploys[0].contracts.home?.proxy!, |
||||
messages, |
||||
updater, |
||||
); |
||||
|
||||
latestUpdate = update; |
||||
latestRoot = update.newRoot; |
||||
}); |
||||
|
||||
it('Destination Replica Accepts the second update', async () => { |
||||
await utils.enqueueUpdateToReplica( |
||||
latestUpdate, |
||||
deploys[1].contracts.replicas[localDomain].proxy, |
||||
); |
||||
}); |
||||
|
||||
it('Destination Replica shows first update as the next pending', async () => { |
||||
const replica = deploys[1].contracts.replicas[localDomain].proxy; |
||||
const [pending] = await replica.nextPending(); |
||||
expect(pending).to.equal(firstRootEnqueuedToReplica); |
||||
}); |
||||
|
||||
it('Destination Replica Batch-confirms several ready updates', async () => { |
||||
const replica = deploys[1].contracts.replicas[localDomain].proxy; |
||||
|
||||
// Increase time enough for both updates to be confirmable
|
||||
const optimisticSeconds = deploys[0].chain.optimisticSeconds; |
||||
await increaseTimestampBy(ethers.provider, optimisticSeconds * 2); |
||||
|
||||
// Replica should be able to confirm updates
|
||||
expect(await replica.canConfirm()).to.be.true; |
||||
|
||||
await replica.confirm(); |
||||
|
||||
// after confirming, current root should be equal to the last submitted update
|
||||
const { newRoot } = latestUpdate; |
||||
expect(await replica.current()).to.equal(newRoot); |
||||
}); |
||||
|
||||
it('Proves and processes a message on Replica', async () => { |
||||
// get governance routers
|
||||
const governorRouter = deploys[0].contracts.governance!.proxy; |
||||
const nonGovernorRouter = deploys[1].contracts.governance!.proxy; |
||||
|
||||
const replica = deploys[1].contracts.replicas[localDomain] |
||||
.proxy as TestReplica; |
||||
const testRecipientFactory = new TestRecipient__factory(randomSigner); |
||||
const TestRecipient = await testRecipientFactory.deploy(); |
||||
|
||||
// ensure `processed` has an initial value of false
|
||||
expect(await TestRecipient.processed()).to.be.false; |
||||
|
||||
// create Call message to test recipient that calls `processCall`
|
||||
const arg = true; |
||||
const call = await utils.formatCall(TestRecipient, 'processCall', [arg]); |
||||
const callMessage = optics.governance.formatCalls([call]); |
||||
|
||||
// Create Optics message that is sent from the governor domain and governor
|
||||
// to the nonGovernorRouter on the nonGovernorDomain
|
||||
const sequence = await replica.nextToProcess(); |
||||
const opticsMessage = optics.formatMessage( |
||||
1000, |
||||
governorRouter.address, |
||||
sequence, |
||||
2000, |
||||
nonGovernorRouter.address, |
||||
callMessage, |
||||
); |
||||
|
||||
// get merkle proof
|
||||
const { path, index } = proveAndProcessTestCases[0]; |
||||
const leaf = optics.messageToLeaf(opticsMessage); |
||||
|
||||
// set root
|
||||
const proofRoot = await replica.testBranchRoot( |
||||
leaf, |
||||
path as BytesArray, |
||||
index, |
||||
); |
||||
await replica.setCurrentRoot(proofRoot); |
||||
|
||||
// prove and process message
|
||||
await replica.proveAndProcess(opticsMessage, path as BytesArray, index); |
||||
|
||||
// expect call to have been processed
|
||||
expect(await TestRecipient.processed()).to.be.true; |
||||
expect(await replica.messages(leaf)).to.equal(MessageStatus.PROCESSED); |
||||
expect(await replica.nextToProcess()).to.equal(sequence + 1); |
||||
}); |
||||
}); |
@ -1,24 +1,22 @@ |
||||
/* global describe before it */ |
||||
import { ethers } from 'hardhat'; |
||||
import { expect } from 'chai'; |
||||
import { TestQueue, TestQueue__factory } from '../../typechain/optics-core'; |
||||
|
||||
const { ethers } = require('hardhat'); |
||||
const { expect } = require('chai'); |
||||
// create a proper hex encoded bytes32 filled with number. e.g 0x01010101...
|
||||
const bytes32 = (num: number) => `0x${Buffer.alloc(32, num).toString('hex')}`; |
||||
|
||||
describe('Queue', async () => { |
||||
let queue; |
||||
|
||||
// create a proper hex encoded bytes32 filled with number. e.g 0x01010101...
|
||||
const bytes32 = (num) => `0x${Buffer.alloc(32, num).toString('hex')}`; |
||||
let queue: TestQueue; |
||||
|
||||
before(async () => { |
||||
const Queue = await ethers.getContractFactory('TestQueue'); |
||||
queue = await Queue.deploy(); |
||||
|
||||
await queue.deployed(); |
||||
const [signer] = await ethers.getSigners(); |
||||
const queueFactory = new TestQueue__factory(signer); |
||||
queue = await queueFactory.deploy(); |
||||
}); |
||||
|
||||
it('should function as a queue', async () => { |
||||
// we put this here for coverage to check that init properly does nothing
|
||||
queue.initializeAgain(); |
||||
await queue.initializeAgain(); |
||||
|
||||
const items = Array.from(new Array(10).keys()).map((i) => bytes32(i)); |
||||
|
@ -0,0 +1,46 @@ |
||||
import { ethers } from 'hardhat'; |
||||
const { BigNumber } = ethers; |
||||
|
||||
import { Chain, Deploy } from '../../optics-deploy/src/chain'; |
||||
|
||||
export async function getTestChain( |
||||
domain: number, |
||||
updater: string, |
||||
watchers: string[], |
||||
recoveryManager?: string, |
||||
): Promise<Chain> { |
||||
const [, , , , , , , deployer] = await ethers.getSigners(); |
||||
return { |
||||
name: 'hh', |
||||
provider: ethers.provider, |
||||
deployer, |
||||
domain, |
||||
recoveryTimelock: 1, |
||||
recoveryManager: recoveryManager || ethers.constants.AddressZero, |
||||
updater, |
||||
optimisticSeconds: 3, |
||||
watchers, |
||||
gasPrice: BigNumber.from('20000000000'), |
||||
confirmations: 0, |
||||
}; |
||||
} |
||||
|
||||
export async function getTestDeploy( |
||||
domain: number, |
||||
updater: string, |
||||
watchers: string[], |
||||
recoveryManager?: string, |
||||
): Promise<Deploy> { |
||||
return { |
||||
chain: await getTestChain(domain, updater, watchers, recoveryManager), |
||||
contracts: { replicas: {} }, |
||||
verificationInput: [ |
||||
{ |
||||
name: 'string', |
||||
address: 'Address', |
||||
constructorArguments: ['arg'], |
||||
}, |
||||
], |
||||
test: true, |
||||
}; |
||||
} |
@ -0,0 +1,51 @@ |
||||
import { ethers } from 'hardhat'; |
||||
|
||||
import { getTestDeploy } from './testChain'; |
||||
import { UpgradeTestHelpers, MysteryMathUpgrade } from './utils'; |
||||
import { Signer } from '../lib/types'; |
||||
import * as contracts from '../../typechain/optics-core'; |
||||
|
||||
describe('Upgrade', async () => { |
||||
const utils = new UpgradeTestHelpers(); |
||||
let signer: Signer, |
||||
mysteryMath: MysteryMathUpgrade, |
||||
upgradeBeaconController: contracts.UpgradeBeaconController; |
||||
|
||||
before(async () => { |
||||
// set signer
|
||||
[signer] = await ethers.getSigners(); |
||||
|
||||
// set up fresh test deploy
|
||||
const deploy = await getTestDeploy(1000, ethers.constants.AddressZero, []); |
||||
|
||||
// deploy upgrade setup for mysteryMath contract
|
||||
mysteryMath = await utils.deployMysteryMathUpgradeSetup( |
||||
deploy, |
||||
signer, |
||||
true, |
||||
); |
||||
|
||||
// set upgradeBeaconController
|
||||
upgradeBeaconController = deploy.contracts.upgradeBeaconController!; |
||||
}); |
||||
|
||||
it('Pre-Upgrade returns values from MysteryMathV1', async () => { |
||||
await utils.expectMysteryMathV1(mysteryMath.proxy); |
||||
}); |
||||
|
||||
it('Upgrades without problem', async () => { |
||||
// Deploy Implementation 2
|
||||
const factory = new contracts.MysteryMathV2__factory(signer); |
||||
const implementation = await factory.deploy(); |
||||
|
||||
// Upgrade to implementation 2
|
||||
await upgradeBeaconController.upgrade( |
||||
mysteryMath.beacon.address, |
||||
implementation.address, |
||||
); |
||||
}); |
||||
|
||||
it('Post-Upgrade returns values from MysteryMathV2', async () => { |
||||
await utils.expectMysteryMathV2(mysteryMath.proxy); |
||||
}); |
||||
}); |
@ -0,0 +1,96 @@ |
||||
import { expect } from 'chai'; |
||||
import ethers from 'ethers'; |
||||
|
||||
import { Signer } from '../lib/types'; |
||||
import { Deploy } from '../../optics-deploy/src/chain'; |
||||
import { |
||||
deployUpdaterManager, |
||||
deployUpgradeBeaconController, |
||||
} from '../../optics-deploy/src/deployOptics'; |
||||
import * as contracts from '../../typechain/optics-core'; |
||||
|
||||
export const increaseTimestampBy = async ( |
||||
provider: ethers.providers.JsonRpcProvider, |
||||
increaseTime: number, |
||||
) => { |
||||
await provider.send('evm_increaseTime', [increaseTime]); |
||||
await provider.send('evm_mine', []); |
||||
}; |
||||
|
||||
export type MysteryMathUpgrade = { |
||||
proxy: contracts.MysteryMathV1 | contracts.MysteryMathV2; |
||||
beacon: contracts.UpgradeBeacon; |
||||
implementation: contracts.MysteryMathV1 | contracts.MysteryMathV2; |
||||
}; |
||||
|
||||
export class UpgradeTestHelpers { |
||||
a: number = 5; |
||||
b: number = 10; |
||||
stateVar: number = 17; |
||||
|
||||
async deployMysteryMathUpgradeSetup( |
||||
deploy: Deploy, |
||||
signer: Signer, |
||||
isNewDeploy?: boolean, |
||||
): Promise<MysteryMathUpgrade> { |
||||
// deploy implementation
|
||||
const mysteryMathFactory = new contracts.MysteryMathV1__factory(signer); |
||||
const mysteryMathImplementation = await mysteryMathFactory.deploy(); |
||||
|
||||
if (isNewDeploy) { |
||||
// deploy UpdaterManager
|
||||
await deployUpdaterManager(deploy); |
||||
// deploy and set UpgradeBeaconController
|
||||
await deployUpgradeBeaconController(deploy); |
||||
} |
||||
|
||||
// deploy and set upgrade beacon
|
||||
const beaconFactory = new contracts.UpgradeBeacon__factory( |
||||
deploy.chain.deployer, |
||||
); |
||||
const beacon = await beaconFactory.deploy( |
||||
mysteryMathImplementation.address, |
||||
deploy.contracts.upgradeBeaconController!.address, |
||||
{ gasPrice: deploy.chain.gasPrice, gasLimit: 2_000_000 }, |
||||
); |
||||
|
||||
// deploy proxy
|
||||
let factory = new contracts.UpgradeBeaconProxy__factory( |
||||
deploy.chain.deployer, |
||||
); |
||||
const upgradeBeaconProxy = await factory.deploy(beacon.address, [], { |
||||
gasPrice: deploy.chain.gasPrice, |
||||
gasLimit: 1_000_000, |
||||
}); |
||||
|
||||
// set proxy
|
||||
const proxy = mysteryMathFactory.attach(upgradeBeaconProxy.address); |
||||
|
||||
// Set state of proxy
|
||||
await proxy.setState(this.stateVar); |
||||
|
||||
return { proxy, beacon, implementation: mysteryMathImplementation }; |
||||
} |
||||
|
||||
async expectMysteryMathV1(mysteryMathProxy: contracts.MysteryMathV1) { |
||||
const versionResult = await mysteryMathProxy.version(); |
||||
expect(versionResult).to.equal(1); |
||||
|
||||
const mathResult = await mysteryMathProxy.doMath(this.a, this.b); |
||||
expect(mathResult).to.equal(this.a + this.b); |
||||
|
||||
const stateResult = await mysteryMathProxy.getState(); |
||||
expect(stateResult).to.equal(this.stateVar); |
||||
} |
||||
|
||||
async expectMysteryMathV2(mysteryMathProxy: contracts.MysteryMathV2) { |
||||
const versionResult = await mysteryMathProxy.version(); |
||||
expect(versionResult).to.equal(2); |
||||
|
||||
const mathResult = await mysteryMathProxy.doMath(this.a, this.b); |
||||
expect(mathResult).to.equal(this.a * this.b); |
||||
|
||||
const stateResult = await mysteryMathProxy.getState(); |
||||
expect(stateResult).to.equal(this.stateVar); |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */ |
||||
|
||||
/* Basic Options */ |
||||
// "incremental": true, /* Enable incremental compilation */ |
||||
"target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, |
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, |
||||
// "lib": [], /* Specify library files to be included in the compilation. */ |
||||
// "allowJs": true, /* Allow javascript files to be compiled. */ |
||||
// "checkJs": true, /* Report errors in .js files. */ |
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ |
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ |
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */ |
||||
// "outFile": "./", /* Concatenate and emit output to single file. */ |
||||
// "outDir": "./", /* Redirect output structure to the directory. */ |
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ |
||||
// "composite": true, /* Enable project compilation */ |
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ |
||||
// "removeComments": true, /* Do not emit comments to output. */ |
||||
// "noEmit": true, /* Do not emit outputs. */ |
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ |
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ |
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ |
||||
"resolveJsonModule": true, /* Allows for importing, extracting types from and generating . json files.*/ |
||||
|
||||
/* Strict Type-Checking Options */ |
||||
"strict": true /* Enable all strict type-checking options. */, |
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ |
||||
// "strictNullChecks": true, /* Enable strict null checks. */ |
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ |
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ |
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ |
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ |
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ |
||||
|
||||
/* Additional Checks */ |
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */ |
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */ |
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ |
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ |
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ |
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ |
||||
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ |
||||
|
||||
/* Module Resolution Options */ |
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ |
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ |
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ |
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ |
||||
// "typeRoots": [], /* List of folders to include type definitions from. */ |
||||
// "types": [], /* Type declaration files to be included in compilation. */ |
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ |
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, |
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ |
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ |
||||
|
||||
/* Source Map Options */ |
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ |
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ |
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ |
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ |
||||
|
||||
/* Experimental Options */ |
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||
|
||||
/* Advanced Options */ |
||||
"skipLibCheck": true /* Skip type checking of declaration files. */, |
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, |
||||
"resolveJsonModule": true |
||||
} |
||||
} |
@ -0,0 +1,118 @@ |
||||
/* Autogenerated file. Do not edit manually. */ |
||||
/* tslint:disable */ |
||||
/* eslint-disable */ |
||||
|
||||
import { |
||||
ethers, |
||||
EventFilter, |
||||
Signer, |
||||
BigNumber, |
||||
BigNumberish, |
||||
PopulatedTransaction, |
||||
BaseContract, |
||||
ContractTransaction, |
||||
CallOverrides, |
||||
} from "ethers"; |
||||
import { BytesLike } from "@ethersproject/bytes"; |
||||
import { Listener, Provider } from "@ethersproject/providers"; |
||||
import { FunctionFragment, EventFragment, Result } from "@ethersproject/abi"; |
||||
import { TypedEventFilter, TypedEvent, TypedListener } from "./commons"; |
||||
|
||||
interface BadRecipientHandleInterface extends ethers.utils.Interface { |
||||
functions: { |
||||
"handle(uint32,bytes32)": FunctionFragment; |
||||
}; |
||||
|
||||
encodeFunctionData( |
||||
functionFragment: "handle", |
||||
values: [BigNumberish, BytesLike] |
||||
): string; |
||||
|
||||
decodeFunctionResult(functionFragment: "handle", data: BytesLike): Result; |
||||
|
||||
events: {}; |
||||
} |
||||
|
||||
export class BadRecipientHandle extends BaseContract { |
||||
connect(signerOrProvider: Signer | Provider | string): this; |
||||
attach(addressOrName: string): this; |
||||
deployed(): Promise<this>; |
||||
|
||||
listeners<EventArgsArray extends Array<any>, EventArgsObject>( |
||||
eventFilter?: TypedEventFilter<EventArgsArray, EventArgsObject> |
||||
): Array<TypedListener<EventArgsArray, EventArgsObject>>; |
||||
off<EventArgsArray extends Array<any>, EventArgsObject>( |
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>, |
||||
listener: TypedListener<EventArgsArray, EventArgsObject> |
||||
): this; |
||||
on<EventArgsArray extends Array<any>, EventArgsObject>( |
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>, |
||||
listener: TypedListener<EventArgsArray, EventArgsObject> |
||||
): this; |
||||
once<EventArgsArray extends Array<any>, EventArgsObject>( |
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>, |
||||
listener: TypedListener<EventArgsArray, EventArgsObject> |
||||
): this; |
||||
removeListener<EventArgsArray extends Array<any>, EventArgsObject>( |
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>, |
||||
listener: TypedListener<EventArgsArray, EventArgsObject> |
||||
): this; |
||||
removeAllListeners<EventArgsArray extends Array<any>, EventArgsObject>( |
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject> |
||||
): this; |
||||
|
||||
listeners(eventName?: string): Array<Listener>; |
||||
off(eventName: string, listener: Listener): this; |
||||
on(eventName: string, listener: Listener): this; |
||||
once(eventName: string, listener: Listener): this; |
||||
removeListener(eventName: string, listener: Listener): this; |
||||
removeAllListeners(eventName?: string): this; |
||||
|
||||
queryFilter<EventArgsArray extends Array<any>, EventArgsObject>( |
||||
event: TypedEventFilter<EventArgsArray, EventArgsObject>, |
||||
fromBlockOrBlockhash?: string | number | undefined, |
||||
toBlock?: string | number | undefined |
||||
): Promise<Array<TypedEvent<EventArgsArray & EventArgsObject>>>; |
||||
|
||||
interface: BadRecipientHandleInterface; |
||||
|
||||
functions: { |
||||
handle( |
||||
arg0: BigNumberish, |
||||
arg1: BytesLike, |
||||
overrides?: CallOverrides |
||||
): Promise<[void]>; |
||||
}; |
||||
|
||||
handle( |
||||
arg0: BigNumberish, |
||||
arg1: BytesLike, |
||||
overrides?: CallOverrides |
||||
): Promise<void>; |
||||
|
||||
callStatic: { |
||||
handle( |
||||
arg0: BigNumberish, |
||||
arg1: BytesLike, |
||||
overrides?: CallOverrides |
||||
): Promise<void>; |
||||
}; |
||||
|
||||
filters: {}; |
||||
|
||||
estimateGas: { |
||||
handle( |
||||
arg0: BigNumberish, |
||||
arg1: BytesLike, |
||||
overrides?: CallOverrides |
||||
): Promise<BigNumber>; |
||||
}; |
||||
|
||||
populateTransaction: { |
||||
handle( |
||||
arg0: BigNumberish, |
||||
arg1: BytesLike, |
||||
overrides?: CallOverrides |
||||
): Promise<PopulatedTransaction>; |
||||
}; |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue