Skip to content

Commit

Permalink
feat: Rollup contracts moves msgs between L1 and L2 (#609)
Browse files Browse the repository at this point in the history
* feat: rollup moves msgs

* chore: forge fmt

* fix: deploy registry, inbox, outbox for E2E and force insert into inbox.
  • Loading branch information
LHerskind authored May 17, 2023
1 parent e61b65f commit 436e5df
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 27 deletions.
17 changes: 15 additions & 2 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// Copyright 2023 Aztec Labs.
pragma solidity >=0.8.18;

import {IRegistry} from "@aztec/core/interfaces/messagebridge/IRegistry.sol";
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol";

import {MockVerifier} from "@aztec/mock/MockVerifier.sol";
import {Decoder} from "./Decoder.sol";

Expand All @@ -20,10 +24,12 @@ contract Rollup is Decoder {
event L2BlockProcessed(uint256 indexed blockNum);

MockVerifier public immutable VERIFIER;
IRegistry public immutable REGISTRY;
bytes32 public rollupStateHash;

constructor() {
constructor(IRegistry _registry) {
VERIFIER = new MockVerifier();
REGISTRY = _registry;
}

/**
Expand All @@ -41,7 +47,7 @@ contract Rollup is Decoder {
bytes32[] memory l1ToL2Msgs
) = _decode(_l2Block);

// @todo Proper genesis state. If the state is empty, we allow anything for now.
// @todo @LHerskind Proper genesis state. If the state is empty, we allow anything for now.
if (rollupStateHash != bytes32(0) && rollupStateHash != oldStateHash) {
revert InvalidStateHash(rollupStateHash, oldStateHash);
}
Expand All @@ -55,6 +61,13 @@ contract Rollup is Decoder {

rollupStateHash = newStateHash;

// @todo (issue #605) handle fee collector
IInbox inbox = REGISTRY.getInbox();
inbox.batchConsume(l1ToL2Msgs, msg.sender);

IOutbox outbox = REGISTRY.getOutbox();
outbox.sendL1Messages(l2ToL1Msgs);

emit L2BlockProcessed(l2BlockNumber);
}
}
5 changes: 4 additions & 1 deletion l1-contracts/src/core/messagebridge/Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,16 @@ contract Inbox is MessageBox, IInbox {
function batchConsume(bytes32[] memory _entryKeys, address _feeCollector) external onlyRollup {
uint256 totalFee = 0;
for (uint256 i = 0; i < _entryKeys.length; i++) {
if (_entryKeys[i] == bytes32(0)) continue;
DataStructures.Entry memory entry = get(_entryKeys[i]);
// cant consume if we are already past deadline.
if (block.timestamp > entry.deadline) revert Inbox__PastDeadline();
_consume(_entryKeys[i]);
totalFee += entry.fee;
}
feesAccrued[_feeCollector] += totalFee;
if (totalFee > 0) {
feesAccrued[_feeCollector] += totalFee;
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/messagebridge/Outbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ contract Outbox is MessageBox, IOutbox {
*/
function sendL1Messages(bytes32[] memory _entryKeys) external onlyRollup {
for (uint256 i = 0; i < _entryKeys.length; i++) {
if (_entryKeys[i] == bytes32(0)) continue;
_insert(_entryKeys[i], 0, 0);
emit MessageAdded(_entryKeys[i]);
}
Expand Down
21 changes: 5 additions & 16 deletions l1-contracts/test/Decoder.t.sol

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions l1-contracts/test/Inbox.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,17 @@ contract InboxTest is Test {
inbox.batchConsume(entryKeys, address(0x1));
}

function testFuzzRevertIfConsumingAMessageThatDoesntExist(bytes32[] memory _entryKeys) public {
if (_entryKeys.length == 0) {
_entryKeys = new bytes32[](1);
_entryKeys[0] = bytes32("random");
function testFuzzRevertIfConsumingAMessageThatDoesntExist(bytes32 _entryKey) public {
bytes32[] memory entryKeys = new bytes32[](1);
if (_entryKey == bytes32(0)) {
entryKeys[0] = bytes32("random");
} else {
entryKeys[0] = _entryKey;
}
vm.expectRevert(
abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, _entryKeys[0])
abi.encodeWithSelector(MessageBox.MessageBox__NothingToConsume.selector, entryKeys[0])
);
inbox.batchConsume(_entryKeys, address(0x1));
inbox.batchConsume(entryKeys, address(0x1));
}

function testRevertIfConsumingTheSameMessageMoreThanTheCountOfEntries() public {
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/test/Outbox.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ contract OutboxTest is Test {
function testFuzzBatchInsert(bytes32[] memory _entryKeys) public {
// expected events
for (uint256 i = 0; i < _entryKeys.length; i++) {
if (_entryKeys[i] == bytes32(0)) continue;
vm.expectEmit(true, false, false, false);
emit MessageAdded(_entryKeys[i]);
}

outbox.sendL1Messages(_entryKeys);
for (uint256 i = 0; i < _entryKeys.length; i++) {
if (_entryKeys[i] == bytes32(0)) continue;
bytes32 key = _entryKeys[i];
DataStructures.Entry memory entry = outbox.get(key);
assertGt(entry.count, 0);
Expand Down
102 changes: 102 additions & 0 deletions l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Aztec Labs.
pragma solidity >=0.8.18;

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

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

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

import {Registry} from "@aztec/core/messagebridge/Registry.sol";
import {Inbox} from "@aztec/core/messagebridge/Inbox.sol";
import {Outbox} from "@aztec/core/messagebridge/Outbox.sol";

import {Decoder} from "@aztec/core/Decoder.sol";
import {Rollup} from "@aztec/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 {
Registry internal registry;
Inbox internal inbox;
Outbox internal outbox;
Rollup internal rollup;

function setUp() public override(DecoderTest) {
super.setUp();
registry = new Registry();
inbox = new Inbox(address(registry));
outbox = new Outbox(address(registry));
rollup = new Rollup(registry);

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

function testEmptyBlock() public override(DecoderTest) {
(,, bytes32 endStateHash,, bytes32[] memory l2ToL1Msgs, bytes32[] memory l1ToL2Msgs) =
helper.decode(block_empty_1);

vm.record();
rollup.process(bytes(""), block_empty_1);

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

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

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");
}

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

function testMixBlock() public override(DecoderTest) {
(,, bytes32 endStateHash,, bytes32[] memory l2ToL1Msgs, bytes32[] memory l1ToL2Msgs) =
helper.decode(block_mixed_1);

for (uint256 i = 0; i < l1ToL2Msgs.length; i++) {
_insertInboxEntry(l1ToL2Msgs[i]);
assertTrue(inbox.contains(l1ToL2Msgs[i]), "msg not in inbox");
}

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

(, 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");
}

for (uint256 i = 0; i < l1ToL2Msgs.length; i++) {
assertEq(l1ToL2Msgs[i], bytes32(uint256(0x401 + i)), "Invalid l1ToL2Msgs");
assertFalse(inbox.contains(l1ToL2Msgs[i]), "msg not consumed");
}

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

function _insertInboxEntry(bytes32 _entryHash) internal {
// Compute where exactly we need to shove the entry
bytes32 slot = keccak256(abi.encodePacked(_entryHash, uint256(0)));
uint256 value = uint256(1) | uint256(type(uint32).max) << 128;
vm.store(address(inbox), slot, bytes32(value));
}
}
42 changes: 41 additions & 1 deletion yarn-project/end-to-end/src/deploy_l1_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { DebugLogger } from '@aztec/foundation/log';
import {
DecoderHelperAbi,
DecoderHelperBytecode,
InboxAbi,
InboxBytecode,
OutboxAbi,
OutboxBytecode,
RegistryAbi,
RegistryBytecode,
RollupAbi,
RollupBytecode,
UnverifiedDataEmitterAbi,
Expand All @@ -18,6 +24,8 @@ import {
WalletClient,
createPublicClient,
createWalletClient,
getAddress,
getContract,
http,
} from 'viem';
import { HDAccount, PrivateKeyAccount } from 'viem/accounts';
Expand Down Expand Up @@ -49,9 +57,36 @@ export const deployL1Contracts = async (
transport: http(rpcUrl),
});

const rollupAddress = await deployL1Contract(walletClient, publicClient, RollupAbi, RollupBytecode);
const registryAddress = await deployL1Contract(walletClient, publicClient, RegistryAbi, RegistryBytecode);
logger(`Deployed Registry at ${registryAddress}`);

const inboxAddress = await deployL1Contract(walletClient, publicClient, InboxAbi, InboxBytecode, [
getAddress(registryAddress.toString()),
]);
logger(`Deployed Inbox at ${inboxAddress}`);

const outboxAddress = await deployL1Contract(walletClient, publicClient, OutboxAbi, OutboxBytecode, [
getAddress(registryAddress.toString()),
]);
logger(`Deployed Outbox at ${outboxAddress}`);

const rollupAddress = await deployL1Contract(walletClient, publicClient, RollupAbi, RollupBytecode, [
getAddress(registryAddress.toString()),
]);
logger(`Deployed Rollup at ${rollupAddress}`);

// We need to call a function on the registry to set the various contract addresses.
const registryContract = getContract({
address: getAddress(registryAddress.toString()),
abi: RegistryAbi,
publicClient,
walletClient,
});
await registryContract.write.setAddresses(
[getAddress(rollupAddress.toString()), getAddress(inboxAddress.toString()), getAddress(outboxAddress.toString())],
{ account },
);

const unverifiedDataEmitterAddress = await deployL1Contract(
walletClient,
publicClient,
Expand All @@ -68,6 +103,9 @@ export const deployL1Contracts = async (

return {
rollupAddress,
registryAddress,
inboxAddress,
outboxAddress,
unverifiedDataEmitterAddress,
decoderHelperAddress,
};
Expand All @@ -86,10 +124,12 @@ async function deployL1Contract(
publicClient: PublicClient<HttpTransport, Chain>,
abi: Narrow<Abi | readonly unknown[]>,
bytecode: Hex,
args: readonly unknown[] | undefined = undefined,
): Promise<EthAddress> {
const hash = await walletClient.deployContract({
abi,
bytecode,
args: args,
});

const receipt = await publicClient.waitForTransactionReceipt({ hash });
Expand Down
Loading

0 comments on commit 436e5df

Please sign in to comment.