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.
262 lines
9.0 KiB
262 lines
9.0 KiB
import { ethers, abacus } from 'hardhat';
|
|
import { expect } from 'chai';
|
|
import { AbacusState, Updater } from './lib/core';
|
|
import { Signer } from './lib/types';
|
|
|
|
import {
|
|
TestHome,
|
|
TestHome__factory,
|
|
UpdaterManager__factory,
|
|
UpdaterManager,
|
|
} from '../typechain';
|
|
|
|
const homeDomainHashTestCases = require('../../../vectors/homeDomainHash.json');
|
|
const destinationNonceTestCases = require('../../../vectors/destinationNonce.json');
|
|
|
|
const localDomain = 1000;
|
|
const destDomain = 2000;
|
|
const emptyAddress: string = '0x' + '00'.repeat(32);
|
|
|
|
describe('Home', async () => {
|
|
let home: TestHome,
|
|
signer: Signer,
|
|
fakeSigner: Signer,
|
|
recipient: Signer,
|
|
updater: Updater,
|
|
fakeUpdater: Updater,
|
|
updaterManager: UpdaterManager;
|
|
|
|
// Helper function that dispatches message and returns intermediate root.
|
|
// The message recipient is the same for all messages dispatched.
|
|
const dispatchMessageAndGetRoot = async (message: string) => {
|
|
message = ethers.utils.formatBytes32String(message);
|
|
await home.dispatch(
|
|
destDomain,
|
|
abacus.ethersAddressToBytes32(recipient.address),
|
|
message,
|
|
);
|
|
const [, latestRoot] = await home.suggestUpdate();
|
|
return latestRoot;
|
|
};
|
|
|
|
before(async () => {
|
|
[signer, fakeSigner, recipient] = await ethers.getSigners();
|
|
updater = await Updater.fromSigner(signer, localDomain);
|
|
fakeUpdater = await Updater.fromSigner(fakeSigner, localDomain);
|
|
|
|
// deploy UpdaterManagers
|
|
const updaterManagerFactory = new UpdaterManager__factory(signer);
|
|
updaterManager = await updaterManagerFactory.deploy(updater.address);
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
// redeploy the home before each test run
|
|
const homeFactory = new TestHome__factory(signer);
|
|
home = await homeFactory.deploy(localDomain);
|
|
await home.initialize(updaterManager.address);
|
|
// set home on UpdaterManager
|
|
await updaterManager.setHome(home.address);
|
|
});
|
|
|
|
it('Cannot be initialized twice', async () => {
|
|
await expect(home.initialize(updaterManager.address)).to.be.revertedWith(
|
|
'Initializable: contract is already initialized',
|
|
);
|
|
});
|
|
|
|
it('Halts on fail', async () => {
|
|
await home.setFailed();
|
|
expect(await home.state()).to.equal(AbacusState.FAILED);
|
|
|
|
const message = ethers.utils.formatBytes32String('message');
|
|
await expect(
|
|
home.dispatch(
|
|
destDomain,
|
|
abacus.ethersAddressToBytes32(recipient.address),
|
|
message,
|
|
),
|
|
).to.be.revertedWith('failed state');
|
|
});
|
|
|
|
it('Calculated domain hash matches Rust-produced domain hash', async () => {
|
|
// Compare Rust output in json file to solidity output (json file matches
|
|
// hash for local domain of 1000)
|
|
for (let testCase of homeDomainHashTestCases) {
|
|
const homeFactory = new TestHome__factory(signer);
|
|
const tempHome = await homeFactory.deploy(testCase.homeDomain);
|
|
const { expectedDomainHash } = testCase;
|
|
const homeDomainHash = await tempHome.homeDomainHash();
|
|
expect(homeDomainHash).to.equal(expectedDomainHash);
|
|
}
|
|
});
|
|
|
|
it('Does not dispatch too large messages', async () => {
|
|
const message = `0x${Buffer.alloc(3000).toString('hex')}`;
|
|
await expect(
|
|
home
|
|
.connect(signer)
|
|
.dispatch(
|
|
destDomain,
|
|
abacus.ethersAddressToBytes32(recipient.address),
|
|
message,
|
|
),
|
|
).to.be.revertedWith('msg too long');
|
|
});
|
|
|
|
it('Dispatches a message', async () => {
|
|
const message = ethers.utils.formatBytes32String('message');
|
|
const nonce = await home.nonces(localDomain);
|
|
|
|
// Format data that will be emitted from Dispatch event
|
|
const destinationAndNonce = abacus.destinationAndNonce(destDomain, nonce);
|
|
|
|
const abacusMessage = abacus.formatMessage(
|
|
localDomain,
|
|
signer.address,
|
|
nonce,
|
|
destDomain,
|
|
recipient.address,
|
|
message,
|
|
);
|
|
const messageHash = abacus.messageHash(abacusMessage);
|
|
const leafIndex = await home.tree();
|
|
const committedRoot = await home.committedRoot();
|
|
|
|
// Send message with signer address as msg.sender
|
|
await expect(
|
|
home
|
|
.connect(signer)
|
|
.dispatch(
|
|
destDomain,
|
|
abacus.ethersAddressToBytes32(recipient.address),
|
|
message,
|
|
),
|
|
)
|
|
.to.emit(home, 'Dispatch')
|
|
.withArgs(
|
|
messageHash,
|
|
leafIndex,
|
|
destinationAndNonce,
|
|
committedRoot,
|
|
abacusMessage,
|
|
);
|
|
});
|
|
|
|
it('Suggests current root and latest root on suggestUpdate', async () => {
|
|
const committedRoot = await home.committedRoot();
|
|
const message = ethers.utils.formatBytes32String('message');
|
|
await home.dispatch(
|
|
destDomain,
|
|
abacus.ethersAddressToBytes32(recipient.address),
|
|
message,
|
|
);
|
|
const latestEnqueuedRoot = await home.queueEnd();
|
|
const [suggestedCommitted, suggestedNew] = await home.suggestUpdate();
|
|
expect(suggestedCommitted).to.equal(committedRoot);
|
|
expect(suggestedNew).to.equal(latestEnqueuedRoot);
|
|
});
|
|
|
|
it('Suggests empty update values when queue is empty', async () => {
|
|
const length = await home.queueLength();
|
|
expect(length).to.equal(0);
|
|
|
|
const [suggestedCommitted, suggestedNew] = await home.suggestUpdate();
|
|
expect(suggestedCommitted).to.equal(emptyAddress);
|
|
expect(suggestedNew).to.equal(emptyAddress);
|
|
});
|
|
|
|
it('Accepts a valid update', async () => {
|
|
const committedRoot = await home.committedRoot();
|
|
const newRoot = await dispatchMessageAndGetRoot('message');
|
|
const { signature } = await updater.signUpdate(committedRoot, newRoot);
|
|
|
|
await expect(home.update(committedRoot, newRoot, signature))
|
|
.to.emit(home, 'Update')
|
|
.withArgs(localDomain, committedRoot, newRoot, signature);
|
|
expect(await home.committedRoot()).to.equal(newRoot);
|
|
expect(await home.queueContains(newRoot)).to.be.false;
|
|
});
|
|
|
|
it('Batch-accepts several updates', async () => {
|
|
const committedRoot = await home.committedRoot();
|
|
const newRoot1 = await dispatchMessageAndGetRoot('message1');
|
|
const newRoot2 = await dispatchMessageAndGetRoot('message2');
|
|
const newRoot3 = await dispatchMessageAndGetRoot('message3');
|
|
const { signature } = await updater.signUpdate(committedRoot, newRoot3);
|
|
|
|
await expect(home.update(committedRoot, newRoot3, signature))
|
|
.to.emit(home, 'Update')
|
|
.withArgs(localDomain, committedRoot, newRoot3, signature);
|
|
expect(await home.committedRoot()).to.equal(newRoot3);
|
|
expect(await home.queueContains(newRoot1)).to.be.false;
|
|
expect(await home.queueContains(newRoot2)).to.be.false;
|
|
expect(await home.queueContains(newRoot3)).to.be.false;
|
|
});
|
|
|
|
it('Rejects update that does not build off of current root', async () => {
|
|
// First root is committedRoot
|
|
const secondRoot = await dispatchMessageAndGetRoot('message');
|
|
const thirdRoot = await dispatchMessageAndGetRoot('message2');
|
|
|
|
// Try to submit update that skips the current (first) root
|
|
const { signature } = await updater.signUpdate(secondRoot, thirdRoot);
|
|
await expect(
|
|
home.update(secondRoot, thirdRoot, signature),
|
|
).to.be.revertedWith('not a current update');
|
|
});
|
|
|
|
it('Rejects update that does not exist in queue', async () => {
|
|
const committedRoot = await home.committedRoot();
|
|
const fakeNewRoot = ethers.utils.formatBytes32String('fake root');
|
|
const { signature } = await updater.signUpdate(committedRoot, fakeNewRoot);
|
|
|
|
await expect(home.update(committedRoot, fakeNewRoot, signature)).to.emit(
|
|
home,
|
|
'ImproperUpdate',
|
|
);
|
|
expect(await home.state()).to.equal(AbacusState.FAILED);
|
|
});
|
|
|
|
it('Rejects update from non-updater address', async () => {
|
|
const committedRoot = await home.committedRoot();
|
|
const newRoot = await dispatchMessageAndGetRoot('message');
|
|
const { signature: fakeSignature } = await fakeUpdater.signUpdate(
|
|
committedRoot,
|
|
newRoot,
|
|
);
|
|
await expect(
|
|
home.update(committedRoot, newRoot, fakeSignature),
|
|
).to.be.revertedWith('!updater sig');
|
|
});
|
|
|
|
it('Fails on valid double update proof', async () => {
|
|
const firstRoot = await home.committedRoot();
|
|
const secondRoot = await dispatchMessageAndGetRoot('message');
|
|
const thirdRoot = await dispatchMessageAndGetRoot('message2');
|
|
const { signature } = await updater.signUpdate(firstRoot, secondRoot);
|
|
const { signature: signature2 } = await updater.signUpdate(
|
|
firstRoot,
|
|
thirdRoot,
|
|
);
|
|
await expect(
|
|
home.doubleUpdate(
|
|
firstRoot,
|
|
[secondRoot, thirdRoot],
|
|
signature,
|
|
signature2,
|
|
),
|
|
).to.emit(home, 'DoubleUpdate');
|
|
expect(await home.state()).to.equal(AbacusState.FAILED);
|
|
});
|
|
|
|
it('Correctly calculates destinationAndNonce', async () => {
|
|
for (let testCase of destinationNonceTestCases) {
|
|
let { destination, nonce, expectedDestinationAndNonce } = testCase;
|
|
const solidityDestinationAndNonce = await home.destinationAndNonce(
|
|
destination,
|
|
nonce,
|
|
);
|
|
expect(solidityDestinationAndNonce).to.equal(expectedDestinationAndNonce);
|
|
}
|
|
});
|
|
});
|
|
|