|
|
@ -3,12 +3,13 @@ import { expect } from 'chai'; |
|
|
|
import { ethers } from 'hardhat'; |
|
|
|
import { ethers } from 'hardhat'; |
|
|
|
|
|
|
|
|
|
|
|
import { Validator, types, utils } from '@abacus-network/utils'; |
|
|
|
import { Validator, types, utils } from '@abacus-network/utils'; |
|
|
|
|
|
|
|
import { BytesArray } from '@abacus-network/utils/dist/src/types'; |
|
|
|
|
|
|
|
|
|
|
|
import { |
|
|
|
import { |
|
|
|
Outbox, |
|
|
|
|
|
|
|
OutboxValidatorManager, |
|
|
|
OutboxValidatorManager, |
|
|
|
OutboxValidatorManager__factory, |
|
|
|
OutboxValidatorManager__factory, |
|
|
|
Outbox__factory, |
|
|
|
TestOutbox, |
|
|
|
|
|
|
|
TestOutbox__factory, |
|
|
|
} from '../../types'; |
|
|
|
} from '../../types'; |
|
|
|
|
|
|
|
|
|
|
|
import { signCheckpoint } from './utils'; |
|
|
|
import { signCheckpoint } from './utils'; |
|
|
@ -17,13 +18,53 @@ const OUTBOX_DOMAIN = 1234; |
|
|
|
const INBOX_DOMAIN = 4321; |
|
|
|
const INBOX_DOMAIN = 4321; |
|
|
|
const QUORUM_THRESHOLD = 2; |
|
|
|
const QUORUM_THRESHOLD = 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface MerkleProof { |
|
|
|
|
|
|
|
root: string; |
|
|
|
|
|
|
|
proof: BytesArray; |
|
|
|
|
|
|
|
leaf: string; |
|
|
|
|
|
|
|
index: number; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
describe('OutboxValidatorManager', () => { |
|
|
|
describe('OutboxValidatorManager', () => { |
|
|
|
let validatorManager: OutboxValidatorManager, |
|
|
|
let validatorManager: OutboxValidatorManager, |
|
|
|
outbox: Outbox, |
|
|
|
outbox: TestOutbox, |
|
|
|
|
|
|
|
helperOutbox: TestOutbox, |
|
|
|
signer: SignerWithAddress, |
|
|
|
signer: SignerWithAddress, |
|
|
|
validator0: Validator, |
|
|
|
validator0: Validator, |
|
|
|
validator1: Validator; |
|
|
|
validator1: Validator; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dispatchMessage = async (outbox: TestOutbox, message: string) => { |
|
|
|
|
|
|
|
const recipient = utils.addressToBytes32(validator0.address); |
|
|
|
|
|
|
|
const destination = INBOX_DOMAIN; |
|
|
|
|
|
|
|
const tx = await outbox.dispatch( |
|
|
|
|
|
|
|
destination, |
|
|
|
|
|
|
|
recipient, |
|
|
|
|
|
|
|
ethers.utils.formatBytes32String(message), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const receipt = await tx.wait(); |
|
|
|
|
|
|
|
const dispatch = receipt.events![0]; |
|
|
|
|
|
|
|
expect(dispatch.event).to.equal('Dispatch'); |
|
|
|
|
|
|
|
return dispatch.args!; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dispatchMessageAndReturnProof = async ( |
|
|
|
|
|
|
|
outbox: TestOutbox, |
|
|
|
|
|
|
|
messageStr: string, |
|
|
|
|
|
|
|
) => { |
|
|
|
|
|
|
|
const { messageHash, leafIndex } = await dispatchMessage( |
|
|
|
|
|
|
|
outbox, |
|
|
|
|
|
|
|
messageStr, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const root = await outbox.root(); |
|
|
|
|
|
|
|
const proof = await outbox.proof(); |
|
|
|
|
|
|
|
return { |
|
|
|
|
|
|
|
root, |
|
|
|
|
|
|
|
proof, |
|
|
|
|
|
|
|
leaf: messageHash, |
|
|
|
|
|
|
|
index: leafIndex, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
before(async () => { |
|
|
|
before(async () => { |
|
|
|
const signers = await ethers.getSigners(); |
|
|
|
const signers = await ethers.getSigners(); |
|
|
|
signer = signers[0]; |
|
|
|
signer = signers[0]; |
|
|
@ -39,82 +80,331 @@ describe('OutboxValidatorManager', () => { |
|
|
|
QUORUM_THRESHOLD, |
|
|
|
QUORUM_THRESHOLD, |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const outboxFactory = new Outbox__factory(signer); |
|
|
|
const outboxFactory = new TestOutbox__factory(signer); |
|
|
|
outbox = await outboxFactory.deploy(OUTBOX_DOMAIN); |
|
|
|
outbox = await outboxFactory.deploy(OUTBOX_DOMAIN); |
|
|
|
await outbox.initialize(validatorManager.address); |
|
|
|
await outbox.initialize(validatorManager.address); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Deploy a second Outbox for convenience. We push a fraudulent message to this Outbox
|
|
|
|
|
|
|
|
// and use it to generate a fraudulent merkle proof.
|
|
|
|
|
|
|
|
helperOutbox = await outboxFactory.deploy(OUTBOX_DOMAIN); |
|
|
|
|
|
|
|
await helperOutbox.initialize(validatorManager.address); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('#improperCheckpoint', () => { |
|
|
|
describe('#prematureCheckpoint', () => { |
|
|
|
|
|
|
|
const messageCount = 1; |
|
|
|
|
|
|
|
// An premature checkpoint is one that has index greater than the latest index
|
|
|
|
|
|
|
|
// in the Outbox.
|
|
|
|
|
|
|
|
const prematureIndex = messageCount; |
|
|
|
const root = ethers.utils.formatBytes32String('test root'); |
|
|
|
const root = ethers.utils.formatBytes32String('test root'); |
|
|
|
const index = 1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('accepts an improper checkpoint if there is a quorum', async () => { |
|
|
|
beforeEach(async () => { |
|
|
|
|
|
|
|
await dispatchMessage(outbox, 'message'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('accepts a premature checkpoint if it has been signed by a quorum of validators', async () => { |
|
|
|
const signatures = await signCheckpoint( |
|
|
|
const signatures = await signCheckpoint( |
|
|
|
root, |
|
|
|
root, |
|
|
|
index, |
|
|
|
prematureIndex, |
|
|
|
[validator0, validator1], // 2/2 signers, making a quorum
|
|
|
|
[validator0, validator1], // 2/2 signers is a quorum
|
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
await expect( |
|
|
|
validatorManager.improperCheckpoint( |
|
|
|
validatorManager.prematureCheckpoint( |
|
|
|
outbox.address, |
|
|
|
outbox.address, |
|
|
|
root, |
|
|
|
root, |
|
|
|
index, |
|
|
|
prematureIndex, |
|
|
|
signatures, |
|
|
|
signatures, |
|
|
|
), |
|
|
|
), |
|
|
|
) |
|
|
|
) |
|
|
|
.to.emit(validatorManager, 'ImproperCheckpoint') |
|
|
|
.to.emit(validatorManager, 'PrematureCheckpoint') |
|
|
|
.withArgs(outbox.address, root, index, signatures); |
|
|
|
.withArgs( |
|
|
|
|
|
|
|
outbox.address, |
|
|
|
|
|
|
|
root, |
|
|
|
|
|
|
|
prematureIndex, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
messageCount, |
|
|
|
|
|
|
|
); |
|
|
|
expect(await outbox.state()).to.equal(types.AbacusState.FAILED); |
|
|
|
expect(await outbox.state()).to.equal(types.AbacusState.FAILED); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('reverts if there is not a quorum', async () => { |
|
|
|
it('reverts if a premature checkpoint has not been signed a quorum of validators', async () => { |
|
|
|
const signatures = await signCheckpoint( |
|
|
|
const signatures = await signCheckpoint( |
|
|
|
root, |
|
|
|
root, |
|
|
|
index, |
|
|
|
prematureIndex, |
|
|
|
[validator0], // 1/2 signers is not a quorum
|
|
|
|
[validator0], // 1/2 signers is not a quorum
|
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
await expect( |
|
|
|
validatorManager.improperCheckpoint( |
|
|
|
validatorManager.prematureCheckpoint( |
|
|
|
outbox.address, |
|
|
|
outbox.address, |
|
|
|
root, |
|
|
|
root, |
|
|
|
index, |
|
|
|
prematureIndex, |
|
|
|
signatures, |
|
|
|
signatures, |
|
|
|
), |
|
|
|
), |
|
|
|
).to.be.revertedWith('!quorum'); |
|
|
|
).to.be.revertedWith('!quorum'); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('reverts if the checkpoint is not improper', async () => { |
|
|
|
it('reverts if a non-premature checkpoint has been signed by a quorum of validators', async () => { |
|
|
|
const message = `0x${Buffer.alloc(10).toString('hex')}`; |
|
|
|
const validIndex = messageCount - 1; |
|
|
|
// Send two messages to allow checkpointing
|
|
|
|
const signatures = await signCheckpoint( |
|
|
|
await outbox.dispatch( |
|
|
|
root, |
|
|
|
INBOX_DOMAIN, |
|
|
|
validIndex, |
|
|
|
utils.addressToBytes32(signer.address), |
|
|
|
[validator0, validator1], // 2/2 signers is a quorum
|
|
|
|
message, |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
await outbox.dispatch( |
|
|
|
|
|
|
|
INBOX_DOMAIN, |
|
|
|
await expect( |
|
|
|
utils.addressToBytes32(signer.address), |
|
|
|
validatorManager.prematureCheckpoint( |
|
|
|
message, |
|
|
|
outbox.address, |
|
|
|
|
|
|
|
root, |
|
|
|
|
|
|
|
validIndex, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.revertedWith('!premature'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const dispatchMessagesAndReturnProofs = async (args: { |
|
|
|
|
|
|
|
differingIndex: number; |
|
|
|
|
|
|
|
proofIndex: number; |
|
|
|
|
|
|
|
messageCount: number; |
|
|
|
|
|
|
|
}) => { |
|
|
|
|
|
|
|
const { differingIndex, proofIndex, messageCount } = args; |
|
|
|
|
|
|
|
const actualMessage = 'message'; |
|
|
|
|
|
|
|
const fraudulentMessage = 'fraud'; |
|
|
|
|
|
|
|
let index = 0; |
|
|
|
|
|
|
|
const helperMessage = (j: number) => |
|
|
|
|
|
|
|
j === differingIndex ? fraudulentMessage : actualMessage; |
|
|
|
|
|
|
|
for (; index < proofIndex; index++) { |
|
|
|
|
|
|
|
await dispatchMessage(outbox, actualMessage); |
|
|
|
|
|
|
|
await dispatchMessage(helperOutbox, helperMessage(index)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
const proofA = await dispatchMessageAndReturnProof(outbox, actualMessage); |
|
|
|
|
|
|
|
const proofB = await dispatchMessageAndReturnProof( |
|
|
|
|
|
|
|
helperOutbox, |
|
|
|
|
|
|
|
helperMessage(proofIndex), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
for (index = proofIndex + 1; index < messageCount; index++) { |
|
|
|
|
|
|
|
await dispatchMessage(outbox, actualMessage); |
|
|
|
|
|
|
|
await dispatchMessage(helperOutbox, helperMessage(index)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return { proofA: proofA, proofB: proofB }; |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
describe('#impliesDifferingLeaf', async () => { |
|
|
|
|
|
|
|
it('returns true when proving a leaf with index greater than the differing leaf', async () => { |
|
|
|
|
|
|
|
const { proofA, proofB } = await dispatchMessagesAndReturnProofs({ |
|
|
|
|
|
|
|
differingIndex: 3, |
|
|
|
|
|
|
|
proofIndex: 4, |
|
|
|
|
|
|
|
messageCount: 5, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
expect( |
|
|
|
|
|
|
|
await validatorManager.impliesDifferingLeaf( |
|
|
|
|
|
|
|
proofA.leaf, |
|
|
|
|
|
|
|
proofA.proof, |
|
|
|
|
|
|
|
proofB.leaf, |
|
|
|
|
|
|
|
proofB.proof, |
|
|
|
|
|
|
|
proofA.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.true; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('returns true when proving a leaf with index equal to the differing leaf', async () => { |
|
|
|
|
|
|
|
const { proofA, proofB } = await dispatchMessagesAndReturnProofs({ |
|
|
|
|
|
|
|
differingIndex: 4, |
|
|
|
|
|
|
|
proofIndex: 4, |
|
|
|
|
|
|
|
messageCount: 5, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
expect( |
|
|
|
|
|
|
|
await validatorManager.impliesDifferingLeaf( |
|
|
|
|
|
|
|
proofA.leaf, |
|
|
|
|
|
|
|
proofA.proof, |
|
|
|
|
|
|
|
proofB.leaf, |
|
|
|
|
|
|
|
proofB.proof, |
|
|
|
|
|
|
|
proofA.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.true; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('returns false when proving a leaf with index less than the differing leaf', async () => { |
|
|
|
|
|
|
|
const { proofA, proofB } = await dispatchMessagesAndReturnProofs({ |
|
|
|
|
|
|
|
differingIndex: 4, |
|
|
|
|
|
|
|
proofIndex: 3, |
|
|
|
|
|
|
|
messageCount: 5, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
expect( |
|
|
|
|
|
|
|
await validatorManager.impliesDifferingLeaf( |
|
|
|
|
|
|
|
proofA.leaf, |
|
|
|
|
|
|
|
proofA.proof, |
|
|
|
|
|
|
|
proofB.leaf, |
|
|
|
|
|
|
|
proofB.proof, |
|
|
|
|
|
|
|
proofA.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.false; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
describe('#fraudulentCheckpoint', async () => { |
|
|
|
|
|
|
|
let actual: MerkleProof, fraudulent: MerkleProof; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => { |
|
|
|
|
|
|
|
const { proofA, proofB } = await dispatchMessagesAndReturnProofs({ |
|
|
|
|
|
|
|
differingIndex: 3, |
|
|
|
|
|
|
|
proofIndex: 4, |
|
|
|
|
|
|
|
messageCount: 5, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
actual = proofA; |
|
|
|
|
|
|
|
fraudulent = proofB; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('accepts a fraud proof signed by a quorum', async () => { |
|
|
|
|
|
|
|
await outbox.cacheCheckpoint(); |
|
|
|
|
|
|
|
const signatures = await signCheckpoint( |
|
|
|
|
|
|
|
fraudulent.root, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
[validator0, validator1], // 2/2 signers is a quorum
|
|
|
|
); |
|
|
|
); |
|
|
|
await outbox.checkpoint(); |
|
|
|
|
|
|
|
const [root, index] = await outbox.latestCheckpoint(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
validatorManager.fraudulentCheckpoint( |
|
|
|
|
|
|
|
outbox.address, |
|
|
|
|
|
|
|
fraudulent.root, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
fraudulent.leaf, |
|
|
|
|
|
|
|
fraudulent.proof, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.to.emit(validatorManager, 'FraudulentCheckpoint') |
|
|
|
|
|
|
|
.withArgs( |
|
|
|
|
|
|
|
outbox.address, |
|
|
|
|
|
|
|
fraudulent.root, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
fraudulent.leaf, |
|
|
|
|
|
|
|
fraudulent.proof, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
expect(await outbox.state()).to.equal(types.AbacusState.FAILED); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('reverts if a fraud proof is not signed by a quorum', async () => { |
|
|
|
|
|
|
|
await outbox.cacheCheckpoint(); |
|
|
|
const signatures = await signCheckpoint( |
|
|
|
const signatures = await signCheckpoint( |
|
|
|
root, |
|
|
|
fraudulent.root, |
|
|
|
index.toNumber(), |
|
|
|
fraudulent.index, |
|
|
|
[validator0, validator1], // 2/2 signers, making a quorum
|
|
|
|
[validator0], // 1/2 signers is not a quorum
|
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
await expect( |
|
|
|
validatorManager.improperCheckpoint( |
|
|
|
validatorManager.fraudulentCheckpoint( |
|
|
|
outbox.address, |
|
|
|
outbox.address, |
|
|
|
root, |
|
|
|
fraudulent.root, |
|
|
|
index, |
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
fraudulent.leaf, |
|
|
|
|
|
|
|
fraudulent.proof, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.revertedWith('!quorum'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('reverts if the signed root is not fraudulent', async () => { |
|
|
|
|
|
|
|
await outbox.cacheCheckpoint(); |
|
|
|
|
|
|
|
const signatures = await signCheckpoint( |
|
|
|
|
|
|
|
actual.root, |
|
|
|
|
|
|
|
actual.index, |
|
|
|
|
|
|
|
[validator0, validator1], // 2/2 signers is a quorum
|
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
validatorManager.fraudulentCheckpoint( |
|
|
|
|
|
|
|
outbox.address, |
|
|
|
|
|
|
|
actual.root, |
|
|
|
|
|
|
|
actual.index, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
fraudulent.leaf, |
|
|
|
|
|
|
|
fraudulent.proof, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.revertedWith('!root'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('reverts if the disputed leaf is not committed to by the signed checkpoint', async () => { |
|
|
|
|
|
|
|
await outbox.cacheCheckpoint(); |
|
|
|
|
|
|
|
const signatures = await signCheckpoint( |
|
|
|
|
|
|
|
fraudulent.root, |
|
|
|
|
|
|
|
fraudulent.index - 1, |
|
|
|
|
|
|
|
[validator0, validator1], // 2/2 signers is a quorum
|
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
validatorManager.fraudulentCheckpoint( |
|
|
|
|
|
|
|
outbox.address, |
|
|
|
|
|
|
|
fraudulent.root, |
|
|
|
|
|
|
|
fraudulent.index - 1, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
fraudulent.leaf, |
|
|
|
|
|
|
|
fraudulent.proof, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.revertedWith('!index'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('reverts if the actual root is not cached', async () => { |
|
|
|
|
|
|
|
const signatures = await signCheckpoint( |
|
|
|
|
|
|
|
fraudulent.root, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
[validator0, validator1], // 2/2 signers is a quorum
|
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
validatorManager.fraudulentCheckpoint( |
|
|
|
|
|
|
|
outbox.address, |
|
|
|
|
|
|
|
fraudulent.root, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
signatures, |
|
|
|
|
|
|
|
fraudulent.leaf, |
|
|
|
|
|
|
|
fraudulent.proof, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
fraudulent.index, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.be.revertedWith('!cache'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('reverts if the root is not fraudulent', async () => { |
|
|
|
|
|
|
|
await outbox.cacheCheckpoint(); |
|
|
|
|
|
|
|
const signatures = await signCheckpoint( |
|
|
|
|
|
|
|
actual.root, |
|
|
|
|
|
|
|
actual.index, |
|
|
|
|
|
|
|
[validator0, validator1], // 2/2 signers is a quorum
|
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
validatorManager.fraudulentCheckpoint( |
|
|
|
|
|
|
|
outbox.address, |
|
|
|
|
|
|
|
actual.root, |
|
|
|
|
|
|
|
actual.index, |
|
|
|
signatures, |
|
|
|
signatures, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
actual.leaf, |
|
|
|
|
|
|
|
actual.proof, |
|
|
|
|
|
|
|
actual.index, |
|
|
|
), |
|
|
|
), |
|
|
|
).to.be.revertedWith('!improper'); |
|
|
|
).to.be.revertedWith('!fraud'); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|