From 2dcb44eaa444839c37b230ad91303b3200a8f774 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 9 Apr 2026 09:09:05 -0600 Subject: [PATCH 1/5] write out checks --- src/rollup/RollupUserLogic.sol | 66 ++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/src/rollup/RollupUserLogic.sol b/src/rollup/RollupUserLogic.sol index b064dc32..8ebb4afa 100644 --- a/src/rollup/RollupUserLogic.sol +++ b/src/rollup/RollupUserLogic.sol @@ -80,17 +80,6 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { ConfigData calldata prevConfig, bytes32 inboxAcc ) external onlyValidator(msg.sender) whenNotPaused { - /* - * To confirm an assertion, the following must be true: - * 1. The assertion must be pending - * 2. The assertion's deadline must have passed - * 3. The assertion's prev must be latest confirmed - * 4. The assertion's prev's child confirm deadline must have passed - * 5. If the assertion's prev has more than 1 child, the assertion must be the winner of the challenge - * - * Note that we do not need to ever reject invalid assertion because they can never confirm - * and the stake on them is swept to the loserStakeEscrow as soon as the leaf is created - */ // The assertion's must exists and be pending and will be validated in RollupCore.confirmAssertionInternal AssertionNode storage assertion = getAssertionStorage(assertionHash); @@ -285,6 +274,61 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { reduceStakeTo(msg.sender, target); } + address zlFastConfirmer; + function fastConfirmZeroLevelBold() external whenNotPaused { + // all of the checks / side effects that are performed during normal confirmation are as follows (reading through confirmAssertion()) + // - whenNotPaused + // - onlyValidator + // - we validate the prev assertion's config hash. (we only do this to find the challenge manager) + // - block.number >= assertion.createdAtBlock + prevConfig.confirmPeriodBlocks + // - require(prevAssertionHash == latestConfirmed(), "PREV_NOT_LATEST_CONFIRMED"); + // - if the prev has more than 1 child, check if this assertion is the challenge winner and grace period passed + // - require assertion is pending + // - validate assertion preimage (global state needed for outbox, assertion hash needed to set latest confirmed) + // - set outbox sendroot + // - set latest confirmed + // - set assertion status to confirmed + // - emit AssertionConfirmed event + + // the subset of checks / side effects that are kept for fastConfirmZeroLevelBold are as follows: + // - whenNotPaused + // - require(prevAssertionHash == latestConfirmed(), "PREV_NOT_LATEST_CONFIRMED"); + // - require assertion is pending + // - validate assertion preimage (global state needed for outbox, assertion hash needed to set latest confirmed) + // - set outbox sendroot + // - set latest confirmed + // - set assertion status to confirmed + // - emit AssertionConfirmed event + + // new checks: + // - is zlFastConfirmer + } + + function fastConfirmNewAssertionZeroLevelBold() external whenNotPaused { + // all of the checks / side effects that are performed during normal creation are as follows (reading through stakeOnNewAssertion()) + // - whenNotPaused + // - onlyValidator + // - require isStaked + // - require baseStake >= assertion.beforeStateData.configData.requiredStake (config hash validated later) + // - require baseStake isn't decreasing + // - require assertion's prev exists + // - require staker is staked on the prev or the prev have a child (not staked on another branch) + // - createNewAssertion() - todo: list out what's in here + // - set staker's latest staked assertion to the new assertion + // - if not overflow, require time since prev >= minimumAssertionPeriod + // - transfer stake to appropriate escrow + + // the subset of checks / side effects that are kept for fastConfirmNewAssertionZeroLevelBold are as follows: + // - whenNotPaused + // - require assertion's prev exists + // - createNewAssertion() + // - if not overflow, require time since prev >= minimumAssertionPeriod + // - transfer stake to appropriate escrow + + // new checks: + // - is zlFastConfirmer + } + /** * @notice This allows fastConfirmers to force confirm any pending assertion */ From 2b3c6567736606ab16aad87982c8e5b86c7a1469 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:15:41 -0600 Subject: [PATCH 2/5] write out createNewAssertion internals --- src/rollup/RollupUserLogic.sol | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/rollup/RollupUserLogic.sol b/src/rollup/RollupUserLogic.sol index 8ebb4afa..b54b4e41 100644 --- a/src/rollup/RollupUserLogic.sol +++ b/src/rollup/RollupUserLogic.sol @@ -313,7 +313,24 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { // - require baseStake isn't decreasing // - require assertion's prev exists // - require staker is staked on the prev or the prev have a child (not staked on another branch) - // - createNewAssertion() - todo: list out what's in here + // - createNewAssertion(): + // - validate config hash against prev assertion + // - require afterState is FINISHED or ERRORED + // - require beforeState matches prevAssertionHash + // - require beforeState is FINISHED + // - validate inbox position: afterGS >= beforeGS, afterGS <= nextInboxPosition + // - detect overflow assertion (didn't reach target nextInboxPosition) + // - require afterGS inbox position <= current bridge inbox count + // - require nextInboxPosition <= current bridge inbox count + // - compute nextInboxPosition for the next assertion (currentInboxCount, or +1 if no new messages) + // - require afterInboxPosition != 0 + // - fetch sequencerBatchAcc from bridge + // - compute newAssertionHash, check against expectedAssertionHash + // - require assertion not already seen + // - create AssertionNode in storage with configHash + // - mark prevAssertion as having a child + // - emit AssertionCreated event + // - record ArbSys block number if on Arbitrum // - set staker's latest staked assertion to the new assertion // - if not overflow, require time since prev >= minimumAssertionPeriod // - transfer stake to appropriate escrow From 6d165aa424ea9056d5cbad35ec8342bec02ae7a5 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:33:00 -0600 Subject: [PATCH 3/5] implement fastConfirmZeroLevelBold --- src/rollup/RollupUserLogic.sol | 40 +++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/rollup/RollupUserLogic.sol b/src/rollup/RollupUserLogic.sol index b54b4e41..a65b78cf 100644 --- a/src/rollup/RollupUserLogic.sol +++ b/src/rollup/RollupUserLogic.sol @@ -274,8 +274,16 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { reduceStakeTo(msg.sender, target); } + + // this version assumes there are no checks being performed by the zlFastConfirmer besides snark + council. + // it is a reimplementation of fastConfirmAssertion + ZeroLevelBoldFastConfirmer to validate the approach address zlFastConfirmer; - function fastConfirmZeroLevelBold() external whenNotPaused { + function fastConfirmZeroLevelBold( + bytes32 assertionHash, + bytes32 prevAssertionHash, + AssertionState calldata confirmState, + bytes32 inboxAcc + ) external whenNotPaused { // all of the checks / side effects that are performed during normal confirmation are as follows (reading through confirmAssertion()) // - whenNotPaused // - onlyValidator @@ -283,25 +291,31 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { // - block.number >= assertion.createdAtBlock + prevConfig.confirmPeriodBlocks // - require(prevAssertionHash == latestConfirmed(), "PREV_NOT_LATEST_CONFIRMED"); // - if the prev has more than 1 child, check if this assertion is the challenge winner and grace period passed - // - require assertion is pending - // - validate assertion preimage (global state needed for outbox, assertion hash needed to set latest confirmed) - // - set outbox sendroot - // - set latest confirmed - // - set assertion status to confirmed - // - emit AssertionConfirmed event + // - confirmAssertionInternal(): + // - require assertion is pending + // - validate assertion preimage (global state needed for outbox, assertion hash needed to set latest confirmed) + // - set outbox sendroot + // - set latest confirmed + // - set assertion status to confirmed + // - emit AssertionConfirmed event // the subset of checks / side effects that are kept for fastConfirmZeroLevelBold are as follows: // - whenNotPaused // - require(prevAssertionHash == latestConfirmed(), "PREV_NOT_LATEST_CONFIRMED"); - // - require assertion is pending - // - validate assertion preimage (global state needed for outbox, assertion hash needed to set latest confirmed) - // - set outbox sendroot - // - set latest confirmed - // - set assertion status to confirmed - // - emit AssertionConfirmed event + // - confirmAssertionInternal(): + // - require assertion is pending + // - validate assertion preimage (global state needed for outbox, assertion hash needed to set latest confirmed) + // - set outbox sendroot + // - set latest confirmed + // - set assertion status to confirmed + // - emit AssertionConfirmed event // new checks: // - is zlFastConfirmer + + require(msg.sender == zlFastConfirmer, "NOT_ZL_FAST_CONFIRMER"); + require(prevAssertionHash == latestConfirmed(), "PREV_NOT_LATEST_CONFIRMED"); // this check exists in the ZeroLevelBoldFastConfirmer in the other impl + confirmAssertionInternal(assertionHash, prevAssertionHash, confirmState, inboxAcc); } function fastConfirmNewAssertionZeroLevelBold() external whenNotPaused { From fd33baea6ed0976e5a12730e43b4dcbfcae84427 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:56:16 -0600 Subject: [PATCH 4/5] implement fastConfirmNewAssertionZeroLevelBold --- src/rollup/RollupUserLogic.sol | 46 ++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/rollup/RollupUserLogic.sol b/src/rollup/RollupUserLogic.sol index a65b78cf..1621d701 100644 --- a/src/rollup/RollupUserLogic.sol +++ b/src/rollup/RollupUserLogic.sol @@ -283,7 +283,7 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { bytes32 prevAssertionHash, AssertionState calldata confirmState, bytes32 inboxAcc - ) external whenNotPaused { + ) public whenNotPaused { // all of the checks / side effects that are performed during normal confirmation are as follows (reading through confirmAssertion()) // - whenNotPaused // - onlyValidator @@ -318,7 +318,11 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { confirmAssertionInternal(assertionHash, prevAssertionHash, confirmState, inboxAcc); } - function fastConfirmNewAssertionZeroLevelBold() external whenNotPaused { + // this is a reimplementation of fastConfirmNewAssertion + ZeroLevelBoldFastConfirmer to validate the approach + function fastConfirmNewAssertionZeroLevelBold( + AssertionInputs calldata assertion, + bytes32 expectedAssertionHash + ) external whenNotPaused { // all of the checks / side effects that are performed during normal creation are as follows (reading through stakeOnNewAssertion()) // - whenNotPaused // - onlyValidator @@ -358,6 +362,44 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { // new checks: // - is zlFastConfirmer + // - require expectedAssertionHash is supplied + + require(msg.sender == zlFastConfirmer, "NOT_ZL_FAST_CONFIRMER"); + + require(expectedAssertionHash != bytes32(0), "EXPECTED_ASSERTION_HASH"); + + bytes32 prevAssertion = RollupLib.assertionHash( + assertion.beforeStateData.prevPrevAssertionHash, + assertion.beforeState, + assertion.beforeStateData.sequencerBatchAcc + ); + getAssertionStorage(prevAssertion).requireExists(); + + AssertionStatus status = getAssertionStorage(expectedAssertionHash).status; + if (status == AssertionStatus.NoAssertion) { + // If not exists, we create the new assertion + (bytes32 newAssertionHash, bool overflowAssertion) = + createNewAssertion(assertion, prevAssertion, expectedAssertionHash); + + if (!overflowAssertion) { + uint256 timeSincePrev = block.number - getAssertionStorage(prevAssertion).createdAtBlock; + // Verify that assertion meets the minimum Delta time requirement + require(timeSincePrev >= minimumAssertionPeriod, "TIME_DELTA"); + } + + if (!getAssertionStorage(newAssertionHash).isFirstChild) { + IERC20(stakeToken).safeTransfer( + loserStakeEscrow, assertion.beforeStateData.configData.requiredStake + ); + } + } + + fastConfirmZeroLevelBold( + expectedAssertionHash, + prevAssertion, + assertion.afterState, + bridge.sequencerInboxAccs(assertion.afterState.globalState.getInboxPosition() - 1) + ); } /** From 6f950cba4fd0fabe5ef91accbf4fb335a9fae0a4 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 14 Apr 2026 08:58:31 -0600 Subject: [PATCH 5/5] undo deletion of existing comment --- src/rollup/RollupUserLogic.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/rollup/RollupUserLogic.sol b/src/rollup/RollupUserLogic.sol index 1621d701..3f064857 100644 --- a/src/rollup/RollupUserLogic.sol +++ b/src/rollup/RollupUserLogic.sol @@ -80,6 +80,17 @@ contract RollupUserLogic is RollupCore, UUPSNotUpgradeable, IRollupUser { ConfigData calldata prevConfig, bytes32 inboxAcc ) external onlyValidator(msg.sender) whenNotPaused { + /* + * To confirm an assertion, the following must be true: + * 1. The assertion must be pending + * 2. The assertion's deadline must have passed + * 3. The assertion's prev must be latest confirmed + * 4. The assertion's prev's child confirm deadline must have passed + * 5. If the assertion's prev has more than 1 child, the assertion must be the winner of the challenge + * + * Note that we do not need to ever reject invalid assertion because they can never confirm + * and the stake on them is swept to the loserStakeEscrow as soon as the leaf is created + */ // The assertion's must exists and be pending and will be validated in RollupCore.confirmAssertionInternal AssertionNode storage assertion = getAssertionStorage(assertionHash);