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
Erin Hales 3 years ago committed by GitHub
parent a38f4f5153
commit 9c9fb87b03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 0
      .github/workflows/mergify.yml.bak
  2. 21
      .github/workflows/solidity.yml
  3. 2
      .gitignore
  4. 4
      pre-commit.sh
  5. 3
      rust/optics-core/src/accumulator/incremental.rs
  6. 71
      rust/optics-core/src/test_output.rs
  7. 14
      rust/optics-core/src/test_utils.rs
  8. 3
      rust/processor/src/prover/mod.rs
  9. 3
      solidity/optics-core/contracts/test/TestRecipient.sol
  10. 2
      solidity/optics-core/contracts/test/bad-recipient/BadRecipient6.sol
  11. 6
      solidity/optics-core/contracts/test/bad-recipient/BadRecipientHandle.sol
  12. 65
      solidity/optics-core/hardhat.config.js
  13. 59
      solidity/optics-core/hardhat.config.ts
  14. 20
      solidity/optics-core/js/deployOptics.js
  15. 274
      solidity/optics-core/js/deployOpticsUtils.js
  16. 168
      solidity/optics-core/js/deployUpgradeSetup.js
  17. 266
      solidity/optics-core/js/index.js
  18. 60
      solidity/optics-core/js/readDeployOutput.js
  19. 38
      solidity/optics-core/js/types.js
  20. 429
      solidity/optics-core/package-lock.json
  21. 13
      solidity/optics-core/package.json
  22. 7
      solidity/optics-core/test.sh
  23. 63
      solidity/optics-core/test/Upgrade.test.js
  24. 212
      solidity/optics-core/test/cross-chain/SimpleMessage.test.js
  25. 176
      solidity/optics-core/test/cross-chain/deployCrossChainTest.js
  26. 55
      solidity/optics-core/test/cross-chain/generateTestChainConfigs.js
  27. 26
      solidity/optics-core/test/cross-chain/types.js
  28. 61
      solidity/optics-core/test/utils.js
  29. 71
      solidity/optics-core/ts/verifyLatestDeploy.ts
  30. 74
      solidity/optics-core/tsconfig.json
  31. 0
      solidity/optics-core/update_abis.sh
  32. 0
      solidity/optics-core/verifyLatestDeploy.js
  33. 4
      solidity/optics-xapps/contracts/Router.sol
  34. 4
      solidity/optics-xapps/contracts/XAppConnectionClient.sol
  35. 15
      solidity/optics-xapps/contracts/bridge/BridgeRouter.sol
  36. 4
      solidity/optics-xapps/contracts/bridge/BridgeToken.sol
  37. 5
      solidity/optics-xapps/contracts/bridge/TokenRegistry.sol
  38. 7
      solidity/optics-xapps/contracts/ping-pong/PingPongRouter.sol
  39. 18
      solidity/optics-xapps/hardhat.config.ts
  40. 23934
      solidity/optics-xapps/package-lock.json
  41. 15
      solidity/optics-xapps/package.json
  42. 0
      solidity/optics-xapps/test/TokenBridge.test.js
  43. 0
      solidity/optics-xapps/test/xAppTemplate.test.js
  44. 74
      solidity/optics-xapps/tsconfig.json
  45. 1
      typescript/.prettierignore
  46. 1
      typescript/optics-deploy/.gitignore
  47. 5
      typescript/optics-deploy/.prettierrc
  48. 158
      typescript/optics-deploy/package-lock.json
  49. 21
      typescript/optics-deploy/package.json
  50. 21
      typescript/optics-deploy/src/chain.ts
  51. 323
      typescript/optics-deploy/src/deployOptics.ts
  52. 8
      typescript/optics-deploy/src/proxyUtils.ts
  53. 0
      typescript/optics-deploy/src/readDeployOutput.ts
  54. 0
      typescript/optics-deploy/tsconfig.json
  55. 2
      typescript/optics-tests/.gitignore
  56. 0
      typescript/optics-tests/.prettierrc
  57. 4
      typescript/optics-tests/cache/solidity-files-cache.json
  58. 9
      typescript/optics-tests/hardhat.config.ts
  59. 206
      typescript/optics-tests/lib/index.ts
  60. 84
      typescript/optics-tests/lib/types.ts
  61. 14
      typescript/optics-tests/lib/utils.ts
  62. 28131
      typescript/optics-tests/package-lock.json
  63. 16
      typescript/optics-tests/package.json
  64. 35
      typescript/optics-tests/test/common.test.ts
  65. 457
      typescript/optics-tests/test/cross-chain/governanceRouter.test.ts
  66. 115
      typescript/optics-tests/test/cross-chain/recoveryManager.test.ts
  67. 206
      typescript/optics-tests/test/cross-chain/simpleMessage.test.ts
  68. 165
      typescript/optics-tests/test/cross-chain/utils.ts
  69. 127
      typescript/optics-tests/test/home.test.ts
  70. 25
      typescript/optics-tests/test/merkle.test.ts
  71. 28
      typescript/optics-tests/test/message.test.ts
  72. 22
      typescript/optics-tests/test/queue.test.ts
  73. 277
      typescript/optics-tests/test/replica.test.ts
  74. 46
      typescript/optics-tests/test/testChain.ts
  75. 51
      typescript/optics-tests/test/upgrade.test.ts
  76. 96
      typescript/optics-tests/test/utils.ts
  77. 173
      typescript/optics-tests/test/xAppConnectionManager.test.ts
  78. 74
      typescript/optics-tests/tsconfig.json
  79. 0
      typescript/typechain/optics-core/BadRecipient1.d.ts
  80. 0
      typescript/typechain/optics-core/BadRecipient2.d.ts
  81. 0
      typescript/typechain/optics-core/BadRecipient3.d.ts
  82. 0
      typescript/typechain/optics-core/BadRecipient4.d.ts
  83. 0
      typescript/typechain/optics-core/BadRecipient5.d.ts
  84. 0
      typescript/typechain/optics-core/BadRecipient6.d.ts
  85. 118
      typescript/typechain/optics-core/BadRecipientHandle.d.ts
  86. 0
      typescript/typechain/optics-core/Common.d.ts
  87. 0
      typescript/typechain/optics-core/GovernanceRouter.d.ts
  88. 0
      typescript/typechain/optics-core/Home.d.ts
  89. 0
      typescript/typechain/optics-core/IMessageRecipient.d.ts
  90. 0
      typescript/typechain/optics-core/IUpdaterManager.d.ts
  91. 0
      typescript/typechain/optics-core/MaliciousRecipient.d.ts
  92. 0
      typescript/typechain/optics-core/MerkleTreeManager.d.ts
  93. 0
      typescript/typechain/optics-core/MysteryMath.d.ts
  94. 0
      typescript/typechain/optics-core/MysteryMathV1.d.ts
  95. 0
      typescript/typechain/optics-core/MysteryMathV2.d.ts
  96. 0
      typescript/typechain/optics-core/Ownable.d.ts
  97. 0
      typescript/typechain/optics-core/QueueManager.d.ts
  98. 0
      typescript/typechain/optics-core/Replica.d.ts
  99. 0
      typescript/typechain/optics-core/TestCommon.d.ts
  100. 0
      typescript/typechain/optics-core/TestGovernanceRouter.d.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -25,14 +25,21 @@ jobs:
- uses: actions/checkout@v2
- name: Install
run: npm i
- name: Test
run: npm test
run: |
npm i
cd ../../typescript/optics-tests
npm i
cd ../optics-deploy
npm i
cd ../typechain
npm i
- name: Lint
run: npm run lint
- name: Test
run: npm test
optics-xapps:
env:
ETHERSCAN_API_KEY: ""
@ -49,12 +56,12 @@ jobs:
- name: Install
run: npm i
- name: Test
run: npm test
- name: Lint
run: npm run lint
- name: Test
run: npm test
complete:
runs-on: ubuntu-latest
needs: [optics-core, optics-xapps]

2
.gitignore vendored

@ -1,8 +1,6 @@
node_modules
test_deploy.env
config.json
typescript/tmp.ts
rust/tmp_db
rust/tmp.env
tmp.env

@ -79,7 +79,7 @@ if ! git diff-index --quiet HEAD -- ./solidity/optics-core; then
# add abis, typechain
cd ../..
git add rust/optics-ethereum/abis
git add typescript/src/typechain
git add typescript/typechain
# add linter modified files
git add solidity/optics-core/contracts
git add solidity/optics-core/libs
@ -95,7 +95,7 @@ if ! git diff-index --quiet HEAD -- ./solidity/optics-xapps; then
npm run compile
# add typechain
cd ../..
git add typescript/src/typechain
git add typescript/typechain
# add linter modified files
git add solidity/optics-xapps/contracts
git add solidity/optics-xapps/interfaces

@ -89,8 +89,7 @@ mod test {
#[test]
fn it_computes_branch_roots() {
let test_json = test_utils::load_merkle_test_json();
let test_cases = test_json.test_cases;
let test_cases = test_utils::load_merkle_test_json();
for test_case in test_cases.iter() {
let mut tree = IncrementalMerkle::default();

@ -1,5 +1,8 @@
use crate::{
accumulator::{merkle::MerkleTree, TREE_DEPTH},
accumulator::{
merkle::{merkle_root_from_branch, MerkleTree},
TREE_DEPTH,
},
utils::{destination_and_sequence, home_domain_hash},
FailureNotification, OpticsMessage, Update,
};
@ -16,7 +19,7 @@ pub mod output_functions {
use super::*;
/// Output proof to /vector/messageTestCases.json
/// Output proof to /vector/message.json
pub fn output_message_and_leaf() {
let optics_message = OpticsMessage {
origin: 1000,
@ -40,13 +43,13 @@ pub mod output_functions {
"body": optics_message.body,
"leaf": optics_message.to_leaf(),
});
let json = json!({ "testCases": [message_json] }).to_string();
let json = json!([message_json]).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open("../../vectors/messageTestCases.json")
.open("../../vectors/message.json")
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
@ -57,21 +60,27 @@ pub mod output_functions {
pub fn output_merkle_proof() {
let mut tree = MerkleTree::create(&[], TREE_DEPTH);
let optics_message = OpticsMessage {
origin: 1000,
sender: H256::from(H160::from_str("0xd753c12650c280383Ce873Cc3a898F6f53973d16").unwrap()),
destination: 2000,
recipient: H256::from(H160::from_str("0xa779C1D17bC5230c07afdC51376CAC1cb3Dd5314").unwrap()),
sequence: 1,
body: Vec::from_hex("01010000000000000000000000006b39b761b1b64c8c095bf0e3bb0c6a74705b4788000000000000000000000000000000000000000000000000000000000000004499a88ec400000000000000000000000024432a08869578aaf4d1eada12e1e78f171b1a2b000000000000000000000000f66cfdf074d2ffd6a4037be3a669ed04380aef2b").unwrap(),
};
tree.push_leaf(optics_message.to_leaf(), TREE_DEPTH)
.unwrap();
let proof = tree.generate_proof(0, TREE_DEPTH);
let proof_json = json!({ "leaf": proof.0, "path": proof.1 });
let json = json!({ "proof": proof_json }).to_string();
let index = 1;
// kludge. this is a manual entry of the hash of the messages sent by the cross-chain governance upgrade tests
tree.push_leaf(
"0xd89959d277019eee21f1c3c270a125964d63b71876880724d287fbb8b8de55f1"
.parse()
.unwrap(),
TREE_DEPTH,
)
.unwrap();
tree.push_leaf(
"0x7d2185e5a65904eeb35980b8e335f72d31feccfdc12b9bc0f6cbe32073ea7fba"
.parse()
.unwrap(),
TREE_DEPTH,
)
.unwrap();
let proof = tree.generate_proof(index, TREE_DEPTH);
let proof_json = json!({ "leaf": proof.0, "path": proof.1, "index": index});
let json = json!({ "proof": proof_json, "root": merkle_root_from_branch(proof.0, &proof.1, 32, index)}).to_string();
let mut file = OpenOptions::new()
.write(true)
@ -84,7 +93,7 @@ pub mod output_functions {
.expect("Failed to write to file");
}
/// Outputs domain hash test cases in /vector/domainHashTestCases.json
/// Outputs domain hash test cases in /vector/domainHash.json
pub fn output_home_domain_hashes() {
let test_cases: Vec<Value> = (1..=3)
.map(|i| {
@ -95,13 +104,13 @@ pub mod output_functions {
})
.collect();
let json = json!({ "testCases": test_cases }).to_string();
let json = json!(test_cases).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open("../../vectors/homeDomainHashTestCases.json")
.open("../../vectors/homeDomainHash.json")
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
@ -109,7 +118,7 @@ pub mod output_functions {
}
/// Outputs combined destination and sequence test cases in /vector/
/// destinationSequenceTestCases.json
/// destinationSequence.json
pub fn output_destination_and_sequences() {
let test_cases: Vec<Value> = (1..=5)
.map(|i| {
@ -121,20 +130,20 @@ pub mod output_functions {
})
.collect();
let json = json!({ "testCases": test_cases }).to_string();
let json = json!(test_cases).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open("../../vectors/destinationSequenceTestCases.json")
.open("../../vectors/destinationSequence.json")
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
.expect("Failed to write to file");
}
/// Outputs signed update test cases in /vector/signedUpdateTestCases.json
/// Outputs signed update test cases in /vector/signedUpdate.json
pub fn output_signed_updates() {
let t = async {
let signer: ethers::signers::LocalWallet =
@ -164,13 +173,13 @@ pub mod output_functions {
}))
}
let json = json!({ "testCases": test_cases }).to_string();
let json = json!(test_cases).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open("../../vectors/signedUpdateTestCases.json")
.open("../../vectors/signedUpdate.json")
.expect("Failed to open/create file");
file.write_all(json.as_bytes())
@ -184,7 +193,7 @@ pub mod output_functions {
.block_on(t)
}
/// Outputs signed update test cases in /vector/signedFailureTestCases.json
/// Outputs signed update test cases in /vector/signedFailure.json
pub fn output_signed_failure_notifications() {
let t = async {
let signer: ethers::signers::LocalWallet =
@ -214,13 +223,13 @@ pub mod output_functions {
"signer": signer.address()
});
let json = json!({ "testCases": vec!(signed_json) }).to_string();
let json = json!(vec!(signed_json)).to_string();
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open("../../vectors/signedFailureTestCases.json")
.open("../../vectors/signedFailure.json")
.expect("Failed to open/create file");
file.write_all(json.as_bytes())

@ -16,17 +16,9 @@ pub struct MerkleTestCase {
pub expected_root: H256,
}
/// Struct containing vec of `MerkleTestCase`s
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MerkleTestJson {
/// Vec of `MerkleTestCase` structs
pub test_cases: Vec<MerkleTestCase>,
}
/// Reads merkle test case json file and returns a `MerkleTestJson`
pub fn load_merkle_test_json() -> MerkleTestJson {
let mut file = File::open("../../vectors/merkleTestCases.json").unwrap();
/// Reads merkle test case json file and returns a vector of `MerkleTestCase`s
pub fn load_merkle_test_json() -> Vec<MerkleTestCase> {
let mut file = File::open("../../vectors/merkle.json").unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
serde_json::from_str(&data).unwrap()

@ -169,8 +169,7 @@ mod test {
#[test]
fn it_produces_and_verifies_proofs() {
let test_json = test_utils::load_merkle_test_json();
let test_cases = test_json.test_cases;
let test_cases = test_utils::load_merkle_test_json();
for test_case in test_cases.iter() {
let mut tree = Prover::default();

@ -6,6 +6,7 @@ import {IMessageRecipient} from "../../interfaces/IMessageRecipient.sol";
contract TestRecipient is IMessageRecipient {
bool public processed = false;
// solhint-disable-next-line payable-fallback
fallback() external {
revert("Fallback");
}
@ -14,7 +15,7 @@ contract TestRecipient is IMessageRecipient {
uint32,
bytes32,
bytes memory
) external pure override {}
) external pure override {} // solhint-disable-line no-empty-blocks
function receiveString(string calldata _str)
public

@ -9,6 +9,6 @@ contract BadRecipient6 is IMessageRecipient {
bytes32,
bytes memory
) external pure override {
require(false);
require(false); // solhint-disable-line reason-string
}
}

@ -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,
* };
*/

@ -11,7 +11,7 @@
"dependencies": {
"@openzeppelin/contracts": "^3.4.0",
"@summa-tx/memview-sol": "^2.0.0",
"envy": "^2.0.0",
"dotenv": "^10.0.0",
"ts-generator": "^0.1.1"
},
"devDependencies": {
@ -31,6 +31,7 @@
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.14",
"ts-node": "^10.1.0",
"typechain": "^5.0.0"
}
},
@ -1597,6 +1598,30 @@
"web3": "1.4.0"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
"integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
"dev": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
"integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
"dev": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
"dev": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz",
"integrity": "sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==",
"dev": true
},
"node_modules/@typechain/ethers-v5": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.0.1.tgz",
@ -2065,6 +2090,12 @@
"node": ">= 8"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -2703,19 +2734,7 @@
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/camelcase-keys": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-5.2.0.tgz",
"integrity": "sha512-mSM/OQKD1HS5Ll2AXxeaHSdqCGC/QQ8IrgTbKYA/rxnC36thBKysfIr9+OVBWuW17jyZF4swHkjtglawgBmVFg==",
"dependencies": {
"camelcase": "^5.3.1",
"map-obj": "^3.0.0",
"quick-lru": "^1.0.0"
},
"dev": true,
"engines": {
"node": ">=6"
}
@ -3324,6 +3343,12 @@
"sha.js": "^2.4.8"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3634,6 +3659,14 @@
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
"dev": true
},
"node_modules/dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
"engines": {
"node": ">=10"
}
},
"node_modules/drbg.js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
@ -3745,21 +3778,6 @@
"node": ">=6"
}
},
"node_modules/envy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/envy/-/envy-2.0.0.tgz",
"integrity": "sha512-jNGMPzaDCfTsaW1Mp4nPyZ4HM3tnKNxOiMLPN7pi5LAgeHo7pHdJPdCHfQuLBZGK+Ok8Nc9Z3Py+ebKM3n3BgQ==",
"dependencies": {
"camelcase": "^5.0.0",
"camelcase-keys": "^5.0.0",
"filter-obj": "^1.1.0",
"is-wsl": "^1.1.0",
"path-type": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
@ -4939,14 +4957,6 @@
"node": ">=8"
}
},
"node_modules/filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -5206,7 +5216,104 @@
"bundleDependencies": [
"source-map-support",
"yargs",
"ethereumjs-util"
"ethereumjs-util",
"@types/bn.js",
"@types/node",
"@types/pbkdf2",
"@types/secp256k1",
"ansi-regex",
"ansi-styles",
"base-x",
"blakejs",
"bn.js",
"brorand",
"browserify-aes",
"bs58",
"bs58check",
"buffer-from",
"buffer-xor",
"camelcase",
"cipher-base",
"cliui",
"color-convert",
"color-name",
"create-hash",
"create-hmac",
"cross-spawn",
"decamelize",
"elliptic",
"emoji-regex",
"end-of-stream",
"ethereum-cryptography",
"ethjs-util",
"evp_bytestokey",
"execa",
"find-up",
"get-caller-file",
"get-stream",
"hash-base",
"hash.js",
"hmac-drbg",
"inherits",
"invert-kv",
"is-fullwidth-code-point",
"is-hex-prefixed",
"is-stream",
"isexe",
"keccak",
"lcid",
"locate-path",
"map-age-cleaner",
"md5.js",
"mem",
"mimic-fn",
"minimalistic-assert",
"minimalistic-crypto-utils",
"nice-try",
"node-addon-api",
"node-gyp-build",
"npm-run-path",
"once",
"os-locale",
"p-defer",
"p-finally",
"p-is-promise",
"p-limit",
"p-locate",
"p-try",
"path-exists",
"path-key",
"pbkdf2",
"pump",
"randombytes",
"readable-stream",
"require-directory",
"require-main-filename",
"ripemd160",
"rlp",
"safe-buffer",
"scrypt-js",
"secp256k1",
"semver",
"set-blocking",
"setimmediate",
"sha.js",
"shebang-command",
"shebang-regex",
"signal-exit",
"source-map",
"string_decoder",
"string-width",
"strip-ansi",
"strip-eof",
"strip-hex-prefix",
"util-deprecate",
"which",
"which-module",
"wrap-ansi",
"wrappy",
"y18n",
"yargs-parser"
],
"dev": true,
"dependencies": {
@ -6253,7 +6360,9 @@
"resolved": "https://registry.npmjs.org/ganache-core/-/ganache-core-2.13.2.tgz",
"integrity": "sha512-tIF5cR+ANQz0+3pHWxHjIwHqFXcVo0Mb+kcsNhglNFALcYo49aQpnS9dqHartqPfMFjiHh/qFoD3mYK0d/qGgw==",
"bundleDependencies": [
"keccak"
"keccak",
"node-addon-api",
"node-gyp-build"
],
"dev": true,
"hasShrinkwrap": true,
@ -16710,14 +16819,6 @@
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true
},
"node_modules/is-wsl": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
"integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
"engines": {
"node": ">=4"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@ -17167,13 +17268,11 @@
"integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=",
"dev": true
},
"node_modules/map-obj": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-3.1.0.tgz",
"integrity": "sha512-Xg1iyYz/+iIW6YoMldux47H/e5QZyDSB41Kb0ev+YYHh3FJnyyzY0vTk/WbVeWcCvdXd70cOriUBmhP8alUFBA==",
"engines": {
"node": ">=6"
}
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/markdown-table": {
"version": "1.1.3",
@ -18576,17 +18675,6 @@
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"dev": true
},
"node_modules/path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"dependencies": {
"pify": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pathval": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
@ -18630,14 +18718,6 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"engines": {
"node": ">=4"
}
},
"node_modules/pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@ -18925,14 +19005,6 @@
}
]
},
"node_modules/quick-lru": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz",
"integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=",
"engines": {
"node": ">=4"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -21032,6 +21104,57 @@
"ts-generator": "dist/cli/run.js"
}
},
"node_modules/ts-node": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
"dev": true,
"dependencies": {
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/ts-node/node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -22492,6 +22615,15 @@
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"engines": {
"node": ">=6"
}
}
},
"dependencies": {
@ -23680,6 +23812,30 @@
"web3": "1.4.0"
}
},
"@tsconfig/node10": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
"integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
"dev": true
},
"@tsconfig/node12": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
"integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
"dev": true
},
"@tsconfig/node14": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
"dev": true
},
"@tsconfig/node16": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.1.tgz",
"integrity": "sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==",
"dev": true
},
"@typechain/ethers-v5": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.0.1.tgz",
@ -24074,6 +24230,12 @@
"picomatch": "^2.0.4"
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -24614,17 +24776,8 @@
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"camelcase-keys": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-5.2.0.tgz",
"integrity": "sha512-mSM/OQKD1HS5Ll2AXxeaHSdqCGC/QQ8IrgTbKYA/rxnC36thBKysfIr9+OVBWuW17jyZF4swHkjtglawgBmVFg==",
"requires": {
"camelcase": "^5.3.1",
"map-obj": "^3.0.0",
"quick-lru": "^1.0.0"
}
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"caseless": {
"version": "0.12.0",
@ -25136,6 +25289,12 @@
"sha.js": "^2.4.8"
}
},
"create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -25386,6 +25545,11 @@
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
"dev": true
},
"dotenv": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="
},
"drbg.js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
@ -25482,18 +25646,6 @@
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true
},
"envy": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/envy/-/envy-2.0.0.tgz",
"integrity": "sha512-jNGMPzaDCfTsaW1Mp4nPyZ4HM3tnKNxOiMLPN7pi5LAgeHo7pHdJPdCHfQuLBZGK+Ok8Nc9Z3Py+ebKM3n3BgQ==",
"requires": {
"camelcase": "^5.0.0",
"camelcase-keys": "^5.0.0",
"filter-obj": "^1.1.0",
"is-wsl": "^1.1.0",
"path-type": "^3.0.0"
}
},
"errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
@ -26488,11 +26640,6 @@
"to-regex-range": "^5.0.1"
}
},
"filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@ -35476,11 +35623,6 @@
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true
},
"is-wsl": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
"integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@ -35861,10 +36003,11 @@
"integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=",
"dev": true
},
"map-obj": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-3.1.0.tgz",
"integrity": "sha512-Xg1iyYz/+iIW6YoMldux47H/e5QZyDSB41Kb0ev+YYHh3FJnyyzY0vTk/WbVeWcCvdXd70cOriUBmhP8alUFBA=="
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"markdown-table": {
"version": "1.1.3",
@ -36994,14 +37137,6 @@
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
"dev": true
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"requires": {
"pify": "^3.0.0"
}
},
"pathval": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
@ -37033,11 +37168,6 @@
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true
},
"pify": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY="
},
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
@ -37245,11 +37375,6 @@
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"quick-lru": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz",
"integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g="
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -38910,6 +39035,32 @@
"ts-essentials": "^1.0.0"
}
},
"ts-node": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.1.0.tgz",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
"dev": true,
"requires": {
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.17",
"yn": "3.1.1"
},
"dependencies": {
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
}
}
},
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@ -40117,6 +40268,12 @@
}
}
}
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
}
}
}

@ -17,6 +17,7 @@
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.14",
"ts-node": "^10.1.0",
"typechain": "^5.0.0"
},
"version": "0.0.0",
@ -26,12 +27,12 @@
"test": "test"
},
"scripts": {
"prettier": "prettier --write ./contracts ./test ./js",
"compile": "hardhat compile && hardhat typechain && npm run prettier && ./js/update_abis.sh",
"prettier": "prettier --write ./contracts",
"compile": "hardhat compile && hardhat typechain && npm run prettier && ./update_abis.sh",
"coverage": "npm run compile && hardhat coverage",
"test": "npm run compile && hardhat test",
"lint": "solhint 'contracts/*.sol' && solhint 'contracts/**/*.sol' && eslint test/",
"lint:fix": "solhint --fix 'contracts/*.sol' && solhint --fix 'contracts/**/*.sol' && eslint --fix test/",
"test": "./test.sh",
"lint": "solhint 'contracts/*.sol' && solhint 'contracts/**/*.sol'",
"lint:fix": "solhint --fix 'contracts/*.sol' && solhint --fix 'contracts/**/*.sol'",
"verify": "hardhat --network kovan verify-latest-deploy"
},
"author": "James Prestwich",
@ -39,7 +40,7 @@
"dependencies": {
"@openzeppelin/contracts": "^3.4.0",
"@summa-tx/memview-sol": "^2.0.0",
"envy": "^2.0.0",
"dotenv": "^10.0.0",
"ts-generator": "^0.1.1"
}
}

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

@ -2,9 +2,7 @@
pragma solidity >=0.6.11;
// ============ External Imports ============
import {
IMessageRecipient
} from "@celo-org/optics-sol/interfaces/IMessageRecipient.sol";
import {IMessageRecipient} from "@celo-org/optics-sol/interfaces/IMessageRecipient.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
abstract contract Router is Ownable, IMessageRecipient {

@ -3,9 +3,7 @@ pragma solidity >=0.6.11;
// ============ External Imports ============
import {Home} from "@celo-org/optics-sol/contracts/Home.sol";
import {
XAppConnectionManager
} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {XAppConnectionManager} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
abstract contract XAppConnectionClient is Ownable {

@ -8,9 +8,7 @@ import {IBridgeToken} from "../../interfaces/bridge/IBridgeToken.sol";
import {BridgeMessage} from "./BridgeMessage.sol";
// ============ External Imports ============
import {Home} from "@celo-org/optics-sol/contracts/Home.sol";
import {
TypeCasts
} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {TypeCasts} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
@ -109,12 +107,11 @@ contract BridgeRouter is Router, TokenRegistry {
bytes32 _remote = _mustHaveRemote(_destination);
// format Update Details message
IBridgeToken _bridgeToken = IBridgeToken(_token);
bytes29 _action =
BridgeMessage.formatDetails(
TypeCasts.coerceBytes32(_bridgeToken.name()),
TypeCasts.coerceBytes32(_bridgeToken.symbol()),
_bridgeToken.decimals()
);
bytes29 _action = BridgeMessage.formatDetails(
TypeCasts.coerceBytes32(_bridgeToken.name()),
TypeCasts.coerceBytes32(_bridgeToken.symbol()),
_bridgeToken.decimals()
);
// send message to remote chain via Optics
Home(xAppConnectionManager.home()).enqueue(
_destination,

@ -5,9 +5,7 @@ pragma solidity >=0.6.11;
import {IBridgeToken} from "../../interfaces/bridge/IBridgeToken.sol";
import {ERC20} from "./OZERC20.sol";
// ============ External Imports ============
import {
TypeCasts
} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {TypeCasts} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract BridgeToken is IBridgeToken, Ownable, ERC20 {

@ -7,10 +7,7 @@ import {BridgeToken} from "./BridgeToken.sol";
import {IBridgeToken} from "../../interfaces/bridge/IBridgeToken.sol";
import {XAppConnectionClient} from "../XAppConnectionClient.sol";
// ============ External Imports ============
import {
XAppConnectionManager,
TypeCasts
} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {XAppConnectionManager, TypeCasts} from "@celo-org/optics-sol/contracts/XAppConnectionManager.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";

@ -153,10 +153,9 @@ contract PingPongRouter is Router, XAppConnectionClient {
bytes32 _remoteRouterAddress = _mustHaveRemote(_destinationDomain);
// format the ping message
bytes memory _message =
_isPing
? PingPongMessage.formatPing(_match, _count)
: PingPongMessage.formatPong(_match, _count);
bytes memory _message = _isPing
? PingPongMessage.formatPing(_match, _count)
: PingPongMessage.formatPong(_match, _count);
// send the message to the xApp Router
(_home()).enqueue(_destinationDomain, _remoteRouterAddress, _message);

@ -1,14 +1,14 @@
require('hardhat-gas-reporter');
require('solidity-coverage');
require('@nomiclabs/hardhat-waffle');
require('@typechain/hardhat');
import "hardhat-gas-reporter";
import "solidity-coverage";
import "@nomiclabs/hardhat-waffle";
import "@typechain/hardhat";
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
version: '0.7.6',
version: "0.7.6",
settings: {
optimizer: {
enabled: true,
@ -18,18 +18,18 @@ module.exports = {
},
gasReporter: {
currency: 'USD',
currency: "USD",
},
networks: {
localhost: {
url: 'http://localhost:8545',
url: "http://localhost:8545",
},
},
typechain: {
outDir: '../../typescript/src/typechain/optics-xapps',
target: 'ethers-v5',
outDir: "../../typescript/typechain/optics-xapps",
target: "ethers-v5",
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
},
};

File diff suppressed because it is too large Load Diff

@ -17,21 +17,20 @@
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.14",
"typechain": "^5.0.0"
"ts-node": "^10.1.0",
"typechain": "^5.0.0",
"typescript": "^4.3.5"
},
"version": "0.0.0",
"description": "A bridge using optics",
"main": " ",
"directories": {
"test": "test"
},
"scripts": {
"prettier": "prettier --write './contracts' ./test",
"prettier": "prettier --write './contracts'",
"compile": "hardhat compile && hardhat typechain && npm run prettier",
"coverage": "npm run compile && hardhat coverage",
"test": "npm run compile && hardhat test",
"lint": "solhint contracts/*.sol && eslint test/",
"lint:fix": "solhint --fix contracts/*.sol && eslint --fix test/"
"test": "npm run compile",
"lint": "solhint contracts/*.sol",
"lint:fix": "solhint --fix contracts/*.sol"
},
"author": "James Prestwich",
"license": "MIT OR Apache-2.0",

@ -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 @@
node_modules

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all"
}

@ -1,30 +1,26 @@
{
"name": "optics-ts",
"name": "optics-deploy",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "optics-ts",
"version": "1.0.0",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@ethersproject/experimental": "^5.3.0",
"@types/node": "^15.6.1",
"ethers": "^5.2.0"
"@ethersproject/experimental": "^5.3.0"
},
"devDependencies": {
"@typechain/ethers-v5": "^7.0.0",
"mkdirp": "^1.0.4",
"prettier": "2.3.0",
"ethers": "^5.3.1",
"prettier": "^2.3.1",
"typechain": "^5.0.0",
"typescript": "^4.3.2"
}
},
"node_modules/@ethersproject/abi": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.3.0.tgz",
"integrity": "sha512-NaT4UacjOwca8qCG/gv8k+DgTcWu49xlrvdhr/p8PTFnoS8e3aMWqjI3znFME5Txa/QWXDrg2/heufIUue9rtw==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.3.1.tgz",
"integrity": "sha512-F98FWTJG7nWWAQ4DcV6R0cSlrj67MWK3ylahuFbzkumem5cLWg1p7fZ3vIdRoS1c7TEf55Lvyx0w7ICR47IImw==",
"funding": [
{
"type": "individual",
@ -374,9 +370,9 @@
]
},
"node_modules/@ethersproject/networks": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.3.0.tgz",
"integrity": "sha512-XGbD9MMgqrR7SYz8o6xVgdG+25v7YT5vQG8ZdlcLj2I7elOBM7VNeQrnxfSN7rWQNcqu2z80OM29gGbQz+4Low==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.3.1.tgz",
"integrity": "sha512-6uQKHkYChlsfeiZhQ8IHIqGE/sQsf25o9ZxAYpMxi15dLPzz3IxOEF5KiSD32aHwsjXVBKBSlo+teAXLlYJybw==",
"funding": [
{
"type": "individual",
@ -429,9 +425,9 @@
}
},
"node_modules/@ethersproject/providers": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.3.0.tgz",
"integrity": "sha512-HtL+DEbzPcRyfrkrMay7Rk/4he+NbUpzI/wHXP4Cqtra82nQOnqqCgTQc4HbdDrl75WVxG/JRMFhyneIPIMZaA==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.3.1.tgz",
"integrity": "sha512-HC63vENTrur6/JKEhcQbA8PRDj1FAesdpX98IW+xAAo3EAkf70ou5fMIA3KCGzJDLNTeYA4C2Bonz849tVLekg==",
"funding": [
{
"type": "individual",
@ -709,29 +705,10 @@
"@ethersproject/strings": "^5.3.0"
}
},
"node_modules/@typechain/ethers-v5": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.0.0.tgz",
"integrity": "sha512-ykNaqYcQ1yC928x8bogL9LECUg0osfqqHCKBhP7qbGlNfvC/bvTiIfnjQUgXUYWEJRx5r0Y78vcKMo8F3sJTBA==",
"dev": true,
"peerDependencies": {
"@ethersproject/abi": "^5.0.0",
"@ethersproject/bytes": "^5.0.0",
"@ethersproject/providers": "^5.0.0",
"ethers": "^5.1.3",
"typechain": "^5.0.0",
"typescript": ">=4.0.0"
}
},
"node_modules/@types/node": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
"integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA=="
},
"node_modules/@types/prettier": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz",
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-hkc1DATxFLQo4VxPDpMH1gCkPpBbpOoJ/4nhuXw4n63/0R6bCpQECj4+K226UJ4JO/eJQz+1mC2I7JsWanAdQw==",
"dev": true
},
"node_modules/aes-js": {
@ -834,9 +811,9 @@
}
},
"node_modules/ethers": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.3.0.tgz",
"integrity": "sha512-myN+338S4sFQZvQ9trii7xit8Hu/LnUtjA0ROFOHpUreQc3fgLZEMNVqF3vM1u2D78DIIeG1TbuozVCVlXQWvQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.3.1.tgz",
"integrity": "sha512-xCKmC0gsZ9gks89ZfK3B1y6LlPdvX5fxDtu9SytnpdDJR1M7pmJI+4H0AxQPMgUYr7GtQdmECLR0gWdJQ+lZYw==",
"funding": [
{
"type": "individual",
@ -848,7 +825,7 @@
}
],
"dependencies": {
"@ethersproject/abi": "5.3.0",
"@ethersproject/abi": "5.3.1",
"@ethersproject/abstract-provider": "5.3.0",
"@ethersproject/abstract-signer": "5.3.0",
"@ethersproject/address": "5.3.0",
@ -863,10 +840,10 @@
"@ethersproject/json-wallets": "5.3.0",
"@ethersproject/keccak256": "5.3.0",
"@ethersproject/logger": "5.3.0",
"@ethersproject/networks": "5.3.0",
"@ethersproject/networks": "5.3.1",
"@ethersproject/pbkdf2": "5.3.0",
"@ethersproject/properties": "5.3.0",
"@ethersproject/providers": "5.3.0",
"@ethersproject/providers": "5.3.1",
"@ethersproject/random": "5.3.0",
"@ethersproject/rlp": "5.3.0",
"@ethersproject/sha2": "5.3.0",
@ -1027,18 +1004,6 @@
"node": "*"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -1064,10 +1029,11 @@
}
},
"node_modules/prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz",
"integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin-prettier.js"
},
@ -1106,9 +1072,9 @@
}
},
"node_modules/ts-essentials": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.1.tgz",
"integrity": "sha512-8lwh3QJtIc1UWhkQtr9XuksXu3O0YQdEE5g79guDfhCaU1FWTDIEDZ1ZSx4HTHUmlJZ8L812j3BZQ4a0aOUkSA==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.2.tgz",
"integrity": "sha512-qWPVC1xZGdefbsgFP7tPo+bsgSA2ZIXL1XeEe5M2WoMZxIOr/HbsHxP/Iv75IFhiMHMDGL7cOOwi5SXcgx9mHw==",
"dev": true,
"peerDependencies": {
"typescript": ">=3.7.0"
@ -1197,9 +1163,9 @@
},
"dependencies": {
"@ethersproject/abi": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.3.0.tgz",
"integrity": "sha512-NaT4UacjOwca8qCG/gv8k+DgTcWu49xlrvdhr/p8PTFnoS8e3aMWqjI3znFME5Txa/QWXDrg2/heufIUue9rtw==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.3.1.tgz",
"integrity": "sha512-F98FWTJG7nWWAQ4DcV6R0cSlrj67MWK3ylahuFbzkumem5cLWg1p7fZ3vIdRoS1c7TEf55Lvyx0w7ICR47IImw==",
"requires": {
"@ethersproject/address": "^5.3.0",
"@ethersproject/bignumber": "^5.3.0",
@ -1389,9 +1355,9 @@
"integrity": "sha512-8bwJ2gxJGkZZnpQSq5uSiZSJjyVTWmlGft4oH8vxHdvO1Asy4TwVepAhPgxIQIMxXZFUNMych1YjIV4oQ4I7dA=="
},
"@ethersproject/networks": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.3.0.tgz",
"integrity": "sha512-XGbD9MMgqrR7SYz8o6xVgdG+25v7YT5vQG8ZdlcLj2I7elOBM7VNeQrnxfSN7rWQNcqu2z80OM29gGbQz+4Low==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.3.1.tgz",
"integrity": "sha512-6uQKHkYChlsfeiZhQ8IHIqGE/sQsf25o9ZxAYpMxi15dLPzz3IxOEF5KiSD32aHwsjXVBKBSlo+teAXLlYJybw==",
"requires": {
"@ethersproject/logger": "^5.3.0"
}
@ -1414,9 +1380,9 @@
}
},
"@ethersproject/providers": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.3.0.tgz",
"integrity": "sha512-HtL+DEbzPcRyfrkrMay7Rk/4he+NbUpzI/wHXP4Cqtra82nQOnqqCgTQc4HbdDrl75WVxG/JRMFhyneIPIMZaA==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.3.1.tgz",
"integrity": "sha512-HC63vENTrur6/JKEhcQbA8PRDj1FAesdpX98IW+xAAo3EAkf70ou5fMIA3KCGzJDLNTeYA4C2Bonz849tVLekg==",
"requires": {
"@ethersproject/abstract-provider": "^5.3.0",
"@ethersproject/abstract-signer": "^5.3.0",
@ -1574,22 +1540,10 @@
"@ethersproject/strings": "^5.3.0"
}
},
"@typechain/ethers-v5": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.0.0.tgz",
"integrity": "sha512-ykNaqYcQ1yC928x8bogL9LECUg0osfqqHCKBhP7qbGlNfvC/bvTiIfnjQUgXUYWEJRx5r0Y78vcKMo8F3sJTBA==",
"dev": true,
"requires": {}
},
"@types/node": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz",
"integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA=="
},
"@types/prettier": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz",
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-hkc1DATxFLQo4VxPDpMH1gCkPpBbpOoJ/4nhuXw4n63/0R6bCpQECj4+K226UJ4JO/eJQz+1mC2I7JsWanAdQw==",
"dev": true
},
"aes-js": {
@ -1678,11 +1632,11 @@
}
},
"ethers": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.3.0.tgz",
"integrity": "sha512-myN+338S4sFQZvQ9trii7xit8Hu/LnUtjA0ROFOHpUreQc3fgLZEMNVqF3vM1u2D78DIIeG1TbuozVCVlXQWvQ==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.3.1.tgz",
"integrity": "sha512-xCKmC0gsZ9gks89ZfK3B1y6LlPdvX5fxDtu9SytnpdDJR1M7pmJI+4H0AxQPMgUYr7GtQdmECLR0gWdJQ+lZYw==",
"requires": {
"@ethersproject/abi": "5.3.0",
"@ethersproject/abi": "5.3.1",
"@ethersproject/abstract-provider": "5.3.0",
"@ethersproject/abstract-signer": "5.3.0",
"@ethersproject/address": "5.3.0",
@ -1697,10 +1651,10 @@
"@ethersproject/json-wallets": "5.3.0",
"@ethersproject/keccak256": "5.3.0",
"@ethersproject/logger": "5.3.0",
"@ethersproject/networks": "5.3.0",
"@ethersproject/networks": "5.3.1",
"@ethersproject/pbkdf2": "5.3.0",
"@ethersproject/properties": "5.3.0",
"@ethersproject/providers": "5.3.0",
"@ethersproject/providers": "5.3.1",
"@ethersproject/random": "5.3.0",
"@ethersproject/rlp": "5.3.0",
"@ethersproject/sha2": "5.3.0",
@ -1845,12 +1799,6 @@
"brace-expansion": "^1.1.7"
}
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -1873,9 +1821,9 @@
"dev": true
},
"prettier": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz",
"integrity": "sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz",
"integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==",
"dev": true
},
"scrypt-js": {
@ -1905,9 +1853,9 @@
}
},
"ts-essentials": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.1.tgz",
"integrity": "sha512-8lwh3QJtIc1UWhkQtr9XuksXu3O0YQdEE5g79guDfhCaU1FWTDIEDZ1ZSx4HTHUmlJZ8L812j3BZQ4a0aOUkSA==",
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.2.tgz",
"integrity": "sha512-qWPVC1xZGdefbsgFP7tPo+bsgSA2ZIXL1XeEe5M2WoMZxIOr/HbsHxP/Iv75IFhiMHMDGL7cOOwi5SXcgx9mHw==",
"dev": true,
"requires": {}
},

@ -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"
}
}

@ -1,15 +1,16 @@
import * as ethers from 'ethers';
import { BigNumber } from 'ethers';
import { BeaconProxy, ProxyAddresses } from './proxyUtils';
import * as contracts from './typechain/optics-core';
import { NonceManager } from '@ethersproject/experimental';
export type Address = string;
import { BeaconProxy, ProxyAddresses } from './proxyUtils';
import * as contracts from '../../typechain/optics-core';
type Address = string;
// Optic's complete contract suite
export type Contracts = {
upgradeBeaconController?: contracts.UpgradeBeaconController;
xappConnectionManager?: contracts.XAppConnectionManager;
xAppConnectionManager?: contracts.XAppConnectionManager;
updaterManager?: contracts.UpdaterManager;
governance?: BeaconProxy<contracts.GovernanceRouter>;
@ -35,7 +36,7 @@ export function toJson(contracts: Contracts): string {
return JSON.stringify(
{
upgradeBeaconController: contracts.upgradeBeaconController!.address,
xappConnectionManager: contracts.xappConnectionManager!.address,
xAppConnectionManager: contracts.xAppConnectionManager!.address,
updaterManager: contracts.updaterManager!.address,
governance: {
implementation: contracts.governance!.implementation.address,
@ -66,12 +67,12 @@ export interface ChainConfig {
optimisticSeconds: number;
watchers?: Address[];
gasPrice?: ethers.BigNumberish;
confirmations?: number;
}
// deserialized version of the ChainConfig
export type Chain = {
name: string;
config: ChainConfig;
provider: ethers.providers.Provider;
deployer: ethers.Signer;
domain: number;
@ -81,6 +82,8 @@ export type Chain = {
optimisticSeconds: number;
watchers: Address[];
gasPrice: ethers.BigNumber;
confirmations: number;
config?: ChainConfig;
};
export type ContractVerificationInput = {
@ -94,6 +97,7 @@ export type Deploy = {
chain: Chain;
contracts: Contracts;
verificationInput: ContractVerificationInput[];
test?: boolean;
};
/**
@ -117,6 +121,7 @@ export function toChain(config: ChainConfig): Chain {
optimisticSeconds: config.optimisticSeconds,
watchers: config.watchers ?? [],
gasPrice: BigNumber.from(config.gasPrice ?? '20000000000'),
confirmations: config.confirmations ?? 5,
};
}
@ -170,7 +175,7 @@ export function buildConfig(local: Deploy, remotes: Deploy[]): RustConfig {
rpcStyle: 'ethereum', // TODO
connection: {
type: 'http', // TODO
url: local.chain.config.rpc,
url: local.chain.config!.rpc,
},
};
@ -195,7 +200,7 @@ export function buildConfig(local: Deploy, remotes: Deploy[]): RustConfig {
rpcStyle: 'ethereum',
connection: {
type: 'http',
url: remote.chain.config.rpc,
url: remote.chain.config!.rpc,
},
};

@ -1,33 +1,48 @@
// TODO: fix ts errors here
// @ts-nocheck
import * as ethers from 'ethers';
import * as contracts from './typechain/optics-core';
import fs from 'fs';
import * as proxyUtils from './proxyUtils';
import { Deploy, toJson, buildConfig } from './chain';
import { toBytes32 } from '../../optics-tests/lib/utils';
import * as contracts from '../../typechain/optics-core';
function toBytes32(address: string): string {
let addr = ethers.utils.getAddress(address);
return '0x' + '00'.repeat(12) + address.slice(2);
function log(isTest: boolean, str: string) {
if (!isTest) {
console.log(str);
}
}
/**
* Deploys the UpgradeBeaconController on the chain of the given deploy and updates
* the deploy instance with the new contract.
*
* @param deploy - The deploy instance
*/
async function deployUpgradeBeaconController(deploy: Deploy) {
function warn(text: string, padded: boolean) {
if (padded) {
const padding = '*'.repeat(text.length + 8)
console.log(
`
${padding}
*** ${text.toUpperCase()} ***
${padding}
`
)
} else {
console.log(`**** ${text.toUpperCase()} ****`)
}
}
export async function deployUpgradeBeaconController(deploy: Deploy) {
let factory = new contracts.UpgradeBeaconController__factory(
deploy.chain.deployer,
);
deploy.contracts.upgradeBeaconController = await factory.deploy({
gasPrice: deploy.chain.gasPrice,
});
await deploy.contracts.upgradeBeaconController.deployTransaction.wait(5);
await deploy.contracts.upgradeBeaconController.deployTransaction.wait(deploy.chain.confirmations);
// add contract information to Etherscan verification array
deploy.verificationInput.push({
name: 'UpgradeBeaconController',
address: deploy.contracts.upgradeBeaconController!.address,
address: deploy.contracts.upgradeBeaconController.address,
constructorArguments: [],
});
}
@ -38,12 +53,12 @@ async function deployUpgradeBeaconController(deploy: Deploy) {
*
* @param deploy - The deploy instance
*/
async function deployUpdaterManager(deploy: Deploy) {
export async function deployUpdaterManager(deploy: Deploy) {
let factory = new contracts.UpdaterManager__factory(deploy.chain.deployer);
deploy.contracts.updaterManager = await factory.deploy(deploy.chain.updater, {
gasPrice: deploy.chain.gasPrice,
});
await deploy.contracts.updaterManager.deployTransaction.wait(5);
await deploy.contracts.updaterManager!.deployTransaction.wait(deploy.chain.confirmations);
// add contract information to Etherscan verification array
deploy.verificationInput.push({
@ -59,14 +74,14 @@ async function deployUpdaterManager(deploy: Deploy) {
*
* @param deploy - The deploy instance
*/
async function deployXAppConnectionManager(deploy: Deploy) {
export async function deployXAppConnectionManager(deploy: Deploy) {
let factory = new contracts.XAppConnectionManager__factory(
deploy.chain.deployer,
);
deploy.contracts.xappConnectionManager = await factory.deploy({
gasPrice: deploy.chain.gasPrice,
});
await deploy.contracts.xappConnectionManager.deployTransaction.wait(5);
await deploy.contracts.xappConnectionManager.deployTransaction.wait(deploy.chain.confirmations);
// add contract information to Etherscan verification array
deploy.verificationInput.push({
@ -77,87 +92,136 @@ async function deployXAppConnectionManager(deploy: Deploy) {
}
/**
* Deploys the Home proxy on the chain of the given deploy and updates
* Deploys the UpdaterManager on the chain of the given deploy and updates
* the deploy instance with the new contract.
*
* @param deploy - The deploy instance
*/
async function deployHome(deploy: Deploy) {
let { updaterManager } = deploy.contracts;
let initData = contracts.Home__factory.createInterface().encodeFunctionData(
'initialize',
[updaterManager!.address],
export async function deployUpdaterManager(deploy: Deploy) {
let factory = new contracts.UpdaterManager__factory(deploy.chain.deployer);
deploy.contracts.updaterManager = await factory.deploy(deploy.chain.updater, {
gasPrice: deploy.chain.gasPrice,
});
await deploy.contracts.updaterManager.deployTransaction.wait(
deploy.chain.confirmations,
);
}
const home = await proxyUtils.deployProxy<contracts.Home>(
deploy,
new contracts.Home__factory(deploy.chain.deployer),
initData,
deploy.chain.domain,
);
/**
* Deploys the XAppConnectionManager on the chain of the given deploy and updates
* the deploy instance with the new contract.
*
* @param deploy - The deploy instance
*/
export async function deployXAppConnectionManager(deploy: Deploy) {
const isTestDeploy: boolean = deploy.test;
if (isTestDeploy) warn('deploying test XAppConnectionManager')
const deployer = deploy.chain.deployer;
const factory = isTestDeploy
? new contracts.TestXAppConnectionManager__factory(deployer)
: contracts.XAppConnectionManager__factory(deployer);
deploy.contracts.home = home;
deploy.contracts.xAppConnectionManager = await factory.deploy({
gasPrice: deploy.chain.gasPrice,
});
await deploy.contracts.xAppConnectionManager.deployTransaction.wait(
deploy.chain.confirmations,
);
}
/**
* Deploys the GovernanceRouter proxy on the chain of the given deploy and updates
* Deploys the Home proxy on the chain of the given deploy and updates
* the deploy instance with the new contract.
*
* @param deploy - The deploy instance
*/
async function deployGovernanceRouter(deploy: Deploy) {
let { recoveryManager, recoveryTimelock } = deploy.chain;
let { xappConnectionManager } = deploy.contracts;
let initData =
contracts.GovernanceRouter__factory.createInterface().encodeFunctionData(
'initialize',
[xappConnectionManager!.address, recoveryManager],
);
export async function deployHome(deploy: Deploy) {
const isTestDeploy: boolean = deploy.test;
if (isTestDeploy) warn('deploying test Home')
const home = isTestDeploy
? contracts.TestHome__factory
: contracts.Home__factory;
let { updaterManager } = deploy.contracts;
let initData = home
.createInterface()
.encodeFunctionData('initialize', [updaterManager!.address]);
const governance = await proxyUtils.deployProxy<contracts.GovernanceRouter>(
deploy.contracts.home = await proxyUtils.deployProxy<contracts.Home>(
deploy,
new contracts.GovernanceRouter__factory(deploy.chain.deployer),
new home(deploy.chain.deployer),
initData,
deploy.chain.domain,
recoveryTimelock,
);
}
deploy.contracts.governance = governance;
/**
* Deploys the GovernanceRouter proxy on the chain of the given deploy and updates
* the deploy instance with the new contract.
*
* @param deploy - The deploy instance
*/
export async function deployGovernanceRouter(deploy: Deploy) {
const isTestDeploy: boolean = deploy.test;
if (isTestDeploy) warn('deploying test GovernanceRouter')
const governanceRouter = isTestDeploy
? contracts.TestGovernanceRouter__factory
: contracts.GovernanceRouter__factory;
let { xAppConnectionManager } = deploy.contracts;
const recoveryManager = deploy.chain.recoveryManager;
const recoveryTimelock = 1;
let initData = governanceRouter
.createInterface()
.encodeFunctionData('initialize', [xAppConnectionManager!.address, recoveryManager]);
deploy.contracts.governance =
await proxyUtils.deployProxy<contracts.GovernanceRouter>(
deploy,
new governanceRouter(deploy.chain.deployer),
initData,
deploy.chain.domain,
recoveryTimelock,
);
}
/**
* Deploys a Replica proxy on the local chain and updates the local deploy
* instance with the new contract.
* Deploys an unenrolled Replica proxy on the local chain and updates the local
* deploy instance with the new contract.
*
* @param local - The local deploy instance
* @param remote - The remote deploy instance
*/
async function deployNewReplica(local: Deploy, remote: Deploy) {
console.log(
export async function deployUnenrolledReplica(local: Deploy, remote: Deploy) {
const isTestDeploy: boolean = remote.test;
if (isTestDeploy) warn('deploying test Replica');
log(
isTestDeploy,
`${local.chain.name}: deploying replica for domain ${remote.chain.name}`,
);
const factory = new contracts.Replica__factory(local.chain.deployer);
// Workaround because typechain doesn't handle overloads well, and Replica
// has two public initializers
const iface = contracts.Replica__factory.createInterface();
const initIFace = new ethers.utils.Interface([
iface.functions['initialize(uint32,address,bytes32,uint256,uint32)'],
]);
const initData = initIFace.encodeFunctionData('initialize', [
remote.chain.domain,
remote.chain.updater,
ethers.constants.HashZero, // TODO: allow configuration
remote.chain.optimisticSeconds,
0, // TODO: allow configuration
]);
const replica = isTestDeploy
? contracts.TestReplica__factory
: contracts.Replica__factory;
const factory = new replica(local.chain.deployer);
let initData = replica
.createInterface()
.encodeFunctionData('initialize', [
remote.chain.domain,
remote.chain.updater,
ethers.constants.HashZero, // TODO: allow configuration
remote.chain.optimisticSeconds,
0, // TODO: allow configuration
]);
// if we have no replicas, deploy the whole setup.
// otherwise just deploy a fresh proxy
let proxy;
if (Object.keys(local.contracts.replicas).length === 0) {
console.log(`${local.chain.name}: initial Replica deploy`);
log(isTestDeploy, `${local.chain.name}: initial Replica deploy`);
proxy = await proxyUtils.deployProxy<contracts.Replica>(
local,
factory,
@ -165,7 +229,7 @@ async function deployNewReplica(local: Deploy, remote: Deploy) {
local.chain.domain,
);
} else {
console.log(`${local.chain.name}: additional Replica deploy`);
log(isTestDeploy, `${local.chain.name}: additional Replica deploy`);
const prev = Object.entries(local.contracts.replicas)[0][1];
proxy = await proxyUtils.duplicate<contracts.Replica>(
local,
@ -174,7 +238,7 @@ async function deployNewReplica(local: Deploy, remote: Deploy) {
);
}
local.contracts.replicas[remote.chain.domain] = proxy;
console.log(`${local.chain.name}: replica deployed for ${remote.chain.name}`);
log(isTestDeploy, `${local.chain.name}: replica deployed for ${remote.chain.name}`);
}
/**
@ -183,41 +247,44 @@ async function deployNewReplica(local: Deploy, remote: Deploy) {
*
* @param deploy - The deploy instance
*/
export async function deploy(deploy: Deploy) {
console.log(`${deploy.chain.name}: awaiting deploy UBC(deploy);`);
export async function deployOptics(deploy: Deploy) {
const isTestDeploy: boolean = deploy.test;
if (isTestDeploy) { warn('deploying test contracts', true) };
log(isTestDeploy, `${deploy.chain.name}: awaiting deploy UBC(deploy);`);
await deployUpgradeBeaconController(deploy);
console.log(`${deploy.chain.name}: awaiting deploy UpdaterManager(deploy);`);
log(isTestDeploy, `${deploy.chain.name}: awaiting deploy UpdaterManager(deploy);`);
await deployUpdaterManager(deploy);
console.log(
log(isTestDeploy,
`${deploy.chain.name}: awaiting deploy XappConnectionManager(deploy);`,
);
await deployXAppConnectionManager(deploy);
console.log(`${deploy.chain.name}: awaiting deploy Home(deploy);`);
await deployHome(deploy);
log(isTestDeploy, `${deploy.chain.name}: awaiting deploy Home(deploy);`);
await deployHome(deploy, isTestDeploy);
console.log(
`${deploy.chain.name}: awaiting xappConnectionManager.setHome(...);`,
log(isTestDeploy,
`${deploy.chain.name}: awaiting XAppConnectionManager.setHome(...);`,
);
await deploy.contracts.xappConnectionManager!.setHome(
await deploy.contracts.xAppConnectionManager!.setHome(
deploy.contracts.home!.proxy.address,
{ gasPrice: deploy.chain.gasPrice },
);
console.log(`${deploy.chain.name}: awaiting updaterManager.setHome(...);`);
log(isTestDeploy, `${deploy.chain.name}: awaiting updaterManager.setHome(...);`);
await deploy.contracts.updaterManager!.setHome(
deploy.contracts.home!.proxy.address,
{ gasPrice: deploy.chain.gasPrice },
);
console.log(
log(isTestDeploy,
`${deploy.chain.name}: awaiting deploy GovernanceRouter(deploy);`,
);
await deployGovernanceRouter(deploy);
await deployGovernanceRouter(deploy, isTestDeploy);
console.log(`${deploy.chain.name}: initial chain deploy completed`);
log(isTestDeploy, `${deploy.chain.name}: initial chain deploy completed`);
}
/**
@ -226,41 +293,44 @@ export async function deploy(deploy: Deploy) {
* @param deploy - The deploy instance
*/
export async function relinquish(deploy: Deploy) {
console.log(`${deploy.chain.name}: Relinquishing control`);
const isTestDeploy = deploy.test;
const govRouter = await deploy.contracts.governance!.proxy.address;
log(isTestDeploy, `${deploy.chain.name}: Relinquishing control`);
await deploy.contracts.updaterManager!.transferOwnership(
deploy.contracts.governance!.proxy.address,
govRouter,
{ gasPrice: deploy.chain.gasPrice },
);
console.log(`${deploy.chain.name}: Dispatched relinquish updatermanager`);
log(isTestDeploy, `${deploy.chain.name}: Dispatched relinquish updatermanager`);
await deploy.contracts.xappConnectionManager!.transferOwnership(
deploy.contracts.governance!.proxy.address,
await deploy.contracts.xAppConnectionManager!.transferOwnership(
govRouter,
{ gasPrice: deploy.chain.gasPrice },
);
console.log(
`${deploy.chain.name}: Dispatched relinquish xappConnectionManager`,
log(isTestDeploy,
`${deploy.chain.name}: Dispatched relinquish XAppConnectionManager`,
);
await deploy.contracts.upgradeBeaconController!.transferOwnership(
deploy.contracts.governance!.proxy.address,
govRouter,
{ gasPrice: deploy.chain.gasPrice },
);
console.log(
log(isTestDeploy,
`${deploy.chain.name}: Dispatched relinquish upgradeBeaconController`,
);
let tx = await deploy.contracts.home!.proxy.transferOwnership(
deploy.contracts.governance!.proxy.address,
govRouter,
{ gasPrice: deploy.chain.gasPrice },
);
console.log(`${deploy.chain.name}: Dispatched relinquish home`);
log(isTestDeploy, `${deploy.chain.name}: Dispatched relinquish home`);
await tx.wait(5);
console.log(`${deploy.chain.name}: Control relinquished`);
await tx.wait(deploy.chain.confirmations);
log(isTestDeploy, `${deploy.chain.name}: Control relinquished`);
}
/**
@ -270,16 +340,17 @@ export async function relinquish(deploy: Deploy) {
* @param remote - The remote deploy instance
*/
export async function enrollReplica(local: Deploy, remote: Deploy) {
console.log(`${local.chain.name}: starting replica enrollment`);
const isTestDeploy = local.test;
log(isTestDeploy, `${local.chain.name}: starting replica enrollment`);
let tx = await local.contracts.xappConnectionManager!.ownerEnrollReplica(
let tx = await local.contracts.xAppConnectionManager!.ownerEnrollReplica(
local.contracts.replicas[remote.chain.domain].proxy.address,
remote.chain.domain,
{ gasPrice: local.chain.gasPrice },
);
await tx.wait(5);
await tx.wait(local.chain.confirmations);
console.log(`${local.chain.name}: replica enrollment done`);
log(isTestDeploy, `${local.chain.name}: replica enrollment done`);
}
/**
@ -289,22 +360,23 @@ export async function enrollReplica(local: Deploy, remote: Deploy) {
* @param remote - The remote deploy instance
*/
export async function enrollWatchers(left: Deploy, right: Deploy) {
console.log(`${left.chain.name}: starting watcher enrollment`);
const isTestDeploy = left.test;
log(isTestDeploy, `${left.chain.name}: starting watcher enrollment`);
await Promise.all(
left.chain.watchers.map(async (watcher) => {
const tx =
await left.contracts.xappConnectionManager!.setWatcherPermission(
await left.contracts.xAppConnectionManager!.setWatcherPermission(
watcher,
right.chain.domain,
true,
{ gasPrice: left.chain.gasPrice },
);
await tx.wait(5);
await tx.wait(deploy.chain.confirmations);
}),
);
console.log(`${left.chain.name}: watcher enrollment done`);
log(isTestDeploy, `${left.chain.name}: watcher enrollment done`);
}
/**
@ -314,14 +386,15 @@ export async function enrollWatchers(left: Deploy, right: Deploy) {
* @param remote - The remote deploy instance
*/
export async function enrollGovernanceRouter(local: Deploy, remote: Deploy) {
console.log(`${local.chain.name}: starting governance enrollment`);
const isTestDeploy = local.test;
log(isTestDeploy, `${local.chain.name}: starting governance enrollment`);
let tx = await local.contracts.governance!.proxy.setRouter(
remote.chain.domain,
toBytes32(remote.contracts.governance!.proxy.address),
{ gasPrice: local.chain.gasPrice },
);
await tx.wait(5);
console.log(`${local.chain.name}: governance enrollment done`);
await tx.wait(local.chain.confirmations);
log(isTestDeploy, `${local.chain.name}: governance enrollment done`);
}
/**
@ -331,7 +404,7 @@ export async function enrollGovernanceRouter(local: Deploy, remote: Deploy) {
* @param remote - The remote deploy instance
*/
export async function enrollRemote(local: Deploy, remote: Deploy) {
await deployNewReplica(local, remote);
await deployUnenrolledReplica(local, remote);
await enrollReplica(local, remote);
await enrollWatchers(local, remote);
await enrollGovernanceRouter(local, remote);
@ -344,15 +417,16 @@ export async function enrollRemote(local: Deploy, remote: Deploy) {
* @param non - The non-governor chain deploy instance
*/
export async function transferGovernorship(gov: Deploy, non: Deploy) {
console.log(`${non.chain.name}: transferring governorship`);
const isTestDeploy = gov.test;
log(isTestDeploy, `${non.chain.name}: transferring governorship`);
let governorAddress = await gov.contracts.governance!.proxy.governor();
let tx = await non.contracts.governance!.proxy.transferGovernor(
gov.chain.domain,
governorAddress,
{ gasPrice: non.chain.gasPrice },
);
await tx.wait(5);
console.log(`${non.chain.name}: governorship transferred`);
await tx.wait(gov.chain.confirmations);
log(isTestDeploy, `${non.chain.name}: governorship transferred`);
}
/**
@ -364,17 +438,25 @@ export async function transferGovernorship(gov: Deploy, non: Deploy) {
* @param non - The non-governor chain deploy instance
*/
export async function deployTwoChains(gov: Deploy, non: Deploy) {
await Promise.all([deploy(gov), deploy(non)]);
const isTestDeploy: boolean = gov.test || non.test;
console.log('initial deploys done');
await Promise.all([
deployOptics(gov),
deployOptics(non),
]);
log(isTestDeploy, 'initial deploys done');
await Promise.all([deployNewReplica(gov, non), deployNewReplica(non, gov)]);
await Promise.all([
deployUnenrolledReplica(gov, non),
deployUnenrolledReplica(non, gov),
]);
console.log('replica deploys done');
log(isTestDeploy, 'replica deploys done');
await Promise.all([enrollReplica(gov, non), enrollReplica(non, gov)]);
console.log('replica enrollment done');
log(isTestDeploy, 'replica enrollment done');
await Promise.all([enrollWatchers(gov, non), enrollWatchers(non, gov)]);
@ -387,7 +469,9 @@ export async function deployTwoChains(gov: Deploy, non: Deploy) {
await Promise.all([relinquish(gov), relinquish(non)]);
writeDeployOutput([gov, non]);
if (!isTestDeploy) {
writeDeployOutput([gov, non]);
}
}
/**
@ -398,10 +482,10 @@ export async function deployTwoChains(gov: Deploy, non: Deploy) {
* @param spokes - An array of remote chain deploy instances
*/
export async function deployHubAndSpokes(gov: Deploy, spokes: Deploy[]) {
await deploy(gov);
await deployOptics(gov);
for (const non of spokes) {
await deploy(non);
await deployOptics(non);
await enrollRemote(gov, non);
await enrollRemote(non, gov);
@ -426,13 +510,16 @@ export async function deployHubAndSpokes(gov: Deploy, spokes: Deploy[]) {
* @param chains - An array of chain deploys
*/
export async function deployNChains(chains: Deploy[]) {
// there exists any chain marked test
const isTestDeploy: boolean = chains.filter((c) => c.test).length > 0;
const govChain = chains[0];
const nonGovChains = chains.slice(1);
await deployHubAndSpokes(govChain, nonGovChains);
for (let local of nonGovChains) {
for (let remote of nonGovChains) {
if (remote.chain.domain != local.chain.domain) {
console.log(
log(isTestDeploy,
`enrolling ${remote.chain.domain} on ${local.chain.domain}`,
);
await enrollRemote(local, remote);
@ -440,7 +527,9 @@ export async function deployNChains(chains: Deploy[]) {
}
}
writeDeployOutput(chains);
if (!isTestDeploy) {
writeDeployOutput(chains);
}
}
/**
@ -470,7 +559,7 @@ export function writePartials(dir: string) {
* @param deploys - The array of chain deploys
*/
export function writeDeployOutput(deploys: Deploy[]) {
console.log(`Have ${deploys.length} deploys`);
log(deploys[0].test, `Have ${deploys.length} deploys`);
const dir = `../rust/config/${Date.now()}`;
for (const local of deploys) {
// get remotes

@ -1,7 +1,7 @@
import { BytesLike, ethers } from 'ethers';
import { Deploy } from './chain';
import * as contracts from './typechain/optics-core';
import { Deploy } from './chain';
import * as contracts from '../../typechain/optics-core';
export type BeaconProxy<T extends ethers.Contract> = {
implementation: T;
@ -37,7 +37,7 @@ export async function deployProxy<T extends ethers.Contract>(
// proxy wait(5) implies implementation and beacon wait(5)
// due to nonce ordering
await proxy.deployTransaction.wait(5);
await proxy.deployTransaction.wait(deploy.chain.confirmations);
const { name } = implementation.constructor;
// add UpgradeBeacon to Etherscan verification
@ -82,7 +82,7 @@ export async function duplicate<T extends ethers.Contract>(
initData: BytesLike,
): Promise<BeaconProxy<T>> {
const proxy = await _deployProxy(deploy, prev.beacon, initData);
await proxy.deployTransaction.wait(5);
await proxy.deployTransaction.wait(deploy.chain.confirmations);
const { name } = prev.implementation.constructor;
// add UpgradeBeacon to etherscan verification

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

@ -4,7 +4,7 @@
* @param hexStr - the hex string
* @return byteLength - length in bytes
*/
function getHexStringByteLength(hexStr) {
export function getHexStringByteLength(hexStr: string) {
let len = hexStr.length;
// check for prefix, remove if necessary
@ -16,6 +16,12 @@ function getHexStringByteLength(hexStr) {
return len / 2;
}
module.exports = {
getHexStringByteLength,
};
/*
* Converts address to Bytes32
*
* @param address - the address
* @return The address as bytes32
*/
export function toBytes32(address: string): string {
return '0x' + '00'.repeat(12) + address.slice(2);
}

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"

@ -1,24 +1,29 @@
const { waffle, ethers } = require('hardhat');
const { provider } = waffle;
const { expect } = require('chai');
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { TestCommon__factory, TestCommon } from '../../typechain/optics-core';
import { OpticsState, Updater } from '../lib';
import { Signer } from '../lib/types';
import signedUpdateTestCases from '../../../vectors/signedUpdate.json';
const {
testCases: signedUpdateTestCases,
} = require('../../../vectors/signedUpdateTestCases.json');
const localDomain = 1000;
describe('Common', async () => {
let common, signer, fakeSigner, updater, fakeUpdater;
let signer: Signer,
fakeSigner: Signer,
common: TestCommon,
updater: Updater,
fakeUpdater: Updater;
before(async () => {
[signer, fakeSigner] = provider.getWallets();
updater = await optics.Updater.fromSigner(signer, localDomain);
fakeUpdater = await optics.Updater.fromSigner(fakeSigner, localDomain);
[signer, fakeSigner] = await ethers.getSigners();
updater = await Updater.fromSigner(signer, localDomain);
fakeUpdater = await Updater.fromSigner(fakeSigner, localDomain);
});
beforeEach(async () => {
const factory = await ethers.getContractFactory('TestCommon');
common = await factory.deploy(localDomain, updater.signer.address);
const commonFactory = new TestCommon__factory(signer);
common = await commonFactory.deploy(localDomain, updater.address);
});
it('Accepts updater signature', async () => {
@ -61,7 +66,7 @@ describe('Common', async () => {
common.doubleUpdate(oldRoot, [newRoot, newRoot2], signature, signature2),
).to.emit(common, 'DoubleUpdate');
expect(await common.state()).to.equal(optics.State.FAILED);
expect(await common.state()).to.equal(OpticsState.FAILED);
});
it('Does not fail contract on invalid double update proof', async () => {
@ -81,8 +86,8 @@ describe('Common', async () => {
// State should not be failed because double update proof does not
// demonstrate fraud
const state = await common.state();
expect(state).not.to.equal(optics.State.FAILED);
expect(state).to.equal(optics.State.ACTIVE);
expect(state).not.to.equal(OpticsState.FAILED);
expect(state).to.equal(OpticsState.ACTIVE);
});
it('Checks Rust-produced SignedUpdate', async () => {

@ -1,49 +1,51 @@
const { ethers } = require('hardhat');
const { provider } = waffle;
const { expect } = require('chai');
const { domainsToTestConfigs } = require('./generateTestChainConfigs');
const testUtils = require('../utils');
const {
import { ethers, optics } from 'hardhat';
import { expect } from 'chai';
import { providers } from 'ethers';
import {
enqueueUpdateToReplica,
formatCall,
formatOpticsMessage,
} = require('./crossChainTestUtils');
const {
deployMultipleChains,
getHome,
getReplica,
getGovernanceRouter,
getUpgradeBeaconController,
getUpdaterManager,
} = require('./deployCrossChainTest');
const { proof } = require('../../../../vectors/proof.json');
} from './utils';
import { increaseTimestampBy, UpgradeTestHelpers } from '../utils';
import { getTestDeploy } from '../testChain';
import { Updater } from '../../lib';
import { Address, Signer } from '../../lib/types';
import { Deploy } from '../../../optics-deploy/src/chain';
import {
deployTwoChains,
deployUnenrolledReplica,
} from '../../../optics-deploy/src/deployOptics';
import * as contracts from '../../../typechain/optics-core';
const helpers = require('../../../../vectors/proof.json');
const governorDomain = 1000;
const nonGovernorDomain = 2000;
const thirdDomain = 3000;
/*
* Deploy the full Optics suite on two chains
*/
describe('GovernanceRouter', async () => {
const domains = [1000, 2000];
const governorDomain = 1000;
const nonGovernorDomain = 2000;
const thirdDomain = 3000;
const walletProvider = new testUtils.WalletProvider(provider);
const [thirdRouter, recoveryManager] = walletProvider.getWalletsPersistent(2);
let governorRouter,
governorHome,
governorReplicaOnNonGovernorChain,
nonGovernorRouter,
nonGovernorReplicaOnGovernorChain,
firstGovernor,
secondGovernor,
secondGovernorSigner,
updater,
chainDetails;
let deploys: Deploy[] = [];
let signer: Signer,
secondGovernorSigner: Signer,
thirdRouter: Signer,
governorRouter: contracts.TestGovernanceRouter,
governorHome: contracts.Home,
governorReplicaOnNonGovernorChain: contracts.TestReplica,
nonGovernorRouter: contracts.TestGovernanceRouter,
nonGovernorReplicaOnGovernorChain: contracts.TestReplica,
firstGovernor: Address,
secondGovernor: Address,
updater: Updater;
async function expectGovernor(
governanceRouter,
expectedGovernorDomain,
expectedGovernor,
governanceRouter: contracts.TestGovernanceRouter,
expectedGovernorDomain: number,
expectedGovernor: Address,
) {
expect(await governanceRouter.governorDomain()).to.equal(
expectedGovernorDomain,
@ -51,70 +53,148 @@ describe('GovernanceRouter', async () => {
expect(await governanceRouter.governor()).to.equal(expectedGovernor);
}
beforeEach(async () => {
// generate TestChainConfigs for the given domains
const configs = await domainsToTestConfigs(
domains,
recoveryManager.address,
);
before(async () => {
[thirdRouter, signer, secondGovernorSigner] = await ethers.getSigners();
updater = await Updater.fromSigner(signer, governorDomain);
// deploy the entire Optics suite on each chain
chainDetails = await deployMultipleChains(configs);
// get fresh test deploy objects
deploys.push(await getTestDeploy(governorDomain, updater.address, []));
deploys.push(await getTestDeploy(nonGovernorDomain, updater.address, []));
deploys.push(await getTestDeploy(thirdDomain, updater.address, []));
});
// set updater
[signer, secondGovernorSigner] = provider.getWallets();
updater = await optics.Updater.fromSigner(signer, governorDomain);
beforeEach(async () => {
// deploy the entire Optics suite on each chain
await deployTwoChains(deploys[0], deploys[1]);
// get both governanceRouters
governorRouter = getGovernanceRouter(chainDetails, governorDomain);
nonGovernorRouter = getGovernanceRouter(chainDetails, nonGovernorDomain);
governorRouter = deploys[0].contracts.governance
?.proxy! as contracts.TestGovernanceRouter;
nonGovernorRouter = deploys[1].contracts.governance
?.proxy! as contracts.TestGovernanceRouter;
firstGovernor = await governorRouter.governor();
secondGovernor = await secondGovernorSigner.getAddress();
governorHome = getHome(chainDetails, governorDomain);
governorReplicaOnNonGovernorChain = getReplica(
chainDetails,
nonGovernorDomain,
governorDomain,
governorHome = deploys[0].contracts.home?.proxy!;
governorReplicaOnNonGovernorChain = deploys[1].contracts.replicas[
governorDomain
].proxy! as contracts.TestReplica;
nonGovernorReplicaOnGovernorChain = deploys[0].contracts.replicas[
nonGovernorDomain
].proxy! as contracts.TestReplica;
});
// NB: must be first test for message proof
it('Sends cross-chain message to upgrade contract', async () => {
const deploy = deploys[1];
const upgradeUtils = new UpgradeTestHelpers();
// get upgradeBeaconController
const upgradeBeaconController = deploy.contracts.upgradeBeaconController!;
const mysteryMath = await upgradeUtils.deployMysteryMathUpgradeSetup(
deploy,
signer,
false,
);
// expect results before upgrade
await upgradeUtils.expectMysteryMathV1(mysteryMath.proxy);
// Deploy Implementation 2
const factory2 = new contracts.MysteryMathV2__factory(signer);
const implementation2 = await factory2.deploy();
// Format optics call message
const call = await formatCall(upgradeBeaconController, 'upgrade', [
mysteryMath.beacon.address,
implementation2.address,
]);
const currentRoot = await governorHome.current();
// dispatch call on local governorRouter
let tx = await governorRouter.callRemote(nonGovernorDomain, [call]);
let receipt = await tx.wait(0);
let leaf = receipt.events?.[0].topics[3];
expect(leaf).to.equal(helpers.proof.leaf);
const [, latestRoot] = await governorHome.suggestUpdate();
expect(latestRoot).to.equal(helpers.root);
const { signature } = await updater.signUpdate(currentRoot, latestRoot);
await expect(governorHome.update(currentRoot, latestRoot, signature))
.to.emit(governorHome, 'Update')
.withArgs(governorDomain, currentRoot, latestRoot, signature);
expect(await governorHome.current()).to.equal(latestRoot);
expect(await governorHome.queueContains(latestRoot)).to.be.false;
await enqueueUpdateToReplica(
{ oldRoot: currentRoot, newRoot: latestRoot, signature },
governorReplicaOnNonGovernorChain,
);
nonGovernorReplicaOnGovernorChain = getReplica(
chainDetails,
const [pending] = await governorReplicaOnNonGovernorChain.nextPending();
expect(pending).to.equal(latestRoot);
// Increase time enough for both updates to be confirmable
const optimisticSeconds = deploy.chain.optimisticSeconds;
await increaseTimestampBy(
deploy.chain.provider as providers.JsonRpcProvider,
optimisticSeconds * 2,
);
// Replica should be able to confirm updates
expect(await governorReplicaOnNonGovernorChain.canConfirm()).to.be.true;
await governorReplicaOnNonGovernorChain.confirm();
// after confirming, current root should be equal to the last submitted update
expect(await governorReplicaOnNonGovernorChain.current()).to.equal(
latestRoot,
);
const callMessage = optics.governance.formatCalls([call]);
const sequence = await governorHome.sequences(nonGovernorDomain);
const opticsMessage = optics.formatMessage(
governorDomain,
governorRouter.address,
sequence - 1,
nonGovernorDomain,
nonGovernorRouter.address,
callMessage,
);
expect(ethers.utils.keccak256(opticsMessage)).to.equal(leaf);
const { path, index } = helpers.proof;
await governorReplicaOnNonGovernorChain.proveAndProcess(
opticsMessage,
path,
index,
);
// test implementation was upgraded
await upgradeUtils.expectMysteryMathV2(mysteryMath.proxy);
});
it('Rejects message from unenrolled replica', async () => {
const optimisticSeconds = 3;
const initialCurrentRoot = ethers.utils.formatBytes32String('current');
const initialIndex = 0;
const controller = null;
// Deploy single replica on nonGovernorDomain that will not be enrolled
const { contracts: unenrolledReplicaContracts } =
await optics.deployUpgradeSetupAndProxy(
'TestReplica',
[nonGovernorDomain],
[
nonGovernorDomain,
updater.signer.address,
initialCurrentRoot,
optimisticSeconds,
initialIndex,
],
controller,
'initialize(uint32, address, bytes32, uint256, uint32)',
);
const unenrolledReplica =
unenrolledReplicaContracts.proxyWithImplementation;
await deployUnenrolledReplica(deploys[1], deploys[2]);
const unenrolledReplica = deploys[1].contracts.replicas[thirdDomain]
.proxy! as contracts.TestReplica;
// Create TransferGovernor message
const transferGovernorMessage =
optics.GovernanceRouter.formatTransferGovernor(
thirdDomain,
optics.ethersAddressToBytes32(secondGovernor),
);
const transferGovernorMessage = optics.governance.formatTransferGovernor(
thirdDomain,
optics.ethersAddressToBytes32(secondGovernor),
);
const opticsMessage = await formatOpticsMessage(
unenrolledReplica,
@ -123,19 +203,17 @@ describe('GovernanceRouter', async () => {
transferGovernorMessage,
);
// Expect replica processing to fail when nonGovernorRouter reverts in
// handle
// Expect replica processing to fail when nonGovernorRouter reverts in handle
let success = await unenrolledReplica.callStatic.testProcess(opticsMessage);
expect(success).to.be.false;
});
it('Rejects message not from governor router', async () => {
// Create TransferGovernor message
const transferGovernorMessage =
optics.GovernanceRouter.formatTransferGovernor(
nonGovernorDomain,
optics.ethersAddressToBytes32(nonGovernorRouter.address),
);
const transferGovernorMessage = optics.governance.formatTransferGovernor(
nonGovernorDomain,
optics.ethersAddressToBytes32(nonGovernorRouter.address),
);
const opticsMessage = await formatOpticsMessage(
governorReplicaOnNonGovernorChain,
@ -147,8 +225,7 @@ describe('GovernanceRouter', async () => {
// Set message status to MessageStatus.Pending
await nonGovernorReplicaOnGovernorChain.setMessagePending(opticsMessage);
// Expect replica processing to fail when nonGovernorRouter reverts in
// handle
// Expect replica processing to fail when nonGovernorRouter reverts in handle
let success =
await nonGovernorReplicaOnGovernorChain.callStatic.testProcess(
opticsMessage,
@ -165,11 +242,10 @@ describe('GovernanceRouter', async () => {
);
// Create TransferGovernor message
const transferGovernorMessage =
optics.GovernanceRouter.formatTransferGovernor(
thirdDomain,
optics.ethersAddressToBytes32(thirdRouter.address),
);
const transferGovernorMessage = optics.governance.formatTransferGovernor(
thirdDomain,
optics.ethersAddressToBytes32(thirdRouter.address),
);
const opticsMessage = await formatOpticsMessage(
governorReplicaOnNonGovernorChain,
@ -194,15 +270,15 @@ describe('GovernanceRouter', async () => {
it('Accepts valid set router message', async () => {
// Create address for router to enroll and domain for router
const [router] = provider.getWallets();
const [router] = await ethers.getSigners();
// Create SetRouter message
const setRouterMessage = optics.GovernanceRouter.formatSetRouter(
const setRouterMessage = optics.governance.formatSetRouter(
thirdDomain,
optics.ethersAddressToBytes32(router.address),
);
const opticsMessage = formatOpticsMessage(
const opticsMessage = await formatOpticsMessage(
governorReplicaOnNonGovernorChain,
governorRouter,
nonGovernorRouter,
@ -225,14 +301,16 @@ describe('GovernanceRouter', async () => {
});
it('Accepts valid call messages', async () => {
const TestRecipient = await optics.deployImplementation('TestRecipient');
// const TestRecipient = await optics.deployImplementation('TestRecipient');
const testRecipientFactory = new contracts.TestRecipient__factory(signer);
const TestRecipient = await testRecipientFactory.deploy();
// Format optics call message
const arg = 'String!';
const call = await formatCall(TestRecipient, 'receiveString', [arg]);
// Create Call message to test recipient that calls receiveString
const callMessage = optics.GovernanceRouter.formatCalls([call, call]);
const callMessage = optics.governance.formatCalls([call, call]);
const opticsMessage = await formatOpticsMessage(
governorReplicaOnNonGovernorChain,
@ -282,18 +360,18 @@ describe('GovernanceRouter', async () => {
// get new root and signed update
const newRoot = await governorHome.queueEnd();
const { signature } = await updater.signUpdate(currentRoot, newRoot);
// update governor chain home
await governorHome.update(currentRoot, newRoot, signature);
const transferGovernorMessage =
optics.GovernanceRouter.formatTransferGovernor(
nonGovernorDomain,
optics.ethersAddressToBytes32(secondGovernor),
);
const transferGovernorMessage = optics.governance.formatTransferGovernor(
nonGovernorDomain,
optics.ethersAddressToBytes32(secondGovernor),
);
const opticsMessage = formatOpticsMessage(
const opticsMessage = await formatOpticsMessage(
governorReplicaOnNonGovernorChain,
governorRouter,
nonGovernorRouter,
@ -330,43 +408,26 @@ describe('GovernanceRouter', async () => {
});
it('Upgrades using GovernanceRouter call', async () => {
const a = 5;
const b = 10;
const stateVar = 17;
const upgradeUtils = new UpgradeTestHelpers();
const deploy = deploys[0];
// get upgradeBeaconController
const upgradeBeaconController = getUpgradeBeaconController(
chainDetails,
governorDomain,
);
// Set up contract suite
const { contracts } = await optics.deployUpgradeSetupAndProxy(
'MysteryMathV1',
[],
[],
upgradeBeaconController,
const mysteryMath = await upgradeUtils.deployMysteryMathUpgradeSetup(
deploy,
signer,
);
const mysteryMathProxy = contracts.proxyWithImplementation;
const upgradeBeacon = contracts.upgradeBeacon;
// Set state of proxy
await mysteryMathProxy.setState(stateVar);
const upgradeBeaconController = deploy.contracts.upgradeBeaconController!;
// expect results before upgrade
let versionResult = await mysteryMathProxy.version();
expect(versionResult).to.equal(1);
let mathResult = await mysteryMathProxy.doMath(a, b);
expect(mathResult).to.equal(a + b);
let stateResult = await mysteryMathProxy.getState();
expect(stateResult).to.equal(stateVar);
await upgradeUtils.expectMysteryMathV1(mysteryMath.proxy);
// Deploy Implementation 2
const implementation = await optics.deployImplementation('MysteryMathV2');
const v2Factory = new contracts.MysteryMathV2__factory(signer);
const implementation = await v2Factory.deploy();
// Format optics call message
const call = await formatCall(upgradeBeaconController, 'upgrade', [
upgradeBeacon.address,
mysteryMath.beacon.address,
implementation.address,
]);
@ -377,134 +438,16 @@ describe('GovernanceRouter', async () => {
);
// test implementation was upgraded
versionResult = await mysteryMathProxy.version();
expect(versionResult).to.equal(2);
mathResult = await mysteryMathProxy.doMath(a, b);
expect(mathResult).to.equal(a * b);
stateResult = await mysteryMathProxy.getState();
expect(stateResult).to.equal(stateVar);
});
it('Sends cross-chain message to upgrade contract', async () => {
const a = 5;
const b = 10;
const stateVar = 17;
// get upgradeBeaconController
const upgradeBeaconController = getUpgradeBeaconController(
chainDetails,
nonGovernorDomain,
);
// Set up contract suite
const { contracts } = await optics.deployUpgradeSetupAndProxy(
'MysteryMathV1',
[],
[],
upgradeBeaconController,
);
const mysteryMathProxy = contracts.proxyWithImplementation;
const upgradeBeacon = contracts.upgradeBeacon;
// Set state of proxy
await mysteryMathProxy.setState(stateVar);
// expect results before upgrade
let versionResult = await mysteryMathProxy.version();
expect(versionResult).to.equal(1);
let mathResult = await mysteryMathProxy.doMath(a, b);
expect(mathResult).to.equal(a + b);
let stateResult = await mysteryMathProxy.getState();
expect(stateResult).to.equal(stateVar);
// Deploy Implementation 2
const implementation = await optics.deployImplementation('MysteryMathV2');
// Format optics call message
const call = await formatCall(upgradeBeaconController, 'upgrade', [
upgradeBeacon.address,
implementation.address,
]);
const currentRoot = await governorHome.current();
// dispatch call on local governorRouter
governorRouter.callRemote(nonGovernorDomain, [call]);
const [, latestRoot] = await governorHome.suggestUpdate();
const { signature } = await updater.signUpdate(currentRoot, latestRoot);
await expect(governorHome.update(currentRoot, latestRoot, signature))
.to.emit(governorHome, 'Update')
.withArgs(governorDomain, currentRoot, latestRoot, signature);
expect(await governorHome.current()).to.equal(latestRoot);
expect(await governorHome.queueContains(latestRoot)).to.be.false;
await enqueueUpdateToReplica(
chainDetails,
{ startRoot: currentRoot, finalRoot: latestRoot, signature },
governorDomain,
nonGovernorDomain,
);
const [pending] = await governorReplicaOnNonGovernorChain.nextPending();
expect(pending).to.equal(latestRoot);
// Increase time enough for both updates to be confirmable
const optimisticSeconds = chainDetails[nonGovernorDomain].optimisticSeconds;
await testUtils.increaseTimestampBy(provider, optimisticSeconds * 2);
// Replica should be able to confirm updates
expect(await governorReplicaOnNonGovernorChain.canConfirm()).to.be.true;
await governorReplicaOnNonGovernorChain.confirm();
// after confirming, current root should be equal to the last submitted update
expect(await governorReplicaOnNonGovernorChain.current()).to.equal(
latestRoot,
);
const callMessage = optics.GovernanceRouter.formatCalls([call]);
const opticsMessage = optics.formatMessage(
governorDomain,
governorRouter.address,
0,
nonGovernorDomain,
nonGovernorRouter.address,
callMessage,
);
const { path } = proof;
const index = 0;
await governorReplicaOnNonGovernorChain.proveAndProcess(
opticsMessage,
path,
index,
);
// test implementation was upgraded
versionResult = await mysteryMathProxy.version();
expect(versionResult).to.equal(2);
mathResult = await mysteryMathProxy.doMath(a, b);
expect(mathResult).to.equal(a * b);
stateResult = await mysteryMathProxy.getState();
expect(stateResult).to.equal(stateVar);
await upgradeUtils.expectMysteryMathV2(mysteryMath.proxy);
});
it('Calls UpdaterManager to change the Updater on Home', async () => {
const [newUpdater] = provider.getWallets();
const updaterManager = getUpdaterManager(chainDetails, governorDomain);
const [newUpdater] = await ethers.getSigners();
const updaterManager = deploys[0].contracts.updaterManager!;
// check current Updater address on Home
let currentUpdaterAddr = await governorHome.updater();
expect(currentUpdaterAddr).to.equal(updater.signer.address);
expect(currentUpdaterAddr).to.equal(deploys[0].chain.updater);
// format optics call message
const call = await formatCall(updaterManager, 'setUpdater', [

@ -1,22 +1,23 @@
const { provider } = waffle;
const { expect } = require('chai');
const testUtils = require('../utils');
const { domainsToTestConfigs } = require('./generateTestChainConfigs');
const { formatCall, sendFromSigner } = require('./crossChainTestUtils');
const {
deployMultipleChains,
getHome,
getGovernanceRouter,
getUpdaterManager,
} = require('./deployCrossChainTest');
import { ethers, optics } from 'hardhat';
import { expect } from 'chai';
import * as types from 'ethers';
import { formatCall, sendFromSigner } from './utils';
import { increaseTimestampBy } from '../utils';
import { getTestDeploy } from '../testChain';
import { Updater } from '../../lib';
import { Signer } from '../../lib/types';
import { Deploy } from '../../../optics-deploy/src/chain';
import { deployTwoChains } from '../../../optics-deploy/src/deployOptics';
import * as contracts from '../../../typechain/optics-core';
async function expectNotInRecovery(
updaterManager,
recoveryManager,
randomSigner,
governor,
governanceRouter,
home,
updaterManager: contracts.UpdaterManager,
recoveryManager: types.Signer,
randomSigner: Signer,
governor: Signer,
governanceRouter: contracts.TestGovernanceRouter,
home: contracts.TestHome,
) {
expect(await governanceRouter.inRecovery()).to.be.false;
@ -100,10 +101,10 @@ async function expectNotInRecovery(
}
async function expectOnlyRecoveryManagerCanTransferRole(
governor,
governanceRouter,
randomSigner,
recoveryManager,
governor: Signer,
governanceRouter: contracts.TestGovernanceRouter,
randomSigner: Signer,
recoveryManager: Signer,
) {
await expect(
sendFromSigner(governor, governanceRouter, 'transferRecoveryManager', [
@ -138,10 +139,10 @@ async function expectOnlyRecoveryManagerCanTransferRole(
}
async function expectOnlyRecoveryManagerCanExitRecovery(
governor,
governanceRouter,
randomSigner,
recoveryManager,
governor: Signer,
governanceRouter: contracts.TestGovernanceRouter,
randomSigner: Signer,
recoveryManager: Signer,
) {
await expect(
sendFromSigner(governor, governanceRouter, 'exitRecovery', []),
@ -159,10 +160,10 @@ async function expectOnlyRecoveryManagerCanExitRecovery(
}
async function expectOnlyRecoveryManagerCanInitiateRecovery(
governor,
governanceRouter,
randomSigner,
recoveryManager,
governor: Signer,
governanceRouter: contracts.TestGovernanceRouter,
randomSigner: Signer,
recoveryManager: Signer,
) {
await expect(
sendFromSigner(governor, governanceRouter, 'initiateRecoveryTimelock', []),
@ -191,36 +192,52 @@ async function expectOnlyRecoveryManagerCanInitiateRecovery(
expect(await governanceRouter.recoveryActiveAt()).to.not.equal(0);
}
const localDomain = 1000;
const remoteDomain = 2000;
/*
* Deploy the full Optics suite on two chains
*/
describe('RecoveryManager', async () => {
const domains = [1000, 2000];
const domain = 1000;
const walletProvider = new testUtils.WalletProvider(provider);
const [governor, recoveryManager, randomSigner] =
walletProvider.getWalletsPersistent(5);
let governor: Signer,
recoveryManager: Signer,
randomSigner: Signer,
governanceRouter: contracts.TestGovernanceRouter,
home: contracts.TestHome,
updaterManager: contracts.UpdaterManager;
let governanceRouter, home, updaterManager, chainDetails;
let deploys: Deploy[] = [];
before(async () => {
// generate TestChainConfigs for the given domains
const configs = await domainsToTestConfigs(
domains,
recoveryManager.address,
);
[governor, recoveryManager, randomSigner] = await ethers.getSigners();
const updater = await Updater.fromSigner(randomSigner, localDomain);
// deploy the entire Optics suite on each chain
chainDetails = await deployMultipleChains(configs);
deploys.push(
await getTestDeploy(
localDomain,
updater.address,
[],
recoveryManager.address,
),
);
deploys.push(
await getTestDeploy(
remoteDomain,
updater.address,
[],
recoveryManager.address,
),
);
// get the governance router
governanceRouter = getGovernanceRouter(chainDetails, domain);
// transfer governorship to the governor signer
await governanceRouter.transferGovernor(domain, governor.address);
await deployTwoChains(deploys[0], deploys[1]);
home = getHome(chainDetails, domain);
governanceRouter = deploys[0].contracts.governance
?.proxy! as contracts.TestGovernanceRouter;
home = deploys[0].contracts.home?.proxy! as contracts.TestHome;
updaterManager = deploys[0].contracts.updaterManager!;
updaterManager = getUpdaterManager(chainDetails, domain);
// set governor
await governanceRouter.transferGovernor(localDomain, governor.address);
});
it('Before Recovery Initiated: Timelock has not been set', async () => {
@ -314,7 +331,7 @@ describe('RecoveryManager', async () => {
it('Recovery Active: inRecovery becomes true when timelock expires', async () => {
// increase timestamp on-chain
const timelock = await governanceRouter.recoveryTimelock();
await testUtils.increaseTimestampBy(provider, timelock.toNumber());
await increaseTimestampBy(ethers.provider, timelock.toNumber());
expect(await governanceRouter.inRecovery()).to.be.true;
});

@ -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,9 +1,21 @@
const { expect } = require('chai');
const {
getHome,
getReplica,
getUpdaterObject,
} = require('./deployCrossChainTest');
import { expect } from 'chai';
import { ethers, optics } from 'hardhat';
import * as types from 'ethers';
import { Updater } from '../../lib';
import { Update, CallData, Address } from '../../lib/types';
import {
Replica,
TestReplica,
Home,
TestGovernanceRouter,
} from '../../../typechain/optics-core';
type MessageDetails = {
message: string;
destinationDomain: number;
recipientAddress: Address;
};
/*
* Enqueue a message to the specified Home contract
@ -18,9 +30,10 @@ const {
*
* @return newRoot - bytes32 of the latest root
*/
async function enqueueMessageToHome(chainDetails, homeDomain, messageDetails) {
const home = getHome(chainDetails, homeDomain);
export async function enqueueMessageToHome(
home: Home,
messageDetails: MessageDetails,
): Promise<string> {
const { message, destinationDomain, recipientAddress } = messageDetails;
// Send message with random signer address as msg.sender
@ -45,24 +58,19 @@ async function enqueueMessageToHome(chainDetails, homeDomain, messageDetails) {
*
* @return update - Update type
*/
async function enqueueMessagesAndUpdateHome(
chainDetails,
homeDomain,
messages,
) {
const home = getHome(chainDetails, homeDomain);
const updater = getUpdaterObject(chainDetails, homeDomain);
export async function enqueueMessagesAndUpdateHome(
home: Home,
messages: MessageDetails[],
updater: Updater,
): Promise<Update> {
const homeDomain = await home.localDomain();
const startRoot = await home.current();
const oldRoot = await home.current();
// enqueue each message to Home and get the intermediate root
const enqueuedRoots = [];
for (let message of messages) {
const newRoot = await enqueueMessageToHome(
chainDetails,
homeDomain,
message,
);
const newRoot = await enqueueMessageToHome(home, message);
enqueuedRoots.push(newRoot);
}
@ -73,17 +81,17 @@ async function enqueueMessagesAndUpdateHome(
expect(await home.queueContains(root)).to.be.true;
}
// sign & submit an update from startRoot to finalRoot
const finalRoot = enqueuedRoots[enqueuedRoots.length - 1];
// sign & submit an update from oldRoot to newRoot
const newRoot = enqueuedRoots[enqueuedRoots.length - 1];
const { signature } = await updater.signUpdate(startRoot, finalRoot);
const { signature } = await updater.signUpdate(oldRoot, newRoot);
await expect(home.update(startRoot, finalRoot, signature))
await expect(home.update(oldRoot, newRoot, signature))
.to.emit(home, 'Update')
.withArgs(homeDomain, startRoot, finalRoot, signature);
.withArgs(homeDomain, oldRoot, newRoot, signature);
// ensure that Home root is now finalRoot
expect(await home.current()).to.equal(finalRoot);
// ensure that Home root is now newRoot
expect(await home.current()).to.equal(newRoot);
// ensure that Home queue no longer contains
// any of the roots we just enqueued -
@ -92,13 +100,11 @@ async function enqueueMessagesAndUpdateHome(
expect(await home.queueContains(root)).to.be.false;
}
const update = {
startRoot,
finalRoot,
return {
oldRoot,
newRoot,
signature,
};
return update;
}
/*
@ -111,23 +117,20 @@ async function enqueueMessagesAndUpdateHome(
*
* @return finalRoot - updated state root enqueued to the Replica
*/
async function enqueueUpdateToReplica(
chainDetails,
latestUpdateOnOriginChain,
homeDomain,
replicaDomain,
) {
const replica = getReplica(chainDetails, replicaDomain, homeDomain);
const { startRoot, finalRoot, signature } = latestUpdateOnOriginChain;
await expect(replica.update(startRoot, finalRoot, signature))
export async function enqueueUpdateToReplica(
latestUpdateOnOriginChain: Update,
replica: Replica,
): Promise<string> {
const homeDomain = await replica.remoteDomain();
const { oldRoot, newRoot, signature } = latestUpdateOnOriginChain;
await expect(replica.update(oldRoot, newRoot, signature))
.to.emit(replica, 'Update')
.withArgs(homeDomain, startRoot, finalRoot, signature);
.withArgs(homeDomain, oldRoot, newRoot, signature);
expect(await replica.queueEnd()).to.equal(finalRoot);
expect(await replica.queueEnd()).to.equal(newRoot);
return finalRoot;
return newRoot;
}
/*
@ -139,26 +142,24 @@ async function enqueueUpdateToReplica(
*
* @return message - Message type
*/
function formatMessage(
messageString,
messageDestinationDomain,
messageRecipient,
) {
const message = {
message: messageString,
destinationDomain: messageDestinationDomain,
recipientAddress: messageRecipient,
export function formatMessage(
message: string,
destinationDomain: number,
recipientAddress: Address,
): MessageDetails {
return {
message,
destinationDomain,
recipientAddress,
};
return message;
}
async function formatOpticsMessage(
replica,
governorRouter,
destinationRouter,
message,
) {
export async function formatOpticsMessage(
replica: TestReplica,
governorRouter: TestGovernanceRouter,
destinationRouter: TestGovernanceRouter,
message: string,
): Promise<string> {
const sequence = await replica.nextToProcess();
const governorDomain = await governorRouter.localDomain();
const destinationDomain = await destinationRouter.localDomain();
@ -180,7 +181,11 @@ async function formatOpticsMessage(
return opticsMessage;
}
async function formatCall(destinationContract, functionStr, functionArgs) {
export async function formatCall(
destinationContract: types.Contract,
functionStr: string,
functionArgs: any[],
): Promise<CallData> {
// Set up data for call message
const callFunc = destinationContract.interface.getFunction(functionStr);
const callDataEncoded = destinationContract.interface.encodeFunctionData(
@ -194,13 +199,13 @@ async function formatCall(destinationContract, functionStr, functionArgs) {
};
}
function encodeData(contract, functionName, args) {
const func = contract.interface.getFunction(functionName);
return contract.interface.encodeFunctionData(func, args);
}
// Send a transaction from the specified signer
async function sendFromSigner(signer, contract, functionName, args) {
export async function sendFromSigner(
signer: types.Signer,
contract: types.Contract,
functionName: string,
args: any[],
) {
const data = encodeData(contract, functionName, args);
return signer.sendTransaction({
@ -209,11 +214,11 @@ async function sendFromSigner(signer, contract, functionName, args) {
});
}
module.exports = {
enqueueUpdateToReplica,
enqueueMessagesAndUpdateHome,
formatMessage,
formatCall,
formatOpticsMessage,
sendFromSigner,
};
function encodeData(
contract: types.Contract,
functionName: string,
args: any[],
): string {
const func = contract.interface.getFunction(functionName);
return contract.interface.encodeFunctionData(func, args);
}

@ -1,30 +1,37 @@
const { waffle, ethers } = require('hardhat');
const { provider, deployMockContract } = waffle;
const { expect } = require('chai');
const UpdaterManager = require('../artifacts/contracts/UpdaterManager.sol/UpdaterManager.json');
const {
testCases: homeDomainHashTestCases,
} = require('../../../vectors/homeDomainHashTestCases.json');
const {
testCases,
} = require('../../../vectors/destinationSequenceTestCases.json');
import { ethers, optics } from 'hardhat';
import { expect } from 'chai';
import { getTestDeploy } from './testChain';
import { OpticsState, Updater } from '../lib';
import { Signer } from '../lib/types';
import { Deploy } from '../../optics-deploy/src/chain';
import * as deploys from '../../optics-deploy/src/deployOptics';
import {
TestHome,
UpdaterManager__factory,
UpdaterManager,
} from '../../typechain/optics-core';
import homeDomainHashTestCases from '../../../vectors/homeDomainHash.json';
import destinationSequenceTestCases from '../../../vectors/destinationSequence.json';
const localDomain = 1000;
const destDomain = 2000;
const emptyAddress: string = '0x' + '00'.repeat(32);
describe('Home', async () => {
let home,
signer,
fakeSigner,
updater,
fakeUpdater,
recipient,
mockUpdaterManager;
let deploy: Deploy,
home: TestHome,
signer: Signer,
fakeSigner: Signer,
recipient: Signer,
updater: Updater,
fakeUpdater: Updater,
fakeUpdaterManager: UpdaterManager;
// Helper function that enqueues message and returns its root.
// The message recipient is the same for all messages enqueued.
const enqueueMessageAndGetRoot = async (message) => {
const enqueueMessageAndGetRoot = async (message: string) => {
message = ethers.utils.formatBytes32String(message);
await home.enqueue(
destDomain,
@ -36,34 +43,42 @@ describe('Home', async () => {
};
before(async () => {
[signer, fakeSigner, recipient] = provider.getWallets();
updater = await optics.Updater.fromSigner(signer, localDomain);
fakeUpdater = await optics.Updater.fromSigner(fakeSigner, localDomain);
mockUpdaterManager = await deployMockContract(signer, UpdaterManager.abi);
[signer, fakeSigner, recipient] = await ethers.getSigners();
updater = await Updater.fromSigner(signer, localDomain);
deploy = await getTestDeploy(localDomain, updater.address, []);
await deploys.deployUpdaterManager(deploy);
await deploys.deployUpgradeBeaconController(deploy);
fakeUpdater = await Updater.fromSigner(fakeSigner, localDomain);
// deploy fake UpdaterManager
const updaterManagerFactory = new UpdaterManager__factory(signer);
fakeUpdaterManager = await updaterManagerFactory.deploy(updater.address);
const ret = await fakeUpdaterManager.updater();
expect(ret).to.equal(signer.address);
});
beforeEach(async () => {
await mockUpdaterManager.mock.updater.returns(signer.address);
await mockUpdaterManager.mock.slashUpdater.returns();
const { contracts } = await optics.deployUpgradeSetupAndProxy(
'TestHome',
[localDomain],
[mockUpdaterManager.address],
);
// redeploy the home before each test run
await deploys.deployHome(deploy);
home = deploy.contracts.home?.proxy as TestHome;
home = contracts.proxyWithImplementation;
// set home on UpdaterManager
await deploy.contracts.updaterManager!.setHome(home.address);
});
it('Cannot be initialized twice', async () => {
await expect(home.initialize(signer.address)).to.be.revertedWith(
'Initializable: contract is already initialized',
);
await expect(
home.initialize(fakeUpdaterManager.address),
).to.be.revertedWith('Initializable: contract is already initialized');
});
it('Halts on fail', async () => {
await home.setFailed();
expect(await home.state()).to.equal(optics.State.FAILED);
expect(await home.state()).to.equal(OpticsState.FAILED);
const message = ethers.utils.formatBytes32String('message');
await expect(
@ -79,13 +94,16 @@ describe('Home', async () => {
// Compare Rust output in json file to solidity output (json file matches
// hash for local domain of 1000)
for (let testCase of homeDomainHashTestCases) {
const { contracts } = await optics.deployUpgradeSetupAndProxy(
'TestHome',
[testCase.homeDomain],
[mockUpdaterManager.address],
let deploy = await getTestDeploy(
testCase.homeDomain,
fakeUpdaterManager.address,
[],
);
const tempHome = contracts.proxyWithImplementation;
await deploys.deployUpdaterManager(deploy);
await deploys.deployUpgradeBeaconController(deploy);
await deploys.deployHome(deploy);
const tempHome = deploy.contracts.home?.proxy! as TestHome;
const { expectedDomainHash } = testCase;
const homeDomainHash = await tempHome.testHomeDomainHash();
expect(homeDomainHash).to.equal(expectedDomainHash);
@ -142,7 +160,6 @@ describe('Home', async () => {
it('Suggests current root and latest root on suggestUpdate', async () => {
const currentRoot = await home.current();
const message = ethers.utils.formatBytes32String('message');
await home.enqueue(
destDomain,
@ -150,7 +167,6 @@ describe('Home', async () => {
message,
);
const latestEnqueuedRoot = await home.queueEnd();
const [suggestedCurrent, suggestedNew] = await home.suggestUpdate();
expect(suggestedCurrent).to.equal(currentRoot);
expect(suggestedNew).to.equal(latestEnqueuedRoot);
@ -161,19 +177,18 @@ describe('Home', async () => {
expect(length).to.equal(0);
const [suggestedCurrent, suggestedNew] = await home.suggestUpdate();
expect(suggestedCurrent).to.equal(ethers.utils.formatBytes32String(0));
expect(suggestedNew).to.equal(ethers.utils.formatBytes32String(0));
expect(suggestedCurrent).to.equal(emptyAddress);
expect(suggestedNew).to.equal(emptyAddress);
});
it('Accepts a valid update', async () => {
const currentRoot = await home.current();
const newRoot = await enqueueMessageAndGetRoot('message');
const { signature } = await updater.signUpdate(currentRoot, newRoot);
await expect(home.update(currentRoot, newRoot, signature))
.to.emit(home, 'Update')
.withArgs(localDomain, currentRoot, newRoot, signature);
expect(await home.current()).to.equal(newRoot);
expect(await home.queueContains(newRoot)).to.be.false;
});
@ -183,12 +198,11 @@ describe('Home', async () => {
const newRoot1 = await enqueueMessageAndGetRoot('message1');
const newRoot2 = await enqueueMessageAndGetRoot('message2');
const newRoot3 = await enqueueMessageAndGetRoot('message3');
const { signature } = await updater.signUpdate(currentRoot, newRoot3);
await expect(home.update(currentRoot, newRoot3, signature))
.to.emit(home, 'Update')
.withArgs(localDomain, currentRoot, newRoot3, signature);
expect(await home.current()).to.equal(newRoot3);
expect(await home.queueContains(newRoot1)).to.be.false;
expect(await home.queueContains(newRoot2)).to.be.false;
@ -210,20 +224,18 @@ describe('Home', async () => {
it('Rejects update that does not exist in queue', async () => {
const currentRoot = await home.current();
const fakeNewRoot = ethers.utils.formatBytes32String('fake root');
const { signature } = await updater.signUpdate(currentRoot, fakeNewRoot);
await expect(home.update(currentRoot, fakeNewRoot, signature)).to.emit(
home,
'ImproperUpdate',
);
expect(await home.state()).to.equal(optics.State.FAILED);
expect(await home.state()).to.equal(OpticsState.FAILED);
});
it('Rejects update from non-updater address', async () => {
const currentRoot = await home.current();
const newRoot = await enqueueMessageAndGetRoot('message');
const { signature: fakeSignature } = await fakeUpdater.signUpdate(
currentRoot,
newRoot,
@ -237,13 +249,11 @@ describe('Home', async () => {
const firstRoot = await home.current();
const secondRoot = await enqueueMessageAndGetRoot('message');
const thirdRoot = await enqueueMessageAndGetRoot('message2');
const { signature } = await updater.signUpdate(firstRoot, secondRoot);
const { signature: signature2 } = await updater.signUpdate(
firstRoot,
thirdRoot,
);
await expect(
home.doubleUpdate(
firstRoot,
@ -252,17 +262,14 @@ describe('Home', async () => {
signature2,
),
).to.emit(home, 'DoubleUpdate');
expect(await home.state()).to.equal(optics.State.FAILED);
expect(await home.state()).to.equal(OpticsState.FAILED);
});
it('Correctly calculates destinationAndSequence', async () => {
for (let testCase of testCases) {
for (let testCase of destinationSequenceTestCases) {
let { destination, sequence, expectedDestinationAndSequence } = testCase;
const solidityDestinationAndSequence =
await home.testDestinationAndSequence(destination, sequence);
expect(solidityDestinationAndSequence).to.equal(
expectedDestinationAndSequence,
);

@ -1,19 +1,22 @@
const { ethers } = require('hardhat');
const { expect } = require('chai');
import { ethers } from 'hardhat';
import { expect } from 'chai';
const { testCases } = require('../../../vectors/merkleTestCases.json');
import { BytesArray } from '../lib/types';
import { TestMerkle, TestMerkle__factory } from '../../typechain/optics-core';
import merkleTestCases from '../../../vectors/merkle.json';
describe('Merkle', async () => {
for (let testCase of testCases) {
for (let testCase of merkleTestCases) {
const { testName, leaves, expectedRoot, proofs } = testCase;
describe(testName, async () => {
let merkle, root;
let merkle: TestMerkle, root: string;
before(async () => {
const Merkle = await ethers.getContractFactory('TestMerkle');
merkle = await Merkle.deploy();
await merkle.deployed();
const [signer] = await ethers.getSigners();
const merkleFactory = new TestMerkle__factory(signer);
merkle = await merkleFactory.deploy();
//insert the leaves
for (let leaf of leaves) {
@ -36,7 +39,11 @@ describe('Merkle', async () => {
for (let proof of proofs) {
const { leaf, path, index } = proof;
const proofRoot = await merkle.branchRoot(leaf, path, index);
const proofRoot = await merkle.branchRoot(
leaf,
path as BytesArray,
index,
);
expect(proofRoot).to.equal(root);
}
});

@ -1,22 +1,24 @@
const { waffle, ethers } = require('hardhat');
const { provider } = waffle;
const { expect } = require('chai');
const { testCases } = require('../../../vectors/messageTestCases.json');
import { ethers, optics } from 'hardhat';
import { expect } from 'chai';
import { TestMessage, TestMessage__factory } from '../../typechain/optics-core';
import testCases from '../../../vectors/message.json';
const remoteDomain = 1000;
const localDomain = 2000;
describe('Message', async () => {
let messageLib;
let messageLib: TestMessage;
before(async () => {
const MessageFactory = await ethers.getContractFactory('TestMessage');
messageLib = await MessageFactory.deploy();
await messageLib.deployed();
const [signer] = await ethers.getSigners();
const Message = new TestMessage__factory(signer);
messageLib = await Message.deploy();
});
it('Returns fields from a message', async () => {
const [sender, recipient] = provider.getWallets();
const [sender, recipient] = await ethers.getSigners();
const sequence = 1;
const body = ethers.utils.formatBytes32String('message');
@ -46,10 +48,10 @@ describe('Message', async () => {
it('Matches Rust-output OpticsMessage and leaf', async () => {
const origin = 1000;
const destination = 2000;
const sender = '0x1111111111111111111111111111111111111111';
const recipient = '0x2222222222222222222222222222222222222222';
const sequence = 1;
const destination = 2000;
const recipient = '0x2222222222222222222222222222222222222222';
const body = ethers.utils.arrayify('0x1234');
const opticsMessage = optics.formatMessage(
@ -63,10 +65,10 @@ describe('Message', async () => {
const {
origin: testOrigin,
destination: testDestination,
sender: testSender,
recipient: testRecipient,
sequence: testSequence,
destination: testDestination,
recipient: testRecipient,
body: testBody,
leaf,
} = testCases[0];

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

@ -1,37 +1,50 @@
const { waffle, ethers } = require('hardhat');
const { provider } = waffle;
const { expect } = require('chai');
const testUtils = require('./utils');
const {
testCases: homeDomainHashTestCases,
} = require('../../../vectors/homeDomainHashTestCases.json');
const {
testCases: merkleTestCases,
} = require('../../../vectors/merkleTestCases.json');
const {
testCases: proveAndProcessTestCases,
} = require('../../../vectors/proveAndProcessTestCases.json');
import { ethers, optics } from 'hardhat';
import { expect } from 'chai';
import { increaseTimestampBy } from './utils';
import { getTestDeploy } from './testChain';
import { Updater, OpticsState, MessageStatus } from '../lib';
import { Signer, BytesArray } from '../lib/types';
import * as contracts from '../../typechain/optics-core';
import { Deploy } from '../../optics-deploy/src/chain';
import {
deployUnenrolledReplica,
deployUpgradeBeaconController,
deployUpdaterManager,
} from '../../optics-deploy/src/deployOptics';
import homeDomainHashTestCases from '../../../vectors/homeDomainHash.json';
import merkleTestCases from '../../../vectors/merkle.json';
import proveAndProcessTestCases from '../../../vectors/proveAndProcess.json';
const remoteDomain = 1000;
const localDomain = 2000;
const remoteDomain = 1000;
const optimisticSeconds = 3;
const initialCurrentRoot = ethers.utils.formatBytes32String('current');
const initialIndex = 0;
const replicaContractName = 'TestReplica';
const replicaInitializeIdentifier =
'initialize(uint32, address, bytes32, uint256, uint32)';
describe('Replica', async () => {
let replica, signer, fakeSigner, updater, fakeUpdater, initializeArgs;
const enqueueValidUpdate = async (newRoot) => {
const badRecipientFactories = [
contracts.BadRecipient1__factory,
contracts.BadRecipient2__factory,
contracts.BadRecipient3__factory,
contracts.BadRecipient4__factory,
contracts.BadRecipient5__factory,
contracts.BadRecipient6__factory,
];
let deploys: Deploy[] = [];
let replica: contracts.TestReplica,
signer: Signer,
fakeSigner: Signer,
opticsMessageSender: Signer,
updater: Updater,
fakeUpdater: Updater;
const enqueueValidUpdate = async (newRoot: string) => {
let oldRoot;
if ((await replica.queueLength()) == 0) {
if ((await replica.queueLength()).isZero()) {
oldRoot = await replica.current();
} else {
const lastEnqueued = await replica.queueEnd();
oldRoot = lastEnqueued;
oldRoot = await replica.queueEnd();
}
const { signature } = await updater.signUpdate(oldRoot, newRoot);
@ -39,50 +52,44 @@ describe('Replica', async () => {
};
before(async () => {
[signer, fakeSigner] = provider.getWallets();
updater = await optics.Updater.fromSigner(signer, remoteDomain);
fakeUpdater = await optics.Updater.fromSigner(fakeSigner, remoteDomain);
[signer, fakeSigner, opticsMessageSender] = await ethers.getSigners();
updater = await Updater.fromSigner(signer, remoteDomain);
fakeUpdater = await Updater.fromSigner(fakeSigner, remoteDomain);
deploys.push(await getTestDeploy(localDomain, updater.address, []));
deploys.push(await getTestDeploy(remoteDomain, updater.address, []));
});
beforeEach(async () => {
const controller = null;
initializeArgs = [
remoteDomain,
updater.signer.address,
initialCurrentRoot,
optimisticSeconds,
initialIndex,
];
const { contracts } = await optics.deployUpgradeSetupAndProxy(
replicaContractName,
[localDomain],
initializeArgs,
controller,
replicaInitializeIdentifier,
);
await deployUpdaterManager(deploys[0]);
await deployUpgradeBeaconController(deploys[0]);
await deployUnenrolledReplica(deploys[0], deploys[1]);
replica = contracts.proxyWithImplementation;
replica = deploys[0].contracts.replicas[remoteDomain]
.proxy! as contracts.TestReplica;
});
it('Cannot be initialized twice', async () => {
const initializeData = await optics.getInitializeData(
replicaContractName,
initializeArgs,
replicaInitializeIdentifier,
);
let initData = replica.interface.encodeFunctionData('initialize', [
deploys[0].chain.domain,
deploys[0].chain.updater,
ethers.constants.HashZero,
deploys[0].chain.optimisticSeconds,
0,
]);
await expect(
signer.sendTransaction({
to: replica.address,
data: initializeData,
data: initData,
}),
).to.be.revertedWith('Initializable: contract is already initialized');
});
it('Halts on fail', async () => {
await replica.setFailed();
expect(await replica.state()).to.equal(optics.State.FAILED);
expect(await replica.state()).to.equal(OpticsState.FAILED);
const newRoot = ethers.utils.formatBytes32String('new root');
await expect(enqueueValidUpdate(newRoot)).to.be.revertedWith(
@ -93,21 +100,18 @@ describe('Replica', async () => {
it('Calculated domain hash matches Rust-produced domain hash', async () => {
// Compare Rust output in json file to solidity output (json file matches
// hash for remote domain of 1000)
let testDeploy = await getTestDeploy(0, updater.address, []);
for (let testCase of homeDomainHashTestCases) {
const { contracts } = await optics.deployUpgradeSetupAndProxy(
'TestReplica',
[testCase.homeDomain],
[
testCase.homeDomain,
updater.signer.address,
initialCurrentRoot,
optimisticSeconds,
initialIndex,
],
null,
'initialize(uint32, address, bytes32, uint256, uint32)',
);
const tempReplica = contracts.proxyWithImplementation;
// set domain, updaterManager and upgradeBeaconController
testDeploy.chain.domain = testCase.homeDomain;
testDeploy.contracts.updaterManager = deploys[0].contracts.updaterManager;
testDeploy.contracts.upgradeBeaconController =
deploys[0].contracts.upgradeBeaconController;
// deploy replica
await deployUnenrolledReplica(testDeploy, testDeploy);
const tempReplica = testDeploy.contracts.replicas[testCase.homeDomain]
.proxy! as contracts.TestReplica;
const { expectedDomainHash } = testCase;
const homeDomainHash = await tempReplica.testHomeDomainHash();
@ -205,14 +209,14 @@ describe('Replica', async () => {
),
).to.emit(replica, 'DoubleUpdate');
expect(await replica.state()).to.equal(optics.State.FAILED);
expect(await replica.state()).to.equal(OpticsState.FAILED);
});
it('Confirms a ready update', async () => {
const newRoot = ethers.utils.formatBytes32String('new root');
await enqueueValidUpdate(newRoot);
await testUtils.increaseTimestampBy(provider, optimisticSeconds);
await increaseTimestampBy(ethers.provider, optimisticSeconds);
expect(await replica.canConfirm()).to.be.true;
await replica.confirm();
@ -227,7 +231,7 @@ describe('Replica', async () => {
await enqueueValidUpdate(secondNewRoot);
// Increase time enough for both updates to be confirmable
await testUtils.increaseTimestampBy(provider, optimisticSeconds * 2);
await increaseTimestampBy(ethers.provider, optimisticSeconds * 2);
expect(await replica.canConfirm()).to.be.true;
await replica.confirm();
@ -248,7 +252,7 @@ describe('Replica', async () => {
// Don't increase time enough for update to be confirmable.
// Note that we use optimisticSeconds - 2 because the call to enqueue
// the valid root has already increased the timestamp by 1.
await testUtils.increaseTimestampBy(provider, optimisticSeconds - 2);
await increaseTimestampBy(ethers.provider, optimisticSeconds - 2);
expect(await replica.canConfirm()).to.be.false;
await expect(replica.confirm()).to.be.revertedWith('!time');
@ -262,10 +266,11 @@ describe('Replica', async () => {
await replica.setCurrentRoot(testCase.expectedRoot);
// Ensure proper static call return value
expect(await replica.callStatic.prove(leaf, path, index)).to.be.true;
expect(await replica.callStatic.prove(leaf, path as BytesArray, index)).to
.be.true;
await replica.prove(leaf, path, index);
expect(await replica.messages(leaf)).to.equal(optics.MessageStatus.PENDING);
await replica.prove(leaf, path as BytesArray, index);
expect(await replica.messages(leaf)).to.equal(MessageStatus.PENDING);
});
it('Rejects an already-proven message', async () => {
@ -275,13 +280,13 @@ describe('Replica', async () => {
await replica.setCurrentRoot(testCase.expectedRoot);
// Prove message, which changes status to MessageStatus.Pending
await replica.prove(leaf, path, index);
expect(await replica.messages(leaf)).to.equal(optics.MessageStatus.PENDING);
await replica.prove(leaf, path as BytesArray, index);
expect(await replica.messages(leaf)).to.equal(MessageStatus.PENDING);
// Try to prove message again
await expect(replica.prove(leaf, path, index)).to.be.revertedWith(
'!MessageStatus.None',
);
await expect(
replica.prove(leaf, path as BytesArray, index),
).to.be.revertedWith('!MessageStatus.None');
});
it('Rejects invalid message proof', async () => {
@ -296,15 +301,18 @@ describe('Replica', async () => {
await replica.setCurrentRoot(testCase.expectedRoot);
expect(await replica.callStatic.prove(leaf, path, index)).to.be.false;
expect(await replica.callStatic.prove(leaf, path as BytesArray, index)).to
.be.false;
await replica.prove(leaf, path, index);
expect(await replica.messages(leaf)).to.equal(optics.MessageStatus.NONE);
await replica.prove(leaf, path as BytesArray, index);
expect(await replica.messages(leaf)).to.equal(MessageStatus.NONE);
});
it('Processes a proved message', async () => {
const sender = testUtils.opticsMessageSender;
const mockRecipient = await optics.deployImplementation('TestRecipient');
const sender = opticsMessageSender;
const testRecipientFactory = new contracts.TestRecipient__factory(signer);
const testRecipient = await testRecipientFactory.deploy();
const sequence = await replica.nextToProcess();
const opticsMessage = optics.formatMessage(
@ -312,7 +320,7 @@ describe('Replica', async () => {
sender.address,
sequence,
localDomain,
mockRecipient.address,
testRecipient.address,
'0x',
);
@ -328,7 +336,7 @@ describe('Replica', async () => {
});
it('Fails to process an unproved message', async () => {
const [sender, recipient] = provider.getWallets();
const [sender, recipient] = await ethers.getSigners();
const sequence = await replica.nextToProcess();
const body = ethers.utils.formatBytes32String('message');
@ -344,35 +352,33 @@ describe('Replica', async () => {
await expect(replica.process(opticsMessage)).to.be.revertedWith('!pending');
});
for (let i = 1; i <= 6; i++) {
it(
'Processes a message from a badly implemented recipient (' + i + ')',
async () => {
const sender = testUtils.opticsMessageSender;
const mockRecipient = await optics.deployImplementation(
`BadRecipient${i}`,
);
const sequence = await replica.nextToProcess();
const opticsMessage = optics.formatMessage(
remoteDomain,
sender.address,
sequence,
localDomain,
mockRecipient.address,
'0x',
);
// Set message status to MessageStatus.Pending
await replica.setMessagePending(opticsMessage);
await replica.process(opticsMessage);
expect(await replica.nextToProcess()).to.equal(sequence + 1);
},
);
for (let i = 0; i < badRecipientFactories.length; i++) {
it(`Processes a message from a badly implemented recipient (${
i + 1
})`, async () => {
const sender = opticsMessageSender;
const factory = new badRecipientFactories[i](signer);
const badRecipient = await factory.deploy();
const sequence = await replica.nextToProcess();
const opticsMessage = optics.formatMessage(
remoteDomain,
sender.address,
sequence,
localDomain,
badRecipient.address,
'0x',
);
// Set message status to MessageStatus.Pending
await replica.setMessagePending(opticsMessage);
await replica.process(opticsMessage);
expect(await replica.nextToProcess()).to.equal(sequence + 1);
});
}
it('Fails to process message with wrong destination Domain', async () => {
const [sender, recipient] = provider.getWallets();
const [sender, recipient] = await ethers.getSigners();
const sequence = await replica.nextToProcess();
const body = ethers.utils.formatBytes32String('message');
@ -392,7 +398,7 @@ describe('Replica', async () => {
});
it('Fails to process an undergased transaction', async () => {
const [sender, recipient] = provider.getWallets();
const [sender, recipient] = await ethers.getSigners();
const sequence = await replica.nextToProcess();
const body = ethers.utils.formatBytes32String('message');
@ -415,12 +421,10 @@ describe('Replica', async () => {
});
it('Returns false when processing message for bad handler function', async () => {
const sender = testUtils.opticsMessageSender;
const mockRecipient =
await testUtils.opticsMessageMockRecipient.getRecipient();
// Recipient handler function reverts
await mockRecipient.mock.handle.reverts();
const sender = opticsMessageSender;
const [recipient] = await ethers.getSigners();
const factory = new contracts.BadRecipientHandle__factory(recipient);
const testRecipient = await factory.deploy();
const sequence = await replica.nextToProcess();
const opticsMessage = optics.formatMessage(
@ -428,7 +432,7 @@ describe('Replica', async () => {
sender.address,
sequence,
localDomain,
mockRecipient.address,
testRecipient.address,
'0x',
);
@ -441,9 +445,9 @@ describe('Replica', async () => {
});
it('Proves and processes a message', async () => {
const sender = testUtils.opticsMessageSender;
const mockRecipient =
await testUtils.opticsMessageMockRecipient.getRecipient();
const sender = opticsMessageSender;
const testRecipientFactory = new contracts.TestRecipient__factory(signer);
const testRecipient = await testRecipientFactory.deploy();
const sequence = await replica.nextToProcess();
@ -454,33 +458,36 @@ describe('Replica', async () => {
sender.address,
sequence,
localDomain,
mockRecipient.address,
testRecipient.address,
'0x',
);
// Assert above message and test case have matching leaves
const { leaf, path, index } = proveAndProcessTestCases[0];
const { path, index } = proveAndProcessTestCases[0];
const messageLeaf = optics.messageToLeaf(opticsMessage);
expect(messageLeaf).to.equal(leaf);
// Set replica's current root to match newly computed root that includes
// the new leaf (normally root will have already been computed and path
// simply verifies leaf is in tree but because it is cryptographically
// impossible to find the inputs that create a pre-determined root, we
// simply recalculate root with the leaf using branchRoot)
const proofRoot = await replica.testBranchRoot(leaf, path, index);
const proofRoot = await replica.testBranchRoot(
messageLeaf,
path as BytesArray,
index,
);
await replica.setCurrentRoot(proofRoot);
await replica.proveAndProcess(opticsMessage, path, index);
await replica.proveAndProcess(opticsMessage, path as BytesArray, index);
expect(await replica.messages(leaf)).to.equal(
optics.MessageStatus.PROCESSED,
expect(await replica.messages(messageLeaf)).to.equal(
MessageStatus.PROCESSED,
);
expect(await replica.nextToProcess()).to.equal(sequence + 1);
});
it('Has proveAndProcess fail if prove fails', async () => {
const [sender, recipient] = provider.getWallets();
const [sender, recipient] = await ethers.getSigners();
const sequence = await replica.nextToProcess();
// Use 1st proof of 1st merkle vector test case
@ -500,11 +507,15 @@ describe('Replica', async () => {
// Ensure root given in proof and actual root don't match so that
// replica.prove(...) will fail
const actualRoot = await replica.current();
const proofRoot = await replica.testBranchRoot(leaf, path, index);
const proofRoot = await replica.testBranchRoot(
leaf,
path as BytesArray,
index,
);
expect(proofRoot).to.not.equal(actualRoot);
await expect(
replica.proveAndProcess(opticsMessage, path, index),
replica.proveAndProcess(opticsMessage, path as BytesArray, index),
).to.be.revertedWith('!prove');
});
});

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

@ -1,102 +1,79 @@
const { waffle, ethers } = require('hardhat');
const { provider, deployMockContract } = waffle;
const { expect } = require('chai');
const UpdaterManager = require('../artifacts/contracts/UpdaterManager.sol/UpdaterManager.json');
import { ethers, optics } from 'hardhat';
import { expect } from 'chai';
const {
testCases: signedFailureTestCases,
} = require('../../../vectors/signedFailureTestCases.json');
const testUtils = require('./utils');
import { getTestDeploy } from './testChain';
import { Updater } from '../lib';
import { Signer } from '../lib/types';
import { Deploy } from '../../optics-deploy/src/chain';
import * as deploys from '../../optics-deploy/src/deployOptics';
import { BeaconProxy } from '../../optics-deploy/src/proxyUtils';
import * as contracts from '../../typechain/optics-core';
import signedFailureTestCases from '../../../vectors/signedFailure.json';
const ONLY_OWNER_REVERT_MSG = 'Ownable: caller is not the owner';
const localDomain = 1000;
const remoteDomain = 2000;
const optimisticSeconds = 3;
const initialCurrentRoot = ethers.utils.formatBytes32String('current');
const initialIndex = 0;
const controller = null;
const walletProvider = new testUtils.WalletProvider(provider);
describe('XAppConnectionManager', async () => {
let connectionManager,
mockUpdaterManager,
enrolledReplica,
home,
signer,
updater;
let localDeploy: Deploy,
remoteDeploy: Deploy,
connectionManager: contracts.TestXAppConnectionManager,
updaterManager: contracts.UpdaterManager,
enrolledReplica: contracts.TestReplica,
home: BeaconProxy<contracts.Home>,
signer: Signer,
updater: Updater;
before(async () => {
[signer] = walletProvider.getWalletsPersistent(1);
updater = await optics.Updater.fromSigner(signer, remoteDomain);
});
beforeEach(async () => {
// Deploy XAppConnectionManager
connectionManager = await optics.deployImplementation(
'TestXAppConnectionManager',
[],
);
[signer] = await ethers.getSigners();
updater = await Updater.fromSigner(signer, localDomain);
// Deploy home's mock updater manager
mockUpdaterManager = await deployMockContract(signer, UpdaterManager.abi);
await mockUpdaterManager.mock.updater.returns(signer.address);
await mockUpdaterManager.mock.slashUpdater.returns();
// Deploy home
const { contracts: homeContracts } =
await optics.deployUpgradeSetupAndProxy(
'TestHome',
[localDomain],
[mockUpdaterManager.address],
);
home = homeContracts.proxyWithImplementation;
// Set XAppConnectionManager's home
await connectionManager.setHome(home.address);
// get fresh test deploys
localDeploy = await getTestDeploy(localDomain, updater.address, []);
remoteDeploy = await getTestDeploy(remoteDomain, updater.address, []);
// Deploy single replica
const { contracts } = await optics.deployUpgradeSetupAndProxy(
'TestReplica',
[localDomain],
[
remoteDomain,
updater.signer.address,
initialCurrentRoot,
optimisticSeconds,
initialIndex,
],
controller,
'initialize(uint32, address, bytes32, uint256, uint32)',
);
enrolledReplica = contracts.proxyWithImplementation;
// deploy optics on remote domain
// NB: as tests stand currently, this only needs to be done once
await deploys.deployOptics(remoteDeploy);
});
// Enroll replica and check that enrolling replica succeeded
await connectionManager.ownerEnrollReplica(
enrolledReplica.address,
remoteDomain,
);
beforeEach(async () => {
// deploy optics on local domain
await deploys.deployOptics(localDeploy);
// deploy replica and enroll on local deploy
await deploys.enrollRemote(localDeploy, remoteDeploy);
// set respective variables
connectionManager = localDeploy.contracts
.xAppConnectionManager! as contracts.TestXAppConnectionManager;
updaterManager = localDeploy.contracts.updaterManager!;
enrolledReplica = localDeploy.contracts.replicas[remoteDomain]
.proxy as contracts.TestReplica;
home = localDeploy.contracts.home!;
});
it('Returns the local domain', async () => {
expect(await connectionManager.localDomain()).to.equal(localDomain);
expect(await connectionManager!.localDomain()).to.equal(localDomain);
});
it('onlyOwner function rejects call from non-owner', async () => {
const [nonOwner, nonHome] = walletProvider.getWalletsEphemeral(2);
const [nonOwner, nonHome] = await ethers.getSigners();
await expect(
connectionManager.connect(nonOwner).setHome(nonHome.address),
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG);
});
it('isOwner returns true for owner and false for non-owner', async () => {
const [newOwner, nonOwner] = walletProvider.getWalletsEphemeral(2);
const [newOwner, nonOwner] = await ethers.getSigners();
await connectionManager.transferOwnership(newOwner.address);
expect(await connectionManager.isOwner(newOwner.address)).to.be.true;
expect(await connectionManager.isOwner(nonOwner.address)).to.be.false;
});
it('isReplica returns true for enrolledReplica and false for non-enrolled Replica', async () => {
const [nonEnrolledReplica] = walletProvider.getWalletsEphemeral(1);
const [nonEnrolledReplica] = await ethers.getSigners();
expect(await connectionManager.isReplica(enrolledReplica.address)).to.be
.true;
expect(await connectionManager.isReplica(nonEnrolledReplica.address)).to.be
@ -104,13 +81,8 @@ describe('XAppConnectionManager', async () => {
});
it('Allows owner to set the home', async () => {
const { contracts: newHomeContracts } =
await optics.deployUpgradeSetupAndProxy(
'TestHome',
[localDomain],
[mockUpdaterManager.address],
);
const newHome = newHomeContracts.proxyWithImplementation;
await deploys.deployHome(localDeploy);
const newHome = localDeploy.contracts.home?.proxy as contracts.TestHome;
await connectionManager.setHome(newHome.address);
expect(await connectionManager.home()).to.equal(newHome.address);
@ -118,36 +90,35 @@ describe('XAppConnectionManager', async () => {
it('Owner can enroll a new replica', async () => {
const newRemoteDomain = 3000;
const { contracts: newReplicaContracts } =
await optics.deployUpgradeSetupAndProxy(
'TestReplica',
[localDomain],
[
newRemoteDomain,
updater.signer.address,
initialCurrentRoot,
optimisticSeconds,
initialIndex,
],
controller,
'initialize(uint32, address, bytes32, uint256, uint32)',
);
const newReplica = newReplicaContracts.proxyWithImplementation;
const newRemoteDeploy = await getTestDeploy(
newRemoteDomain,
updater.address,
[],
);
// await deploys.deployOptics(newRemoteDeploy);
await deploys.deployUnenrolledReplica(localDeploy, newRemoteDeploy);
const newReplicaProxy =
localDeploy.contracts.replicas[newRemoteDomain].proxy;
// Assert new replica not considered replica before enrolled
expect(await connectionManager.isReplica(newReplica.address)).to.be.false;
expect(await connectionManager.isReplica(newReplicaProxy.address)).to.be
.false;
await expect(
connectionManager.ownerEnrollReplica(newReplica.address, newRemoteDomain),
connectionManager.ownerEnrollReplica(
newReplicaProxy.address,
newRemoteDomain,
),
).to.emit(connectionManager, 'ReplicaEnrolled');
expect(await connectionManager.domainToReplica(newRemoteDomain)).to.equal(
newReplica.address,
newReplicaProxy.address,
);
expect(
await connectionManager.replicaToDomain(newReplica.address),
await connectionManager.replicaToDomain(newReplicaProxy.address),
).to.equal(newRemoteDomain);
expect(await connectionManager.isReplica(newReplica.address)).to.be.true;
expect(await connectionManager.isReplica(newReplicaProxy.address)).to.be
.true;
});
it('Owner can unenroll a replica', async () => {
@ -166,7 +137,7 @@ describe('XAppConnectionManager', async () => {
});
it('Owner can set watcher permissions', async () => {
const [watcher] = walletProvider.getWalletsEphemeral(1);
const [watcher] = await ethers.getSigners();
expect(
await connectionManager.watcherPermission(watcher.address, remoteDomain),
).to.be.false;
@ -186,7 +157,7 @@ describe('XAppConnectionManager', async () => {
it('Unenrolls a replica given valid SignedFailureNotification', async () => {
// Set watcher permissions for domain of currently enrolled replica
const [watcher] = walletProvider.getWalletsEphemeral(1);
const [watcher] = await ethers.getSigners();
await connectionManager.setWatcherPermission(
watcher.address,
remoteDomain,
@ -228,7 +199,7 @@ describe('XAppConnectionManager', async () => {
const noReplicaDomain = 3000;
// Set watcher permissions for noReplicaDomain
const [watcher] = walletProvider.getWalletsEphemeral(1);
const [watcher] = await ethers.getSigners();
await connectionManager.setWatcherPermission(
watcher.address,
noReplicaDomain,
@ -254,7 +225,7 @@ describe('XAppConnectionManager', async () => {
});
it('unenrollReplica reverts if provided updater does not match replica updater', async () => {
const [watcher, nonUpdater] = walletProvider.getWalletsEphemeral(2);
const [watcher, nonUpdater] = await ethers.getSigners();
// Set watcher permissions
await connectionManager.setWatcherPermission(
@ -282,7 +253,7 @@ describe('XAppConnectionManager', async () => {
});
it('unenrollReplica reverts if incorrect watcher provided', async () => {
const [watcher, nonWatcher] = walletProvider.getWalletsEphemeral(2);
const [watcher, nonWatcher] = await ethers.getSigners();
// Set watcher permissions
await connectionManager.setWatcherPermission(

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