Appendix: Coq proof of reserve-balance safety

This file accompanies the blog post and serves as a literate walkthrough of the final model.
The guiding idea is simple:
  • *Consensus* computes a **worst-case, fee-only budget** (the “effective reserve”) for the yet-to-be-executed suffix and only proposes blocks when that budget stays non-negative for every **sender** that appears later.
  • *Execution* runs the actual EVM step and enforces that **no account dips below its protected reserve slice** (with a carefully fenced “emptying” exception). If a transaction would violate that, it is reverted in place.
The definitions below encode those two algorithms and the invariants we prove about them. The consensus check is defined in consensusAcceptableTxs, the execution check is in execTx. The main soundness theorem is fullBlockStep. References to any Coq item are hyperlinked to its definition if the definition is in this file or in the Coq standard library.


Preliminaries

This is the full EVM state that EVM execution takes as input and returns as output.
Definition StateOfAccounts : Type := EvmAddr AccountM.

Definition addrDelegated (s: StateOfAccounts) (a : EvmAddr) : bool :=
  match delegatedTo (s a) with
  | []false
  | _true
  end.

Many of the EVM semantics definitions we use come from Yoichi's EVM semantics, developed several years ago. The definition of Transaction there lacks fields to support newer features like delegation. Also, the last field is to support user-configurable reserve balances in Monad. There is a new transaction type which can update the configured reserve balance of the sender. Such transactions do nothing else.
Record TxExtra :=
  {
    dels: list EvmAddr;
    undels: list EvmAddr;

    
The fields above should ultimately come from EVM semantics and not here. The fields below are monad-specific.
    reserveBalUpdate: option N
   
^ updates the reserve balance of the sender if Some. In that case, the transaction does nothing else, e.g., smart contract invocation or transfer.
  }.

Definition TxWithHdr : Type := (BlockHeader × TxExtra) × (Transaction).

Our **fee upper bound** is intentionally pessimistic: the consensus rule reasons about gas_limit × gas_price, never about *actual* gas used. This mismatch is the source of slack that makes safety proofs simple while remaining implementable.
Definition maxTxFee (t: TxWithHdr) : N :=
  ((w256_to_N (block.tr_gas_price t.2)) × (w256_to_N (block.tr_gas_limit t.2))).

The proofs in this file never unfold the definition of maxTxFee, so nothing will break if this definition is changed.

Section K.
Parameterization by the lookahead window K: consensus can be ahead of execution by at most K. n+Kth block must have the state root hash after execution block n. the next 2 variables are parameters for the rest of the proofs. All “emptying” checks therefore only look at activity within a K-sized window. We also fix a default reserve cap used when an account has never reconfigured its reserve.
Variable K: N.
Variable DefaultReserveBal: N.

Definition sender (t: TxWithHdr): EvmAddr := tsender t.2.

Definition value (t: TxWithHdr): N := w256_to_N (block.tr_value t.2).

Definition addrsDelUndelByTx (tx: TxWithHdr) : list EvmAddr := (dels tx.1.2 ++undels tx.1.2).

Definition txDelUndelAddr (addr: EvmAddr) (tx: TxWithHdr) : bool :=
  bool_decide (addr addrsDelUndelByTx tx).

Definition txBlockNum (t: TxWithHdr) : N := number t.1.1.

Definition reserveBalUpdateOfTx (t: TxWithHdr) : option N :=
  reserveBalUpdate t.1.2.

To implement reserve balance checks, execution needs to maintain some extra state (beyond the core EVM state) for each account:
Record ExtraAcState :=
  {
    lastTxInBlockIndex : option N;
    
^ last block index where this account sent a transaction. In the implementation, we can just track the last 2K range, e.g. this can be None if the last tx was more than 2K block before. we do not need to store this information in the db as it can be easily computed
    lastDelUndelInBlockIndex : option N;
    
^ last block index where this address was delegated or undelegated. In the implementation, we can just track the last 2K range.
    configuredReserveBal: N;
    
^ the current configured reserve balance of the account. will either be DefaultReserveBal or something else if the account sent a transaction where reserveBalUpdate is not None
  }.

#[only(lens)] derive ExtraAcState.
#[global] Instance inhabitedeacs : Inhabited ExtraAcState := populate (Build_ExtraAcState None None DefaultReserveBal).

Definition ExtraAcStates := (EvmAddr ExtraAcState).

Our modified execution function which does reserve balance checks will use the following type as input/output. Consensus checks also take this as input, where the AugmentedState is the state after N-K block when proposing a new block. However, when the next pending (already proposed) block is executed, it can be a later state.
Definition AugmentedState : Type := StateOfAccounts × ExtraAcStates.

Definition indexWithinK (proj: ExtraAcState option N) (state : ExtraAcStates) (tx: TxWithHdr) : bool :=
  let startIndex := txBlockNum tx -K in
  match proj (state (sender tx)) with
  | Some index
      bool_decide (startIndex index txBlockNum tx)
  | Nonefalse
  end.

returns true if sender tx sent a transaction within the last K blocks of txBlockNum tx
returns true if the account sender tx was delegated/undelegated within the last K blocks of txBlockNum tx
Definition existsDelUndelTxWithinK (state : AugmentedState) (tx: TxWithHdr) : bool :=
  indexWithinK lastDelUndelInBlockIndex (snd state) tx.

Definition hasCode (s: StateOfAccounts) (addr: EvmAddr): bool:=
  block.block_account_hascode (coreAc (s addr)).


Definition updateKey {T} `{c: EqDecision T} {V} (oldmap: T V) (updKey: T) (f: V V) : T V :=
  fun kif (bool_decide (k=updKey)) then f (oldmap updKey) else oldmap k.

Disable Notation "!!!".

Lemma updateKeyLkp3 {T} `{c: EqDecision T} {V} (m: T V) (a b: T) (f: V V) :
  (updateKey m a f) b = if (bool_decide (b=a)) then (f (m a)) else m b.

Consensus Check (algo 1)

Below, we build up the definition of the consensus check consensusAcceptableTxs The “emptying” gate:
A sender may “empty” (i.e., allow value to eat into reserve for this one tx) iff all three are true:
  • it is *not* currently delegated,
  • it had no delegation change within the last K blocks (including the in-flight suffix),
  • it did not send a transaction in the last K blocks (including the in-flight suffix).
This gate is what lets consensus handle fee-only budgets and still tolerate occasional value drains safely.
The effective reserve map:
Consensus reasons about *how much of the protected (reserve) slice of the balance can be consumed* by the yet-to-run suffix. This is the “effective reserve” map:
  • It is initialized with min(balance, configuredReserve).
  • Every transaction removes *at most* its fee from the sender’s entry, except for the “emptying” hole, where the pessimistic removal accounts for value + fee in one shot.
Notice the type is Z: negative entries encode an *over-consumption* that should make the proposal unacceptable.
Definition EffReserveBals := EvmAddr Z.

Definition configuredReserveBalOfAddr (s: ExtraAcStates) addr := configuredReserveBal (s addr).


Definition balanceOfAc (s: StateOfAccounts) (a: EvmAddr) : N :=
  balance (s a).

Definition updateBalanceOfAc (s: StateOfAccounts) (addr: EvmAddr) (upd: N N) : StateOfAccounts :=
  updateKey s addr (fun oldold &: _balance %= upd).

Definition balanceOfAcA (s: AugmentedState) (ac: EvmAddr) := balanceOfAc s.1 ac.

Definition initialEffReserveBals (s: AugmentedState) : EffReserveBals :=
  fun addr ⇒ (balanceOfAc s.1 addr `min` configuredReserveBalOfAddr s.2 addr).

Consensus’ decrement step:
remainingEffReserveBals is the algebraic heart of consensus check algorithm: fold this function left-to-right over the suffix, and you get the remaining worst-case protected reserve for every sender. Formally, this function conservatively estimates the remaining effective reserve balance after executing candidateTx, assuming preCandidateTxResBalances is the estimate before candidateTx. This is done in a setting where the latest available state is preIntermediatesState and intermediates are all the transactions between preIntermediatesState and candidateTx.
Only the current sender’s entry changes; all other entries are unchanged.
In the definition of newBal (let binding), the subtraction is capped below at 0: the result is a natural number (N). So, if if sbal < maxTxFee next + value next but maxTxFee next sbal, this transaction (next) will be accepted but all subsequent ones from the same sender will be rejected as the remaining effective reserve balance becomes 0.
regular tx, not one that sets reserve balance

Algorithm 1 (consensus): acceptability of a suffix

A suffix is **consensus-acceptable** if, after pessimistically removing the head-then-tail debits, *every sender that appears later still has a non-negative effective reserve*. This is exactly the safety condition proposers maintain as they build blocks up to K ahead.
When evaluating a new tx at block number N to add at the end of postStateSuffix, latestState must be the state after N-K block when proposing a new block. However, when the next pending (already proposed) block is executed, we need to derive that the remaining already proposed transactions are still valid on top of the more recent state: this is what the main soundness lemma fullBlockStep proves, in addition to proving that postStateSuffix will execute without running out of fees to pay.

Execution Check (algo 2)

The execution logic is also tweaked to ensure that a transaction cannot dip too much into reserves so as to not have enough fees for a transaction already included by consensus. First, some helpers for that.
isAllowedToEmptyExec is a trivial wrapper used in execution, where there are intermediate transactions between the current transaction and the last known fully executed state.
updateExtraState is the execution-time maintenance of the tiny history we need for emptiness checks and reserve-config changes. It is deliberately side-effect-free except for the fields it touches.

Definition updateExtraState (a: ExtraAcStates) (tx: TxWithHdr) : ExtraAcStates :=
  (fun addr
     let oldes := a addr in
       {|
         lastTxInBlockIndex :=
           if bool_decide (sender tx = addr)
           then Some (txBlockNum tx)
           else lastTxInBlockIndex oldes;
         lastDelUndelInBlockIndex :=
           if bool_decide (addr addrsDelUndelByTx tx)
           then Some (txBlockNum tx)
           else lastDelUndelInBlockIndex oldes;
         configuredReserveBal:=
           if bool_decide (sender tx = addr)
           then
             match reserveBalUpdateOfTx tx with
             | Some newRbnewRb
             | NoneconfiguredReserveBal oldes
             end
           else configuredReserveBal oldes
       |}
    ).


Abstract execution and revert

We postulate a single-step EVM core (evmExecTxCore) that returns the new state and the set of changed accounts; and the revert step for failed checks. This keeps the reserve logic orthogonal to the (much larger) EVM semantics.

Algorithm 2 (execution): execute a transaction

This assumes that t has already been validated to ensure that the sender has enough balance to cover maxTxFee.
Execution proceeds as follows:
  • Special “reserve update” tx: pay fee; set new configured reserve.
  • Otherwise, run the core EVM step to obtain the *actual* post state.
  • For *changed* accounts, enforce reserve discipline:
    • Non-sender: their *previous* protected slice must still fit in the *current* balance (unless they have code, deployed in this tx or before). if the account does have code, the address was generated by a keccak, so the proof assumes that nobody has the corresponding private key and thus the address can never send a transaction, thus this step can empty the balance.
    • Sender: either allowed to empty, or must retain at least min(R, balance_before) maxTxFee.
  • If any check fails, revert the tx *and still* update the extra history (so K-window bookkeeping remains accurate).

Definition execValidatedTx (s: AugmentedState) (t: TxWithHdr)
  : AugmentedState :=
  match reserveBalUpdateOfTx t with
  | Some n(updateBalanceOfAc s.1 (sender t) (fun bb - maxTxFee t)
                 , updateExtraState s.2 t)
  | None

     let (si, changedAccounts) := evmExecTxCore (fst s) t in
     let balCheck (a: EvmAddr) :=
       let ReserveBal := configuredReserveBalOfAddr s.2 a in
       let erb:N := ReserveBal `min` (balanceOfAcA s a) in
       if hasCode si a
       then true
       else
         if bool_decide (sender t =a)
         then if isAllowedToEmptyExec s t then true else bool_decide ((erb - maxTxFee t) balanceOfAc si a)
         else bool_decide (erb balanceOfAc si a) in
     let allBalCheck : bool := (forallb balCheck changedAccounts) in
     if (allBalCheck)
     then (si, updateExtraState s.2 t)
     else (revertTx s.1 t, updateExtraState s.2 t)
  end
.

Note that because the hasCode check is done on si (the result of running the EVM blackbox to execute t), not s (the pre-exec state), the following scenario is allowed.
  • Alice sends money to addr2 in some contract. Alice is EOA.
  • Alice sends tx foo to a smart contract address addr.
  • addr execution deploys code at addr2 and calls it, and the call empties addr2.
Basic pre-validation: proposers guaranteed fee solvency for senders of the head transaction; executors re-check that cheaply before doing real work. The implementation checks nonces as well, but here we are only concerned with tx fees.
Definition validateTx (preTxState: StateOfAccounts) (t: TxWithHdr): bool :=
   bool_decide (maxTxFee t balanceOfAc preTxState (sender t))%N.

Top-level execution wrapper that fails fast when validation fails. None means the execution of the whole block containing t aborts, which is what the consensus/execution checks must guarantee to never happen.
execute a list of transactions one by one. note that the execution of any tx returns None (balance insufficient to cover fees), the entire execution (of the whole list of txs) returns None
Fixpoint execTxs (s: AugmentedState) (ts: list TxWithHdr): option AugmentedState :=
  match ts with
  | []Some s
  | t::tls
      match execTx s t with
      | Some siexecTxs si tls
      | NoneNone
      end
  end.

Main correctness theorem

our correctness property only holds when the set of proposed transactions are within K. this helps capture that assumption
Fixpoint blockNumsInRange (ltx: list TxWithHdr) : Prop :=
  match ltx with
  | []True
  | htx::ttx
      ( txext, txext ttx txBlockNum txext - K txBlockNum htx txBlockNum txext)
       blockNumsInRange ttx
  end.

Definition txCannotCreateContractAtAddrs tx (eoasWithPrivateKey: list EvmAddr) :=
   s, let sf := (execValidatedTx s tx) in
             addr, addr eoasWithPrivateKey hasCode s.1 addr = false hasCode sf.1 addr = false.

The lemma below is probably what one would come up first as the main correctness theorem. blocks represents the transactions in the blocks proposed after latestState. It says that consensus checks (consensusAcceptableTxs latestState blocks) implies that the execution of all transactions blocks one by one, starting from the state latestState will succeed and not abort (None) due to preTx balance being less than maxTxFee.

main correctness theorem

We will prove the above correctness theorem below, but the actual correctness theorem we need is slightly different. Suppose we split blocks in the theorem above into firstblock and restblocks such that blocks=firstblocks++blocksrest and suppose these blocks together are all transactions from the K proposed blocks since the last consensed state. Now, consensus will wait for execution to catch up and compute the state after firstblock, say latestState'. After that, consensus should check the next block after blocksrest w.r.t latestState'. At that time, it needs to know that blocksrest is already valid w.r.t latestState', i.e. consensusAcceptableTxs latestState' blocksrest. This is precisely what the main theorem, shown next does:
^ execution cannot abort (indicated by returning None) due to balance being insufficient to even pay fees
     | Some si
        
in this case, we have enough conditions to guarantee fee-solvency of block2, so that it can be extended and then this lemma reapplied
         consensusAcceptableTxs si restblocks
          blockNumsInRange restblocks
          ( ac, ac (map sender restblocks) hasCode si.1 ac = false)
          ( txext, txext (restblocks) txCannotCreateContractAtAddrs txext (map sender (restblocks)))
     end.

Proof

core execution assumptions

To prove the theorem fullBlockStep, we need to make some assumptions about how the core EVM execution updates balances and delegated-ness. The names of these axioms are fairly descriptive.

Axiom balanceOfRevertSender: s tx,
  maxTxFee tx balanceOfAc s (sender tx)
   reserveBalUpdateOfTx tx = None
   balanceOfAc (revertTx s tx) (sender tx)
     = balanceOfAc s (sender tx) - maxTxFee tx.

Axiom balanceOfRevertOther: s tx ac,
  reserveBalUpdateOfTx tx = None
   ac (sender tx)
   balanceOfAc (revertTx s tx) ac
     = balanceOfAc s ac.

Axiom revertTxDelegationUpdCore: tx s,
  reserveBalUpdateOfTx tx = None
  let sf := (revertTx s tx) in
  ( ac, addrDelegated sf ac =
                (addrDelegated s ac && bool_decide (ac (undels tx.1.2)))
                || bool_decide (ac (dels tx.1.2))).

Axiom execTxDelegationUpdCore: tx s,
  reserveBalUpdateOfTx tx = None
  let sf := (evmExecTxCore s tx).1 in
  ( ac, addrDelegated sf ac =
                (addrDelegated s ac && bool_decide (ac (undels tx.1.2)))
                || bool_decide (ac (dels tx.1.2))).

Axiom execTxSenderBalCore: tx s,
  maxTxFee tx balanceOfAc s (sender tx)
  reserveBalUpdateOfTx tx = None
  let sf := (evmExecTxCore s tx).1 in
  addrDelegated s (sender tx) = false
    balanceOfAc sf (sender tx) = balanceOfAc s (sender tx) - ( maxTxFee tx + value tx)
         balanceOfAc sf (sender tx) = balanceOfAc s (sender tx) - (maxTxFee tx).

One caveat in the assumption below is that it assumes that the account ac does not receive so much credit that it overflows 2^256. In practice, this should never happen, assuming the ETH supply is well below 2^256. Thus, we can assume that evmExecTxCore caps the balance at 2^256 should it overflow, instead of wrapping around, which may violate this assumption.
Axiom execTxCannotDebitNonDelegatedNonContractAccountsCore: tx s,
  reserveBalUpdateOfTx tx = None
  let sf := (evmExecTxCore s tx).1 in
   ac, ac sender tx
               (addrDelegated sf ac || hasCode sf ac) = false
                  balanceOfAc s ac balanceOfAc sf ac.

Axiom changedAccountSetSound: tx s,
  reserveBalUpdateOfTx tx = None
  let (sf, changedAccounts) := (evmExecTxCore s tx) in
  ( ac, ac changedAccounts sf ac = s ac).

lemmas about execution

Unless there is a comment before a lemma, the lemma follows easily from the axioms above about evmExecTxCore and revertTx and by the definition of execTx

Lemma addrDelegatedUnchangedByBalUpd s f addr baladdr:
  addrDelegated (updateBalanceOfAc s baladdr f) addr = addrDelegated s addr.

Lemma execTxDelegationUpdCoreImpl tx s:
  reserveBalUpdateOfTx tx = None
  let sf := (evmExecTxCore s tx).1 in
  ( ac, addrDelegated sf ac addrDelegated s ac || bool_decide (ac (addrsDelUndelByTx tx))).

Lemma revertTxDelegationUpdCoreImpl tx s:
  reserveBalUpdateOfTx tx = None
  let sf := (revertTx s tx) in
  ( ac, addrDelegated sf ac addrDelegated s ac || bool_decide (ac (addrsDelUndelByTx tx))).

Lemma balanceOfUpd s ac f acp:
  balanceOfAc (updateBalanceOfAc s ac f) acp = if (bool_decide (ac=acp)) then f (balanceOfAc s ac) else (balanceOfAc s acp).

Lemma execTxOtherBalanceLB tx s:
  maxTxFee tx balanceOfAc s.1 (sender tx)
  let sf := (execValidatedTx s tx) in
  ( ac,
      let ReserveBal := configuredReserveBalOfAddr s.2 ac in
      (ac sender tx)
        if (hasCode sf.1 ac)
          then True
          else ReserveBal `min` (balanceOfAcA s ac) (balanceOfAcA sf ac)).

Lemma execTxSenderBal tx s:
  maxTxFee tx balanceOfAc s.1 (sender tx)
  let ReserveBal := configuredReserveBalOfAddr s.2 (sender tx) in
  let sf := (execValidatedTx s tx) in
  hasCode sf.1 (sender tx) = false
  (if isAllowedToEmpty s [] tx
   then balanceOfAcA sf (sender tx) = balanceOfAcA s (sender tx) - ( maxTxFee tx + value tx)
         balanceOfAcA sf (sender tx) = balanceOfAcA s (sender tx) - (maxTxFee tx)
  else ReserveBal `min` (balanceOfAcA s (sender tx)) - maxTxFee tx (balanceOfAcA sf (sender tx))).

Lemma execTxDelegationUpd tx s:
  let sf := (execValidatedTx s tx) in
  ( ac, addrDelegated (fst sf) ac addrDelegated (fst s) ac || bool_decide (ac (addrsDelUndelByTx tx))).

Lemma execTxCannotDebitNonDelegatedNonContractAccounts tx s:
  let sf := (execValidatedTx s tx) in
  ( ac, ac sender tx
               if (addrDelegated (fst sf) ac || hasCode (fst sf) ac)
                 then True
                 else balanceOfAcA s ac balanceOfAcA sf ac).

This lemma combines many of the execution lemmas above to build a lower bound of the balance of any account after executing a transaction.
Lemma execBalLb ac s tx:
  maxTxFee tx balanceOfAc s.1 (sender tx)
  let sf := (execValidatedTx s tx) in
  let ReserveBal := configuredReserveBalOfAddr s.2 ac in
  if (bool_decide (ac=sender tx)) then
    hasCode sf.1 (sender tx) = false
    (if isAllowedToEmpty s [] tx
     then balanceOfAcA sf (sender tx) = balanceOfAcA s (sender tx) - ( maxTxFee tx + value tx)
           balanceOfAcA sf (sender tx) = balanceOfAcA s (sender tx) - (maxTxFee tx)
     else ReserveBal `min` (balanceOfAcA s (sender tx)) - maxTxFee tx (balanceOfAcA sf (sender tx)))
  else
    if (hasCode sf.1 ac)
    then True
    else (if addrDelegated (fst sf) ac then ReserveBal `min` (balanceOfAcA s ac) else balanceOfAcA s ac)
          (balanceOfAcA sf ac).

Lemma execS2 s txlast:
  ((execValidatedTx s txlast)).2 = updateExtraState s.2 txlast.

Lemma lastTxInBlockIndexUpd s txlast:
  lastTxInBlockIndex ((((execValidatedTx s txlast)).2) (sender txlast))
  = Some (txBlockNum txlast).

Lemma otherTxLstSenderLkp s addr txlast :
  addr sender txlast
  
    lastTxInBlockIndex ((((execValidatedTx s txlast)).2) addr)
    = lastTxInBlockIndex (s.2 addr).

Lemma delgUndelgUpdTx txlast s addr:
  addr addrsDelUndelByTx txlast
   lastDelUndelInBlockIndex (((execValidatedTx s txlast)).2 addr) = Some (txBlockNum txlast).

Lemma otherDelUndelLkp s addr txlast :
  addr addrsDelUndelByTx txlast
  
    lastDelUndelInBlockIndex (((execValidatedTx s txlast)).2 addr)
    = lastDelUndelInBlockIndex (s.2 addr).

Lemma otherDelUndelDelegationStatusUnchanged s addr txlast :
  addr addrsDelUndelByTx txlast
  
    addrDelegated ((execValidatedTx s txlast)).1 addr
    = addrDelegated s.1 addr.


Lemma isAllowedToEmptyImpl s tx inter a:
  isAllowedToEmpty s (tx::inter) a = true
   sender tx sender a
      addrDelegated (execValidatedTx s tx).1 (sender a) = false.

Lemma emptyBalanceUb s tx inter a:
  hasCode (execValidatedTx s tx).1 (sender a) = false
   isAllowedToEmpty s (tx :: inter) a = true
   balanceOfAc s.1 (sender a) balanceOfAc (execValidatedTx s tx).1 (sender a).

Definition rbAfterTx s tx :=
  match reserveBalUpdateOfTx tx with
  | Some rbrb
  | NoneconfiguredReserveBalOfAddr s (sender tx)
  end.

Lemma configuredReserveBalOfAddrSpec s tx a:
  configuredReserveBalOfAddr (execValidatedTx s tx).2 a
  = if bool_decide (a=sender tx)
    then rbAfterTx s.2 tx
    else configuredReserveBalOfAddr s.2 a.

Lemma configuredReserveBalOfAddrSame s tx a:
  sender tx a
   (configuredReserveBalOfAddr (execValidatedTx s tx).2 a
      =
        configuredReserveBalOfAddr s.2 a).

Lemma configuredReserveBalOfAddrSame2 s tx inter a:
  isAllowedToEmpty s (tx :: inter) a = true
   (configuredReserveBalOfAddr (execValidatedTx s tx).2 (sender a)
      =
        configuredReserveBalOfAddr s.2 (sender a)).

Lemma hasCodeFalsePresExec l s tx:
  ( txext, txext (tx::l) txCannotCreateContractAtAddrs txext (map sender (tx::l)))
   ( ac, ac (map sender (tx::l)) hasCode s.1 ac = false)
   ( ac, ac (map sender (tx::l)) hasCode (execValidatedTx s tx).1 ac = false).

Lemma initResBal s addr:
  (initialEffReserveBals s) addr =
    balanceOfAcA s addr `min` configuredReserveBalOfAddr s.2 addr.

isAllowedToEmpty lemmas

Recall isAllowedToEmpty s (txInterfirst :: rest) txnext determines whether the transaction txnext is allowed to empty its balance, in the setting where s is the last available state and (txInterfirst :: rest) are the transactions between s and txnext.
This lemma proves that to be equivalent to executing txInterfirst at state s and considering the result as the latest available state and thereby removing txInterfirst from intermediates.

lemmas about remainingEffReserveBals

The next pair of lemma states that the “remaining effective reserve” function is monotone: if you start from a pointwise larger map, you end at a pointwise larger map. rbLe is defined right above.
In the proof of the main correctness theorem, we use a slightly different variant of monotoncity, where in the RHS of , remainingEffReserveBals starts from the final state after executing tx from s and as a result, tx is dropped from the list of intermediate transactions between the state and the candidate transaction txc. The proof follows from the definition of remainingEffReserveBals and the execution lemmas above.
Lemma mono2 tx txc extension s (eoas: list EvmAddr) rb1 rb2 inter:
  ( ac : EvmAddr,
      ac sender tx :: sender txc :: map sender extension
       hasCode (execValidatedTx s tx).1 ac = false)
   ( addr : EvmAddr, addr eoas rb1 addr rb2 addr)
   txBlockNum txc - K txBlockNum tx txBlockNum txc
   addr : EvmAddr,
      addr eoas
       remainingEffReserveBals s rb1 (tx :: inter) txc addr
           remainingEffReserveBals (execValidatedTx s tx) rb2 inter txc addr.

lifts mono from remainingEffReserveBals to remainingEffReserveBalsL: proof follows a straightforward induction on the list extension, using mono to fulfil the obligations of the induction hypothesis
Similarly, lifts mono2 from remainingEffReserveBals to remainingEffReserveBalsL: proof follows a straightforward induction on the list extension, using mono2 to fulfil the obligations of the induction hypothesis.
This lemma captures a key property of remainingEffReserveBals: it underapproximates the resultant effective balance after execution of the transaction
Lemma exec1 tx extension s :
  maxTxFee tx balanceOfAc s.1 (sender tx)
   let sf := (execValidatedTx s tx) in
     ( ac : EvmAddr, ac sender tx :: map sender extension hasCode sf.1 ac = false)
      ( addr : EvmAddr,
            addr sender tx :: map sender extension
             remainingEffReserveBals s (initialEffReserveBals s) [] tx addr
               initialEffReserveBals sf addr).

for any account that is a sender of one of the intermediate transactions (between s and the candidate transaction txc), remainingEffReserveBals is a non-increasing function.
lifts the previous lemma from remainingEffReserveBals to remainingEffReserveBals. induction on nextL
Finally, here is one of the key stepping stones to the main theorem, it says that the consensus checks guarantee that execution of the first transaction will pass validation during execution, i.e. the balance would be sufficient to cover maxTxFee.
This doesn't say anything about whether the execution of the later transactions (extension) will also pass the check. That is where the next lemma comes in handy.
you can pick the first tx in the proposed extension and the consensus checks would still hold on the resultant state for the remaining transactions in the proposal.
the above 2 lemmas can be combined to yield the following:

Proof of main theorem:

Straightforward induction on firstblock, with inductiveStep used in the inductive step.
All assumptions of the proof:
Section Variables:
K
: N
Axioms:
revertTxDelegationUpdCore :
  ∀ (tx : TxWithHdr) (s : StateOfAccounts),
    reserveBalUpdateOfTx tx = None
    → ∀ (sf := revertTx s tx) (ac : EvmAddr),
        addrDelegated sf ac =
        addrDelegated s ac && bool_decide (acundels tx.1.2) || bool_decide (acdels tx.1.2)
revertTx : StateOfAccountsTxWithHdrStateOfAccounts
execTxSenderBalCore :
  ∀ (tx : TxWithHdr) (s : StateOfAccounts),
    (maxTxFee txbalanceOfAc s (sender tx))%N
    → reserveBalUpdateOfTx tx = None
      → let sf := (evmExecTxCore s tx).1 in
        addrDelegated s (sender tx) = false
        → balanceOfAc sf (sender tx) = (balanceOfAc s (sender tx) - (maxTxFee tx + value tx))%N
          ∨ balanceOfAc sf (sender tx) = (balanceOfAc s (sender tx) - maxTxFee tx)%N
execTxDelegationUpdCore :
  ∀ (tx : TxWithHdr) (s : StateOfAccounts),
    reserveBalUpdateOfTx tx = None
    → ∀ (sf := (evmExecTxCore s tx).1) (ac : EvmAddr),
        addrDelegated sf ac =
        addrDelegated s ac && bool_decide (acundels tx.1.2) || bool_decide (acdels tx.1.2)
execTxCannotDebitNonDelegatedNonContractAccountsCore :
  ∀ (tx : TxWithHdr) (s : StateOfAccounts),
    reserveBalUpdateOfTx tx = None
    → ∀ (sf := (evmExecTxCore s tx).1) (ac : EvmAddr),
        acsender tx
        → addrDelegated sf ac || hasCode sf ac = false
          → (balanceOfAc s acbalanceOfAc sf ac)%N
evmExecTxCore : StateOfAccountsTxWithHdrStateOfAccounts × list EvmAddr
changedAccountSetSound :
  ∀ (tx : TxWithHdr) (s : StateOfAccounts),
    reserveBalUpdateOfTx tx = None
    → let (sf, changedAccounts) := evmExecTxCore s tx in
      ∀ ac : EvmAddr, acchangedAccountssf ac = s ac
balanceOfRevertSender :
  ∀ (s : StateOfAccounts) (tx : TxWithHdr),
    (maxTxFee txbalanceOfAc s (sender tx))%N
    → reserveBalUpdateOfTx tx = None
      → balanceOfAc (revertTx s tx) (sender tx) = (balanceOfAc s (sender tx) - maxTxFee tx)%N
balanceOfRevertOther :
  ∀ (s : StateOfAccounts) (tx : TxWithHdr) (ac : EvmAddr),
    reserveBalUpdateOfTx tx = None
    → acsender txbalanceOfAc (revertTx s tx) ac = balanceOfAc s ac

This page has been generated by coqdoc