Coq model of reserve balance and proof of safety
- *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.
Whether an address is currently delegated in the core state.
Definition addrDelegated (s: StateOfAccounts) (a : EvmAddr) : bool :=
match delegatedTo (s a) with
| [] ⇒ false
| _::_ ⇒ true
end.
Record EIP7702Authority := {
del_from: option EvmAddr;
match delegatedTo (s a) with
| [] ⇒ false
| _::_ ⇒ true
end.
Record EIP7702Authority := {
del_from: option EvmAddr;
^ None means the signature was invalid
^ address 0 means undelegate
}.
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. We extend the extra fields with a summary of reserve-balance configuration updates (if any) that occurred during execution; these updates are applied with a delayed effect after K blocks, so consensus does not need to inspect transactions to determine current reserve balances.
The fields above should ultimately come from EVM semantics. The fields below are monad-specific.
^ index of this transaction in the block. monaddb stores when (blockindex, txindex) an account was born
Reserve-balance updates are no longer summarized in TxExtra; they are
emitted by the EVM core during execution and applied with a delayed effect.
}.
for any decidable assertion/proposition P, asbool P is a boolean such that (asbool P = true) ↔ P, where
↔ means iff (if and only if)
Notation asbool := bool_decide.
Definition is_undelegation (a: EIP7702Authority) : bool :=
asbool (del_to a = 0).
Definition is_undelegation (a: EIP7702Authority) : bool :=
asbool (del_to a = 0).
list of accounts requested to be delegated in transaction txe
Definition dels (txe: TxExtra) : list EvmAddr :=
flat_map (fun a ⇒ match del_from a with
| Some addr ⇒ if is_undelegation a then [] else [addr]
| None ⇒ []
end ) (authorities txe).
flat_map (fun a ⇒ match del_from a with
| Some addr ⇒ if is_undelegation a then [] else [addr]
| None ⇒ []
end ) (authorities txe).
list of accounts requested to be undelegated in transaction txe
Definition undels (txe: TxExtra) : list EvmAddr :=
flat_map (fun a ⇒ match del_from a with
| Some addr ⇒ if is_undelegation a then [addr] else []
| None ⇒ []
end ) (authorities txe).
#[global] Opaque dels undels.
Definition Transaction: Type := block.transaction × TxExtra.
flat_map (fun a ⇒ match del_from a with
| Some addr ⇒ if is_undelegation a then [addr] else []
| None ⇒ []
end ) (authorities txe).
#[global] Opaque dels undels.
Definition Transaction: Type := block.transaction × TxExtra.
The type A × B in Coq can be thought of as std::pair<A,B>. The projections .1 and .2 can be used to obtain the first and second components of the pair, respectively.
Our **fee upper bound** is intentionally pessimistic: the consensus rule
reasons about gas_limit × gas_price, not about *actual* gas used, which can be hard to efficiently estimate.
This excludes storage fees, which are are separately handled below because storage fees are only accounted by consensus for transactions that are allowed to empty.
The proofs in this file never unfold the definition of maxTxFee, so nothing will break if this definition is changed.
Definition maxTxFee (t: TxWithHdr) : N :=
((w256_to_N (block.tr_gas_price t.1.1)) × (w256_to_N (block.tr_gas_limit t.1.1))).
((w256_to_N (block.tr_gas_price t.1.1)) × (w256_to_N (block.tr_gas_limit t.1.1))).
max storage fee. the exact definition does not matter for our proofs, so we keep it undefined, so our proofs would have to work for all possible definitions. one can choose the body as 0 to effectively disable storage fees
Parameterization by the lookahead window K:
Consensus can be ahead of execution by at most K. The n+Kth block must have the state root hash after executing block n. The next two variables are parameters for the rest of the proofs.
Next, we have some simple wrappers for brevity.
sender of a transaction:
balance of an account in a given state:
update the value of the key updKey in oldMap to f (oldMap updKey) (f applied to the old value of the key updKey in the map)
Definition updateKey {T} `{c: EqDecision T} {V} (oldmap: T → V) (updKey: T) (f: V → V) : T → V :=
fun k ⇒ if (asbool (k=updKey)) then f (oldmap updKey) else oldmap k.
fun k ⇒ if (asbool (k=updKey)) then f (oldmap updKey) else oldmap k.
update balance of the account addr such that the new value is upd applied to the old value
Definition updateBalanceOfAc (s: StateOfAccounts) (addr: EvmAddr) (upd: N → N) : StateOfAccounts :=
updateKey s addr (fun old ⇒ old &: _balance %= upd).
updateKey s addr (fun old ⇒ old &: _balance %= upd).
value transfer field of a of a transaction
addresses delegated or undelegated by tx
block number of a transaction
A transaction may emit multiple reserve-balance updates (e.g., via repeated
precompile calls). Each entry targets a specific address.
To implement reserve balance checks, execution needs to maintain some extra state (beyond the core EVM state) for each account:
^ 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
^ last block index where this address was delegated or undelegated. In the implementation, we can just track the last 2K range.
^ last *settled* reserve balance value for this account (defaults to DefaultReserveBal).
^ pending reserve balance update (if any), as (value, block).
}.
#[only(lens)] derive ExtraAcState.
Definition ExtraAcStates := (EvmAddr → ExtraAcState).
Definition settledReserveBalOfAddr (s: ExtraAcStates) (addr : EvmAddr) : N := settledReserveBal (s addr).
Definition pendingReserveBalOfAddr (s: ExtraAcStates) (addr : EvmAddr) : option (N × N) := pendingReserveBal (s addr).
Definition pendingReserveBalBlockOfAddr (s: ExtraAcStates) (addr : EvmAddr) : option N :=
option_map snd (pendingReserveBal (s addr)).
#[only(lens)] derive ExtraAcState.
Definition ExtraAcStates := (EvmAddr → ExtraAcState).
Definition settledReserveBalOfAddr (s: ExtraAcStates) (addr : EvmAddr) : N := settledReserveBal (s addr).
Definition pendingReserveBalOfAddr (s: ExtraAcStates) (addr : EvmAddr) : option (N × N) := pendingReserveBal (s addr).
Definition pendingReserveBalBlockOfAddr (s: ExtraAcStates) (addr : EvmAddr) : option N :=
option_map snd (pendingReserveBal (s addr)).
The settled (non-delayed) configured reserve balance value.
Definition configuredReserveBalOfAddr (s: ExtraAcStates) (addr : EvmAddr) : N :=
settledReserveBalOfAddr s addr.
settledReserveBalOfAddr s addr.
Our modified execution function which does reserve balance checks will use the following type as input/output.
balance of an account in a given augmented state
just an abstract helper used in the next two definitions, which choose different instantiations for the proj argument
Definition indexWithinK (proj: ExtraAcState → option N) (state : ExtraAcStates) (tx: TxWithHdr) : bool :=
let startIndex := txBlockNum tx - (K-1) in
match proj (state (sender tx)) with
| Some index ⇒
asbool (startIndex ≤ index ≤ txBlockNum tx)
| None ⇒ false
end.
let startIndex := txBlockNum tx - (K-1) in
match proj (state (sender tx)) with
| Some index ⇒
asbool (startIndex ≤ index ≤ txBlockNum tx)
| None ⇒ false
end.
Definition existsTxWithinK (state : AugmentedState) (tx: TxWithHdr) : bool :=
indexWithinK lastTxInBlockIndex (state.2) tx.
indexWithinK lastTxInBlockIndex (state.2) 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 (state.2) tx.
Axiom keccak256_program : evm.program → N.
Definition code_hash_of_program
(pr: monad.EVMOpSem.evm.program)
: Corelib.Numbers.BinNums.N :=
keccak256_program pr.
Definition delegation_indicator_size : Z := 23%Z.
Parameter delegation_marker_prefix : evm.program → bool.
Definition isDelegationMarker (p: evm.program) : bool :=
(asbool (monad.EVMOpSem.evm.program_length p = delegation_indicator_size))
&& delegation_marker_prefix p.
indexWithinK lastDelUndelInBlockIndex (state.2) tx.
Axiom keccak256_program : evm.program → N.
Definition code_hash_of_program
(pr: monad.EVMOpSem.evm.program)
: Corelib.Numbers.BinNums.N :=
keccak256_program pr.
Definition delegation_indicator_size : Z := 23%Z.
Parameter delegation_marker_prefix : evm.program → bool.
Definition isDelegationMarker (p: evm.program) : bool :=
(asbool (monad.EVMOpSem.evm.program_length p = delegation_indicator_size))
&& delegation_marker_prefix p.
is an account a smart contract?
Definition isAcSC (os: option AccountM) : bool:=
match os with
| Some s ⇒
(asbool (code_hash_of_program (block.block_account_code (coreAc s)) ≠ 0%N)) && (negb (isDelegationMarker (block.block_account_code (coreAc s))))
| None ⇒ false
end.
match os with
| Some s ⇒
(asbool (code_hash_of_program (block.block_account_code (coreAc s)) ≠ 0%N)) && (negb (isDelegationMarker (block.block_account_code (coreAc s))))
| None ⇒ false
end.
in state s, is the account at address addr a smart contract?
Consensus Check (algo 1)
Below, we build up the definition of the consensus check consensusAcceptableTxs The “emptying” gate:- it is *not* currently delegated,
- it had no delegation change within the last K blocks (last K-1 blocks and the prev transactions in the current block),
- it did not send a transaction in the last K blocks.
Definition isAllowedToEmpty
(preIntermediatesState : AugmentedState) (intermediates: list TxWithHdr) (candidateTx: TxWithHdr) : bool :=
let consideredDelegated :=
(addrDelegated (preIntermediatesState.1) (sender candidateTx))
|| existsDelUndelTxWithinK preIntermediatesState candidateTx
|| asbool ((sender candidateTx) ∈ flat_map addrsDelUndelByTx (candidateTx::intermediates)) in
let existsSameSenderTxInWindow :=
(existsTxWithinK preIntermediatesState candidateTx)
|| asbool ((sender candidateTx) ∈ map sender intermediates) in
(negb consideredDelegated) && (negb existsSameSenderTxInWindow).
(preIntermediatesState : AugmentedState) (intermediates: list TxWithHdr) (candidateTx: TxWithHdr) : bool :=
let consideredDelegated :=
(addrDelegated (preIntermediatesState.1) (sender candidateTx))
|| existsDelUndelTxWithinK preIntermediatesState candidateTx
|| asbool ((sender candidateTx) ∈ flat_map addrsDelUndelByTx (candidateTx::intermediates)) in
let existsSameSenderTxInWindow :=
(existsTxWithinK preIntermediatesState candidateTx)
|| asbool ((sender candidateTx) ∈ map sender intermediates) in
(negb consideredDelegated) && (negb existsSameSenderTxInWindow).
Whether this is the first transaction from the sender within the last K
blocks and without delegation/undelegation activity in that window. This is
a slightly *weaker* condition than isAllowedToEmpty because it ignores
delegation/undelegation activity that may appear in candidateTx itself.
Hence isAllowedToEmpty implies senderNoRecentActivity, but not conversely
(e.g., a tx that contains a self-authorization can make isAllowedToEmpty
false while senderNoRecentActivity remains true). A precise characterization
of this gap is given later in Lemma isAllowedToEmpty_false_senderNoRecentActivity.
Definition senderNoRecentActivity
(preIntermediatesState : AugmentedState) (intermediates: list TxWithHdr) (candidateTx: TxWithHdr) : bool :=
let senderAddr := sender candidateTx in
let existsSameSenderTxInWindow :=
(existsTxWithinK preIntermediatesState candidateTx)
|| asbool (senderAddr ∈ map sender intermediates) in
let senderTouchedByDelUndel :=
asbool (senderAddr ∈ flat_map addrsDelUndelByTx intermediates) in
let consideredDelegated :=
(addrDelegated preIntermediatesState.1 senderAddr)
|| existsDelUndelTxWithinK preIntermediatesState candidateTx
|| senderTouchedByDelUndel in
(negb existsSameSenderTxInWindow) && (negb consideredDelegated).
(preIntermediatesState : AugmentedState) (intermediates: list TxWithHdr) (candidateTx: TxWithHdr) : bool :=
let senderAddr := sender candidateTx in
let existsSameSenderTxInWindow :=
(existsTxWithinK preIntermediatesState candidateTx)
|| asbool (senderAddr ∈ map sender intermediates) in
let senderTouchedByDelUndel :=
asbool (senderAddr ∈ flat_map addrsDelUndelByTx intermediates) in
let consideredDelegated :=
(addrDelegated preIntermediatesState.1 senderAddr)
|| existsDelUndelTxWithinK preIntermediatesState candidateTx
|| senderTouchedByDelUndel in
(negb existsSameSenderTxInWindow) && (negb consideredDelegated).
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:
Notice the type is Z: negative entries encode an *over-consumption* that
should make the proposal unacceptable.
Z is the type of mathematical numbers in Coq: unbounded, exact arithmetic (no over/under flows). At the end of this file, we implement in Coq the algorithm using just U256, while still avoiding over/underflows and thus prove equivalence
- It is initialized with min(balance, userConfiguredReserve).
- 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.
delayed user reserve balance: latest update whose block index plus K is ≤ n.
Definition delayedReserveBal (es: ExtraAcState) (n: N) : N :=
match pendingReserveBal es with
| Some (v, blk) ⇒
if asbool (blk + K ≤ n)
then v
else settledReserveBal es
| None ⇒ settledReserveBal es
end.
match pendingReserveBal es with
| Some (v, blk) ⇒
if asbool (blk + K ≤ n)
then v
else settledReserveBal es
| None ⇒ settledReserveBal es
end.
delayed user reserve balance at address addr in the state map.
Definition delayedReserveBalOfAddr (s: ExtraAcStates) (addr : EvmAddr) (n: N) : N :=
delayedReserveBal (s addr) n.
delayedReserveBal (s addr) n.
initial effective reserve balances, before any proposed tx.
For delayed URB, the initial value is a placeholder; the first time a sender
appears, we recompute using the block number of that tx (via the
senderNoRecentActivity and advanceEffReserveBals steps). For senders that
never appear in the suffix, this placeholder is irrelevant.
Definition initialEffReserveBals (s: AugmentedState) : EffReserveBals :=
fun addr ⇒ (balanceOfAc s.1 addr `min` delayedReserveBalOfAddr s.2 addr 0).
fun addr ⇒ (balanceOfAc s.1 addr `min` delayedReserveBalOfAddr s.2 addr 0).
Advance the effective-reserve map to a block b by clamping each entry
to the delayed reserve balance effective at b.
Definition advanceEffReserveBals (s: AugmentedState) (erbs: EffReserveBals) (b: N) : EffReserveBals :=
fun addr ⇒ (erbs addr) `min` delayedReserveBalOfAddr s.2 addr b.
fun addr ⇒ (erbs addr) `min` delayedReserveBalOfAddr s.2 addr b.
Consensus’ decrement step:
The next defn is the algebraic heart of consensus check algorithm:
fold this function left-to-right over the entire sequence of proposed txs, and you get the remaining
worst-case protected reserve for every sender.
Formally, this function conservatively estimates the remaining effective reserve balance of the sender of candidateTx after executing candidateTx, assuming prevErb is the estimate of the reserve balance of the sender of candidateTx just before executing candidateTx. preIntermediatesState is the latest fully-executed state and intermediates is a list of all the transactions between preIntermediatesState and candidateTx.
In the definition of newBal (let binding), the subtraction is capped below at 0: the result is non-negative.
So, if sbal < maxTxFee candidateTx + value candidateTx but maxTxFee next ≤ sbal, this transaction (candidateTx) will be accepted but all
subsequent ones (with maxTxFee > 0) from the same sender will be rejected as the remaining effective reserve balance becomes 0.
This function represents the check that consensus needs to do when adding candidateTx at the end of the already built/proposed valid sequence of txs intermediates. candidateTx will be accepted iff the returned value is non-negative.
The intermediates list is used only for the isAllowedToEmpty check and to
detect whether the sender already appeared in the last K blocks. So the Rust
implementation can optimize it by instead just maintaining the necessary
summaries (the set of senders and the set of addrs that are delegated or
undelegated in those txs).
Definition remainingEffReserveBalOfSender (preIntermediatesState : AugmentedState) (prevErb: Z) (intermediates: list TxWithHdr) (candidateTx: TxWithHdr)
: Z :=
let s := preIntermediatesState in
let senderAddr := sender candidateTx in
let baseErb :=
if senderNoRecentActivity s intermediates candidateTx
then (balanceOfAc s.1 senderAddr `min`
delayedReserveBalOfAddr s.2 senderAddr (txBlockNum candidateTx))
else (prevErb `min`
delayedReserveBalOfAddr s.2 senderAddr (txBlockNum candidateTx)) in
if isAllowedToEmpty s intermediates candidateTx
then
let sbal := balanceOfAc s.1 senderAddr in
let newBal:= (sbal - maxTxFee candidateTx - value candidateTx - maxStorageFee candidateTx) `max` 0 in
if asbool (maxTxFee candidateTx ≤ sbal)
then newBal `min` (delayedReserveBalOfAddr s.2 senderAddr (txBlockNum candidateTx))
else -1
else (baseErb - maxTxFee candidateTx).
The file also contains a finite-width (U256) variant of the consensus checks;
see Section U256Consensus near the end.
Next, we just "fold" the above function over the entire sequence of proposed transactions, starting from the first one after the fully executed state. Note how in the recursive call, we add the processed head to the list of intermediate transactions.
As with the function above, postStateAccountedSuffix is only used to track
whether the sender already appeared in the suffix and for the isAllowedToEmpty
check. An optimized implementation can maintain just the necessary summaries
instead of the full list.
Fixpoint remainingEffReserveBalsL (latestState : AugmentedState) (preRestERBs: EffReserveBals) (postStateAccountedSuffix rest: list TxWithHdr)
: EffReserveBals:=
match rest with
| [] ⇒ preRestERBs
| hrest::tlrest ⇒
let preRestERBs' := advanceEffReserveBals latestState preRestERBs (txBlockNum hrest) in
let rem: Z :=
remainingEffReserveBalOfSender latestState (preRestERBs' (sender hrest)) postStateAccountedSuffix hrest in
let erbs: EffReserveBals := updateKey preRestERBs' (sender hrest) (fun _ ⇒ rem) in
remainingEffReserveBalsL latestState erbs (postStateAccountedSuffix++[hrest]) tlrest
end.
: EffReserveBals:=
match rest with
| [] ⇒ preRestERBs
| hrest::tlrest ⇒
let preRestERBs' := advanceEffReserveBals latestState preRestERBs (txBlockNum hrest) in
let rem: Z :=
remainingEffReserveBalOfSender latestState (preRestERBs' (sender hrest)) postStateAccountedSuffix hrest in
let erbs: EffReserveBals := updateKey preRestERBs' (sender hrest) (fun _ ⇒ rem) in
remainingEffReserveBalsL latestState erbs (postStateAccountedSuffix++[hrest]) tlrest
end.
Algorithm 1 (consensus): acceptability of a suffix
Definition consensusAcceptableTxs (latestState : AugmentedState) (postStateProposedTxs: list TxWithHdr) : Prop :=
∀ addr, addr ∈ map sender postStateProposedTxs →
0≤ (remainingEffReserveBalsL latestState (initialEffReserveBals latestState) [] postStateProposedTxs) addr.
∀ addr, addr ∈ map sender postStateProposedTxs →
0≤ (remainingEffReserveBalsL latestState (initialEffReserveBals latestState) [] postStateProposedTxs) addr.
Execution Check (algo 2)
Definition isAllowedToEmptyExec
(state : AugmentedState) (tx: TxWithHdr) : bool :=
isAllowedToEmpty state [] tx.
updateExtraState is the execution-time maintenance of the tiny history we
need for emptiness checks and delayed reserve-config changes. It applies all
reserve updates emitted during execution (possibly for multiple addresses)
only on successful execution; updateExtraStateNoReserve is used on revert,
and only promotes already-pending updates (it discards any new reserve
updates from the reverted tx).
Definition promotePendingReserveBal (es: ExtraAcState) (n: N) : ExtraAcState :=
match pendingReserveBal es with
| Some (v, blk) ⇒
if asbool (blk + K ≤ n)%N
then {|
lastTxInBlockIndex := lastTxInBlockIndex es;
lastDelUndelInBlockIndex := lastDelUndelInBlockIndex es;
settledReserveBal := v;
pendingReserveBal := None;
|}
else es
| None ⇒ es
end.
Definition applyReserveBalUpdate (es: ExtraAcState) (n: N) (newRb: N) : ExtraAcState :=
match pendingReserveBal es with
| Some (_v, blk) ⇒
match pendingReserveBal es with
| Some (v, blk) ⇒
if asbool (blk + K ≤ n)%N
then {|
lastTxInBlockIndex := lastTxInBlockIndex es;
lastDelUndelInBlockIndex := lastDelUndelInBlockIndex es;
settledReserveBal := v;
pendingReserveBal := None;
|}
else es
| None ⇒ es
end.
Definition applyReserveBalUpdate (es: ExtraAcState) (n: N) (newRb: N) : ExtraAcState :=
match pendingReserveBal es with
| Some (_v, blk) ⇒
if multiple updates are applied in a single block, the latest wins
if asbool (blk = n)%N
then {|
lastTxInBlockIndex := lastTxInBlockIndex es;
lastDelUndelInBlockIndex := lastDelUndelInBlockIndex es;
settledReserveBal := settledReserveBal es;
pendingReserveBal := Some (newRb, n)
|}
else es
| None ⇒
{|
lastTxInBlockIndex := lastTxInBlockIndex es;
lastDelUndelInBlockIndex := lastDelUndelInBlockIndex es;
settledReserveBal := settledReserveBal es;
pendingReserveBal := Some (newRb, n)
|}
end.
Definition applyReserveBalUpdatesForAddr (es: ExtraAcState) (n: N) (addr: EvmAddr)
(updates: ReserveBalUpdate) : ExtraAcState :=
fold_left (fun st '(a, newRb) ⇒
if asbool (a = addr) then applyReserveBalUpdate st n newRb else st)
updates es.
Definition updateExtraStateBase (a: ExtraAcStates) (tx: TxWithHdr) : ExtraAcStates :=
(fun addr ⇒
let oldes := a addr in
{|
lastTxInBlockIndex :=
if asbool (sender tx = addr)
then Some (txBlockNum tx)
else lastTxInBlockIndex oldes;
lastDelUndelInBlockIndex :=
if asbool (addr ∈ addrsDelUndelByTx tx)
then Some (txBlockNum tx)
else lastDelUndelInBlockIndex oldes;
settledReserveBal := settledReserveBal oldes;
pendingReserveBal := pendingReserveBal oldes
|}
).
Definition updateExtraState (a: ExtraAcStates) (tx: TxWithHdr) (rbUpdate: ReserveBalUpdate) : ExtraAcStates :=
(fun addr ⇒
let oldes := (updateExtraStateBase a tx) addr in
let oldes' := promotePendingReserveBal oldes (txBlockNum tx) in
applyReserveBalUpdatesForAddr oldes' (txBlockNum tx) addr rbUpdate
).
Definition updateExtraStateNoReserve (a: ExtraAcStates) (tx: TxWithHdr) : ExtraAcStates :=
(fun addr ⇒
let oldes := updateExtraStateBase a tx addr in
promotePendingReserveBal oldes (txBlockNum tx)).
Section EvmCore.
then {|
lastTxInBlockIndex := lastTxInBlockIndex es;
lastDelUndelInBlockIndex := lastDelUndelInBlockIndex es;
settledReserveBal := settledReserveBal es;
pendingReserveBal := Some (newRb, n)
|}
else es
| None ⇒
{|
lastTxInBlockIndex := lastTxInBlockIndex es;
lastDelUndelInBlockIndex := lastDelUndelInBlockIndex es;
settledReserveBal := settledReserveBal es;
pendingReserveBal := Some (newRb, n)
|}
end.
Definition applyReserveBalUpdatesForAddr (es: ExtraAcState) (n: N) (addr: EvmAddr)
(updates: ReserveBalUpdate) : ExtraAcState :=
fold_left (fun st '(a, newRb) ⇒
if asbool (a = addr) then applyReserveBalUpdate st n newRb else st)
updates es.
Definition updateExtraStateBase (a: ExtraAcStates) (tx: TxWithHdr) : ExtraAcStates :=
(fun addr ⇒
let oldes := a addr in
{|
lastTxInBlockIndex :=
if asbool (sender tx = addr)
then Some (txBlockNum tx)
else lastTxInBlockIndex oldes;
lastDelUndelInBlockIndex :=
if asbool (addr ∈ addrsDelUndelByTx tx)
then Some (txBlockNum tx)
else lastDelUndelInBlockIndex oldes;
settledReserveBal := settledReserveBal oldes;
pendingReserveBal := pendingReserveBal oldes
|}
).
Definition updateExtraState (a: ExtraAcStates) (tx: TxWithHdr) (rbUpdate: ReserveBalUpdate) : ExtraAcStates :=
(fun addr ⇒
let oldes := (updateExtraStateBase a tx) addr in
let oldes' := promotePendingReserveBal oldes (txBlockNum tx) in
applyReserveBalUpdatesForAddr oldes' (txBlockNum tx) addr rbUpdate
).
Definition updateExtraStateNoReserve (a: ExtraAcStates) (tx: TxWithHdr) : ExtraAcStates :=
(fun addr ⇒
let oldes := updateExtraStateBase a tx addr in
promotePendingReserveBal oldes (txBlockNum tx)).
Section EvmCore.
Abstract execution and revert
Variable evmExecTxCore :
StateOfAccounts → TxWithHdr →
((StateOfAccounts × TxResult) × (list EvmAddr)) × ReserveBalUpdate.
Variable revertTx : StateOfAccounts → TxWithHdr → (StateOfAccounts × TxResult).
Definition revertTxState (s: StateOfAccounts) (t: TxWithHdr) : StateOfAccounts :=
(revertTx s t).1.
Definition evmExecTxCoreState (s: StateOfAccounts) (t: TxWithHdr) : StateOfAccounts :=
(evmExecTxCore s t).1.1.1.
Algorithm 2 (execution): execute a transaction
- Run the core EVM step to obtain the *actual* post state.
- For *changed* accounts, allFinalBalSufficient checks that the account was not debited too much, which may endanger the fee solvency of the later transactions already accepted by consensus.
- If any check fails, revert the tx *and still* update the extra history (so K-window bookkeeping remains accurate). Reserve-balance updates are applied only on success.
Definition finalBalSufficient (preTxState: AugmentedState) (postTxState : StateOfAccounts)
(t: TxWithHdr) (a: EvmAddr): bool :=
let ReserveBal := delayedReserveBalOfAddr preTxState.2 a (txBlockNum t) in
let erb:N := ReserveBal `min` (balanceOfAc preTxState.1 a) in
if isSC postTxState a
then true
else
if asbool (sender t =a)
then if isAllowedToEmptyExec preTxState t then true else asbool ((erb - maxTxFee t) ≤ balanceOfAc postTxState a)
else asbool (erb ≤ balanceOfAc postTxState a).
Definition allFinalBalSufficient (preTxState: AugmentedState) (postTxState : StateOfAccounts)
(changedAccounts: list EvmAddr) (t: TxWithHdr): bool:=
forallb (finalBalSufficient preTxState postTxState t) changedAccounts.
Execute a validated tx and return the post-state plus the EVM receipt.
Definition execValidatedTx (s: AugmentedState) (t: TxWithHdr)
: AugmentedState × TxResult :=
let '(((postTxState, txr), changedAccounts), rbUpdate) := evmExecTxCore (s.1) t in
if (allFinalBalSufficient s postTxState changedAccounts t)
then ((postTxState, updateExtraState s.2 t rbUpdate), txr)
else
let (revState, revRes) := revertTx s.1 t in
((revState, updateExtraStateNoReserve s.2 t), revRes).
: AugmentedState × TxResult :=
let '(((postTxState, txr), changedAccounts), rbUpdate) := evmExecTxCore (s.1) t in
if (allFinalBalSufficient s postTxState changedAccounts t)
then ((postTxState, updateExtraState s.2 t rbUpdate), txr)
else
let (revState, revRes) := revertTx s.1 t in
((revState, updateExtraStateNoReserve s.2 t), revRes).
Note that because the isSC 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.
Execution check of tx validity:
The implementation checks nonces as well, but here we are only concerned with tx fees.
- Before the tx, Alice sends money to some address addr2 (computed using keccak). Alice is EOA.
- Alice sends tx foo to a smart contract address addr.
- foo execution deploys code at addr2 and calls it, and the call empties addr2.
Definition validateTx (preTxState: StateOfAccounts) (tx: TxWithHdr): bool :=
asbool (maxTxFee tx ≤ balanceOfAc preTxState (sender tx)).
asbool (maxTxFee tx ≤ balanceOfAc preTxState (sender tx)).
Top-level execution wrapper that fails fast when validation fails.
On success, it returns the post-state and receipt for t.
None means the execution of the whole block containing t aborts, which is what the consensus/execution checks must guarantee to never happen.
Definition execTx (s: AugmentedState) (t: TxWithHdr): option (AugmentedState × TxResult) :=
if (negb (validateTx (s.1) t))
then None
else Some (execValidatedTx s t).
if (negb (validateTx (s.1) t))
then None
else Some (execValidatedTx s t).
execute a list of transactions one by one, returning the final state and receipts. Note that if 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 × list TxResult) :=
match ts with
| [] ⇒ Some (s, [])
| t::tls ⇒
match execTx s t with
| None ⇒ None
| Some (si, r) ⇒
match execTxs si tls with
| Some (sf, rs) ⇒ Some (sf, r :: rs)
| None ⇒ None
end
end
end.
match ts with
| [] ⇒ Some (s, [])
| t::tls ⇒
match execTx s t with
| None ⇒ None
| Some (si, r) ⇒
match execTxs si tls with
| Some (sf, rs) ⇒ Some (sf, r :: rs)
| None ⇒ None
end
end
end.
Our correctness property only holds when the set of proposed transactions is 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-1) ≤ txBlockNum htx ≤ txBlockNum txext)
∧ blockNumsInRange ttx
end.
Definition txCannotCreateContractAtAddrs tx (eoasWithPrivateKey: list EvmAddr) :=
∀ s, let sf := (execValidatedTx s tx).1 in
∀ addr, addr ∈ eoasWithPrivateKey → isSC s.1 addr = false → isSC sf.1 addr = false.
match ltx with
| [] ⇒ True
| htx::ttx ⇒
(∀ txext, txext ∈ ttx → txBlockNum txext - (K-1) ≤ txBlockNum htx ≤ txBlockNum txext)
∧ blockNumsInRange ttx
end.
Definition txCannotCreateContractAtAddrs tx (eoasWithPrivateKey: list EvmAddr) :=
∀ s, let sf := (execValidatedTx s tx).1 in
∀ addr, addr ∈ eoasWithPrivateKey → isSC s.1 addr = false → isSC 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.
Theorem fullBlockStep2 (latestState : AugmentedState) (blocks: list TxWithHdr) :
(∀ ac, ac ∈ (map sender blocks) → isSC latestState.1 ac = false)
→ (∀ txext, txext ∈ blocks → txCannotCreateContractAtAddrs txext (map sender blocks))
→ blockNumsInRange blocks
→ consensusAcceptableTxs latestState blocks
→ match execTxs latestState blocks with
| None ⇒ False
| Some (si, _) ⇒ True
end.
(∀ ac, ac ∈ (map sender blocks) → isSC latestState.1 ac = false)
→ (∀ txext, txext ∈ blocks → txCannotCreateContractAtAddrs txext (map sender blocks))
→ blockNumsInRange blocks
→ consensusAcceptableTxs latestState blocks
→ match execTxs latestState blocks with
| None ⇒ False
| Some (si, _) ⇒ True
end.
main correctness theorem
We will prove the above correctness theorem below, but the actual correctness theorem we need is slightly stronger. Suppose we split blocks in the theorem above into firstblock and restblocks such that blocks=firstblock++blocksrest and suppose these blocks together are all transactions from the K proposed blocks since the last consensus 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:
Theorem fullBlockStep (latestState : AugmentedState) (firstblock: list TxWithHdr) (restblocks: list TxWithHdr) :
blockNumsInRange (firstblock++restblocks)
→ consensusAcceptableTxs latestState (firstblock++restblocks)
→ (∀ txext, txext ∈ (firstblock++restblocks) → txCannotCreateContractAtAddrs txext (map sender (firstblock++restblocks)))
→ (∀ ac, ac ∈ (map sender (firstblock++restblocks)) → isSC latestState.1 ac = false)
→ match execTxs latestState firstblock with
| None ⇒ False
blockNumsInRange (firstblock++restblocks)
→ consensusAcceptableTxs latestState (firstblock++restblocks)
→ (∀ txext, txext ∈ (firstblock++restblocks) → txCannotCreateContractAtAddrs txext (map sender (firstblock++restblocks)))
→ (∀ ac, ac ∈ (map sender (firstblock++restblocks)) → isSC latestState.1 ac = false)
→ match execTxs latestState firstblock with
| None ⇒ False
^ execution cannot abort (indicated by returning None) due to balance being insufficient to even pay fees
in this case, we have enough conditions to guarantee fee-solvency of restblocks, so that it can be extended and then this lemma reapplied:
consensusAcceptableTxs si restblocks
∧ blockNumsInRange restblocks
∧ (∀ ac, ac ∈ (map sender restblocks) → isSC si.1 ac = false)
∧ (∀ txext, txext ∈ (restblocks) → txCannotCreateContractAtAddrs txext (map sender (restblocks)))
end.
∧ blockNumsInRange restblocks
∧ (∀ ac, ac ∈ (map sender restblocks) → isSC si.1 ac = false)
∧ (∀ txext, txext ∈ (restblocks) → txCannotCreateContractAtAddrs txext (map sender (restblocks)))
end.
core execution assumptions
To prove the theorem fullBlockStep, we needed to make the following assumptions about how the core EVM execution updates balances and delegated-ness. The names of these assumptions are fairly descriptive.Class EVMAssupmtions :=
{
balanceOfRevertSender: ∀ s tx,
maxTxFee tx ≤ balanceOfAc s (sender tx)
→ balanceOfAc (revertTxState s tx) (sender tx)
= balanceOfAc s (sender tx) - maxTxFee tx;
balanceOfRevertOther: ∀ s tx ac,
ac ≠ (sender tx)
→ balanceOfAc (revertTxState s tx) ac
= balanceOfAc s ac;
revertTxDelegationUpdCore: ∀ tx s,
let sf := (revertTxState s tx) in
(∀ ac, addrDelegated sf ac =
(addrDelegated s ac && asbool (ac ∉ (undels tx.1.2)))
|| asbool (ac ∈ (dels tx.1.2)));
execTxDelegationUpdCore: ∀ tx s,
let sf := (evmExecTxCoreState s tx) in
(∀ ac, addrDelegated sf ac =
(addrDelegated s ac && asbool (ac ∉ (undels tx.1.2)))
|| asbool (ac ∈ (dels tx.1.2)));
execTxSenderBalCore: ∀ tx s,
maxTxFee tx ≤ balanceOfAc s (sender tx) →
let sf := (evmExecTxCoreState s tx) in
addrDelegated sf (sender tx) = false
→ balanceOfAc s (sender tx) - ( maxTxFee tx + value tx + maxStorageFee tx) ≤ balanceOfAc sf (sender 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.
execTxCannotDebitNonDelegatedNonContractAccountsCore: ∀ tx s,
let sf := (evmExecTxCoreState s tx) in
∀ ac, ac ≠ sender tx
→ (addrDelegated sf ac || isSC sf ac) = false
→ balanceOfAc s ac ≤ balanceOfAc sf ac;
changedAccountSetSound: ∀ tx s,
let '(((sf, sf_res), changedAccounts), _) := (evmExecTxCore s tx) in
(∀ ac, ac ∉ changedAccounts → sf ac = s ac);
}.
let sf := (evmExecTxCoreState s tx) in
∀ ac, ac ≠ sender tx
→ (addrDelegated sf ac || isSC sf ac) = false
→ balanceOfAc s ac ≤ balanceOfAc sf ac;
changedAccountSetSound: ∀ tx s,
let '(((sf, sf_res), changedAccounts), _) := (evmExecTxCore s tx) in
(∀ ac, ac ∉ changedAccounts → sf ac = s ac);
}.
This lemma combines the axioms 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).1 in
let ReserveBal := delayedReserveBalOfAddr s.2 ac (txBlockNum tx) in
if (asbool (ac=sender tx)) then
isSC sf.1 (sender tx) = false→
(if isAllowedToEmpty s [] tx
then balanceOfAcA s (sender tx) - ( maxTxFee tx + value tx + maxStorageFee tx) ≤ balanceOfAcA sf (sender 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 (isSC sf.1 ac)
then True
else (if addrDelegated (sf.1) ac then ReserveBal `min` (balanceOfAcA s ac) else balanceOfAcA s ac)
≤ (balanceOfAcA sf ac).
maxTxFee tx ≤ balanceOfAc s.1 (sender tx) →
let sf := (execValidatedTx s tx).1 in
let ReserveBal := delayedReserveBalOfAddr s.2 ac (txBlockNum tx) in
if (asbool (ac=sender tx)) then
isSC sf.1 (sender tx) = false→
(if isAllowedToEmpty s [] tx
then balanceOfAcA s (sender tx) - ( maxTxFee tx + value tx + maxStorageFee tx) ≤ balanceOfAcA sf (sender 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 (isSC sf.1 ac)
then True
else (if addrDelegated (sf.1) ac then ReserveBal `min` (balanceOfAcA s ac) else balanceOfAcA s ac)
≤ (balanceOfAcA sf ac).
isAllowedToEmpty lemmas
Lemma execPreservesIsAllowedToEmpty s txInterfirst rest txnext:
let sf := (execValidatedTx s txInterfirst).1 in
txBlockNum txnext - (K-1) ≤ txBlockNum txInterfirst ≤ txBlockNum txnext
→ isAllowedToEmpty s (txInterfirst :: rest) txnext = isAllowedToEmpty sf rest txnext.
Lemma sender_in_flat_map_delundel_cons addr tx inter :
asbool (addr ∈ flat_map addrsDelUndelByTx (tx :: inter)) =
asbool (addr ∈ addrsDelUndelByTx tx) || asbool (addr ∈ flat_map addrsDelUndelByTx inter).
Lemma isAllowedToEmpty_false_senderNoRecentActivity s inter tx :
senderNoRecentActivity s inter tx = true →
isAllowedToEmpty s inter tx = false ↔ asbool (sender tx ∈ addrsDelUndelByTx tx) = true.
Definition rbLe (eoas: list EvmAddr) (rb1 rb2: EffReserveBals) :=
∀ addr, addr ∈ eoas → rb1 addr ≤ rb2 addr.
lemmas about remainingEffReserveBalOfSender
Lemma zmax_mono_l a b c : (a ≤ b)%Z → (a `max` c) ≤ (b `max` c).
Lemma zmin_mono_l a b c : (a ≤ b)%Z → (a `min` c) ≤ (b `min` c).
Lemma zmin_mono_r a b c : (a ≤ b)%Z → (c `min` a) ≤ (c `min` b).
Lemma zle_min a b c : (a ≤ b)%Z → (a ≤ c)%Z → (a ≤ b `min` c)%Z.
Lemma nmin_le_l a b : N.min a b ≤ a.
Lemma nmin_le_r a b : N.min a b ≤ b.
Lemma nmin_mono_l a b c : (a ≤ b)%N → N.min a c ≤ N.min b c.
Lemma nle_min a b c : (a ≤ b)%N → (a ≤ c)%N → (a ≤ N.min b c)%N.
Lemma Z_of_N_min a b :
Z.of_N (N.min a b) = Z.min (Z.of_N a) (Z.of_N b).
Lemma Z_of_N_sub_le a b :
(Z.of_N a - Z.of_N b ≤ Z.of_N (a - b))%Z.
Lemma nsub_le a b : (a - b ≤ a)%N.
Lemma nsub_le_mono_l a b c : (a ≤ b)%N → (a - c ≤ b - c)%N.
Lemma mono s rb1 rb2 inter tx:
rb1 ≤ rb2
→ (remainingEffReserveBalOfSender s rb1 inter tx) ≤ (remainingEffReserveBalOfSender s rb2 inter tx).
Lemma zmin_mono_l a b c : (a ≤ b)%Z → (a `min` c) ≤ (b `min` c).
Lemma zmin_mono_r a b c : (a ≤ b)%Z → (c `min` a) ≤ (c `min` b).
Lemma zle_min a b c : (a ≤ b)%Z → (a ≤ c)%Z → (a ≤ b `min` c)%Z.
Lemma nmin_le_l a b : N.min a b ≤ a.
Lemma nmin_le_r a b : N.min a b ≤ b.
Lemma nmin_mono_l a b c : (a ≤ b)%N → N.min a c ≤ N.min b c.
Lemma nle_min a b c : (a ≤ b)%N → (a ≤ c)%N → (a ≤ N.min b c)%N.
Lemma Z_of_N_min a b :
Z.of_N (N.min a b) = Z.min (Z.of_N a) (Z.of_N b).
Lemma Z_of_N_sub_le a b :
(Z.of_N a - Z.of_N b ≤ Z.of_N (a - b))%Z.
Lemma nsub_le a b : (a - b ≤ a)%N.
Lemma nsub_le_mono_l a b c : (a ≤ b)%N → (a - c ≤ b - c)%N.
Lemma mono s rb1 rb2 inter tx:
rb1 ≤ rb2
→ (remainingEffReserveBalOfSender s rb1 inter tx) ≤ (remainingEffReserveBalOfSender s rb2 inter tx).
remainingEffReserveBalOfSender never exceeds the sender's balance, assuming prevErb doesn't.
Lemma remRb_le_balance s prevErb inter tx :
prevErb ≤ balanceOfAc s.1 (sender tx) →
remainingEffReserveBalOfSender s prevErb inter tx
≤ balanceOfAc s.1 (sender tx).
prevErb ≤ balanceOfAc s.1 (sender tx) →
remainingEffReserveBalOfSender s prevErb inter tx
≤ balanceOfAc s.1 (sender tx).
In the proof of the main correctness theorem, we use a slightly different
variant of monotonicity, where in the RHS of ≤, remainingEffReserveBalOfSender 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 remainingEffReserveBalOfSender
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
→ isSC ((execValidatedTx s tx).1.1) ac = false)
→ (rb1 ≤ rb2)
→ (rb2 ≤ balanceOfAc (execValidatedTx s tx).1.1 (sender txc))
→ txBlockNum txc - (K-1) ≤ txBlockNum tx ≤ txBlockNum txc
→ ∀ addr : EvmAddr,
addr ∈ eoas
→ remainingEffReserveBalOfSender s rb1 (tx :: inter) txc
≤ remainingEffReserveBalOfSender ((execValidatedTx s tx).1) rb2 inter txc.
(∀ ac : EvmAddr,
ac ∈ sender tx :: sender txc :: map sender extension
→ isSC ((execValidatedTx s tx).1.1) ac = false)
→ (rb1 ≤ rb2)
→ (rb2 ≤ balanceOfAc (execValidatedTx s tx).1.1 (sender txc))
→ txBlockNum txc - (K-1) ≤ txBlockNum tx ≤ txBlockNum txc
→ ∀ addr : EvmAddr,
addr ∈ eoas
→ remainingEffReserveBalOfSender s rb1 (tx :: inter) txc
≤ remainingEffReserveBalOfSender ((execValidatedTx s tx).1) rb2 inter txc.
lifts mono2 from remainingEffReserveBalOfSender to remainingEffReserveBalsL:
proof follows a straightforward induction on the list extension,
using mono2 to fulfil the obligations of the induction hypothesis.
Lemma monoL2 eoas s rb1 rb2 inter extension tx:
(map sender extension) ⊆ eoas
→ rbLe eoas rb1 rb2
→ (∀ txext, txext ∈ extension → txBlockNum txext - (K-1) ≤ txBlockNum tx ≤ txBlockNum txext)
→ (∀ ac : EvmAddr, ac ∈ map sender (tx :: extension) → isSC ((execValidatedTx s tx).1.1) ac = false)
→ (∀ addr : EvmAddr, addr ∈ eoas → rb2 addr ≤ balanceOfAc (execValidatedTx s tx).1.1 addr)
→ rbLe eoas (remainingEffReserveBalsL s rb1 (tx::inter) extension)
(remainingEffReserveBalsL ((execValidatedTx s tx).1) rb2 inter extension).
Print Assumptions monoL2.
This lemma captures a key property of remainingEffReserveBalOfSender: it underapproximates
the resultant effective balance after execution of the transaction.
This is the heart of the proof of the top-level correctness theorem fullBlockStep.
Proof follows by unfolding definitions and case analysis. uses mono2, execBalLb, and many other lemmas above
Lemma exec1 tx extension s :
let irb0 := advanceEffReserveBals s (initialEffReserveBals s) (txBlockNum tx) in
let remf := remainingEffReserveBalOfSender s (irb0 (sender tx)) [] tx in
let remRbsf := updateKey irb0 (sender tx) (fun _ ⇒ remf) in
let sf := (execValidatedTx s tx).1 in
maxTxFee tx ≤ balanceOfAc s.1 (sender tx)
→ (∀ ac : EvmAddr, ac ∈ sender tx :: map sender extension → isSC sf.1 ac = false)
→ (∀ addr : EvmAddr,
addr ∈ sender tx :: map sender extension
→ remRbsf addr
≤ initialEffReserveBals sf addr).
Definition remainingEffReserveBals s irb inter txc:=
let irb' := advanceEffReserveBals s irb (txBlockNum txc) in
let rem := remainingEffReserveBalOfSender s (irb' (sender txc)) inter txc in
updateKey irb' (sender txc) (fun _ ⇒ rem).
Lemma decreasingRemTxSender s irb proc tx txc:
let remRbs := remainingEffReserveBals s irb (tx :: proc) txc in
remRbs (sender tx) ≤ irb (sender tx).
let irb0 := advanceEffReserveBals s (initialEffReserveBals s) (txBlockNum tx) in
let remf := remainingEffReserveBalOfSender s (irb0 (sender tx)) [] tx in
let remRbsf := updateKey irb0 (sender tx) (fun _ ⇒ remf) in
let sf := (execValidatedTx s tx).1 in
maxTxFee tx ≤ balanceOfAc s.1 (sender tx)
→ (∀ ac : EvmAddr, ac ∈ sender tx :: map sender extension → isSC sf.1 ac = false)
→ (∀ addr : EvmAddr,
addr ∈ sender tx :: map sender extension
→ remRbsf addr
≤ initialEffReserveBals sf addr).
Definition remainingEffReserveBals s irb inter txc:=
let irb' := advanceEffReserveBals s irb (txBlockNum txc) in
let rem := remainingEffReserveBalOfSender s (irb' (sender txc)) inter txc in
updateKey irb' (sender txc) (fun _ ⇒ rem).
Lemma decreasingRemTxSender s irb proc tx txc:
let remRbs := remainingEffReserveBals s irb (tx :: proc) txc in
remRbs (sender tx) ≤ irb (sender tx).
lifts the previous lemma from remainingEffReserveBalOfSender to remainingEffReserveBalsL. induction on nextL
Lemma decreasingRemL s irb proc (nextL: list TxWithHdr) tx:
(remainingEffReserveBalsL s irb (tx::proc) nextL) (sender tx) ≤ (irb (sender tx)).
(remainingEffReserveBalsL s irb (tx::proc) nextL) (sender tx) ≤ (irb (sender tx)).
Here is a component of 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.
Lemma execValidate tx extension s:
consensusAcceptableTxs s (tx::extension)
→ validateTx s.1 tx = true.
consensusAcceptableTxs s (tx::extension)
→ validateTx s.1 tx = true.
This lemma says that you can execute 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.
This follows from exec1 and monoL2
Lemma execPreservesConsensusChecks tx extension s:
maxTxFee tx ≤ balanceOfAc s.1 (sender tx) →
(∀ txext, txext ∈ extension → txBlockNum txext - (K-1) ≤ txBlockNum tx ≤ txBlockNum txext) → (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → isSC s.1 ac = false)
→ consensusAcceptableTxs s (tx::extension)
→ consensusAcceptableTxs ((execValidatedTx s tx).1) extension.
maxTxFee tx ≤ balanceOfAc s.1 (sender tx) →
(∀ txext, txext ∈ extension → txBlockNum txext - (K-1) ≤ txBlockNum tx ≤ txBlockNum txext) → (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → isSC s.1 ac = false)
→ consensusAcceptableTxs s (tx::extension)
→ consensusAcceptableTxs ((execValidatedTx s tx).1) extension.
The above 2 lemmas are used to yield the following:
Lemma inductiveStep (latestState : AugmentedState) (tx: TxWithHdr) (extension: list TxWithHdr) :
maxTxFee tx ≤ balanceOfAc latestState.1 (sender tx)
→ (∀ txext, txext ∈ extension → txBlockNum txext - (K-1) ≤ txBlockNum tx ≤ txBlockNum txext)
→ (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → isSC latestState.1 ac = false)
→ consensusAcceptableTxs latestState (tx::extension)
→ match execTx latestState tx with
| None ⇒ False
| Some (si,_) ⇒
consensusAcceptableTxs si extension
end.
Fixpoint blockNumsInRange2 (ltx: list TxWithHdr) : Prop :=
match ltx with
| [] ⇒ True
| htx::ttx ⇒
(∀ txext, txext ∈ ttx → txBlockNum txext ≤ txBlockNum htx + (K-1) ∧ txBlockNum htx ≤ txBlockNum txext)
∧ blockNumsInRange2 ttx
end.
Lemma bnequiv ltx: blockNumsInRange2 ltx → blockNumsInRange ltx .
Lemma bnequiv2 ltx: blockNumsInRange ltx → blockNumsInRange2 ltx .
Lemma txCannotCreateContractAtAddrsMono tx l1 l2:
l1 ⊆ l2
→ txCannotCreateContractAtAddrs tx l2
→ txCannotCreateContractAtAddrs tx l1.
Lemma txCannotCreateContractAtAddrsTrimHead tx h l:
txCannotCreateContractAtAddrs tx (h::l)
→ txCannotCreateContractAtAddrs tx l.
maxTxFee tx ≤ balanceOfAc latestState.1 (sender tx)
→ (∀ txext, txext ∈ extension → txBlockNum txext - (K-1) ≤ txBlockNum tx ≤ txBlockNum txext)
→ (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → isSC latestState.1 ac = false)
→ consensusAcceptableTxs latestState (tx::extension)
→ match execTx latestState tx with
| None ⇒ False
| Some (si,_) ⇒
consensusAcceptableTxs si extension
end.
Fixpoint blockNumsInRange2 (ltx: list TxWithHdr) : Prop :=
match ltx with
| [] ⇒ True
| htx::ttx ⇒
(∀ txext, txext ∈ ttx → txBlockNum txext ≤ txBlockNum htx + (K-1) ∧ txBlockNum htx ≤ txBlockNum txext)
∧ blockNumsInRange2 ttx
end.
Lemma bnequiv ltx: blockNumsInRange2 ltx → blockNumsInRange ltx .
Lemma bnequiv2 ltx: blockNumsInRange ltx → blockNumsInRange2 ltx .
Lemma txCannotCreateContractAtAddrsMono tx l1 l2:
l1 ⊆ l2
→ txCannotCreateContractAtAddrs tx l2
→ txCannotCreateContractAtAddrs tx l1.
Lemma txCannotCreateContractAtAddrsTrimHead tx h l:
txCannotCreateContractAtAddrs tx (h::l)
→ txCannotCreateContractAtAddrs tx l.
Proof of main theorem:
Straightforward induction on firstblock, with inductiveStep used in the inductive step.Lemma fullBlockStep (latestState : AugmentedState) (firstblock restblocks: list TxWithHdr) :
blockNumsInRange (firstblock++restblocks)
→ consensusAcceptableTxs latestState (firstblock++restblocks)
→ (∀ txext, txext ∈ (firstblock++restblocks) → txCannotCreateContractAtAddrs txext (map sender (firstblock++restblocks)))
→ (∀ ac, ac ∈ (map sender (firstblock++restblocks)) → isSC latestState.1 ac = false)
→ match execTxs latestState firstblock with
| None ⇒ False
| Some (si, _) ⇒
consensusAcceptableTxs si restblocks
∧ blockNumsInRange restblocks
∧ (∀ ac, ac ∈ (map sender restblocks) → isSC si.1 ac = false)
∧ (∀ txext, txext ∈ (restblocks) → txCannotCreateContractAtAddrs txext (map sender (restblocks)))
end.
Corollary fullBlockStep2 (latestState : AugmentedState) (blocks: list TxWithHdr) :
(∀ ac, ac ∈ (map sender (blocks)) → isSC latestState.1 ac = false)
→ (∀ txext, txext ∈ (blocks) → txCannotCreateContractAtAddrs txext (map sender (blocks)))
→ blockNumsInRange (blocks)
→ consensusAcceptableTxs latestState (blocks)
→ match execTxs latestState blocks with
| None ⇒ False
| Some (si, _) ⇒ True
end.
Lemma acceptableNil lastConsensedState:
consensusAcceptableTxs lastConsensedState [].
If an account's balance did not decrease, the reserve-balance check cannot fail for that account.
finalBalSufficient preTxState postTxState t a is the condition that is checked at the end of a transaction t for every changed account a.
If the check returns false for any changed account, the transaction is reverted: see execValidatedTx above
Lemma finalBalSufficient_if_balance_not_decreased:
∀ preTxState postTxState t a,
balanceOfAc preTxState.1 a ≤ balanceOfAc postTxState a
→ finalBalSufficient preTxState postTxState t a = true.
End EvmCore.
End K.
∀ preTxState postTxState t a,
balanceOfAc preTxState.1 a ≤ balanceOfAc postTxState a
→ finalBalSufficient preTxState postTxState t a = true.
End EvmCore.
End K.
Consensus checks using finite-width arithmetic (U256).
Definition u256z_equiv (o: option U256) (z: Z) : Prop :=
(z < 2^256) ∧
match o with
| None ⇒ (z < 0)
| Some u ⇒ z = u256_to_Z u
end.
(z < 2^256) ∧
match o with
| None ⇒ (z < 0)
| Some u ⇒ z = u256_to_Z u
end.
Below, we define variants of previous definitions where Z is substituted in by U256.
The suffix F stands for finite width numbers.
Helper projections relying on the invariant that balances already fit in 256 bits.
Definition balanceOfAcF (s : AugmentedState) (addr : EvmAddr) : U256 :=
u256_of_N (balanceOfAc s.1 addr).
Definition delayedReserveBalF (s : AugmentedState) (addr : EvmAddr) (n: N) : U256 :=
u256_of_N (delayedReserveBalOfAddr K s.2 addr n).
Definition valueF (t: TxWithHdr) : U256 := u256_of_N (value t).
Definition maxTxFeeF (t: TxWithHdr) : U256 := u256_of_N (maxTxFee t).
Definition maxStorageFeeF (t: TxWithHdr) : U256 := u256_of_N (maxStorageFee t).
u256_of_N (balanceOfAc s.1 addr).
Definition delayedReserveBalF (s : AugmentedState) (addr : EvmAddr) (n: N) : U256 :=
u256_of_N (delayedReserveBalOfAddr K s.2 addr n).
Definition valueF (t: TxWithHdr) : U256 := u256_of_N (value t).
Definition maxTxFeeF (t: TxWithHdr) : U256 := u256_of_N (maxTxFee t).
Definition maxStorageFeeF (t: TxWithHdr) : U256 := u256_of_N (maxStorageFee t).
The definition of remainingEffReserveBalOfSenderF will look almost exactly like remainingEffReserveBalOfSender except that it uses variants of arithmetic operations (like -, `min`, `max`) that work on option U256 instead of Z. So, we define those operations on option 256 and prove that they respect the equivalence u256z_equiv.
The first one is the min operation:
Definition u256_opt_min (lhs rhs : option U256) : option U256 :=
match lhs, rhs with
| Some l, Some r ⇒ Some (u256_min l r)
| _, _ ⇒ None
match lhs, rhs with
| Some l, Some r ⇒ Some (u256_min l r)
| _, _ ⇒ None
^ the min of 2 negative numbers is always a negative number
end.
We easily prove that our min operation on option U256 respects the
u256z_equiv relation: on inputs that are equivalent,
the outputs of u256_opt_min and Z.min are equivalent
Lemma u256z_equiv_mino (x y : option U256) (zx zy : Z) :
u256z_equiv x zx →
u256z_equiv y zy →
u256z_equiv (u256_opt_min x y) (Z.min zx zy).
u256z_equiv x zx →
u256z_equiv y zy →
u256z_equiv (u256_opt_min x y) (Z.min zx zy).
max operation on option U256
Definition u256_opt_max (lhs rhs : option U256) : option U256 :=
match lhs, rhs with
| Some l, Some r ⇒
Some (u256_wrap ((u256_to_N l) `max` (u256_to_N r)))
| Some l, None ⇒ Some l
| None, Some r ⇒ Some r
| None, None ⇒ None
end.
Lemma u256z_equiv_maxo (x y : option U256) (zx zy : Z) :
u256z_equiv x zx →
u256z_equiv y zy →
u256z_equiv (u256_opt_max x y) (Z.max zx zy).
match lhs, rhs with
| Some l, Some r ⇒
Some (u256_wrap ((u256_to_N l) `max` (u256_to_N r)))
| Some l, None ⇒ Some l
| None, Some r ⇒ Some r
| None, None ⇒ None
end.
Lemma u256z_equiv_maxo (x y : option U256) (zx zy : Z) :
u256z_equiv x zx →
u256z_equiv y zy →
u256z_equiv (u256_opt_max x y) (Z.max zx zy).
Subtraction is tricky. if a and b are negative numbers, the result may be negative or positive. But as we represent both a and b by None, we do not have enough information to determine that. Fortunately, in remainingEffReserveBalOfSender, the RHS of subtraction was always known to be non-negative. So it suffices to just define for that case:
Definition u256_opt_sub (lhs : option U256) (rhs: U256) : option U256 :=
match lhs with
| Some l ⇒
if (asbool (u256_to_N l < u256_to_N rhs)) then None else Some (u256_sub l rhs)
| None ⇒ None
match lhs with
| Some l ⇒
if (asbool (u256_to_N l < u256_to_N rhs)) then None else Some (u256_sub l rhs)
| None ⇒ None
^ subtracting a non-negative number from a negative number is always negative
end.
Lemma u256z_equiv_sub (x : option U256) (y : U256) (zx zy : Z) :
u256z_equiv x zx →
u256z_equiv (Some y) zy →
u256z_equiv (u256_opt_sub x y) (zx - zy).
Lemma u256z_equiv_sub (x : option U256) (y : U256) (zx zy : Z) :
u256z_equiv x zx →
u256z_equiv (Some y) zy →
u256z_equiv (u256_opt_sub x y) (zx - zy).
We define some notations to make our definition of remainingEffReserveBalOfSenderF look similar
Notation "x ⊖ y" := (u256_opt_sub x y) (at level 50, left associativity) : u256opt_scope.
Notation "x `mino` y" := (u256_opt_min x y) (at level 35, y at next level) : u256opt_scope.
Notation "x `maxo` y" := (u256_opt_max x y) (at level 35, y at next level) : u256opt_scope.
Notation "0f" := (Some u256_zero) : u256opt_scope.
Notation "x `mino` y" := (u256_opt_min x y) (at level 35, y at next level) : u256opt_scope.
Notation "x `maxo` y" := (u256_opt_max x y) (at level 35, y at next level) : u256opt_scope.
Notation "0f" := (Some u256_zero) : u256opt_scope.
Finally, below is the U256 version of remainingEffReserveBalOfSender
Definition remainingEffReserveBalOfSenderF
(preIntermediatesState : AugmentedState)
(prevErb : U256)
(intermediates : list TxWithHdr)
(candidateTx : TxWithHdr) : option U256 :=
let s := preIntermediatesState in
let senderAddr := sender candidateTx in
let senderBal := Some (balanceOfAcF preIntermediatesState senderAddr) in
let delayedRb := Some (delayedReserveBalF preIntermediatesState senderAddr (txBlockNum candidateTx)) in
let baseErb :=
if senderNoRecentActivity K s intermediates candidateTx
then senderBal `mino` delayedRb
else (Some prevErb) `mino` delayedRb in
if isAllowedToEmpty K preIntermediatesState intermediates candidateTx
then
let newBal := (((senderBal ⊖ (maxTxFeeF candidateTx)) ⊖ (valueF candidateTx)) ⊖ (maxStorageFeeF candidateTx)) `maxo` 0f in
(preIntermediatesState : AugmentedState)
(prevErb : U256)
(intermediates : list TxWithHdr)
(candidateTx : TxWithHdr) : option U256 :=
let s := preIntermediatesState in
let senderAddr := sender candidateTx in
let senderBal := Some (balanceOfAcF preIntermediatesState senderAddr) in
let delayedRb := Some (delayedReserveBalF preIntermediatesState senderAddr (txBlockNum candidateTx)) in
let baseErb :=
if senderNoRecentActivity K s intermediates candidateTx
then senderBal `mino` delayedRb
else (Some prevErb) `mino` delayedRb in
if isAllowedToEmpty K preIntermediatesState intermediates candidateTx
then
let newBal := (((senderBal ⊖ (maxTxFeeF candidateTx)) ⊖ (valueF candidateTx)) ⊖ (maxStorageFeeF candidateTx)) `maxo` 0f in
^ pay attention to the bracketing. If we instead first add up maxTxFeeF candidateTx, valueF candidateTx, and maxStorageFeeF candidateTx, and only then subtract from senderBal, the result of addition may overflow, although only a malicious person would probably send such a transaction
if asbool (maxTxFee candidateTx ≤ balanceOfAc (preIntermediatesState.1) senderAddr)
then newBal `mino` delayedRb
else None
else baseErb ⊖ (maxTxFeeF candidateTx).
then newBal `mino` delayedRb
else None
else baseErb ⊖ (maxTxFeeF candidateTx).
Next we prove equivalence: for that, we need to assume that the balances, configured reserve balance thresholds of all accounts, value, maxTxFee, etc. are within bounds of U256. These are trivial invariants because the EVM datatypes already enforce that
Definition stateWithinBounds (s : AugmentedState) : Prop :=
∀ addr,
((balanceOfAc s.1 addr) < 2^256) ∧
((settledReserveBalOfAddr s.2 addr) < 2^256) ∧
match pendingReserveBalOfAddr s.2 addr with
| Some (rb, _) ⇒ rb < 2^256
| None ⇒ True
end.
Definition txReserveUpdateWithinBounds (tx : TxWithHdr) : Prop :=
maxTxFee tx < 2^256 ∧
value tx < 2^256 ∧
maxStorageFee tx < 2^256.
∀ addr,
((balanceOfAc s.1 addr) < 2^256) ∧
((settledReserveBalOfAddr s.2 addr) < 2^256) ∧
match pendingReserveBalOfAddr s.2 addr with
| Some (rb, _) ⇒ rb < 2^256
| None ⇒ True
end.
Definition txReserveUpdateWithinBounds (tx : TxWithHdr) : Prop :=
maxTxFee tx < 2^256 ∧
value tx < 2^256 ∧
maxStorageFee tx < 2^256.
we can now use the above 2 definitions to state the equivalence theorem. The proof is mainly just unfolding definitions and using an arithmetic solver that understands Z.modulo
Lemma remainingEffReserveBalOfSender_equivF
(preIntermediatesState : AugmentedState)
(prevErbF : U256)
(intermediates : list TxWithHdr)
(candidateTx : TxWithHdr) :
stateWithinBounds preIntermediatesState →
txReserveUpdateWithinBounds candidateTx →
u256_val prevErbF < 2^256 →
u256z_equiv
(remainingEffReserveBalOfSenderF preIntermediatesState prevErbF intermediates candidateTx)
(remainingEffReserveBalOfSender K preIntermediatesState (u256_to_N prevErbF) intermediates candidateTx).
End U256Consensus.
(preIntermediatesState : AugmentedState)
(prevErbF : U256)
(intermediates : list TxWithHdr)
(candidateTx : TxWithHdr) :
stateWithinBounds preIntermediatesState →
txReserveUpdateWithinBounds candidateTx →
u256_val prevErbF < 2^256 →
u256z_equiv
(remainingEffReserveBalOfSenderF preIntermediatesState prevErbF intermediates candidateTx)
(remainingEffReserveBalOfSender K preIntermediatesState (u256_to_N prevErbF) intermediates candidateTx).
End U256Consensus.
Section RBTests.
Context (K: N) (K_pos: (0 < K)%N).
Variable s0 : AugmentedState.
Variables A B : EvmAddr.
Variable tx1 : TxWithHdr.
Let n1 : N := txBlockNum tx1.
Let rb10 : N := 10.
Context (K: N) (K_pos: (0 < K)%N).
Variable s0 : AugmentedState.
Variables A B : EvmAddr.
Variable tx1 : TxWithHdr.
Let n1 : N := txBlockNum tx1.
Let rb10 : N := 10.
Example setup: A is delegated and its settled reserve is 0 in the pre‑state.
A can recover by having another EOA B call into A’s delegated code to
set the reserve to 10, which yields a reserve‑balance update for A
during execution.
Hypothesis Hdeleg : addrDelegated s0.1 A = true.
Hypothesis Hsettled0 : settledReserveBal (s0.2 A) = 0.
Hypothesis Hpend0 : pendingReserveBal (s0.2 A) = None.
Hypothesis Hsender1 : sender tx1 = B.
Let rbUpdate1 : ReserveBalUpdate := [(A, rb10)].
Let es1 : ExtraAcStates := @updateExtraState K s0.2 tx1 rbUpdate1.
Hypothesis Hsettled0 : settledReserveBal (s0.2 A) = 0.
Hypothesis Hpend0 : pendingReserveBal (s0.2 A) = None.
Hypothesis Hsender1 : sender tx1 = B.
Let rbUpdate1 : ReserveBalUpdate := [(A, rb10)].
Let es1 : ExtraAcStates := @updateExtraState K s0.2 tx1 rbUpdate1.
In the pre‑state, A’s delayed reserve is 0.
Consensus acceptability for the sponsor tx (sender = B).
Hypothesis HnotDelegB : addrDelegated s0.1 B = false.
Hypothesis HnoDelWindow1B : @existsDelUndelTxWithinK K s0 tx1 = false.
Hypothesis HnoDelTx1B :
asbool (B ∈ flat_map addrsDelUndelByTx [tx1]) = false.
Hypothesis HnoTxWindow1B : @existsTxWithinK K s0 tx1 = false.
Hypothesis Hbal1B :
(maxTxFee tx1 + value tx1 + maxStorageFee tx1 ≤ balanceOfAc s0.1 B)%N.
Lemma RBTest_consensus_accepts_sponsor :
consensusAcceptableTxs K s0 [tx1].
Hypothesis HnoDelWindow1B : @existsDelUndelTxWithinK K s0 tx1 = false.
Hypothesis HnoDelTx1B :
asbool (B ∈ flat_map addrsDelUndelByTx [tx1]) = false.
Hypothesis HnoTxWindow1B : @existsTxWithinK K s0 tx1 = false.
Hypothesis Hbal1B :
(maxTxFee tx1 + value tx1 + maxStorageFee tx1 ≤ balanceOfAc s0.1 B)%N.
Lemma RBTest_consensus_accepts_sponsor :
consensusAcceptableTxs K s0 [tx1].
Putting the pieces together: although A’s reserve is 0 in the pre‑state,
a sponsor tx from B can be accepted by consensus and raise A’s delayed
reserve back to 10 after K blocks.
Lemma RBTest_recover_from_zero :
delayedReserveBalOfAddr K s0.2 A n1 = 0 ∧
consensusAcceptableTxs K s0 [tx1] ∧
delayedReserveBalOfAddr K es1 A (n1 + K) = rb10.
delayedReserveBalOfAddr K s0.2 A n1 = 0 ∧
consensusAcceptableTxs K s0 [tx1] ∧
delayedReserveBalOfAddr K es1 A (n1 + K) = rb10.
Execution success for a concrete two‑tx suffix: just unfold.
Variables evmExecTxCore :
StateOfAccounts → TxWithHdr → ((StateOfAccounts × TxResult) × (list EvmAddr)) × ReserveBalUpdate.
Variables revertTx : StateOfAccounts → TxWithHdr → (StateOfAccounts × TxResult).
Variables post1 : StateOfAccounts.
Variable changed1 : list EvmAddr.
Variable rbUpdate1' : ReserveBalUpdate.
Variable r1 : TxResult.
Hypothesis Hcore1 :
evmExecTxCore s0.1 tx1 = (((post1, r1), changed1), rbUpdate1').
Hypothesis Hsufficient1 :
@allFinalBalSufficient K s0 post1 changed1 tx1 = true.
Lemma RBTest_validateTx :
validateTx s0.1 tx1 = true.
Lemma RBTest_execTx_no_revert :
@execTx K evmExecTxCore revertTx s0 tx1 =
Some ((post1, @updateExtraState K s0.2 tx1 rbUpdate1'), r1).
Lemma RBTest_execTxs_one :
@execTxs K evmExecTxCore revertTx s0 [tx1] =
Some ((post1, @updateExtraState K s0.2 tx1 rbUpdate1'), [r1]).
End RBTests.
StateOfAccounts → TxWithHdr → ((StateOfAccounts × TxResult) × (list EvmAddr)) × ReserveBalUpdate.
Variables revertTx : StateOfAccounts → TxWithHdr → (StateOfAccounts × TxResult).
Variables post1 : StateOfAccounts.
Variable changed1 : list EvmAddr.
Variable rbUpdate1' : ReserveBalUpdate.
Variable r1 : TxResult.
Hypothesis Hcore1 :
evmExecTxCore s0.1 tx1 = (((post1, r1), changed1), rbUpdate1').
Hypothesis Hsufficient1 :
@allFinalBalSufficient K s0 post1 changed1 tx1 = true.
Lemma RBTest_validateTx :
validateTx s0.1 tx1 = true.
Lemma RBTest_execTx_no_revert :
@execTx K evmExecTxCore revertTx s0 tx1 =
Some ((post1, @updateExtraState K s0.2 tx1 rbUpdate1'), r1).
Lemma RBTest_execTxs_one :
@execTxs K evmExecTxCore revertTx s0 [tx1] =
Some ((post1, @updateExtraState K s0.2 tx1 rbUpdate1'), [r1]).
End RBTests.
This page has been generated by coqdoc