How should MIP-4 integrations handle an already-active reserve violation?

Continuing the discussion from MIP-4: Reserve Balance Introspection:

I’ve been working with the Cortex Open Source Working Group’s MIPs subgroup on a reusable MIP-4 guard for smart accounts and application contracts. While implementing the guard, I found a case where dippedIntoReserve() does not provide enough information to judge one operation safely.

The precompile returns one Boolean for the whole transaction. When an operation starts with false, the result is clear:

false -> false: the operation did not introduce a violation
false -> true: the operation introduced a violation

The problem starts when an operation begins with true.

Account A dips below reserve          -> true
Account B performs an innocent call   -> true

The result is also true -> true when Account B adds another violation:

Account A dips below reserve          -> true
Account B also dips below reserve     -> true

A contract can see the same result in both cases, but it cannot tell whether B was innocent or added a second violating account. The c++ client and monad-revm maintain the violating addresses internally. The 0x1001 precompile only exposes whether that set is empty.

For EntryPoint or smart-account integrations that execute several operations in one transaction, what behavior does Category Labs recommend when an operation begins with dippedIntoReserve() == true?

Should the integration reject the operation because it can no longer attribute the violation? Or is there an isolation pattern that Category Labs expects these integrations to follow?

I documented the two-account case here: Test multi-account reserve dips when the transaction is already in violation · Issue #21 · Cortex-XYZ/monad-mip-lab · GitHub

3 Likes

Definitionally, a transaction can’t begin in a state where dippedIntoReserve() == true. The definition of reserve dipping in the Monad Initial Spec should clarify this: dipping occurs when an EOA spends below the minimum of its reserve threshold and its initial balance at the start of a transaction. It’s therefore not possible to begin in a dipping state.

Thank you!. This clears up the top-level transaction boundary. I was referring to the boundary between UserOperations inside one EntryPoint transaction.

Suppose UserOperation A changes a touched EOA below min(10 MON, its balance at the start of the transaction) and leaves that state in place. Before UserOperation B executes, would dippedIntoReserve() return true?

If so, is the intended EntryPoint pattern to isolate each UserOperation in a revertible call frame, check the signal immediately after it runs, and unwind A before B begins?

1 Like

Yes, this is precisely the usage we intended when designing this precompile.

2 Likes