Yes, the wallet could check its own balance this way, much like it could check its own storage. The user would be able to invalidate an op that is already in the mempool by using a non-op transaction to change the wallet’s balance.
However, the wallet can only check its own value this way, so it can only be used to invalidate the op of a single wallet at a time. The wallet could similarly use a separate transaction to update its nonce and invalidate the op.
Your question does highlight an important point - that the client should treat the value as part of the account state, and not just the storage. I.e. if a wallet calls a function in a 3rd party contract, which doesn’t access storage but does attempt to send value, the clients shouldn’t accept it. Otherwise this could be used to invalidate ops of multiple wallets.
The EIP specifies this condition: The first call does not access mutable state of any contract except the wallet itself and its deposit in the entry point contract.
I now edited it to clarify that mutable state
includes both value and storage. Thanks for bringing it up.
The simulation is performed against the latest state while building the block. It is the block proposer’s responsibility to ensure that an earlier transaction in the block doesn’t invalidate an op. The simplest way to do it is what you suggested - make the handleOps
call in the first transaction in the block. A client could simulate it against a mid-block state or use access lists to prevent conflicts, but making it first is easier. The current client implementations already do this, but I now added a comment to the EIP to make this requirement clearer.
Thanks for highlighting these points! Client developers need to take them into account.