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.
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:
- Keep a cache of reserve balance check results per warm account address (with a count of failed checks for quick lookup)
- Run the reserve balance check only on crossing boundaries where it actually might change. I think that is at transaction start (after auth processing)
CALLwithvalue!=0andCREATEandSELFDESTRUCTand call frame reverting. - Run the check and update cache only for affected addresses (1 or 2 max per such op)
- Have
CHECKRESERVEBALANCEjust 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
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.
We have updated this MIP:
- Instead of a new opcode, it proposes a new precompile at address
0x1001with a methoddippedIntoReserve()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.
My thoughts and comments to the new precompile:
-
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 at0x1000as a percedent,0x1001it is. -
Include the actual solidity selector in the spec (keeping the interface it corresponds to as reference)
-
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?
-
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.
-
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.
- Yes, the staking precompile is the prior art here.
- Agreed, this makes sense - Iāll update to include.
- 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.
- 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.
- 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.