Skip to content

Commit e3e7ecd

Browse files
williamdemeoclaude
andcommitted
add govDepsMatch precondition to EventuallyRefunded statement (#414)
The naive statement is false for arbitrary ChainStates: an "orphan" GovActionDeposit entry (present in the deposit map but absent from GovState) would never be removed by any EPOCH transition. Adding govDepsMatch—a known CHAIN invariant—ensures every such deposit has a matching GovActionState with an expiresIn epoch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4c03dce commit e3e7ecd

1 file changed

Lines changed: 41 additions & 26 deletions

File tree

src/Ledger/Conway/Specification/Chain/Properties/EventuallyRefunded.lagda.md

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,37 @@ module Ledger.Conway.Specification.Chain.Properties.EventuallyRefunded
2020
open import Ledger.Conway.Specification.Certs govStructure
2121
open import Ledger.Conway.Specification.Chain txs abs
2222
open import Ledger.Conway.Specification.Epoch txs abs
23+
open import Ledger.Conway.Specification.Ledger txs abs
24+
open import Ledger.Conway.Specification.Ledger.Properties.Base txs abs
2325
open import Ledger.Prelude
2426
```
2527
-->
2628

2729
*Informally*.
2830

2931
Every governance action carries a deposit (a `GovActionDeposit`{.AgdaInductiveConstructor}
30-
entry in the deposit pot of the `UTxOState`{.AgdaRecord}) which is held only while the
31-
action is part of the governance state. Each action also records an
32-
`expiresIn`{.AgdaField} epoch in its `GovActionState`{.AgdaRecord}. Once that epoch
33-
has passed, the `EPOCH`{.AgdaDatatype} transition (run as part of `CHAIN`{.AgdaDatatype}
34-
whenever a block advances the epoch) removes the action from the governance state and
35-
refunds its deposit.
36-
37-
Hence, for every `ChainState`{.AgdaRecord} `cs`{.AgdaBound} and every governance action
38-
deposit still held in `cs`{.AgdaBound}, there is an epoch `e`{.AgdaBound} (namely, just
39-
after the action's `expiresIn`{.AgdaField}) such that, once the chain has progressed to
40-
`e`{.AgdaBound} (i.e. a block has been produced in epoch `e`{.AgdaBound} or later), the
41-
deposit is no longer in the deposit pot.
32+
entry in the `UTxOState`{.AgdaRecord}) which is held only while the action is part of
33+
the governance state. Each action also records an `expiresIn`{.AgdaField} epoch in
34+
its `GovActionState`{.AgdaRecord}.
35+
36+
Once that epoch has passed, the `EPOCH`{.AgdaDatatype} transition (run as part of
37+
`CHAIN`{.AgdaDatatype} whenever a block advances the epoch) removes the action from
38+
the governance state and refunds its deposit.
39+
40+
Hence, for every `ChainState`{.AgdaRecord} `cs`{.AgdaBound} satisfying
41+
`govDepsMatch`{.AgdaFunction} and every governance action deposit still held in
42+
`cs`{.AgdaBound}, there is an epoch `e`{.AgdaBound} (namely, just after the action's
43+
`expiresIn`{.AgdaField}) such that, once the chain has progressed to `e`{.AgdaBound},
44+
the deposit is no longer in the deposit pot.[^1]
45+
46+
The precondition `govDepsMatch`{.AgdaFunction} is needed to ensure that every
47+
`GovActionDeposit`{.AgdaInductiveConstructor} entry has a corresponding
48+
`GovActionState`{.AgdaRecord} in the governance state (from which we read
49+
the `expiresIn`{.AgdaField} epoch). Without the precondition, "orphan" deposit
50+
entries with no matching governance action would never be removed.
51+
52+
The predicate `govDepsMatch`{.AgdaFunction} is a known `CHAIN`{.AgdaDatatype}
53+
invariant.[^2]
4254

4355
*Formally*.
4456

@@ -47,30 +59,33 @@ chain state, and we close `CHAIN`{.AgdaDatatype} under composition along a list
4759
blocks.
4860

4961
```agda
50-
-- The deposit pot of `cs` still holds the deposit for the governance action `gaid`.
62+
-- The deposits of `cs` still holds the deposit for governance action `gaid`.
5163
gaDepositInPot : ChainState → GovActionID → Type
5264
gaDepositInPot cs gaid = GovActionDeposit gaid ∈ dom (DepositsOf cs)
5365
5466
-- Reflexive-transitive closure of CHAIN along a list of blocks.
5567
data CHAINStar : ChainState → List Block → ChainState → Type where
56-
[]* : {cs} → CHAINStar cs [] cs
57-
_∷*_ : {cs cs' cs'' b bs}
58-
→ _ ⊢ cs ⇀⦇ b ,CHAIN⦈ cs'
59-
→ CHAINStar cs' bs cs''
60-
→ CHAINStar cs (b ∷ bs) cs''
68+
[]* : {cs : ChainState} → CHAINStar cs [] cs
69+
_∷*_ : {cs cs' cs'' : ChainState} {b : Block} {bs : List Block}
70+
→ _ ⊢ cs ⇀⦇ b ,CHAIN⦈ cs'
71+
→ CHAINStar cs' bs cs''
72+
→ CHAINStar cs (b ∷ bs) cs''
6173
```
6274

63-
The property then states that every held governance action deposit is eventually
64-
refunded:
75+
The property asserts that every held governance action deposit is eventually
76+
refunded. Formally,
6577

6678
```agda
6779
GADepositsEventuallyRefunded : Type
68-
GADepositsEventuallyRefunded = ∀ {cs : ChainState} {gaid : GovActionID}
69-
→ gaDepositInPot cs gaid
70-
→ Σ[ e ∈ Epoch ] (∀ {bs : List Block} {cs' : ChainState}
71-
→ CHAINStar cs bs cs'
72-
→ e ≤ LastEpochOf cs'
73-
→ ¬ gaDepositInPot cs' gaid)
80+
GADepositsEventuallyRefunded = {cs : ChainState} {gaid : GovActionID}
81+
→ govDepsMatch (LStateOf cs) → gaDepositInPot cs gaid
82+
→ ∃[ e ] ( ∀ {bs cs'} → CHAINStar cs bs cs' → e ≤ LastEpochOf cs'
83+
→ ¬ gaDepositInPot cs' gaid )
7484
```
7585

7686
*Proof*. (coming soon)
87+
88+
---
89+
90+
[^1]: "The chain has progressed to `e`{.AgdaBound}" holds when a block has been produced in epoch `e`{.AgdaBound} *or later*.
91+
[^2]: See [`CHAIN-govDepsMatch`][Chain.Properties.GovDepsMatch] for the proof.

0 commit comments

Comments
 (0)