1use 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#[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#[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#[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#[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#[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#[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#[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}