Improve ergonomics of app mixins (#330)

Co-authored-by: Yorke Rhodes <yorke@useabacus.network>
Co-authored-by: Asa Oines <asa@useabacus.network>
pull/340/head
Yorke Rhodes 3 years ago committed by GitHub
parent c2206c814c
commit 9cc2a4388b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/workflows/node.yml
  2. 196
      package-lock.json
  3. 1
      package.json
  4. 15
      solidity/app/.eslintrc.json
  5. 8
      solidity/app/.gitignore
  6. 18
      solidity/app/.prettierrc
  7. 3
      solidity/app/.solcover.js
  8. 10
      solidity/app/.solhint.json
  9. 28
      solidity/app/contracts/Router.sol
  10. 14
      solidity/app/contracts/XAppConnectionClient.sol
  11. 47
      solidity/app/contracts/test/TestRouter.sol
  12. 32
      solidity/app/contracts/test/TestXAppConnectionClient.sol
  13. 35
      solidity/app/hardhat.config.ts
  14. 43
      solidity/app/package.json
  15. 105
      solidity/app/test/router.test.ts
  16. 96
      solidity/app/test/xAppConnectionClient.test.ts
  17. 16
      solidity/app/tsconfig.json
  18. 14
      solidity/apps/contracts/governance/GovernanceRouter.sol
  19. 10
      solidity/apps/contracts/ping-pong/PingPongRouter.sol
  20. 13
      solidity/apps/contracts/xapp-template/RouterTemplate.sol
  21. 9
      solidity/apps/package.json
  22. 79
      solidity/apps/test/governance/governanceRouter.test.ts
  23. 26
      solidity/apps/test/governance/lib/utils.ts
  24. 11
      solidity/core/contracts/XAppConnectionManager.sol
  25. 12
      solidity/core/interfaces/IXAppConnectionManager.sol
  26. 4
      solidity/core/package.json
  27. 11
      solidity/core/test/badrecipient.test.ts
  28. 2
      typescript/contract-metrics/package.json
  29. 6
      typescript/deploy/package.json
  30. 1
      typescript/hardhat/.gitignore
  31. 29
      typescript/hardhat/hardhat.config.ts
  32. 10
      typescript/hardhat/package.json
  33. 109
      typescript/hardhat/src/TestAbacusDeploy.ts
  34. 72
      typescript/hardhat/test/testAbacusDeploy.test.ts
  35. 3
      typescript/infra/package.json
  36. 8
      typescript/sdk/package.json
  37. 2
      typescript/utils/package.json
  38. 12
      typescript/utils/src/utils.ts

@ -87,6 +87,8 @@ jobs:
run: npm --prefix ./typescript/sdk run test
- name: infra
run: npm --prefix ./typescript/infra run test
- name: hardhat
run: npm --prefix ./typescript/hardhat run test
test-sol:
env:
@ -104,5 +106,7 @@ jobs:
- name: core
run: npm --prefix ./solidity/core run test
- name: app
run: npm --prefix ./solidity/app run test
- name: apps
run: npm --prefix ./solidity/apps run test

196
package-lock.json generated

@ -9,6 +9,7 @@
"typescript/utils",
"solidity/core",
"typescript/hardhat",
"solidity/app",
"solidity/apps",
"typescript/sdk",
"typescript/contract-metrics",
@ -16,6 +17,10 @@
"typescript/infra"
]
},
"node_modules/@abacus-network/app": {
"resolved": "solidity/app",
"link": true
},
"node_modules/@abacus-network/apps": {
"resolved": "solidity/apps",
"link": true
@ -24910,14 +24915,51 @@
"typescript": "^4.3.5"
}
},
"solidity/app": {
"name": "@abacus-network/app",
"version": "0.0.3",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.8",
"@openzeppelin/contracts-upgradeable": "^4.5.0"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@typechain/ethers-v5": "~7.0.0",
"@typechain/hardhat": "^2.0.1",
"@types/mocha": "^9.1.0",
"chai": "^4.3.0",
"eslint": "^7.20.0",
"ethereum-waffle": "^3.2.2",
"ethers": "^5.4.4",
"hardhat": "^2.8.3",
"hardhat-gas-reporter": "^1.0.7",
"prettier": "^2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.5",
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.14",
"ts-node": "^10.1.0",
"typechain": "^5.0.0",
"typescript": "^4.3.5"
}
},
"solidity/app/node_modules/@openzeppelin/contracts-upgradeable": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.5.2.tgz",
"integrity": "sha512-xgWZYaPlrEOQo3cBj97Ufiuv79SPd8Brh4GcFYhPgb6WvAq4ppz8dWKL6h+jLAK01rUqMRp/TS9AdXgAeNvCLA=="
},
"solidity/apps": {
"name": "@abacus-network/apps",
"version": "0.0.1",
"version": "0.0.2",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/hardhat": "^0.0.8",
"@abacus-network/utils": "^0.0.7",
"@abacus-network/app": "^0.0.3",
"@abacus-network/core": "^0.0.6",
"@abacus-network/hardhat": "^0.0.18",
"@abacus-network/utils": "^0.0.8",
"@openzeppelin/contracts": "~3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0"
@ -24946,10 +24988,10 @@
},
"solidity/core": {
"name": "@abacus-network/core",
"version": "0.0.3",
"version": "0.0.6",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/utils": "^0.0.7",
"@abacus-network/utils": "^0.0.8",
"@openzeppelin/contracts": "^3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0",
@ -25038,7 +25080,7 @@
},
"typescript/contract-metrics": {
"name": "@abacus-network/contract-metrics",
"version": "0.0.0",
"version": "0.0.1",
"dependencies": {
"@abacus-network/apps": "file:../../solidity/apps",
"@abacus-network/sdk": "file:../sdk",
@ -25074,11 +25116,11 @@
},
"typescript/deploy": {
"name": "@abacus-network/deploy",
"version": "0.0.6",
"version": "0.0.7",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/sdk": "^0.0.3",
"@abacus-network/core": "^0.0.6",
"@abacus-network/sdk": "^0.0.4",
"@types/node": "^16.9.1",
"axios": "^0.21.3"
},
@ -25097,10 +25139,10 @@
},
"typescript/hardhat": {
"name": "@abacus-network/hardhat",
"version": "0.0.8",
"version": "0.0.18",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/core": "^0.0.5",
"@abacus-network/utils": "^0.0.7",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.2",
@ -25114,7 +25156,38 @@
"typescript": "^4.3.2"
}
},
"typescript/hardhat/node_modules/@abacus-network/core": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/core/-/core-0.0.5.tgz",
"integrity": "sha512-DKDqnGGfsgKrNzZvc9ewD8DS+4yVs+ugpGZtlAjlOzwpZ97Rk61RjrSBO0p4g85UtVg5GI0CLqfzgYdWSS81eQ==",
"dependencies": {
"@abacus-network/utils": "^0.0.5",
"@openzeppelin/contracts": "^3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0",
"ts-generator": "^0.1.1"
}
},
"typescript/hardhat/node_modules/@abacus-network/core/node_modules/@abacus-network/utils": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.5.tgz",
"integrity": "sha512-B8zS1RlKh8Pu7hifeenbAtPg2PRubgcPx3MyJz0TE8ryyDDOd54Xl9g96Xx9mrF7xeLRXBkACsi8S6DkgK+GOw==",
"dependencies": {
"chai": "^4.3.0",
"ethers": "^5.4.7"
}
},
"typescript/hardhat/node_modules/@abacus-network/utils": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.7.tgz",
"integrity": "sha512-hrL8+mIk0SI+Wde7PWGC/3GZsZtVpYvQP/GHX8/ZBKuiP7F28bgSCa8ZZc8jZvhcxm0XmMkpoAaMeGjaehg+0Q==",
"dependencies": {
"chai": "^4.3.0",
"ethers": "^5.4.7"
}
},
"typescript/infra": {
"version": "0.0.1",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/apps": "file:../../solidity/apps",
@ -25154,12 +25227,12 @@
},
"typescript/sdk": {
"name": "@abacus-network/sdk",
"version": "0.0.3",
"version": "0.0.4",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/apps": "^0.0.1",
"@abacus-network/core": "^0.0.3",
"@abacus-network/utils": "^0.0.7",
"@abacus-network/apps": "^0.0.2",
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.8",
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/bytes": "^5.5.0",
"celo-ethers-provider": "0.0.0",
@ -25489,7 +25562,7 @@
},
"typescript/utils": {
"name": "@abacus-network/utils",
"version": "0.0.7",
"version": "0.0.8",
"license": "MIT OR Apache-2.0",
"dependencies": {
"chai": "^4.3.0",
@ -25498,12 +25571,47 @@
}
},
"dependencies": {
"@abacus-network/app": {
"version": "file:solidity/app",
"requires": {
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.8",
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts-upgradeable": "^4.5.0",
"@typechain/ethers-v5": "~7.0.0",
"@typechain/hardhat": "^2.0.1",
"@types/mocha": "^9.1.0",
"chai": "^4.3.0",
"eslint": "^7.20.0",
"ethereum-waffle": "^3.2.2",
"ethers": "^5.4.4",
"hardhat": "^2.8.3",
"hardhat-gas-reporter": "^1.0.7",
"prettier": "^2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.5",
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.14",
"ts-node": "^10.1.0",
"typechain": "^5.0.0",
"typescript": "^4.3.5"
},
"dependencies": {
"@openzeppelin/contracts-upgradeable": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.5.2.tgz",
"integrity": "sha512-xgWZYaPlrEOQo3cBj97Ufiuv79SPd8Brh4GcFYhPgb6WvAq4ppz8dWKL6h+jLAK01rUqMRp/TS9AdXgAeNvCLA=="
}
}
},
"@abacus-network/apps": {
"version": "file:solidity/apps",
"requires": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/hardhat": "^0.0.8",
"@abacus-network/utils": "^0.0.7",
"@abacus-network/app": "^0.0.3",
"@abacus-network/core": "^0.0.6",
"@abacus-network/hardhat": "^0.0.18",
"@abacus-network/utils": "^0.0.8",
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "~3.4.2",
@ -25565,7 +25673,7 @@
"@abacus-network/core": {
"version": "file:solidity/core",
"requires": {
"@abacus-network/utils": "^0.0.7",
"@abacus-network/utils": "^0.0.8",
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^3.4.2",
@ -25592,8 +25700,8 @@
"@abacus-network/deploy": {
"version": "file:typescript/deploy",
"requires": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/sdk": "^0.0.3",
"@abacus-network/core": "^0.0.6",
"@abacus-network/sdk": "^0.0.4",
"@typechain/ethers-v5": "~7.0.0",
"@types/node": "^16.9.1",
"axios": "^0.21.3",
@ -25613,7 +25721,7 @@
"@abacus-network/hardhat": {
"version": "file:typescript/hardhat",
"requires": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/core": "^0.0.5",
"@abacus-network/utils": "^0.0.7",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.2",
@ -25625,14 +25733,48 @@
"ts-node": "^10.1.0",
"typechain": "^5.0.0",
"typescript": "^4.3.2"
},
"dependencies": {
"@abacus-network/core": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/core/-/core-0.0.5.tgz",
"integrity": "sha512-DKDqnGGfsgKrNzZvc9ewD8DS+4yVs+ugpGZtlAjlOzwpZ97Rk61RjrSBO0p4g85UtVg5GI0CLqfzgYdWSS81eQ==",
"requires": {
"@abacus-network/utils": "^0.0.5",
"@openzeppelin/contracts": "^3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0",
"ts-generator": "^0.1.1"
},
"dependencies": {
"@abacus-network/utils": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.5.tgz",
"integrity": "sha512-B8zS1RlKh8Pu7hifeenbAtPg2PRubgcPx3MyJz0TE8ryyDDOd54Xl9g96Xx9mrF7xeLRXBkACsi8S6DkgK+GOw==",
"requires": {
"chai": "^4.3.0",
"ethers": "^5.4.7"
}
}
}
},
"@abacus-network/utils": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@abacus-network/utils/-/utils-0.0.7.tgz",
"integrity": "sha512-hrL8+mIk0SI+Wde7PWGC/3GZsZtVpYvQP/GHX8/ZBKuiP7F28bgSCa8ZZc8jZvhcxm0XmMkpoAaMeGjaehg+0Q==",
"requires": {
"chai": "^4.3.0",
"ethers": "^5.4.7"
}
}
}
},
"@abacus-network/sdk": {
"version": "file:typescript/sdk",
"requires": {
"@abacus-network/apps": "^0.0.1",
"@abacus-network/core": "^0.0.3",
"@abacus-network/utils": "^0.0.7",
"@abacus-network/apps": "^0.0.2",
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.8",
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/bytes": "^5.5.0",
"@types/node": "^16.9.1",

@ -9,6 +9,7 @@
"typescript/utils",
"solidity/core",
"typescript/hardhat",
"solidity/app",
"solidity/apps",
"typescript/sdk",
"typescript/contract-metrics",

@ -0,0 +1,15 @@
{
"env": {
"browser": true,
"node": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
"no-undef": "off"
}
}

@ -0,0 +1,8 @@
node_modules/
cache/
artifacts/
types/
dist/
coverage/
coverage.json
.env

@ -0,0 +1,18 @@
{
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all",
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
"explicitTypes": "always"
}
}
]
}

@ -0,0 +1,3 @@
module.exports = {
skipFiles: ["test"],
};

@ -0,0 +1,10 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["error", "^0.6.11"],
"func-visibility": ["warn", {"ignoreConstructors":true}],
"not-rely-on-time": "off",
"avoid-low-level-calls": "off",
"no-inline-assembly": "off"
}
}

@ -3,7 +3,7 @@ pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {XAppConnectionClient} from "./XAppConnectionClient.sol";
import {IMessageRecipient} from "../../interfaces/IMessageRecipient.sol";
import {IMessageRecipient} from "@abacus-network/core/interfaces/IMessageRecipient.sol";
abstract contract Router is XAppConnectionClient, IMessageRecipient {
// ============ Mutable Storage ============
@ -32,6 +32,12 @@ abstract contract Router is XAppConnectionClient, IMessageRecipient {
_;
}
// ======== Initializer =========
function __Router_initialize(address _xAppConnectionManager) internal {
__XAppConnectionClient_initialize(_xAppConnectionManager);
}
// ============ External functions ============
/**
@ -47,13 +53,27 @@ abstract contract Router is XAppConnectionClient, IMessageRecipient {
_enrollRemoteRouter(_domain, _router);
}
// ============ Virtual functions ============
/**
* @notice Handles an incoming message
* @param _origin The origin domain
* @param _sender The sender address
* @param _message The message
*/
function handle(
uint32 _origin,
bytes32 _sender,
bytes memory _message
) external virtual override;
) external virtual override onlyInbox onlyRemoteRouter(_origin, _sender) {
// TODO: callbacks on success/failure
_handle(_origin, _sender, _message);
}
// ============ Virtual functions ============
function _handle(
uint32 _origin,
bytes32 _sender,
bytes memory _message
) internal virtual;
// ============ Internal functions ============

@ -2,16 +2,17 @@
pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {IInterchainGasPaymaster} from "../../interfaces/IInterchainGasPaymaster.sol";
import {IOutbox} from "../../interfaces/IOutbox.sol";
import {XAppConnectionManager} from "../XAppConnectionManager.sol";
import {IInterchainGasPaymaster} from "@abacus-network/core/interfaces/IInterchainGasPaymaster.sol";
import {IOutbox} from "@abacus-network/core/interfaces/IOutbox.sol";
import {IXAppConnectionManager} from "@abacus-network/core/interfaces/IXAppConnectionManager.sol";
// ============ External Imports ============
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
abstract contract XAppConnectionClient is OwnableUpgradeable {
// ============ Mutable Storage ============
XAppConnectionManager public xAppConnectionManager;
IXAppConnectionManager public xAppConnectionManager;
uint256[49] private __GAP; // gap for upgrade safety
// ============ Events ============
@ -36,9 +37,8 @@ abstract contract XAppConnectionClient is OwnableUpgradeable {
function __XAppConnectionClient_initialize(address _xAppConnectionManager)
internal
initializer
{
xAppConnectionManager = XAppConnectionManager(_xAppConnectionManager);
_setXAppConnectionManager(_xAppConnectionManager);
__Ownable_init();
}
@ -65,7 +65,7 @@ abstract contract XAppConnectionClient is OwnableUpgradeable {
function _setXAppConnectionManager(address _xAppConnectionManager)
internal
{
xAppConnectionManager = XAppConnectionManager(_xAppConnectionManager);
xAppConnectionManager = IXAppConnectionManager(_xAppConnectionManager);
emit SetXAppConnectionManager(_xAppConnectionManager);
}

@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import "../Router.sol";
contract TestRouter is Router {
function initialize(address _xAppConnectionManager) external initializer {
__Router_initialize(_xAppConnectionManager);
}
function _handle(
uint32,
bytes32,
bytes memory
) internal pure override {}
function isRemoteRouter(uint32 _domain, bytes32 _potentialRemoteRouter)
external
view
returns (bool)
{
return _isRemoteRouter(_domain, _potentialRemoteRouter);
}
function mustHaveRemoteRouter(uint32 _domain)
external
view
returns (bytes32)
{
return _mustHaveRemoteRouter(_domain);
}
function dispatchToRemoteRouter(uint32 _destination, bytes calldata _msg)
external
returns (uint256)
{
return _dispatchToRemoteRouter(_destination, _msg);
}
function dispatchToRemoteRouterWithGas(
uint32 _destination,
bytes calldata _msg,
uint256 _gasPayment
) external {
return _dispatchToRemoteRouterWithGas(_destination, _msg, _gasPayment);
}
}

@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {IInterchainGasPaymaster} from "@abacus-network/core/interfaces/IInterchainGasPaymaster.sol";
import {IOutbox} from "@abacus-network/core/interfaces/IOutbox.sol";
import "../XAppConnectionClient.sol";
contract TestXAppConnectionClient is XAppConnectionClient {
function initialize(address _xAppConnectionManager) external initializer {
__XAppConnectionClient_initialize(_xAppConnectionManager);
}
function outbox() external view returns (IOutbox) {
return _outbox();
}
function interchainGasPaymaster()
external
view
returns (IInterchainGasPaymaster)
{
return _interchainGasPaymaster();
}
function isInbox(address _potentialInbox) external view returns (bool) {
return _isInbox(_potentialInbox);
}
function localDomain() external view returns (uint32) {
return _localDomain();
}
}

@ -0,0 +1,35 @@
import '@abacus-network/hardhat';
import '@nomiclabs/hardhat-waffle';
import '@typechain/hardhat';
import 'hardhat-gas-reporter';
import 'solidity-coverage';
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
compilers: [
{
version: '0.8.13',
},
{
version: '0.7.6',
settings: {
optimizer: {
enabled: true,
runs: 999999,
},
},
},
],
},
gasReporter: {
currency: 'USD',
},
typechain: {
outDir: './types',
target: 'ethers-v5',
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
},
};

@ -0,0 +1,43 @@
{
"name": "@abacus-network/app",
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.1",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@typechain/ethers-v5": "~7.0.0",
"@typechain/hardhat": "^2.0.1",
"@types/mocha": "^9.1.0",
"chai": "^4.3.0",
"eslint": "^7.20.0",
"ethereum-waffle": "^3.2.2",
"ethers": "^5.4.4",
"hardhat": "^2.8.3",
"hardhat-gas-reporter": "^1.0.7",
"prettier": "^2.2.1",
"prettier-plugin-solidity": "^1.0.0-beta.5",
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.14",
"ts-node": "^10.1.0",
"typechain": "^5.0.0",
"typescript": "^4.3.5"
},
"version": "0.0.3",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"directories": {
"test": "test"
},
"scripts": {
"prettier": "prettier --write ./contracts ./test",
"build": "hardhat compile && hardhat typechain && npm run prettier && tsc && npm run copy-types",
"copy-types": "cp types/*.d.ts dist/",
"coverage": "hardhat coverage",
"test": "hardhat test"
},
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.8",
"@openzeppelin/contracts-upgradeable": "^4.5.0"
}
}

@ -0,0 +1,105 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import {
Outbox__factory,
XAppConnectionManager,
XAppConnectionManager__factory,
} from '@abacus-network/core';
import { utils } from '@abacus-network/utils';
import { TestRouter, TestRouter__factory } from '../types';
const ONLY_OWNER_REVERT_MSG = 'Ownable: caller is not the owner';
const origin = 1;
const destination = 2;
const message = '0xdeadbeef';
describe('Router', async () => {
let router: TestRouter,
connectionManager: XAppConnectionManager,
signer: SignerWithAddress,
nonOwner: SignerWithAddress;
before(async () => {
[signer, nonOwner] = await ethers.getSigners();
});
beforeEach(async () => {
const connectionManagerFactory = new XAppConnectionManager__factory(signer);
connectionManager = await connectionManagerFactory.deploy();
const routerFactory = new TestRouter__factory(signer);
router = await routerFactory.deploy();
await router.initialize(connectionManager.address);
});
it('Cannot be initialized twice', async () => {
await expect(
router.initialize(ethers.constants.AddressZero),
).to.be.revertedWith('Initializable: contract is already initialized');
});
it('accepts message from enrolled inbox and router', async () => {
await connectionManager.enrollInbox(origin, signer.address);
const remote = utils.addressToBytes32(nonOwner.address);
await router.enrollRemoteRouter(origin, remote);
// Does not revert.
await router.handle(origin, remote, message);
});
it('rejects message from unenrolled inbox', async () => {
await expect(
router.handle(origin, utils.addressToBytes32(nonOwner.address), message),
).to.be.revertedWith('!inbox');
});
it('rejects message from unenrolled router', async () => {
await connectionManager.enrollInbox(origin, signer.address);
await expect(
router.handle(origin, utils.addressToBytes32(nonOwner.address), message),
).to.be.revertedWith('!router');
});
it('owner can enroll remote router', async () => {
const remote = nonOwner.address;
const remoteBytes = utils.addressToBytes32(nonOwner.address);
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(false);
await expect(router.mustHaveRemoteRouter(origin)).to.be.revertedWith(
'!router',
);
await router.enrollRemoteRouter(origin, utils.addressToBytes32(remote));
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(true);
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes);
});
it('non-owner cannot enroll remote router', async () => {
await expect(
router
.connect(nonOwner)
.enrollRemoteRouter(origin, utils.addressToBytes32(nonOwner.address)),
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG);
});
it('dispatches message to enrolled remote router', async () => {
const outboxFactory = new Outbox__factory(signer);
const outbox = await outboxFactory.deploy(origin);
await connectionManager.setOutbox(outbox.address);
const remote = nonOwner.address;
await router.enrollRemoteRouter(
destination,
utils.addressToBytes32(remote),
);
await expect(router.dispatchToRemoteRouter(destination, message)).to.emit(
outbox,
'Dispatch',
);
});
it('reverts when dispatching message to unenrolled remote router', async () => {
await expect(
router.dispatchToRemoteRouter(destination, message),
).to.be.revertedWith('!router');
});
});

@ -0,0 +1,96 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import {
Outbox__factory,
XAppConnectionManager,
XAppConnectionManager__factory,
} from '@abacus-network/core';
import {
TestXAppConnectionClient,
TestXAppConnectionClient__factory,
} from '../types';
const ONLY_OWNER_REVERT_MSG = 'Ownable: caller is not the owner';
describe('XAppConnectionClient', async () => {
let connectionClient: TestXAppConnectionClient,
connectionManager: XAppConnectionManager,
signer: SignerWithAddress,
nonOwner: SignerWithAddress;
before(async () => {
[signer, nonOwner] = await ethers.getSigners();
});
beforeEach(async () => {
const connectionManagerFactory = new XAppConnectionManager__factory(signer);
connectionManager = await connectionManagerFactory.deploy();
const connectionClientFactory = new TestXAppConnectionClient__factory(
signer,
);
connectionClient = await connectionClientFactory.deploy();
await connectionClient.initialize(connectionManager.address);
});
it('Cannot be initialized twice', async () => {
await expect(
connectionClient.initialize(ethers.constants.AddressZero),
).to.be.revertedWith('Initializable: contract is already initialized');
});
it('owner can set connection manager', async () => {
const newConnectionManager = signer.address;
expect(await connectionClient.xAppConnectionManager()).to.not.equal(
newConnectionManager,
);
await connectionClient.setXAppConnectionManager(newConnectionManager);
expect(await connectionClient.xAppConnectionManager()).to.equal(
newConnectionManager,
);
});
it('non-owner cannot set connection manager', async () => {
await expect(
connectionClient
.connect(nonOwner)
.setXAppConnectionManager(signer.address),
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG);
});
it('returns outbox from connection manager', async () => {
const outbox = nonOwner.address;
expect(await connectionClient.outbox()).to.equal(
ethers.constants.AddressZero,
);
await connectionManager.setOutbox(outbox);
expect(await connectionClient.outbox()).to.equal(outbox);
});
it('returns paymaster from connection manager', async () => {
const paymaster = nonOwner.address;
expect(await connectionClient.interchainGasPaymaster()).to.equal(
ethers.constants.AddressZero,
);
await connectionManager.setInterchainGasPaymaster(paymaster);
expect(await connectionClient.interchainGasPaymaster()).to.equal(paymaster);
});
it('returns inbox from connection manager', async () => {
const inbox = nonOwner.address;
const domain = 1;
expect(await connectionClient.isInbox(inbox)).to.equal(false);
await connectionManager.enrollInbox(domain, inbox);
expect(await connectionClient.isInbox(inbox)).to.equal(true);
});
it('returns local domain from outbox', async () => {
const localDomain = 3;
const outboxFactory = new Outbox__factory(signer);
const outbox = await outboxFactory.deploy(localDomain);
await connectionManager.setOutbox(outbox.address);
expect(await connectionClient.localDomain()).to.equal(localDomain);
});
});

@ -0,0 +1,16 @@
{
"compilerOptions": {
"outDir": "./dist/",
"rootDir": "./types/"
},
"exclude": [
"./node_modules/",
"./dist/",
"./types/hardhat.d.ts"
],
"extends": "../../tsconfig.package.json",
"include": [
"./types/*.ts",
"./types/factories/*.ts"
]
}

@ -5,8 +5,8 @@ pragma experimental ABIEncoderV2;
// ============ Internal Imports ============
import {GovernanceMessage} from "./GovernanceMessage.sol";
// ============ External Imports ============
import {Router} from "@abacus-network/app/contracts/Router.sol";
import {Version0} from "@abacus-network/core/contracts/Version0.sol";
import {Router} from "@abacus-network/core/contracts/router/Router.sol";
import {TypeCasts} from "@abacus-network/core/libs/TypeCasts.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/Initializable.sol";
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
@ -103,7 +103,7 @@ contract GovernanceRouter is Version0, Router {
// ============ Initializer ============
function initialize(address _xAppConnectionManager) public initializer {
__XAppConnectionClient_initialize(_xAppConnectionManager);
__Router_initialize(_xAppConnectionManager);
governor = msg.sender;
}
@ -115,15 +115,13 @@ contract GovernanceRouter is Version0, Router {
* sent from the Governor chain via Abacus.
* Governor chain should never receive messages,
* because non-Governor chains are not able to send them
* @param _origin The domain (of the Governor Router)
* @param _sender The message sender (must be the Governor Router)
* @param _message The message
*/
function handle(
uint32 _origin,
bytes32 _sender,
function _handle(
uint32,
bytes32,
bytes memory _message
) external override onlyInbox onlyRemoteRouter(_origin, _sender) {
) internal override {
bytes29 _msg = _message.ref(0);
if (_msg.isValidCall()) {
_handleCall(_msg.tryAsCall());

@ -3,8 +3,7 @@ pragma solidity >=0.6.11;
// ============ External Imports ============
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {Router} from "@abacus-network/core/contracts/router/Router.sol";
import {XAppConnectionClient} from "@abacus-network/core/contracts/router/XAppConnectionClient.sol";
import {Router} from "@abacus-network/app/contracts/Router.sol";
// ============ Internal Imports ============
import {PingPongMessage} from "./PingPongMessage.sol";
@ -57,14 +56,13 @@ contract PingPongRouter is Router {
/**
* @notice Handle "volleys" sent via Abacus from other remote PingPong Routers
* @param _origin The domain the message is coming from
* @param _sender The address the message is coming from
* @param _message The message in the form of raw bytes
*/
function handle(
function _handle(
uint32 _origin,
bytes32 _sender,
bytes32,
bytes memory _message
) external override onlyInbox onlyRemoteRouter(_origin, _sender) {
) internal override {
bytes29 _msg = _message.ref(0);
if (_msg.isPing()) {
_handlePing(_origin, _msg);

@ -3,8 +3,7 @@ pragma solidity >=0.6.11;
// ============ External Imports ============
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {Router} from "@abacus-network/core/contracts/router/Router.sol";
import {XAppConnectionClient} from "@abacus-network/core/contracts/router/XAppConnectionClient.sol";
import {Router} from "@abacus-network/app/contracts/Router.sol";
// ============ Internal Imports ============
import {Message} from "./MessageTemplate.sol";
@ -43,15 +42,13 @@ contract RouterTemplate is Router {
* @notice Receive messages sent via Abacus from other remote xApp Routers;
* parse the contents of the message and enact the message's effects on the local chain
* @dev Called by an Abacus Inbox contract while processing a message sent via Abacus
* @param _origin The domain the message is coming from
* @param _sender The address the message is coming from
* @param _message The message in the form of raw bytes
*/
function handle(
uint32 _origin,
bytes32 _sender,
function _handle(
uint32,
bytes32,
bytes memory _message
) external override onlyInbox onlyRemoteRouter(_origin, _sender) {
) internal override {
bytes29 _msg = _message.ref(0);
// route message to appropriate _handle function
// based on what type of message is encoded

@ -21,7 +21,7 @@
"typechain": "^5.0.0",
"typescript": "^4.3.5"
},
"version": "0.0.1",
"version": "0.0.2",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"directories": {
@ -36,9 +36,10 @@
},
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/hardhat": "^0.0.8",
"@abacus-network/utils": "^0.0.7",
"@abacus-network/app": "^0.0.3",
"@abacus-network/core": "^0.0.6",
"@abacus-network/hardhat": "^0.0.18",
"@abacus-network/utils": "^0.0.8",
"@openzeppelin/contracts": "~3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0"

@ -1,16 +1,11 @@
import { ethers, abacus } from 'hardhat';
import { expect } from 'chai';
import { InterchainGasPaymaster, Outbox } from '@abacus-network/core';
import { utils } from '@abacus-network/utils';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import {
formatSetGovernor,
formatCall,
increaseTimestampBy,
} from './lib/utils';
import { expect } from 'chai';
import { abacus, ethers } from 'hardhat';
import { GovernanceRouter, TestSet, TestSet__factory } from '../../types';
import { GovernanceConfig, GovernanceDeploy } from './lib/GovernanceDeploy';
import { TestSet, TestSet__factory, GovernanceRouter } from '../../types';
import { formatCall, increaseTimestampBy } from './lib/utils';
const recoveryTimelock = 60 * 60 * 24 * 7;
const localDomain = 1000;
@ -57,54 +52,6 @@ describe('GovernanceRouter', async () => {
).to.be.revertedWith('Initializable: contract is already initialized');
});
it('accepts message from enrolled inbox and router', async () => {
expect(await router.governor()).to.not.equal(ethers.constants.AddressZero);
const message = formatSetGovernor(ethers.constants.AddressZero);
// Create a fake abacus message coming from the remote governance router.
const fakeMessage = utils.formatMessage(
remoteDomain,
remote.address,
0, // nonce is ignored
localDomain,
router.address,
message,
);
const inbox = abacus.inbox(localDomain, remoteDomain);
await inbox.setMessageProven(fakeMessage);
await inbox.testProcess(fakeMessage);
expect(await router.governor()).to.equal(ethers.constants.AddressZero);
});
it('rejects message from unenrolled inbox', async () => {
const message = formatSetGovernor(ethers.constants.AddressZero);
await expect(
router.handle(
remoteDomain,
utils.addressToBytes32(remote.address),
message,
),
).to.be.revertedWith('!inbox');
});
it('rejects message from unenrolled router', async () => {
const message = formatSetGovernor(ethers.constants.AddressZero);
// Create a fake abacus message coming from the remote governance router.
const fakeMessage = utils.formatMessage(
remoteDomain,
ethers.constants.AddressZero,
0, // nonce is ignored
localDomain,
router.address,
message,
);
const inbox = abacus.inbox(localDomain, remoteDomain);
await inbox.setMessageProven(fakeMessage);
// Expect inbox processing to fail when reverting in handle
await expect(inbox.testProcess(fakeMessage)).to.be.revertedWith('!router');
});
describe('when not in recovery mode', async () => {
it('governor is the owner', async () => {
expect(await router.owner()).to.equal(governor.address);
@ -119,7 +66,7 @@ describe('GovernanceRouter', async () => {
it('governor can make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await router.call([call]);
expect(await testSet.get()).to.equal(value);
});
@ -151,7 +98,7 @@ describe('GovernanceRouter', async () => {
it('governor can make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await router.callRemote(domains[1], [call]);
await abacus.processMessages();
expect(await testSet.get()).to.equal(value);
@ -213,7 +160,7 @@ describe('GovernanceRouter', async () => {
it('recovery manager cannot make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await expect(
router.connect(recoveryManager).call([call]),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
@ -248,7 +195,7 @@ describe('GovernanceRouter', async () => {
it('recovery manager cannot make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await expect(
router.connect(recoveryManager).callRemote(domains[1], [call]),
).to.be.revertedWith('!governor');
@ -318,7 +265,7 @@ describe('GovernanceRouter', async () => {
it('recovery manager can make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await router.call([call]);
expect(await testSet.get()).to.equal(value);
});
@ -350,7 +297,7 @@ describe('GovernanceRouter', async () => {
it('recovery manager cannot make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await expect(router.callRemote(domains[1], [call])).to.be.revertedWith(
'!governor',
);
@ -391,7 +338,7 @@ describe('GovernanceRouter', async () => {
it('governor cannot make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await expect(router.connect(governor).call([call])).to.be.revertedWith(
ONLY_OWNER_REVERT_MESSAGE,
);
@ -428,7 +375,7 @@ describe('GovernanceRouter', async () => {
it('governor cannot make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
const call = formatCall(testSet, 'set', [value]);
await expect(
router.connect(governor).callRemote(domains[1], [call]),
).to.be.revertedWith('recovery');
@ -488,7 +435,7 @@ describe('GovernanceRouter', async () => {
it('allows interchain gas payment for remote calls', async () => {
const leafIndex = await outbox.count();
const call = await formatCall(testSet, 'set', [13]);
const call = formatCall(testSet, 'set', [13]);
await expect(
await router.callRemote(domains[1], [call], {
value: testInterchainGasPayment,

@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { types, utils } from '@abacus-network/utils';
import { ethers } from 'ethers';
export enum GovernanceMessage {
CALL = 1,
@ -66,21 +66,23 @@ export function formatCalls(callsData: types.CallData[]): string {
);
}
export async function formatCall(
destinationContract: ethers.Contract,
functionStr: string,
functionArgs: any[],
): Promise<types.CallData> {
export function formatCall<
C extends ethers.Contract,
I extends Parameters<C['interface']['encodeFunctionData']>,
>(
destinationContract: C,
functionName: I[0],
functionArgs: I[1],
): types.CallData {
// Set up data for call message
const callFunc = destinationContract.interface.getFunction(functionStr);
const callDataEncoded = destinationContract.interface.encodeFunctionData(
callFunc,
functionArgs,
const callData = utils.formatCallData(
destinationContract,
functionName as any,
functionArgs as any,
);
return {
to: utils.addressToBytes32(destinationContract.address),
data: callDataEncoded,
data: callData,
};
}

@ -4,6 +4,7 @@ pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {IOutbox} from "../interfaces/IOutbox.sol";
import {IInterchainGasPaymaster} from "../interfaces/IInterchainGasPaymaster.sol";
import {IXAppConnectionManager} from "../interfaces/IXAppConnectionManager.sol";
// ============ External Imports ============
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
@ -13,16 +14,16 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
* @notice Manages a registry of local Inbox contracts for remote Outbox
* domains.
*/
contract XAppConnectionManager is Ownable {
contract XAppConnectionManager is IXAppConnectionManager, Ownable {
// ============ Public Storage ============
// Outbox contract
IOutbox public outbox;
IOutbox public override outbox;
// Interchain Gas Paymaster contract. The off-chain processor associated with
// the paymaster contract must be willing to process messages dispatched from
// the current Outbox contract, otherwise payments made to the paymaster will
// not result in processed messages.
IInterchainGasPaymaster public interchainGasPaymaster;
IInterchainGasPaymaster public override interchainGasPaymaster;
// local Inbox address => remote Outbox domain
mapping(address => uint32) public inboxToDomain;
// remote Outbox domain => local Inbox address
@ -104,7 +105,7 @@ contract XAppConnectionManager is Ownable {
* @notice Query local domain from Outbox
* @return local domain
*/
function localDomain() external view returns (uint32) {
function localDomain() external view override returns (uint32) {
return outbox.localDomain();
}
@ -142,7 +143,7 @@ contract XAppConnectionManager is Ownable {
* @param _inbox the inbox to check for enrollment
* @return TRUE iff _inbox is enrolled
*/
function isInbox(address _inbox) public view returns (bool) {
function isInbox(address _inbox) public view override returns (bool) {
return inboxToDomain[_inbox] != 0;
}

@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {IInterchainGasPaymaster} from "./IInterchainGasPaymaster.sol";
import {IOutbox} from "./IOutbox.sol";
interface IXAppConnectionManager {
function outbox() external view returns (IOutbox);
function interchainGasPaymaster() external view returns (IInterchainGasPaymaster);
function isInbox(address _inbox) external view returns (bool);
function localDomain() external view returns (uint32);
}

@ -19,7 +19,7 @@
"ts-node": "^10.1.0",
"typechain": "^5.0.0"
},
"version": "0.0.3",
"version": "0.0.6",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"directories": {
@ -34,7 +34,7 @@
},
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/utils": "^0.0.7",
"@abacus-network/utils": "^0.0.8",
"@openzeppelin/contracts": "^3.4.2",
"@openzeppelin/contracts-upgradeable": "~3.4.2",
"@summa-tx/memview-sol": "^2.0.0",

@ -1,8 +1,7 @@
import { ethers } from 'hardhat';
import { BadRandomRecipient__factory } from '../types';
import { utils } from '@abacus-network/utils';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { BadRandomRecipient__factory } from '../types';
describe('BadRecipient', () => {
describe('RandomBadRecipient', () => {
@ -15,7 +14,7 @@ describe('BadRecipient', () => {
// Didn't know how else to test the randomness
let successes = 0;
let failures = 0;
for (let i = 0; i < 10; i++) {
for (let i = 0; i < 100; i++) {
try {
// "Inject randomness"
await signer.sendTransaction({
@ -34,8 +33,8 @@ describe('BadRecipient', () => {
}
}
expect(successes).to.be.greaterThan(1);
expect(failures).to.be.greaterThan(1);
expect(successes).to.be.greaterThan(5);
expect(failures).to.be.greaterThan(5);
});
});
});

@ -1,6 +1,6 @@
{
"name": "@abacus-network/contract-metrics",
"version": "0.0.0",
"version": "0.0.1",
"scripts": {
"monitor": "ts-node src/monitor.ts",
"monitor-once": "ts-node src/monitor.ts once | ./node_modules/.bin/bunyan",

@ -8,7 +8,7 @@
},
"prepublish": "npm run build",
"name": "@abacus-network/deploy",
"version": "0.0.6",
"version": "0.0.7",
"description": "Abacus deploy tools",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -19,8 +19,8 @@
},
"license": "MIT OR Apache-2.0",
"dependencies": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/sdk": "^0.0.3",
"@abacus-network/core": "^0.0.6",
"@abacus-network/sdk": "^0.0.4",
"@types/node": "^16.9.1",
"axios": "^0.21.3"
}

@ -1 +1,2 @@
dist/
cache/

@ -0,0 +1,29 @@
import "@typechain/hardhat";
import "@nomiclabs/hardhat-ethers";
import "@nomiclabs/hardhat-waffle";
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
version: "0.7.6",
settings: {
optimizer: {
enabled: true,
runs: 999999,
},
},
},
gasReporter: {
currency: "USD",
},
typechain: {
outDir: "./types",
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,
},
};

@ -1,18 +1,22 @@
{
"prepublish": "npm run build",
"name": "@abacus-network/hardhat",
"version": "0.0.8",
"version": "0.0.18",
"description": "Abacus hardhat tools",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"check": "tsc --noEmit",
"prettier": "prettier --write ./src"
"prettier": "prettier --write ./src ./test",
"test": "hardhat test"
},
"license": "MIT OR Apache-2.0",
"directories": {
"test": "test"
},
"dependencies": {
"@abacus-network/core": "^0.0.3",
"@abacus-network/core": "^0.0.5",
"@abacus-network/utils": "^0.0.7",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.2",

@ -1,19 +1,19 @@
import { ethers } from "ethers";
import { types, Validator } from "@abacus-network/utils";
import {
Outbox,
Outbox__factory,
InterchainGasPaymaster,
InterchainGasPaymaster__factory,
ValidatorManager,
ValidatorManager__factory,
Outbox,
Outbox__factory,
TestInbox,
TestInbox__factory,
UpgradeBeaconController,
UpgradeBeaconController__factory,
ValidatorManager,
ValidatorManager__factory,
XAppConnectionManager,
XAppConnectionManager__factory,
TestInbox,
TestInbox__factory,
} from "@abacus-network/core";
import { types, Validator } from "@abacus-network/utils";
import { ethers } from "ethers";
import { TestDeploy } from "./TestDeploy";
export type TestAbacusConfig = {
@ -53,8 +53,10 @@ export class TestAbacusDeploy extends TestDeploy<
const validatorManagerFactory = new ValidatorManager__factory(signer);
const validatorManager = await validatorManagerFactory.deploy();
await validatorManager.enrollValidator(domain, signerAddress);
// this.remotes reads this.instances which has not yet been set.
const remotes = Object.keys(this.config.signer).map((d) => parseInt(d));
await Promise.all(
this.remotes(domain).map(async (remote) =>
remotes.map(async (remote) =>
validatorManager.enrollValidator(remote, signerAddress)
)
);
@ -85,8 +87,6 @@ export class TestAbacusDeploy extends TestDeploy<
const inboxFactory = new TestInbox__factory(signer);
const inboxes: Record<types.Domain, TestInbox> = {};
// this.remotes reads this.instances which has not yet been set.
const remotes = Object.keys(this.config.signer).map((d) => parseInt(d));
const deploys = remotes.map(async (remote) => {
const inbox = await inboxFactory.deploy(domain);
await inbox.initialize(
@ -114,8 +114,8 @@ export class TestAbacusDeploy extends TestDeploy<
await this.upgradeBeaconController(domain).transferOwnership(address);
await this.xAppConnectionManager(domain).transferOwnership(address);
await this.validatorManager(domain).transferOwnership(address);
for (const remote of this.remotes(domain)) {
await this.inbox(domain, remote).transferOwnership(address);
for (const origin of this.remotes(domain)) {
await this.inbox(origin, domain).transferOwnership(address);
}
}
@ -127,8 +127,8 @@ export class TestAbacusDeploy extends TestDeploy<
return this.instances[domain].upgradeBeaconController;
}
inbox(local: types.Domain, remote: types.Domain): TestInbox {
return this.instances[local].inboxes[remote];
inbox(origin: types.Domain, destination: types.Domain): TestInbox {
return this.instances[destination].inboxes[origin];
}
interchainGasPaymaster(domain: types.Domain): InterchainGasPaymaster {
@ -143,62 +143,71 @@ export class TestAbacusDeploy extends TestDeploy<
return this.instances[domain].validatorManager;
}
async processMessages() {
await Promise.all(
this.domains.map((d) => this.processMessagesFromDomain(d))
);
async processMessages(): Promise<
Map<types.Domain, Map<types.Domain, ethers.providers.TransactionResponse[]>>
> {
const responses: Map<
types.Domain,
Map<types.Domain, ethers.providers.TransactionResponse[]>
> = new Map();
for (const origin of this.domains) {
const outbound = await this.processOutboundMessages(origin);
responses.set(origin, new Map());
this.domains.forEach((destination) => {
responses
.get(origin)!
.set(destination, outbound.get(destination) ?? []);
});
}
return responses;
}
async processMessagesFromDomain(domain: types.Domain) {
const outbox = this.outbox(domain);
const [checkpointedRoot, checkpointedIndex] =
await outbox.latestCheckpoint();
const latestIndex = await outbox.tree();
if (latestIndex.eq(checkpointedIndex)) return;
// Find the block number of the last checkpoint submitted on Outbox.
const checkpointFilter = outbox.filters.Checkpoint(checkpointedRoot);
const checkpoints = await outbox.queryFilter(checkpointFilter);
if (!(checkpoints.length === 0 || checkpoints.length === 1))
throw new Error("found multiple checkpoints");
const fromBlock = checkpoints.length === 0 ? 0 : checkpoints[0].blockNumber;
async processOutboundMessages(
origin: types.Domain
): Promise<Map<types.Domain, ethers.providers.TransactionResponse[]>> {
const responses: Map<types.Domain, ethers.providers.TransactionResponse[]> =
new Map();
const outbox = this.outbox(origin);
const [, checkpointedIndex] = await outbox.latestCheckpoint();
const latestIndex = await outbox.count();
if (latestIndex.eq(checkpointedIndex)) return responses;
await outbox.checkpoint();
const [root, index] = await outbox.latestCheckpoint();
// If there have been no checkpoints since the last checkpoint, return.
if (
index.eq(0) ||
(checkpoints.length == 1 && index.eq(checkpoints[0].args.index))
) {
return;
}
// Update the Outbox and Inboxes to the latest roots.
// Sign the checkpoint and update the Inboxes to the latest root.
// This is technically not necessary given that we are not proving against
// a root in the TestInbox.
const validator = await Validator.fromSigner(
this.config.signer[domain],
domain
this.config.signer[origin],
origin
);
const { signature } = await validator.signCheckpoint(
root,
index.toNumber()
);
for (const remote of this.remotes(domain)) {
const inbox = this.inbox(remote, domain);
for (const destination of this.remotes(origin)) {
const inbox = this.inbox(origin, destination);
await inbox.checkpoint(root, index, signature);
}
// Find all messages dispatched on the outbox since the previous checkpoint.
// Find all unprocessed messages dispatched on the outbox since the previous checkpoint.
const dispatchFilter = outbox.filters.Dispatch();
const dispatches = await outbox.queryFilter(dispatchFilter, fromBlock);
const dispatches = await outbox.queryFilter(dispatchFilter);
for (const dispatch of dispatches) {
const destination = dispatch.args.destinationAndNonce.shr(32).toNumber();
if (destination !== domain) {
const inbox = this.inbox(destination, domain);
if (destination === origin)
throw new Error("Dispatched message to local domain");
const inbox = this.inbox(origin, destination);
const status = await inbox.messages(dispatch.args.messageHash);
if (status !== types.MessageStatus.PROCESSED) {
await inbox.setMessageProven(dispatch.args.message);
await inbox.testProcess(dispatch.args.message);
const response = await inbox.testProcess(dispatch.args.message);
let destinationResponses = responses.get(destination) || [];
destinationResponses.push(response);
responses.set(destination, destinationResponses);
}
}
return responses;
}
}

@ -0,0 +1,72 @@
import { ethers } from "hardhat";
import { expect } from "chai";
import { TestRecipient__factory } from "@abacus-network/core";
import { utils } from "@abacus-network/utils";
import { TestAbacusDeploy } from "..";
const localDomain = 1000;
const remoteDomain = 2000;
const domains = [localDomain, remoteDomain];
const message = "0xdeadbeef";
describe("TestAbacusDeploy", async () => {
let abacus: TestAbacusDeploy;
beforeEach(async () => {
abacus = new TestAbacusDeploy({ signer: {} });
const [signer] = await ethers.getSigners();
await abacus.deploy(domains, signer);
const recipient = await new TestRecipient__factory(signer).deploy();
const localOutbox = abacus.outbox(localDomain);
await expect(
localOutbox.dispatch(
remoteDomain,
utils.addressToBytes32(recipient.address),
message
)
).to.emit(localOutbox, "Dispatch");
const remoteOutbox = abacus.outbox(remoteDomain);
await expect(
remoteOutbox.dispatch(
localDomain,
utils.addressToBytes32(recipient.address),
message
)
).to.emit(remoteOutbox, "Dispatch");
});
it("processes outbound messages for a single domain", async () => {
const responses = await abacus.processOutboundMessages(localDomain);
expect(responses.get(remoteDomain)!.length).to.equal(1);
const [_, index] = await abacus.outbox(localDomain).latestCheckpoint();
expect(index).to.equal(1);
});
it("processes outbound messages for two domains", async () => {
const localResponses = await abacus.processOutboundMessages(localDomain);
expect(localResponses.get(remoteDomain)!.length).to.equal(1);
const [, localIndex] = await abacus.outbox(localDomain).latestCheckpoint();
expect(localIndex).to.equal(1);
const remoteResponses = await abacus.processOutboundMessages(remoteDomain);
expect(remoteResponses.get(localDomain)!.length).to.equal(1);
const [, remoteIndex] = await abacus
.outbox(remoteDomain)
.latestCheckpoint();
expect(remoteIndex).to.equal(1);
});
it("processes all messages", async () => {
const responses = await abacus.processMessages();
expect(responses.get(localDomain)!.get(remoteDomain)!.length).to.equal(1);
expect(responses.get(remoteDomain)!.get(localDomain)!.length).to.equal(1);
const [, localIndex] = await abacus.outbox(localDomain).latestCheckpoint();
expect(localIndex).to.equal(1);
const [, remoteIndex] = await abacus
.outbox(remoteDomain)
.latestCheckpoint();
expect(remoteIndex).to.equal(1);
});
});

@ -39,5 +39,6 @@
"chai": "^4.3.4",
"dotenv": "^10.0.0",
"yargs": "^17.3.1"
}
},
"version": "0.0.1"
}

@ -1,6 +1,6 @@
{
"name": "@abacus-network/sdk",
"version": "0.0.3",
"version": "0.0.4",
"description": "Abacus Network SDK",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@ -26,9 +26,9 @@
"typescript": "^4.4.3"
},
"dependencies": {
"@abacus-network/apps": "^0.0.1",
"@abacus-network/core": "^0.0.3",
"@abacus-network/utils": "^0.0.7",
"@abacus-network/apps": "^0.0.2",
"@abacus-network/core": "^0.0.6",
"@abacus-network/utils": "^0.0.8",
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/bytes": "^5.5.0",
"celo-ethers-provider": "0.0.0",

@ -1,7 +1,7 @@
{
"prepublish": "npm run build",
"name": "@abacus-network/utils",
"version": "0.0.7",
"version": "0.0.8",
"description": "Abacus utilities",
"main": "dist/index.js",
"types": "dist/index.d.ts",

@ -1,6 +1,6 @@
import { assert } from 'chai';
import { ethers } from 'ethers';
import { Domain, Address, HexString } from './types';
import { Address, Domain, HexString } from './types';
/*
* Gets the byte length of a hex string
@ -38,6 +38,16 @@ export function bytes32ToAddress(bytes32: string): Address {
return ethers.utils.getAddress(bytes32.slice(-40));
}
export function formatCallData<
C extends ethers.Contract,
I extends Parameters<C['interface']['encodeFunctionData']>,
>(destinationContract: C, functionName: I[0], functionArgs: I[1]): string {
return destinationContract.interface.encodeFunctionData(
functionName,
functionArgs,
);
}
export const formatMessage = (
localDomain: Domain,
senderAddr: Address,

Loading…
Cancel
Save