Allow for Checkpointing in an application's router (#359)

* Allow for Checkpointing in an application's router

* Checkpoint during hardhat kathy

* Add comboDispatch

* PR review

* Add prettier config

* Prettier

* Different router dispatch functions by behavior

* Natspec

* clean up

* Add _checkpoint

* nit

* Rename to destinationDomain

* Rename _destination to _destinationDomain, move from @notice to @dev for some natspec

Co-authored-by: Trevor Porter <trkporter@ucdavis.edu>
pull/400/head
Nam Chu Hoai 3 years ago committed by GitHub
parent 9cc9d3ab52
commit 1e93e7c897
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 140
      solidity/app/contracts/Router.sol
  2. 29
      solidity/app/contracts/test/TestRouter.sol
  3. 161
      solidity/app/test/router.test.ts
  4. 8
      solidity/apps/contracts/governance/GovernanceRouter.sol
  5. 136
      solidity/apps/test/governance/governanceRouter.test.ts
  6. 5
      typescript/hardhat/.prettierrc
  7. 36
      typescript/hardhat/src/TestAbacusDeploy.ts
  8. 3
      typescript/hardhat/src/TestRouterDeploy.ts
  9. 81
      typescript/hardhat/test/testAbacusDeploy.test.ts
  10. 3
      typescript/infra/hardhat.config.ts

@ -3,7 +3,10 @@ pragma solidity >=0.6.11;
// ============ Internal Imports ============
import {AbacusConnectionClient} from "./AbacusConnectionClient.sol";
import {IAbacusConnectionManager} from "@abacus-network/core/interfaces/IAbacusConnectionManager.sol";
import {IInterchainGasPaymaster} from "@abacus-network/core/interfaces/IInterchainGasPaymaster.sol";
import {IMessageRecipient} from "@abacus-network/core/interfaces/IMessageRecipient.sol";
import {IOutbox} from "@abacus-network/core/interfaces/IOutbox.sol";
abstract contract Router is AbacusConnectionClient, IMessageRecipient {
// ============ Mutable Storage ============
@ -114,38 +117,141 @@ abstract contract Router is AbacusConnectionClient, IMessageRecipient {
}
/**
* @notice Dispatches a message to an enrolled router via the local router's
* 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
* @notice Dispatches a message to an enrolled router via the local router's Outbox.
* @notice Does not pay interchain gas or create a checkpoint.
* @dev Reverts if there is no enrolled router for _destinationDomain.
* @param _destinationDomain The domain of the chain to which to send the message.
* @param _msg The message to dispatch.
*/
function _dispatchToRemoteRouter(uint32 _destination, bytes memory _msg)
function _dispatch(uint32 _destinationDomain, bytes memory _msg)
internal
returns (uint256)
{
// ensure that destination chain has enrolled router
bytes32 _router = _mustHaveRemoteRouter(_destination);
return _outbox().dispatch(_destination, _router, _msg);
return _dispatch(_outbox(), _destinationDomain, _msg);
}
/**
* @notice Dispatches a message to an enrolled router via the local router's Outbox
* and creates a checkpoint.
* @dev Does not pay interchain gas.
* @dev Reverts if there is no enrolled router for _destinationDomain.
* @param _destinationDomain The domain of the chain to which to send the message.
* @param _msg The message to dispatch.
*/
function _dispatchAndCheckpoint(
uint32 _destinationDomain,
bytes memory _msg
) internal {
// Gets the outbox once to avoid multiple storage reads and calls.
IOutbox _outbox = _outbox();
_dispatch(_outbox, _destinationDomain, _msg);
_outbox.checkpoint();
}
/**
* @notice Dispatches a message to an enrolled router via the local router's Outbox
* and pays interchain gas for the dispatched message.
* @dev Does not create a checkpoint on the Outbox.
* @dev Reverts if there is no enrolled router for _destinationDomain.
* @param _destinationDomain The domain of the chain to which to send the message.
* @param _msg The message to dispatch.
* @param _gasPayment The amount of native tokens to pay the Interchain Gas
* Paymaster to process the dispatched message.
*/
function _dispatchWithGas(
uint32 _destinationDomain,
bytes memory _msg,
uint256 _gasPayment
) internal {
// Gets the abacusConnectionManager from storage once to avoid multiple reads.
IAbacusConnectionManager _abacusConnectionManager = abacusConnectionManager;
_dispatchWithGas(
_abacusConnectionManager.outbox(),
_abacusConnectionManager.interchainGasPaymaster(),
_destinationDomain,
_msg,
_gasPayment
);
}
/**
* @notice Dispatches a message to an enrolled router via the local router's
* Outbox and pays for message processing on the destination chain.
* @dev Reverts if there is no enrolled router for _destination.
* @param _destination The domain of the chain to which to send the message.
* @notice Dispatches a message to an enrolled router via the local router's Outbox,
* pays interchain gas for the dispatched message, and creates a checkpoint.
* @dev Reverts if there is no enrolled router for _destinationDomain.
* @param _destinationDomain The domain of the chain to which to send the message.
* @param _msg The message to dispatch.
* @param _gasPayment The amount of native tokens to pay the Interchain Gas
* Paymaster to process the dispatched message.
*/
function _dispatchToRemoteRouterWithGas(
uint32 _destination,
function _dispatchWithGasAndCheckpoint(
uint32 _destinationDomain,
bytes memory _msg,
uint256 _gasPayment
) internal {
uint256 leafIndex = _dispatchToRemoteRouter(_destination, _msg);
// Gets the abacusConnectionManager and outbox once to avoid multiple storage reads
// and calls.
IAbacusConnectionManager _abacusConnectionManager = abacusConnectionManager;
IOutbox _outbox = _abacusConnectionManager.outbox();
_dispatchWithGas(
_outbox,
_abacusConnectionManager.interchainGasPaymaster(),
_destinationDomain,
_msg,
_gasPayment
);
_outbox.checkpoint();
}
/**
* @notice Creates a checkpoint on the local router's Outbox.
* @dev If dispatching a single message and immediately checkpointing,
* `_dispatchAndCheckpoint` or `_dispatchWithGasAndCheckpoint` should be preferred,
* as they will consume less gas than calling `_dispatch` and this function.
*/
function _checkpoint() internal {
_outbox().checkpoint();
}
// ============ Private functions ============
/**
* @notice Dispatches a message to an enrolled router via the provided Outbox.
* @dev Does not pay interchain gas or create a checkpoint.
* @dev Reverts if there is no enrolled router for _destinationDomain.
* @param _outbox The outbox contract to dispatch the message through.
* @param _destinationDomain The domain of the chain to which to send the message.
* @param _msg The message to dispatch.
*/
function _dispatch(
IOutbox _outbox,
uint32 _destinationDomain,
bytes memory _msg
) private returns (uint256) {
// Ensure that destination chain has an enrolled router.
bytes32 _router = _mustHaveRemoteRouter(_destinationDomain);
return _outbox.dispatch(_destinationDomain, _router, _msg);
}
/**
* @notice Dispatches a message to an enrolled router via the provided Outbox
* and pays interchain gas for the dispatched message via the provided InterchainGasPaymaster.
* @dev Does not create a checkpoint.
* @dev Reverts if there is no enrolled router for _destinationDomain.
* @param _outbox The outbox contract to dispatch the message through.
* @param _interchainGasPaymaster The InterchainGasPaymaster contract to pay for interchain gas.
* @param _destinationDomain The domain of the chain to which to send the message.
* @param _msg The message to dispatch.
*/
function _dispatchWithGas(
IOutbox _outbox,
IInterchainGasPaymaster _interchainGasPaymaster,
uint32 _destinationDomain,
bytes memory _msg,
uint256 _gasPayment
) private {
uint256 _leafIndex = _dispatch(_outbox, _destinationDomain, _msg);
if (_gasPayment > 0) {
_interchainGasPaymaster().payGasFor{value: _gasPayment}(leafIndex);
_interchainGasPaymaster.payGasFor{value: _gasPayment}(_leafIndex);
}
}
}

@ -30,18 +30,33 @@ contract TestRouter is Router {
return _mustHaveRemoteRouter(_domain);
}
function dispatchToRemoteRouter(uint32 _destination, bytes calldata _msg)
function dispatch(uint32 _destination, bytes memory _msg) external {
_dispatch(_destination, _msg);
}
function dispatchWithGas(
uint32 _destination,
bytes memory _msg,
uint256 _gasPayment
) external payable {
_dispatchWithGas(_destination, _msg, _gasPayment);
}
function dispatchAndCheckpoint(uint32 _destination, bytes memory _msg)
external
returns (uint256)
{
return _dispatchToRemoteRouter(_destination, _msg);
_dispatchAndCheckpoint(_destination, _msg);
}
function dispatchToRemoteRouterWithGas(
function dispatchWithGasAndCheckpoint(
uint32 _destination,
bytes calldata _msg,
bytes memory _msg,
uint256 _gasPayment
) external {
return _dispatchToRemoteRouterWithGas(_destination, _msg, _gasPayment);
) external payable {
_dispatchWithGasAndCheckpoint(_destination, _msg, _gasPayment);
}
function checkpoint() external {
_checkpoint();
}
}

@ -1,10 +1,14 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { ContractTransaction } from 'ethers';
import { ethers } from 'hardhat';
import {
AbacusConnectionManager,
AbacusConnectionManager__factory,
InterchainGasPaymaster,
InterchainGasPaymaster__factory,
Outbox,
Outbox__factory,
} from '@abacus-network/core';
import { utils } from '@abacus-network/utils';
@ -14,10 +18,12 @@ import { TestRouter, TestRouter__factory } from '../types';
const ONLY_OWNER_REVERT_MSG = 'Ownable: caller is not the owner';
const origin = 1;
const destination = 2;
const destinationWithoutRouter = 3;
const message = '0xdeadbeef';
describe('Router', async () => {
let router: TestRouter,
outbox: Outbox,
connectionManager: AbacusConnectionManager,
signer: SignerWithAddress,
nonOwner: SignerWithAddress;
@ -32,6 +38,16 @@ describe('Router', async () => {
);
connectionManager = await connectionManagerFactory.deploy();
const outboxFactory = new Outbox__factory(signer);
outbox = await outboxFactory.deploy(origin);
// dispatch dummy message
await outbox.dispatch(
destination,
utils.addressToBytes32(outbox.address),
'0x',
);
await connectionManager.setOutbox(outbox.address);
const routerFactory = new TestRouter__factory(signer);
router = await routerFactory.deploy();
await router.initialize(connectionManager.address);
@ -84,25 +100,134 @@ describe('Router', async () => {
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG);
});
it('dispatches message to enrolled remote router', async () => {
const outboxFactory = new Outbox__factory(signer);
const outbox = await outboxFactory.deploy(origin);
await connectionManager.setOutbox(outbox.address);
const remote = nonOwner.address;
await router.enrollRemoteRouter(
destination,
utils.addressToBytes32(remote),
);
await expect(router.dispatchToRemoteRouter(destination, message)).to.emit(
outbox,
'Dispatch',
);
describe('dispatch functions', () => {
let interchainGasPaymaster: InterchainGasPaymaster;
beforeEach(async () => {
const interchainGasPaymasterFactory = new InterchainGasPaymaster__factory(
signer,
);
interchainGasPaymaster = await interchainGasPaymasterFactory.deploy();
await connectionManager.setInterchainGasPaymaster(
interchainGasPaymaster.address,
);
// Enroll a remote router on the destination domain.
// The address is arbitrary because no messages will actually be processed.
await router.enrollRemoteRouter(
destination,
utils.addressToBytes32(nonOwner.address),
);
});
// Helper for testing different variations of dispatch functions
const runDispatchFunctionTests = async (
dispatchFunction: (
destinationDomain: number,
interchainGasPayment?: number,
) => Promise<ContractTransaction>,
expectCheckpoint: boolean,
expectGasPayment: boolean,
) => {
// Allows a Chai Assertion to be programmatically negated
const expectAssertion = (
assertion: Chai.Assertion,
expected: boolean,
) => {
return expected ? assertion : assertion.not;
};
it('dispatches a message', async () => {
await expect(dispatchFunction(destination)).to.emit(outbox, 'Dispatch');
});
it(`${
expectGasPayment ? 'pays' : 'does not pay'
} interchain gas`, async () => {
const testInterchainGasPayment = 1234;
const leafIndex = await outbox.count();
const assertion = expectAssertion(
expect(dispatchFunction(destination, testInterchainGasPayment)).to,
expectGasPayment,
);
await assertion
.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it(`${
expectCheckpoint ? 'creates' : 'does not create'
} a checkpoint`, async () => {
const assertion = expectAssertion(
expect(dispatchFunction(destination)).to,
expectCheckpoint,
);
await assertion.emit(outbox, 'Checkpoint');
});
it('reverts when dispatching a message to an unenrolled remote router', async () => {
await expect(
dispatchFunction(destinationWithoutRouter),
).to.be.revertedWith('!router');
});
};
describe('#dispatch', () => {
runDispatchFunctionTests(
(destinationDomain) => router.dispatch(destinationDomain, '0x'),
false,
false,
);
});
describe('#dispatchAndCheckpoint', () => {
runDispatchFunctionTests(
(destinationDomain) =>
router.dispatchAndCheckpoint(destinationDomain, '0x'),
true,
false,
);
});
describe('#dispatchWithGas', () => {
runDispatchFunctionTests(
(destinationDomain, interchainGasPayment = 0) =>
router.dispatchWithGas(
destinationDomain,
'0x',
interchainGasPayment,
{
value: interchainGasPayment,
},
),
false,
true,
);
});
describe('#dispatchWithGasAndCheckpoint', () => {
runDispatchFunctionTests(
(destinationDomain, interchainGasPayment = 0) =>
router.dispatchWithGasAndCheckpoint(
destinationDomain,
'0x',
interchainGasPayment,
{ value: interchainGasPayment },
),
true,
true,
);
});
});
it('reverts when dispatching message to unenrolled remote router', async () => {
await expect(
router.dispatchToRemoteRouter(destination, message),
).to.be.revertedWith('!router');
describe('#checkpoint', () => {
it('creates a checkpoint', async () => {
// dispatch dummy message
await outbox.dispatch(
destination,
utils.addressToBytes32(outbox.address),
'0x',
);
await expect(router.checkpoint()).to.emit(outbox, 'Checkpoint');
});
});
});

@ -195,7 +195,7 @@ contract GovernanceRouter is Version0, Router {
GovernanceMessage.Call[] calldata _calls
) external payable onlyGovernor onlyNotInRecovery {
bytes memory _msg = GovernanceMessage.formatCalls(_calls);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
_dispatchWithGasAndCheckpoint(_destination, _msg, msg.value);
}
/**
@ -214,7 +214,7 @@ contract GovernanceRouter is Version0, Router {
_domain,
_router
);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
_dispatchWithGasAndCheckpoint(_destination, _msg, msg.value);
}
/**
@ -230,7 +230,7 @@ contract GovernanceRouter is Version0, Router {
bytes memory _msg = GovernanceMessage.formatSetAbacusConnectionManager(
TypeCasts.addressToBytes32(_abacusConnectionManager)
);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
_dispatchWithGasAndCheckpoint(_destination, _msg, msg.value);
}
/**
@ -248,7 +248,7 @@ contract GovernanceRouter is Version0, Router {
bytes memory _msg = GovernanceMessage.formatSetGovernor(
TypeCasts.addressToBytes32(_governor)
);
_dispatchToRemoteRouterWithGas(_destination, _msg, msg.value);
_dispatchWithGasAndCheckpoint(_destination, _msg, msg.value);
}
// ============ Public Functions ============

@ -26,6 +26,7 @@ describe('GovernanceRouter', async () => {
governance: GovernanceDeploy;
let outbox: Outbox;
let interchainGasPaymaster: InterchainGasPaymaster;
const testInterchainGasPayment = 123456789;
before(async () => {
[governor, recoveryManager] = await ethers.getSigners();
@ -111,6 +112,28 @@ describe('GovernanceRouter', async () => {
expect(await testSet.get()).to.equal(value);
});
it('allows interchain gas payment for remote calls', async () => {
const leafIndex = await outbox.count();
const value = 13;
const call = formatCall(testSet, 'set', [value]);
await expect(
await router.callRemote(domains[1], [call], {
value: testInterchainGasPayment,
}),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it('creates a checkpoint for remote calls', async () => {
const value = 13;
const call = formatCall(testSet, 'set', [value]);
await expect(await router.callRemote(domains[1], [call])).to.emit(
outbox,
'Checkpoint',
);
});
it('governor can set remote governor', async () => {
const newGovernor = governor.address;
expect(await remote.governor()).to.not.equal(newGovernor);
@ -119,6 +142,26 @@ describe('GovernanceRouter', async () => {
expect(await remote.governor()).to.equal(newGovernor);
});
it('allows interchain gas payment when setting a remote governor', async () => {
const newGovernor = governor.address;
const leafIndex = await outbox.count();
await expect(
router.setGovernorRemote(remoteDomain, newGovernor, {
value: testInterchainGasPayment,
}),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it('creates a checkpoint when setting a remote governor', async () => {
const newGovernor = governor.address;
await expect(router.setGovernorRemote(remoteDomain, newGovernor)).to.emit(
outbox,
'Checkpoint',
);
});
it('governor can set remote abacusConnectionManager', async () => {
const newConnectionManager = ethers.constants.AddressZero;
expect(await remote.abacusConnectionManager()).to.not.equal(
@ -134,6 +177,28 @@ describe('GovernanceRouter', async () => {
);
});
it('allows interchain gas payment when setting a remote abacusConnectionManager', async () => {
const leafIndex = await outbox.count();
await expect(
router.setAbacusConnectionManagerRemote(
remoteDomain,
ethers.constants.AddressZero,
{ value: testInterchainGasPayment },
),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it('creates a checkpoint when setting a remote abacusConnectionManager', async () => {
await expect(
router.setAbacusConnectionManagerRemote(
remoteDomain,
ethers.constants.AddressZero,
),
).to.emit(outbox, 'Checkpoint');
});
it('governor can enroll remote remote router', async () => {
expect(await remote.routers(testDomain)).to.equal(
ethers.constants.HashZero,
@ -148,6 +213,25 @@ describe('GovernanceRouter', async () => {
expect(await remote.routers(testDomain)).to.equal(newRouter);
});
it('allows interchain gas payment when enrolling a remote router', async () => {
const leafIndex = await outbox.count();
const newRouter = utils.addressToBytes32(router.address);
await expect(
router.enrollRemoteRouterRemote(remoteDomain, testDomain, newRouter, {
value: testInterchainGasPayment,
}),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it('creates a checkpoint when enrolling a remote router', async () => {
const newRouter = utils.addressToBytes32(router.address);
await expect(
router.enrollRemoteRouterRemote(remoteDomain, testDomain, newRouter),
).to.emit(outbox, 'Checkpoint');
});
it('governor cannot initiate recovery', async () => {
await expect(router.initiateRecoveryTimelock()).to.be.revertedWith(
'!recoveryManager',
@ -428,56 +512,4 @@ describe('GovernanceRouter', async () => {
);
});
});
describe('interchain gas payments for dispatched messages', async () => {
const testInterchainGasPayment = 123456789;
it('allows interchain gas payment for remote calls', async () => {
const leafIndex = await outbox.count();
const call = formatCall(testSet, 'set', [13]);
await expect(
await router.callRemote(domains[1], [call], {
value: testInterchainGasPayment,
}),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it('allows interchain gas payment when setting a remote governor', async () => {
const leafIndex = await outbox.count();
await expect(
router.setGovernorRemote(remoteDomain, governor.address, {
value: testInterchainGasPayment,
}),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it('allows interchain gas payment when setting a remote abacusConnectionManager', async () => {
const leafIndex = await outbox.count();
await expect(
router.setAbacusConnectionManagerRemote(
remoteDomain,
ethers.constants.AddressZero,
{ value: testInterchainGasPayment },
),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
it('allows interchain gas payment when enrolling a remote router', async () => {
const leafIndex = await outbox.count();
const newRouter = utils.addressToBytes32(router.address);
await expect(
router.enrollRemoteRouterRemote(remoteDomain, testDomain, newRouter, {
value: testInterchainGasPayment,
}),
)
.to.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(leafIndex, testInterchainGasPayment);
});
});
});

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

@ -1,5 +1,4 @@
import { ethers } from 'ethers';
import { TestDeploy } from './TestDeploy';
import {
AbacusConnectionManager,
AbacusConnectionManager__factory,
@ -14,8 +13,7 @@ import {
} from '@abacus-network/core';
import { types } from '@abacus-network/utils';
import { addressToBytes32 } from '@abacus-network/utils/dist/src/utils';
import { TestDeploy } from './TestDeploy';
import { ethers } from 'ethers';
export type TestAbacusConfig = {
signer: Record<types.Domain, ethers.Signer>;
@ -171,28 +169,17 @@ export class TestAbacusDeploy extends TestDeploy<
const responses: Map<types.Domain, ethers.providers.TransactionResponse[]> =
new Map();
const outbox = this.outbox(origin);
const [, checkpointedIndex] = await outbox.latestCheckpoint();
const messageCount = await outbox.count();
// Message count does allow for a checkpoint
if (messageCount.lte(checkpointedIndex.add(1))) return responses;
// Can't checkpoint a single message
if (messageCount.toNumber() <= 1) {
return responses;
}
await outbox.checkpoint();
const [root, index] = await outbox.latestCheckpoint();
for (const destination of this.remotes(origin)) {
const inbox = this.inbox(origin, destination);
await inbox.setCheckpoint(root, index);
}
// Find all unprocessed messages dispatched on the outbox since the previous checkpoint.
const dispatchFilter = outbox.filters.Dispatch();
const dispatches = await outbox.queryFilter(dispatchFilter);
for (const dispatch of dispatches) {
if (dispatch.args.leafIndex > index) {
// Message has not been checkpointed on the outbox
break;
}
const destination = dispatch.args.destination;
if (destination === origin)
throw new Error('Dispatched message to local domain');
@ -203,6 +190,15 @@ export class TestAbacusDeploy extends TestDeploy<
// disregard the dummy message
continue;
}
const [, inboxCheckpointIndex] = await inbox.latestCheckpoint();
if (
inboxCheckpointIndex < dispatch.args.leafIndex &&
inboxCheckpointIndex < index
) {
await inbox.setCheckpoint(root, index);
}
const response = await inbox.testProcess(
dispatch.args.message,
dispatch.args.leafIndex.toNumber(),

@ -1,7 +1,6 @@
import { types, utils } from '@abacus-network/utils';
import { TestAbacusDeploy } from './TestAbacusDeploy';
import { TestDeploy } from './TestDeploy';
import { types, utils } from '@abacus-network/utils';
export interface Router {
address: types.Address;

@ -1,9 +1,8 @@
import { TestAbacusDeploy } from '..';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { TestRecipient__factory } from '@abacus-network/core';
import { utils } from '@abacus-network/utils';
import { expect } from 'chai';
import { ethers } from 'hardhat';
const localDomain = 1000;
const remoteDomain = 2000;
@ -37,35 +36,57 @@ describe('TestAbacusDeploy', async () => {
).to.emit(remoteOutbox, 'Dispatch');
});
it('processes outbound messages for a single domain', async () => {
const responses = await abacus.processOutboundMessages(localDomain);
expect(responses.get(remoteDomain)!.length).to.equal(1);
const [_, index] = await abacus.outbox(localDomain).latestCheckpoint();
expect(index).to.equal(1);
describe('without a created checkpoint', () => {
it('does not process outbound messages', async () => {
const responses = await abacus.processOutboundMessages(localDomain);
expect(responses.get(remoteDomain)).to.be.undefined;
});
});
it('processes outbound messages for two domains', async () => {
const localResponses = await abacus.processOutboundMessages(localDomain);
expect(localResponses.get(remoteDomain)!.length).to.equal(1);
const [, localIndex] = await abacus.outbox(localDomain).latestCheckpoint();
expect(localIndex).to.equal(1);
const remoteResponses = await abacus.processOutboundMessages(remoteDomain);
expect(remoteResponses.get(localDomain)!.length).to.equal(1);
const [, remoteIndex] = await abacus
.outbox(remoteDomain)
.latestCheckpoint();
expect(remoteIndex).to.equal(1);
});
describe('with a checkpoint', () => {
beforeEach(async () => {
const localOutbox = abacus.outbox(localDomain);
const remoteOutbox = abacus.outbox(remoteDomain);
await localOutbox.checkpoint();
await remoteOutbox.checkpoint();
});
it('processes outbound messages for a single domain', async () => {
const responses = await abacus.processOutboundMessages(localDomain);
expect(responses.get(remoteDomain)!.length).to.equal(1);
const [_, index] = await abacus.outbox(localDomain).latestCheckpoint();
expect(index).to.equal(1);
});
it('processes outbound messages for two domains', async () => {
const localResponses = await abacus.processOutboundMessages(localDomain);
expect(localResponses.get(remoteDomain)!.length).to.equal(1);
const [, localIndex] = await abacus
.outbox(localDomain)
.latestCheckpoint();
expect(localIndex).to.equal(1);
const remoteResponses = await abacus.processOutboundMessages(
remoteDomain,
);
expect(remoteResponses.get(localDomain)!.length).to.equal(1);
const [, remoteIndex] = await abacus
.outbox(remoteDomain)
.latestCheckpoint();
expect(remoteIndex).to.equal(1);
});
it('processes all messages', async () => {
const responses = await abacus.processMessages();
expect(responses.get(localDomain)!.get(remoteDomain)!.length).to.equal(1);
expect(responses.get(remoteDomain)!.get(localDomain)!.length).to.equal(1);
const [, localIndex] = await abacus.outbox(localDomain).latestCheckpoint();
expect(localIndex).to.equal(1);
const [, remoteIndex] = await abacus
.outbox(remoteDomain)
.latestCheckpoint();
expect(remoteIndex).to.equal(1);
it('processes all messages', async () => {
const responses = await abacus.processMessages();
expect(responses.get(localDomain)!.get(remoteDomain)!.length).to.equal(1);
expect(responses.get(remoteDomain)!.get(localDomain)!.length).to.equal(1);
const [, localIndex] = await abacus
.outbox(localDomain)
.latestCheckpoint();
expect(localIndex).to.equal(1);
const [, remoteIndex] = await abacus
.outbox(remoteDomain)
.latestCheckpoint();
expect(remoteIndex).to.equal(1);
});
});
});

@ -119,13 +119,14 @@ task('kathy', 'Dispatches random abacus messages').setAction(
const coreContracts = core.getContracts(local);
const outbox = coreContracts.outbox.outbox;
// Send a batch of messages to the remote domain to test
// the checkpointer/relayer submitting only greedily
// the relayer submitting only greedily
for (let i = 0; i < 10; i++) {
await outbox.dispatch(
remote,
utils.addressToBytes32(recipient.address),
'0x1234',
);
await outbox.checkpoint();
console.log(
`send to ${recipient.address} on ${remote} at index ${
(await outbox.count()).toNumber() - 1

Loading…
Cancel
Save