Skip to main content

dpp/voting/contender_structs/contender/
mod.rs

1pub mod v0;
2
3use crate::data_contract::document_type::DocumentTypeRef;
4use crate::data_contract::DataContract;
5use crate::document::Document;
6#[cfg(feature = "json-conversion")]
7use crate::serialization::JsonConvertible;
8#[cfg(feature = "value-conversion")]
9use crate::serialization::ValueConvertible;
10use crate::serialization::{PlatformDeserializableUntrusted, PlatformSerializable};
11use crate::voting::contender_structs::contender::v0::ContenderV0;
12use crate::voting::contender_structs::ContenderWithSerializedDocumentV0;
13use crate::ProtocolError;
14use bincode::{Decode, DecodeUntrusted, Encode};
15use derive_more::From;
16use platform_serialization_derive::{
17    PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize,
18};
19use platform_value::Identifier;
20use platform_version::version::PlatformVersion;
21
22/// Represents a contender in the contested document vote poll.
23///
24/// This struct holds the identity ID of the contender, the serialized document,
25/// and the vote tally.
26#[derive(Debug, PartialEq, Clone, From)]
27pub enum Contender {
28    /// V0
29    V0(ContenderV0),
30}
31
32/// Represents a contender in the contested document vote poll.
33/// This is for internal use where the document is in serialized form
34///
35/// This struct holds the identity ID of the contender, the serialized document,
36/// and the vote tally.
37#[cfg_attr(
38    all(feature = "json-conversion", feature = "serde-conversion"),
39    derive(JsonConvertible)
40)]
41#[derive(
42    Debug,
43    PartialEq,
44    Eq,
45    Clone,
46    From,
47    Encode,
48    Decode,
49    PlatformSerialize,
50    PlatformDeserializeTrusted,
51    PlatformDeserializeUntrusted,
52    DecodeUntrusted,
53)]
54#[cfg_attr(
55    feature = "serde-conversion",
56    derive(serde::Serialize, serde::Deserialize),
57    serde(tag = "$formatVersion")
58)]
59#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
60#[platform_serialize(unversioned)]
61pub enum ContenderWithSerializedDocument {
62    /// V0
63    #[cfg_attr(feature = "serde-conversion", serde(rename = "0"))]
64    V0(ContenderWithSerializedDocumentV0),
65}
66
67impl Contender {
68    pub fn identity_id(&self) -> Identifier {
69        match self {
70            Contender::V0(v0) => v0.identity_id,
71        }
72    }
73
74    pub fn identity_id_ref(&self) -> &Identifier {
75        match self {
76            Contender::V0(v0) => &v0.identity_id,
77        }
78    }
79
80    pub fn document(&self) -> &Option<Document> {
81        match self {
82            Contender::V0(v0) => &v0.document,
83        }
84    }
85
86    pub fn take_document(&mut self) -> Option<Document> {
87        match self {
88            Contender::V0(v0) => v0.document.take(),
89        }
90    }
91
92    pub fn vote_tally(&self) -> Option<u32> {
93        match self {
94            Contender::V0(v0) => v0.vote_tally,
95        }
96    }
97}
98
99impl ContenderWithSerializedDocument {
100    pub fn identity_id(&self) -> Identifier {
101        match self {
102            ContenderWithSerializedDocument::V0(v0) => v0.identity_id,
103        }
104    }
105
106    pub fn identity_id_ref(&self) -> &Identifier {
107        match self {
108            ContenderWithSerializedDocument::V0(v0) => &v0.identity_id,
109        }
110    }
111
112    pub fn serialized_document(&self) -> &Option<Vec<u8>> {
113        match self {
114            ContenderWithSerializedDocument::V0(v0) => &v0.serialized_document,
115        }
116    }
117
118    pub fn take_serialized_document(&mut self) -> Option<Vec<u8>> {
119        match self {
120            ContenderWithSerializedDocument::V0(v0) => v0.serialized_document.take(),
121        }
122    }
123
124    pub fn vote_tally(&self) -> Option<u32> {
125        match self {
126            ContenderWithSerializedDocument::V0(v0) => v0.vote_tally,
127        }
128    }
129}
130
131impl ContenderWithSerializedDocument {
132    pub fn try_into_contender(
133        self,
134        document_type_ref: DocumentTypeRef,
135        platform_version: &PlatformVersion,
136    ) -> Result<Contender, ProtocolError> {
137        match self {
138            ContenderWithSerializedDocument::V0(v0) => Ok(v0
139                .try_into_contender(document_type_ref, platform_version)?
140                .into()),
141        }
142    }
143
144    pub fn try_to_contender(
145        &self,
146        document_type_ref: DocumentTypeRef,
147        platform_version: &PlatformVersion,
148    ) -> Result<Contender, ProtocolError> {
149        match self {
150            ContenderWithSerializedDocument::V0(v0) => Ok(v0
151                .try_to_contender(document_type_ref, platform_version)?
152                .into()),
153        }
154    }
155}
156
157impl Contender {
158    pub fn try_into_contender_with_serialized_document(
159        self,
160        document_type_ref: DocumentTypeRef,
161        data_contract: &DataContract,
162        platform_version: &PlatformVersion,
163    ) -> Result<ContenderWithSerializedDocument, ProtocolError> {
164        match self {
165            Contender::V0(v0) => Ok(v0
166                .try_into_contender_with_serialized_document(
167                    document_type_ref,
168                    data_contract,
169                    platform_version,
170                )?
171                .into()),
172        }
173    }
174
175    pub fn try_to_contender_with_serialized_document(
176        &self,
177        document_type_ref: DocumentTypeRef,
178        data_contract: &DataContract,
179        platform_version: &PlatformVersion,
180    ) -> Result<ContenderWithSerializedDocument, ProtocolError> {
181        match self {
182            Contender::V0(v0) => Ok(v0
183                .try_to_contender_with_serialized_document(
184                    document_type_ref,
185                    data_contract,
186                    platform_version,
187                )?
188                .into()),
189        }
190    }
191
192    pub fn serialize(
193        &self,
194        document_type: DocumentTypeRef,
195        data_contract: &DataContract,
196        platform_version: &PlatformVersion,
197    ) -> Result<Vec<u8>, ProtocolError> {
198        self.try_to_contender_with_serialized_document(
199            document_type,
200            data_contract,
201            platform_version,
202        )?
203        .serialize_to_bytes()
204    }
205
206    pub fn serialize_consume(
207        self,
208        document_type: DocumentTypeRef,
209        data_contract: &DataContract,
210        platform_version: &PlatformVersion,
211    ) -> Result<Vec<u8>, ProtocolError> {
212        self.try_into_contender_with_serialized_document(
213            document_type,
214            data_contract,
215            platform_version,
216        )?
217        .serialize_to_bytes()
218    }
219
220    pub fn from_bytes(
221        serialized_contender: &[u8],
222        document_type: DocumentTypeRef,
223        platform_version: &PlatformVersion,
224    ) -> Result<Self, ProtocolError>
225    where
226        Self: Sized,
227    {
228        let serialized_contender =
229            ContenderWithSerializedDocument::deserialize_from_bytes_untrusted(
230                serialized_contender,
231            )?;
232        serialized_contender.try_into_contender(document_type, platform_version)
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use crate::voting::contender_structs::contender::v0::{
240        ContenderV0, ContenderWithSerializedDocumentV0,
241    };
242    use platform_value::Identifier;
243
244    mod contender_construction {
245        use super::*;
246
247        #[test]
248        fn contender_v0_default() {
249            let contender = ContenderV0::default();
250            assert_eq!(contender.identity_id, Identifier::default());
251            assert!(contender.document.is_none());
252            assert!(contender.vote_tally.is_none());
253        }
254
255        #[test]
256        fn contender_v0_with_fields() {
257            let id = Identifier::new([1u8; 32]);
258            let contender = ContenderV0 {
259                identity_id: id,
260                document: None,
261                vote_tally: Some(42),
262            };
263            assert_eq!(contender.identity_id, id);
264            assert!(contender.document.is_none());
265            assert_eq!(contender.vote_tally, Some(42));
266        }
267
268        #[test]
269        fn contender_from_v0() {
270            let id = Identifier::new([2u8; 32]);
271            let v0 = ContenderV0 {
272                identity_id: id,
273                document: None,
274                vote_tally: Some(100),
275            };
276            let contender: Contender = v0.into();
277            assert_eq!(contender.identity_id(), id);
278            assert_eq!(contender.vote_tally(), Some(100));
279        }
280    }
281
282    mod contender_accessors {
283        use super::*;
284
285        #[test]
286        fn identity_id_returns_correct_value() {
287            let id = Identifier::new([3u8; 32]);
288            let contender = Contender::V0(ContenderV0 {
289                identity_id: id,
290                document: None,
291                vote_tally: None,
292            });
293            assert_eq!(contender.identity_id(), id);
294        }
295
296        #[test]
297        fn identity_id_ref_returns_reference() {
298            let id = Identifier::new([4u8; 32]);
299            let contender = Contender::V0(ContenderV0 {
300                identity_id: id,
301                document: None,
302                vote_tally: None,
303            });
304            assert_eq!(*contender.identity_id_ref(), id);
305        }
306
307        #[test]
308        fn document_returns_none_when_empty() {
309            let contender = Contender::V0(ContenderV0::default());
310            assert!(contender.document().is_none());
311        }
312
313        #[test]
314        fn vote_tally_returns_none_when_not_set() {
315            let contender = Contender::V0(ContenderV0::default());
316            assert!(contender.vote_tally().is_none());
317        }
318
319        #[test]
320        fn vote_tally_returns_value_when_set() {
321            let contender = Contender::V0(ContenderV0 {
322                identity_id: Identifier::default(),
323                document: None,
324                vote_tally: Some(999),
325            });
326            assert_eq!(contender.vote_tally(), Some(999));
327        }
328
329        #[test]
330        fn take_document_returns_none_and_leaves_none() {
331            let mut contender = Contender::V0(ContenderV0::default());
332            let doc = contender.take_document();
333            assert!(doc.is_none());
334            assert!(contender.document().is_none());
335        }
336    }
337
338    mod contender_with_serialized_document {
339        use super::*;
340
341        #[test]
342        fn default_values() {
343            let csd = ContenderWithSerializedDocumentV0::default();
344            assert_eq!(csd.identity_id, Identifier::default());
345            assert!(csd.serialized_document.is_none());
346            assert!(csd.vote_tally.is_none());
347        }
348
349        #[test]
350        fn construction_with_data() {
351            let id = Identifier::new([5u8; 32]);
352            let doc_bytes = vec![1, 2, 3, 4, 5];
353            let csd = ContenderWithSerializedDocumentV0 {
354                identity_id: id,
355                serialized_document: Some(doc_bytes.clone()),
356                vote_tally: Some(50),
357            };
358            let wrapped = ContenderWithSerializedDocument::V0(csd);
359            assert_eq!(wrapped.identity_id(), id);
360            assert_eq!(*wrapped.identity_id_ref(), id);
361            assert_eq!(wrapped.serialized_document(), &Some(doc_bytes));
362            assert_eq!(wrapped.vote_tally(), Some(50));
363        }
364
365        #[test]
366        fn take_serialized_document() {
367            let doc_bytes = vec![10, 20, 30];
368            let csd = ContenderWithSerializedDocumentV0 {
369                identity_id: Identifier::default(),
370                serialized_document: Some(doc_bytes.clone()),
371                vote_tally: None,
372            };
373            let mut wrapped = ContenderWithSerializedDocument::V0(csd);
374            let taken = wrapped.take_serialized_document();
375            assert_eq!(taken, Some(doc_bytes));
376            assert!(wrapped.serialized_document().is_none());
377        }
378
379        #[test]
380        fn serialization_round_trip() {
381            let id = Identifier::new([6u8; 32]);
382            let csd = ContenderWithSerializedDocumentV0 {
383                identity_id: id,
384                serialized_document: Some(vec![0xAA, 0xBB, 0xCC]),
385                vote_tally: Some(77),
386            };
387            let wrapped = ContenderWithSerializedDocument::V0(csd);
388
389            // Serialize to bytes using PlatformSerializable
390            let bytes = wrapped
391                .serialize_to_bytes()
392                .expect("should serialize to bytes");
393            assert!(!bytes.is_empty());
394
395            // Deserialize back
396            let restored =
397                ContenderWithSerializedDocument::deserialize_from_bytes_untrusted(&bytes)
398                    .expect("should deserialize from bytes");
399
400            assert_eq!(wrapped, restored);
401        }
402
403        #[test]
404        fn serialization_round_trip_with_no_document() {
405            let id = Identifier::new([7u8; 32]);
406            let csd = ContenderWithSerializedDocumentV0 {
407                identity_id: id,
408                serialized_document: None,
409                vote_tally: None,
410            };
411            let wrapped = ContenderWithSerializedDocument::V0(csd);
412
413            let bytes = wrapped
414                .serialize_to_bytes()
415                .expect("should serialize to bytes");
416            let restored =
417                ContenderWithSerializedDocument::deserialize_from_bytes_untrusted(&bytes)
418                    .expect("should deserialize from bytes");
419
420            assert_eq!(wrapped, restored);
421        }
422    }
423
424    mod equality {
425        use super::*;
426
427        #[test]
428        fn equal_contenders() {
429            let id = Identifier::new([8u8; 32]);
430            let a = Contender::V0(ContenderV0 {
431                identity_id: id,
432                document: None,
433                vote_tally: Some(10),
434            });
435            let b = Contender::V0(ContenderV0 {
436                identity_id: id,
437                document: None,
438                vote_tally: Some(10),
439            });
440            assert_eq!(a, b);
441        }
442
443        #[test]
444        fn different_vote_tallies_not_equal() {
445            let id = Identifier::new([9u8; 32]);
446            let a = Contender::V0(ContenderV0 {
447                identity_id: id,
448                document: None,
449                vote_tally: Some(10),
450            });
451            let b = Contender::V0(ContenderV0 {
452                identity_id: id,
453                document: None,
454                vote_tally: Some(20),
455            });
456            assert_ne!(a, b);
457        }
458
459        #[test]
460        fn different_identity_ids_not_equal() {
461            let a = Contender::V0(ContenderV0 {
462                identity_id: Identifier::new([1u8; 32]),
463                document: None,
464                vote_tally: None,
465            });
466            let b = Contender::V0(ContenderV0 {
467                identity_id: Identifier::new([2u8; 32]),
468                document: None,
469                vote_tally: None,
470            });
471            assert_ne!(a, b);
472        }
473    }
474}
475
476#[cfg(all(
477    test,
478    feature = "json-conversion",
479    feature = "value-conversion",
480    feature = "serde-conversion"
481))]
482mod json_convertible_tests_contender_with_serialized_document {
483    use super::*;
484    use platform_value::{platform_value, Identifier, Value};
485    use serde_json::json;
486
487    /// Non-default values per field (real identity_id bytes, non-empty
488    /// serialized_document, non-zero tally) so the wire-shape assertion
489    /// catches silent zero-out / flip on round-trip.
490    fn fixture() -> ContenderWithSerializedDocument {
491        ContenderWithSerializedDocument::V0(ContenderWithSerializedDocumentV0 {
492            identity_id: Identifier::new([0xa1; 32]),
493            serialized_document: Some(vec![0xde, 0xad, 0xbe, 0xef]),
494            vote_tally: Some(42),
495        })
496    }
497
498    #[test]
499    fn json_round_trip_with_full_wire_shape() {
500        use crate::serialization::JsonConvertible;
501        let original = fixture();
502        let json = original.to_json().expect("to_json");
503        // `Identifier` renders as base58 in JSON. `Vec<u8>` (from the default
504        // `serialize_seq` in serde_json) renders as an array of numbers — NOT
505        // bytes — so each element appears as `Number(...)`. `vote_tally` is
506        // `Option<u32>`; JSON erases the size — the value-path assertion uses
507        // `42u32` to lock in `Value::U32`.
508        assert_eq!(
509            json,
510            json!({
511                "$formatVersion": "0",
512                "identityId": "Bswb3UyeD1pUTaGiE6WvqwFpJZsQSEY1xhJePCDTHdvp",
513                "serializedDocument": [222, 173, 190, 239],
514                "voteTally": 42,
515            })
516        );
517        let recovered = ContenderWithSerializedDocument::from_json(json).expect("from_json");
518        assert_eq!(original, recovered);
519    }
520
521    #[test]
522    fn value_round_trip_with_full_wire_shape() {
523        use crate::serialization::ValueConvertible;
524        let original = fixture();
525        let value = original.to_object().expect("to_object");
526        // platform_value preserves typed variants: `Identifier` renders as
527        // `Value::Identifier`, `Vec<u8>` renders as `Value::Array([U8(...), ...])`
528        // (NOT `Value::Bytes`, because `Vec<u8>` uses the generic `serialize_seq`
529        // path), `Option<u32>` becomes `Value::U32`.
530        let id = Identifier::new([0xa1; 32]);
531        let bytes_array = Value::Array(vec![
532            Value::U8(0xde),
533            Value::U8(0xad),
534            Value::U8(0xbe),
535            Value::U8(0xef),
536        ]);
537        assert_eq!(
538            value,
539            platform_value!({
540                "$formatVersion": "0",
541                "identityId": id,
542                "serializedDocument": bytes_array,
543                "voteTally": 42u32,
544            })
545        );
546        let recovered = ContenderWithSerializedDocument::from_object(value).expect("from_object");
547        assert_eq!(original, recovered);
548    }
549}