Skip to main content

ave_common/
governance.rs

1//! Governance update payloads.
2//!
3//! These types model member, role, schema and policy changes applied to a
4//! governance subject. They are plain serializable data structures and are
5//! shared by the API layer, the core ledger and TypeScript exports.
6
7use std::{
8    collections::{BTreeSet, HashSet},
9    fmt,
10    hash::Hash,
11};
12
13use borsh::{BorshDeserialize, BorshSerialize};
14use serde::{Deserialize, Deserializer, Serialize, Serializer};
15use serde_json::Value;
16
17#[cfg(feature = "typescript")]
18use ts_rs::TS;
19
20use crate::identity::PublicKey;
21use crate::{Namespace, SchemaType};
22
23pub type MemberName = String;
24
25/// Governance change set grouped by concern.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[cfg_attr(feature = "typescript", derive(TS))]
28#[cfg_attr(feature = "typescript", ts(export))]
29pub struct GovernanceEvent {
30    pub members: Option<MemberEvent>,
31    pub roles: Option<RolesEvent>,
32    pub schemas: Option<SchemasEvent>,
33    pub policies: Option<PoliciesEvent>,
34}
35
36///// Members /////
37/// Member additions and removals.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[cfg_attr(feature = "typescript", derive(TS))]
40#[cfg_attr(feature = "typescript", ts(export))]
41pub struct MemberEvent {
42    pub add: Option<HashSet<NewMember>>,
43    pub remove: Option<HashSet<MemberName>>,
44}
45
46/// New member entry used in governance updates.
47#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
48#[cfg_attr(feature = "typescript", derive(TS))]
49#[cfg_attr(feature = "typescript", ts(export))]
50pub struct NewMember {
51    pub name: MemberName,
52    #[cfg_attr(feature = "typescript", ts(type = "string"))]
53    pub key: PublicKey,
54}
55
56///// Roles /////
57/// Role updates grouped by role family.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[cfg_attr(feature = "typescript", derive(TS))]
60#[cfg_attr(feature = "typescript", ts(export))]
61pub struct RolesEvent {
62    pub governance: Option<GovRoleEvent>,
63    pub tracker_schemas: Option<TrackerSchemasRoleEvent>,
64    pub schema: Option<HashSet<SchemaIdRole>>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
68#[cfg_attr(feature = "typescript", derive(TS))]
69#[cfg_attr(feature = "typescript", ts(export))]
70pub struct GovRoleEvent {
71    pub add: Option<GovRolesEvent>,
72    pub remove: Option<GovRolesEvent>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
76#[cfg_attr(feature = "typescript", derive(TS))]
77#[cfg_attr(feature = "typescript", ts(export))]
78pub struct SchemaIdRole {
79    pub schema_id: SchemaType,
80    pub add: Option<SchemaRolesAddEvent>,
81    pub remove: Option<SchemaRolesRemoveEvent>,
82    pub change: Option<SchemaRolesChangeEvent>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
86#[cfg_attr(feature = "typescript", derive(TS))]
87#[cfg_attr(feature = "typescript", ts(export))]
88pub struct TrackerSchemasRoleEvent {
89    pub add: Option<TrackerSchemasRolesAddEvent>,
90    pub remove: Option<TrackerSchemasRolesRemoveEvent>,
91    pub change: Option<TrackerSchemasRolesChangeEvent>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
95#[cfg_attr(feature = "typescript", derive(TS))]
96#[cfg_attr(feature = "typescript", ts(export))]
97pub struct GovRolesEvent {
98    pub approver: Option<BTreeSet<MemberName>>,
99    pub evaluator: Option<BTreeSet<MemberName>>,
100    pub validator: Option<BTreeSet<MemberName>>,
101    pub witness: Option<BTreeSet<MemberName>>,
102    pub issuer: Option<BTreeSet<MemberName>>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
106#[cfg_attr(feature = "typescript", derive(TS))]
107#[cfg_attr(feature = "typescript", ts(export))]
108pub struct TrackerSchemasRolesAddEvent {
109    pub evaluator: Option<BTreeSet<Role>>,
110    pub validator: Option<BTreeSet<Role>>,
111    pub witness: Option<BTreeSet<Role>>,
112    pub issuer: Option<BTreeSet<Role>>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
116#[cfg_attr(feature = "typescript", derive(TS))]
117#[cfg_attr(feature = "typescript", ts(export))]
118pub struct SchemaRolesAddEvent {
119    pub evaluator: Option<BTreeSet<Role>>,
120    pub validator: Option<BTreeSet<Role>>,
121    pub witness: Option<BTreeSet<Role>>,
122    pub creator: Option<BTreeSet<RoleCreator>>,
123    pub issuer: Option<BTreeSet<Role>>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "typescript", derive(TS))]
128#[cfg_attr(feature = "typescript", ts(export))]
129pub struct TrackerSchemasRolesRemoveEvent {
130    pub evaluator: Option<BTreeSet<Role>>,
131    pub validator: Option<BTreeSet<Role>>,
132    pub witness: Option<BTreeSet<Role>>,
133    pub issuer: Option<BTreeSet<Role>>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
137#[cfg_attr(feature = "typescript", derive(TS))]
138#[cfg_attr(feature = "typescript", ts(export))]
139pub struct SchemaRolesRemoveEvent {
140    pub evaluator: Option<BTreeSet<Role>>,
141    pub validator: Option<BTreeSet<Role>>,
142    pub witness: Option<BTreeSet<Role>>,
143    pub creator: Option<BTreeSet<Role>>,
144    pub issuer: Option<BTreeSet<Role>>,
145}
146
147#[derive(
148    Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord,
149)]
150#[cfg_attr(feature = "typescript", derive(TS))]
151#[cfg_attr(feature = "typescript", ts(export))]
152pub struct TrackerSchemasRolesChangeEvent {
153    pub evaluator: Option<BTreeSet<RoleChange>>,
154    pub validator: Option<BTreeSet<RoleChange>>,
155    pub witness: Option<BTreeSet<RoleChange>>,
156    pub issuer: Option<BTreeSet<RoleChange>>,
157}
158
159#[derive(
160    Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord,
161)]
162#[cfg_attr(feature = "typescript", derive(TS))]
163#[cfg_attr(feature = "typescript", ts(export))]
164pub struct SchemaRolesChangeEvent {
165    pub evaluator: Option<BTreeSet<RoleChange>>,
166    pub validator: Option<BTreeSet<RoleChange>>,
167    pub witness: Option<BTreeSet<RoleChange>>,
168    pub creator: Option<BTreeSet<RoleCreatorChange>>,
169    pub issuer: Option<BTreeSet<RoleChange>>,
170}
171
172#[derive(
173    Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord,
174)]
175#[cfg_attr(feature = "typescript", derive(TS))]
176#[cfg_attr(feature = "typescript", ts(export))]
177pub struct RoleCreatorChange {
178    pub actual_name: MemberName,
179    pub actual_namespace: Namespace,
180    pub new_namespace: Option<Namespace>,
181    pub new_witnesses: Option<BTreeSet<CreatorWitness>>,
182    pub new_quantity: Option<CreatorQuantity>,
183}
184
185#[derive(
186    Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord,
187)]
188#[cfg_attr(feature = "typescript", derive(TS))]
189#[cfg_attr(feature = "typescript", ts(export))]
190pub struct RoleChange {
191    pub actual_name: MemberName,
192    pub actual_namespace: Namespace,
193    pub new_namespace: Namespace,
194}
195
196///// Schemas /////
197#[derive(Debug, Clone, Serialize, Deserialize)]
198#[cfg_attr(feature = "typescript", derive(TS))]
199#[cfg_attr(feature = "typescript", ts(export))]
200pub struct SchemasEvent {
201    pub add: Option<HashSet<SchemaAdd>>,
202    pub remove: Option<HashSet<SchemaType>>,
203    pub change: Option<HashSet<SchemaChange>>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
207#[cfg_attr(feature = "typescript", derive(TS))]
208#[cfg_attr(feature = "typescript", ts(export))]
209pub struct SchemaAdd {
210    pub id: SchemaType,
211    pub contract: String,
212    pub initial_value: Value,
213    #[serde(default)]
214    pub viewpoints: Vec<String>,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)]
218#[cfg_attr(feature = "typescript", derive(TS))]
219#[cfg_attr(feature = "typescript", ts(export))]
220pub struct SchemaChange {
221    pub actual_id: SchemaType,
222    pub new_contract: Option<String>,
223    pub new_initial_value: Option<Value>,
224    #[serde(default)]
225    pub new_viewpoints: Option<Vec<String>>,
226}
227
228///// Policies /////
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[cfg_attr(feature = "typescript", derive(TS))]
231#[cfg_attr(feature = "typescript", ts(export))]
232pub struct PoliciesEvent {
233    pub governance: Option<GovPolicieEvent>,
234    pub schema: Option<HashSet<SchemaIdPolicie>>,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
238#[cfg_attr(feature = "typescript", derive(TS))]
239#[cfg_attr(feature = "typescript", ts(export))]
240pub struct SchemaIdPolicie {
241    pub schema_id: SchemaType,
242    pub change: SchemaPolicieChange,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246#[cfg_attr(feature = "typescript", derive(TS))]
247#[cfg_attr(feature = "typescript", ts(export))]
248pub struct GovPolicieEvent {
249    pub change: GovPolicieChange,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253#[cfg_attr(feature = "typescript", derive(TS))]
254#[cfg_attr(feature = "typescript", ts(export))]
255pub struct GovPolicieChange {
256    pub approve: Option<Quorum>,
257    pub evaluate: Option<Quorum>,
258    pub validate: Option<Quorum>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
262#[cfg_attr(feature = "typescript", derive(TS))]
263#[cfg_attr(feature = "typescript", ts(export))]
264pub struct SchemaPolicieChange {
265    pub evaluate: Option<Quorum>,
266    pub validate: Option<Quorum>,
267}
268
269/// Governance-wide quorum policy.
270/// Governance quorum.
271#[derive(
272    Debug,
273    Clone,
274    Default,
275    Serialize,
276    Deserialize,
277    PartialEq,
278    Hash,
279    Eq,
280    BorshDeserialize,
281    BorshSerialize,
282)]
283#[cfg_attr(feature = "typescript", derive(TS))]
284#[cfg_attr(feature = "typescript", ts(export))]
285#[serde(rename_all = "lowercase")]
286pub enum Quorum {
287    #[default]
288    Majority,
289    Fixed(u32),
290    Percentage(u8),
291}
292
293#[derive(
294    Debug,
295    Clone,
296    Eq,
297    PartialEq,
298    Hash,
299    PartialOrd,
300    Ord,
301    BorshDeserialize,
302    BorshSerialize,
303)]
304#[cfg_attr(feature = "typescript", derive(TS))]
305#[cfg_attr(feature = "typescript", ts(export, type = "number | \"infinity\""))]
306pub enum CreatorQuantity {
307    Quantity(u32),
308    Infinity,
309}
310
311impl<'de> Deserialize<'de> for CreatorQuantity {
312    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
313    where
314        D: serde::Deserializer<'de>,
315    {
316        let value = serde_json::Value::deserialize(deserializer)?;
317
318        match value {
319            serde_json::Value::String(s) if s == "infinity" => {
320                Ok(Self::Infinity)
321            }
322            serde_json::Value::Number(n) if n.is_u64() => {
323                Ok(Self::Quantity(n.as_u64().ok_or_else(|| {
324                    serde::de::Error::custom(
325                        "Quantity must be a number or 'infinity'",
326                    )
327                })? as u32))
328            }
329            _ => Err(serde::de::Error::custom(
330                "Quantity must be a number or 'infinity'",
331            )),
332        }
333    }
334}
335
336impl Serialize for CreatorQuantity {
337    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
338    where
339        S: Serializer,
340    {
341        match self {
342            Self::Quantity(n) => serializer.serialize_u32(*n),
343            Self::Infinity => serializer.serialize_str("infinity"),
344        }
345    }
346}
347
348impl CreatorQuantity {
349    pub const fn check(&self) -> bool {
350        match self {
351            Self::Quantity(quantity) => *quantity != 0,
352            Self::Infinity => true,
353        }
354    }
355}
356
357#[derive(
358    Debug,
359    Serialize,
360    Deserialize,
361    Clone,
362    PartialEq,
363    Hash,
364    Eq,
365    PartialOrd,
366    Ord,
367    BorshDeserialize,
368    BorshSerialize,
369)]
370#[cfg_attr(feature = "typescript", derive(TS))]
371#[cfg_attr(feature = "typescript", ts(export))]
372pub struct Role {
373    pub name: String,
374    pub namespace: Namespace,
375}
376
377#[derive(
378    Debug,
379    Serialize,
380    Clone,
381    PartialEq,
382    Hash,
383    Eq,
384    PartialOrd,
385    Ord,
386    BorshDeserialize,
387    BorshSerialize,
388)]
389#[cfg_attr(feature = "typescript", derive(TS))]
390#[cfg_attr(feature = "typescript", ts(export))]
391pub struct CreatorWitness {
392    pub name: String,
393    #[cfg_attr(feature = "typescript", ts(type = "string[] | undefined"))]
394    pub viewpoints: BTreeSet<String>,
395}
396
397#[derive(Debug, Default, Clone, PartialEq, Eq)]
398struct UniqueViewpoints(BTreeSet<String>);
399
400impl From<UniqueViewpoints> for BTreeSet<String> {
401    fn from(value: UniqueViewpoints) -> Self {
402        value.0
403    }
404}
405
406impl<'de> Deserialize<'de> for UniqueViewpoints {
407    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
408    where
409        D: Deserializer<'de>,
410    {
411        let viewpoints =
412            <Vec<String> as serde::Deserialize>::deserialize(deserializer)?;
413        let mut unique = BTreeSet::new();
414
415        for viewpoint in viewpoints {
416            if !unique.insert(viewpoint.clone()) {
417                return Err(serde::de::Error::custom(format!(
418                    "duplicated viewpoint '{viewpoint}'"
419                )));
420            }
421        }
422
423        Ok(Self(unique))
424    }
425}
426
427#[derive(Deserialize)]
428struct CreatorWitnessDef {
429    name: String,
430    #[serde(default)]
431    viewpoints: UniqueViewpoints,
432}
433
434impl<'de> Deserialize<'de> for CreatorWitness {
435    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
436    where
437        D: Deserializer<'de>,
438    {
439        let CreatorWitnessDef { name, viewpoints } =
440            CreatorWitnessDef::deserialize(deserializer)?;
441
442        Ok(Self {
443            name,
444            viewpoints: viewpoints.into(),
445        })
446    }
447}
448
449fn default_creator_witnesses() -> BTreeSet<CreatorWitness> {
450    BTreeSet::from([CreatorWitness {
451        name: "Witnesses".to_owned(),
452        viewpoints: BTreeSet::from(["AllViewpoints".to_owned()]),
453    }])
454}
455
456#[derive(Deserialize)]
457#[serde(untagged)]
458enum CreatorWitnessInput {
459    Name(String),
460    Detailed(CreatorWitness),
461}
462
463#[derive(Debug, Clone, PartialEq, Eq)]
464struct CreatorWitnesses(BTreeSet<CreatorWitness>);
465
466impl From<CreatorWitnesses> for BTreeSet<CreatorWitness> {
467    fn from(value: CreatorWitnesses) -> Self {
468        value.0
469    }
470}
471
472impl<'de> Deserialize<'de> for CreatorWitnesses {
473    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
474    where
475        D: Deserializer<'de>,
476    {
477        let values =
478            <Vec<CreatorWitnessInput> as serde::Deserialize>::deserialize(
479                deserializer,
480            )?;
481        let mut by_name = HashSet::new();
482        let mut out = BTreeSet::new();
483
484        for value in values {
485            let witness = match value {
486                CreatorWitnessInput::Name(name) => CreatorWitness {
487                    name,
488                    viewpoints: BTreeSet::new(),
489                },
490                CreatorWitnessInput::Detailed(witness) => witness,
491            };
492
493            if !by_name.insert(witness.name.clone()) {
494                return Err(serde::de::Error::custom(format!(
495                    "duplicated creator witness '{}'",
496                    witness.name
497                )));
498            }
499
500            out.insert(witness);
501        }
502
503        Ok(Self(out))
504    }
505}
506
507#[derive(Debug, Serialize, Clone, BorshDeserialize, BorshSerialize)]
508#[cfg_attr(feature = "typescript", derive(TS))]
509#[cfg_attr(feature = "typescript", ts(export))]
510pub struct RoleCreator {
511    pub name: String,
512    pub namespace: Namespace,
513    #[cfg_attr(
514        feature = "typescript",
515        ts(type = "(string | CreatorWitness)[] | undefined")
516    )]
517    pub witnesses: BTreeSet<CreatorWitness>,
518    pub quantity: CreatorQuantity,
519}
520
521#[derive(Deserialize)]
522struct RoleCreatorDef {
523    name: String,
524    pub namespace: Namespace,
525    #[serde(default)]
526    witnesses: Option<CreatorWitnesses>,
527    pub quantity: CreatorQuantity,
528}
529
530impl<'de> Deserialize<'de> for RoleCreator {
531    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
532    where
533        D: Deserializer<'de>,
534    {
535        let RoleCreatorDef {
536            name,
537            namespace,
538            witnesses,
539            quantity,
540        } = RoleCreatorDef::deserialize(deserializer)?;
541
542        let witnesses = witnesses
543            .map_or_else(default_creator_witnesses, |values| values.into());
544
545        Ok(Self {
546            name,
547            namespace,
548            witnesses,
549            quantity,
550        })
551    }
552}
553
554impl Hash for RoleCreator {
555    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
556        self.name.hash(state);
557        self.namespace.hash(state);
558    }
559}
560
561impl PartialOrd for RoleCreator {
562    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
563        Some(self.cmp(other))
564    }
565}
566
567impl Ord for RoleCreator {
568    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
569        (self.name.clone(), self.namespace.clone())
570            .cmp(&(other.name.clone(), other.namespace.clone()))
571    }
572}
573
574impl PartialEq for RoleCreator {
575    fn eq(&self, other: &Self) -> bool {
576        self.name == other.name && self.namespace == other.namespace
577    }
578}
579
580impl Eq for RoleCreator {}
581
582impl RoleCreator {
583    pub fn create(name: &str, namespace: Namespace) -> Self {
584        Self {
585            name: name.to_owned(),
586            namespace,
587            witnesses: default_creator_witnesses(),
588            quantity: CreatorQuantity::Infinity,
589        }
590    }
591}
592
593impl Quorum {
594    pub fn check_values(&self) -> Result<(), String> {
595        if let Self::Percentage(percentage) = self
596            && (*percentage == 0_u8 || *percentage > 100_u8)
597        {
598            return Err("the percentage must be between 1 and 100".to_owned());
599        }
600
601        Ok(())
602    }
603
604    pub fn get_signers(&self, total_members: u32, pending: u32) -> u32 {
605        let signers = match self {
606            Self::Fixed(fixed) => {
607                let min = std::cmp::min(fixed, &total_members);
608                *min
609            }
610            Self::Majority => total_members / 2 + 1,
611            Self::Percentage(percentage) => {
612                total_members * (percentage / 100) as u32
613            }
614        };
615
616        std::cmp::min(signers, pending)
617    }
618
619    pub fn check_quorum(&self, total_members: u32, signers: u32) -> bool {
620        match self {
621            Self::Fixed(fixed) => {
622                let min = std::cmp::min(fixed, &total_members);
623                signers >= *min
624            }
625            Self::Majority => signers > total_members / 2,
626            Self::Percentage(percentage) => {
627                signers >= (total_members * (percentage / 100) as u32)
628            }
629        }
630    }
631}
632
633#[cfg(test)]
634mod tests {
635    use super::{CreatorWitness, RoleCreator, SchemaAdd, SchemaChange};
636    use serde_json::json;
637    use std::collections::BTreeSet;
638
639    #[test]
640    fn test_schema_add_allows_duplicated_viewpoints_deserialization() {
641        let schema = serde_json::from_value::<SchemaAdd>(json!({
642            "id": "Example",
643            "contract": "contract",
644            "initial_value": {},
645            "viewpoints": ["agua", "agua"]
646        }))
647        .unwrap();
648
649        assert_eq!(
650            schema.viewpoints,
651            vec!["agua".to_owned(), "agua".to_owned()]
652        );
653    }
654
655    #[test]
656    fn test_schema_change_allows_duplicated_viewpoints_deserialization() {
657        let change = serde_json::from_value::<SchemaChange>(json!({
658            "actual_id": "Example",
659            "new_viewpoints": ["agua", "agua"]
660        }))
661        .unwrap();
662
663        assert_eq!(
664            change.new_viewpoints,
665            Some(vec!["agua".to_owned(), "agua".to_owned()])
666        );
667    }
668
669    #[test]
670    fn test_creator_witness_rejects_duplicated_viewpoints() {
671        let error = serde_json::from_value::<CreatorWitness>(json!({
672            "name": "pepito",
673            "viewpoints": ["agua", "agua"]
674        }))
675        .unwrap_err();
676
677        assert!(error.to_string().contains("duplicated viewpoint"));
678    }
679
680    #[test]
681    fn test_role_creator_defaults_to_generic_all_viewpoints() {
682        let creator = serde_json::from_value::<RoleCreator>(json!({
683            "name": "Owner",
684            "namespace": [],
685            "quantity": 1
686        }))
687        .unwrap();
688
689        assert_eq!(
690            creator.witnesses,
691            BTreeSet::from([CreatorWitness {
692                name: "Witnesses".to_owned(),
693                viewpoints: BTreeSet::from(["AllViewpoints".to_owned()]),
694            }])
695        );
696    }
697
698    #[test]
699    fn test_role_creator_allows_legacy_string_witnesses() {
700        let creator = serde_json::from_value::<RoleCreator>(json!({
701            "name": "Owner",
702            "namespace": [],
703            "witnesses": ["Alice"],
704            "quantity": 1
705        }))
706        .unwrap();
707
708        assert_eq!(
709            creator.witnesses,
710            BTreeSet::from([CreatorWitness {
711                name: "Alice".to_owned(),
712                viewpoints: BTreeSet::new(),
713            }])
714        );
715    }
716}
717
718#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
719#[cfg_attr(feature = "typescript", derive(TS))]
720#[cfg_attr(feature = "typescript", ts(export))]
721pub struct Member {
722    #[cfg_attr(feature = "typescript", ts(type = "string"))]
723    pub id: PublicKey,
724    pub name: String,
725}
726
727#[derive(Debug, Serialize, Deserialize, Clone)]
728#[cfg_attr(feature = "typescript", derive(TS))]
729#[cfg_attr(feature = "typescript", ts(export))]
730pub enum ProtocolTypes {
731    Approval,
732    Evaluation,
733    Validation,
734}
735
736impl fmt::Display for ProtocolTypes {
737    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738        match self {
739            Self::Approval => write!(f, "Approval"),
740            Self::Evaluation => write!(f, "Evaluation"),
741            Self::Validation => write!(f, "Validation"),
742        }
743    }
744}