Reimplement GovernanceRouter as an AbacusApp (#178)

asaj/agent-dev-env
Asa Oines 3 years ago committed by GitHub
parent 6e7fbd76fe
commit 3796248374
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 557
      solidity/abacus-core/contracts/governance/GovernanceRouter.sol
  2. 31
      solidity/abacus-core/contracts/test/TestGovernanceRouter.sol
  3. 1
      solidity/abacus-core/hardhat.config.ts
  4. 390
      solidity/abacus-core/test/cross-chain/governanceRouter.test.ts
  5. 138
      solidity/abacus-core/test/cross-chain/simpleMessage.test.ts
  6. 164
      solidity/abacus-core/test/cross-chain/utils.ts
  7. 51
      solidity/abacus-core/test/inbox.test.ts
  8. 4
      solidity/abacus-core/test/index.ts
  9. 8
      solidity/abacus-core/test/lib/AbacusDeployment.ts
  10. 83
      solidity/abacus-core/test/lib/core.ts
  11. 9
      solidity/abacus-core/test/lib/index.ts
  12. 23
      solidity/abacus-core/test/lib/types.ts
  13. 12
      solidity/abacus-core/test/lib/upgrade.ts
  14. 11
      solidity/abacus-core/test/lib/utils.ts
  15. 16
      solidity/abacus-core/test/message.test.ts
  16. 48
      solidity/abacus-core/test/outbox.test.ts
  17. 2
      solidity/abacus-core/test/upgrade.test.ts
  18. 5
      solidity/abacus-core/test/validatorManager.test.ts
  19. 10
      solidity/abacus-core/test/xAppConnectionManager.test.ts
  20. 54
      solidity/abacus-xapps/contracts/Router.sol
  21. 22
      solidity/abacus-xapps/contracts/XAppConnectionClient.sol
  22. 27
      solidity/abacus-xapps/contracts/bridge/BridgeRouter.sol
  23. 162
      solidity/abacus-xapps/contracts/governance/GovernanceMessage.sol
  24. 385
      solidity/abacus-xapps/contracts/governance/GovernanceRouter.sol
  25. 14
      solidity/abacus-xapps/contracts/governance/test/TestSet.sol
  26. 4
      solidity/abacus-xapps/contracts/ping-pong/PingPongRouter.sol
  27. 4
      solidity/abacus-xapps/contracts/xapp-template/RouterTemplate.sol
  28. 1
      solidity/abacus-xapps/hardhat.config.ts
  29. 0
      solidity/abacus-xapps/recoveryManager.test.ts
  30. 2
      solidity/abacus-xapps/test/bridge/BridgeToken.test.ts
  31. 27
      solidity/abacus-xapps/test/bridge/EthHelper.test.ts
  32. 135
      solidity/abacus-xapps/test/bridge/bridge.test.ts
  33. 32
      solidity/abacus-xapps/test/bridge/bridgeMessage.test.ts
  34. 2
      solidity/abacus-xapps/test/bridge/encoding.test.ts
  35. 2
      solidity/abacus-xapps/test/bridge/lib/BridgeDeployment.ts
  36. 2
      solidity/abacus-xapps/test/bridge/lib/permit.ts
  37. 49
      solidity/abacus-xapps/test/bridge/lib/types.ts
  38. 50
      solidity/abacus-xapps/test/bridge/lib/utils.ts
  39. 482
      solidity/abacus-xapps/test/governance/governanceRouter.test.ts
  40. 56
      solidity/abacus-xapps/test/governance/lib/GovernanceDeployment.ts
  41. 93
      solidity/abacus-xapps/test/governance/lib/utils.ts
  42. 12
      solidity/abacus-xapps/test/lib/index.ts
  43. 61
      solidity/abacus-xapps/test/lib/types.ts
  44. 11
      typescript/abacus-deploy/hardhat.config.ts
  45. 4
      typescript/abacus-deploy/scripts/update-abacus-sdk.ts
  46. 0
      typescript/abacus-deploy/scripts/upgrade-inbox.ts
  47. 4
      typescript/abacus-deploy/scripts/utils.ts
  48. 2
      typescript/abacus-deploy/src/bridge/checks.ts
  49. 2
      typescript/abacus-deploy/src/config/addresses.ts
  50. 2
      typescript/abacus-deploy/src/config/agent.ts
  51. 74
      typescript/abacus-deploy/src/core/CoreContracts.ts
  52. 6
      typescript/abacus-deploy/src/core/CoreDeploy.ts
  53. 44
      typescript/abacus-deploy/src/core/checks.ts
  54. 6
      typescript/abacus-deploy/src/core/implementation.ts
  55. 95
      typescript/abacus-deploy/src/core/index.ts
  56. 8
      typescript/abacus-deploy/src/sdk.ts
  57. 39
      typescript/abacus-sdk/src/abacus/AbacusContext.ts
  58. 41
      typescript/abacus-sdk/src/abacus/contracts/CoreContracts.ts
  59. 10
      typescript/abacus-sdk/src/abacus/domains/dev.ts
  60. 2
      typescript/abacus-sdk/src/abacus/domains/domain.ts
  61. 8
      typescript/abacus-sdk/src/abacus/domains/mainnet.ts
  62. 6
      typescript/abacus-sdk/src/abacus/domains/mainnetLegacy.ts
  63. 8
      typescript/abacus-sdk/src/abacus/domains/testnet.ts
  64. 6
      typescript/abacus-sdk/src/abacus/domains/testnetLegacy.ts
  65. 7
      typescript/abacus-sdk/src/abacus/govern/index.ts
  66. 3
      typescript/contract-metrics/.prettierignore
  67. 4
      typescript/contract-metrics/src/monitor.ts
  68. 24
      typescript/contract-metrics/src/monitor/governance.ts

@ -1,557 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
pragma experimental ABIEncoderV2;
// ============ Internal Imports ============
import {Outbox} from "../Outbox.sol";
import {Version0} from "../Version0.sol";
import {XAppConnectionManager, TypeCasts} from "../XAppConnectionManager.sol";
import {IMessageRecipient} from "../../interfaces/IMessageRecipient.sol";
import {GovernanceMessage} from "./GovernanceMessage.sol";
// ============ External Imports ============
import {Initializable} from "@openzeppelin/contracts/proxy/Initializable.sol";
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
contract GovernanceRouter is Version0, Initializable, IMessageRecipient {
// ============ Libraries ============
using SafeMath for uint256;
using TypedMemView for bytes;
using TypedMemView for bytes29;
using GovernanceMessage for bytes29;
// ============ Immutables ============
uint32 public immutable localDomain;
// number of seconds before recovery can be activated
uint256 public immutable recoveryTimelock;
// ============ Public Storage ============
// timestamp when recovery timelock expires; 0 if timelock has not been initiated
uint256 public recoveryActiveAt;
// the address of the recovery manager multisig
address public recoveryManager;
// the local entity empowered to call governance functions, set to 0x0 on non-Governor chains
address public governor;
// domain of Governor chain -- for accepting incoming messages from Governor
uint32 public governorDomain;
// xAppConnectionManager contract which stores Inbox addresses
XAppConnectionManager public xAppConnectionManager;
// domain -> remote GovernanceRouter contract address
mapping(uint32 => bytes32) public routers;
// array of all domains with registered GovernanceRouter
uint32[] public domains;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[43] private __GAP;
// ============ Events ============
/**
* @notice Emitted a remote GovernanceRouter address is added, removed, or changed
* @param domain the domain of the remote Router
* @param previousRouter the previously registered router; 0 if router is being added
* @param newRouter the new registered router; 0 if router is being removed
*/
event SetRouter(
uint32 indexed domain,
bytes32 previousRouter,
bytes32 newRouter
);
/**
* @notice Emitted when the Governor role is transferred
* @param previousGovernorDomain the domain of the previous Governor
* @param newGovernorDomain the domain of the new Governor
* @param previousGovernor the address of the previous Governor; 0 if the governor was remote
* @param newGovernor the address of the new Governor; 0 if the governor is remote
*/
event TransferGovernor(
uint32 previousGovernorDomain,
uint32 newGovernorDomain,
address indexed previousGovernor,
address indexed newGovernor
);
/**
* @notice Emitted when the RecoveryManager role is transferred
* @param previousRecoveryManager the address of the previous RecoveryManager
* @param newRecoveryManager the address of the new RecoveryManager
*/
event TransferRecoveryManager(
address indexed previousRecoveryManager,
address indexed newRecoveryManager
);
/**
* @notice Emitted when recovery state is initiated by the RecoveryManager
* @param recoveryManager the address of the current RecoveryManager who initiated the transition
* @param recoveryActiveAt the block at which recovery state will be active
*/
event InitiateRecovery(
address indexed recoveryManager,
uint256 recoveryActiveAt
);
/**
* @notice Emitted when recovery state is exited by the RecoveryManager
* @param recoveryManager the address of the current RecoveryManager who initiated the transition
*/
event ExitRecovery(address recoveryManager);
modifier typeAssert(bytes29 _view, GovernanceMessage.Types _type) {
_view.assertType(uint40(_type));
_;
}
// ============ Modifiers ============
modifier onlyInbox() {
require(xAppConnectionManager.isInbox(msg.sender), "!inbox");
_;
}
modifier onlyGovernorRouter(uint32 _domain, bytes32 _address) {
require(_isGovernorRouter(_domain, _address), "!governorRouter");
_;
}
modifier onlyGovernor() {
require(msg.sender == governor, "! called by governor");
_;
}
modifier onlyRecoveryManager() {
require(msg.sender == recoveryManager, "! called by recovery manager");
_;
}
modifier onlyInRecovery() {
require(inRecovery(), "! in recovery");
_;
}
modifier onlyNotInRecovery() {
require(!inRecovery(), "in recovery");
_;
}
modifier onlyGovernorOrRecoveryManager() {
if (!inRecovery()) {
require(msg.sender == governor, "! called by governor");
} else {
require(
msg.sender == recoveryManager,
"! called by recovery manager"
);
}
_;
}
// ============ Constructor ============
constructor(uint32 _localDomain, uint256 _recoveryTimelock) {
localDomain = _localDomain;
recoveryTimelock = _recoveryTimelock;
}
// ============ Initializer ============
function initialize(
address _xAppConnectionManager,
address _recoveryManager
) public initializer {
// initialize governor
address _governorAddr = msg.sender;
bool _isLocalGovernor = true;
_transferGovernor(localDomain, _governorAddr, _isLocalGovernor);
// initialize recovery manager
recoveryManager = _recoveryManager;
// initialize XAppConnectionManager
setXAppConnectionManager(_xAppConnectionManager);
require(
xAppConnectionManager.localDomain() == localDomain,
"XAppConnectionManager bad domain"
);
}
// ============ External Functions ============
/**
* @notice Handle Abacus messages
* For all non-Governor chains to handle messages
* 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,
bytes memory _message
) external override onlyInbox onlyGovernorRouter(_origin, _sender) {
bytes29 _msg = _message.ref(0);
if (_msg.isValidCall()) {
_handleCall(_msg.tryAsCall());
} else if (_msg.isValidTransferGovernor()) {
_handleTransferGovernor(_msg.tryAsTransferGovernor());
} else if (_msg.isValidSetRouter()) {
_handleSetRouter(_msg.tryAsSetRouter());
} else {
require(false, "!valid message type");
}
}
/**
* @notice Dispatch calls locally
* @param _calls The calls
*/
function callLocal(GovernanceMessage.Call[] calldata _calls)
external
onlyGovernorOrRecoveryManager
{
for (uint256 i = 0; i < _calls.length; i++) {
_dispatchCall(_calls[i]);
}
}
/**
* @notice Dispatch calls on a remote chain via the remote GovernanceRouter
* @param _destination The domain of the remote chain
* @param _calls The calls
*/
function callRemote(
uint32 _destination,
GovernanceMessage.Call[] calldata _calls
) external onlyGovernor onlyNotInRecovery {
// ensure that destination chain has enrolled router
bytes32 _router = _mustHaveRouter(_destination);
// format call message
bytes memory _msg = GovernanceMessage.formatCalls(_calls);
// dispatch call message using Abacus
Outbox(xAppConnectionManager.outbox()).dispatch(
_destination,
_router,
_msg
);
}
/**
* @notice Transfer governorship
* @param _newDomain The domain of the new governor
* @param _newGovernor The address of the new governor
*/
function transferGovernor(uint32 _newDomain, address _newGovernor)
external
onlyGovernor
onlyNotInRecovery
{
bool _isLocalGovernor = _isLocalDomain(_newDomain);
// transfer the governor locally
_transferGovernor(_newDomain, _newGovernor, _isLocalGovernor);
// if the governor domain is local, we only need to change the governor address locally
// no need to message remote routers; they should already have the same domain set and governor = bytes32(0)
if (_isLocalGovernor) {
return;
}
// format transfer governor message
bytes memory _transferGovernorMessage = GovernanceMessage
.formatTransferGovernor(
_newDomain,
TypeCasts.addressToBytes32(_newGovernor)
);
// send transfer governor message to all remote routers
// note: this assumes that the Router is on the global GovernorDomain;
// this causes a process error when relinquishing governorship
// on a newly deployed domain which is not the GovernorDomain
_sendToAllRemoteRouters(_transferGovernorMessage);
}
/**
* @notice Transfer recovery manager role
* @dev callable by the recoveryManager at any time to transfer the role
* @param _newRecoveryManager The address of the new recovery manager
*/
function transferRecoveryManager(address _newRecoveryManager)
external
onlyRecoveryManager
{
emit TransferRecoveryManager(recoveryManager, _newRecoveryManager);
recoveryManager = _newRecoveryManager;
}
/**
* @notice Set the router address for a given domain and
* dispatch the change to all remote routers
* @param _domain The domain
* @param _router The address of the new router
*/
function setRouter(uint32 _domain, bytes32 _router)
external
onlyGovernor
onlyNotInRecovery
{
// set the router locally
_setRouter(_domain, _router);
// format message to set the router on all remote routers
bytes memory _setRouterMessage = GovernanceMessage.formatSetRouter(
_domain,
_router
);
_sendToAllRemoteRouters(_setRouterMessage);
}
/**
* @notice Set the router address *locally only*
* for the deployer to setup the router mapping locally
* before transferring governorship to the "true" governor
* @dev External helper for contract setup
* @param _domain The domain
* @param _router The new router
*/
function setRouterLocal(uint32 _domain, bytes32 _router)
external
onlyGovernorOrRecoveryManager
{
// set the router locally
_setRouter(_domain, _router);
}
/**
* @notice Set the address of the XAppConnectionManager
* @dev Domain/address validation helper
* @param _xAppConnectionManager The address of the new xAppConnectionManager
*/
function setXAppConnectionManager(address _xAppConnectionManager)
public
onlyGovernorOrRecoveryManager
{
xAppConnectionManager = XAppConnectionManager(_xAppConnectionManager);
}
/**
* @notice Initiate the recovery timelock
* @dev callable by the recovery manager
*/
function initiateRecoveryTimelock()
external
onlyNotInRecovery
onlyRecoveryManager
{
require(recoveryActiveAt == 0, "recovery already initiated");
// set the time that recovery will be active
recoveryActiveAt = block.timestamp.add(recoveryTimelock);
emit InitiateRecovery(recoveryManager, recoveryActiveAt);
}
/**
* @notice Exit recovery mode
* @dev callable by the recovery manager to end recovery mode
*/
function exitRecovery() external onlyRecoveryManager {
require(recoveryActiveAt != 0, "recovery not initiated");
delete recoveryActiveAt;
emit ExitRecovery(recoveryManager);
}
// ============ Public Functions ============
/**
* @notice Check if the contract is in recovery mode currently
* @return TRUE iff the contract is actively in recovery mode currently
*/
function inRecovery() public view returns (bool) {
uint256 _recoveryActiveAt = recoveryActiveAt;
bool _recoveryInitiated = _recoveryActiveAt != 0;
bool _recoveryActive = _recoveryActiveAt <= block.timestamp;
return _recoveryInitiated && _recoveryActive;
}
// ============ Internal Functions ============
/**
* @notice Handle message dispatching calls locally
* @param _msg The message
*/
function _handleCall(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.Call)
{
GovernanceMessage.Call[] memory _calls = _msg.getCalls();
for (uint256 i = 0; i < _calls.length; i++) {
_dispatchCall(_calls[i]);
}
}
/**
* @notice Handle message transferring governorship to a new Governor
* @param _msg The message
*/
function _handleTransferGovernor(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.TransferGovernor)
{
uint32 _newDomain = _msg.domain();
address _newGovernor = TypeCasts.bytes32ToAddress(_msg.governor());
bool _isLocalGovernor = _isLocalDomain(_newDomain);
_transferGovernor(_newDomain, _newGovernor, _isLocalGovernor);
}
/**
* @notice Handle message setting the router address for a given domain
* @param _msg The message
*/
function _handleSetRouter(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.SetRouter)
{
uint32 _domain = _msg.domain();
bytes32 _router = _msg.router();
_setRouter(_domain, _router);
}
/**
* @notice Dispatch message to all remote routers
* @param _msg The message
*/
function _sendToAllRemoteRouters(bytes memory _msg) internal {
Outbox _outbox = Outbox(xAppConnectionManager.outbox());
for (uint256 i = 0; i < domains.length; i++) {
if (domains[i] != uint32(0)) {
_outbox.dispatch(domains[i], routers[domains[i]], _msg);
}
}
}
/**
* @notice Dispatch call locally
* @param _call The call
* @return _ret
*/
function _dispatchCall(GovernanceMessage.Call memory _call)
internal
returns (bytes memory _ret)
{
address _toContract = TypeCasts.bytes32ToAddress(_call.to);
// attempt to dispatch using low-level call
bool _success;
(_success, _ret) = _toContract.call(_call.data);
// revert if the call failed
require(_success, "call failed");
}
/**
* @notice Transfer governorship within this contract's state
* @param _newDomain The domain of the new governor
* @param _newGovernor The address of the new governor
* @param _isLocalGovernor True if the newDomain is the localDomain
*/
function _transferGovernor(
uint32 _newDomain,
address _newGovernor,
bool _isLocalGovernor
) internal {
// require that the governor domain has a valid router
if (!_isLocalGovernor) {
_mustHaveRouter(_newDomain);
}
// Governor is 0x0 unless the governor is local
address _newGov = _isLocalGovernor ? _newGovernor : address(0);
// emit event before updating state variables
emit TransferGovernor(governorDomain, _newDomain, governor, _newGov);
// update state
governorDomain = _newDomain;
governor = _newGov;
}
/**
* @notice Set the router for a given domain
* @param _domain The domain
* @param _newRouter The new router
*/
function _setRouter(uint32 _domain, bytes32 _newRouter) internal {
bytes32 _previousRouter = routers[_domain];
// emit event at beginning in case return after remove
emit SetRouter(_domain, _previousRouter, _newRouter);
// if the router is being removed, remove the domain
if (_newRouter == bytes32(0)) {
_removeDomain(_domain);
return;
}
// if the router is being added, add the domain
if (_previousRouter == bytes32(0)) {
_addDomain(_domain);
}
// update state with new router
routers[_domain] = _newRouter;
}
/**
* @notice Add a domain that has a router
* @param _domain The domain
*/
function _addDomain(uint32 _domain) internal {
domains.push(_domain);
}
/**
* @notice Remove a domain and its associated router
* @param _domain The domain
*/
function _removeDomain(uint32 _domain) internal {
delete routers[_domain];
// find the index of the domain to remove & delete it from domains[]
for (uint256 i = 0; i < domains.length; i++) {
if (domains[i] == _domain) {
delete domains[i];
return;
}
}
}
/**
* @notice Determine if a given domain and address is the Governor Router
* @param _domain The domain
* @param _address The address of the domain's router
* @return _ret True if the given domain/address is the
* Governor Router.
*/
function _isGovernorRouter(uint32 _domain, bytes32 _address)
internal
view
returns (bool)
{
return _domain == governorDomain && _address == routers[_domain];
}
/**
* @notice Determine if a given domain is the local domain
* @param _domain The domain
* @return _ret - True if the given domain is the local domain
*/
function _isLocalDomain(uint32 _domain) internal view returns (bool) {
return _domain == localDomain;
}
/**
* @notice Require that a domain has a router and returns the router
* @param _domain The domain
* @return _router - The domain's router
*/
function _mustHaveRouter(uint32 _domain)
internal
view
returns (bytes32 _router)
{
_router = routers[_domain];
require(_router != bytes32(0), "!router");
}
}

@ -1,31 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
pragma experimental ABIEncoderV2;
import "../governance/GovernanceRouter.sol";
import {TypeCasts} from "../XAppConnectionManager.sol";
contract TestGovernanceRouter is GovernanceRouter {
constructor(uint32 _localDomain, uint256 _recoveryTimelock)
GovernanceRouter(_localDomain, 50)
{} // solhint-disable-line no-empty-blocks
function testSetRouter(uint32 _domain, bytes32 _router) external {
_setRouter(_domain, _router); // set the router locally
bytes memory _setRouterMessage = GovernanceMessage.formatSetRouter(
_domain,
_router
);
_sendToAllRemoteRouters(_setRouterMessage);
}
function containsDomain(uint32 _domain) external view returns (bool) {
for (uint256 i = 0; i < domains.length; i++) {
if (domains[i] == _domain) return true;
}
return false;
}
}

@ -3,7 +3,6 @@ import "@typechain/hardhat";
import "@nomiclabs/hardhat-etherscan";
import "@nomiclabs/hardhat-waffle";
import "hardhat-gas-reporter";
import './test/lib/index';
import { task } from "hardhat/config";
import { verifyLatestCoreDeploy } from "../../typescript/abacus-deploy/src/verification/verifyLatestDeploy";

@ -1,390 +0,0 @@
import { ethers, abacus } from 'hardhat';
import { expect } from 'chai';
import { formatCall, formatAbacusMessage } from './utils';
import { increaseTimestampBy, UpgradeTestHelpers } from '../utils';
import { Validator } from '../lib/core';
import { Address, Signer } from '../lib/types';
import { AbacusDeployment } from '../lib/AbacusDeployment';
import { GovernanceDeployment } from '../lib/GovernanceDeployment';
import {
MysteryMathV2__factory,
TestInbox,
TestInbox__factory,
TestRecipient__factory,
TestGovernanceRouter,
Inbox,
Outbox,
} from '../../typechain';
const helpers = require('../../../../vectors/proof.json');
const governorDomain = 1000;
const nonGovernorDomain = 2000;
const thirdDomain = 3000;
const domains = [governorDomain, nonGovernorDomain, thirdDomain];
const processGas = 850000;
const reserveGas = 15000;
const nullRoot = '0x' + '00'.repeat(32);
/*
* Deploy the full Abacus suite on two chains
*/
describe('GovernanceRouter', async () => {
let abacusDeployment: AbacusDeployment;
let governanceDeployment: GovernanceDeployment;
let signer: Signer,
secondSigner: Signer,
thirdRouter: Signer,
firstGovernor: Address,
secondGovernor: Address,
governorRouter: TestGovernanceRouter,
nonGovernorRouter: TestGovernanceRouter,
governorOutbox: Outbox,
governorInboxOnNonGovernorChain: TestInbox,
nonGovernorInboxOnGovernorChain: TestInbox,
validator: Validator;
async function expectGovernor(
governanceRouter: TestGovernanceRouter,
expectedGovernorDomain: number,
expectedGovernor: Address,
) {
expect(await governanceRouter.governorDomain()).to.equal(
expectedGovernorDomain,
);
expect(await governanceRouter.governor()).to.equal(expectedGovernor);
}
before(async () => {
[thirdRouter, signer, secondSigner] = await ethers.getSigners();
validator = await Validator.fromSigner(signer, governorDomain);
});
beforeEach(async () => {
abacusDeployment = await AbacusDeployment.fromDomains(domains, signer);
governanceDeployment = await GovernanceDeployment.fromAbacusDeployment(
abacusDeployment,
signer,
);
firstGovernor = await signer.getAddress();
secondGovernor = await secondSigner.getAddress();
governorRouter = governanceDeployment.router(governorDomain);
nonGovernorRouter = governanceDeployment.router(nonGovernorDomain);
governorInboxOnNonGovernorChain = abacusDeployment.inbox(
nonGovernorDomain,
governorDomain,
);
nonGovernorInboxOnGovernorChain = abacusDeployment.inbox(
governorDomain,
nonGovernorDomain,
);
governorOutbox = abacusDeployment.outbox(governorDomain);
});
// NB: must be first test for message proof
it('Sends cross-chain message to upgrade contract', async () => {
const upgradeUtils = new UpgradeTestHelpers();
// get upgradeBeaconController
const ubc = abacusDeployment.ubc(nonGovernorDomain);
// Transfer ownership of the UBC to governance.
await ubc.transferOwnership(nonGovernorRouter.address);
const mysteryMath = await upgradeUtils.deployMysteryMathUpgradeSetup(
signer,
ubc,
);
// expect results before upgrade
await upgradeUtils.expectMysteryMathV1(mysteryMath.proxy);
// Deploy Implementation 2
const factory2 = new MysteryMathV2__factory(signer);
const implementation2 = await factory2.deploy();
// Format abacus call message
const call = await formatCall(ubc, 'upgrade', [
mysteryMath.beacon.address,
implementation2.address,
]);
// dispatch call on local governorRouter
let tx = await governorRouter.callRemote(nonGovernorDomain, [call]);
await abacusDeployment.processMessagesFromDomain(governorDomain);
// test implementation was upgraded
await upgradeUtils.expectMysteryMathV2(mysteryMath.proxy);
});
it('Rejects message from unenrolled inbox', async () => {
const inboxFactory = new TestInbox__factory(signer);
const unenrolledInbox = await inboxFactory.deploy(
nonGovernorDomain,
processGas,
reserveGas,
);
// The ValdiatorManager is unused in this test, but needs to be a contract.
await unenrolledInbox.initialize(
thirdDomain,
unenrolledInbox.address,
nullRoot,
0,
);
// Create TransferGovernor message
const transferGovernorMessage = abacus.governance.formatTransferGovernor(
thirdDomain,
abacus.ethersAddressToBytes32(secondGovernor),
);
const abacusMessage = await formatAbacusMessage(
unenrolledInbox,
governorRouter,
nonGovernorRouter,
transferGovernorMessage,
);
// Expect inbox processing to fail when nonGovernorRouter reverts in handle
let success = await unenrolledInbox.callStatic.testProcess(abacusMessage);
expect(success).to.be.false;
});
it('Rejects message not from governor router', async () => {
// Create TransferGovernor message
const transferGovernorMessage = abacus.governance.formatTransferGovernor(
nonGovernorDomain,
abacus.ethersAddressToBytes32(nonGovernorRouter.address),
);
const abacusMessage = await formatAbacusMessage(
governorInboxOnNonGovernorChain,
nonGovernorRouter,
governorRouter,
transferGovernorMessage,
);
// Set message status to MessageStatus.Proven
await nonGovernorInboxOnGovernorChain.setMessageProven(abacusMessage);
// Expect inbox processing to fail when nonGovernorRouter reverts in handle
let success = await nonGovernorInboxOnGovernorChain.callStatic.testProcess(
abacusMessage,
);
expect(success).to.be.false;
});
it('Accepts a valid transfer governor message', async () => {
// Enroll router for new domain (in real setting this would
// be executed with an Abacus message sent to the nonGovernorRouter)
await nonGovernorRouter.testSetRouter(
thirdDomain,
abacus.ethersAddressToBytes32(thirdRouter.address),
);
// Create TransferGovernor message
const transferGovernorMessage = abacus.governance.formatTransferGovernor(
thirdDomain,
abacus.ethersAddressToBytes32(thirdRouter.address),
);
const abacusMessage = await formatAbacusMessage(
governorInboxOnNonGovernorChain,
governorRouter,
nonGovernorRouter,
transferGovernorMessage,
);
// Expect successful tx on static call
let success = await governorInboxOnNonGovernorChain.callStatic.process(
abacusMessage,
);
expect(success).to.be.true;
await governorInboxOnNonGovernorChain.process(abacusMessage);
await expectGovernor(
nonGovernorRouter,
thirdDomain,
ethers.constants.AddressZero,
);
});
it('Accepts valid set router message', async () => {
// Create address for router to enroll and domain for router
const [router] = await ethers.getSigners();
// Create SetRouter message
const setRouterMessage = abacus.governance.formatSetRouter(
thirdDomain,
abacus.ethersAddressToBytes32(router.address),
);
const abacusMessage = await formatAbacusMessage(
governorInboxOnNonGovernorChain,
governorRouter,
nonGovernorRouter,
setRouterMessage,
);
// Expect successful tx
let success = await governorInboxOnNonGovernorChain.callStatic.process(
abacusMessage,
);
expect(success).to.be.true;
// Expect new router to be registered for domain and for new domain to be
// in domains array
await governorInboxOnNonGovernorChain.process(abacusMessage);
expect(await nonGovernorRouter.routers(thirdDomain)).to.equal(
abacus.ethersAddressToBytes32(router.address),
);
expect(await nonGovernorRouter.containsDomain(thirdDomain)).to.be.true;
});
it('Accepts valid call messages', async () => {
// const TestRecipient = await abacus.deployImplementation('TestRecipient');
const testRecipientFactory = new TestRecipient__factory(signer);
const testRecipient = await testRecipientFactory.deploy();
// Format abacus call message
const arg = 'String!';
const call = await formatCall(testRecipient, 'receiveString', [arg]);
// Create Call message to test recipient that calls receiveString
const callMessage = abacus.governance.formatCalls([call, call]);
const abacusMessage = await formatAbacusMessage(
governorInboxOnNonGovernorChain,
governorRouter,
nonGovernorRouter,
callMessage,
);
// Expect successful tx
let success = await governorInboxOnNonGovernorChain.callStatic.testProcess(
abacusMessage,
);
expect(success).to.be.true;
});
it('Transfers governorship', async () => {
// Transfer governor on current governor chain
// Governor HAS NOT been transferred on original governor domain
await expectGovernor(governorRouter, governorDomain, firstGovernor);
// Governor HAS NOT been transferred on original non-governor domain
await expectGovernor(
nonGovernorRouter,
governorDomain,
ethers.constants.AddressZero,
);
// transfer governorship to nonGovernorRouter
await governorRouter.transferGovernor(nonGovernorDomain, secondGovernor);
// Governor HAS been transferred on original governor domain
await expectGovernor(
governorRouter,
nonGovernorDomain,
ethers.constants.AddressZero,
);
// Governor HAS NOT been transferred on original non-governor domain
await expectGovernor(
nonGovernorRouter,
governorDomain,
ethers.constants.AddressZero,
);
const transferGovernorMessage = abacus.governance.formatTransferGovernor(
nonGovernorDomain,
abacus.ethersAddressToBytes32(secondGovernor),
);
const abacusMessage = await formatAbacusMessage(
governorInboxOnNonGovernorChain,
governorRouter,
nonGovernorRouter,
transferGovernorMessage,
);
// Process transfer governor message on Inbox
await governorInboxOnNonGovernorChain.process(abacusMessage);
// Governor HAS been transferred on original governor domain
await expectGovernor(
governorRouter,
nonGovernorDomain,
ethers.constants.AddressZero,
);
// Governor HAS been transferred on original non-governor domain
await expectGovernor(nonGovernorRouter, nonGovernorDomain, secondGovernor);
});
it('Upgrades using GovernanceRouter call', async () => {
const upgradeUtils = new UpgradeTestHelpers();
// get upgradeBeaconController
const ubc = abacusDeployment.ubc(governorDomain);
// Transfer ownership of the UBC to governance.
await ubc.transferOwnership(governorRouter.address);
const mysteryMath = await upgradeUtils.deployMysteryMathUpgradeSetup(
signer,
ubc,
);
// expect results before upgrade
await upgradeUtils.expectMysteryMathV1(mysteryMath.proxy);
// Deploy Implementation 2
const v2Factory = new MysteryMathV2__factory(signer);
const implementation = await v2Factory.deploy();
// Format abacus call message
const call = await formatCall(ubc, 'upgrade', [
mysteryMath.beacon.address,
implementation.address,
]);
// dispatch call on local governorRouter
await expect(governorRouter.callLocal([call])).to.emit(
ubc,
'BeaconUpgraded',
);
// test implementation was upgraded
await upgradeUtils.expectMysteryMathV2(mysteryMath.proxy);
});
it('Calls ValidatorManager to set the validator for a domain', async () => {
const [newValidator] = await ethers.getSigners();
const validatorManager = abacusDeployment.validatorManager(governorDomain);
await validatorManager.transferOwnership(governorRouter.address);
// check current Validator address on Outbox
let currentValidatorAddr = await validatorManager.validators(
governorDomain,
);
expect(currentValidatorAddr).to.equal(
await abacusDeployment.validator(governorDomain).signer.getAddress(),
);
// format abacus call message
const call = await formatCall(validatorManager, 'setValidator', [
governorDomain,
newValidator.address,
]);
await expect(governorRouter.callLocal([call])).to.emit(
validatorManager,
'NewValidator',
);
// check for new validator
currentValidatorAddr = await validatorManager.validators(governorDomain);
expect(currentValidatorAddr).to.equal(newValidator.address);
});
});

@ -1,138 +0,0 @@
import { abacus, ethers } from 'hardhat';
import { expect } from 'chai';
import * as utils from './utils';
import { Validator, MessageStatus } from '../lib/core';
import { Signer, BytesArray } from '../lib/types';
import { TestRecipient__factory, TestInbox } from '../../typechain';
import { AbacusDeployment } from '../lib/AbacusDeployment';
import { GovernanceDeployment } from '../lib/GovernanceDeployment';
const proveAndProcessTestCases = require('../../../../vectors/proveAndProcess.json');
const domains = [1000, 2000];
const localDomain = domains[0];
const remoteDomain = domains[1];
/*
* Deploy the full Abacus suite on two chains
* dispatch messages to Outbox
* checkpoint on Outbox
* Sign and relay checkpoints to Inbox
* TODO prove and process messages on Inbox
*/
describe('SimpleCrossChainMessage', async () => {
let abacusDeployment: AbacusDeployment;
let governanceDeployment: GovernanceDeployment;
let randomSigner: Signer, validator: Validator;
before(async () => {
[randomSigner] = await ethers.getSigners();
validator = await Validator.fromSigner(randomSigner, localDomain);
abacusDeployment = await AbacusDeployment.fromDomains(
domains,
randomSigner,
);
governanceDeployment = await GovernanceDeployment.fromAbacusDeployment(
abacusDeployment,
randomSigner,
);
});
it('All Outboxs have correct initial state', async () => {
const nullRoot = '0x' + '00'.repeat(32);
const governorOutbox = abacusDeployment.outbox(localDomain);
let [root, index] = await governorOutbox.latestCheckpoint();
expect(root).to.equal(nullRoot);
expect(index).to.equal(0);
const nonGovernorOutbox = abacusDeployment.outbox(remoteDomain);
[root, index] = await nonGovernorOutbox.latestCheckpoint();
expect(root).to.equal(nullRoot);
expect(index).to.equal(0);
});
it('Origin Outbox accepts valid messages', async () => {
const messages = ['message'].map((message) =>
utils.formatMessage(message, remoteDomain, randomSigner.address),
);
await utils.dispatchMessages(
abacusDeployment.outbox(localDomain),
messages,
);
});
it('Destination Inbox accepts a checkpoint', async () => {
const outbox = abacusDeployment.outbox(localDomain);
const inbox = abacusDeployment.inbox(remoteDomain, localDomain);
await utils.checkpoint(outbox, inbox, validator);
});
it('Origin Outbox accepts batched messages', async () => {
const messages = ['message1', 'message2', 'message3'].map((message) =>
utils.formatMessage(message, remoteDomain, randomSigner.address),
);
await utils.dispatchMessages(
abacusDeployment.outbox(localDomain),
messages,
);
});
it('Destination Inbox Accepts a second checkpoint', async () => {
const outbox = abacusDeployment.outbox(localDomain);
const inbox = abacusDeployment.inbox(remoteDomain, localDomain);
await utils.checkpoint(outbox, inbox, validator);
});
it('Proves and processes a message on Inbox', async () => {
// get governance routers
const governorRouter = governanceDeployment.router(localDomain);
const nonGovernorRouter = governanceDeployment.router(remoteDomain);
const inbox = abacusDeployment.inbox(remoteDomain, localDomain);
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 = abacus.governance.formatCalls([call]);
// Create Abacus message that is sent from the governor domain and governor
// to the nonGovernorRouter on the nonGovernorDomain
const nonce = 0;
const abacusMessage = abacus.formatMessage(
1000,
governorRouter.address,
nonce,
2000,
nonGovernorRouter.address,
callMessage,
);
// get merkle proof
const { path, index } = proveAndProcessTestCases[0];
const messageHash = abacus.messageHash(abacusMessage);
// set root
const proofRoot = await inbox.testBranchRoot(
messageHash,
path as BytesArray,
index,
);
await inbox.setCheckpoint(proofRoot, 1);
// prove and process message
await inbox.proveAndProcess(abacusMessage, path as BytesArray, index);
// expect call to have been processed
expect(await TestRecipient.processed()).to.be.true;
expect(await inbox.messages(messageHash)).to.equal(MessageStatus.PROCESSED);
});
});

@ -1,164 +0,0 @@
import { expect } from 'chai';
import { ethers, abacus } from 'hardhat';
import * as types from 'ethers';
import { Validator } from '../lib/core';
import { CallData, Address } from '../lib/types';
import {
Inbox,
TestInbox,
Outbox,
TestGovernanceRouter,
} from '../../typechain';
type MessageDetails = {
message: string;
destinationDomain: number;
recipientAddress: Address;
};
/*
* Dispatch a message from the specified Outbox contract.
*
* @param messageDetails - Message type containing
* the message string,
* the destination domain to which the message will be sent,
* the recipient address on the destination domain to which the message will be dispatched
*/
export async function dispatchMessage(
outbox: Outbox,
messageDetails: MessageDetails,
) {
const { message, destinationDomain, recipientAddress } = messageDetails;
// Send message with random signer address as msg.sender
await outbox.dispatch(
destinationDomain,
abacus.ethersAddressToBytes32(recipientAddress),
ethers.utils.formatBytes32String(message),
);
}
/*
* Dispatch a set of messages to the specified Outbox contract.
*
* @param messages - Message[]
*/
export async function dispatchMessages(
outbox: Outbox,
messages: MessageDetails[],
) {
for (let message of messages) {
await dispatchMessage(outbox, message);
}
}
/*
* Checkpoints a Outbox, signs that checkpoint, and checkpoints the Inbox
*
* @param outbox - The Outbox contract
* @param inbox - The Inbox contract
* @param validator - The Validator
*/
export async function checkpoint(
outbox: Outbox,
inbox: Inbox,
validator: Validator,
) {
await outbox.checkpoint();
const [root, index] = await outbox.latestCheckpoint();
const { signature } = await validator.signCheckpoint(root, index.toNumber());
await inbox.checkpoint(root, index, signature);
const checkpointedIndex = await inbox.checkpoints(root);
expect(checkpointedIndex).to.equal(index);
}
/*
* Format into a Message type
*
* @param messageString - string for the body of the message
* @param messageDestinationDomain - domain where the message will be sent
* @param messageRecipient - recipient of the message on the destination domain
*
* @return message - Message type
*/
export function formatMessage(
message: string,
destinationDomain: number,
recipientAddress: Address,
): MessageDetails {
return {
message,
destinationDomain,
recipientAddress,
};
}
export async function formatAbacusMessage(
inbox: TestInbox,
governorRouter: TestGovernanceRouter,
destinationRouter: TestGovernanceRouter,
message: string,
): Promise<string> {
const nonce = 0;
const governorDomain = await governorRouter.localDomain();
const destinationDomain = await destinationRouter.localDomain();
// Create Abacus message that is sent from the governor domain and governor
// to the nonGovernorRouter on the nonGovernorDomain
const abacusMessage = abacus.formatMessage(
governorDomain,
governorRouter.address,
nonce,
destinationDomain,
destinationRouter.address,
message,
);
// Set message status to MessageStatus.Proven
await inbox.setMessageProven(abacusMessage);
return abacusMessage;
}
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(
callFunc,
functionArgs,
);
return {
to: abacus.ethersAddressToBytes32(destinationContract.address),
data: callDataEncoded,
};
}
// Send a transaction from the specified signer
export async function sendFromSigner(
signer: types.Signer,
contract: types.Contract,
functionName: string,
args: any[],
) {
const data = encodeData(contract, functionName, args);
return signer.sendTransaction({
to: contract.address,
data,
});
}
function encodeData(
contract: types.Contract,
functionName: string,
args: any[],
): string {
const func = contract.interface.getFunction(functionName);
return contract.interface.encodeFunctionData(func, args);
}

@ -1,7 +1,13 @@
import { ethers, abacus } from 'hardhat';
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { Validator, AbacusState, MessageStatus } from './lib/core';
import {
formatMessage,
messageHash,
Validator,
AbacusState,
MessageStatus,
} from './lib/core';
import { Signer, BytesArray } from './lib/types';
import {
BadRecipient1__factory,
@ -26,7 +32,6 @@ const localDomain = 2000;
const remoteDomain = 1000;
const processGas = 850000;
const reserveGas = 15000;
const nullRoot = '0x' + '00'.repeat(32);
describe('Inbox', async () => {
const badRecipientFactories = [
@ -58,12 +63,22 @@ describe('Inbox', async () => {
beforeEach(async () => {
const inboxFactory = new TestInbox__factory(signer);
inbox = await inboxFactory.deploy(localDomain, processGas, reserveGas);
await inbox.initialize(remoteDomain, validatorManager.address, nullRoot, 0);
await inbox.initialize(
remoteDomain,
validatorManager.address,
ethers.constants.HashZero,
0,
);
});
it('Cannot be initialized twice', async () => {
await expect(
inbox.initialize(remoteDomain, validatorManager.address, nullRoot, 0),
inbox.initialize(
remoteDomain,
validatorManager.address,
ethers.constants.HashZero,
0,
),
).to.be.revertedWith('Initializable: contract is already initialized');
});
@ -162,7 +177,7 @@ describe('Inbox', async () => {
const testRecipient = await testRecipientFactory.deploy();
const nonce = 0;
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -181,7 +196,7 @@ describe('Inbox', async () => {
const processTx = inbox.process(abacusMessage);
await expect(processTx)
.to.emit(inbox, 'Process')
.withArgs(abacus.messageHash(abacusMessage), true, '0x');
.withArgs(messageHash(abacusMessage), true, '0x');
});
it('Fails to process an unproved message', async () => {
@ -189,7 +204,7 @@ describe('Inbox', async () => {
const nonce = 0;
const body = ethers.utils.formatBytes32String('message');
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -210,7 +225,7 @@ describe('Inbox', async () => {
const badRecipient = await factory.deploy();
const nonce = 0;
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -230,7 +245,7 @@ describe('Inbox', async () => {
const nonce = 0;
const body = ethers.utils.formatBytes32String('message');
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -249,7 +264,7 @@ describe('Inbox', async () => {
const nonce = 0;
const body = ethers.utils.formatBytes32String('message');
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
abacusMessageSender.address,
nonce,
@ -268,7 +283,7 @@ describe('Inbox', async () => {
const nonce = 0;
const body = ethers.utils.formatBytes32String('message');
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -293,7 +308,7 @@ describe('Inbox', async () => {
const testRecipient = await factory.deploy();
const nonce = 0;
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -319,7 +334,7 @@ describe('Inbox', async () => {
// Note that hash of this message specifically matches leaf of 1st
// proveAndProcess test case
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -330,7 +345,7 @@ describe('Inbox', async () => {
// Assert above message and test case have matching leaves
const { path, index } = proveAndProcessTestCases[0];
const messageHash = abacus.messageHash(abacusMessage);
const hash = messageHash(abacusMessage);
// Set inbox's current root to match newly computed root that includes
// the new leaf (normally root will have already been computed and path
@ -338,7 +353,7 @@ describe('Inbox', async () => {
// impossible to find the inputs that create a pre-determined root, we
// simply recalculate root with the leaf using branchRoot)
const proofRoot = await inbox.testBranchRoot(
messageHash,
hash,
path as BytesArray,
index,
);
@ -346,7 +361,7 @@ describe('Inbox', async () => {
await inbox.proveAndProcess(abacusMessage, path as BytesArray, index);
expect(await inbox.messages(messageHash)).to.equal(MessageStatus.PROCESSED);
expect(await inbox.messages(hash)).to.equal(MessageStatus.PROCESSED);
});
it('Has proveAndProcess fail if prove fails', async () => {
@ -358,7 +373,7 @@ describe('Inbox', async () => {
let { leaf, index, path } = testCase.proofs[0];
// Create arbitrary message (contents not important)
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
remoteDomain,
sender.address,
nonce,

@ -0,0 +1,4 @@
export * as types from './lib/types';
export * as utils from './lib/utils';
export * as core from './lib/core';
export { AbacusDeployment } from './lib/AbacusDeployment';

@ -30,7 +30,6 @@ export interface AbacusInstance {
const processGas = 850000;
const reserveGas = 15000;
const nullRoot = '0x' + '00'.repeat(32);
export class AbacusDeployment {
constructor(
@ -84,7 +83,7 @@ export class AbacusDeployment {
await inbox.initialize(
remoteDomain,
validatorManager.address,
nullRoot,
ethers.constants.HashZero,
0,
);
await connectionManager.enrollInbox(inbox.address, remoteDomain);
@ -146,7 +145,10 @@ export class AbacusDeployment {
async processMessagesFromDomain(local: types.Domain) {
const outbox = this.outbox(local);
const [checkpointedRoot] = await outbox.latestCheckpoint();
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);

@ -1,8 +1,8 @@
import { assert } from 'chai';
import * as ethers from 'ethers';
import { addressToBytes32 } from './utils';
import * as types from './types';
import { getHexStringByteLength } from './utils';
export class Validator {
localDomain: types.Domain;
@ -51,7 +51,7 @@ export class Validator {
}
}
const formatMessage = (
export const formatMessage = (
localDomain: types.Domain,
senderAddr: types.Address,
sequence: number,
@ -59,8 +59,8 @@ const formatMessage = (
recipientAddr: types.Address,
body: types.HexString,
): string => {
senderAddr = ethersAddressToBytes32(senderAddr);
recipientAddr = ethersAddressToBytes32(recipientAddr);
senderAddr = addressToBytes32(senderAddr);
recipientAddr = addressToBytes32(recipientAddr);
return ethers.utils.solidityPack(
['uint32', 'bytes32', 'uint32', 'uint32', 'bytes32', 'bytes'],
@ -74,46 +74,17 @@ export enum AbacusState {
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 messageHash(message: types.HexString): string {
export function messageHash(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 destinationAndNonce(
export function destinationAndNonce(
destination: types.Domain,
sequence: number,
): ethers.BigNumber {
@ -125,49 +96,9 @@ function destinationAndNonce(
.add(ethers.BigNumber.from(sequence));
}
function domainHash(domain: Number): string {
export function domainHash(domain: Number): string {
return ethers.utils.solidityKeccak256(
['uint32', 'string'],
[domain, 'OPTICS'],
);
}
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],
);
}
export const abacus: types.HardhatAbacusHelpers = {
formatMessage,
governance: {
formatTransferGovernor,
formatSetRouter,
formatCalls,
},
messageHash,
ethersAddressToBytes32,
destinationAndNonce,
domainHash,
};

@ -1,9 +0,0 @@
import '@nomiclabs/hardhat-waffle';
import { extendEnvironment } from 'hardhat/config';
import { abacus } from './core';
// HardhatRuntimeEnvironment
extendEnvironment((hre) => {
hre.abacus = abacus;
});

@ -1,26 +1,5 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { BytesLike, ethers } from 'ethers';
/********* HRE *********/
export interface HardhatAbacusHelpers {
formatMessage: Function;
governance: {
formatTransferGovernor: Function;
formatSetRouter: Function;
formatCalls: Function;
};
messageHash: Function;
ethersAddressToBytes32: Function;
destinationAndNonce: Function;
domainHash: Function;
}
declare module 'hardhat/types/runtime' {
interface HardhatRuntimeEnvironment {
abacus: HardhatAbacusHelpers;
}
}
import { BytesLike } from 'ethers';
/********* BASIC TYPES *********/
export type Domain = number;

@ -1,7 +1,7 @@
import { expect } from 'chai';
import ethers from 'ethers';
import { Signer } from './lib/types';
import { Signer } from './types';
import {
MysteryMathV1,
MysteryMathV2,
@ -10,15 +10,7 @@ import {
UpgradeBeacon,
UpgradeBeacon__factory,
UpgradeBeaconProxy__factory,
} from '../typechain';
export const increaseTimestampBy = async (
provider: ethers.providers.JsonRpcProvider,
increaseTime: number,
) => {
await provider.send('evm_increaseTime', [increaseTime]);
await provider.send('evm_mine', []);
};
} from '../../typechain';
export type MysteryMathUpgrade = {
proxy: MysteryMathV1 | MysteryMathV2;

@ -1,3 +1,6 @@
import { ethers } from 'ethers';
import { Address } from './types';
/*
* Gets the byte length of a hex string
*
@ -22,7 +25,7 @@ export function getHexStringByteLength(hexStr: string) {
* @param address - the address
* @return The address as bytes32
*/
export function toBytes32(address: string): string {
export function toBytes32(address: Address): string {
return '0x' + '00'.repeat(12) + address.slice(2);
}
@ -33,3 +36,9 @@ export const stringToBytes32 = (s: string): string => {
return '0x' + result.toString('hex');
};
export function addressToBytes32(address: Address): string {
return ethers.utils
.hexZeroPad(ethers.utils.hexStripZeros(address), 32)
.toLowerCase();
}

@ -1,5 +1,7 @@
import { ethers, abacus } from 'hardhat';
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { formatMessage, messageHash as msgHash } from './lib/core';
import { addressToBytes32 } from './lib/utils';
import { TestMessage, TestMessage__factory } from '../typechain';
const testCases = require('../../../vectors/message.json');
@ -22,7 +24,7 @@ describe('Message', async () => {
const nonce = 1;
const body = ethers.utils.formatBytes32String('message');
const message = abacus.formatMessage(
const message = formatMessage(
remoteDomain,
sender.address,
nonce,
@ -33,12 +35,12 @@ describe('Message', async () => {
expect(await messageLib.origin(message)).to.equal(remoteDomain);
expect(await messageLib.sender(message)).to.equal(
abacus.ethersAddressToBytes32(sender.address),
addressToBytes32(sender.address),
);
expect(await messageLib.nonce(message)).to.equal(nonce);
expect(await messageLib.destination(message)).to.equal(localDomain);
expect(await messageLib.recipient(message)).to.equal(
abacus.ethersAddressToBytes32(recipient.address),
addressToBytes32(recipient.address),
);
expect(await messageLib.recipientAddress(message)).to.equal(
recipient.address,
@ -52,9 +54,9 @@ describe('Message', async () => {
const nonce = 1;
const destination = 2000;
const recipient = '0x2222222222222222222222222222222222222222';
const body = ethers.utils.arrayify('0x1234');
const body = '0x1234';
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
origin,
sender,
nonce,
@ -84,6 +86,6 @@ describe('Message', async () => {
ethers.utils.hexlify(testBody),
);
expect(await messageLib.leaf(abacusMessage)).to.equal(messageHash);
expect(abacus.messageHash(abacusMessage)).to.equal(messageHash);
expect(msgHash(abacusMessage)).to.equal(messageHash);
});
});

@ -1,7 +1,14 @@
import { ethers, abacus } from 'hardhat';
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { AbacusState, Validator } from './lib/core';
import { Signer } from './lib/types';
import {
AbacusState,
Validator,
formatMessage,
messageHash,
destinationAndNonce,
} from './lib/core';
import { addressToBytes32 } from './lib/utils';
import { TestOutbox, TestOutbox__factory } from '../typechain';
@ -9,7 +16,6 @@ const destinationNonceTestCases = require('../../../vectors/destinationNonce.jso
const localDomain = 1000;
const destDomain = 2000;
const nullAddress: string = '0x' + '00'.repeat(32);
describe('Outbox', async () => {
let outbox: TestOutbox, signer: Signer, recipient: Signer;
@ -40,11 +46,7 @@ describe('Outbox', async () => {
const message = ethers.utils.formatBytes32String('message');
await expect(
outbox.dispatch(
destDomain,
abacus.ethersAddressToBytes32(recipient.address),
message,
),
outbox.dispatch(destDomain, addressToBytes32(recipient.address), message),
).to.be.revertedWith('failed state');
});
@ -57,11 +59,7 @@ describe('Outbox', async () => {
it('Does not dispatch too large messages', async () => {
const message = `0x${Buffer.alloc(3000).toString('hex')}`;
await expect(
outbox.dispatch(
destDomain,
abacus.ethersAddressToBytes32(recipient.address),
message,
),
outbox.dispatch(destDomain, addressToBytes32(recipient.address), message),
).to.be.revertedWith('msg too long');
});
@ -70,9 +68,9 @@ describe('Outbox', async () => {
const nonce = await outbox.nonces(localDomain);
// Format data that will be emitted from Dispatch event
const destinationAndNonce = abacus.destinationAndNonce(destDomain, nonce);
const destAndNonce = destinationAndNonce(destDomain, nonce);
const abacusMessage = abacus.formatMessage(
const abacusMessage = formatMessage(
localDomain,
signer.address,
nonce,
@ -80,7 +78,7 @@ describe('Outbox', async () => {
recipient.address,
message,
);
const messageHash = abacus.messageHash(abacusMessage);
const hash = messageHash(abacusMessage);
const leafIndex = await outbox.tree();
const [checkpointedRoot] = await outbox.latestCheckpoint();
@ -88,32 +86,22 @@ describe('Outbox', async () => {
await expect(
outbox
.connect(signer)
.dispatch(
destDomain,
abacus.ethersAddressToBytes32(recipient.address),
message,
),
.dispatch(destDomain, addressToBytes32(recipient.address), message),
)
.to.emit(outbox, 'Dispatch')
.withArgs(
messageHash,
leafIndex,
destinationAndNonce,
checkpointedRoot,
abacusMessage,
);
.withArgs(hash, leafIndex, destAndNonce, checkpointedRoot, abacusMessage);
});
it('Checkpoints the latest root', async () => {
const message = ethers.utils.formatBytes32String('message');
await outbox.dispatch(
destDomain,
abacus.ethersAddressToBytes32(recipient.address),
addressToBytes32(recipient.address),
message,
);
await outbox.checkpoint();
const [root, index] = await outbox.latestCheckpoint();
expect(root).to.not.equal(nullAddress);
expect(root).to.not.equal(ethers.constants.HashZero);
expect(index).to.equal(1);
});

@ -1,6 +1,6 @@
import { ethers } from 'hardhat';
import { UpgradeTestHelpers, MysteryMathUpgrade } from './utils';
import { UpgradeTestHelpers, MysteryMathUpgrade } from './lib/upgrade';
import { Signer } from './lib/types';
import {
UpgradeBeaconController__factory,

@ -1,7 +1,8 @@
import { ethers, abacus } from 'hardhat';
import { ethers } from 'hardhat';
import { expect } from 'chai';
import { AbacusState, Validator } from './lib/core';
import { addressToBytes32 } from './lib/utils';
import { Signer } from './lib/types';
import {
@ -127,7 +128,7 @@ describe('ValidatorManager', async () => {
const message = `0x${Buffer.alloc(10).toString('hex')}`;
await outbox.dispatch(
localDomain,
abacus.ethersAddressToBytes32(signer.address),
addressToBytes32(signer.address),
message,
);
await outbox.checkpoint();

@ -1,4 +1,4 @@
import { ethers, abacus } from 'hardhat';
import { ethers } from 'hardhat';
import { expect } from 'chai';
import {
@ -18,7 +18,6 @@ const localDomain = 1000;
const remoteDomain = 2000;
const processGas = 850000;
const reserveGas = 15000;
const nullRoot = '0x' + '00'.repeat(32);
describe('XAppConnectionManager', async () => {
let connectionManager: XAppConnectionManager,
@ -43,7 +42,12 @@ describe('XAppConnectionManager', async () => {
);
// The ValidatorManager is unused in these tests *but* needs to be a
// contract.
await enrolledInbox.initialize(remoteDomain, outbox.address, nullRoot, 0);
await enrolledInbox.initialize(
remoteDomain,
outbox.address,
ethers.constants.HashZero,
0,
);
const connectionManagerFactory = new XAppConnectionManager__factory(signer);
connectionManager = await connectionManagerFactory.deploy();

@ -9,9 +9,18 @@ import {IMessageRecipient} from "@abacus-network/abacus-sol/interfaces/IMessageR
abstract contract Router is XAppConnectionClient, IMessageRecipient {
// ============ Mutable Storage ============
mapping(uint32 => bytes32) public remotes;
mapping(uint32 => bytes32) public routers;
uint256[49] private __GAP; // gap for upgrade safety
// ============ Events ============
/**
* @notice Emitted when a router is set.
* @param domain The domain of the new router
* @param router The address of the new router
*/
event EnrollRemoteRouter(uint32 indexed domain, bytes32 indexed router);
// ============ Modifiers ============
/**
@ -20,7 +29,7 @@ abstract contract Router is XAppConnectionClient, IMessageRecipient {
* @param _router The address the message is coming from
*/
modifier onlyRemoteRouter(uint32 _origin, bytes32 _router) {
require(_isRemoteRouter(_origin, _router), "!remote router");
require(_isRemoteRouter(_origin, _router), "!router");
_;
}
@ -33,9 +42,10 @@ abstract contract Router is XAppConnectionClient, IMessageRecipient {
*/
function enrollRemoteRouter(uint32 _domain, bytes32 _router)
external
virtual
onlyOwner
{
remotes[_domain] = _router;
_enrollRemoteRouter(_domain, _router);
}
// ============ Virtual functions ============
@ -47,6 +57,17 @@ abstract contract Router is XAppConnectionClient, IMessageRecipient {
) external virtual override;
// ============ Internal functions ============
/**
* @notice Set the router for a given domain
* @param _domain The domain
* @param _router The new router
*/
function _enrollRemoteRouter(uint32 _domain, bytes32 _router) internal {
routers[_domain] = _router;
emit EnrollRemoteRouter(_domain, _router);
}
/**
* @notice Return true if the given domain / router is the address of a remote xApp Router
* @param _domain The domain of the potential remote xApp Router
@ -57,20 +78,35 @@ abstract contract Router is XAppConnectionClient, IMessageRecipient {
view
returns (bool)
{
return remotes[_domain] == _router;
return routers[_domain] == _router;
}
/**
* @notice Assert that the given domain has a xApp Router registered and return its address
* @param _domain The domain of the chain for which to get the xApp Router
* @return _remote The address of the remote xApp Router on _domain
* @return _router The address of the remote xApp Router on _domain
*/
function _mustHaveRemote(uint32 _domain)
function _mustHaveRemoteRouter(uint32 _domain)
internal
view
returns (bytes32 _remote)
returns (bytes32 _router)
{
_router = routers[_domain];
require(_router != bytes32(0), "!router");
}
/**
* @notice Dispatches a message to an enrolled router via the local routers
* Outbox
* @dev Reverts if there is no enrolled router for _destination
* @param _destination The domain of the chain to which to send the message
* @param _msg The message to dispatch
*/
function _dispatchToRemoteRouter(uint32 _destination, bytes memory _msg)
internal
{
_remote = remotes[_domain];
require(_remote != bytes32(0), "!remote");
// ensure that destination chain has enrolled router
bytes32 _router = _mustHaveRemoteRouter(_destination);
_outbox().dispatch(_destination, _router, _msg);
}
}

@ -12,6 +12,14 @@ abstract contract XAppConnectionClient is OwnableUpgradeable {
XAppConnectionManager public xAppConnectionManager;
uint256[49] private __GAP; // gap for upgrade safety
// ============ Events ============
/**
* @notice Emitted when a new xAppConnectionManager is set.
* @param xAppConnectionManager The address of the xAppConnectionManager contract
*/
event SetXAppConnectionManager(address indexed xAppConnectionManager);
// ============ Modifiers ============
/**
@ -40,13 +48,25 @@ abstract contract XAppConnectionClient is OwnableUpgradeable {
*/
function setXAppConnectionManager(address _xAppConnectionManager)
external
virtual
onlyOwner
{
xAppConnectionManager = XAppConnectionManager(_xAppConnectionManager);
_setXAppConnectionManager(_xAppConnectionManager);
}
// ============ Internal functions ============
/**
* @notice Modify the contract the xApp uses to validate Inbox contracts
* @param _xAppConnectionManager The address of the xAppConnectionManager contract
*/
function _setXAppConnectionManager(address _xAppConnectionManager)
internal
{
xAppConnectionManager = XAppConnectionManager(_xAppConnectionManager);
emit SetXAppConnectionManager(_xAppConnectionManager);
}
/**
* @notice Get the local Outbox contract from the xAppConnectionManager
* @return The local Outbox contract

@ -93,7 +93,7 @@ contract BridgeRouter is Version0, Router, TokenRegistry {
} else if (_action.isDetails()) {
_handleDetails(_tokenId, _action);
} else if (_action.isRequestDetails()) {
_handleRequestDetails(_origin, _sender, _tokenId);
_handleRequestDetails(_origin, _tokenId);
} else {
require(false, "!valid action");
}
@ -130,8 +130,6 @@ contract BridgeRouter is Version0, Router, TokenRegistry {
) external {
require(_amount > 0, "!amnt");
require(_recipient != bytes32(0), "!recip");
// get remote BridgeRouter address; revert if not found
bytes32 _remote = _mustHaveRemote(_destination);
// remove tokens from circulation on this chain
IERC20 _bridgeToken = IERC20(_token);
if (_isLocalOrigin(_bridgeToken)) {
@ -146,9 +144,8 @@ contract BridgeRouter is Version0, Router, TokenRegistry {
// format Transfer Tokens action
bytes29 _action = BridgeMessage.formatTransfer(_recipient, _amount);
// send message to remote chain via Abacus
Outbox(xAppConnectionManager.outbox()).dispatch(
_dispatchToRemoteRouter(
_destination,
_remote,
BridgeMessage.formatMessage(_formatTokenId(_token), _action)
);
// emit Send event to record token sender
@ -336,14 +333,11 @@ contract BridgeRouter is Version0, Router, TokenRegistry {
* @dev The origin and remote are pre-checked by the handle function
* `onlyRemoteRouter` modifier and can be used without additional check
* @param _messageOrigin The domain from which the message arrived
* @param _messageRemoteRouter The remote router that sent the message
* @param _tokenId The token ID
*/
function _handleRequestDetails(
uint32 _messageOrigin,
bytes32 _messageRemoteRouter,
bytes29 _tokenId
) internal {
function _handleRequestDetails(uint32 _messageOrigin, bytes29 _tokenId)
internal
{
// get token & ensure is of local origin
address _token = _tokenId.evmId();
require(_isLocalOrigin(_token), "!local origin");
@ -355,9 +349,8 @@ contract BridgeRouter is Version0, Router, TokenRegistry {
_bridgeToken.decimals()
);
// send message to remote chain via Abacus
Outbox(xAppConnectionManager.outbox()).dispatch(
_dispatchToRemoteRouter(
_messageOrigin,
_messageRemoteRouter,
BridgeMessage.formatMessage(_tokenId, _updateDetailsAction)
);
}
@ -380,22 +373,20 @@ contract BridgeRouter is Version0, Router, TokenRegistry {
// ============ Internal: Request Details ============
/**
* @notice Handles an incoming Details message.
* @notice Request updated token metadata from another chain
* @param _tokenId The token ID
*/
function _requestDetails(bytes29 _tokenId) internal {
uint32 _destination = _tokenId.domain();
// get remote BridgeRouter address; revert if not found
bytes32 _remote = remotes[_destination];
bytes32 _remote = routers[_destination];
if (_remote == bytes32(0)) {
return;
}
// format Request Details message
bytes29 _action = BridgeMessage.formatRequestDetails();
// send message to remote chain via Abacus
Outbox(xAppConnectionManager.outbox()).dispatch(
_dispatchToRemoteRouter(
_destination,
_remote,
BridgeMessage.formatMessage(_tokenId, _action)
);
}

@ -12,14 +12,16 @@ library GovernanceMessage {
uint256 private constant CALL_PREFIX_LEN = 64;
uint256 private constant MSG_PREFIX_NUM_ITEMS = 2;
uint256 private constant MSG_PREFIX_LEN = 2;
uint256 private constant GOV_ACTION_LEN = 37;
uint256 private constant ENROLL_REMOTE_ROUTER_LEN = 37;
uint256 private constant SET_ADDRESS_LEN = 33;
enum Types {
Invalid, // 0
Call, // 1
TransferGovernor, // 2
SetRouter, // 3
Data // 4
SetGovernor, // 2
EnrollRemoteRouter, // 3
Data, // 4
SetXAppConnectionManager // 5
}
struct Call {
@ -66,28 +68,45 @@ library GovernanceMessage {
_msg = TypedMemView.join(_encodedCalls);
}
function formatTransferGovernor(uint32 _domain, bytes32 _governor)
function formatSetGovernor(bytes32 _governor)
internal
view
returns (bytes memory _msg)
{
_msg = TypedMemView.clone(
mustBeTransferGovernor(
mustBeSetGovernor(
abi.encodePacked(Types.SetGovernor, _governor).ref(0)
)
);
}
function formatEnrollRemoteRouter(uint32 _domain, bytes32 _router)
internal
view
returns (bytes memory _msg)
{
_msg = TypedMemView.clone(
mustBeEnrollRemoteRouter(
abi
.encodePacked(Types.TransferGovernor, _domain, _governor)
.encodePacked(Types.EnrollRemoteRouter, _domain, _router)
.ref(0)
)
);
}
function formatSetRouter(uint32 _domain, bytes32 _router)
function formatSetXAppConnectionManager(bytes32 _xAppConnectionManager)
internal
view
returns (bytes memory _msg)
{
_msg = TypedMemView.clone(
mustBeSetRouter(
abi.encodePacked(Types.SetRouter, _domain, _router).ref(0)
mustBeSetXAppConnectionManager(
abi
.encodePacked(
Types.SetXAppConnectionManager,
_xAppConnectionManager
)
.ref(0)
)
);
}
@ -154,19 +173,28 @@ library GovernanceMessage {
return uint256(_view.index(32, 32));
}
// Types.TransferGovernor & Types.EnrollRemote
// Types.EnrollRemoteRouter
function domain(bytes29 _view) internal pure returns (uint32) {
return uint32(_view.indexUint(1, 4));
}
// Types.EnrollRemote
// Types.EnrollRemoteRouter
function router(bytes29 _view) internal pure returns (bytes32) {
return _view.index(5, 32);
}
// Types.TransferGovernor
// Types.SetGovernor
function governor(bytes29 _view) internal pure returns (bytes32) {
return _view.index(5, 32);
return _view.index(1, 32);
}
// Types.SetXAppConnectionManager
function xAppConnectionManager(bytes29 _view)
internal
pure
returns (bytes32)
{
return _view.index(1, 32);
}
/*
@ -199,81 +227,129 @@ library GovernanceMessage {
return TypedMemView.nullView();
}
function mustBeCalls(bytes29 _view) internal pure returns (bytes29) {
function mustBeCall(bytes29 _view) internal pure returns (bytes29) {
return tryAsCall(_view).assertValid();
}
/*
Message Type: TRANSFER GOVERNOR
struct TransferGovernor {
Message Type: SET GOVERNOR
struct SetGovernor {
identifier, // message ID -- 1 byte
domain, // domain of new governor -- 4 bytes
addr // address of new governor -- 32 bytes
}
*/
function isValidTransferGovernor(bytes29 _view)
function isValidSetGovernor(bytes29 _view) internal pure returns (bool) {
return
identifier(_view) == uint8(Types.SetGovernor) &&
_view.len() == SET_ADDRESS_LEN;
}
function isSetGovernor(bytes29 _view) internal pure returns (bool) {
return
isValidSetGovernor(_view) &&
messageType(_view) == Types.SetGovernor;
}
function tryAsSetGovernor(bytes29 _view) internal pure returns (bytes29) {
if (isValidSetGovernor(_view)) {
return _view.castTo(uint40(Types.SetGovernor));
}
return TypedMemView.nullView();
}
function mustBeSetGovernor(bytes29 _view) internal pure returns (bytes29) {
return tryAsSetGovernor(_view).assertValid();
}
/*
Message Type: ENROLL REMOTE ROUTER
struct EnrollRemoteRouter {
identifier, // message ID -- 1 byte
domain, // domain of new router -- 4 bytes
addr // address of new router -- 32 bytes
}
*/
function isValidEnrollRemoteRouter(bytes29 _view)
internal
pure
returns (bool)
{
return
identifier(_view) == uint8(Types.TransferGovernor) &&
_view.len() == GOV_ACTION_LEN;
identifier(_view) == uint8(Types.EnrollRemoteRouter) &&
_view.len() == ENROLL_REMOTE_ROUTER_LEN;
}
function isTransferGovernor(bytes29 _view) internal pure returns (bool) {
function isEnrollRemoteRouter(bytes29 _view) internal pure returns (bool) {
return
isValidTransferGovernor(_view) &&
messageType(_view) == Types.TransferGovernor;
isValidEnrollRemoteRouter(_view) &&
messageType(_view) == Types.EnrollRemoteRouter;
}
function tryAsTransferGovernor(bytes29 _view)
function tryAsEnrollRemoteRouter(bytes29 _view)
internal
pure
returns (bytes29)
{
if (isValidTransferGovernor(_view)) {
return _view.castTo(uint40(Types.TransferGovernor));
if (isValidEnrollRemoteRouter(_view)) {
return _view.castTo(uint40(Types.EnrollRemoteRouter));
}
return TypedMemView.nullView();
}
function mustBeTransferGovernor(bytes29 _view)
function mustBeEnrollRemoteRouter(bytes29 _view)
internal
pure
returns (bytes29)
{
return tryAsTransferGovernor(_view).assertValid();
return tryAsEnrollRemoteRouter(_view).assertValid();
}
/*
Message Type: ENROLL ROUTER
struct SetRouter {
Message Type: SET XAPPCONNECTIONMANAGER
struct SetXAppConnectionManager {
identifier, // message ID -- 1 byte
domain, // domain of new router -- 4 bytes
addr // address of new router -- 32 bytes
addr // address of new xAppConnectionManager -- 32 bytes
}
*/
function isValidSetRouter(bytes29 _view) internal pure returns (bool) {
function isValidSetXAppConnectionManager(bytes29 _view)
internal
pure
returns (bool)
{
return
identifier(_view) == uint8(Types.SetRouter) &&
_view.len() == GOV_ACTION_LEN;
identifier(_view) == uint8(Types.SetXAppConnectionManager) &&
_view.len() == SET_ADDRESS_LEN;
}
function isSetRouter(bytes29 _view) internal pure returns (bool) {
return isValidSetRouter(_view) && messageType(_view) == Types.SetRouter;
function isSetXAppConnectionManager(bytes29 _view)
internal
pure
returns (bool)
{
return
isValidSetXAppConnectionManager(_view) &&
messageType(_view) == Types.SetXAppConnectionManager;
}
function tryAsSetRouter(bytes29 _view) internal pure returns (bytes29) {
if (isValidSetRouter(_view)) {
return _view.castTo(uint40(Types.SetRouter));
function tryAsSetXAppConnectionManager(bytes29 _view)
internal
pure
returns (bytes29)
{
if (isValidSetXAppConnectionManager(_view)) {
return _view.castTo(uint40(Types.SetXAppConnectionManager));
}
return TypedMemView.nullView();
}
function mustBeSetRouter(bytes29 _view) internal pure returns (bytes29) {
return tryAsSetRouter(_view).assertValid();
function mustBeSetXAppConnectionManager(bytes29 _view)
internal
pure
returns (bytes29)
{
return tryAsSetXAppConnectionManager(_view).assertValid();
}
}

@ -0,0 +1,385 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
pragma experimental ABIEncoderV2;
// ============ Internal Imports ============
import {Router} from "../Router.sol";
import {GovernanceMessage} from "./GovernanceMessage.sol";
// ============ External Imports ============
import {Version0} from "@abacus-network/abacus-sol/contracts/Version0.sol";
import {TypeCasts} from "@abacus-network/abacus-sol/contracts/XAppConnectionManager.sol";
import {Initializable} from "@openzeppelin/contracts/proxy/Initializable.sol";
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/**
* @dev GovernanceRouter has two modes of operation, normal and recovery.
* During normal mode, `owner()` returns the `governor`, giving it permission
* to call `onlyOwner` functions.
* During recovery mode, `owner()` returns the `_owner`, giving it permission
* to call `onlyOwner` functions.
*/
contract GovernanceRouter is Version0, Router {
// ============ Libraries ============
using SafeMath for uint256;
using TypedMemView for bytes;
using TypedMemView for bytes29;
using GovernanceMessage for bytes29;
// ============ Immutables ============
// number of seconds before recovery can be activated
uint256 public immutable recoveryTimelock;
// ============ Public Storage ============
// timestamp when recovery timelock expires; 0 if timelock has not been initiated
uint256 public recoveryActiveAt;
// the local entity empowered to call governance functions during normal
// operation, typically set to 0x0 on all chains but one
address public governor;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[48] private __GAP;
// ============ Events ============
/**
* @notice Emitted when the Governor role is set
* @param governor the address of the new Governor
*/
event SetGovernor(address indexed governor);
/**
* @notice Emitted when recovery state is initiated by the Owner
* @param owner the address of the current owner who initiated the transition
* @param recoveryActiveAt the block at which recovery state will be active
*/
event InitiateRecovery(address indexed owner, uint256 recoveryActiveAt);
/**
* @notice Emitted when recovery state is exited by the Owner
* @param owner the address of the current Owner who initiated the transition
*/
event ExitRecovery(address owner);
// ============ Modifiers ============
modifier typeAssert(bytes29 _view, GovernanceMessage.Types _type) {
_view.assertType(uint40(_type));
_;
}
modifier onlyInRecovery() {
require(inRecovery(), "!recovery");
_;
}
modifier onlyNotInRecovery() {
require(!inRecovery(), "recovery");
_;
}
modifier onlyGovernor() {
require(msg.sender == governor, "!governor");
_;
}
modifier onlyRecoveryManager() {
require(msg.sender == recoveryManager(), "!recoveryManager");
_;
}
// ============ Constructor ============
constructor(uint256 _recoveryTimelock) {
recoveryTimelock = _recoveryTimelock;
}
// ============ Initializer ============
function initialize(address _xAppConnectionManager) public initializer {
__XAppConnectionClient_initialize(_xAppConnectionManager);
governor = msg.sender;
}
// ============ External Functions ============
/**
* @notice Handle Abacus messages
* For all non-Governor chains to handle messages
* 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,
bytes memory _message
) external override onlyInbox onlyRemoteRouter(_origin, _sender) {
bytes29 _msg = _message.ref(0);
if (_msg.isValidCall()) {
_handleCall(_msg.tryAsCall());
} else if (_msg.isValidSetGovernor()) {
_handleSetGovernor(_msg.tryAsSetGovernor());
} else if (_msg.isValidEnrollRemoteRouter()) {
_handleEnrollRemoteRouter(_msg.tryAsEnrollRemoteRouter());
} else if (_msg.isValidSetXAppConnectionManager()) {
_handleSetXAppConnectionManager(
_msg.tryAsSetXAppConnectionManager()
);
} else {
require(false, "!valid message type");
}
}
// ============ External Local Functions ============
/**
* @notice Make local calls.
* @param _calls The calls
*/
function call(GovernanceMessage.Call[] calldata _calls) external onlyOwner {
for (uint256 i = 0; i < _calls.length; i++) {
_makeCall(_calls[i]);
}
}
/**
* @notice Sets the governor of the router.
* @param _governor The address of the new governor
*/
function setGovernor(address _governor) external onlyOwner {
_setGovernor(_governor);
}
/**
* @notice Initiate the recovery timelock
* @dev callable by the recovery manager iff not in recovery
*/
function initiateRecoveryTimelock()
external
onlyNotInRecovery
onlyRecoveryManager
{
require(recoveryActiveAt == 0, "recovery already initiated");
// set the time that recovery will be active
recoveryActiveAt = block.timestamp.add(recoveryTimelock);
emit InitiateRecovery(recoveryManager(), recoveryActiveAt);
}
/**
* @notice Exit recovery mode
* @dev callable by the recovery manager iff in recovery
*/
function exitRecovery() external onlyInRecovery onlyRecoveryManager {
delete recoveryActiveAt;
emit ExitRecovery(recoveryManager());
}
// ============ External Remote Functions ============
/**
* @notice Dispatch calls on a remote chain via the remote GovernanceRouter
* @param _destination The domain of the remote chain
* @param _calls The calls
*/
function callRemote(
uint32 _destination,
GovernanceMessage.Call[] calldata _calls
) external onlyGovernor onlyNotInRecovery {
bytes memory _msg = GovernanceMessage.formatCalls(_calls);
_dispatchToRemoteRouter(_destination, _msg);
}
/**
* @notice Enroll a remote router on a remote router.
* @param _destination The domain of the enroller
* @param _domain The domain of the enrollee
* @param _router The address of the enrollee
*/
function enrollRemoteRouterRemote(
uint32 _destination,
uint32 _domain,
bytes32 _router
) external onlyGovernor onlyNotInRecovery {
bytes memory _msg = GovernanceMessage.formatEnrollRemoteRouter(
_domain,
_router
);
_dispatchToRemoteRouter(_destination, _msg);
}
/**
* @notice Sets the xAppConnectionManager of a remote router.
* @param _destination The domain of router on which to set the xAppConnectionManager
* @param _xAppConnectionManager The address of the xAppConnectionManager contract
*/
function setXAppConnectionManagerRemote(
uint32 _destination,
address _xAppConnectionManager
) external onlyGovernor onlyNotInRecovery {
bytes memory _msg = GovernanceMessage.formatSetXAppConnectionManager(
TypeCasts.addressToBytes32(_xAppConnectionManager)
);
_dispatchToRemoteRouter(_destination, _msg);
}
/**
* @notice Sets the governor of a remote router.
* @param _destination The domain of router on which to set the governor
* @param _governor The address of the new governor
*/
function setGovernorRemote(uint32 _destination, address _governor)
external
onlyGovernor
onlyNotInRecovery
{
bytes memory _msg = GovernanceMessage.formatSetGovernor(
TypeCasts.addressToBytes32(_governor)
);
_dispatchToRemoteRouter(_destination, _msg);
}
// ============ Public Functions ============
/**
* @notice Transfers the recovery manager to a new address.
* @dev Callable by the governor when not in recovery mode or the
* recoveryManager at any time.
* @param _recoveryManager The address of the new recovery manager
*/
function transferOwnership(address _recoveryManager)
public
virtual
override
{
// If we are not in recovery, temporarily enter recovery so that the
// recoveryManager can call transferOwnership.
if (msg.sender == recoveryManager() && !inRecovery()) {
uint256 _recoveryActiveAt = recoveryActiveAt;
recoveryActiveAt = 1;
OwnableUpgradeable.transferOwnership(_recoveryManager);
recoveryActiveAt = _recoveryActiveAt;
} else {
OwnableUpgradeable.transferOwnership(_recoveryManager);
}
}
/**
* @notice Returns the address of the current owner.
* @dev When not in recovery mode, the governor owns the contract.
*/
function owner() public view virtual override returns (address) {
return inRecovery() ? recoveryManager() : governor;
}
/**
* @notice Returns the address of the recovery manager.
* @dev Exposing via this funciton is necessary because we overload
* `owner()` in order to make `onlyOwner()` work as intended, and because
* `OwnableUpgradeable` does not expose the private `_owner`.
*/
function recoveryManager() public view returns (address) {
return OwnableUpgradeable.owner();
}
/**
* @notice Check if the contract is in recovery mode currently
* @return TRUE iff the contract is actively in recovery mode currently
*/
function inRecovery() public view returns (bool) {
uint256 _recoveryActiveAt = recoveryActiveAt;
bool _recoveryInitiated = _recoveryActiveAt != 0;
bool _recoveryActive = _recoveryActiveAt <= block.timestamp;
return _recoveryInitiated && _recoveryActive;
}
// ============ Internal Functions ============
/**
* @notice Handle message dispatching calls locally
* @param _msg The message
*/
function _handleCall(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.Call)
{
GovernanceMessage.Call[] memory _calls = _msg.getCalls();
for (uint256 i = 0; i < _calls.length; i++) {
_makeCall(_calls[i]);
}
}
/**
* @notice Handle message transferring governorship to a new Governor
* @param _msg The message
*/
function _handleSetGovernor(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.SetGovernor)
{
address _governor = TypeCasts.bytes32ToAddress(_msg.governor());
_setGovernor(_governor);
}
/**
* @notice Handle message setting the router address for a given domain
* @param _msg The message
*/
function _handleEnrollRemoteRouter(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.EnrollRemoteRouter)
{
uint32 _domain = _msg.domain();
bytes32 _router = _msg.router();
_enrollRemoteRouter(_domain, _router);
}
/**
* @notice Handle message setting the xAppConnectionManager address
* @param _msg The message
*/
function _handleSetXAppConnectionManager(bytes29 _msg)
internal
typeAssert(_msg, GovernanceMessage.Types.SetXAppConnectionManager)
{
address _xAppConnectionManager = TypeCasts.bytes32ToAddress(
_msg.xAppConnectionManager()
);
_setXAppConnectionManager(_xAppConnectionManager);
}
/**
* @notice Call local contract.
* @param _call The call
* @return _ret
*/
function _makeCall(GovernanceMessage.Call memory _call)
internal
returns (bytes memory _ret)
{
address _to = TypeCasts.bytes32ToAddress(_call.to);
// attempt to dispatch using low-level call
bool _success;
(_success, _ret) = _to.call(_call.data);
// revert if the call failed
require(_success, "call failed");
}
/**
* @notice Set the governor.
* @param _governor The address of the new governor
*/
function _setGovernor(address _governor) internal {
governor = _governor;
emit SetGovernor(_governor);
}
}

@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
contract TestSet {
uint256 private x;
function set(uint256 _x) external {
x = _x;
}
function get() external view returns (uint256) {
return x;
}
}

@ -146,7 +146,9 @@ contract PingPongRouter is Router {
uint256 _count
) internal {
// get the xApp Router at the destinationDomain
bytes32 _remoteRouterAddress = _mustHaveRemote(_destinationDomain);
bytes32 _remoteRouterAddress = _mustHaveRemoteRouter(
_destinationDomain
);
// format the ping message
bytes memory _message = _isPing
? PingPongMessage.formatPing(_match, _count)

@ -92,7 +92,9 @@ contract RouterTemplate is Router {
external
{
// get the xApp Router at the destinationDomain
bytes32 _remoteRouterAddress = _mustHaveRemote(_destinationDomain);
bytes32 _remoteRouterAddress = _mustHaveRemoteRouter(
_destinationDomain
);
// encode a message to send to the remote xApp Router
bytes memory _outboundMessage = Message.formatTypeA(_number);
// send the message to the xApp Router

@ -3,7 +3,6 @@ import "@typechain/hardhat";
import "@nomiclabs/hardhat-etherscan";
import "@nomiclabs/hardhat-waffle";
import "hardhat-gas-reporter";
import './test/lib/index';
import { task } from "hardhat/config";
import { verifyLatestBridgeDeploy } from "../../typescript/abacus-deploy/src/verification/verifyLatestDeploy";

@ -4,7 +4,7 @@ import { Wallet } from 'ethers';
import { Signer } from '@abacus-network/abacus-sol/test/lib/types';
import { permitDigest } from './lib/permit';
import { BridgeToken__factory, BridgeToken } from '../typechain';
import { BridgeToken__factory, BridgeToken } from '../../typechain';
const VALUE = 100;

@ -1,13 +1,12 @@
import { ethers, bridge, abacus } from 'hardhat';
import { Signer } from 'ethers';
import { ethers } from 'hardhat';
import { BytesLike, Signer } from 'ethers';
import { expect } from 'chai';
import * as types from './lib/types';
import { serializeMessage } from './lib/utils';
import { BridgeDeployment } from './lib/BridgeDeployment';
import { AbacusDeployment } from '@abacus-network/abacus-sol/test/lib/AbacusDeployment';
import { toBytes32 } from '@abacus-network/abacus-sol/test/lib/utils';
import { AbacusDeployment, utils } from '@abacus-network/abacus-sol/test';
const { BridgeMessageTypes } = bridge;
const localDomain = 1000;
const remoteDomain = 2000;
const domains = [localDomain, remoteDomain];
@ -24,17 +23,17 @@ describe('EthHelper', async () => {
let recipientAddress: string;
let recipientId: string;
let transferToSelfMessage: string;
let transferMessage: string;
let transferToSelfMessage: BytesLike;
let transferMessage: BytesLike;
const value = 1;
before(async () => {
[deployer, recipient] = await ethers.getSigners();
deployerAddress = await deployer.getAddress();
deployerId = toBytes32(deployerAddress).toLowerCase();
deployerId = utils.toBytes32(deployerAddress).toLowerCase();
recipientAddress = await recipient.getAddress();
recipientId = toBytes32(recipientAddress).toLowerCase();
recipientId = utils.toBytes32(recipientAddress).toLowerCase();
abacusDeployment = await AbacusDeployment.fromDomains(domains, deployer);
bridgeDeployment = await BridgeDeployment.fromAbacusDeployment(
abacusDeployment,
@ -43,27 +42,27 @@ describe('EthHelper', async () => {
const tokenId: types.TokenIdentifier = {
domain: localDomain,
id: toBytes32(bridgeDeployment.weth(localDomain).address),
id: utils.toBytes32(bridgeDeployment.weth(localDomain).address),
};
const transferToSelfMessageObj: types.Message = {
tokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: value,
},
};
transferToSelfMessage = bridge.serializeMessage(transferToSelfMessageObj);
transferToSelfMessage = serializeMessage(transferToSelfMessageObj);
const transferMessageObj: types.Message = {
tokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: recipientId,
amount: value,
},
};
transferMessage = bridge.serializeMessage(transferMessageObj);
transferMessage = serializeMessage(transferMessageObj);
});
it('send function', async () => {

@ -1,18 +1,17 @@
import { expect } from 'chai';
import { ethers, bridge, abacus } from 'hardhat';
import { ethers } from 'hardhat';
import { BigNumber, BytesLike } from 'ethers';
import {
utils,
types as core,
AbacusDeployment,
} from '@abacus-network/abacus-sol/test';
import * as types from './lib/types';
import { serializeMessage } from './lib/utils';
import { BridgeDeployment } from './lib/BridgeDeployment';
import { AbacusDeployment } from '@abacus-network/abacus-sol/test/lib/AbacusDeployment';
import { Signer } from '@abacus-network/abacus-sol/test/lib/types';
import {
stringToBytes32,
toBytes32,
} from '@abacus-network/abacus-sol/test/lib/utils';
import { BridgeToken, BridgeToken__factory, IERC20 } from '../typechain';
import { BridgeToken, BridgeToken__factory, IERC20 } from '../../typechain';
const { BridgeMessageTypes } = bridge;
const localDomain = 1000;
const remoteDomain = 2000;
const domains = [localDomain, remoteDomain];
@ -25,7 +24,7 @@ const testTokenId = {
describe('BridgeRouter', async () => {
let abacusDeployment: AbacusDeployment;
let bridgeDeployment: BridgeDeployment;
let deployer: Signer;
let deployer: core.Signer;
let deployerAddress: string;
let deployerId: BytesLike;
@ -38,7 +37,7 @@ describe('BridgeRouter', async () => {
// populate deployer signer
[deployer] = await ethers.getSigners();
deployerAddress = await deployer.getAddress();
deployerId = toBytes32(await deployer.getAddress()).toLowerCase();
deployerId = utils.toBytes32(await deployer.getAddress()).toLowerCase();
abacusDeployment = await AbacusDeployment.fromDomains(domains, deployer);
// Enroll ourselves as a inbox so we can send messages directly to the
// local router.
@ -81,12 +80,12 @@ describe('BridgeRouter', async () => {
const transferMessageObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: TOKEN_VALUE,
},
};
transferMessage = bridge.serializeMessage(transferMessageObj);
transferMessage = serializeMessage(transferMessageObj);
// Send a message to the local BridgeRouter triggering a BridgeToken
// deployment.
@ -128,15 +127,15 @@ describe('BridgeRouter', async () => {
});
it('errors when missing a remote router', async () => {
expect(
await expect(
bridgeDeployment
.router(localDomain)
.send(repr.address, TOKEN_VALUE * 10, 121234, deployerId),
).to.be.revertedWith('!remote');
.send(repr.address, TOKEN_VALUE, 121234, deployerId),
).to.be.revertedWith('!router');
});
it('errors on send when recipient is the 0 address', async () => {
expect(
await expect(
bridgeDeployment
.router(localDomain)
.send(
@ -161,7 +160,7 @@ describe('BridgeRouter', async () => {
.router(localDomain)
.send(repr.address, 1, 3000, deployerId);
await expect(unknownRemote).to.be.revertedWith('!remote');
await expect(unknownRemote).to.be.revertedWith('!router');
});
it('burns tokens on outbound message', async () => {
@ -190,7 +189,7 @@ describe('BridgeRouter', async () => {
});
describe('locally-originating asset roundtrip', async () => {
let transferMessage: string;
let transferMessage: BytesLike;
let localToken: BridgeToken;
beforeEach(async () => {
@ -202,15 +201,15 @@ describe('BridgeRouter', async () => {
const transferMessageObj: types.Message = {
tokenId: {
domain: localDomain,
id: toBytes32(localToken.address),
id: utils.toBytes32(localToken.address),
},
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: TOKEN_VALUE,
},
};
transferMessage = bridge.serializeMessage(transferMessageObj);
transferMessage = serializeMessage(transferMessageObj);
expect(await localToken.balanceOf(deployerAddress)).to.equal(
BigNumber.from(TOKEN_VALUE),
@ -320,12 +319,12 @@ describe('BridgeRouter', async () => {
const transferMessageObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: TOKEN_VALUE,
},
};
const transferMessage = bridge.serializeMessage(transferMessageObj);
const transferMessage = serializeMessage(transferMessageObj);
expect(
bridgeDeployment.router(localDomain).preFill(transferMessage),
@ -333,38 +332,38 @@ describe('BridgeRouter', async () => {
});
describe('remotely-originating asset', async () => {
let setupMessage: string;
let setupMessage: BytesLike;
let repr: IERC20;
let recipient: string;
let recipientId: string;
let transferMessage: string;
let transferMessage: BytesLike;
beforeEach(async () => {
// generate actions
recipient = `0x${'00'.repeat(19)}ff`;
recipientId = toBytes32(recipient);
recipientId = utils.toBytes32(recipient);
// transfer message
const transferMessageObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: recipientId,
amount: TOKEN_VALUE,
},
};
transferMessage = bridge.serializeMessage(transferMessageObj);
transferMessage = serializeMessage(transferMessageObj);
// setup message
const setupMessageObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: TOKEN_VALUE,
},
};
setupMessage = bridge.serializeMessage(setupMessageObj);
setupMessage = serializeMessage(setupMessageObj);
// perform setup
const setupTx = await bridgeDeployment
@ -423,7 +422,7 @@ describe('BridgeRouter', async () => {
let localToken: BridgeToken;
let recipient: string;
let recipientId: string;
let transferMessage: string;
let transferMessage: BytesLike;
beforeEach(async () => {
localToken = await new BridgeToken__factory(deployer).deploy();
@ -449,20 +448,20 @@ describe('BridgeRouter', async () => {
// generate transfer action
recipient = `0x${'00'.repeat(19)}ff`;
recipientId = toBytes32(recipient);
recipientId = utils.toBytes32(recipient);
const transferMessageObj: types.Message = {
tokenId: {
domain: localDomain,
id: toBytes32(localToken.address),
id: utils.toBytes32(localToken.address),
},
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: recipientId,
amount: TOKEN_VALUE,
},
};
transferMessage = bridge.serializeMessage(transferMessageObj);
transferMessage = serializeMessage(transferMessageObj);
});
it('transfers tokens on prefill', async () => {
@ -498,10 +497,10 @@ describe('BridgeRouter', async () => {
describe('details message', async () => {
let localToken: BridgeToken;
let requestMessage: string;
let outgoingDetails: string;
let incomingDetails: string;
let transferMessage: string;
let requestMessage: BytesLike;
let outgoingDetails: BytesLike;
let incomingDetails: BytesLike;
let transferMessage: BytesLike;
let repr: BridgeToken;
const TEST_NAME = 'TEST TOKEN';
@ -516,49 +515,49 @@ describe('BridgeRouter', async () => {
const requestMessageObj: types.Message = {
tokenId: {
domain: localDomain,
id: toBytes32(localToken.address),
id: utils.toBytes32(localToken.address),
},
action: {
type: BridgeMessageTypes.REQUEST_DETAILS,
type: types.BridgeMessageTypes.REQUEST_DETAILS,
},
};
requestMessage = bridge.serializeMessage(requestMessageObj);
requestMessage = serializeMessage(requestMessageObj);
const outgoingDetailsObj: types.Message = {
tokenId: {
domain: localDomain,
id: toBytes32(localToken.address),
id: utils.toBytes32(localToken.address),
},
action: {
type: BridgeMessageTypes.DETAILS,
name: stringToBytes32(TEST_NAME),
symbol: stringToBytes32(TEST_SYMBOL),
type: types.BridgeMessageTypes.DETAILS,
name: utils.stringToBytes32(TEST_NAME),
symbol: utils.stringToBytes32(TEST_SYMBOL),
decimals: TEST_DECIMALS,
},
};
outgoingDetails = bridge.serializeMessage(outgoingDetailsObj);
outgoingDetails = serializeMessage(outgoingDetailsObj);
// generate transfer action
const transferMessageObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: TOKEN_VALUE,
},
};
transferMessage = bridge.serializeMessage(transferMessageObj);
transferMessage = serializeMessage(transferMessageObj);
const incomingDetailsObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.DETAILS,
name: stringToBytes32(TEST_NAME),
symbol: stringToBytes32(TEST_SYMBOL),
type: types.BridgeMessageTypes.DETAILS,
name: utils.stringToBytes32(TEST_NAME),
symbol: utils.stringToBytes32(TEST_SYMBOL),
decimals: TEST_DECIMALS,
},
};
incomingDetails = bridge.serializeMessage(incomingDetailsObj);
incomingDetails = serializeMessage(incomingDetailsObj);
// first send in a transfer to create the repr
await bridgeDeployment
@ -580,10 +579,10 @@ describe('BridgeRouter', async () => {
const requestDetailsObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.REQUEST_DETAILS,
type: types.BridgeMessageTypes.REQUEST_DETAILS,
},
};
const requestDetails = bridge.serializeMessage(requestDetailsObj);
const requestDetails = serializeMessage(requestDetailsObj);
await expect(requestTx).to.emit(
abacusDeployment.outbox(localDomain),
@ -606,13 +605,13 @@ describe('BridgeRouter', async () => {
const badRequestObj: types.Message = {
tokenId: {
domain: localDomain,
id: toBytes32(repr.address),
id: utils.toBytes32(repr.address),
},
action: {
type: BridgeMessageTypes.REQUEST_DETAILS,
type: types.BridgeMessageTypes.REQUEST_DETAILS,
},
};
const badRequest = bridge.serializeMessage(badRequestObj);
const badRequest = serializeMessage(badRequestObj);
let badRequestTx = bridgeDeployment
.router(localDomain)
@ -625,19 +624,19 @@ describe('BridgeRouter', async () => {
const badRequestObj: types.Message = {
tokenId: {
domain: localDomain,
id: toBytes32(localToken.address),
id: utils.toBytes32(localToken.address),
},
action: {
type: BridgeMessageTypes.REQUEST_DETAILS,
type: types.BridgeMessageTypes.REQUEST_DETAILS,
},
};
const badRequest = bridge.serializeMessage(badRequestObj);
const badRequest = serializeMessage(badRequestObj);
let badRequestTx = bridgeDeployment
.router(localDomain)
.handle(3812, deployerId, badRequest);
await expect(badRequestTx).to.be.revertedWith('!remote router');
await expect(badRequestTx).to.be.revertedWith('!router');
});
it('sets details during details message handling', async () => {
@ -657,7 +656,7 @@ describe('BridgeRouter', async () => {
});
describe('custom token representations', async () => {
let transferMessage: string;
let transferMessage: BytesLike;
let defaultRepr: BridgeToken;
let customRepr: BridgeToken;
const VALUE = `0xffffffffffffffff`;
@ -667,12 +666,12 @@ describe('BridgeRouter', async () => {
const transferMessageObj: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: VALUE,
},
};
transferMessage = bridge.serializeMessage(transferMessageObj);
transferMessage = serializeMessage(transferMessageObj);
// first send in a transfer to create the repr
await bridgeDeployment
@ -782,12 +781,12 @@ describe('BridgeRouter', async () => {
const smallTransfer: types.Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.TRANSFER,
type: types.BridgeMessageTypes.TRANSFER,
recipient: deployerId,
amount: TOKEN_VALUE,
},
};
const smallTransferMessage = bridge.serializeMessage(smallTransfer);
const smallTransferMessage = serializeMessage(smallTransfer);
const defaultSendTx = await bridgeDeployment
.router(localDomain)

@ -1,19 +1,24 @@
import { ethers, abacus, bridge } from 'hardhat';
import { ethers } from 'hardhat';
import { BytesLike } from 'ethers';
import { expect } from 'chai';
import { toBytes32 } from '@abacus-network/abacus-sol/test/lib/utils';
import { formatTokenId } from './lib/bridge';
import { utils } from '@abacus-network/abacus-sol/test';
import {
BridgeMessageTypes,
TokenIdentifier,
TransferAction,
DetailsAction,
Message,
RequestDetailsAction,
} from './lib/types';
import { TestBridgeMessage__factory, TestBridgeMessage } from '../typechain';
const { BridgeMessageTypes } = bridge;
import {
formatTokenId,
serializeMessage,
serializeDetailsAction,
serializeTransferAction,
serializeRequestDetailsAction,
} from './lib/utils';
import { TestBridgeMessage__factory, TestBridgeMessage } from '../../typechain';
const stringToBytes32 = (s: string): string => {
const str = Buffer.from(s.slice(0, 32), 'utf-8');
@ -40,7 +45,7 @@ describe('BridgeMessage', async () => {
before(async () => {
const [deployer] = await ethers.getSigners();
deployerAddress = await deployer.getAddress();
const deployerId = toBytes32(deployerAddress).toLowerCase();
const deployerId = utils.toBytes32(deployerAddress).toLowerCase();
const TOKEN_VALUE = 0xffff;
// tokenId
@ -59,12 +64,12 @@ describe('BridgeMessage', async () => {
recipient: deployerId,
amount: TOKEN_VALUE,
};
transferBytes = bridge.serializeTransferAction(transferAction);
transferBytes = serializeTransferAction(transferAction);
const transferMessage: Message = {
tokenId: testTokenId,
action: transferAction,
};
transferMessageBytes = bridge.serializeMessage(transferMessage);
transferMessageBytes = serializeMessage(transferMessage);
// details action/message
detailsAction = {
@ -73,26 +78,25 @@ describe('BridgeMessage', async () => {
symbol: stringToBytes32('TEST'),
decimals: 8,
};
detailsBytes = bridge.serializeDetailsAction(detailsAction);
detailsBytes = serializeDetailsAction(detailsAction);
const detailsMessage: Message = {
tokenId: testTokenId,
action: detailsAction,
};
detailsMessageBytes = bridge.serializeMessage(detailsMessage);
detailsMessageBytes = serializeMessage(detailsMessage);
// requestDetails action/message
const requestDetailsAction: RequestDetailsAction = {
type: BridgeMessageTypes.REQUEST_DETAILS,
};
requestDetailsBytes =
bridge.serializeRequestDetailsAction(requestDetailsAction);
requestDetailsBytes = serializeRequestDetailsAction(requestDetailsAction);
const requestDetailsMessage: Message = {
tokenId: testTokenId,
action: {
type: BridgeMessageTypes.REQUEST_DETAILS,
},
};
requestDetailsMessageBytes = bridge.serializeMessage(requestDetailsMessage);
requestDetailsMessageBytes = serializeMessage(requestDetailsMessage);
const [signer] = await ethers.getSigners();

@ -1,5 +1,5 @@
import { ethers } from 'hardhat';
import { TestEncoding__factory } from '../typechain';
import { TestEncoding__factory } from '../../typechain';
describe('Encoding', async () => {
it('encodes', async () => {

@ -15,7 +15,7 @@ import {
BridgeRouter__factory,
ETHHelper,
ETHHelper__factory,
} from '../../typechain';
} from '../../../typechain';
import {
UpgradeBeacon__factory,
UpgradeBeacon,

@ -1,5 +1,5 @@
import { BigNumber, BigNumberish, ethers } from 'ethers';
import { BridgeToken } from '../../typechain';
import { BridgeToken } from '../../../typechain';
const PERMIT_TYPEHASH = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(

@ -0,0 +1,49 @@
import { BytesLike } from 'ethers';
export enum BridgeMessageTypes {
INVALID = 0,
TOKEN_ID,
MESSAGE,
TRANSFER,
DETAILS,
REQUEST_DETAILS,
}
/********* TOKEN BRIDGE *********/
export type MessageLen = {
identifier: number;
tokenId: number;
transfer: number;
details: number;
requestDetails: number;
};
export type Action = DetailsAction | TransferAction | RequestDetailsAction;
export type Message = {
tokenId: TokenIdentifier;
action: Action;
};
export type TransferAction = {
type: BridgeMessageTypes.TRANSFER;
recipient: BytesLike;
amount: number | BytesLike;
};
export type DetailsAction = {
type: BridgeMessageTypes.DETAILS;
name: string;
symbol: string;
decimals: number;
};
export type RequestDetailsAction = {
type: BridgeMessageTypes.REQUEST_DETAILS;
};
export type TokenIdentifier = {
domain: string | number;
id: BytesLike;
};

@ -2,16 +2,6 @@ import { assert } from 'chai';
import { ethers } from 'ethers';
import * as types from './types';
import { TokenIdentifier } from './types';
export enum BridgeMessageTypes {
INVALID = 0,
TOKEN_ID,
MESSAGE,
TRANSFER,
DETAILS,
REQUEST_DETAILS,
}
const typeToByte = (type: number): string => `0x0${type}`;
@ -30,7 +20,7 @@ export function formatTransfer(
): ethers.BytesLike {
return ethers.utils.solidityPack(
['bytes1', 'bytes32', 'uint256'],
[BridgeMessageTypes.TRANSFER, to, amnt],
[types.BridgeMessageTypes.TRANSFER, to, amnt],
);
}
@ -42,7 +32,7 @@ export function formatDetails(
): ethers.BytesLike {
return ethers.utils.solidityPack(
['bytes1', 'bytes32', 'bytes32', 'uint8'],
[BridgeMessageTypes.DETAILS, name, symbol, decimals],
[types.BridgeMessageTypes.DETAILS, name, symbol, decimals],
);
}
@ -50,7 +40,7 @@ export function formatDetails(
export function formatRequestDetails(): ethers.BytesLike {
return ethers.utils.solidityPack(
['bytes1'],
[BridgeMessageTypes.REQUEST_DETAILS],
[types.BridgeMessageTypes.REQUEST_DETAILS],
);
}
@ -71,7 +61,7 @@ export function serializeTransferAction(
): ethers.BytesLike {
const { type, recipient, amount } = transferAction;
assert(type === BridgeMessageTypes.TRANSFER);
assert(type === types.BridgeMessageTypes.TRANSFER);
return formatTransfer(recipient, amount);
}
@ -80,29 +70,31 @@ export function serializeDetailsAction(
): ethers.BytesLike {
const { type, name, symbol, decimals } = detailsAction;
assert(type === BridgeMessageTypes.DETAILS);
assert(type === types.BridgeMessageTypes.DETAILS);
return formatDetails(name, symbol, decimals);
}
export function serializeRequestDetailsAction(
requestDetailsAction: types.RequestDetailsAction,
): ethers.BytesLike {
assert(requestDetailsAction.type === BridgeMessageTypes.REQUEST_DETAILS);
assert(
requestDetailsAction.type === types.BridgeMessageTypes.REQUEST_DETAILS,
);
return formatRequestDetails();
}
export function serializeAction(action: types.Action): ethers.BytesLike {
let actionBytes: ethers.BytesLike = [];
switch (action.type) {
case BridgeMessageTypes.TRANSFER: {
actionBytes = serializeTransferAction(action);
case types.BridgeMessageTypes.TRANSFER: {
actionBytes = serializeTransferAction(action as types.TransferAction);
break;
}
case BridgeMessageTypes.DETAILS: {
actionBytes = serializeDetailsAction(action);
case types.BridgeMessageTypes.DETAILS: {
actionBytes = serializeDetailsAction(action as types.DetailsAction);
break;
}
case BridgeMessageTypes.REQUEST_DETAILS: {
case types.BridgeMessageTypes.REQUEST_DETAILS: {
actionBytes = serializeRequestDetailsAction(action);
break;
}
@ -114,7 +106,9 @@ export function serializeAction(action: types.Action): ethers.BytesLike {
return actionBytes;
}
export function serializeTokenId(tokenId: TokenIdentifier): ethers.BytesLike {
export function serializeTokenId(
tokenId: types.TokenIdentifier,
): ethers.BytesLike {
if (typeof tokenId.domain !== 'number' || typeof tokenId.id !== 'string') {
throw new Error('!types');
}
@ -126,15 +120,3 @@ export function serializeMessage(message: types.Message): ethers.BytesLike {
const action = serializeAction(message.action);
return formatMessage(tokenId, action);
}
export const bridge: types.HardhatBridgeHelpers = {
BridgeMessageTypes,
typeToByte,
MESSAGE_LEN,
serializeTransferAction,
serializeDetailsAction,
serializeRequestDetailsAction,
serializeAction,
serializeTokenId,
serializeMessage,
};

@ -0,0 +1,482 @@
import { ethers } from 'hardhat';
import { expect } from 'chai';
import {
types,
utils,
core,
AbacusDeployment,
} from '@abacus-network/abacus-sol/test';
import {
formatSetGovernor,
formatCall,
increaseTimestampBy,
} from './lib/utils';
import { GovernanceDeployment } from './lib/GovernanceDeployment';
import {
TestSet,
TestSet__factory,
GovernanceRouter,
GovernanceRouter__factory,
} from '../../typechain';
const recoveryTimelock = 60 * 60 * 24 * 7;
const localDomain = 1000;
const remoteDomain = 2000;
const testDomain = 3000;
const domains = [localDomain, remoteDomain];
const ONLY_OWNER_REVERT_MESSAGE = 'Ownable: caller is not the owner';
describe('GovernanceRouter', async () => {
let governor: types.Signer,
recoveryManager: types.Signer,
router: GovernanceRouter,
remote: GovernanceRouter,
testSet: TestSet,
abacus: AbacusDeployment,
governance: GovernanceDeployment;
before(async () => {
[governor, recoveryManager] = await ethers.getSigners();
const testSetFactory = new TestSet__factory(governor);
testSet = await testSetFactory.deploy();
abacus = await AbacusDeployment.fromDomains(domains, governor);
});
beforeEach(async () => {
governance = await GovernanceDeployment.fromAbacusDeployment(
abacus,
governor,
recoveryManager,
);
router = governance.router(localDomain);
remote = governance.router(remoteDomain);
});
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 () => {
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 = core.formatMessage(
remoteDomain,
remote.address,
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 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 = core.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
let success = await inbox.callStatic.testProcess(fakeMessage);
expect(success).to.be.false;
});
describe('when not in recovery mode', async () => {
it('governor is the owner', async () => {
expect(await router.owner()).to.equal(governor.address);
});
it('governor can set local recovery manager', async () => {
expect(await router.recoveryManager()).to.equal(recoveryManager.address);
await router.transferOwnership(router.address);
expect(await router.recoveryManager()).to.equal(router.address);
expect(await router.recoveryActiveAt()).to.equal(0);
});
it('governor can make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
await router.call([call]);
expect(await testSet.get()).to.equal(value);
});
it('governor can set local governor', async () => {
expect(await router.governor()).to.equal(governor.address);
await router.setGovernor(ethers.constants.AddressZero);
expect(await router.governor()).to.equal(ethers.constants.AddressZero);
});
it('governor can set local xAppConnectionManager', async () => {
expect(await router.xAppConnectionManager()).to.equal(
abacus.connectionManager(localDomain).address,
);
await router.setXAppConnectionManager(ethers.constants.AddressZero);
expect(await router.xAppConnectionManager()).to.equal(
ethers.constants.AddressZero,
);
});
it('governor can enroll local remote router', async () => {
expect(await router.routers(testDomain)).to.equal(
ethers.constants.HashZero,
);
const newRouter = utils.addressToBytes32(router.address);
await router.enrollRemoteRouter(testDomain, newRouter);
expect(await router.routers(testDomain)).to.equal(newRouter);
});
it('governor can make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
await router.callRemote(domains[1], [call]);
await abacus.processMessages();
expect(await testSet.get()).to.equal(value);
});
it('governor can set remote governor', async () => {
const newGovernor = governor.address;
expect(await remote.governor()).to.not.equal(newGovernor);
await router.setGovernorRemote(remoteDomain, newGovernor);
await abacus.processMessages();
expect(await remote.governor()).to.equal(newGovernor);
});
it('governor can set remote xAppConnectionManager', async () => {
const newConnectionManager = ethers.constants.AddressZero;
expect(await remote.xAppConnectionManager()).to.not.equal(
newConnectionManager,
);
await router.setXAppConnectionManagerRemote(
remoteDomain,
newConnectionManager,
);
await abacus.processMessages();
expect(await remote.xAppConnectionManager()).to.equal(
newConnectionManager,
);
});
it('governor can enroll remote remote router', async () => {
expect(await remote.routers(testDomain)).to.equal(
ethers.constants.HashZero,
);
const newRouter = utils.addressToBytes32(router.address);
await router.enrollRemoteRouterRemote(
remoteDomain,
testDomain,
newRouter,
);
await abacus.processMessages();
expect(await remote.routers(testDomain)).to.equal(newRouter);
});
it('governor cannot initiate recovery', async () => {
await expect(router.initiateRecoveryTimelock()).to.be.revertedWith(
'!recoveryManager',
);
});
it('governor cannot exit recovery', async () => {
await expect(router.exitRecovery()).to.be.revertedWith('!recovery');
});
it('recoveryManager can set local recovery manager', async () => {
expect(await router.recoveryManager()).to.equal(recoveryManager.address);
await router.connect(recoveryManager).transferOwnership(router.address);
expect(await router.recoveryManager()).to.equal(router.address);
expect(await router.recoveryActiveAt()).to.equal(0);
});
it('recovery manager cannot make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
await expect(
router.connect(recoveryManager).call([call]),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('recovery manager cannot set local governor', async () => {
await expect(
router
.connect(recoveryManager)
.setGovernor(ethers.constants.AddressZero),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('recovery manager cannot set local xAppConnectionManager', async () => {
await expect(
router
.connect(recoveryManager)
.setXAppConnectionManager(router.address),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('recovery manager cannot enroll local remote router', async () => {
await expect(
router
.connect(recoveryManager)
.enrollRemoteRouter(
testDomain,
utils.addressToBytes32(router.address),
),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('recovery manager cannot make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
await expect(
router.connect(recoveryManager).callRemote(domains[1], [call]),
).to.be.revertedWith('!governor');
});
it('recovery manager cannot set remote governor', async () => {
await expect(
router
.connect(recoveryManager)
.setGovernorRemote(remoteDomain, router.address),
).to.be.revertedWith('!governor');
});
it('recovery manager cannot set remote xAppConnectionManager', async () => {
await expect(
router
.connect(recoveryManager)
.setXAppConnectionManagerRemote(remoteDomain, router.address),
).to.be.revertedWith('!governor');
});
it('recovery manager cannot enroll remote remote router', async () => {
await expect(
router
.connect(recoveryManager)
.enrollRemoteRouterRemote(
testDomain,
testDomain,
utils.addressToBytes32(router.address),
),
).to.be.revertedWith('!governor');
});
it('recovery manager can initiate recovery', async () => {
await expect(
router.connect(recoveryManager).initiateRecoveryTimelock(),
).to.emit(router, 'InitiateRecovery');
});
it('recovery manager cannot exit recovery', async () => {
await expect(
router.connect(recoveryManager).exitRecovery(),
).to.be.revertedWith('!recovery');
});
});
describe('when in recovery mode', async () => {
beforeEach(async () => {
router = router.connect(recoveryManager);
await router.initiateRecoveryTimelock();
expect(await router.inRecovery()).to.be.false;
await increaseTimestampBy(ethers.provider, recoveryTimelock);
expect(await router.inRecovery()).to.be.true;
});
it('recovery manager is the owner', async () => {
expect(await router.owner()).to.equal(recoveryManager.address);
});
it('recovery manager can set local recovery manager', async () => {
const recoveryActiveAt = await router.recoveryActiveAt();
expect(await router.recoveryManager()).to.equal(recoveryManager.address);
await router.transferOwnership(router.address);
expect(await router.recoveryManager()).to.equal(router.address);
expect(await router.recoveryActiveAt()).to.equal(recoveryActiveAt);
});
it('recovery manager can make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
await router.call([call]);
expect(await testSet.get()).to.equal(value);
});
it('recovery manager can set local governor', async () => {
expect(await router.governor()).to.equal(governor.address);
await router.setGovernor(ethers.constants.AddressZero);
expect(await router.governor()).to.equal(ethers.constants.AddressZero);
});
it('recovery manager can set local xAppConnectionManager', async () => {
expect(await router.xAppConnectionManager()).to.equal(
abacus.connectionManager(localDomain).address,
);
await router.setXAppConnectionManager(ethers.constants.AddressZero);
expect(await router.xAppConnectionManager()).to.equal(
ethers.constants.AddressZero,
);
});
it('recovery manager can enroll local remote router', async () => {
expect(await router.routers(testDomain)).to.equal(
ethers.constants.HashZero,
);
const newRouter = utils.addressToBytes32(router.address);
await router.enrollRemoteRouter(testDomain, newRouter);
expect(await router.routers(testDomain)).to.equal(newRouter);
});
it('recovery manager cannot make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
await expect(router.callRemote(domains[1], [call])).to.be.revertedWith(
'!governor',
);
});
it('recovery manager cannot set remote governor', async () => {
await expect(
router.setGovernorRemote(remoteDomain, router.address),
).to.be.revertedWith('!governor');
});
it('recovery manager cannot set remote xAppConnectionManager', async () => {
await expect(
router.setXAppConnectionManagerRemote(remoteDomain, router.address),
).to.be.revertedWith('!governor');
});
it('recovery manager cannot enroll remote remote router', async () => {
await expect(
router.enrollRemoteRouterRemote(
remoteDomain,
testDomain,
utils.addressToBytes32(router.address),
),
).to.be.revertedWith('!governor');
});
it('recovery manager cannot initiate recovery', async () => {
await expect(router.initiateRecoveryTimelock()).to.be.revertedWith(
'recovery',
);
});
it('recovery manager can exit recovery ', async () => {
await expect(router.exitRecovery()).to.emit(router, 'ExitRecovery');
expect(await router.inRecovery()).to.be.false;
});
it('governor cannot make local calls', async () => {
const value = 12;
const call = await formatCall(testSet, 'set', [value]);
await expect(router.connect(governor).call([call])).to.be.revertedWith(
ONLY_OWNER_REVERT_MESSAGE,
);
});
it('governor cannot set local governor', async () => {
await expect(
router.connect(governor).setGovernor(ethers.constants.AddressZero),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('governor cannot set local recovery manager', async () => {
await expect(
router.connect(governor).transferOwnership(router.address),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('governor cannot set local xAppConnectionManager', async () => {
await expect(
router.connect(governor).setXAppConnectionManager(router.address),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('governor cannot enroll local remote router', async () => {
await expect(
router
.connect(governor)
.enrollRemoteRouter(
testDomain,
utils.addressToBytes32(router.address),
),
).to.be.revertedWith(ONLY_OWNER_REVERT_MESSAGE);
});
it('governor cannot make remote calls', async () => {
const value = 13;
const call = await formatCall(testSet, 'set', [value]);
await expect(
router.connect(governor).callRemote(domains[1], [call]),
).to.be.revertedWith('recovery');
});
it('governor cannot set remote governor', async () => {
await expect(
router
.connect(governor)
.setGovernorRemote(remoteDomain, router.address),
).to.be.revertedWith('recovery');
});
it('governor cannot set remote xAppConnectionManager', async () => {
await expect(
router
.connect(governor)
.setXAppConnectionManagerRemote(remoteDomain, router.address),
).to.be.revertedWith('recovery');
});
it('governor cannot enroll remote remote router', async () => {
await expect(
router
.connect(governor)
.enrollRemoteRouterRemote(
remoteDomain,
testDomain,
utils.addressToBytes32(router.address),
),
).to.be.revertedWith('recovery');
});
it('governor cannot initiate recovery', async () => {
await expect(
router.connect(governor).initiateRecoveryTimelock(),
).to.be.revertedWith('recovery');
});
it('governor cannot exit recovery', async () => {
await expect(router.connect(governor).exitRecovery()).to.be.revertedWith(
'!recoveryManager',
);
});
});
});

@ -1,19 +1,18 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { assert } from 'chai';
import * as ethers from 'ethers';
import { AbacusDeployment } from './AbacusDeployment';
import { toBytes32 } from './utils';
import * as types from './types';
import { ethers } from 'ethers';
import {
utils,
types,
AbacusDeployment,
} from '@abacus-network/abacus-sol/test';
import {
TestGovernanceRouter__factory,
TestGovernanceRouter,
} from '../../typechain';
GovernanceRouter__factory,
GovernanceRouter,
} from '../../../typechain';
export interface GovernanceInstance {
domain: types.Domain;
router: TestGovernanceRouter;
router: GovernanceRouter;
}
const recoveryTimelock = 60 * 60 * 24 * 7;
@ -26,14 +25,16 @@ export class GovernanceDeployment {
static async fromAbacusDeployment(
abacus: AbacusDeployment,
signer: ethers.Signer,
governor: types.Signer,
recoveryManager: types.Signer,
) {
// Deploy routers.
const instances: Record<number, GovernanceInstance> = {};
for (const domain of abacus.domains) {
const instance = await GovernanceDeployment.deployInstance(
domain,
signer,
governor,
recoveryManager,
abacus.connectionManager(domain).address,
);
instances[domain] = instance;
@ -42,21 +43,17 @@ export class GovernanceDeployment {
// Make all routers aware of eachother.
for (const local of abacus.domains) {
for (const remote of abacus.domains) {
await instances[local].router.setRouterLocal(
await instances[local].router.enrollRemoteRouter(
remote,
toBytes32(instances[remote].router.address),
utils.toBytes32(instances[remote].router.address),
);
}
}
// Set the governor on all routers.
// Set the governor on one router, clear it on all other routers.
for (let i = 0; i < abacus.domains.length; i++) {
if (i > 0) {
await instances[abacus.domains[i]].router.transferGovernor(
abacus.domains[0],
instances[abacus.domains[0]].router.address,
);
}
const addr = i === 0 ? governor.address : ethers.constants.AddressZero;
await instances[abacus.domains[i]].router.setGovernor(addr);
}
return new GovernanceDeployment(abacus.domains, instances);
@ -64,22 +61,21 @@ export class GovernanceDeployment {
static async deployInstance(
domain: types.Domain,
signer: ethers.Signer,
governor: types.Signer,
recoveryManager: types.Signer,
connectionManagerAddress: types.Address,
): Promise<GovernanceInstance> {
const routerFactory = new TestGovernanceRouter__factory(signer);
const router = await routerFactory.deploy(domain, recoveryTimelock);
await router.initialize(
connectionManagerAddress,
await signer.getAddress(),
);
const routerFactory = new GovernanceRouter__factory(governor);
const router = await routerFactory.deploy(recoveryTimelock);
await router.initialize(connectionManagerAddress);
await router.transferOwnership(recoveryManager.address);
return {
domain,
router,
};
}
router(domain: types.Domain): TestGovernanceRouter {
router(domain: types.Domain): GovernanceRouter {
return this.instances[domain].router;
}
}

@ -0,0 +1,93 @@
import { ethers } from 'ethers';
import { types, utils } from '@abacus-network/abacus-sol/test';
export enum GovernanceMessage {
CALL = 1,
SETGOVERNOR = 2,
ENROLLREMOTEROUTER = 3,
SETXAPPCONNECTIONMANAGER = 5,
}
export function formatSetGovernor(address: types.Address): string {
return ethers.utils.solidityPack(
['bytes1', 'bytes32'],
[GovernanceMessage.SETGOVERNOR, utils.addressToBytes32(address)],
);
}
export function formatSetXAppConnectionManager(address: types.Address): string {
return ethers.utils.solidityPack(
['bytes1', 'bytes32'],
[
GovernanceMessage.SETXAPPCONNECTIONMANAGER,
utils.addressToBytes32(address),
],
);
}
export function formatEnrollRemoteRouter(
domain: types.Domain,
address: types.Address,
): string {
return ethers.utils.solidityPack(
['bytes1', 'uint32', 'bytes32'],
[
GovernanceMessage.ENROLLREMOTEROUTER,
domain,
utils.addressToBytes32(address),
],
);
}
export 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 = 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],
);
}
export async function formatCall(
destinationContract: ethers.Contract,
functionStr: string,
functionArgs: any[],
): Promise<types.CallData> {
// Set up data for call message
const callFunc = destinationContract.interface.getFunction(functionStr);
const callDataEncoded = destinationContract.interface.encodeFunctionData(
callFunc,
functionArgs,
);
return {
to: utils.addressToBytes32(destinationContract.address),
data: callDataEncoded,
};
}
export const increaseTimestampBy = async (
provider: ethers.providers.JsonRpcProvider,
increaseTime: number,
) => {
await provider.send('evm_increaseTime', [increaseTime]);
await provider.send('evm_mine', []);
};

@ -1,12 +0,0 @@
import '@nomiclabs/hardhat-waffle';
import { extendEnvironment } from 'hardhat/config';
import { abacus } from '@abacus-network/abacus-sol/test/lib/core';
import { bridge } from './bridge';
// HardhatRuntimeEnvironment
extendEnvironment((hre) => {
hre.abacus = abacus;
hre.bridge = bridge;
});

@ -1,61 +0,0 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { BytesLike, ethers } from 'ethers';
import { BridgeMessageTypes } from './bridge';
/********* HRE *********/
export interface HardhatBridgeHelpers {
BridgeMessageTypes: typeof BridgeMessageTypes;
typeToByte: Function;
MESSAGE_LEN: MessageLen;
serializeTransferAction: Function;
serializeDetailsAction: Function;
serializeRequestDetailsAction: Function;
serializeAction: Function;
serializeTokenId: Function;
serializeMessage: Function;
}
declare module 'hardhat/types/runtime' {
interface HardhatRuntimeEnvironment {
bridge: HardhatBridgeHelpers;
}
}
/********* TOKEN BRIDGE *********/
export type MessageLen = {
identifier: number;
tokenId: number;
transfer: number;
details: number;
requestDetails: number;
};
export type Action = DetailsAction | TransferAction | RequestDetailsAction;
export type Message = {
tokenId: TokenIdentifier;
action: Action;
};
export type TransferAction = {
type: BridgeMessageTypes.TRANSFER;
recipient: ethers.BytesLike;
amount: number | ethers.BytesLike;
};
export type DetailsAction = {
type: BridgeMessageTypes.DETAILS;
name: string;
symbol: string;
decimals: number;
};
export type RequestDetailsAction = {
type: BridgeMessageTypes.REQUEST_DETAILS;
};
export interface TokenIdentifier {
domain: string | number;
id: BytesLike;
}

@ -4,6 +4,15 @@ import '@nomiclabs/hardhat-waffle';
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: '0.7.6',
solidity: {
version: "0.7.6",
},
// For some reason the BridgeRouter is oversized here but not in
// in abacus-xapps.
networks: {
hardhat: {
allowUnlimitedContractSize: true
}
},
};

@ -1,11 +1,11 @@
import { getCoreDeploys, getBridgeDeploys, getEnvironment } from './utils';
import { updateProviderDomain } from '../src/provider';
import { updateSdkDomain } from '../src/sdk';
async function main() {
const environment = await getEnvironment();
const coreDeploys = await getCoreDeploys(environment);
const bridgeDeploys = await getBridgeDeploys(environment);
updateProviderDomain(environment, coreDeploys, bridgeDeploys);
updateSdkDomain(environment, coreDeploys, bridgeDeploys);
}
main().then(console.log).catch(console.error);

@ -121,8 +121,8 @@ export async function registerGovernorSigner(
context: AbacusContext,
chains: ChainConfig[],
): Promise<void> {
const govDomain = await context.governorDomain();
const govChains = chains.filter((c) => c.domain === govDomain);
const governor = await context.governor();
const govChains = chains.filter((c) => c.domain === governor.domain);
if (govChains.length !== 1) throw new Error('could not find governor chain');
const govChain = govChains[0];
context.registerSigner(

@ -39,7 +39,7 @@ export class BridgeInvariantChecker extends InvariantChecker<BridgeDeploy> {
);
await Promise.all(
remoteDomains.map(async (remoteDomain) => {
const registeredRouter = await bridgeRouter.remotes(remoteDomain);
const registeredRouter = await bridgeRouter.routers(remoteDomain);
expect(registeredRouter).to.not.equal(emptyAddr);
}),
);

@ -13,7 +13,7 @@ export type CoreContractAddresses = {
governanceRouter: ProxiedAddress;
outbox: ProxiedAddress;
// TODO: Put chain name in here
inboxs?: Record<number, ProxiedAddress>;
inboxes?: Record<number, ProxiedAddress>;
};
export type BridgeContractAddresses = {

@ -48,7 +48,7 @@ export type RustContractBlock = {
export type RustConfig = {
environment: string;
signers: Record<string, RustSigner>;
inboxs: Record<string, RustContractBlock>;
inboxes: Record<string, RustContractBlock>;
outbox: RustContractBlock;
tracing: {
level: string;

@ -1,4 +1,7 @@
import * as contracts from '@abacus-network/ts-interface/dist/abacus-core';
import {
core as coreContracts,
xapps as xappContracts,
} from '@abacus-network/ts-interface';
import { BeaconProxy } from '../utils/proxy';
import { Contracts } from '../contracts';
import {
@ -8,24 +11,24 @@ import {
import * as ethers from 'ethers';
export class CoreContracts extends Contracts {
upgradeBeaconController?: contracts.UpgradeBeaconController;
xAppConnectionManager?: contracts.XAppConnectionManager;
validatorManager?: contracts.ValidatorManager;
governanceRouter?: BeaconProxy<contracts.GovernanceRouter>;
outbox?: BeaconProxy<contracts.Outbox>;
inboxs: Record<number, BeaconProxy<contracts.Inbox>>;
upgradeBeaconController?: coreContracts.UpgradeBeaconController;
xAppConnectionManager?: coreContracts.XAppConnectionManager;
validatorManager?: coreContracts.ValidatorManager;
governanceRouter?: BeaconProxy<xappContracts.GovernanceRouter>;
outbox?: BeaconProxy<coreContracts.Outbox>;
inboxes: Record<number, BeaconProxy<coreContracts.Inbox>>;
constructor() {
super();
this.inboxs = {};
this.inboxes = {};
}
toObject(): CoreContractAddresses {
const inboxs: Record<number, ProxiedAddress> = {};
Object.keys(this.inboxs!)
const inboxes: Record<number, ProxiedAddress> = {};
Object.keys(this.inboxes!)
.map((d) => parseInt(d))
.map((domain: number) => {
inboxs[domain] = this.inboxs[domain].toObject();
inboxes[domain] = this.inboxes[domain].toObject();
});
return {
@ -34,7 +37,7 @@ export class CoreContracts extends Contracts {
validatorManager: this.validatorManager!.address,
governanceRouter: this.governanceRouter!.toObject(),
outbox: this.outbox!.toObject(),
inboxs,
inboxes,
};
}
@ -44,75 +47,76 @@ export class CoreContracts extends Contracts {
): CoreContracts {
const core = new CoreContracts();
core.upgradeBeaconController =
contracts.UpgradeBeaconController__factory.connect(
coreContracts.UpgradeBeaconController__factory.connect(
addresses.upgradeBeaconController,
provider,
);
core.xAppConnectionManager =
contracts.XAppConnectionManager__factory.connect(
coreContracts.XAppConnectionManager__factory.connect(
addresses.xAppConnectionManager,
provider,
);
core.validatorManager = contracts.ValidatorManager__factory.connect(
core.validatorManager = coreContracts.ValidatorManager__factory.connect(
addresses.validatorManager,
provider,
);
// TODO: needs type magic for turning governance, outbox and inboxs to BeaconProxy contracts
// TODO: needs type magic for turning governance, outbox and inboxes to BeaconProxy contracts
const governanceRouterImplementation =
contracts.GovernanceRouter__factory.connect(
xappContracts.GovernanceRouter__factory.connect(
addresses.governanceRouter.implementation,
provider,
);
const governanceRouterProxy = contracts.GovernanceRouter__factory.connect(
addresses.governanceRouter.proxy,
provider,
);
const governanceRouterProxy =
xappContracts.GovernanceRouter__factory.connect(
addresses.governanceRouter.proxy,
provider,
);
const governanceRouterUpgradeBeacon =
contracts.UpgradeBeacon__factory.connect(
coreContracts.UpgradeBeacon__factory.connect(
addresses.governanceRouter.beacon,
provider,
);
core.governanceRouter = new BeaconProxy<contracts.GovernanceRouter>(
core.governanceRouter = new BeaconProxy<xappContracts.GovernanceRouter>(
governanceRouterImplementation,
governanceRouterProxy,
governanceRouterUpgradeBeacon,
);
const outboxImplementation = contracts.Outbox__factory.connect(
const outboxImplementation = coreContracts.Outbox__factory.connect(
addresses.outbox.implementation,
provider,
);
const outboxProxy = contracts.Outbox__factory.connect(
const outboxProxy = coreContracts.Outbox__factory.connect(
addresses.outbox.proxy,
provider,
);
const outboxUpgradeBeacon = contracts.UpgradeBeacon__factory.connect(
const outboxUpgradeBeacon = coreContracts.UpgradeBeacon__factory.connect(
addresses.outbox.beacon,
provider,
);
core.outbox = new BeaconProxy<contracts.Outbox>(
core.outbox = new BeaconProxy<coreContracts.Outbox>(
outboxImplementation,
outboxProxy,
outboxUpgradeBeacon,
);
Object.keys(addresses.inboxs!)
Object.keys(addresses.inboxes!)
.map((d) => parseInt(d))
.map((domain: number) => {
const inboxImplementation = contracts.Inbox__factory.connect(
addresses.inboxs![domain].implementation,
const inboxImplementation = coreContracts.Inbox__factory.connect(
addresses.inboxes![domain].implementation,
provider,
);
const inboxProxy = contracts.Inbox__factory.connect(
addresses.inboxs![domain].proxy,
const inboxProxy = coreContracts.Inbox__factory.connect(
addresses.inboxes![domain].proxy,
provider,
);
const inboxUpgradeBeacon = contracts.UpgradeBeacon__factory.connect(
addresses.inboxs![domain].beacon,
const inboxUpgradeBeacon = coreContracts.UpgradeBeacon__factory.connect(
addresses.inboxes![domain].beacon,
provider,
);
core.inboxs[domain] = new BeaconProxy<contracts.Inbox>(
core.inboxes[domain] = new BeaconProxy<coreContracts.Inbox>(
inboxImplementation,
inboxProxy,
inboxUpgradeBeacon,

@ -101,7 +101,7 @@ export class CoreDeploy extends Deploy<CoreContracts> {
signers: {
[outbox.name]: { key: '', type: 'hexKey' },
},
inboxs: {},
inboxes: {},
outbox,
tracing: {
level: 'debug',
@ -112,7 +112,7 @@ export class CoreDeploy extends Deploy<CoreContracts> {
for (var remote of remotes) {
const inbox = {
address: remote.contracts.inboxs[local.chain.domain].proxy.address,
address: remote.contracts.inboxes[local.chain.domain].proxy.address,
domain: remote.chain.domain.toString(),
name: remote.chain.name,
rpcStyle: 'ethereum',
@ -123,7 +123,7 @@ export class CoreDeploy extends Deploy<CoreContracts> {
};
rustConfig.signers[inbox.name] = { key: '', type: 'hexKey' };
rustConfig.inboxs[inbox.name] = inbox;
rustConfig.inboxes[inbox.name] = inbox;
}
return rustConfig;

@ -1,4 +1,5 @@
import { expect } from 'chai';
import * as ethers from 'ethers';
import { BeaconProxy } from '../utils/proxy';
import { CoreDeploy } from './CoreDeploy';
@ -10,8 +11,6 @@ import {
InvariantChecker,
} from '../checks';
const emptyAddr = '0x' + '00'.repeat(20);
export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
constructor(deploys: CoreDeploy[]) {
super(deploys);
@ -35,8 +34,8 @@ export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
expect(contracts.upgradeBeaconController).to.not.be.undefined;
expect(contracts.xAppConnectionManager).to.not.be.undefined;
expect(contracts.validatorManager).to.not.be.undefined;
for (const domain in contracts.inboxs) {
expect(contracts.inboxs[domain]).to.not.be.undefined;
for (const domain in contracts.inboxes) {
expect(contracts.inboxes[domain]).to.not.be.undefined;
}
}
@ -85,12 +84,12 @@ export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
(d) => d.chain.domain !== domain,
);
if (remoteDeploys.length > 0) {
// Check that all inboxs on this domain share the same implementation and
// Check that all inboxes on this domain share the same implementation and
// UpgradeBeacon.
const inboxs = Object.values(deploy.contracts.inboxs);
const implementations = inboxs.map((r) => r.implementation.address);
const inboxes = Object.values(deploy.contracts.inboxes);
const implementations = inboxes.map((r) => r.implementation.address);
const identical = (a: any, b: any) => (a === b ? a : false);
const upgradeBeacons = inboxs.map((r) => r.beacon.address);
const upgradeBeacons = inboxes.map((r) => r.beacon.address);
expect(implementations.reduce(identical)).to.not.be.false;
expect(upgradeBeacons.reduce(identical)).to.not.be.false;
}
@ -101,22 +100,21 @@ export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
// governanceRouter for each remote domain is registered
const registeredRouters = await Promise.all(
Object.keys(deploy.contracts.inboxs).map((_) =>
Object.keys(deploy.contracts.inboxes).map((_) =>
deploy.contracts.governanceRouter?.proxy.routers(_),
),
);
registeredRouters.map((_) => expect(_).to.not.equal(emptyAddr));
registeredRouters.map((_) =>
expect(_).to.not.equal(ethers.constants.AddressZero),
);
// governor is set on governor chain, empty on others
// TODO: assert all governance routers have the same governor domain
const governorDomain =
await deploy.contracts.governanceRouter?.proxy.governorDomain();
const gov = await deploy.contracts.governanceRouter?.proxy.governor();
const localDomain = await deploy.contracts.outbox?.proxy.localDomain();
if (governorDomain == localDomain) {
expect(gov).to.not.equal(emptyAddr);
const governor = await deploy.contracts.governanceRouter?.proxy.governor();
if (localDomain === this._deploys[0].chain.domain) {
expect(governor).to.not.equal(ethers.constants.AddressZero);
} else {
expect(gov).to.equal(emptyAddr);
expect(governor).to.equal(ethers.constants.AddressZero);
}
const owners = [
@ -125,7 +123,7 @@ export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
deploy.contracts.upgradeBeaconController?.owner()!,
deploy.contracts.outbox?.proxy.owner()!,
];
Object.values(deploy.contracts.inboxs).map((_) =>
Object.values(deploy.contracts.inboxes).map((_) =>
owners.push(_.proxy.owner()),
);
@ -136,11 +134,11 @@ export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
async checkXAppConnectionManager(deploy: CoreDeploy): Promise<void> {
expect(deploy.contracts.xAppConnectionManager).to.not.be.undefined;
for (const domain in deploy.contracts.inboxs) {
for (const domain in deploy.contracts.inboxes) {
// inbox is enrolled in xAppConnectionManager
const enrolledInbox =
await deploy.contracts.xAppConnectionManager?.domainToInbox(domain);
expect(enrolledInbox).to.not.equal(emptyAddr);
expect(enrolledInbox).to.not.equal(ethers.constants.AddressZero);
}
// Outbox is set on xAppConnectionManager
const xAppManagerOutbox =
@ -168,8 +166,8 @@ export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
};
addInputsForUpgradableContract(contracts.outbox!, 'Outbox');
addInputsForUpgradableContract(contracts.governanceRouter!, 'Governance');
for (const domain in contracts.inboxs) {
addInputsForUpgradableContract(contracts.inboxs[domain], 'Inbox');
for (const domain in contracts.inboxes) {
addInputsForUpgradableContract(contracts.inboxes[domain], 'Inbox');
}
return inputs;
}
@ -192,7 +190,7 @@ export class CoreInvariantChecker extends InvariantChecker<CoreDeploy> {
);
await Promise.all(
Object.values(contracts.inboxs).map((_) =>
Object.values(contracts.inboxes).map((_) =>
this.checkBeaconProxyImplementation(domain, 'Inbox', _),
),
);

@ -75,13 +75,13 @@ export class ImplementationDeployer {
deploy.config.reserveGas,
);
for (const domain in deploy.contracts.inboxs) {
deploy.contracts.inboxs[domain] =
for (const domain in deploy.contracts.inboxes) {
deploy.contracts.inboxes[domain] =
proxyUtils.overrideBeaconProxyImplementation<contracts.Inbox>(
implementation,
deploy,
new inboxFactory(deploy.signer),
deploy.contracts.inboxs[domain],
deploy.contracts.inboxes[domain],
);
}
}

@ -1,15 +1,16 @@
import { assert } from 'console';
import { ethers } from 'ethers';
import fs from 'fs';
import * as proxyUtils from '../utils/proxy';
import { CoreDeploy } from './CoreDeploy';
import * as contracts from '@abacus-network/ts-interface/dist/abacus-core';
import { core, xapps } from '@abacus-network/ts-interface';
import { CoreInvariantChecker } from './checks';
import { log, warn, toBytes32 } from '../utils/utils';
const nullRoot: string = '0x' + '00'.repeat(32);
export async function deployUpgradeBeaconController(deploy: CoreDeploy) {
let factory = new contracts.UpgradeBeaconController__factory(deploy.signer);
let factory = new core.UpgradeBeaconController__factory(deploy.signer);
deploy.contracts.upgradeBeaconController = await factory.deploy(
deploy.overrides,
);
@ -33,7 +34,7 @@ export async function deployUpgradeBeaconController(deploy: CoreDeploy) {
* @param deploy - The deploy instance
*/
export async function deployValidatorManager(deploy: CoreDeploy) {
let factory = new contracts.ValidatorManager__factory(deploy.signer);
let factory = new core.ValidatorManager__factory(deploy.signer);
deploy.contracts.validatorManager = await factory.deploy(deploy.overrides);
await deploy.contracts.validatorManager.deployTransaction.wait(
deploy.chain.confirmations,
@ -55,7 +56,7 @@ export async function deployValidatorManager(deploy: CoreDeploy) {
*/
export async function deployXAppConnectionManager(deploy: CoreDeploy) {
const signer = deploy.signer;
const factory = new contracts.XAppConnectionManager__factory(signer);
const factory = new core.XAppConnectionManager__factory(signer);
deploy.contracts.xAppConnectionManager = await factory.deploy(
deploy.overrides,
@ -82,15 +83,15 @@ export async function deployOutbox(deploy: CoreDeploy) {
const isTestDeploy: boolean = deploy.test;
if (isTestDeploy) warn('deploying test Outbox');
const outboxFactory = isTestDeploy
? contracts.TestOutbox__factory
: contracts.Outbox__factory;
? core.TestOutbox__factory
: core.Outbox__factory;
let { validatorManager } = deploy.contracts;
let initData = outboxFactory
.createInterface()
.encodeFunctionData('initialize', [validatorManager!.address]);
deploy.contracts.outbox = await proxyUtils.deployProxy<contracts.Outbox>(
deploy.contracts.outbox = await proxyUtils.deployProxy<core.Outbox>(
'Outbox',
deploy,
new outboxFactory(deploy.signer),
@ -106,29 +107,21 @@ export async function deployOutbox(deploy: CoreDeploy) {
* @param deploy - The deploy instance
*/
export async function deployGovernanceRouter(deploy: CoreDeploy) {
const isTestDeploy: boolean = deploy.test;
if (isTestDeploy) warn('deploying test GovernanceRouter');
const governanceRouter = isTestDeploy
? contracts.TestGovernanceRouter__factory
: contracts.GovernanceRouter__factory;
const governanceRouter = xapps.GovernanceRouter__factory;
let { xAppConnectionManager } = deploy.contracts;
const recoveryTimelock = deploy.config.recoveryTimelock;
let initData = governanceRouter
.createInterface()
.encodeFunctionData('initialize', [
xAppConnectionManager!.address,
deploy.recoveryManager,
]);
.encodeFunctionData('initialize', [xAppConnectionManager!.address]);
deploy.contracts.governanceRouter =
await proxyUtils.deployProxy<contracts.GovernanceRouter>(
await proxyUtils.deployProxy<xapps.GovernanceRouter>(
'Governance',
deploy,
new governanceRouter(deploy.signer),
initData,
deploy.chain.domain,
recoveryTimelock,
);
}
@ -147,9 +140,7 @@ export async function deployUnenrolledInbox(
const isTestDeploy: boolean = remote.test;
if (isTestDeploy) warn('deploying test Inbox');
const inbox = isTestDeploy
? contracts.TestInbox__factory
: contracts.Inbox__factory;
const inbox = isTestDeploy ? core.TestInbox__factory : core.Inbox__factory;
let initData = inbox
.createInterface()
@ -160,15 +151,15 @@ export async function deployUnenrolledInbox(
0,
]);
// if we have no inboxs, deploy the whole setup.
// if we have no inboxes, deploy the whole setup.
// otherwise just deploy a fresh proxy
let proxy;
if (Object.keys(local.contracts.inboxs).length === 0) {
if (Object.keys(local.contracts.inboxes).length === 0) {
log(
isTestDeploy,
`${local.chain.name}: deploying initial Inbox for ${remote.chain.name}`,
);
proxy = await proxyUtils.deployProxy<contracts.Inbox>(
proxy = await proxyUtils.deployProxy<core.Inbox>(
'Inbox',
local,
new inbox(local.signer),
@ -182,15 +173,15 @@ export async function deployUnenrolledInbox(
isTestDeploy,
`${local.chain.name}: deploying additional Inbox for ${remote.chain.name}`,
);
const prev = Object.entries(local.contracts.inboxs)[0][1];
proxy = await proxyUtils.duplicate<contracts.Inbox>(
const prev = Object.entries(local.contracts.inboxes)[0][1];
proxy = await proxyUtils.duplicate<core.Inbox>(
'Inbox',
local,
prev,
initData,
);
}
local.contracts.inboxs[remote.chain.domain] = proxy;
local.contracts.inboxes[remote.chain.domain] = proxy;
log(
isTestDeploy,
`${local.chain.name}: inbox deployed for ${remote.chain.name}`,
@ -295,7 +286,7 @@ export async function relinquish(deploy: CoreDeploy) {
`${deploy.chain.name}: Dispatched relinquish upgradeBeaconController`,
);
Object.entries(deploy.contracts.inboxs).forEach(async ([domain, inbox]) => {
Object.entries(deploy.contracts.inboxes).forEach(async ([domain, inbox]) => {
await inbox.proxy.transferOwnership(govRouter, deploy.overrides);
log(
isTestDeploy,
@ -325,7 +316,7 @@ export async function enrollInbox(local: CoreDeploy, remote: CoreDeploy) {
log(isTestDeploy, `${local.chain.name}: starting inbox enrollment`);
let tx = await local.contracts.xAppConnectionManager!.enrollInbox(
local.contracts.inboxs[remote.chain.domain].proxy.address,
local.contracts.inboxes[remote.chain.domain].proxy.address,
remote.chain.domain,
local.overrides,
);
@ -349,7 +340,7 @@ export async function enrollGovernanceRouter(
isTestDeploy,
`${local.chain.name}: starting enroll ${remote.chain.name} governance router`,
);
let tx = await local.contracts.governanceRouter!.proxy.setRouter(
let tx = await local.contracts.governanceRouter!.proxy.enrollRemoteRouter(
remote.chain.domain,
toBytes32(remote.contracts.governanceRouter!.proxy.address),
local.overrides,
@ -395,44 +386,36 @@ export async function enrollRemote(local: CoreDeploy, remote: CoreDeploy) {
}
/**
* Transfers governorship to the governing chain's GovernanceRouter.
* Sets the governor to the null address.
*
* @param gov - The governor chain deploy instance
* @param non - The non-governor chain deploy instance
* @param local - The deploy instance on which to renounce governorship
*/
export async function transferGovernorship(gov: CoreDeploy, non: CoreDeploy) {
log(gov.test, `${non.chain.name}: transferring governorship`);
let governorAddress = await gov.contracts.governanceRouter!.proxy.governor();
let tx = await non.contracts.governanceRouter!.proxy.transferGovernor(
gov.chain.domain,
governorAddress,
non.overrides,
export async function renounceGovernorship(deploy: CoreDeploy) {
log(deploy.test, `${deploy.chain.name}: renouncing governorship`);
let tx = await deploy.contracts.governanceRouter!.proxy.setGovernor(
ethers.constants.AddressZero,
deploy.overrides,
);
await tx.wait(gov.chain.confirmations);
log(gov.test, `${non.chain.name}: governorship transferred`);
await tx.wait(deploy.chain.confirmations);
log(deploy.test, `${deploy.chain.name}: governorship transferred`);
}
/**
* Appints the intended ultimate governor in that domain's Governance Router.
* Sets the intended ultimate governor on that domain's Governance Router.
* If the governor address is not configured, it will remain the signer
* address.
* @param gov - The governor chain deploy instance
*/
export async function appointGovernor(gov: CoreDeploy) {
const domain = gov.chain.domain;
export async function setGovernor(gov: CoreDeploy) {
const governor = await gov.governorOrSigner();
if (governor) {
log(
gov.test,
`${gov.chain.name}: transferring root governorship to ${domain}:${governor}`,
);
const tx = await gov.contracts.governanceRouter!.proxy.transferGovernor(
domain,
log(gov.test, `${gov.chain.name}: setting governor to:${governor}`);
const tx = await gov.contracts.governanceRouter!.proxy.setGovernor(
governor,
gov.overrides,
);
await tx.wait(gov.chain.confirmations);
log(gov.test, `${gov.chain.name}: root governorship transferred`);
log(gov.test, `${gov.chain.name}: governor set`);
}
}
@ -500,15 +483,15 @@ export async function deployNChains(deploys: CoreDeploy[]) {
}
}
// appoint the configured governance account as governor
// set the configured governance account as governor
if (govChain.governor) {
log(isTestDeploy, `appoint governor: ${govChain.governor}`);
await appointGovernor(govChain);
log(isTestDeploy, `set governor: ${govChain.governor}`);
await setGovernor(govChain);
}
await Promise.all(
nonGovChains.map(async (non) => {
await transferGovernorship(govChain, non);
await renounceGovernorship(non);
}),
);

@ -3,7 +3,7 @@ import { CoreDeploy } from './core/CoreDeploy';
import { writeFileSync } from 'fs';
import { resolve } from 'path';
export function updateProviderDomain(
export function updateSdkDomain(
environment: string,
coreDeploys: CoreDeploy[],
bridgeDeploys: BridgeDeploy[],
@ -25,12 +25,12 @@ export const ${coreDeploy.chain.name}: AbacusDomain = {
xAppConnectionManager: '${
coreDeploy.contracts.xAppConnectionManager!.address
}',
inboxs: [
${Object.keys(coreDeploy.contracts.inboxs)
inboxes: [
${Object.keys(coreDeploy.contracts.inboxes)
.map(Number)
.map(
(inboxDomain) =>
` { domain: ${inboxDomain}, address: '${coreDeploy.contracts.inboxs[inboxDomain].proxy.address}' },`,
` { domain: ${inboxDomain}, address: '${coreDeploy.contracts.inboxes[inboxDomain].proxy.address}' },`,
)
.join('\n')}
],

@ -2,7 +2,7 @@ import { BigNumberish, ethers } from 'ethers';
import { MultiProvider } from '..';
import { xapps, core } from '@abacus-network/ts-interface';
import { BridgeContracts } from './contracts/BridgeContracts';
import { CoreContracts } from './contracts/CoreContracts';
import { Governor, CoreContracts } from './contracts/CoreContracts';
import { ResolvedTokenInfo, TokenIdentifier } from './tokens';
import { canonizeId, evmId } from '../utils';
import {
@ -33,7 +33,6 @@ type Address = string;
export class AbacusContext extends MultiProvider {
private cores: Map<number, CoreContracts>;
private bridges: Map<number, BridgeContracts>;
private _governorDomain?: number;
constructor(
domains: AbacusDomain[],
@ -226,21 +225,30 @@ export class AbacusContext extends MultiProvider {
}
/**
* Discovers the governor domain of this abacus deployment and caches it.
* Returns the governors of this abacus deployment.
*
* @returns The identifier of the governing domain
* @returns The governors of the deployment
*/
async governorDomain(): Promise<number> {
if (this._governorDomain) {
return this._governorDomain;
}
const core: CoreContracts = this.cores.values().next().value;
if (!core) throw new Error('empty core map');
async governors(): Promise<Governor[]> {
let governors = await Promise.all(
Array.from(this.cores.values()).map((core) => core.governor()),
);
governors = governors.filter(
(governor) => governor.identifier !== ethers.constants.AddressZero,
);
if (governors.length === 0) throw new Error('no governors');
return governors;
}
const governorDomain = await core.governanceRouter.governorDomain();
this._governorDomain = governorDomain !== 0 ? governorDomain : core.domain;
return this._governorDomain;
/**
* Returns the single governor of this deployment, throws an error if not found.
*
* @returns The governor of the deployment
*/
async governor(): Promise<Governor> {
const governors = await this.governors();
if (governors.length !== 1) throw new Error('multiple governors');
return governors[0];
}
/**
@ -250,7 +258,8 @@ export class AbacusContext extends MultiProvider {
* @returns The identifier of the governing domain
*/
async governorCore(): Promise<CoreContracts> {
return this.mustGetCore(await this.governorDomain());
const governor = await this.governor();
return this.mustGetCore(governor.domain);
}
/**

@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { core } from '@abacus-network/ts-interface';
import { core, xapps } from '@abacus-network/ts-interface';
import { Contracts } from '../../contracts';
import { InboxInfo } from '../domains/domain';
import { CallBatch } from '../govern';
@ -9,13 +9,12 @@ type Address = string;
interface Core {
id: number;
outbox: Address;
inboxs: InboxInfo[];
inboxes: InboxInfo[];
governanceRouter: Address;
xAppConnectionManager: Address;
}
export type Governor = {
local: boolean;
domain: number;
identifier: string;
};
@ -23,7 +22,7 @@ export type Governor = {
export class CoreContracts extends Contracts {
readonly domain: number;
readonly _outbox: Address;
readonly _inboxs: Map<number, InboxInfo>;
readonly _inboxes: Map<number, InboxInfo>;
readonly _governanceRouter: Address;
readonly _xAppConnectionManager: Address;
private providerOrSigner?: ethers.providers.Provider | ethers.Signer;
@ -32,21 +31,21 @@ export class CoreContracts extends Contracts {
constructor(
domain: number,
outbox: Address,
inboxs: InboxInfo[],
inboxes: InboxInfo[],
governanceRouter: Address,
xAppConnectionManager: Address,
providerOrSigner?: ethers.providers.Provider | ethers.Signer,
) {
super(domain, outbox, inboxs, providerOrSigner);
super(domain, outbox, inboxes, providerOrSigner);
this.providerOrSigner = providerOrSigner;
this.domain = domain;
this._outbox = outbox;
this._governanceRouter = governanceRouter;
this._xAppConnectionManager = xAppConnectionManager;
this._inboxs = new Map();
inboxs.forEach((inbox) => {
this._inboxs.set(inbox.domain, {
this._inboxes = new Map();
inboxes.forEach((inbox) => {
this._inboxes.set(inbox.domain, {
address: inbox.address,
domain: inbox.domain,
});
@ -57,7 +56,7 @@ export class CoreContracts extends Contracts {
if (!this.providerOrSigner) {
throw new Error('No provider or signer. Call `connect` first.');
}
const inbox = this._inboxs.get(domain);
const inbox = this._inboxes.get(domain);
if (!inbox) return;
return core.Inbox__factory.connect(inbox.address, this.providerOrSigner);
}
@ -69,11 +68,11 @@ export class CoreContracts extends Contracts {
return core.Outbox__factory.connect(this._outbox, this.providerOrSigner);
}
get governanceRouter(): core.GovernanceRouter {
get governanceRouter(): xapps.GovernanceRouter {
if (!this.providerOrSigner) {
throw new Error('No provider or signer. Call `connect` first.');
}
return core.GovernanceRouter__factory.connect(
return xapps.GovernanceRouter__factory.connect(
this._governanceRouter,
this.providerOrSigner,
);
@ -93,12 +92,8 @@ export class CoreContracts extends Contracts {
if (this._governor) {
return this._governor;
}
const [domain, identifier] = await Promise.all([
this.governanceRouter.governorDomain(),
this.governanceRouter.governor(),
]);
const local = identifier !== ethers.constants.AddressZero;
this._governor = { local, domain, identifier };
const identifier = await this.governanceRouter.governor();
this._governor = { domain: this.domain, identifier };
return this._governor;
}
@ -111,23 +106,23 @@ export class CoreContracts extends Contracts {
}
toObject(): Core {
const inboxs: InboxInfo[] = Array.from(this._inboxs.values());
const inboxes: InboxInfo[] = Array.from(this._inboxes.values());
return {
id: this.domain,
outbox: this._outbox,
inboxs: inboxs,
inboxes: inboxes,
governanceRouter: this._governanceRouter,
xAppConnectionManager: this._xAppConnectionManager,
};
}
static fromObject(data: Core, signer?: ethers.Signer): CoreContracts {
const { id, outbox, inboxs, governanceRouter, xAppConnectionManager } =
const { id, outbox, inboxes, governanceRouter, xAppConnectionManager } =
data;
if (
!id ||
!outbox ||
!inboxs ||
!inboxes ||
!governanceRouter ||
!xAppConnectionManager
) {
@ -136,7 +131,7 @@ export class CoreContracts extends Contracts {
return new CoreContracts(
id,
outbox,
inboxs,
inboxes,
governanceRouter,
xAppConnectionManager,
signer,

@ -5,7 +5,7 @@ export const alfajores: AbacusDomain = {
id: 1000,
bridgeRouter: '0x684C74fBA4dF7F7A542709C5f9688AB806C7B828',
outbox: '0xeA057840858645bb68134a913A252a44a0C58652',
inboxs: [
inboxes: [
{ domain: 5, address: '0x3354D5956612C38D0dD831dcdf83CF30BC674231' },
{ domain: 3000, address: '0x6AdB8ba7C826d70506D26eDdc74236fB88Fa647F' },
{ domain: 43113, address: '0x570EDeF0c271E3f1ba6B5C66D040195750a79762' },
@ -21,7 +21,7 @@ export const kovan: AbacusDomain = {
bridgeRouter: '0x53d09A4B49443F7f7C66321C306601dC9d483D4F',
ethHelper: '0xFE7c9Cc7116429Ae50823a218315C7E01EC7A761',
outbox: '0xc53F82FAF17B4c521A85C514791593847Bdf1655',
inboxs: [
inboxes: [
{ domain: 5, address: '0xc501ad2163Ebd9921B4a6E46B344Ef7bA76A2cBa' },
{ domain: 1000, address: '0xEdDA4762fe6388C69d37b8Ee15B1deC10cA3B964' },
{ domain: 43113, address: '0xf3855B99b7cEfa56C66f0C2d0550b545df11d54A' },
@ -37,7 +37,7 @@ export const gorli: AbacusDomain = {
bridgeRouter: '0x53d09A4B49443F7f7C66321C306601dC9d483D4F',
ethHelper: '0xFE7c9Cc7116429Ae50823a218315C7E01EC7A761',
outbox: '0xc53F82FAF17B4c521A85C514791593847Bdf1655',
inboxs: [
inboxes: [
{ domain: 1000, address: '0xEdDA4762fe6388C69d37b8Ee15B1deC10cA3B964' },
{ domain: 3000, address: '0xc501ad2163Ebd9921B4a6E46B344Ef7bA76A2cBa' },
{ domain: 43113, address: '0xf3855B99b7cEfa56C66f0C2d0550b545df11d54A' },
@ -53,7 +53,7 @@ export const fuji: AbacusDomain = {
bridgeRouter: '0xFE7c9Cc7116429Ae50823a218315C7E01EC7A761',
ethHelper: '0x7B99a9cf26c9813b16E3DDb3D6E593c3624c9EBA',
outbox: '0xc53F82FAF17B4c521A85C514791593847Bdf1655',
inboxs: [
inboxes: [
{ domain: 5, address: '0xf3855B99b7cEfa56C66f0C2d0550b545df11d54A' },
{ domain: 1000, address: '0xEdDA4762fe6388C69d37b8Ee15B1deC10cA3B964' },
{ domain: 3000, address: '0xc501ad2163Ebd9921B4a6E46B344Ef7bA76A2cBa' },
@ -74,7 +74,7 @@ export const mumbai: AbacusDomain = {
bridgeRouter: '0xFE7c9Cc7116429Ae50823a218315C7E01EC7A761',
ethHelper: '0x7B99a9cf26c9813b16E3DDb3D6E593c3624c9EBA',
outbox: '0xc53F82FAF17B4c521A85C514791593847Bdf1655',
inboxs: [
inboxes: [
{ domain: 5, address: '0xf3855B99b7cEfa56C66f0C2d0550b545df11d54A' },
{ domain: 1000, address: '0xEdDA4762fe6388C69d37b8Ee15B1deC10cA3B964' },
{ domain: 3000, address: '0xc501ad2163Ebd9921B4a6E46B344Ef7bA76A2cBa' },

@ -5,7 +5,7 @@ export interface AbacusDomain extends Domain {
bridgeRouter: Address;
ethHelper?: Address;
outbox: Address;
inboxs: InboxInfo[];
inboxes: InboxInfo[];
governanceRouter: Address;
xAppConnectionManager: Address;
}

@ -7,7 +7,7 @@ export const celo: AbacusDomain = {
outbox: '0x913EE05036f3cbc94Ee4afDea87ceb430524648a',
governanceRouter: '0xd13aC1024d266B73180cA7445Ca0E78b3Acfe8CE',
xAppConnectionManager: '0xaa099aF87ACE9E437b9B410a687F263eeaeC4321',
inboxs: [
inboxes: [
{ domain: 6648936, address: '0xcDE146d1C673fE13f4fF1569d3F0d9f4d0b9c837' },
{
domain: 1635148152,
@ -28,7 +28,7 @@ export const ethereum: AbacusDomain = {
outbox: '0xa73a3a74C7044B5411bD61E1990618A1400DA379',
governanceRouter: '0xcbcF180dbd02460dCFCdD282A0985DdC049a4c94',
xAppConnectionManager: '0x8A926cE79f83A5A4C234BeE93feAFCC85b1E40cD',
inboxs: [
inboxes: [
{
domain: 1635148152,
address: '0xaa099aF87ACE9E437b9B410a687F263eeaeC4321',
@ -57,7 +57,7 @@ export const avalanche: AbacusDomain = {
outbox: '0x101a39eA1143cb252fc8093847399046fc35Db89',
governanceRouter: '0x4d89F34dB307015F8002F97c1d100d84e3AFb76c',
xAppConnectionManager: '0x81B97dfBB743c343983e9bE7B863dB636DbD7373',
inboxs: [
inboxes: [
{ domain: 6648936, address: '0xCf9066ee2fF063dD09862B745414c8dEa4Cc0497' },
{
domain: 1667591279,
@ -83,7 +83,7 @@ export const polygon: AbacusDomain = {
outbox: '0xCf9066ee2fF063dD09862B745414c8dEa4Cc0497',
governanceRouter: '0xf1dd0edC8f8C9a881F350e8010e06bE9eaf7DafA',
xAppConnectionManager: '0x4eA75c12eD058F0e6651475688a941555FA62395',
inboxs: [
inboxes: [
{ domain: 6648936, address: '0x2784a755690453035f32Ac5e28c52524d127AfE2' },
{
domain: 1635148152,

@ -8,7 +8,7 @@ export const ethereum: AbacusDomain = {
outbox: '0xf25C5932bb6EFc7afA4895D9916F2abD7151BF97',
governanceRouter: '0x42303634F37956687fB7ff2c6146AC842481A052',
xAppConnectionManager: '0xcEc158A719d11005Bd9339865965bed938BEafA3',
inboxs: [
inboxes: [
{
domain: 1667591279,
address: '0x07b5B57b08202294E657D51Eb453A189290f6385',
@ -33,7 +33,7 @@ export const polygon: AbacusDomain = {
outbox: '0x97bbda9A1D45D86631b243521380Bc070D6A4cBD',
governanceRouter: '0xcEc158A719d11005Bd9339865965bed938BEafA3',
xAppConnectionManager: '0x3BAD272559949B455f14ee394798E4D744342661',
inboxs: [
inboxes: [
{ domain: 6648936, address: '0xf25C5932bb6EFc7afA4895D9916F2abD7151BF97' },
{
domain: 1667591279,
@ -49,7 +49,7 @@ export const celo: AbacusDomain = {
outbox: '0x97bbda9A1D45D86631b243521380Bc070D6A4cBD',
governanceRouter: '0xcEc158A719d11005Bd9339865965bed938BEafA3',
xAppConnectionManager: '0x3BAD272559949B455f14ee394798E4D744342661',
inboxs: [
inboxes: [
{ domain: 6648936, address: '0xf25C5932bb6EFc7afA4895D9916F2abD7151BF97' },
{
domain: 1886350457,

@ -7,7 +7,7 @@ export const alfajores: AbacusDomain = {
outbox: '0xDf89d5d4039ada018BCDb992Bb6C2e05fEf86328',
governanceRouter: '0x1E2DE9CD3f64c4e9AadE11a60C7b3620dD026888',
xAppConnectionManager: '0x56Bf96be9ab395aa2861E7Ae4aCEFc11D8C2Ec49',
inboxs: [
inboxes: [
{ domain: 3, address: '0xC9e581Cd4fF6533f5ccBA4Dc5d5f642B8b658B93' },
{ domain: 5, address: '0x4eAD31e37b950B32b9EBbE747f0ef4BffAc336a5' },
{ domain: 3000, address: '0x15fA9169F7495162ac52b4A7957c9054097Ab0FF' },
@ -22,7 +22,7 @@ export const ropsten: AbacusDomain = {
outbox: '0x7E26E170dB94E81979927d2D39CB703048Ad599D',
governanceRouter: '0xa8C889D257d9eE02cb957941cd785CfffDe5a453',
xAppConnectionManager: '0xe5C92bC2a443016c00b3908dFA63f55bEe1a7a16',
inboxs: [
inboxes: [
{ domain: 5, address: '0x15C1edbf6E6161d50d58682dF7587F0d61db5C38' },
{ domain: 1000, address: '0x30dAE25E9eBd644841d1A1fF25e303331B1CdEb3' },
{ domain: 3000, address: '0xF782C67AA111a9D75f6ccEf3d7aDB54620D5A8e9' },
@ -37,7 +37,7 @@ export const kovan: AbacusDomain = {
outbox: '0x7E26E170dB94E81979927d2D39CB703048Ad599D',
governanceRouter: '0xa8C889D257d9eE02cb957941cd785CfffDe5a453',
xAppConnectionManager: '0xe5C92bC2a443016c00b3908dFA63f55bEe1a7a16',
inboxs: [
inboxes: [
{ domain: 3, address: '0x15C1edbf6E6161d50d58682dF7587F0d61db5C38' },
{ domain: 5, address: '0xF782C67AA111a9D75f6ccEf3d7aDB54620D5A8e9' },
{ domain: 1000, address: '0x30dAE25E9eBd644841d1A1fF25e303331B1CdEb3' },
@ -52,7 +52,7 @@ export const gorli: AbacusDomain = {
outbox: '0xDf89d5d4039ada018BCDb992Bb6C2e05fEf86328',
governanceRouter: '0x1E2DE9CD3f64c4e9AadE11a60C7b3620dD026888',
xAppConnectionManager: '0x56Bf96be9ab395aa2861E7Ae4aCEFc11D8C2Ec49',
inboxs: [
inboxes: [
{ domain: 3, address: '0xC9e581Cd4fF6533f5ccBA4Dc5d5f642B8b658B93' },
{ domain: 1000, address: '0x15fA9169F7495162ac52b4A7957c9054097Ab0FF' },
{ domain: 3000, address: '0x4eAD31e37b950B32b9EBbE747f0ef4BffAc336a5' },

@ -7,7 +7,7 @@ export const alfajores: AbacusDomain = {
outbox: '0xc8abA9c65A292C84EA00441B81124d9507fB22A8',
governanceRouter: '0x760AbbE9496BD9cEe159402E2B4d96E3d76dbE6a',
xAppConnectionManager: '0x02c144AeBA550634c8EE185F78657fd3C4a3F9B5',
inboxs: [
inboxes: [
{ domain: 2000, address: '0x7149bF9f804F27e7259d0Ce328Dd5f6D5639ef19' },
{ domain: 3000, address: '0xE469D8587D45BF85297BD924b159E726E7CA5408' },
],
@ -21,7 +21,7 @@ export const kovan: AbacusDomain = {
outbox: '0xB6Ee3e8fE5b577Bd6aB9a06FA169F97303586E7C',
governanceRouter: '0xa95868Ffaed7489e9059d4a08A0C1B0F78041b33',
xAppConnectionManager: '0x1d9Af80594930574201d919Af0fBfe6bb89800E2',
inboxs: [
inboxes: [
{ domain: 1000, address: '0xF76995174f3C02e2900d0F6261e8cbeC04078E1f' },
{ domain: 2000, address: '0xFF47138c42119Fe0B1f267e2fa254321DE287Fc6' },
],
@ -35,7 +35,7 @@ export const rinkeby: AbacusDomain = {
outbox: '0x8459EDe1ed4dADD6D5B142d845240088A6530Cf8',
governanceRouter: '0x8f8424DC94b4c302984Ab5a03fc4c2d1Ec95DC92',
xAppConnectionManager: '0x53B94f2D4a3159b66fcCC4f406Ea388426A3f3cB',
inboxs: [
inboxes: [
{ domain: 1000, address: '0xb473F5e0AAf47Ba54dac048633e7b578c1eBde01' },
{ domain: 3000, address: '0x7EB8450a5397b795F2d89BC48EA20c24fa147F11' },
],

@ -22,7 +22,7 @@ export class CallBatch {
static async fromCore(core: CoreContracts): Promise<CallBatch> {
const governor = await core.governor();
if (!governor.local)
if (governor.identifier === ethers.constants.AddressZero)
throw new Error(
'Cannot create call batch on a chain without governance rights. Use the governing chain.',
);
@ -48,9 +48,7 @@ export class CallBatch {
this.built = await Promise.all(
domains.map((domain: number, i: number) => {
if (domain === this.core.domain) {
return this.core.governanceRouter.populateTransaction.callLocal(
calls[i],
);
return this.core.governanceRouter.populateTransaction.call(calls[i]);
} else {
return this.core.governanceRouter.populateTransaction.callRemote(
domain,
@ -93,7 +91,6 @@ export class CallBatch {
const signer = this.core.governanceRouter.signer;
const governor = await this.core.governor();
const signerAddress = await signer.getAddress();
if (!governor.local) throw new Error('Governor is not local');
if (signerAddress !== governor.identifier)
throw new Error('Signer is not Governor');
return signer;

@ -1 +1,2 @@
**/*.yaml
**/*.yaml
dist/

@ -1,6 +1,6 @@
import config from './config';
import { monitorCore } from './monitor/core';
import { monitorGovernor } from './monitor/governance';
import { monitorGovernance } from './monitor/governance';
import { context } from './registerContext';
const cliArgs = process.argv.slice(2);
@ -29,7 +29,7 @@ async function main(forever: boolean) {
}
async function monitorAll() {
await monitorGovernor(context);
await monitorGovernance(context, config.networks);
for (let network of config.networks) {
const origin = network;
const remotes = config.networks.filter((m) => m != origin);

@ -1,21 +1,29 @@
import { AbacusContext } from '@abacus-network/sdk';
import { xapps } from '@abacus-network/ts-interface';
import config from '../config';
export async function monitorGovernor(context: AbacusContext) {
await monitorGovernanceRouter(context, await context.governorDomain());
export async function monitorGovernance(
context: AbacusContext,
networks: string[],
) {
const routers = networks.map(
(network) => context.mustGetCore(network).governanceRouter,
);
await Promise.all(
networks.map((network, i) => monitorRecoveryActiveAt(network, routers[i])),
);
}
async function monitorGovernanceRouter(context: AbacusContext, domain: number) {
const network = context.mustGetDomain(domain).name;
async function monitorRecoveryActiveAt(
network: string,
router: xapps.GovernanceRouter,
) {
const logger = config.baseLogger.child({
network,
});
logger.info('Getting GovernanceRouter recoveryActiveAt');
const governanceRouter = context.mustGetCore(domain).governanceRouter;
const recoveryActiveAt = (
await governanceRouter.recoveryActiveAt()
).toNumber();
const recoveryActiveAt = (await router.recoveryActiveAt()).toNumber();
config.metrics.setGovernorRecoveryActiveAt(
network,

Loading…
Cancel
Save