Skip to content

Commit

Permalink
SDK 0.2.1 (#537)
Browse files Browse the repository at this point in the history
* Alpha7

* Checker

* Move AbacusCoreChecker to deploy
  • Loading branch information
nambrot authored Jun 14, 2022
1 parent 630e20b commit 55c9750
Show file tree
Hide file tree
Showing 47 changed files with 367 additions and 266 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"private": true,
"scripts": {
"build": "yarn workspaces foreach --verbose --parallel --topological run build",
"publish": "yarn workspaces foreach --no-private --verbose --topological npm publish --access public",
"postinstall": "husky install",
"prettier": "yarn workspaces foreach --verbose --parallel run prettier",
"lint-ts": "eslint . --ext .ts",
Expand Down
2 changes: 1 addition & 1 deletion solidity/app/contracts/AbacusConnectionClient.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ abstract contract AbacusConnectionClient is OwnableUpgradeable {

function __AbacusConnectionClient_initialize(
address _abacusConnectionManager
) internal {
) internal onlyInitializing {
_setAbacusConnectionManager(_abacusConnectionManager);
__Ownable_init();
}
Expand Down
12 changes: 11 additions & 1 deletion solidity/app/contracts/Router.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@ abstract contract Router is AbacusConnectionClient, IMessageRecipient {
}

// ======== Initializer =========
function initialize(address _abacusConnectionManager)
external
virtual
initializer
{
__Router_initialize(_abacusConnectionManager);
}

function __Router_initialize(address _abacusConnectionManager) internal {
function __Router_initialize(address _abacusConnectionManager)
internal
onlyInitializing
{
__AbacusConnectionClient_initialize(_abacusConnectionManager);
}

Expand Down
11 changes: 9 additions & 2 deletions solidity/app/contracts/test/TestRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ pragma solidity >=0.6.11;
import "../Router.sol";

contract TestRouter is Router {
function initialize(address _abacusConnectionManager) external initializer {
__Router_initialize(_abacusConnectionManager);
event InitializeOverload();

function initialize(address abacusConnectionManager)
external
override
initializer
{
__Router_initialize(abacusConnectionManager);
emit InitializeOverload();
}

function _handle(
Expand Down
1 change: 0 additions & 1 deletion solidity/app/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ module.exports = {
typechain: {
outDir: './types',
target: 'ethers-v5',
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
},
};
7 changes: 4 additions & 3 deletions solidity/app/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "@abacus-network/app",
"description": "Solidity contracts for Abacus apps",
"version": "0.2.1-alpha3",
"version": "0.2.1",
"dependencies": {
"@abacus-network/core": "^0.2.1-alpha3",
"@abacus-network/utils": "^0.2.1-alpha3",
"@abacus-network/core": "^0.2.1",
"@abacus-network/utils": "^0.2.1",
"@openzeppelin/contracts-upgradeable": "^4.5.0"
},
"devDependencies": {
Expand Down Expand Up @@ -45,6 +45,7 @@
"repository": "https://github.com/abacus-network/abacus-monorepo",
"scripts": {
"build": "hardhat compile && tsc",
"clean": "hardhat clean",
"coverage": "hardhat coverage",
"prettier": "prettier --write ./contracts ./test",
"test": "hardhat test"
Expand Down
260 changes: 147 additions & 113 deletions solidity/app/test/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,138 +49,172 @@ describe('Router', async () => {
);
await connectionManager.setOutbox(outbox.address);

const routerFactory = new TestRouter__factory(signer);
router = await routerFactory.deploy();
await router.initialize(connectionManager.address);
router = await new TestRouter__factory(signer).deploy();
});

it('Cannot be initialized twice', async () => {
await expect(
router.initialize(ethers.constants.AddressZero),
).to.be.revertedWith('Initializable: contract is already initialized');
});
describe('#initialize', () => {
it('should set the abacus connection manager', async () => {
await router.initialize(connectionManager.address);
expect(await router.abacusConnectionManager()).to.equal(
connectionManager.address,
);
});

it('accepts message from enrolled inbox and router', async () => {
await connectionManager.enrollInbox(origin, signer.address);
const remote = utils.addressToBytes32(nonOwner.address);
await router.enrollRemoteRouter(origin, remote);
// Does not revert.
await router.handle(origin, remote, message);
});
it('should transfer owner to deployer', async () => {
await router.initialize(connectionManager.address);
expect(await router.owner()).to.equal(signer.address);
});

it('rejects message from unenrolled inbox', async () => {
await expect(
router.handle(origin, utils.addressToBytes32(nonOwner.address), message),
).to.be.revertedWith('!inbox');
});
it('should use overloaded initialize', async () => {
await expect(router.initialize(connectionManager.address)).to.emit(
router,
'InitializeOverload',
);
});

it('rejects message from unenrolled router', async () => {
await connectionManager.enrollInbox(origin, signer.address);
await expect(
router.handle(origin, utils.addressToBytes32(nonOwner.address), message),
).to.be.revertedWith('!router');
it('cannot be initialized twice', async () => {
await router.initialize(ethers.constants.AddressZero);
await expect(
router.initialize(ethers.constants.AddressZero),
).to.be.revertedWith('Initializable: contract is already initialized');
});
});

it('owner can enroll remote router', async () => {
const remote = nonOwner.address;
const remoteBytes = utils.addressToBytes32(nonOwner.address);
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(false);
await expect(router.mustHaveRemoteRouter(origin)).to.be.revertedWith(
'!router',
);
await expect(
router.enrollRemoteRouter(origin, utils.addressToBytes32(remote)),
).to.emit(router, 'RemoteRouterEnrolled');
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(true);
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes);
});
describe('when initialized', () => {
beforeEach(async () => {
await router.initialize(connectionManager.address);
});

it('non-owner cannot enroll remote router', async () => {
await expect(
router
.connect(nonOwner)
.enrollRemoteRouter(origin, utils.addressToBytes32(nonOwner.address)),
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG);
});
it('accepts message from enrolled inbox and router', async () => {
await connectionManager.enrollInbox(origin, signer.address);
const remote = utils.addressToBytes32(nonOwner.address);
await router.enrollRemoteRouter(origin, remote);
// Does not revert.
await router.handle(origin, remote, message);
});

describe('dispatch functions', () => {
let interchainGasPaymaster: InterchainGasPaymaster;
beforeEach(async () => {
const interchainGasPaymasterFactory = new InterchainGasPaymaster__factory(
signer,
);
interchainGasPaymaster = await interchainGasPaymasterFactory.deploy();
await router.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),
it('rejects message from unenrolled inbox', async () => {
await expect(
router.handle(
origin,
utils.addressToBytes32(nonOwner.address),
message,
),
).to.be.revertedWith('!inbox');
});

it('rejects message from unenrolled router', async () => {
await connectionManager.enrollInbox(origin, signer.address);
await expect(
router.handle(
origin,
utils.addressToBytes32(nonOwner.address),
message,
),
).to.be.revertedWith('!router');
});

it('owner can enroll remote router', async () => {
const remote = nonOwner.address;
const remoteBytes = utils.addressToBytes32(nonOwner.address);
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(false);
await expect(router.mustHaveRemoteRouter(origin)).to.be.revertedWith(
'!router',
);
await router.enrollRemoteRouter(origin, utils.addressToBytes32(remote));
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(true);
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes);
});

// Helper for testing different variations of dispatch functions
const runDispatchFunctionTests = async (
dispatchFunction: (
destinationDomain: number,
interchainGasPayment?: number,
) => Promise<ContractTransaction>,
expectGasPayment: boolean,
) => {
// Allows a Chai Assertion to be programmatically negated
const expectAssertion = (
assertion: Chai.Assertion,
expected: boolean,
) => {
return expected ? assertion : assertion.not;
};
it('non-owner cannot enroll remote router', async () => {
await expect(
router
.connect(nonOwner)
.enrollRemoteRouter(origin, utils.addressToBytes32(nonOwner.address)),
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG);
});

it('dispatches a message', async () => {
await expect(dispatchFunction(destination)).to.emit(outbox, 'Dispatch');
describe('dispatch functions', () => {
let interchainGasPaymaster: InterchainGasPaymaster;
beforeEach(async () => {
const interchainGasPaymasterFactory =
new InterchainGasPaymaster__factory(signer);
interchainGasPaymaster = await interchainGasPaymasterFactory.deploy();
await router.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),
);
});

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,
// Helper for testing different variations of dispatch functions
const runDispatchFunctionTests = async (
dispatchFunction: (
destinationDomain: number,
interchainGasPayment?: number,
) => Promise<ContractTransaction>,
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(outbox.address, leafIndex, testInterchainGasPayment);
});

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,
);
await assertion
.emit(interchainGasPaymaster, 'GasPayment')
.withArgs(outbox.address, leafIndex, testInterchainGasPayment);
});

it('reverts when dispatching a message to an unenrolled remote router', async () => {
await expect(
dispatchFunction(destinationWithoutRouter),
).to.be.revertedWith('!router');
describe('#dispatchWithGas', () => {
runDispatchFunctionTests(
(destinationDomain, interchainGasPayment = 0) =>
router.dispatchWithGas(
destinationDomain,
'0x',
interchainGasPayment,
{
value: interchainGasPayment,
},
),
true,
);
});
};

describe('#dispatch', () => {
runDispatchFunctionTests(
(destinationDomain) => router.dispatch(destinationDomain, '0x'),
false,
);
});

describe('#dispatchWithGas', () => {
runDispatchFunctionTests(
(destinationDomain, interchainGasPayment = 0) =>
router.dispatchWithGas(
destinationDomain,
'0x',
interchainGasPayment,
{
value: interchainGasPayment,
},
),
true,
);
});
});
});
2 changes: 1 addition & 1 deletion solidity/app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
},
"exclude": ["./node_modules/", "./dist/", "./types/hardhat.d.ts"],
"extends": "../../tsconfig.json",
"include": ["./types/*.ts", "./types/factories/*.ts"]
"include": ["./types/**/*.ts"]
}
6 changes: 3 additions & 3 deletions solidity/core/contracts/Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pragma solidity >=0.8.0;
// ============ Internal Imports ============
import {Version0} from "./Version0.sol";
import {Mailbox} from "./Mailbox.sol";
import {MerkleLib} from "../libs/Merkle.sol";
import {Message} from "../libs/Message.sol";
import {TypeCasts} from "../libs/TypeCasts.sol";
import {MerkleLib} from "./libs/Merkle.sol";
import {Message} from "./libs/Message.sol";
import {TypeCasts} from "./libs/TypeCasts.sol";
import {IMessageRecipient} from "../interfaces/IMessageRecipient.sol";
import {IInbox} from "../interfaces/IInbox.sol";

Expand Down
2 changes: 1 addition & 1 deletion solidity/core/contracts/MerkleTreeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity >=0.8.0;

// ============ Internal Imports ============
import {MerkleLib} from "../libs/Merkle.sol";
import {MerkleLib} from "./libs/Merkle.sol";

/**
* @title MerkleTreeManager
Expand Down
Loading

0 comments on commit 55c9750

Please sign in to comment.