Skip to content
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
9 changes: 9 additions & 0 deletions l1-contracts/src/core/RollupCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {RewardLib, RewardConfig} from "@aztec/core/libraries/rollup/RewardLib.so
import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol";
import {FeeConfigLib, CompressedFeeConfig} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
import {ChainTipsLib, CompressedChainTips} from "@aztec/core/libraries/compressed-data/Tips.sol";

/**
* @title RollupCore
Expand Down Expand Up @@ -177,6 +178,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
using TimeLib for Slot;
using TimeLib for Epoch;
using FeeConfigLib for CompressedFeeConfig;
using ChainTipsLib for CompressedChainTips;

/**
* @notice The L1 block number when this rollup was deployed
Expand Down Expand Up @@ -319,6 +321,13 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
uint256 currentManaTarget = FeeLib.getStorage().config.getManaTarget();
require(_manaTarget >= currentManaTarget, Errors.Rollup__InvalidManaTarget(currentManaTarget, _manaTarget));
FeeLib.updateManaTarget(_manaTarget);

// If we are going from 0 to non-zero mana limits, we need to catch up the inbox
if (currentManaTarget == 0 && _manaTarget > 0) {
RollupStore storage rollupStore = STFLib.getStorage();
rollupStore.config.inbox.catchUp(rollupStore.tips.getPendingBlockNumber());
}

emit IRollupCore.ManaTargetUpdated(_manaTarget);
}

Expand Down
4 changes: 4 additions & 0 deletions l1-contracts/src/core/interfaces/messagebridge/IInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ interface IInbox {
*/
event MessageSent(uint256 indexed l2BlockNumber, uint256 index, bytes32 indexed hash, bytes16 rollingHash);

event InboxSynchronized(uint256 indexed inProgress);

// docs:start:send_l1_to_l2_message
/**
* @notice Inserts a new message into the Inbox
Expand Down Expand Up @@ -59,6 +61,8 @@ interface IInbox {
function consume(uint256 _toConsume) external returns (bytes32);
// docs:end:consume

function catchUp(uint256 _pendingBlockNumber) external;

function getFeeAssetPortal() external view returns (address);

function getRoot(uint256 _blockNumber) external view returns (bytes32);
Expand Down
16 changes: 16 additions & 0 deletions l1-contracts/src/core/messagebridge/Inbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
import {Errors} from "@aztec/core/libraries/Errors.sol";
import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol";
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";

/**
* @title Inbox
Expand Down Expand Up @@ -153,6 +154,21 @@ contract Inbox is IInbox {
return root;
}

/**
* @notice Catch up the inbox to the pending block number
*
* @dev Only callable by the rollup contract
* Will only be called WHEN a change is made from 0 to non-zero mana limits
*
* @param _pendingBlockNumber - The pending block number to catch up to
*/
function catchUp(uint256 _pendingBlockNumber) external override(IInbox) {
require(msg.sender == ROLLUP, Errors.Inbox__Unauthorized());
// The next expected will be 1 ahead of the next block, e.g., + 2 from current.
state.inProgress = SafeCast.toUint64(_pendingBlockNumber + 2);
emit InboxSynchronized(state.inProgress);
}

function getFeeAssetPortal() external view override(IInbox) returns (address) {
return FEE_ASSET_PORTAL;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;

import {DecoderBase} from "./base/DecoderBase.sol";
import {DecoderBase} from "../base/DecoderBase.sol";

import {Registry} from "@aztec/governance/Registry.sol";
import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol";
import {TestERC20} from "@aztec/mock/TestERC20.sol";
import {TestConstants} from "./harnesses/TestConstants.sol";
import {TestConstants} from "../harnesses/TestConstants.sol";
import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol";
import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol";

import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";

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

import {RollupBase, IInstance} from "./base/RollupBase.sol";
import {RollupBuilder} from "./builder/RollupBuilder.sol";
import {TimeCheater} from "./staking/TimeCheater.sol";
import {RollupBase, IInstance} from "../base/RollupBase.sol";
import {RollupBuilder} from "../builder/RollupBuilder.sol";
import {TimeCheater} from "../staking/TimeCheater.sol";
import {Bps, BpsLib} from "@aztec/core/libraries/rollup/RewardLib.sol";
// solhint-disable comprehensive-interface

Expand Down
179 changes: 179 additions & 0 deletions l1-contracts/test/ignition/tmnt223.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Aztec Labs.
pragma solidity >=0.8.27;

import {DecoderBase} from "../base/DecoderBase.sol";

import {Registry} from "@aztec/governance/Registry.sol";
import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol";
import {TestERC20} from "@aztec/mock/TestERC20.sol";
import {TestConstants} from "../harnesses/TestConstants.sol";
import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol";
import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol";

import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";

import {Errors} from "@aztec/core/libraries/Errors.sol";
import {ProposeArgs, ProposePayload, OracleInput, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol";

import {RollupBase, IInstance} from "../base/RollupBase.sol";
import {RollupBuilder} from "../builder/RollupBuilder.sol";
import {TimeCheater} from "../staking/TimeCheater.sol";
import {Bps, BpsLib} from "@aztec/core/libraries/rollup/RewardLib.sol";
import {
AttestationLib,
Signature,
CommitteeAttestation,
CommitteeAttestations
} from "@aztec/core/libraries/rollup/AttestationLib.sol";
import {Constants} from "@aztec/core/libraries/ConstantsGen.sol";
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {AttestationLibHelper} from "@test/helper_libraries/AttestationLibHelper.sol";
import {Ownable} from "@oz/access/Ownable.sol";
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
// solhint-disable comprehensive-interface

struct Block {
ProposeArgs proposeArgs;
bytes blobInputs;
CommitteeAttestation[] attestations;
address[] signers;
}

/**
* 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 Tmnt223Test is RollupBase {
using ProposeLib for ProposeArgs;
using TimeLib for Timestamp;
using TimeLib for Slot;
using TimeLib for Epoch;

Registry internal registry;
TestERC20 internal testERC20;
FeeJuicePortal internal feeJuicePortal;
RewardDistributor internal rewardDistributor;
TimeCheater internal timeCheater;

uint256 internal SLOT_DURATION;
uint256 internal EPOCH_DURATION;
uint256 internal PROOF_SUBMISSION_EPOCHS;
uint256 internal MANA_TARGET = 0;

address internal sequencer = address(bytes20("sequencer"));

DecoderBase.Full internal full;

/**
* @notice Set up the contracts needed for the tests with time aligned to the provided block name
*/
modifier setUpFor(string memory _name) {
{
full = load(_name);
Slot slotNumber = full.block.header.slotNumber;
uint256 initialTime = Timestamp.unwrap(full.block.header.timestamp) - Slot.unwrap(slotNumber) * SLOT_DURATION;
vm.warp(initialTime);
}

TimeLib.initialize(
block.timestamp,
TestConstants.AZTEC_SLOT_DURATION,
TestConstants.AZTEC_EPOCH_DURATION,
TestConstants.AZTEC_PROOF_SUBMISSION_EPOCHS
);
SLOT_DURATION = TestConstants.AZTEC_SLOT_DURATION;
EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION;
PROOF_SUBMISSION_EPOCHS = TestConstants.AZTEC_PROOF_SUBMISSION_EPOCHS;
timeCheater =
new TimeCheater(address(this), block.timestamp, SLOT_DURATION, EPOCH_DURATION, PROOF_SUBMISSION_EPOCHS);

RollupBuilder builder = new RollupBuilder(address(this)).setManaTarget(MANA_TARGET).setTargetCommitteeSize(0);
builder.deploy();

rollup = IInstance(address(builder.getConfig().rollup));
testERC20 = builder.getConfig().testERC20;
registry = builder.getConfig().registry;

feeJuicePortal = FeeJuicePortal(address(rollup.getFeeAssetPortal()));
rewardDistributor = RewardDistributor(address(registry.getRewardDistributor()));

_;
}

function test_deadlock() public setUpFor("empty_block_1") {
skipBlobCheck(address(rollup));
timeCheater.cheat__progressSlot();

for (uint256 i = 0; i < 10; i++) {
Block memory l2Block = getBlock();
rollup.propose(
l2Block.proposeArgs,
AttestationLibHelper.packAttestations(l2Block.attestations),
l2Block.signers,
l2Block.blobInputs
);
timeCheater.cheat__progressSlot();
}

// Now say that we alter the mana limit! Ensure that we can still produce blocks!
MANA_TARGET = 1e6;
vm.expectEmit(true, true, true, true, address(rollup.getInbox()));
emit IInbox.InboxSynchronized(12);
vm.prank(Ownable(address(rollup)).owner());
rollup.updateManaTarget(MANA_TARGET);

Block memory nonEmptyBlock = getBlock();
rollup.propose(
nonEmptyBlock.proposeArgs,
AttestationLibHelper.packAttestations(nonEmptyBlock.attestations),
nonEmptyBlock.signers,
nonEmptyBlock.blobInputs
);

assertEq(rollup.getPendingBlockNumber(), 11);
assertEq(rollup.getInbox().getInProgress(), 13);
}

function getBlock() internal view returns (Block memory) {
// We will be using the genesis for both before and after. This will be impossible
// to prove, but we don't need to prove anything here.
bytes32 archiveRoot = bytes32(Constants.GENESIS_ARCHIVE_ROOT);

ProposedHeader memory header = full.block.header;

Slot slotNumber = rollup.getCurrentSlot();
Timestamp ts = rollup.getTimestampForSlot(slotNumber);

// Updating the header with important information!
header.lastArchiveRoot = archiveRoot;
header.slotNumber = slotNumber;
header.timestamp = ts;
header.coinbase = address(bytes20("coinbase"));
header.feeRecipient = bytes32(0);
header.gasFees.feePerL2Gas = SafeCast.toUint128(rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true));
if (MANA_TARGET > 0) {
header.totalManaUsed = MANA_TARGET;
} else {
header.totalManaUsed = 0;
}

ProposeArgs memory proposeArgs = ProposeArgs({
header: header,
archive: archiveRoot,
stateReference: EMPTY_STATE_REFERENCE,
oracleInput: OracleInput({feeAssetPriceModifier: 0})
});

CommitteeAttestation[] memory attestations = new CommitteeAttestation[](0);
address[] memory signers = new address[](0);

return Block({
proposeArgs: proposeArgs,
blobInputs: full.block.blobCommitments,
attestations: attestations,
signers: signers
});
}
}
31 changes: 21 additions & 10 deletions yarn-project/end-to-end/src/fixtures/l1_to_l2_messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,31 @@ export async function sendL1ToL2Message(
throw new Error(`Receipt transaction hash mismatch: ${txReceipt.transactionHash} !== ${txHash}`);
}

// Exactly 1 event should be emitted in the transaction
if (txReceipt.logs.length !== 1) {
// Filter for MessageSent events from the Inbox contract by trying to decode each log
const messageSentLogs = txReceipt.logs
.filter(log => log.address.toLowerCase() === ctx.l1ContractAddresses.inboxAddress.toString().toLowerCase())
.map(log => {
try {
const decoded = decodeEventLog({
abi: InboxAbi,
data: log.data,
topics: log.topics,
});
return { log, decoded };
} catch {
return null; // Not a decodable event from this ABI
}
})
.filter((item): item is { log: any; decoded: any } => item !== null && item.decoded.eventName === 'MessageSent');

if (messageSentLogs.length !== 1) {
throw new Error(
`Wrong number of logs found in ${txHash} transaction (got ${txReceipt.logs.length} expected 1)\n${tryJsonStringify(txReceipt.logs)}`,
`Wrong number of MessageSent logs found in ${txHash} transaction (got ${messageSentLogs.length} expected 1)\n${tryJsonStringify(messageSentLogs.map(item => item.log))}`,
);
}

// We decode the event and get leaf out of it
const messageSentLog = txReceipt.logs[0];
const topics = decodeEventLog({
abi: InboxAbi,
data: messageSentLog.data,
topics: messageSentLog.topics,
});
// We already have the decoded event
const topics = messageSentLogs[0].decoded;
const receivedMsgHash = topics.args.hash;
const receivedGlobalLeafIndex = topics.args.index;

Expand Down
Loading