diff --git a/packages/protocol/contract_layout_layer1.md b/packages/protocol/contract_layout_layer1.md index b0c2c85963e..7b0e122e635 100644 --- a/packages/protocol/contract_layout_layer1.md +++ b/packages/protocol/contract_layout_layer1.md @@ -56,3 +56,7 @@ ## ForkRouter +## contracts/layer1/forced-inclusion/TaikoWrapper + +## contracts/layer1/forced-inclusion/ForcedInclusionStore + diff --git a/packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionStore.sol b/packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionStore.sol index 78ae4f92242..0f865b5235b 100644 --- a/packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionStore.sol +++ b/packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionStore.sol @@ -5,6 +5,7 @@ import "src/shared/common/EssentialContract.sol"; import "src/shared/libs/LibMath.sol"; import "src/shared/libs/LibAddress.sol"; import "src/shared/libs/LibStrings.sol"; +import "src/layer1/based/ITaikoInbox.sol"; import "./IForcedInclusionStore.sol"; /// @title ForcedInclusionStore @@ -19,19 +20,20 @@ contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore { uint256 private constant SECONDS_PER_BLOCK = 12; - uint256 public immutable inclusionDelay; + uint8 public immutable inclusionDelay; uint64 public immutable feeInGwei; mapping(uint256 id => ForcedInclusion inclusion) public queue; // slot 1 uint64 public head; // slot 2 uint64 public tail; - uint128 private __reserved1; + uint64 public lastProcessedAtBatchId; + uint64 private __reserved1; uint256[48] private __gap; constructor( address _resolver, - uint256 _inclusionDelay, + uint8 _inclusionDelay, uint64 _feeInGwei ) EssentialContract(_resolver) @@ -60,10 +62,12 @@ contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore { require(blobHash != bytes32(0), BlobNotFound()); require(msg.value == feeInGwei * 1 gwei, IncorrectFee()); + ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); + ForcedInclusion memory inclusion = ForcedInclusion({ blobHash: blobHash, feeInGwei: uint64(msg.value / 1 gwei), - createdAt: uint64(block.timestamp), + createdAtBatchId: inbox.getStats2().numBatches, blobByteOffset: blobByteOffset, blobByteSize: blobByteSize }); @@ -73,23 +77,47 @@ contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore { emit ForcedInclusionStored(inclusion); } - function consumeForcedInclusion(address _feeRecipient) + function consumeOldestForcedInclusion(address _feeRecipient) external nonReentrant - onlyFromNamed(LibStrings.B_TAIKO_FORCED_INCLUSION_INBOX) + onlyFromNamed(LibStrings.B_TAIKO_WRAPPER) returns (ForcedInclusion memory inclusion_) { // we only need to check the first one, since it will be the oldest. uint64 _head = head; ForcedInclusion storage inclusion = queue[_head]; + require(inclusion.createdAtBatchId != 0, NoForcedInclusionFound()); + + ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); + + inclusion_ = inclusion; + delete queue[_head]; - if (inclusion.createdAt != 0 && block.timestamp >= inclusionDelay + inclusion.createdAt) { - inclusion_ = inclusion; - _feeRecipient.sendEtherAndVerify(inclusion.feeInGwei * 1 gwei); - delete queue[_head]; + unchecked { + lastProcessedAtBatchId = inbox.getStats2().numBatches; head = _head + 1; - emit ForcedInclusionConsumed(inclusion); } + + emit ForcedInclusionConsumed(inclusion_); + _feeRecipient.sendEtherAndVerify(inclusion_.feeInGwei * 1 gwei); + } + + function getForcedInclusion(uint256 index) external view returns (ForcedInclusion memory) { + return queue[index]; + } + + function getOldestForcedInclusionDeadline() public view returns (uint256) { + unchecked { + ForcedInclusion storage inclusion = queue[head]; + return inclusion.createdAtBatchId == 0 + ? type(uint64).max + : uint256(lastProcessedAtBatchId).max(inclusion.createdAtBatchId) + inclusionDelay; + } + } + + function isOldestForcedInclusionDue() external view returns (bool) { + ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); + return inbox.getStats2().numBatches >= getOldestForcedInclusionDeadline(); } // @dev Override this function for easier testing blobs diff --git a/packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionInbox.sol b/packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionInbox.sol deleted file mode 100644 index faeec2ed45a..00000000000 --- a/packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionInbox.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "src/layer1/based/TaikoInbox.sol"; -import "./ForcedInclusionStore.sol"; - -/// @title IForcedInclusionInbox -/// @custom:security-contact security@taiko.xyz -interface IForcedInclusionInbox { - event ForcedInclusionProcessed(IForcedInclusionStore.ForcedInclusion); - - /// @notice Proposes a batch of blocks with forced inclusion. - /// @param _forcedInclusionParams An optional ABI-encoded BlockParams for the forced inclusion - /// batch. - /// @param _params ABI-encoded BlockParams. - /// @param _txList The transaction list in calldata. If the txList is empty, blob will be used - /// for data availability. - /// @return info_ The info of the proposed batch. - /// @return meta_ The metadata of the proposed batch. - function proposeBatchWithForcedInclusion( - bytes calldata _forcedInclusionParams, - bytes calldata _params, - bytes calldata _txList - ) - external - returns (ITaikoInbox.BatchInfo memory info_, ITaikoInbox.BatchMetadata memory meta_); -} diff --git a/packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionStore.sol b/packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionStore.sol index 00de1bc1bb5..dae28021a52 100644 --- a/packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionStore.sol +++ b/packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionStore.sol @@ -11,6 +11,8 @@ interface IForcedInclusionStore { /// @dev Error thrown when the fee is incorrect. error IncorrectFee(); + error NoForcedInclusionFound(); + /// @dev Event emitted when a forced inclusion is stored. event ForcedInclusionStored(ForcedInclusion forcedInclusion); /// @dev Event emitted when a forced inclusion is consumed. @@ -19,16 +21,30 @@ interface IForcedInclusionStore { struct ForcedInclusion { bytes32 blobHash; uint64 feeInGwei; - uint64 createdAt; + uint64 createdAtBatchId; uint32 blobByteOffset; uint32 blobByteSize; } + /// @dev Retrieve a forced inclusion request by its index. + /// @param index The index of the forced inclusion request in the queue. + /// @return The forced inclusion request at the specified index. + function getForcedInclusion(uint256 index) external view returns (ForcedInclusion memory); + + /// @dev Get the deadline for the oldest forced inclusion. + /// @return The deadline for the oldest forced inclusion. + function getOldestForcedInclusionDeadline() external view returns (uint256); + + /// @dev Check if the oldest forced inclusion is due. + /// @return True if the oldest forced inclusion is due, false otherwise. + function isOldestForcedInclusionDue() external view returns (bool); + /// @dev Consume a forced inclusion request. /// The inclusion request must be marked as processed and the priority fee must be paid to the /// caller. + /// @param _feeRecipient The address to receive the priority fee. /// @return inclusion_ The forced inclusion request. - function consumeForcedInclusion(address _feeRecipient) + function consumeOldestForcedInclusion(address _feeRecipient) external returns (ForcedInclusion memory); diff --git a/packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionInbox.sol b/packages/protocol/contracts/layer1/forced-inclusion/TaikoWrapper.sol similarity index 81% rename from packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionInbox.sol rename to packages/protocol/contracts/layer1/forced-inclusion/TaikoWrapper.sol index 99ecd0c82fc..6f5b02d4010 100644 --- a/packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionInbox.sol +++ b/packages/protocol/contracts/layer1/forced-inclusion/TaikoWrapper.sol @@ -12,7 +12,7 @@ import "src/layer1/verifiers/IVerifier.sol"; import "src/layer1/based/TaikoInbox.sol"; import "./ForcedInclusionStore.sol"; -/// @title ForcedInclusionInbox +/// @title TaikoWrapper /// @dev This contract is part of a delayed inbox implementation to enforce the inclusion of /// transactions. /// The current design is a simplified and can be improved with the following ideas: @@ -38,12 +38,18 @@ import "./ForcedInclusionStore.sol"; /// consumption. /// /// @custom:security-contact security@taiko.xyz -contract ForcedInclusionInbox is EssentialContract { + +contract TaikoWrapper is EssentialContract { using LibMath for uint256; + /// @dev Event emitted when a forced inclusion is processed. event ForcedInclusionProcessed(IForcedInclusionStore.ForcedInclusion); + /// @dev Error thrown when the oldest forced inclusion is due. + + error OldestForcedInclusionDue(); uint16 public constant MAX_FORCED_TXS_PER_FORCED_INCLUSION = 512; + uint256[50] private __gap; constructor(address _resolver) EssentialContract(_resolver) { } @@ -52,12 +58,14 @@ contract ForcedInclusionInbox is EssentialContract { __Essential_init(_owner); } - /// @notice Proposes a batch of blocks. + /// @notice Proposes a batch of blocks with forced inclusion. /// @param _forcedInclusionParams An optional ABI-encoded BlockParams for the forced inclusion /// batch. /// @param _params ABI-encoded BlockParams. /// @param _txList The transaction list in calldata. If the txList is empty, blob will be used /// for data availability. + /// @return info_ The info of the proposed batch. + /// @return meta_ The metadata of the proposed batch. function proposeBatchWithForcedInclusion( bytes calldata _forcedInclusionParams, bytes calldata _params, @@ -68,21 +76,18 @@ contract ForcedInclusionInbox is EssentialContract { returns (ITaikoInbox.BatchInfo memory info_, ITaikoInbox.BatchMetadata memory meta_) { ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); - (info_, meta_) = inbox.proposeBatch(_params, _txList); - // Process the next forced inclusion. IForcedInclusionStore store = IForcedInclusionStore(resolve(LibStrings.B_FORCED_INCLUSION_STORE, false)); - IForcedInclusionStore.ForcedInclusion memory inclusion = - store.consumeForcedInclusion(msg.sender); + if (_forcedInclusionParams.length == 0) { + require(!store.isOldestForcedInclusionDue(), OldestForcedInclusionDue()); + } else { + IForcedInclusionStore.ForcedInclusion memory inclusion = + store.consumeOldestForcedInclusion(msg.sender); - if (inclusion.createdAt != 0) { - ITaikoInbox.BatchParams memory params; - - if (_forcedInclusionParams.length != 0) { - params = abi.decode(_forcedInclusionParams, (ITaikoInbox.BatchParams)); - } + ITaikoInbox.BatchParams memory params = + abi.decode(_forcedInclusionParams, (ITaikoInbox.BatchParams)); // Overwrite the batch params to have only 1 block and up to // MAX_FORCED_TXS_PER_FORCED_INCLUSION transactions @@ -102,5 +107,7 @@ contract ForcedInclusionInbox is EssentialContract { inbox.proposeBatch(abi.encode(params), ""); emit ForcedInclusionProcessed(inclusion); } + + (info_, meta_) = inbox.proposeBatch(_params, _txList); } } diff --git a/packages/protocol/contracts/layer1/preconf/impl/PreconfRouter.sol b/packages/protocol/contracts/layer1/preconf/impl/PreconfRouter.sol index 2a5e6cfa690..6f80c6f7c1e 100644 --- a/packages/protocol/contracts/layer1/preconf/impl/PreconfRouter.sol +++ b/packages/protocol/contracts/layer1/preconf/impl/PreconfRouter.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import "src/shared/common/EssentialContract.sol"; import "src/shared/libs/LibStrings.sol"; import "src/layer1/based/TaikoInbox.sol"; -import "src/layer1/forced-inclusion/IForcedInclusionInbox.sol"; +import "src/layer1/forced-inclusion/TaikoWrapper.sol"; import "../iface/IPreconfRouter.sol"; import "../iface/IPreconfWhitelist.sol"; @@ -34,14 +34,14 @@ contract PreconfRouter is EssentialContract, IPreconfRouter { require(msg.sender == selectedOperator, NotTheOperator()); // check if we have a forced inclusion inbox - address forcedInclusionInbox = resolve(LibStrings.B_TAIKO_FORCED_INCLUSION_INBOX, true); - if (forcedInclusionInbox == address(0)) { + address wrapper = resolve(LibStrings.B_TAIKO_WRAPPER, true); + if (wrapper == address(0)) { // Call the proposeBatch function on the TaikoInbox address taikoInbox = resolve(LibStrings.B_TAIKO, false); (, meta_) = ITaikoInbox(taikoInbox).proposeBatch(_batchParams, _batchTxList); } else { // Call the proposeBatchWithForcedInclusion function on the ForcedInclusionInbox - (, meta_) = IForcedInclusionInbox(forcedInclusionInbox).proposeBatchWithForcedInclusion( + (, meta_) = TaikoWrapper(wrapper).proposeBatchWithForcedInclusion( _forcedInclusionParams, _batchParams, _batchTxList ); } diff --git a/packages/protocol/contracts/shared/libs/LibStrings.sol b/packages/protocol/contracts/shared/libs/LibStrings.sol index 2ab54b46a38..f629f1a42b1 100644 --- a/packages/protocol/contracts/shared/libs/LibStrings.sol +++ b/packages/protocol/contracts/shared/libs/LibStrings.sol @@ -26,9 +26,8 @@ library LibStrings { bytes32 internal constant B_SGX_WATCHDOG = bytes32("sgx_watchdog"); bytes32 internal constant B_SIGNAL_SERVICE = bytes32("signal_service"); bytes32 internal constant B_TAIKO = bytes32("taiko"); - bytes32 internal constant B_TAIKO_FORCED_INCLUSION_INBOX = - bytes32("taiko_forced_inclusion_inbox"); bytes32 internal constant B_TAIKO_TOKEN = bytes32("taiko_token"); + bytes32 internal constant B_TAIKO_WRAPPER = bytes32("taiko_wrapper"); bytes32 internal constant B_WITHDRAWER = bytes32("withdrawer"); bytes32 internal constant H_SIGNAL_ROOT = keccak256("SIGNAL_ROOT"); bytes32 internal constant H_STATE_ROOT = keccak256("STATE_ROOT"); diff --git a/packages/protocol/script/gen-layouts.sh b/packages/protocol/script/gen-layouts.sh index 6c5feb8b563..9bb1da7f875 100755 --- a/packages/protocol/script/gen-layouts.sh +++ b/packages/protocol/script/gen-layouts.sh @@ -37,7 +37,7 @@ contracts_layer1=( "contracts/layer1/team/TokenUnlock.sol:TokenUnlock" "contracts/layer1/provers/ProverSet.sol:ProverSet" "contracts/layer1/based/ForkRouter.sol:ForkRouter" -"contracts/layer1/forced-inclusion/ForcedInclusionInbox" +"contracts/layer1/forced-inclusion/TaikoWrapper" "contracts/layer1/forced-inclusion/ForcedInclusionStore" ) diff --git a/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol b/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol index b67daf3ca0e..4d1022e3df5 100644 --- a/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol +++ b/packages/protocol/script/layer1/based/DeployProtocolOnL1.s.sol @@ -20,7 +20,7 @@ import "src/layer1/devnet/verifiers/DevnetVerifier.sol"; import "src/layer1/mainnet/MainnetInbox.sol"; import "src/layer1/based/TaikoInbox.sol"; import "src/layer1/fork-router/ForkRouter.sol"; -import "src/layer1/forced-inclusion/ForcedInclusionInbox.sol"; +import "src/layer1/forced-inclusion/TaikoWrapper.sol"; import "src/layer1/forced-inclusion/ForcedInclusionStore.sol"; import "src/layer1/mainnet/multirollup/MainnetBridge.sol"; import "src/layer1/mainnet/multirollup/MainnetERC1155Vault.sol"; @@ -413,7 +413,7 @@ contract DeployProtocolOnL1 is DeployCapability { impl: address( new ForcedInclusionStore( resolver, - vm.envUint("INCLUSION_WINDOW"), + uint8(vm.envUint("INCLUSION_WINDOW")), uint64(vm.envUint("INCLUSION_FEE_IN_GWEI")) ) ), @@ -422,9 +422,9 @@ contract DeployProtocolOnL1 is DeployCapability { }); forcedInclusionInbox = deployProxy({ - name: "taiko_forced_inclusion_inbox", - impl: address(new ForcedInclusionInbox(resolver)), - data: abi.encodeCall(ForcedInclusionInbox.init, (owner)), + name: "taiko_wrapper", + impl: address(new TaikoWrapper(resolver)), + data: abi.encodeCall(TaikoWrapper.init, (owner)), registerTo: resolver }); diff --git a/packages/protocol/test/layer1/Layer1Test.sol b/packages/protocol/test/layer1/Layer1Test.sol index 5a413c70950..c44b210ae43 100644 --- a/packages/protocol/test/layer1/Layer1Test.sol +++ b/packages/protocol/test/layer1/Layer1Test.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.24; import "src/layer1/based/TaikoInbox.sol"; -import "src/layer1/forced-inclusion/ForcedInclusionInbox.sol"; +import "src/layer1/forced-inclusion/TaikoWrapper.sol"; import "src/layer1/forced-inclusion/ForcedInclusionStore.sol"; import "src/layer1/token/TaikoToken.sol"; import "src/layer1/verifiers/SgxVerifier.sol"; @@ -66,18 +66,18 @@ abstract contract Layer1Test is CommonTest { ); } - function deployForcedInclusionInbox() internal returns (ForcedInclusionInbox) { - return ForcedInclusionInbox( + function deployForcedInclusionInbox() internal returns (TaikoWrapper) { + return TaikoWrapper( deploy({ - name: "taiko_forced_inclusion_inbox", - impl: address(new ForcedInclusionInbox(address(resolver))), - data: abi.encodeCall(ForcedInclusionInbox.init, (address(0))) + name: "taiko_wrapper", + impl: address(new TaikoWrapper(address(resolver))), + data: abi.encodeCall(TaikoWrapper.init, (address(0))) }) ); } function deployForcedInclusionStore( - uint256 inclusionDelay, + uint8 inclusionDelay, uint64 feeInGwei, address owner ) diff --git a/packages/protocol/test/layer1/forced-inclusion/ForcedInclusionInbox.t.sol b/packages/protocol/test/layer1/forced-inclusion/ForcedInclusionInbox.t.sol deleted file mode 100644 index 1275afefa23..00000000000 --- a/packages/protocol/test/layer1/forced-inclusion/ForcedInclusionInbox.t.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.24; - -import "../based/InboxTestBase.sol"; -import "test/layer1/based/helpers/Verifier_ToggleStub.sol"; -import "src/layer1/forced-inclusion/ForcedInclusionInbox.sol"; -import "src/layer1/forced-inclusion/ForcedInclusionStore.sol"; - -contract ForcedInclusionInboxTest is InboxTestBase { - function pacayaConfig() internal pure override returns (ITaikoInbox.Config memory) { - return ITaikoInbox.Config({ - chainId: LibNetwork.TAIKO_MAINNET, - maxUnverifiedBatches: 10, - batchRingBufferSize: 15, - maxBatchesToVerify: 5, - blockMaxGasLimit: 240_000_000, - livenessBondBase: 125e18, // 125 Taiko token per batch - livenessBondPerBlock: 5e18, // 5 Taiko token per block - stateRootSyncInternal: 5, - maxAnchorHeightOffset: 64, - baseFeeConfig: LibSharedData.BaseFeeConfig({ - adjustmentQuotient: 8, - sharingPctg: 75, - gasIssuancePerSecond: 5_000_000, - minGasExcess: 1_340_000_000, // correspond to 0.008847185 gwei basefee - maxGasIssuancePerBlock: 600_000_000 // two minutes: 5_000_000 * 120 - }), - provingWindow: 1 hours, - maxSignalsToReceive: 16, - maxBlocksPerBatch: 768, - forkHeights: ITaikoInbox.ForkHeights({ ontake: 0, pacaya: 0 }) - }); - } - - ForcedInclusionInbox internal forcedInclusionInbox; - IForcedInclusionStore internal forcedInclusionStore; - address owner; - - function setUpOnEthereum() internal virtual override { - owner = Alice; - - genesisBlockProposedAt = block.timestamp; - genesisBlockProposedIn = block.number; - - inbox = deployInbox(correctBlockhash(0), pacayaConfig()); - - forcedInclusionStore = deployForcedInclusionStore(100, 100, owner); - forcedInclusionInbox = deployForcedInclusionInbox(); - - signalService = deploySignalService(address(new SignalService(address(resolver)))); - signalService.authorize(address(inbox), true); - - resolver.registerAddress( - block.chainid, "proof_verifier", address(new Verifier_ToggleStub()) - ); - - mineOneBlockAndWrap(12 seconds); - } -} diff --git a/packages/protocol/test/layer1/forced-inclusion/ForcedInclusionStore.t.sol b/packages/protocol/test/layer1/forced-inclusion/ForcedInclusionStore.t.sol index 2603253fdea..62dceb44d2e 100644 --- a/packages/protocol/test/layer1/forced-inclusion/ForcedInclusionStore.t.sol +++ b/packages/protocol/test/layer1/forced-inclusion/ForcedInclusionStore.t.sol @@ -7,7 +7,7 @@ import "src/layer1/forced-inclusion/ForcedInclusionStore.sol"; contract ForcedInclusionStoreForTest is ForcedInclusionStore { constructor( address _resolver, - uint256 _inclusionDelay, + uint8 _inclusionDelay, uint64 _feeInGwei ) ForcedInclusionStore(_resolver, _inclusionDelay, _feeInGwei) @@ -18,16 +18,33 @@ contract ForcedInclusionStoreForTest is ForcedInclusionStore { } } +contract MockInbox { + uint64 public numBatches; + + constructor() { + numBatches = 1; + } + + function setNumBatches(uint64 _numBatches) external { + numBatches = _numBatches; + } + + function getStats2() external view returns (ITaikoInbox.Stats2 memory stats2_) { + stats2_.numBatches = numBatches; + } +} + abstract contract ForcedInclusionStoreTestBase is CommonTest { address internal storeOwner = Alice; address internal whitelistedProposer = Alice; - uint64 internal constant inclusionDelay = 24 seconds; + uint8 internal constant inclusionDelay = 12; uint64 internal constant feeInGwei = 0.001 ether / 1 gwei; ForcedInclusionStore internal store; + MockInbox internal mockInbox; function setUpOnEthereum() internal virtual override { - register(LibStrings.B_TAIKO_FORCED_INCLUSION_INBOX, whitelistedProposer); + register(LibStrings.B_TAIKO_WRAPPER, whitelistedProposer); store = ForcedInclusionStore( deploy({ @@ -38,6 +55,9 @@ abstract contract ForcedInclusionStoreTestBase is CommonTest { data: abi.encodeCall(ForcedInclusionStore.init, (storeOwner)) }) ); + + mockInbox = new MockInbox(); + register(LibStrings.B_TAIKO, address(mockInbox)); } } @@ -92,6 +112,8 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase { vm.deal(Alice, 1 ether); uint64 _feeInGwei = store.feeInGwei(); + mockInbox.setNumBatches(100); + vm.prank(Alice); store.storeForcedInclusion{ value: _feeInGwei * 1 gwei }({ blobIndex: 0, @@ -102,17 +124,16 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase { assertEq(store.head(), 0); assertEq(store.tail(), 1); - uint256 createdAt = block.timestamp; - vm.warp(createdAt + inclusionDelay); + IForcedInclusionStore.ForcedInclusion memory inclusion = store.getForcedInclusion(0); vm.prank(whitelistedProposer); - IForcedInclusionStore.ForcedInclusion memory consumed = store.consumeForcedInclusion(Bob); + inclusion = store.consumeOldestForcedInclusion(Bob); - assertEq(consumed.blobHash, bytes32(uint256(1))); - assertEq(consumed.blobByteOffset, 0); - assertEq(consumed.blobByteSize, 1024); - assertEq(consumed.feeInGwei, _feeInGwei); - assertEq(consumed.createdAt, createdAt); + assertEq(inclusion.blobHash, bytes32(uint256(1))); + assertEq(inclusion.blobByteOffset, 0); + assertEq(inclusion.blobByteSize, 1024); + assertEq(inclusion.feeInGwei, _feeInGwei); + assertEq(inclusion.createdAtBatchId, 100); assertEq(Bob.balance, _feeInGwei * 1 gwei); } @@ -120,6 +141,8 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase { vm.deal(Alice, 1 ether); uint64 _feeInGwei = store.feeInGwei(); + mockInbox.setNumBatches(100); + vm.prank(Alice); store.storeForcedInclusion{ value: _feeInGwei * 1 gwei }({ blobIndex: 0, @@ -134,21 +157,20 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase { vm.prank(Carol); vm.expectRevert(EssentialContract.ACCESS_DENIED.selector); - store.consumeForcedInclusion(Bob); + store.consumeOldestForcedInclusion(Bob); } function test_storeConsumeForcedInclusion_noEligibleInclusion() public { vm.prank(whitelistedProposer); - IForcedInclusionStore.ForcedInclusion memory inclusion = store.consumeForcedInclusion(Bob); - assertEq(inclusion.blobHash, bytes32(0)); - assertEq(inclusion.blobByteOffset, 0); - assertEq(inclusion.blobByteSize, 0); - assertEq(inclusion.feeInGwei, 0); + vm.expectRevert(IForcedInclusionStore.NoForcedInclusionFound.selector); + store.consumeOldestForcedInclusion(Bob); } function test_storeConsumeForcedInclusion_beforeWindowExpires() public { vm.deal(Alice, 1 ether); + mockInbox.setNumBatches(100); + vm.prank(whitelistedProposer); store.storeForcedInclusion{ value: store.feeInGwei() * 1 gwei }({ blobIndex: 0, @@ -156,12 +178,32 @@ contract ForcedInclusionStoreTest is ForcedInclusionStoreTestBase { blobByteSize: 1024 }); + // Verify the stored reqeust is correct + IForcedInclusionStore.ForcedInclusion memory inclusion = store.getForcedInclusion(0); + + assertEq(inclusion.blobHash, bytes32(uint256(1))); + assertEq(inclusion.blobByteOffset, 0); + assertEq(inclusion.blobByteSize, 1024); + assertEq(inclusion.createdAtBatchId, mockInbox.numBatches()); + assertEq(inclusion.feeInGwei, store.feeInGwei()); + vm.warp(block.timestamp + inclusionDelay - 1); vm.prank(whitelistedProposer); - IForcedInclusionStore.ForcedInclusion memory inclusion = store.consumeForcedInclusion(Bob); - assertEq(inclusion.blobHash, bytes32(0)); + + // head request should be consumable + inclusion = store.consumeOldestForcedInclusion(Bob); + assertEq(inclusion.blobHash, bytes32(uint256(1))); + assertEq(inclusion.blobByteOffset, 0); + assertEq(inclusion.blobByteSize, 1024); + assertEq(inclusion.createdAtBatchId, mockInbox.numBatches()); + assertEq(inclusion.feeInGwei, store.feeInGwei()); + + // the head request should have been deleted + inclusion = store.getForcedInclusion(0); + assertEq(inclusion.blobHash, 0); assertEq(inclusion.blobByteOffset, 0); assertEq(inclusion.blobByteSize, 0); + assertEq(inclusion.createdAtBatchId, 0); assertEq(inclusion.feeInGwei, 0); } }