Skip to content

Commit

Permalink
feat: Generate json blocks for tests (AztecProtocol#3923)
Browse files Browse the repository at this point in the history
Fixes AztecProtocol#1736 and makes it easier to update the decoders to align with typescript in the future.
  • Loading branch information
LHerskind authored Jan 11, 2024
1 parent f53f8ed commit a09fd2a
Show file tree
Hide file tree
Showing 14 changed files with 691 additions and 341 deletions.
10 changes: 9 additions & 1 deletion l1-contracts/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ remappings = [

# See more config options https://github.com/foundry-rs/foundry/tree/master/config

fs_permissions = [
{access = "read", path = "./test/fixtures/mixed_block_0.json"},
{access = "read", path = "./test/fixtures/mixed_block_1.json"},
{access = "read", path = "./test/fixtures/empty_block_0.json"},
{access = "read", path = "./test/fixtures/empty_block_1.json"},
]

[fmt]
line_length = 100
tab_width = 2
variable_override_spacing=false

[rpc_endpoints]
mainnet_fork="https://mainnet.infura.io/v3/9928b52099854248b3a096be07a6b23c"
mainnet_fork="https://mainnet.infura.io/v3/9928b52099854248b3a096be07a6b23c"

2 changes: 1 addition & 1 deletion l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ contract Rollup is IRollup {
}

// Decode the cross-chain messages
(bytes32 inHash,, bytes32[] memory l2ToL1Msgs, bytes32[] memory l1ToL2Msgs) =
(bytes32 inHash,, bytes32[] memory l1ToL2Msgs, bytes32[] memory l2ToL1Msgs) =
MessagesDecoder.decode(_l2Block[HeaderDecoder.BLOCK_HEADER_SIZE:]);

bytes32 publicInputHash =
Expand Down
22 changes: 13 additions & 9 deletions l1-contracts/src/core/libraries/decoders/MessagesDecoder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,22 @@ library MessagesDecoder {
/**
* @notice Computes consumables for the block
* @param _body - The L2 block calldata.
* @return l1ToL2MsgsHash - The hash of the L1 to L2 messages
* @return l2ToL1MsgsHash - The hash of the L1 to L2 messages
* @return l2ToL1Msgs - The L2 to L1 messages of the block
* @return inHash - The hash of the L1 to L2 messages
* @return outHash - The hash of the L1 to L2 messages
* @return l1ToL2Msgs - The L1 to L2 messages of the block
* @return l2ToL1Msgs - The L2 to L1 messages of the block
*/
function decode(bytes calldata _body)
internal
pure
returns (bytes32, bytes32, bytes32[] memory, bytes32[] memory)
returns (
bytes32 inHash,
bytes32 outHash,
bytes32[] memory l1ToL2Msgs,
bytes32[] memory l2ToL1Msgs
)
{
bytes32[] memory l1ToL2Msgs = new bytes32[](Constants.NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);
bytes32[] memory l2ToL1Msgs;
l1ToL2Msgs = new bytes32[](Constants.NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP);

uint256 offset = 0;

Expand Down Expand Up @@ -91,10 +95,10 @@ library MessagesDecoder {
calldatacopy(add(l1ToL2Msgs, 0x20), add(_body.offset, add(offset, 0x04)), mul(count, 0x20))
}

bytes32 l1ToL2MsgsHash = sha256(abi.encodePacked(l1ToL2Msgs));
bytes32 l2ToL1MsgsHash = sha256(abi.encodePacked(l2ToL1Msgs));
inHash = sha256(abi.encodePacked(l1ToL2Msgs));
outHash = sha256(abi.encodePacked(l2ToL1Msgs));

return (l1ToL2MsgsHash, l2ToL1MsgsHash, l2ToL1Msgs, l1ToL2Msgs);
return (inHash, outHash, l1ToL2Msgs, l2ToL1Msgs);
}

/**
Expand Down
274 changes: 0 additions & 274 deletions l1-contracts/test/Decoder.t.sol

This file was deleted.

4 changes: 1 addition & 3 deletions l1-contracts/test/DecoderHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ contract DecoderHelper {
pure
returns (bytes32, uint256)
{
(bytes32 logsHash, uint256 offset) = Decoder.computeKernelLogsHash(0, _kernelLogs);

return (logsHash, offset);
return Decoder.computeKernelLogsHash(0, _kernelLogs);
}
}
127 changes: 82 additions & 45 deletions l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ pragma solidity >=0.8.18;

import {Test} from "forge-std/Test.sol";

import {DecoderTest} from "./Decoder.t.sol";
import {DecoderTest} from "./decoders/Decoder.t.sol";
import {DecoderHelper} from "./DecoderHelper.sol";

import {DecoderBase} from "./decoders/Base.sol";

import {DataStructures} from "../src/core/libraries/DataStructures.sol";

Expand All @@ -18,35 +21,45 @@ import {Rollup} from "../src/core/Rollup.sol";
* Blocks are generated using the `integration_l1_publisher.test.ts` tests.
* Main use of these test is shorter cycles when updating the decoder contract.
*/
contract RollupTest is DecoderTest {
// Skipping this because the block is invalid after I changed the constants.
function __testEmptyBlock() public {
(,, bytes32 endStateHash,, bytes32[] memory l2ToL1Msgs, bytes32[] memory l1ToL2Msgs) =
helper.decode(block_empty_1);

vm.record();
rollup.process(bytes(""), block_empty_1);
contract RollupTest is DecoderBase {
DecoderHelper internal helper;
Registry internal registry;
Inbox internal inbox;
Outbox internal outbox;
Rollup internal rollup;

function setUp() public virtual {
helper = new DecoderHelper();

registry = new Registry();
inbox = new Inbox(address(registry));
outbox = new Outbox(address(registry));
rollup = new Rollup(registry);

registry.upgrade(address(rollup), address(inbox), address(outbox));
}

(, bytes32[] memory inboxWrites) = vm.accesses(address(inbox));
(, bytes32[] memory outboxWrites) = vm.accesses(address(outbox));
function testMixedBlock() public {
_testBlock("mixed_block_0");
}

assertEq(inboxWrites.length, 0, "Invalid inbox writes");
assertEq(outboxWrites.length, 0, "Invalid outbox writes");
function testConsecutiveMixedBlocks() public {
_testBlock("mixed_block_0");
_testBlock("mixed_block_1");
}

for (uint256 i = 0; i < l2ToL1Msgs.length; i++) {
assertEq(l2ToL1Msgs[i], bytes32(0), "Invalid l2ToL1Msgs");
assertFalse(outbox.contains(l2ToL1Msgs[i]), "msg in outbox");
}
for (uint256 i = 0; i < l1ToL2Msgs.length; i++) {
assertEq(l1ToL2Msgs[i], bytes32(0), "Invalid l1ToL2Msgs");
assertFalse(inbox.contains(l1ToL2Msgs[i]), "msg in inbox");
}
function testEmptyBlock() public {
_testBlock("empty_block_0");
}

assertEq(rollup.rollupStateHash(), endStateHash, "Invalid rollup state hash");
function testConsecutiveEmptyBlocks() public {
_testBlock("empty_block_0");
_testBlock("empty_block_1");
}

function testRevertInvalidChainId() public {
bytes memory block_ = block_empty_1;
bytes memory block_ = load("empty_block_0").block.body;

assembly {
mstore(add(block_, 0x20), 0x420)
}
Expand All @@ -56,7 +69,8 @@ contract RollupTest is DecoderTest {
}

function testRevertInvalidVersion() public {
bytes memory block_ = block_empty_1;
bytes memory block_ = load("empty_block_0").block.body;

assembly {
mstore(add(block_, 0x40), 0x420)
}
Expand All @@ -66,7 +80,7 @@ contract RollupTest is DecoderTest {
}

function testRevertTimestampInFuture() public {
bytes memory block_ = block_empty_1;
bytes memory block_ = load("empty_block_0").block.body;

uint256 ts = block.timestamp + 1;
assembly {
Expand All @@ -78,7 +92,7 @@ contract RollupTest is DecoderTest {
}

function testRevertTimestampTooOld() public {
bytes memory block_ = block_empty_1;
bytes memory block_ = load("empty_block_0").block.body;

// Overwrite in the rollup contract
vm.store(address(rollup), bytes32(uint256(1)), bytes32(uint256(block.timestamp)));
Expand All @@ -87,37 +101,60 @@ contract RollupTest is DecoderTest {
rollup.process(bytes(""), block_);
}

function testMixBlock() public {
(,, bytes32 endStateHash,, bytes32[] memory l2ToL1Msgs, bytes32[] memory l1ToL2Msgs) =
helper.decode(block_mixed_1);
function _testBlock(string memory name) public {
DecoderBase.Full memory full = load(name);
// We jump to the time of the block.
vm.warp(full.block.timestamp);

bytes32[] memory expectedL1ToL2Msgs = _populateInbox();
_populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content);

for (uint256 i = 0; i < l1ToL2Msgs.length; i++) {
assertTrue(inbox.contains(l1ToL2Msgs[i]), "msg not in inbox");
for (uint256 i = 0; i < full.messages.l1ToL2Messages.length; i++) {
if (full.messages.l1ToL2Messages[i] == bytes32(0)) {
continue;
}
assertTrue(inbox.contains(full.messages.l1ToL2Messages[i]), "msg not in inbox");
}

vm.record();
rollup.process(bytes(""), block_mixed_1);
rollup.process(bytes(""), full.block.body);

(, bytes32[] memory inboxWrites) = vm.accesses(address(inbox));
(, bytes32[] memory outboxWrites) = vm.accesses(address(outbox));

assertEq(inboxWrites.length, 16, "Invalid inbox writes");
assertEq(outboxWrites.length, 8, "Invalid outbox writes");

for (uint256 i = 0; i < l2ToL1Msgs.length; i++) {
// recreate the value generated by `integration_l1_publisher.test.ts`.
bytes32 expectedValue = bytes32(uint256(0x300 + 32 * (1 + i / 2) + i % 2));
assertEq(l2ToL1Msgs[i], expectedValue, "Invalid l2ToL1Msgs");
assertTrue(outbox.contains(l2ToL1Msgs[i]), "msg not in outbox");
{
uint256 count = 0;
for (uint256 i = 0; i < full.messages.l2ToL1Messages.length; i++) {
if (full.messages.l2ToL1Messages[i] == bytes32(0)) {
continue;
}
assertTrue(outbox.contains(full.messages.l2ToL1Messages[i]), "msg not in outbox");
count++;
}
assertEq(outboxWrites.length, count, "Invalid outbox writes");
}

for (uint256 i = 0; i < l1ToL2Msgs.length; i++) {
assertEq(l1ToL2Msgs[i], expectedL1ToL2Msgs[i], "Invalid l1ToL2Msgs");
assertFalse(inbox.contains(l1ToL2Msgs[i]), "msg not consumed");
{
uint256 count = 0;
for (uint256 i = 0; i < full.messages.l1ToL2Messages.length; i++) {
if (full.messages.l1ToL2Messages[i] == bytes32(0)) {
continue;
}
assertFalse(inbox.contains(full.messages.l1ToL2Messages[i]), "msg not consumed");
count++;
}
assertEq(inboxWrites.length, count, "Invalid inbox writes");
}

assertEq(rollup.rollupStateHash(), endStateHash, "Invalid rollup state hash");
assertEq(rollup.rollupStateHash(), full.block.endStateHash, "Invalid rollup state hash");
}

function _populateInbox(address _sender, bytes32 _recipient, bytes32[] memory _contents) internal {
uint32 deadline = type(uint32).max;
for (uint256 i = 0; i < _contents.length; i++) {
vm.prank(_sender);
inbox.sendL2Message(
DataStructures.L2Actor({actor: _recipient, version: 1}), deadline, _contents[i], bytes32(0)
);
}
}
}
45 changes: 45 additions & 0 deletions l1-contracts/test/decoders/Base.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Aztec Labs.
pragma solidity >=0.8.18;

import {Test} from "forge-std/Test.sol";

contract DecoderBase is Test {
// When I had data and messages as one combined struct it failed, but I can have this top-layer and it works :shrug:
struct Full {
Data block;
Messages messages;
Populate populate;
}

struct Populate {
bytes32[] l1ToL2Content;
bytes32 recipient;
address sender;
}

struct Messages {
bytes32[] l1ToL2Messages;
bytes32[] l2ToL1Messages;
}

struct Data {
uint256 blockNumber;
bytes body;
bytes32 calldataHash;
bytes32 endStateHash;
bytes32 l1ToL2MessagesHash;
bytes32 publicInputsHash;
bytes32 startStateHash;
uint256 timestamp;
}

function load(string memory name) public view returns (Full memory) {
string memory root = vm.projectRoot();
string memory path = string.concat(root, "/test/fixtures/", name, ".json");
string memory json = vm.readFile(path);
bytes memory json_bytes = vm.parseJson(json);
Full memory full = abi.decode(json_bytes, (Full));
return full;
}
}
Loading

0 comments on commit a09fd2a

Please sign in to comment.