Skip to content

feat: implement reserve balance introspection changes#3

Open
QEDK wants to merge 10 commits intoreleases/v0.9-monadfrom
feat/mip-4
Open

feat: implement reserve balance introspection changes#3
QEDK wants to merge 10 commits intoreleases/v0.9-monadfrom
feat/mip-4

Conversation

@QEDK
Copy link
Member

@QEDK QEDK commented Feb 23, 2026

  • implements precompile changes as part of userop execution
  • adds simulated testcases to assert execution outcome

@QEDK QEDK requested a review from mijovic February 23, 2026 11:10
@QEDK QEDK self-assigned this Feb 23, 2026
Copilot AI review requested due to automatic review settings February 23, 2026 11:10
@QEDK QEDK added the enhancement New feature or request label Feb 23, 2026
@greptile-apps
Copy link

greptile-apps bot commented Feb 23, 2026

Greptile Summary

This PR adds reserve balance introspection to the ERC-4337 EntryPoint, enforcing that UserOperations cannot dip into a Monad-specific reserve balance during execution. After a successful UserOp call, the EntryPoint queries a precompile at 0x1001 via _dippedIntoReserve(). If the precompile reports a violation, the operation is reverted with a new INNER_REVERT_DIPPED_INTO_RESERVE error code (0xdeadba51), the entire prefund is consumed with no refund, and a UserOperationReserveBalanceViolated event is emitted.

  • Adds IReserveBalance interface and _dippedIntoReserve() internal function that queries the precompile, defaulting to true (conservative denial) on call failure
  • Adds new INNER_REVERT_DIPPED_INTO_RESERVE revert code and handling in _executeUserOp, mirroring the existing INNER_REVERT_LOW_PREFUND pattern
  • Adds UserOperationReserveBalanceViolated event to IEntryPoint
  • Tests cover both standard smart accounts and EIP-7702 delegated accounts, including mixed-batch operations, with mock precompile bytecode deployed via hardhat_setCode
  • The reserve check only runs when mode == opSucceeded — reverted inner calls already roll back any reserve dip, so this is correct
  • The precompile is called with .call() rather than .staticcall(), which could be tightened if the check is purely read-only

Confidence Score: 4/5

  • This PR is safe to merge — the core logic is sound and well-tested, with minor style considerations around call safety.
  • The implementation follows the established pattern (mirrors INNER_REVERT_LOW_PREFUND), the conservative default (return true on failure) is correct, and tests cover standard accounts, EIP-7702 accounts, and mixed batches. The two points noted (staticcall vs call, and paymaster postOp gap) are worth discussing but are not blockers.
  • Pay close attention to contracts/core/EntryPoint.sol — specifically the _dippedIntoReserve() function's use of .call() and the reserve check placement relative to paymaster postOp execution.

Important Files Changed

Filename Overview
contracts/core/EntryPoint.sol Core implementation of reserve balance introspection: adds _dippedIntoReserve() check after successful userOp execution in innerHandleOp, new INNER_REVERT_DIPPED_INTO_RESERVE error code, and handler in _executeUserOp. Uses .call() instead of .staticcall() for the precompile query, and doesn't cover reserve dips during paymaster postOp.
contracts/interfaces/IEntryPoint.sol Adds UserOperationReserveBalanceViolated event with userOpHash, sender, and nonce parameters, matching the pattern of the existing UserOperationPrefundTooLow event.
contracts/interfaces/IReserveBalance.sol New interface for the reserve balance precompile at 0x1001. Declares dippedIntoReserve() without a view modifier — consistent with using .call() in the implementation, but could benefit from a view modifier if the precompile is read-only.
test/entrypoint.test.ts Adds mock precompile setup (hardhat_setCode) to the test setup and a new reserve balance precompile introspection describe block with tests for both the success (precompile returns false) and failure (precompile returns true) paths using snapshot/revert.
test/entrypoint-7702.test.ts Adds reserve balance precompile mock setup and comprehensive tests for EIP-7702 accounts including single ops and mixed batches (7702 + smart wallet ops), testing both success and failure paths with proper snapshot/revert management.
test/entrypointsimulations.test.ts Adds mock precompile setup to ensure simulations pass with the reserve balance check active. Minor whitespace change (removed blank line).

Sequence Diagram

sequenceDiagram
    participant Bundler
    participant EP as EntryPoint
    participant Inner as innerHandleOp
    participant Account
    participant RB as ReserveBalance<br/>Precompile (0x1001)

    Bundler->>EP: handleOps([userOp], beneficiary)
    EP->>EP: _iterateValidationPhase()
    EP->>Inner: innerHandleOp(callData, opInfo, context)
    Inner->>Account: Exec.call(sender, callData)
    Account-->>Inner: success / revert

    alt callData succeeded
        Inner->>RB: call(dippedIntoReserve())
        RB-->>Inner: bool dipped

        alt dipped == true or call failed
            Inner-->>EP: revert(INNER_REVERT_DIPPED_INTO_RESERVE)
            EP->>EP: _emitReserveBalanceViolatedEvent()
            EP->>EP: _emitUserOperationEvent(success=false)
            EP->>EP: collected = prefund (no refund)
        else dipped == false
            Inner->>EP: _postExecution(opSucceeded)
            EP->>EP: _emitUserOperationEvent(success=true)
        end
    else callData reverted
        Note over Inner: Skip reserve check<br/>(state already rolled back)
        Inner->>EP: _postExecution(opReverted)
    end

    EP->>Bundler: _compensate(beneficiary, collected)
Loading

Last reviewed commit: 6c16c17

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

6 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements reserve balance introspection for ERC-4337 UserOperations by adding a precompile check that prevents successful operations from dipping into reserve balances.

Changes:

  • Added IReserveBalance interface and reserve balance precompile integration in EntryPoint
  • Added UserOperationReserveBalanceViolated event for tracking violations
  • Added comprehensive test coverage for reserve balance checks across regular accounts, EIP-7702 accounts, and mixed batches

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
contracts/interfaces/IReserveBalance.sol New interface defining the reserve balance precompile contract at address 0x1001
contracts/interfaces/IEntryPoint.sol Added UserOperationReserveBalanceViolated event declaration
contracts/core/EntryPoint.sol Implemented reserve balance check after UserOp execution with special revert handling and event emission
test/entrypoint.test.ts Added tests for reserve balance violations in regular account scenarios
test/entrypointsimulations.test.ts Set up precompile mock for simulation tests
test/entrypoint-7702.test.ts Added comprehensive tests for reserve balance violations with EIP-7702 accounts and mixed batches

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

abi.encodeWithSelector(IReserveBalance.dippedIntoReserve.selector)
);
if (!success || ret.length != 32) {
return true;

Choose a reason for hiding this comment

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

As a design point: if this is instead return false this becomes chain independent and, when run on non-monad chains that do not have reserve balance, will correctly return that the contract is not in a reverting state.

Copy link
Member Author

Choose a reason for hiding this comment

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

we did consider it but it's highly unlikely that this would be deployed on other chains due to the difference in bytecode (and thus, address) and also the side-effect in case a different precompile was to be made available at the same address. i'm erring on the side on caution here but we can consider a chain ID-based condition if we do need it to be agnostic (just that we haven't found a good reason yet).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants