Skip to content

Commit 24336d9

Browse files
committed
Restate #1233 as a CERTS invariant after the removal of POST-CERT
Master removed the POST-CERT rule; delegated voting stake is now dropped by GOVCERT at the moment a DRep is deregistered. POST-CERT used to corestrict voteDelegs to the active VDelegs at the end of every batch, which is what made the property an unconditional postcondition of CERTS and its proof a one-liner. That sweep is gone, so an arbitrary input state may already violate the containment and nothing in a CERTS step would repair it. Restate the property as a genuine CERTS invariant and prove it: CERTS-voteDelegsVDeleg : LedgerInvariant _|-_->(_,CERTS)_ voteDelegsVDeleg The containment is now maintained incrementally by the two rules that could break it. DELEG-delegate may install only a VDeleg that is already active for the current delegatees, and GOVCERT-deregdrep, the one rule that shrinks the registered DReps, deletes every delegation to the credential it deregisters in the same step. The remaining rules either leave both fields alone or only grow the DRep domain, for which activeVDelegs is monotone. The proof establishes invariance for DELEG, GOVCERT, CERT and PRE-CERT, then lifts the CERT lemma along RTC-preserves-inv. In Axiom.Set.Map.Extra, drop cores-range-|_ (nothing corestricts any more) and add what the new proof needs: coex-|-in, for reading a pair back out of a complement corestriction, and dom-cup-l-supset-r with its corollaries dom-insert-supset and dom-mapValueRestricted-supset, for the two left-biased unions that refresh and extend the DReps. Each takes the map whose keys are preserved explicitly, since it sits under proj1 and unification cannot recover it. Also restate the prose entry in Conway/Specification/Properties.lagda.md and restore the changelog line dropped in an earlier rebase. AI-assisted development: Claude Opus 5 (Anthropic)
1 parent 3d85dbc commit 24336d9

3 files changed

Lines changed: 202 additions & 60 deletions

File tree

src-lib-exts/abstract-set-theory/Axiom/Set/Map/Extra.agda

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -705,14 +705,37 @@ module _ {A B : Type}
705705

706706
-- Corestriction
707707

708-
-- Corestricting a map to a set `X` of values confines its range to `X`.
709-
-- Indeed, `(m ∣^ X) ˢ` is `m ˢ` filtered by the predicate `(_∈ X) ∘ proj₂`, so every
710-
-- pair surviving the filter carries a proof that its value belongs to `X`.
708+
-- Corestricting a map away from a set `X` of values: `(m ∣^ X ᶜ) ˢ` is `m ˢ` filtered
709+
-- by `(_∉ X) ∘ proj₂`, so every surviving pair is a pair of `m` whose value avoids `X`.
711710
-- The map `m` is an explicit argument because it cannot be recovered by unification:
712-
-- `_∣^_` goes through `⊆-map`, which mentions `m` only as `m ˢ` (i.e., `proj₁ m`).
713-
cores-range-⊆ : {A B : Type} ⦃ _ : DecEq B ⦄ (m : A ⇀ B) {X : ℙ B} range (m ∣^ X) ⊆ X
714-
cores-range-⊆ m b∈range with Equivalence.from ∈-map b∈range
715-
... | _ , refl , ab∈cores = proj₁ (Equivalence.from ∈-filter ab∈cores)
711+
-- `_∣^_ᶜ` goes through `⊆-map`, which mentions `m` only as `m ˢ` (i.e., `proj₁ m`).
712+
coex-∈⁻ : {A B : Type} ⦃ _ : DecEq B ⦄ (m : A ⇀ B) {X : ℙ B} {a : A} {b : B}
713+
(a , b) ∈ (m ∣^ X ᶜ) ˢ b ∉ X × (a , b) ∈ (m ˢ)
714+
coex-∈⁻ m = from ∈-filter
715+
716+
717+
-- A left-biased union never drops a key of its right operand. A key that `m` also
718+
-- binds is taken from `m`; a key that `m` does not bind survives the filter that
719+
-- `_∪ˡ_` applies to `m'`. Either way the key stays in the domain.
720+
dom-∪ˡ-⊇ʳ : {A B : Type} ⦃ _ : DecEq A ⦄ (m m' : A ⇀ B) dom m' ⊆ dom (m ∪ˡ m')
721+
dom-∪ˡ-⊇ʳ m m' {a} a∈dom' with a ∈? dom m
722+
... | yes a∈dom =
723+
to dom∈ ( from dom∈ a∈dom .proj₁
724+
, Properties.∈-∪⁺ (inj₁ (from dom∈ a∈dom .proj₂)))
725+
... | no a∉dom =
726+
to dom∈ ( from dom∈ a∈dom' .proj₁
727+
, Properties.∈-∪⁺ (inj₂ (to ∈-filter (a∉dom , from dom∈ a∈dom' .proj₂))))
728+
729+
-- Two consequences, phrased so that the map whose keys are preserved is the explicit
730+
-- argument: it is the one a caller can name, whereas the overriding map generally is
731+
-- not (it sits under `proj₁`, so unification cannot recover it from the goal).
732+
dom-insert-⊇ : {A B : Type} ⦃ _ : DecEq A ⦄ (m : A ⇀ B) {k : A} {v : B}
733+
dom m ⊆ dom (insert m k v)
734+
dom-insert-⊇ m {k} {v} = dom-∪ˡ-⊇ʳ ❴ k , v ❵ m
735+
736+
dom-mapValueRestricted-⊇ : {A B : Type} ⦃ _ : DecEq A ⦄ (m : A ⇀ B)
737+
{f : B B} {X : ℙ A} dom m ⊆ dom (mapValueRestricted f m X)
738+
dom-mapValueRestricted-⊇ m {f} {X} = dom-∪ˡ-⊇ʳ (mapValues f (m ∣ X)) m
716739

717740

718741
-- Map lemmas: lookup after insert

src/Ledger/Conway/Specification/Certs/Properties/VoteDelegsVDeleg.lagda.md

Lines changed: 160 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -12,91 +12,204 @@ source_path: src/Ledger/Conway/Specification/Certs/Properties/VoteDelegsVDeleg.l
1212
1313
open import Ledger.Conway.Specification.Gov.Base
1414
15-
module Ledger.Conway.Specification.Certs.Properties.VoteDelegsVDeleg (gs : _) (open GovStructure gs) where
15+
module Ledger.Conway.Specification.Certs.Properties.VoteDelegsVDeleg
16+
(gs : GovStructure) (open GovStructure gs)
17+
where
1618
1719
open import Ledger.Conway.Specification.Certs gs
1820
open import Ledger.Prelude
1921
open import Ledger.Conway.Specification.Gov.Actions gs
2022
23+
open import Data.List.Relation.Unary.Any using (here; there)
24+
2125
private variable
22-
Γ : CertEnv
23-
s s' : CertState
24-
certs : List DCert
26+
Γ : CertEnv
27+
s s' : CertState
28+
stᵈ stᵈ' : DState
29+
certs : List DCert
30+
dCert : DCert
31+
D D' : ℙ Credential
32+
m : VoteDelegs
33+
v : VDeleg
2534
```
2635
-->
2736

2837
*Informally*.
2938

30-
A `CertState`{.AgdaRecord} has a `DState`{.AgdaRecord}, a `PState`{.AgdaRecord}, and a
31-
`GState`{.AgdaRecord}. The `DState`{.AgdaRecord} contains a field
32-
`voteDelegs`{.AgdaField}, a map sending the `Credential`{.AgdaDatatype} of a delegator to
33-
the `VDeleg`{.AgdaDatatype} that receives its voting stake. The `GState`{.AgdaRecord}
34-
contains a field `dreps`{.AgdaField} whose domain is the set of registered
35-
`DReps`{.AgdaFunction}.
39+
A `CertState`{.AgdaRecord} has a `DState`{.AgdaRecord}, a `PState`{.AgdaRecord},
40+
and a `GState`{.AgdaRecord}. The `DState`{.AgdaRecord} contains a field
41+
`voteDelegs`{.AgdaField}, a map sending the `Credential`{.AgdaDatatype} of a
42+
delegator to the `VDeleg`{.AgdaDatatype} that receives its voting stake. The
43+
`GState`{.AgdaRecord} contains a field `dreps`{.AgdaField} whose domain is the set
44+
of registered `DReps`{.AgdaFunction}.
3645

3746
`VDeleg`{.AgdaDatatype} has three constructors:
3847
`vDelegCredential`{.AgdaInductiveConstructor}, which takes the
3948
`Credential`{.AgdaDatatype} of a `DRep`, and the two constants
4049
`vDelegAbstain`{.AgdaInductiveConstructor} and
41-
`vDelegNoConfidence`{.AgdaInductiveConstructor}.
50+
`vDelegNoConfidence`{.AgdaInductiveConstructor}. Call a `VDeleg`{.AgdaDatatype}
51+
*active* for a set of credentials if it is one of those two constants or if it
52+
wraps a credential from that set.
53+
54+
The property proved here asserts that no `CERTS`{.AgdaDatatype} step introduces a
55+
vote delegation that is inactive for the registered `DReps`{.AgdaFunction}: if
56+
every value of `voteDelegs`{.AgdaField} is active before the batch of
57+
certificates, then so is every value after it.
4258

43-
Call a `VDeleg`{.AgdaDatatype} *active* in a state if it is one of those two constants or
44-
if it wraps the `Credential`{.AgdaDatatype} of a `DRep` registered in that state. The
45-
present property asserts that a `CERTS`{.AgdaDatatype} step leaves no other kind of
46-
`VDeleg`{.AgdaDatatype} behind: every value of the `voteDelegs`{.AgdaField} map of the
47-
resulting state is active in that state. Nothing is assumed about the initial state,
48-
because the `POST-CERT`{.AgdaDatatype} rule concluding every `CERTS`{.AgdaDatatype} step
49-
corestricts `voteDelegs`{.AgdaField} to exactly the set of active
50-
`VDelegs`{.AgdaDatatype}.
59+
The two rules that could break this maintain it themselves, in opposite ways.
60+
`DELEG-delegate`{.AgdaInductiveConstructor} may only install a `VDeleg`{.AgdaDatatype}
61+
that is already active, and `GOVCERT-deregdrep`{.AgdaInductiveConstructor}, which
62+
*shrinks* the set of registered `DReps`{.AgdaFunction}, simultaneously deletes every
63+
delegation to the credential it deregisters.
5164

5265
*Formally*.
5366

5467
```agda
55-
activeVDelegs : CertState → ℙ VDeleg
56-
activeVDelegs s = mapˢ vDelegCredential (dom (DRepsOf s))
68+
activeVDelegs : ℙ Credential → ℙ VDeleg
69+
activeVDelegs D = mapˢ vDelegCredential D
5770
∪ fromList (vDelegNoConfidence ∷ vDelegAbstain ∷ [])
5871
5972
voteDelegsVDeleg : CertState → Type
60-
voteDelegsVDeleg s = range (VoteDelegsOf s) ⊆ activeVDelegs s
73+
voteDelegsVDeleg s = range (VoteDelegsOf s) ⊆ activeVDelegs (dom (DRepsOf s))
6174
62-
CERTS-voteDelegsVDeleg : Γ ⊢ s ⇀⦇ certs ,CERTS⦈ s' → voteDelegsVDeleg s'
75+
CERTS-voteDelegsVDeleg : LedgerInvariant _⊢_⇀⦇_,CERTS⦈_ voteDelegsVDeleg
6376
```
6477

6578
*Proof*.
6679

67-
A `CERTS`{.AgdaDatatype} step consists of a `PRE-CERT`{.AgdaDatatype} step followed by a
68-
trace of `CERT`{.AgdaDatatype} steps that ends with a `POST-CERT`{.AgdaDatatype} step, so
69-
it suffices to establish the property at the end of such a trace. We do so in two
70-
lemmas.
80+
It is convenient to read the property off one entry at a time, so we name the pointwise
81+
form and record that the two forms agree.
82+
83+
```agda
84+
vDelegsIn : ℙ Credential → VoteDelegs → Type
85+
vDelegsIn D m = ∀ {c v} → (c , v) ∈ m → v ∈ activeVDelegs D
86+
87+
⊆⇒vDelegsIn : (m : VoteDelegs) → range m ⊆ activeVDelegs D → vDelegsIn D m
88+
⊆⇒vDelegsIn _ h cv∈ = h (∈-map′ cv∈)
89+
90+
vDelegsIn⇒⊆ : (m : VoteDelegs) → vDelegsIn D m → range m ⊆ activeVDelegs D
91+
vDelegsIn⇒⊆ _ h v∈range with Equivalence.from ∈-map v∈range
92+
... | _ , refl , cv∈ = h cv∈
93+
```
94+
95+
The set of active `VDelegs`{.AgdaDatatype} grows with the set of credentials, and
96+
the two constants are active for every set.
97+
98+
```agda
99+
activeVDelegs-mono : D ⊆ D' → activeVDelegs D ⊆ activeVDelegs D'
100+
activeVDelegs-mono D⊆D' v∈ with Equivalence.from ∈-∪ v∈
101+
... | inj₂ v∈consts = Equivalence.to ∈-∪ (inj₂ v∈consts)
102+
... | inj₁ v∈creds with Equivalence.from ∈-map v∈creds
103+
... | c , refl , c∈D =
104+
Equivalence.to ∈-∪ (inj₁ (Equivalence.to ∈-map (c , refl , D⊆D' c∈D)))
105+
106+
abstain∈active : vDelegAbstain ∈ activeVDelegs D
107+
abstain∈active = Equivalence.to ∈-∪ (inj₂ (Equivalence.to ∈-fromList (there (here refl))))
108+
109+
noConfidence∈active : vDelegNoConfidence ∈ activeVDelegs D
110+
noConfidence∈active = Equivalence.to ∈-∪ (inj₂ (Equivalence.to ∈-fromList (here refl)))
111+
```
112+
113+
**Lemma (`DELEG`{.AgdaDatatype} preserves the property)**.
114+
The delegatee set is fixed throughout, so this is a statement about
115+
`voteDelegs`{.AgdaField} alone. The premise of
116+
`DELEG-delegate`{.AgdaInductiveConstructor} says precisely that the installed
117+
`VDeleg`{.AgdaDatatype} is active; `DELEG-dereg`{.AgdaInductiveConstructor} only
118+
removes entries, and `DELEG-reg`{.AgdaInductiveConstructor} leaves
119+
`voteDelegs`{.AgdaField} alone.
120+
121+
```agda
122+
delegatee∈active :
123+
just v ∈ mapˢ (just ∘ vDelegCredential) D
124+
∪ fromList (nothing ∷ just vDelegAbstain ∷ just vDelegNoConfidence ∷ [])
125+
→ v ∈ activeVDelegs D
126+
delegatee∈active mvd∈ with Equivalence.from ∈-∪ mvd∈
127+
... | inj₁ ∈creds with Equivalence.from ∈-map ∈creds
128+
... | c , refl , c∈D = Equivalence.to ∈-∪ (inj₁ (Equivalence.to ∈-map (c , refl , c∈D)))
129+
delegatee∈active mvd∈ | inj₂ ∈consts with Equivalence.from ∈-fromList ∈consts
130+
... | there (here refl) = abstain∈active
131+
... | there (there (here refl)) = noConfidence∈active
132+
```
133+
134+
```agda
135+
DELEG-vDelegsIn : ∀ {pp : PParams} {pools : Pools}
136+
→ ⟦ pp , pools , D ⟧ ⊢ stᵈ ⇀⦇ dCert ,DELEG⦈ stᵈ'
137+
→ vDelegsIn D (VoteDelegsOf stᵈ) → vDelegsIn D (VoteDelegsOf stᵈ')
138+
DELEG-vDelegsIn (DELEG-delegate {mvd = nothing} _) h = h
139+
DELEG-vDelegsIn (DELEG-delegate {mvd = just _} (_ , _ , mvd∈ , _)) h cv∈
140+
with Properties.∈-∪⁻ cv∈
141+
... | inj₂ cv∈rest = h (proj₂ (Equivalence.from ∈-filter cv∈rest))
142+
... | inj₁ cv∈new =
143+
subst (_∈ activeVDelegs _)
144+
(sym (cong proj₂ (Equivalence.from ∈-singleton cv∈new)))
145+
(delegatee∈active mvd∈)
146+
DELEG-vDelegsIn (DELEG-dereg _) h cv∈ = h (ex-⊆ cv∈)
147+
DELEG-vDelegsIn (DELEG-reg _) h = h
148+
```
71149

72-
**Lemma (`POST-CERT`{.AgdaDatatype} establishes the property).** The single constructor
73-
`CERT-post`{.AgdaInductiveConstructor} reveals the `voteDelegs`{.AgdaField} of its
74-
resulting state to be a corestriction `vd`{.AgdaBound} `∣^`{.AgdaOperator}
75-
`X`{.AgdaBound} of the incoming map `vd`{.AgdaBound}, and the range of a corestricted map
76-
is contained in the corestricting set (`cores-range-⊆`{.AgdaFunction}). The set
77-
`X`{.AgdaBound} is read off the `GState`{.AgdaRecord}, which the rule leaves untouched, so
78-
`X`{.AgdaBound} is `activeVDelegs`{.AgdaFunction} of the resulting state.
150+
**Lemma (`GOVCERT`{.AgdaDatatype} preserves the property)**.
151+
`GOVCERT-regdrep`{.AgdaInductiveConstructor} only grows the domain of
152+
`dreps`{.AgdaField}, so `activeVDelegs`{.AgdaFunction} only grows;
153+
`GOVCERT-ccreghot`{.AgdaInductiveConstructor} touches neither field. In the
154+
`GOVCERT-deregdrep`{.AgdaInductiveConstructor} case a value `v`{.AgdaBound} of the
155+
resulting map comes from the incoming map and, by the corestriction, differs from
156+
`vDelegCredential`{.AgdaInductiveConstructor} `c`{.AgdaBound}. If `v`{.AgdaBound}
157+
is one of the two constants it stays active; otherwise `v`{.AgdaBound} is
158+
`vDelegCredential`{.AgdaInductiveConstructor} `c'`{.AgdaBound} for some registered
159+
`c'`{.AgdaBound}, and `c'`{.AgdaBound} ``{.AgdaFunction} `c`{.AgdaBound}, so
160+
`c'`{.AgdaBound} is still registered after the deregistration.
79161

80162
```agda
81-
POST-CERT-voteDelegsVDeleg : Γ ⊢ s ⇀⦇ _ ,POST-CERT⦈ s' → voteDelegsVDeleg s'
82-
POST-CERT-voteDelegsVDeleg (CERT-post {voteDelegs = vd}) = cores-range-⊆ vd
163+
GOVCERT-voteDelegsVDeleg : LedgerInvariant _⊢_⇀⦇_,GOVCERT⦈_ voteDelegsVDeleg
164+
GOVCERT-voteDelegsVDeleg (GOVCERT-regdrep {dReps = dReps} _) h =
165+
activeVDelegs-mono (dom-insert-⊇ dReps) ∘ h
166+
GOVCERT-voteDelegsVDeleg (GOVCERT-ccreghot _) h = h
167+
GOVCERT-voteDelegsVDeleg (GOVCERT-deregdrep {c = c} {dReps = dReps} {vDelegs = vDelegs} _) h =
168+
vDelegsIn⇒⊆ (vDelegs ∣^ ❴ vDelegCredential c ❵ ᶜ) λ cv∈ →
169+
let v∉ , cv∈vd = coex-∈⁻ vDelegs cv∈ in
170+
reinstate v∉ (⊆⇒vDelegsIn vDelegs h cv∈vd)
171+
where
172+
-- A delegation to `c'` survives the deregistration of `c` because `c' ≢ c`: were they
173+
-- equal, `v` would be the very `vDelegCredential c` the corestriction ruled out.
174+
keep : ∀ {v c'} → v ∉ ❴ vDelegCredential c ❵ → v ≡ vDelegCredential c'
175+
→ c' ∈ dom dReps → v ∈ activeVDelegs (dom (dReps ∣ ❴ c ❵ ᶜ))
176+
keep {c' = c'} v∉ v≡ c'∈dom = Equivalence.to ∈-∪ (inj₁ (Equivalence.to ∈-map
177+
( c' , v≡
178+
, ∈-resᶜ-dom⁺ ( (λ c'∈ → v∉ (Equivalence.to ∈-singleton (trans v≡
179+
(cong vDelegCredential (Equivalence.from ∈-singleton c'∈)))))
180+
, Equivalence.from dom∈ c'∈dom ) )))
181+
182+
reinstate : ∀ {v} → v ∉ ❴ vDelegCredential c ❵ → v ∈ activeVDelegs (dom dReps)
183+
→ v ∈ activeVDelegs (dom (dReps ∣ ❴ c ❵ ᶜ))
184+
reinstate v∉ v∈ with Equivalence.from ∈-∪ v∈
185+
... | inj₂ v∈consts = Equivalence.to ∈-∪ (inj₂ v∈consts)
186+
... | inj₁ v∈creds =
187+
let c' , v≡ , c'∈dom = Equivalence.from ∈-map v∈creds in keep v∉ v≡ c'∈dom
83188
```
84189

85-
**Lemma (the property holds at the end of a `CERT`{.AgdaDatatype} trace).** Induct on the
86-
trace. The intermediate `CERT`{.AgdaDatatype} steps are irrelevant: only the final state
87-
is constrained, and it is produced by the `POST-CERT`{.AgdaDatatype} step in the
88-
`run-[]`{.AgdaInductiveConstructor} case.
190+
**Lemma (`CERT`{.AgdaDatatype} and `PRE-CERT`{.AgdaDatatype} preserve the property)**.
191+
`CERT-pool`{.AgdaInductiveConstructor} touches neither field, and
192+
`CERT-pre`{.AgdaInductiveConstructor} leaves `voteDelegs`{.AgdaField} alone while
193+
refreshing `dreps`{.AgdaField} with a left-biased union that keeps every key.
89194

90195
```agda
91-
CERT-trace-voteDelegsVDeleg :
92-
RunTraceAndThen _⊢_⇀⦇_,CERT⦈_ _⊢_⇀⦇_,POST-CERT⦈_ Γ s certs s' → voteDelegsVDeleg s'
93-
CERT-trace-voteDelegsVDeleg (run-[] post) = POST-CERT-voteDelegsVDeleg post
94-
CERT-trace-voteDelegsVDeleg (run-∷ _ trace) = CERT-trace-voteDelegsVDeleg trace
196+
CERT-voteDelegsVDeleg : LedgerInvariant _⊢_⇀⦇_,CERT⦈_ voteDelegsVDeleg
197+
CERT-voteDelegsVDeleg (CERT-deleg {stᵈ = stᵈ} {stᵈ' = stᵈ'} deleg) h =
198+
vDelegsIn⇒⊆ (VoteDelegsOf stᵈ')
199+
(DELEG-vDelegsIn deleg (⊆⇒vDelegsIn (VoteDelegsOf stᵈ) h))
200+
CERT-voteDelegsVDeleg (CERT-pool _) h = h
201+
CERT-voteDelegsVDeleg (CERT-vdel govcert) h = GOVCERT-voteDelegsVDeleg govcert h
202+
203+
PRE-CERT-voteDelegsVDeleg : LedgerInvariant _⊢_⇀⦇_,PRE-CERT⦈_ voteDelegsVDeleg
204+
PRE-CERT-voteDelegsVDeleg (CERT-pre {dReps = dReps} _) h =
205+
activeVDelegs-mono (dom-mapValueRestricted-⊇ dReps) ∘ h
95206
```
96207

97-
The theorem follows by inverting the `CERTS`{.AgdaDatatype} step and discarding its
98-
`PRE-CERT`{.AgdaDatatype} component.
208+
A `CERTS`{.AgdaDatatype} step is a `PRE-CERT`{.AgdaDatatype} step followed by a
209+
trace of `CERT`{.AgdaDatatype} steps, so the theorem follows by lifting the two
210+
lemmas along the reflexive-transitive closure.
99211

100212
```agda
101-
CERTS-voteDelegsVDeleg (run (_ , trace)) = CERT-trace-voteDelegsVDeleg trace
213+
CERTS-voteDelegsVDeleg (run (pre , trace)) =
214+
RTC-preserves-inv CERT-voteDelegsVDeleg trace ∘ PRE-CERT-voteDelegsVDeleg pre
102215
```

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,19 @@ proposals in `tx`{.AgdaBound}.
165165
+ **Theorem** [Certs-VoteDelegsVDeleg][]. Vote delegations point at registered
166166
`DReps`{.AgdaFunction}.
167167

168-
Let `s`{.AgdaBound}, `s'`{.AgdaBound} be `CertStates`{.AgdaRecord} and
169-
`certs`{.AgdaBound} a list of `DCerts`{.AgdaDatatype} such that `s`{.AgdaBound}
170-
`⇀⦇`{.AgdaDatatype} `certs`{.AgdaBound} `,CERTS⦈`{.AgdaDatatype} `s'`{.AgdaBound}.
171-
Then every value of the `voteDelegs`{.AgdaField} map of `s'`{.AgdaBound} either wraps
172-
the `Credential`{.AgdaDatatype} of a `DRep` registered in `s'`{.AgdaBound}, or is
168+
Say a `CertState`{.AgdaRecord} `s`{.AgdaBound} *delegates actively* when every value
169+
of its `voteDelegs`{.AgdaField} map either wraps the `Credential`{.AgdaDatatype} of a
170+
`DRep` registered in `s`{.AgdaBound}, or is
173171
`vDelegAbstain`{.AgdaInductiveConstructor}, or is
174-
`vDelegNoConfidence`{.AgdaInductiveConstructor}.
172+
`vDelegNoConfidence`{.AgdaInductiveConstructor}. This is an invariant of the
173+
`CERTS`{.AgdaDatatype} rule: if `s`{.AgdaBound} delegates actively and `s`{.AgdaBound}
174+
`⇀⦇`{.AgdaDatatype} `certs`{.AgdaBound} `,CERTS⦈`{.AgdaDatatype} `s'`{.AgdaBound},
175+
then `s'`{.AgdaBound} delegates actively.
176+
177+
Two rules keep it so. `DELEG-delegate`{.AgdaInductiveConstructor} may install only a
178+
delegation that is already active, and
179+
`GOVCERT-deregdrep`{.AgdaInductiveConstructor}, the one rule that unregisters a
180+
`DRep`, deletes every delegation to it in the same step.
175181

176182
---
177183

0 commit comments

Comments
 (0)