Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Rollup contracts moves msgs between L1 and L2 #609

Merged
merged 3 commits into from
May 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be more gas efficient to just make one external call getting all addresses then destructure?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If getting all the addresses we will be reading an extra storage slot for the rollup address as well, e.g., 1 contract access + 3 storage reads. Here we have 1 cold contract access and 1 hot + 2 storage reads.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds right

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