|
| 1 | +--- |
| 2 | +source_branch: master |
| 3 | +source_path: src/Ledger/Dijkstra/Specification/Gov/Properties/PoV.lagda.md |
| 4 | +--- |
| 5 | + |
| 6 | +# Properties of `GOV`: Preservation of Value {#sec:gov-properties-pov} |
| 7 | + |
| 8 | +This module proves the two governance-deposit accounting facts that the top-level |
| 9 | +preservation-of-value proof (`LEDGER-pov`{.AgdaFunction} in |
| 10 | +`Ledger.Properties.PoV`{.AgdaModule}) assumes as module parameters (#1276): |
| 11 | + |
| 12 | ++ `rmOrphanDRepVotes-coinFromGovDeposit`{.AgdaFunction}: |
| 13 | + `rmOrphanDRepVotes`{.AgdaFunction} only rewrites the `gvDRep`{.AgdaField} votes of |
| 14 | + each governance action, never `GovActionState.deposit`{.AgdaField}, so it leaves |
| 15 | + `coinFromGovDeposit`{.AgdaFunction} unchanged. |
| 16 | + |
| 17 | ++ `GOVS-coinFromGovDeposit`{.AgdaFunction}: a `GOVS`{.AgdaDatatype} step grows |
| 18 | + `coinFromGovDeposit`{.AgdaFunction} by exactly the |
| 19 | + `govProposalsDeposits`{.AgdaFunction} of the proposals in its signal list — |
| 20 | + `GOV-Propose`{.AgdaInductiveConstructor} stores `deposit = pp .govActionDeposit` |
| 21 | + for the new action, and `GOV-Vote`{.AgdaInductiveConstructor} never changes a |
| 22 | + deposit. |
| 23 | + |
| 24 | +??? note "**Status: complete**" |
| 25 | + |
| 26 | + Both facts typecheck under `--safe`, with no module parameters and no postulates. |
| 27 | + Their statements match the corresponding `LEDGER-PoV`{.AgdaModule} module |
| 28 | + parameters (modulo this module's own `proposalsOf`{.AgdaFunction} copy; see |
| 29 | + below), so the rewiring (#1277) can instantiate them directly. |
| 30 | + |
| 31 | +<!-- |
| 32 | +```agda |
| 33 | +{-# OPTIONS --safe #-} |
| 34 | +
|
| 35 | +open import Ledger.Dijkstra.Specification.Transaction |
| 36 | +open import Ledger.Dijkstra.Specification.Abstract |
| 37 | +
|
| 38 | +module Ledger.Dijkstra.Specification.Gov.Properties.PoV |
| 39 | + (txs : _) (open TransactionStructure txs) |
| 40 | + (abs : AbstractFunctions txs) (open AbstractFunctions abs) |
| 41 | + where |
| 42 | +
|
| 43 | +open import Data.Nat.Base using () renaming (_+_ to infixl 6 _+ᴺ_) |
| 44 | +open import Data.Nat.Properties using (+-assoc; +-identityʳ) |
| 45 | +open import Data.Nat.Tactic.RingSolver using (solve-∀) |
| 46 | +
|
| 47 | +open import Ledger.Prelude |
| 48 | +
|
| 49 | +open import Ledger.Dijkstra.Specification.Certs govStructure |
| 50 | +open import Ledger.Dijkstra.Specification.Gov govStructure |
| 51 | +open import Ledger.Dijkstra.Specification.Ledger txs abs |
| 52 | +open import Ledger.Dijkstra.Specification.Utxo txs abs |
| 53 | +
|
| 54 | +open import Interface.STS |
| 55 | +``` |
| 56 | +--> |
| 57 | + |
| 58 | +## `proposalsOf` |
| 59 | + |
| 60 | +The right injections of a list of sums, used (at `GovVote ⊎ GovProposal`) to extract |
| 61 | +the proposals from a mixed `GOVS`{.AgdaDatatype} signal list. |
| 62 | +`Ledger.Properties.PoV`{.AgdaModule} defines an identical function for stating its |
| 63 | +`GOVS-coinFromGovDeposit`{.AgdaFunction} parameter, but importing it here would |
| 64 | +create an import cycle once #1277 rewires `Ledger.Properties.PoV`{.AgdaModule} to |
| 65 | +import *this* module. So we keep a local copy (clause-for-clause identical), |
| 66 | +together with the `proposalsOf-Proposals+Votes`{.AgdaFunction} extraction lemma; the |
| 67 | +rewiring should delete the copies in `Ledger.Properties.PoV`{.AgdaModule} and import |
| 68 | +these. |
| 69 | + |
| 70 | +```agda |
| 71 | +proposalsOf : ∀ {A B : Type} → List (A ⊎ B) → List B |
| 72 | +proposalsOf [] = [] |
| 73 | +proposalsOf (inj₁ _ ∷ xs) = proposalsOf xs |
| 74 | +proposalsOf (inj₂ p ∷ xs) = p ∷ proposalsOf xs |
| 75 | +``` |
| 76 | + |
| 77 | +`proposalsOf (GovProposals+Votes t)` recovers exactly the proposals of `t`, since |
| 78 | +`GovProposals+Votes`{.AgdaFunction} lists all proposals (as right injections) before |
| 79 | +all votes (as left injections). |
| 80 | + |
| 81 | +```agda |
| 82 | +proposalsOf-Proposals+Votes : ∀ {ℓ} (t : Tx ℓ) |
| 83 | + → proposalsOf (GovProposals+Votes t) ≡ ListOfGovProposalsOf t |
| 84 | +proposalsOf-Proposals+Votes t = go (ListOfGovProposalsOf t) (ListOfGovVotesOf t) |
| 85 | + where |
| 86 | + drop-votes : ∀ {A B : Type} (vs : List A) |
| 87 | + → proposalsOf {A = A} {B = B} (map inj₁ vs) ≡ [] |
| 88 | + drop-votes [] = refl |
| 89 | + drop-votes (_ ∷ vs) = drop-votes vs |
| 90 | + go : ∀ {A B : Type} (ps : List B) (vs : List A) |
| 91 | + → proposalsOf (map inj₂ ps ++ map inj₁ vs) ≡ ps |
| 92 | + go [] vs = drop-votes vs |
| 93 | + go (p ∷ ps) vs = cong (p ∷_) (go ps vs) |
| 94 | +``` |
| 95 | + |
| 96 | +## Arithmetic helpers |
| 97 | + |
| 98 | +The two pure `+`-rearrangements below are discharged by the reflective ring solver |
| 99 | +over the commutative semiring of naturals (`Data.Nat.Tactic.RingSolver`). The |
| 100 | +solver recognises the ring's operations *syntactically*, so the |
| 101 | +`Ledger.Prelude`{.AgdaModule} overloaded `_+_` (a `HasAdd`{.AgdaRecord} method, |
| 102 | +which merely *reduces* to `Data.Nat._+_`) defeats it. The solver-facing statements |
| 103 | +are therefore written with the raw natural-number addition, imported as `_+ᴺ_` — |
| 104 | +definitionally equal to `_+_` at `Coin`, so the lemmas discharge `_+_` goals |
| 105 | +unchanged. |
| 106 | + |
| 107 | +```agda |
| 108 | +swap-head : ∀ a b c → a +ᴺ (b +ᴺ c) ≡ b +ᴺ (a +ᴺ c) |
| 109 | +swap-head = solve-∀ |
| 110 | +
|
| 111 | +rotate-in : ∀ a b c → a +ᴺ b +ᴺ c ≡ b +ᴺ (a +ᴺ c) |
| 112 | +rotate-in = solve-∀ |
| 113 | +``` |
| 114 | + |
| 115 | +## `rmOrphanDRepVotes` preserves `coinFromGovDeposit` |
| 116 | + |
| 117 | +`rmOrphanDRepVotes`{.AgdaFunction} maps over the `GovState`{.AgdaRecord}, rewriting |
| 118 | +only the `votes`{.AgdaField} field of each `GovActionState`{.AgdaRecord} (it filters |
| 119 | +the `gvDRep`{.AgdaField} votes down to registered DReps). The record update leaves |
| 120 | +`deposit`{.AgdaField} definitionally unchanged in every cons cell, so a list |
| 121 | +induction closes the proof. |
| 122 | + |
| 123 | +```agda |
| 124 | +rmOrphanDRepVotes-coinFromGovDeposit : ∀ (cs : CertState) (g : GovState) |
| 125 | + → coinFromGovDeposit (rmOrphanDRepVotes cs g) ≡ coinFromGovDeposit g |
| 126 | +rmOrphanDRepVotes-coinFromGovDeposit cs [] = refl |
| 127 | +rmOrphanDRepVotes-coinFromGovDeposit cs ((_ , gas) ∷ g) = |
| 128 | + cong (GovActionState.deposit gas +_) (rmOrphanDRepVotes-coinFromGovDeposit cs g) |
| 129 | +``` |
| 130 | + |
| 131 | +## A cons law for `govProposalsDeposits` |
| 132 | + |
| 133 | +`govProposalsDeposits`{.AgdaFunction} is a `foldl`{.AgdaFunction} that adds the |
| 134 | +(constant) `govActionDeposit`{.AgdaField} to its accumulator once per proposal. |
| 135 | +The `GOVS`{.AgdaDatatype} induction below consumes proposals head-first, so it needs |
| 136 | +the cons law: one proposal contributes one `govActionDeposit`{.AgdaField}. Since |
| 137 | +`foldl`{.AgdaFunction} buries the head's contribution in the accumulator, the |
| 138 | +workhorse is the accumulator-shift lemma |
| 139 | +`govProposalsDeposits-acc`{.AgdaFunction}; the cons law is its direct corollary. |
| 140 | +(The `GOVS`{.AgdaDatatype} induction uses the shift lemma directly: the fold |
| 141 | +discards the list elements, so after reduction the head proposal never appears in |
| 142 | +the equation.) |
| 143 | + |
| 144 | +```agda |
| 145 | +govProposalsDeposits-acc : ∀ (pp : PParams) (acc : Coin) (ps : List GovProposal) |
| 146 | + → foldl (λ a _ → a + PParams.govActionDeposit pp) acc ps |
| 147 | + ≡ acc + govProposalsDeposits pp ps |
| 148 | +govProposalsDeposits-acc pp acc [] = sym (+-identityʳ acc) |
| 149 | +govProposalsDeposits-acc pp acc (_ ∷ ps) = |
| 150 | + trans (govProposalsDeposits-acc pp (acc + d) ps) |
| 151 | + (trans (+-assoc acc d (govProposalsDeposits pp ps)) |
| 152 | + (cong (acc +_) (sym (govProposalsDeposits-acc pp d ps)))) |
| 153 | + where d = PParams.govActionDeposit pp |
| 154 | +
|
| 155 | +govProposalsDeposits-∷ : ∀ (pp : PParams) {p : GovProposal} (ps : List GovProposal) |
| 156 | + → govProposalsDeposits pp (p ∷ ps) |
| 157 | + ≡ PParams.govActionDeposit pp + govProposalsDeposits pp ps |
| 158 | +govProposalsDeposits-∷ pp = govProposalsDeposits-acc pp (PParams.govActionDeposit pp) |
| 159 | +``` |
| 160 | + |
| 161 | +## Per-step facts: `addVote` and `insertGovAction` |
| 162 | + |
| 163 | +A `GOV-Vote`{.AgdaInductiveConstructor} step concludes in `addVote`{.AgdaFunction}, |
| 164 | +which maps over the state, rewriting only the `votes`{.AgdaField} field of the |
| 165 | +matching action. Whether or not an entry matches the voted action id, its |
| 166 | +`deposit`{.AgdaField} is untouched, so `coinFromGovDeposit`{.AgdaFunction} is |
| 167 | +preserved. (`addVote`{.AgdaFunction} is `opaque`{.AgdaKeyword}, so the proof must |
| 168 | +unfold it explicitly.) |
| 169 | + |
| 170 | +```agda |
| 171 | +opaque |
| 172 | + unfolding addVote |
| 173 | +
|
| 174 | + addVote-coinFromGovDeposit : |
| 175 | + ∀ {s : GovState} {aid voter v} |
| 176 | + → coinFromGovDeposit (addVote s aid voter v) ≡ coinFromGovDeposit s |
| 177 | + addVote-coinFromGovDeposit {[]} = refl |
| 178 | + addVote-coinFromGovDeposit {(gid , gas) ∷ s} {aid} {voter} {v} |
| 179 | + with gid ≟ aid |
| 180 | + ... | yes _ = cong (GovActionState.deposit gas +_) |
| 181 | + (addVote-coinFromGovDeposit {s} {aid} {voter} {v}) |
| 182 | + ... | no _ = cong (GovActionState.deposit gas +_) |
| 183 | + (addVote-coinFromGovDeposit {s} {aid} {voter} {v}) |
| 184 | +``` |
| 185 | + |
| 186 | +A `GOV-Propose`{.AgdaInductiveConstructor} step concludes in |
| 187 | +`addAction`{.AgdaFunction}, which is `insertGovAction`{.AgdaFunction} of a fresh |
| 188 | +`GovActionState`{.AgdaRecord} pair carrying `deposit = pp .govActionDeposit`. |
| 189 | +`insertGovAction`{.AgdaFunction} is a priority-ordered insertion; wherever the new |
| 190 | +pair lands, `coinFromGovDeposit`{.AgdaFunction} grows by exactly its deposit. The |
| 191 | +proof is a list induction with a case split on the priority comparison: in the |
| 192 | +*then* branch the new pair sinks past the head, so the head's deposit and the |
| 193 | +inserted deposit swap places (`swap-head`{.AgdaFunction}); in the *else* branch the |
| 194 | +list is extended in place and both sides agree definitionally. |
| 195 | + |
| 196 | +```agda |
| 197 | +insertGovAction-coinFromGovDeposit : |
| 198 | + ∀ {s : GovState} {p : GovActionID × GovActionState} |
| 199 | + → coinFromGovDeposit (insertGovAction s p) |
| 200 | + ≡ GovActionState.deposit (proj₂ p) + coinFromGovDeposit s |
| 201 | +insertGovAction-coinFromGovDeposit {[]} {_} = refl |
| 202 | +insertGovAction-coinFromGovDeposit {(gaID₀ , gaSt₀) ∷ s} {gaID₁ , gaSt₁} |
| 203 | + with govActionPriority (GovActionTypeOf gaSt₀) |
| 204 | + ≤? govActionPriority (GovActionTypeOf gaSt₁) |
| 205 | +... | yes _ = |
| 206 | + trans (cong (GovActionState.deposit gaSt₀ +_) |
| 207 | + (insertGovAction-coinFromGovDeposit {s} {gaID₁ , gaSt₁})) |
| 208 | + (swap-head (GovActionState.deposit gaSt₀) |
| 209 | + (GovActionState.deposit gaSt₁) |
| 210 | + (coinFromGovDeposit s)) |
| 211 | +... | no _ = refl |
| 212 | +``` |
| 213 | + |
| 214 | +## `GOVS` accounting |
| 215 | + |
| 216 | +`GOVS`{.AgdaDatatype} is the *indexed* reflexive-transitive closure of |
| 217 | +`GOV`{.AgdaDatatype}: the convenience wrapper fixes the index at `0`, but |
| 218 | +`BS-ind`{.AgdaInductiveConstructor}'s tail runs at `suc n`, so the induction must be |
| 219 | +stated over the primed relation `_⊢_⇀⟦_⟧ᵢ*'_`{.AgdaDatatype}, generalized over the |
| 220 | +environment-index pair, and then specialized to `0`. Per step, |
| 221 | +`GOV-Vote`{.AgdaInductiveConstructor} preserves the deposit total |
| 222 | +(`addVote-coinFromGovDeposit`{.AgdaFunction}, and its signal contributes no |
| 223 | +proposal), while `GOV-Propose`{.AgdaInductiveConstructor} adds one |
| 224 | +`govActionDeposit`{.AgdaField} on both sides |
| 225 | +(`insertGovAction-coinFromGovDeposit`{.AgdaFunction} on the state side, |
| 226 | +`govProposalsDeposits-∷`{.AgdaFunction} on the accounting side). |
| 227 | + |
| 228 | +```agda |
| 229 | +GOVSᵢ-coinFromGovDeposit : |
| 230 | + ∀ {Γ : GovEnv} {n : ℕ} {s s' : GovState} {sigs : List (GovVote ⊎ GovProposal)} |
| 231 | + → _⊢_⇀⟦_⟧ᵢ*'_ {_⊢_⇀⟦_⟧ᵇ_ = IdSTS} {_⊢_⇀⦇_,GOV⦈_} (Γ , n) s sigs s' |
| 232 | + → coinFromGovDeposit s' |
| 233 | + ≡ coinFromGovDeposit s + govProposalsDeposits (PParamsOf Γ) (proposalsOf sigs) |
| 234 | +
|
| 235 | +GOVSᵢ-coinFromGovDeposit (BS-base Id-nop) = sym (+-identityʳ _) |
| 236 | +
|
| 237 | +GOVSᵢ-coinFromGovDeposit {Γ} (BS-ind {sigs = sigs} (GOV-Vote _) rest) = |
| 238 | + trans (GOVSᵢ-coinFromGovDeposit rest) |
| 239 | + (cong (_+ govProposalsDeposits (PParamsOf Γ) (proposalsOf sigs)) |
| 240 | + addVote-coinFromGovDeposit) |
| 241 | +
|
| 242 | +GOVSᵢ-coinFromGovDeposit {Γ} {s = s} (BS-ind {sigs = sigs} (GOV-Propose _) rest) = |
| 243 | + trans (GOVSᵢ-coinFromGovDeposit rest) |
| 244 | + (trans (cong (_+ X) (insertGovAction-coinFromGovDeposit {s = s})) |
| 245 | + (trans (rotate-in dep (coinFromGovDeposit s) X) |
| 246 | + (cong (coinFromGovDeposit s +_) |
| 247 | + (sym (govProposalsDeposits-acc (PParamsOf Γ) dep (proposalsOf sigs)))))) |
| 248 | + where |
| 249 | + X = govProposalsDeposits (PParamsOf Γ) (proposalsOf sigs) |
| 250 | + dep = PParams.govActionDeposit (PParamsOf Γ) |
| 251 | +``` |
| 252 | + |
| 253 | +Specializing to index `0` gives the statement in the shape of the |
| 254 | +`LEDGER-PoV`{.AgdaModule} module parameter: |
| 255 | + |
| 256 | +```agda |
| 257 | +GOVS-coinFromGovDeposit : |
| 258 | + ∀ {Γ : GovEnv} {govSt govSt′ : GovState} {props : List (GovVote ⊎ GovProposal)} |
| 259 | + → Γ ⊢ govSt ⇀⦇ props ,GOVS⦈ govSt′ |
| 260 | + → coinFromGovDeposit govSt′ |
| 261 | + ≡ coinFromGovDeposit govSt + govProposalsDeposits (PParamsOf Γ) (proposalsOf props) |
| 262 | +GOVS-coinFromGovDeposit = GOVSᵢ-coinFromGovDeposit |
| 263 | +``` |
0 commit comments