Appendix: Coq proof of reserve-balance safety
This file accompanies the blog post and serves as a literate walkthrough of the final model.- *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.
Definition StateOfAccounts : Type := EvmAddr → AccountM.
Definition addrDelegated (s: StateOfAccounts) (a : EvmAddr) : bool :=
match delegatedTo (s a) with
| [] ⇒ false
| _ ⇒ true
end.
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.
The fields above should ultimately come from EVM semantics and not here. The fields below are monad-specific.
^ updates the reserve balance of the sender if Some. In that case, the transaction does nothing else, e.g., smart contract invocation or transfer.
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))).
((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.
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.
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:
^ 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.
^ 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).
#[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)
| None ⇒ false
end.
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)
| None ⇒ false
end.
Definition existsTxWithinK (state : AugmentedState) (tx: TxWithHdr) : bool :=
indexWithinK lastTxInBlockIndex (snd state) tx.
indexWithinK lastTxInBlockIndex (snd state) 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 k ⇒ if (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.
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 k ⇒ if (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:- 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).
Definition isAllowedToEmpty
(state : AugmentedState) (intermediateTxsSinceState: list TxWithHdr) (tx: TxWithHdr) : bool :=
let consideredDelegated := (addrDelegated (fst state) (sender tx))
|| existsDelUndelTxWithinK state tx
|| bool_decide ((sender tx) ∈ flat_map addrsDelUndelByTx (tx::intermediateTxsSinceState))
in
let existsSameSenderTxInWindow :=
(existsTxWithinK state tx)
|| bool_decide ((sender tx) ∈ map sender intermediateTxsSinceState) in
(negb consideredDelegated) && (negb existsSameSenderTxInWindow).
(state : AugmentedState) (intermediateTxsSinceState: list TxWithHdr) (tx: TxWithHdr) : bool :=
let consideredDelegated := (addrDelegated (fst state) (sender tx))
|| existsDelUndelTxWithinK state tx
|| bool_decide ((sender tx) ∈ flat_map addrsDelUndelByTx (tx::intermediateTxsSinceState))
in
let existsSameSenderTxInWindow :=
(existsTxWithinK state tx)
|| bool_decide ((sender tx) ∈ map sender intermediateTxsSinceState) in
(negb consideredDelegated) && (negb existsSameSenderTxInWindow).
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.
- 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.
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 old ⇒ old &: _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).
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 old ⇒ old &: _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.
Definition remainingEffReserveBals (preIntermediatesState : AugmentedState) (preCandidateTxResBalances: EffReserveBals) (intermediates: list TxWithHdr) (candidateTx: TxWithHdr)
: EffReserveBals :=
let s := preIntermediatesState in
let addr := sender candidateTx in
match reserveBalUpdateOfTx candidateTx with
| Some newRb ⇒
updateKey preCandidateTxResBalances addr (fun prevErb ⇒ (prevErb - maxTxFee candidateTx) `min` newRb)
| None ⇒
regular tx, not one that sets reserve balance
if isAllowedToEmpty s intermediates candidateTx
then
let sbal := balanceOfAc s.1 addr in
let newBal:N := (sbal - maxTxFee candidateTx - value candidateTx)%N in
if bool_decide (maxTxFee candidateTx ≤ sbal)
then updateKey preCandidateTxResBalances addr (fun prevErb ⇒ newBal `min` (configuredReserveBalOfAddr s.2 addr))
else updateKey preCandidateTxResBalances addr (fun _ ⇒ -1)
else (updateKey preCandidateTxResBalances addr (fun prevErb ⇒ (prevErb - maxTxFee candidateTx)%Z))
end.
Fixpoint remainingEffReserveBalsL (latestState : AugmentedState) (preRestResBalances: EffReserveBals) (postStateAccountedSuffix rest: list TxWithHdr)
: EffReserveBals:=
match rest with
| [] ⇒ preRestResBalances
| hrest::tlrest ⇒
let remainingReserves :=
remainingEffReserveBals latestState preRestResBalances postStateAccountedSuffix hrest in
remainingEffReserveBalsL latestState remainingReserves (postStateAccountedSuffix++[hrest]) tlrest
end.
then
let sbal := balanceOfAc s.1 addr in
let newBal:N := (sbal - maxTxFee candidateTx - value candidateTx)%N in
if bool_decide (maxTxFee candidateTx ≤ sbal)
then updateKey preCandidateTxResBalances addr (fun prevErb ⇒ newBal `min` (configuredReserveBalOfAddr s.2 addr))
else updateKey preCandidateTxResBalances addr (fun _ ⇒ -1)
else (updateKey preCandidateTxResBalances addr (fun prevErb ⇒ (prevErb - maxTxFee candidateTx)%Z))
end.
Fixpoint remainingEffReserveBalsL (latestState : AugmentedState) (preRestResBalances: EffReserveBals) (postStateAccountedSuffix rest: list TxWithHdr)
: EffReserveBals:=
match rest with
| [] ⇒ preRestResBalances
| hrest::tlrest ⇒
let remainingReserves :=
remainingEffReserveBals latestState preRestResBalances postStateAccountedSuffix hrest in
remainingEffReserveBalsL latestState remainingReserves (postStateAccountedSuffix++[hrest]) tlrest
end.
Algorithm 1 (consensus): acceptability of a suffix
Definition consensusAcceptableTxs (latestState : AugmentedState) (postStateSuffix: list TxWithHdr) : Prop :=
∀ addr, addr ∈ map sender postStateSuffix →
0≤ (remainingEffReserveBalsL latestState (initialEffReserveBals latestState) [] postStateSuffix) addr.
∀ addr, addr ∈ map sender postStateSuffix →
0≤ (remainingEffReserveBalsL latestState (initialEffReserveBals latestState) [] postStateSuffix) 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 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 newRb ⇒ newRb
| None ⇒ configuredReserveBal oldes
end
else configuredReserveBal oldes
|}
).
Abstract execution and revert
Axiom evmExecTxCore : StateOfAccounts → TxWithHdr → StateOfAccounts × (list EvmAddr) .
Axiom revertTx : StateOfAccounts → TxWithHdr → StateOfAccounts.
Algorithm 2 (execution): execute a transaction
- 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 b ⇒ b - 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.
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.
- 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.
Definition validateTx (preTxState: StateOfAccounts) (t: TxWithHdr): bool :=
bool_decide (maxTxFee t ≤ balanceOfAc preTxState (sender t))%N.
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.
Definition execTx (s: AugmentedState) (t: TxWithHdr): option (AugmentedState) :=
if (negb (validateTx (fst s) t))
then None
else Some (execValidatedTx s t).
if (negb (validateTx (fst s) t))
then None
else Some (execValidatedTx s t).
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 si ⇒ execTxs si tls
| None ⇒ None
end
end.
match ts with
| [] ⇒ Some s
| t::tls ⇒
match execTx s t with
| Some si ⇒ execTxs si tls
| None ⇒ None
end
end.
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.
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.
Theorem fullBlockStep2 (latestState : AugmentedState) (blocks: list TxWithHdr) :
(∀ ac, ac ∈ (map sender blocks) → hasCode 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) → hasCode 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 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:
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)) → hasCode 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)) → hasCode 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 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.
∧ blockNumsInRange restblocks
∧ (∀ ac, ac ∈ (map sender restblocks) → hasCode si.1 ac = false)
∧ (∀ txext, txext ∈ (restblocks) → txCannotCreateContractAtAddrs txext (map sender (restblocks)))
end.
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).
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 execTxLemma 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 rb ⇒ rb
| None ⇒ configuredReserveBalOfAddr 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.
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 rb ⇒ rb
| None ⇒ configuredReserveBalOfAddr 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
Lemma execPreservesIsAllowedToEmpty s txInterfirst rest txnext:
let sf := execValidatedTx s txInterfirst in
txBlockNum txnext - K ≤ txBlockNum txInterfirst ≤ txBlockNum txnext
→ isAllowedToEmpty s (txInterfirst :: rest) txnext = isAllowedToEmpty sf rest txnext.
Definition rbLe (eoas: list EvmAddr) (rb1 rb2: EffReserveBals) :=
∀ addr, addr ∈ eoas → rb1 addr ≤ rb2 addr.
lemmas about remainingEffReserveBals
Lemma mono eoas s rb1 rb2 inter tx:
rbLe eoas rb1 rb2
→ rbLe eoas (remainingEffReserveBals s rb1 inter tx)
(remainingEffReserveBals s rb2 inter tx).
rbLe eoas rb1 rb2
→ rbLe eoas (remainingEffReserveBals s rb1 inter tx)
(remainingEffReserveBals s rb2 inter tx).
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.
(∀ 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
Lemma monoL eoas s rb1 rb2 inter extension:
map sender extension ⊆ eoas
→ rbLe eoas rb1 rb2
→ rbLe eoas (remainingEffReserveBalsL s rb1 inter extension)
(remainingEffReserveBalsL s rb2 inter extension).
map sender extension ⊆ eoas
→ rbLe eoas rb1 rb2
→ rbLe eoas (remainingEffReserveBalsL s rb1 inter extension)
(remainingEffReserveBalsL s rb2 inter extension).
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.
Lemma monoL2 eoas s rb1 rb2 inter extension tx:
(map sender extension) ⊆ eoas
→ rbLe eoas rb1 rb2
→ (∀ txext, txext ∈ extension → txBlockNum txext - K ≤ txBlockNum tx ≤ txBlockNum txext)
→ (∀ ac : EvmAddr, ac ∈ map sender (tx :: extension) → hasCode (execValidatedTx s tx).1 ac = false)
→ rbLe eoas (remainingEffReserveBalsL s rb1 (tx::inter) extension)
(remainingEffReserveBalsL (execValidatedTx s tx) rb2 inter extension).
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).
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.
Lemma decreasingRemTxSender s irb proc tx txc:
remainingEffReserveBals s irb (tx :: proc) txc (sender tx) ≤ irb (sender tx).
remainingEffReserveBals s irb (tx :: proc) txc (sender tx) ≤ irb (sender tx).
lifts the previous lemma from remainingEffReserveBals to remainingEffReserveBals. 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)).
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.
Lemma execValidate tx extension s:
consensusAcceptableTxs s (tx::extension)
→ validateTx s.1 tx = true.
consensusAcceptableTxs s (tx::extension)
→ validateTx s.1 tx = true.
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.
Lemma execPreservesConsensusChecks tx extension s:
maxTxFee tx ≤ balanceOfAc s.1 (sender tx) →
(∀ txext, txext ∈ extension → txBlockNum txext - K ≤ txBlockNum tx ≤ txBlockNum txext) → (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → hasCode s.1 ac = false)
→ consensusAcceptableTxs s (tx::extension)
→ consensusAcceptableTxs (execValidatedTx s tx) extension.
maxTxFee tx ≤ balanceOfAc s.1 (sender tx) →
(∀ txext, txext ∈ extension → txBlockNum txext - K ≤ txBlockNum tx ≤ txBlockNum txext) → (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → hasCode s.1 ac = false)
→ consensusAcceptableTxs s (tx::extension)
→ consensusAcceptableTxs (execValidatedTx s tx) extension.
the above 2 lemmas can be combined 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 ≤ txBlockNum tx ≤ txBlockNum txext)
→ (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → hasCode 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 ∧ 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 ≤ txBlockNum tx ≤ txBlockNum txext)
→ (∀ txext, txext ∈ tx::extension → txCannotCreateContractAtAddrs txext (map sender (tx::extension)))
→ (∀ ac, ac ∈ (map sender (tx::extension)) → hasCode 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 ∧ 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)) → hasCode latestState.1 ac = false)
→ match execTxs latestState firstblock with
| None ⇒ False
| Some si ⇒
consensusAcceptableTxs si restblocks
∧ blockNumsInRange restblocks
∧ (∀ ac, ac ∈ (map sender restblocks) → hasCode si.1 ac = false)
∧ (∀ txext, txext ∈ (restblocks) → txCannotCreateContractAtAddrs txext (map sender (restblocks)))
end.
Print Assumptions fullBlockStep.
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 (ac ∉ undels tx.1.2) || bool_decide (ac ∈ dels tx.1.2)
revertTx : StateOfAccounts → TxWithHdr → StateOfAccounts
execTxSenderBalCore :
∀ (tx : TxWithHdr) (s : StateOfAccounts),
(maxTxFee tx ≤ balanceOfAc 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 (ac ∉ undels tx.1.2) || bool_decide (ac ∈ dels tx.1.2)
execTxCannotDebitNonDelegatedNonContractAccountsCore :
∀ (tx : TxWithHdr) (s : StateOfAccounts),
reserveBalUpdateOfTx tx = None
→ ∀ (sf := (evmExecTxCore s tx).1) (ac : EvmAddr),
ac ≠ sender tx
→ addrDelegated sf ac || hasCode sf ac = false
→ (balanceOfAc s ac ≤ balanceOfAc sf ac)%N
evmExecTxCore : StateOfAccounts → TxWithHdr → StateOfAccounts × list EvmAddr
changedAccountSetSound :
∀ (tx : TxWithHdr) (s : StateOfAccounts),
reserveBalUpdateOfTx tx = None
→ let (sf, changedAccounts) := evmExecTxCore s tx in
∀ ac : EvmAddr, ac ∉ changedAccounts → sf ac = s ac
balanceOfRevertSender :
∀ (s : StateOfAccounts) (tx : TxWithHdr),
(maxTxFee tx ≤ balanceOfAc 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
→ ac ≠ sender tx → balanceOfAc (revertTx s tx) ac = balanceOfAc s ac
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 (ac ∉ undels tx.1.2) || bool_decide (ac ∈ dels tx.1.2)
revertTx : StateOfAccounts → TxWithHdr → StateOfAccounts
execTxSenderBalCore :
∀ (tx : TxWithHdr) (s : StateOfAccounts),
(maxTxFee tx ≤ balanceOfAc 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 (ac ∉ undels tx.1.2) || bool_decide (ac ∈ dels tx.1.2)
execTxCannotDebitNonDelegatedNonContractAccountsCore :
∀ (tx : TxWithHdr) (s : StateOfAccounts),
reserveBalUpdateOfTx tx = None
→ ∀ (sf := (evmExecTxCore s tx).1) (ac : EvmAddr),
ac ≠ sender tx
→ addrDelegated sf ac || hasCode sf ac = false
→ (balanceOfAc s ac ≤ balanceOfAc sf ac)%N
evmExecTxCore : StateOfAccounts → TxWithHdr → StateOfAccounts × list EvmAddr
changedAccountSetSound :
∀ (tx : TxWithHdr) (s : StateOfAccounts),
reserveBalUpdateOfTx tx = None
→ let (sf, changedAccounts) := evmExecTxCore s tx in
∀ ac : EvmAddr, ac ∉ changedAccounts → sf ac = s ac
balanceOfRevertSender :
∀ (s : StateOfAccounts) (tx : TxWithHdr),
(maxTxFee tx ≤ balanceOfAc 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
→ ac ≠ sender tx → balanceOfAc (revertTx s tx) ac = balanceOfAc s ac
Corollary fullBlockStep2 (latestState : AugmentedState) (blocks: list TxWithHdr) :
(∀ ac, ac ∈ (map sender (blocks)) → hasCode 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 [].
End K.
This page has been generated by coqdoc