MIP-4: Reserve Balance Introspection

This MIP adds a new opcode that allows contracts on Monad to detect whether their local execution state is in violation of reserve balance and take action to avoid reverting.

4 Likes

Re gas cost

Expected to be `O(N)` in the number of warm accounts (i.e., accounts with modified balances).

Would it be feasible to:

  1. Keep a cache of reserve balance check results per warm account address (with a count of failed checks for quick lookup)
  2. Run the reserve balance check only on crossing boundaries where it actually might change. I think that is at transaction start (after auth processing) CALLwith value!=0 and CREATE and SELFDESTRUCT and call frame reverting.
  3. Run the check and update cache only for affected addresses (1 or 2 max per such op)
  4. Have CHECKRESERVEBALANCE just return the cached result

Then it would be possible to price CHECKRESERVEBALANCE at O(1) (and as cheap as an environment opcode)?

The cost of checking and updating would be assumed to be included in the (expensive) ops from (2.). Cost of memory to hold the extra execution state included in cold access cost.

Motivation

  • simpler testing
  • cheaper for users
  • scales better with # of UserOps in a bundle, i.e. gas cost of a UserOp doesn’t depend on previous ones executed
3 Likes

I think that should be feasible to implement. We’ll try that method once we have our tests nailed down using the eager method as a reference.

Could the MIP link to the algorithm referenced? We will need to update our revm-based codebase to include this opcode.

Yes, we’ll update the MIP to point to the algorithm in the current version of the Monad client - roughly speaking, the opcode will represent a call to dipped_into_reserve here.

1 Like

We have updated this MIP:

  • Instead of a new opcode, it proposes a new precompile at address 0x1001 with a method dippedIntoReserve() with similar semantics to the opcode version.
  • Committed to @pdobaczā€˜s suggestion to incrementally recompute the violation set such that gas costs can be O(1), with a presumedly cheap gas cost.

@ARitz-Cracker we’re still working on the precise algorithm for the incremental recomputation, but will update you when we have it finalised.

2 Likes

My thoughts and comments to the new precompile:

  1. I was considering making the precompile address even more unique (e.g. putting in a `0x0143` or `0x8f` to stand for Monad somewhere, to denote Monad-specific precompiles). But `0x1001` is also fine, as it lies outside of [EIP-7587](EIP-7587: Reserve Precompile Address Range for RIPs) and [EIP-1352](EIP-1352: Specify restricted address range for precompiles/system contracts) doesn’t seem to be implemented. So not sure
    EDIT: Nevermind this, TIL the staking precompile is at 0x1000 as a percedent, 0x1001 it is.

  2. Include the actual solidity selector in the spec (keeping the interface it corresponds to as reference)

  3. Spell out the rationale for the decision of including the solidity selector logic, as opposed to just returning `0x01` on reserve balance violation and `0x` otherwise (or `0x00`). I guess it makes it easier for solidity to call it, without `assembly` snippets, is that all?

  4. Spec out what happens if any other input is given - too short / too long / not the selector. For now I’ve assumed it reverts with empty returndata on too short / not the selector, and works happily if extra bytes are given on top of the correct selector. On reverts unspent gas returned to caller. I admit I don’t remember off-hand what a solidity contract like this would do by default, but naturally it should also be consistent.

  5. In relation to the 2 previous points: do we see it as advantageous that contract code designed to run on Monad (and use the new precompile) would also run on other EVM chains without modification? Right now I think the design is ok, but would require such ā€œgenericā€ code to first check RETURNDATASIZE and later the value. Empty RETURNDATASIZE would mean reserve balance is ok.

  1. Yes, the staking precompile is the prior art here.
  2. Agreed, this makes sense - I’ll update to include.
  3. Trying to avoid assembly and for general developer ease. Again, the staking precompile is set up this way and we’re using that as prior art in large part here.
  4. Slight differences here from your assumptions: on error / revert, the precompile consumes all the frame gas sent, rather than refunding any. This is consistent with Ethereum precompiles, but not with Solidity functions. The machinery here is shared with the staking precompile. Extra calldata causes a revert, rather than being ignored as solc would. I’ll clarify all this in the MIP.
  5. I guess that’s true - you could write a reserve-balance-aware entrypoint that would work on any chain. I hadn’t thought of that previously (as above, motivation is just easy integration into existing toolchains + similarity to the staking contract), but it’s a useful side effect.

Edit: the MIP-4 document has been updated to include these suggestions.

1 Like