Skip to main content

dpp/voting/votes/
mod.rs

1pub mod resource_vote;
2pub mod yes_no_vote;
3
4#[cfg(feature = "json-conversion")]
5use crate::serialization::JsonConvertible;
6#[cfg(feature = "value-conversion")]
7use crate::serialization::ValueConvertible;
8use crate::voting::votes::resource_vote::accessors::v0::ResourceVoteGettersV0;
9use crate::voting::votes::resource_vote::ResourceVote;
10use crate::voting::votes::yes_no_vote::accessors::v0::YesNoVoteGettersV0;
11use crate::voting::votes::yes_no_vote::YesNoVote;
12use crate::ProtocolError;
13use bincode::{Decode, DecodeUntrusted, Encode};
14use derive_more::From;
15use platform_serialization_derive::{
16    PlatformDeserializeTrusted, PlatformDeserializeUntrusted, PlatformSerialize,
17};
18use platform_value::Identifier;
19use platform_version::version::PlatformVersion;
20#[cfg(feature = "serde-conversion")]
21use serde::{Deserialize, Serialize};
22
23#[derive(
24    Debug,
25    Clone,
26    Encode,
27    Decode,
28    PlatformSerialize,
29    PlatformDeserializeTrusted,
30    PlatformDeserializeUntrusted,
31    PartialEq,
32    From,
33    DecodeUntrusted,
34)]
35#[cfg_attr(
36    feature = "serde-conversion",
37    derive(Serialize, Deserialize),
38    // Internal tagging with `$type` — system-field convention. Drops the
39    // `data` wrapper. Inner `ResourceVote` is `tag = "$formatVersion"`
40    // (different key, no collision).
41    serde(tag = "$type", rename_all = "camelCase")
42)]
43#[cfg_attr(feature = "value-conversion", derive(ValueConvertible))]
44#[platform_serialize(limit = 15000, unversioned)]
45pub enum Vote {
46    ResourceVote(ResourceVote),
47    /// A vote on a yes/no poll (protocol version 15). Appended: the bincode discriminant is
48    /// the variant position.
49    YesNoVote(YesNoVote),
50}
51
52// Manual impl because Vote is a flat enum (not versioned V0/V1).
53#[cfg(feature = "json-conversion")]
54impl JsonConvertible for Vote {}
55
56impl Default for Vote {
57    fn default() -> Self {
58        Vote::ResourceVote(ResourceVote::default())
59    }
60}
61
62impl Vote {
63    /// The prefunded balance the votes on this vote's poll are paid from. A yes/no vote names
64    /// its poll by id, and a yes/no poll's balance has the poll's id
65    /// (`YesNoVotePoll::specialized_balance_id`).
66    pub fn specialized_balance_id(
67        &self,
68        platform_version: &PlatformVersion,
69    ) -> Result<Option<Identifier>, ProtocolError> {
70        match self {
71            Vote::ResourceVote(resource_vote) => resource_vote
72                .vote_poll()
73                .specialized_balance_id(platform_version),
74            Vote::YesNoVote(yes_no_vote) => Ok(Some(yes_no_vote.vote_poll_id())),
75        }
76    }
77
78    /// The id of the poll this vote answers. A yes/no vote carries it.
79    pub fn vote_poll_unique_id(
80        &self,
81        platform_version: &PlatformVersion,
82    ) -> Result<Identifier, ProtocolError> {
83        match self {
84            Vote::ResourceVote(resource_vote) => {
85                resource_vote.vote_poll().unique_id(platform_version)
86            }
87            Vote::YesNoVote(yes_no_vote) => Ok(yes_no_vote.vote_poll_id()),
88        }
89    }
90}
91
92#[cfg(all(test, feature = "json-conversion"))]
93mod tests {
94    use super::*;
95    use crate::serialization::JsonConvertible;
96    use crate::voting::vote_choices::resource_vote_choice::ResourceVoteChoice;
97    use crate::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll;
98    use crate::voting::vote_polls::VotePoll;
99    use crate::voting::votes::resource_vote::v0::ResourceVoteV0;
100    use crate::voting::votes::resource_vote::ResourceVote;
101
102    #[test]
103    fn vote_json_round_trip() {
104        let contract_id = Identifier::from([0x11u8; 32]);
105        let towards_id = Identifier::from([0x22u8; 32]);
106
107        let vote = Vote::ResourceVote(ResourceVote::V0(ResourceVoteV0 {
108            vote_poll: VotePoll::ContestedDocumentResourceVotePoll(
109                ContestedDocumentResourceVotePoll {
110                    contract_id,
111                    document_type_name: "domain".to_string(),
112                    index_name: "parentNameAndLabel".to_string(),
113                    index_values: vec![platform_value::Value::Text("dash".to_string())],
114                },
115            ),
116            resource_vote_choice: ResourceVoteChoice::TowardsIdentity(towards_id),
117        }));
118
119        let json = vote.to_json().expect("to_json should succeed");
120
121        // Verify it's a valid JSON object
122        assert!(json.is_object(), "Vote JSON should be an object");
123
124        // round-trip
125        let restored = Vote::from_json(json).expect("from_json should succeed");
126        assert_eq!(vote, restored);
127    }
128}
129
130#[cfg(all(
131    test,
132    feature = "json-conversion",
133    feature = "value-conversion",
134    feature = "serde-conversion"
135))]
136mod json_convertible_tests_vote {
137    use super::*;
138
139    #[test]
140    fn json_round_trip_vote() {
141        use crate::serialization::JsonConvertible;
142        let original = Vote::default();
143        let json = original.to_json().expect("to_json");
144        let recovered = Vote::from_json(json).expect("from_json");
145        assert_eq!(original, recovered);
146    }
147
148    #[test]
149    fn value_round_trip_vote() {
150        use crate::serialization::ValueConvertible;
151        let original = Vote::default();
152        let value = original.to_object().expect("to_object");
153        let recovered = Vote::from_object(value).expect("from_object");
154        assert_eq!(original, recovered);
155    }
156}