Skip to content

Commit

Permalink
feat(contracts): verify xsubmission merke proofs
Browse files Browse the repository at this point in the history
Verify XSubmission merkle proofs on xsubmit.
  • Loading branch information
kevinhalliday committed Jan 29, 2024
1 parent fbb0fcd commit 6a17a46
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 201 deletions.
1 change: 1 addition & 0 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"test:gen:xsubs": "ts-node test/ts/script/genxsubs/main.ts"
},
"devDependencies": {
"@openzeppelin/contracts": "^5.0.1",
"@openzeppelin/merkle-tree": "^1.0.5",
"@types/node": "^20.11.7",
"ds-test": "https://github.com/dapphub/ds-test",
Expand Down
7 changes: 7 additions & 0 deletions contracts/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions contracts/src/libraries/XBlockMerkleProof.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.23;

import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { XTypes } from "./XTypes.sol";

/**
* @title XBlockMerkleProof
* @dev Library for verifying XBlock merkle proofs
*/
library XBlockMerkleProof {
/**
* @dev Verifies a multi merkle proof for the provided block header and messages, against the provided root.
* Msgs order must match the order used to contstruct the merkle proof.
* @param root The root of the xblock merkle tree, generally XSubmission.attestationRoot.
* @param blockHeader The xblock header.
* @param msgs The xmsgs to verify.
* @param proof The merkle proof.
* @param proofFlags The merkle proof flags.
* @return True if the proof is valid.
*/
function verify(
bytes32 root,
XTypes.BlockHeader calldata blockHeader,
XTypes.Msg[] calldata msgs,
bytes32[] calldata proof,
bool[] calldata proofFlags
) internal pure returns (bool) {
return MerkleProof.multiProofVerify(proof, proofFlags, root, _leaves(blockHeader, msgs));
}

/// @dev Convert block header and msgs to leaf hashes
function _leaves(XTypes.BlockHeader calldata blockHeader, XTypes.Msg[] calldata msgs)
private
pure
returns (bytes32[] memory)
{
bytes32[] memory leaves = new bytes32[](msgs.length + 1);

leaves[0] = _leafHash(abi.encode((blockHeader)));
for (uint256 i = 0; i < msgs.length; i++) {
leaves[i + 1] = _leafHash(abi.encode((msgs[i])));
}

return leaves;
}

/// @dev Double hash leaves, as recommended by OpenZeppelin, to prevent second preimage attacks
/// Leaves must be double hashed in tree / proof construction
function _leafHash(bytes memory leaf) private pure returns (bytes32) {
return keccak256(bytes.concat(keccak256(leaf)));
}
}
22 changes: 2 additions & 20 deletions contracts/test/OmniPortal_exec.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,7 @@ contract OmniPortal_exec_Test is Base {

assertEq(counter.count(), count + 1);
assertEq(portal.inXStreamOffset(xmsg.sourceChainId), offset + 1);

Vm.Log[] memory logs = vm.getRecordedLogs();

_assertReceiptEmitted(
logs[0],
xmsg.sourceChainId,
offset,
relayer,
true // success
);
assertReceipt(vm.getRecordedLogs()[0], xmsg);
}

/// @dev Test that exec of an XMsg that reverts succeeds, and emits the correct XReceipt
Expand All @@ -50,16 +41,7 @@ contract OmniPortal_exec_Test is Base {

assertEq(counter.count(), count);
assertEq(portal.inXStreamOffset(xmsg.sourceChainId), offset + 1);

Vm.Log[] memory logs = vm.getRecordedLogs();

_assertReceiptEmitted(
logs[0],
xmsg.sourceChainId,
offset,
relayer,
false // failure
);
assertReceipt(vm.getRecordedLogs()[0], xmsg);
}

/// @dev Test that exec of an XMsg with the wrong destChainId reverts
Expand Down
195 changes: 48 additions & 147 deletions contracts/test/OmniPortal_xsubmit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,182 +10,83 @@ import { Vm } from "forge-std/Vm.sol";
* @dev Tests of OmniPortal.xsubmit
*/
contract OmniPortal_xsubmit_Test is Base {
/// @dev Test that an XSubmission with a single XMsg succeeds
/// Check that the correct XReceipt's are emitter, and stream offset is incremented.
function test_xsubmit_xmsgSingle_succeeds() public {
XTypes.Msg[] memory xmsgs = new XTypes.Msg[](1);
function test_xsubmit_xblock1_succeeds() public {
XTypes.Submission memory xsub = readXSubmission("xblock1", portal.chainId());

xmsgs[0] = _inbound_increment(0);

XTypes.Submission memory submission = _xsub(xmsgs);

uint256 count = counter.count();
uint64 sourceChainId = xmsgs[0].sourceChainId;
uint64 offset = portal.inXStreamOffset(sourceChainId);
uint64 sourceChainId = xsub.msgs[0].sourceChainId;
uint64 expectedOffset = xsub.msgs[0].streamOffset + uint64(xsub.msgs.length);

vm.prank(relayer);
vm.recordLogs();
portal.xsubmit(submission);

assertEq(counter.count(), count + 1);
assertEq(portal.inXStreamOffset(sourceChainId), offset + 1);

Vm.Log[] memory logs = vm.getRecordedLogs();
expectCalls(xsub.msgs);
portal.xsubmit(xsub);

assertEq(logs.length, xmsgs.length);

_assertReceiptEmitted(
logs[0],
sourceChainId,
offset,
relayer,
true // success
);
assertEq(portal.inXStreamOffset(sourceChainId), expectedOffset);
assertReceipts(vm.getRecordedLogs(), xsub.msgs);
}

/// @dev Test that an XSubmission with a batch of XMsgs succeeds.
/// Check that the correct XReceipt's are emitter, and stream offset is incremented.
function test_xsubmit_xmsgBatch_succeeds() public {
XTypes.Msg[] memory xmsgs = new XTypes.Msg[](4);

xmsgs[0] = _inbound_increment(0);
xmsgs[1] = _inbound_increment(1);
xmsgs[2] = _inbound_increment(2);
xmsgs[3] = _inbound_increment(3);
function test_xsubmit_xblock2_succeeds() public {
XTypes.Submission memory xsub = readXSubmission("xblock2", portal.chainId());

XTypes.Submission memory submission = _xsub(xmsgs);
uint64 sourceChainId = xsub.msgs[0].sourceChainId;
uint64 expectedOffset = xsub.msgs[0].streamOffset + uint64(xsub.msgs.length);

uint256 count = counter.count();
uint64 sourceChainId = xmsgs[0].sourceChainId;
uint64 offset = portal.inXStreamOffset(sourceChainId);
// xblock2 starts are later offset
portal.setInXStreamOffset(sourceChainId, xsub.msgs[0].streamOffset);

vm.prank(relayer);
vm.recordLogs();
portal.xsubmit(submission);

assertEq(counter.count(), count + 4);
assertEq(portal.inXStreamOffset(sourceChainId), offset + 4);
expectCalls(xsub.msgs);
portal.xsubmit(xsub);

Vm.Log[] memory logs = vm.getRecordedLogs();

assertEq(logs.length, xmsgs.length);

for (uint256 i = 0; i < xmsgs.length; i++) {
_assertReceiptEmitted(
logs[i],
sourceChainId,
offset + uint64(i),
relayer,
true // success
);
}
assertEq(portal.inXStreamOffset(sourceChainId), expectedOffset);
assertReceipts(vm.getRecordedLogs(), xsub.msgs);
}

/// @dev Test that an XSubmission with a batch of XMsgs, in which one reverts, succeeds.
/// Check that the correct XReceipt's are emitter, and stream offset is incremented.
function test_xsubmit_xmsgBatchWithRevert_succeeds() public {
XTypes.Msg[] memory xmsgs = new XTypes.Msg[](4);

xmsgs[0] = _inbound_increment(0);
xmsgs[1] = _inbound_increment(1);
xmsgs[2] = _inbound_revert(2);
xmsgs[3] = _inbound_increment(3);
function test_xsubmit_xblock1_chainB_succeeds() public {
XTypes.Submission memory xsub = readXSubmission("xblock1", chainBId);

XTypes.Submission memory submission = _xsub(xmsgs);

uint256 count = counter.count();
uint64 sourceChainId = xmsgs[0].sourceChainId;
uint64 offset = portal.inXStreamOffset(sourceChainId);
uint64 sourceChainId = xsub.msgs[0].sourceChainId;
uint64 expectedOffset = xsub.msgs[0].streamOffset + uint64(xsub.msgs.length);

vm.prank(relayer);
vm.recordLogs();
portal.xsubmit(submission);

assertEq(counter.count(), count + 3); // only 3, because one msg was a revert
assertEq(portal.inXStreamOffset(sourceChainId), offset + 4);

Vm.Log[] memory logs = vm.getRecordedLogs();

assertEq(logs.length, xmsgs.length);

_assertReceiptEmitted(
logs[0],
sourceChainId,
offset,
relayer,
true // success
);

_assertReceiptEmitted(
logs[1],
sourceChainId,
offset + 1,
relayer,
true // success
);

// this one fails
_assertReceiptEmitted(
logs[2],
sourceChainId,
offset + 2,
relayer,
false // failure
);

_assertReceiptEmitted(
logs[3],
sourceChainId,
offset + 3,
relayer,
true // success
);
}

/// @dev Test that an XSubmission with a batch of XMsgs with an XMsg behind the current offset reverts
function test_xsubmit_xmsgBatchOneBehindOffset_reverts() public {
XTypes.Msg[] memory xmsgs = new XTypes.Msg[](4);
expectCalls(xsub.msgs);
chainBPortal.xsubmit(xsub);

xmsgs[0] = _inbound_increment(0);
xmsgs[1] = _inbound_increment(1);
xmsgs[2] = _inbound_increment(2);
xmsgs[3] = _inbound_increment(2); // intentionally behind offset

XTypes.Submission memory submission = _xsub(xmsgs);

vm.expectRevert("OmniPortal: wrong streamOffset");
portal.xsubmit(submission);
assertEq(chainBPortal.inXStreamOffset(sourceChainId), expectedOffset);
assertReceipts(vm.getRecordedLogs(), xsub.msgs);
}

/// @dev Test that an XSubmission with a batch of XMsgs with an XMsg ahead the current offset reverts
function test_xsubmit_xmsgBatchOneAheadOffset_reverts() public {
XTypes.Msg[] memory xmsgs = new XTypes.Msg[](4);
function test_xsubmit_xblock2_chainB_succeeds() public {
XTypes.Submission memory xsub = readXSubmission("xblock2", chainBId);

xmsgs[0] = _inbound_increment(0);
xmsgs[1] = _inbound_increment(1);
xmsgs[2] = _inbound_increment(2);
xmsgs[3] = _inbound_increment(4); // intentionally ahead offset
uint64 sourceChainId = xsub.msgs[0].sourceChainId;
uint64 expectedOffset = xsub.msgs[0].streamOffset + uint64(xsub.msgs.length);

XTypes.Submission memory submission = _xsub(xmsgs);
// xblock2 starts are later offset
chainBPortal.setInXStreamOffset(sourceChainId, xsub.msgs[0].streamOffset);

vm.expectRevert("OmniPortal: wrong streamOffset");
portal.xsubmit(submission);
}
vm.prank(relayer);
vm.recordLogs();
expectCalls(xsub.msgs);
chainBPortal.xsubmit(xsub);

/// @dev Test that an XSubmission with a batch of XMsgs in which one has the wrong destChainId reverts
function test_xsubmit_xmsgBatchWrongChainId_reverts() public {
XTypes.Msg[] memory xmsgs = new XTypes.Msg[](4);
assertEq(chainBPortal.inXStreamOffset(sourceChainId), expectedOffset);
assertReceipts(vm.getRecordedLogs(), xsub.msgs);
}

xmsgs[0] = _inbound_increment(0);
xmsgs[1] = _inbound_increment(1);
xmsgs[2] = _inbound_increment(2);
xmsgs[3] = _inbound_increment(3);
function test_xsubmit_wrongChainId_reverts() public {
XTypes.Submission memory xsub = readXSubmission("xblock1", portal.chainId());

xmsgs[1].destChainId = xmsgs[0].destChainId + 1; // intentionally wrong chainId
vm.expectRevert("OmniPortal: wrong destChainId");
chainBPortal.xsubmit(xsub);
}

XTypes.Submission memory submission = _xsub(xmsgs);
function test_xsubmit_wrongStreamOffset_reverts() public {
XTypes.Submission memory xsub = readXSubmission("xblock2", portal.chainId());

vm.expectRevert("OmniPortal: wrong destChainId");
portal.xsubmit(submission);
vm.expectRevert("OmniPortal: wrong streamOffset");
portal.xsubmit(xsub);
}
}
Loading

0 comments on commit 6a17a46

Please sign in to comment.