The home for Hyperlane core contracts, sdk packages, and other infrastructure
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
hyperlane-monorepo/solidity/test/inbox.test.ts

234 lines
6.3 KiB

/* eslint-disable @typescript-eslint/no-floating-promises */
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import { types, utils } from '@hyperlane-xyz/utils';
import {
BadRecipient1__factory,
BadRecipient2__factory,
BadRecipient3__factory,
BadRecipient5__factory,
BadRecipient6__factory,
TestInbox,
TestInbox__factory,
TestOutbox,
TestOutbox__factory,
TestRecipient__factory,
TestValidatorManager,
TestValidatorManager__factory,
} from '../types';
import { MerkleProof, dispatchMessageAndReturnProof } from './lib/mailboxes';
const localDomain = 3000;
const remoteDomain = 1000;
describe('Inbox', async () => {
const badRecipientFactories = [
BadRecipient1__factory,
BadRecipient2__factory,
BadRecipient3__factory,
BadRecipient5__factory,
BadRecipient6__factory,
];
let inbox: TestInbox,
signer: SignerWithAddress,
validatorManager: TestValidatorManager,
helperOutbox: TestOutbox,
recipient: string,
proof: MerkleProof;
before(async () => {
[signer] = await ethers.getSigners();
// Inbox.initialize will ensure the validator manager is a contract.
// TestValidatorManager doesn't have any special logic, it just forwards
// calls to Inbox.process.
const testValidatorManagerFactory = new TestValidatorManager__factory(
signer,
);
validatorManager = await testValidatorManagerFactory.deploy();
const recipientF = new TestRecipient__factory(signer);
recipient = utils.addressToBytes32((await recipientF.deploy()).address);
// Deploy a helper outbox contract so that we can easily construct merkle
// proofs.
const outboxFactory = new TestOutbox__factory(signer);
helperOutbox = await outboxFactory.deploy(localDomain);
await helperOutbox.initialize(validatorManager.address);
proof = await dispatchMessageAndReturnProof(
helperOutbox,
remoteDomain,
recipient,
'hello world',
);
});
beforeEach(async () => {
const inboxFactory = new TestInbox__factory(signer);
inbox = await inboxFactory.deploy(remoteDomain);
await inbox.initialize(localDomain, validatorManager.address);
});
it('Cannot be initialized twice', async () => {
await expect(
inbox.initialize(localDomain, validatorManager.address),
).to.be.revertedWith('Initializable: contract is already initialized');
});
it('processes a message', async () => {
await expect(
validatorManager.process(
inbox.address,
proof.root,
proof.index,
proof.message,
proof.proof,
proof.index,
),
).to.emit(inbox, 'Process');
expect(await inbox.messages(proof.leaf)).to.eql(
types.MessageStatus.PROCESSED,
);
});
it('Rejects an already-processed message', async () => {
await inbox.setMessageStatus(proof.leaf, types.MessageStatus.PROCESSED);
// Try to process message again
await expect(
validatorManager.process(
inbox.address,
proof.root,
proof.index,
proof.message,
proof.proof,
proof.index,
),
).to.be.revertedWith('!MessageStatus.None');
});
it('Rejects invalid message proof', async () => {
// Switch ordering of proof hashes
// NB: We copy 'path' here to avoid mutating the test cases for
// other tests.
const newProof = proof.proof.slice().reverse();
expect(
validatorManager.process(
inbox.address,
proof.root,
proof.index,
proof.message,
newProof,
proof.index,
),
).to.be.revertedWith('!proof');
expect(await inbox.messages(proof.leaf)).to.equal(types.MessageStatus.NONE);
});
it('Fails to process message when not called by validator manager', async () => {
await expect(
inbox.process(
proof.root,
proof.index,
proof.message,
proof.proof,
proof.index,
),
).to.be.revertedWith('!validatorManager');
});
for (let i = 0; i < badRecipientFactories.length; i++) {
it(`Fails to process a message for a badly implemented recipient (${
i + 1
})`, async () => {
const factory = new badRecipientFactories[i](signer);
const badRecipient = await factory.deploy();
const badProof = await dispatchMessageAndReturnProof(
helperOutbox,
localDomain,
utils.addressToBytes32(badRecipient.address),
'hello world',
);
await expect(
validatorManager.process(
inbox.address,
badProof.root,
badProof.index,
badProof.message,
badProof.proof,
badProof.index,
),
).to.be.reverted;
});
}
it('Fails to process message with wrong origin Domain', async () => {
const outboxFactory = new TestOutbox__factory(signer);
const originOutbox = await outboxFactory.deploy(localDomain + 1);
await originOutbox.initialize(validatorManager.address);
const proof = await dispatchMessageAndReturnProof(
originOutbox,
localDomain,
recipient,
'hello world',
);
await expect(
validatorManager.process(
inbox.address,
proof.root,
proof.index,
proof.message,
proof.proof,
proof.index,
),
).to.be.revertedWith('!origin');
});
it('Fails to process message with wrong destination Domain', async () => {
const badProof = await dispatchMessageAndReturnProof(
helperOutbox,
localDomain + 1,
recipient,
'hello world',
);
await expect(
validatorManager.process(
inbox.address,
badProof.root,
badProof.index,
badProof.message,
badProof.proof,
badProof.index,
),
).to.be.revertedWith('!destination');
});
it('Fails to process message sent to a non-existent contract address', async () => {
const badProof = await dispatchMessageAndReturnProof(
helperOutbox,
localDomain,
utils.addressToBytes32('0x1234567890123456789012345678901234567890'), // non-existent contract address
'hello world',
);
await expect(
validatorManager.process(
inbox.address,
badProof.root,
badProof.index,
badProof.message,
badProof.proof,
badProof.index,
),
).to.be.reverted;
});
});