penumbra_sdk_governance/
vote.rs

1use std::{
2    fmt::{self, Display},
3    str::FromStr,
4};
5
6use anyhow::anyhow;
7use penumbra_sdk_proto::{penumbra::core::component::governance::v1 as pb, DomainType};
8use serde::{Deserialize, Serialize};
9
10/// A vote on a proposal.
11#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
12#[serde(try_from = "pb::Vote", into = "pb::Vote")]
13#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
14pub enum Vote {
15    /// Vote to approve the proposal.
16    Yes,
17    /// Vote is to reject the proposal.
18    No,
19    /// Vote to abstain from the proposal.
20    Abstain,
21}
22
23impl FromStr for Vote {
24    type Err = anyhow::Error;
25
26    fn from_str(s: &str) -> anyhow::Result<Vote> {
27        match s {
28            "yes" | "y" => Ok(Vote::Yes),
29            "no" | "n" => Ok(Vote::No),
30            "abstain" | "a" => Ok(Vote::Abstain),
31            _ => Err(anyhow::anyhow!("invalid vote: {}", s)),
32        }
33    }
34}
35
36impl Display for Vote {
37    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), fmt::Error> {
38        match self {
39            Vote::Yes => write!(f, "yes"),
40            Vote::No => write!(f, "no"),
41            Vote::Abstain => write!(f, "abstain"),
42        }
43    }
44}
45
46impl From<Vote> for pb::Vote {
47    fn from(value: Vote) -> Self {
48        pb_from_vote(value)
49    }
50}
51
52// Factored out so it can be used in a const
53const fn pb_from_vote(vote: Vote) -> pb::Vote {
54    match vote {
55        Vote::Yes => pb::Vote {
56            vote: pb::vote::Vote::Yes as i32,
57        },
58        Vote::No => pb::Vote {
59            vote: pb::vote::Vote::No as i32,
60        },
61        Vote::Abstain => pb::Vote {
62            vote: pb::vote::Vote::Abstain as i32,
63        },
64    }
65}
66
67impl TryFrom<pb::Vote> for Vote {
68    type Error = anyhow::Error;
69
70    fn try_from(msg: pb::Vote) -> Result<Self, Self::Error> {
71        let vote_state = pb::vote::Vote::try_from(msg.vote)
72            .map_err(|e| anyhow::anyhow!("invalid vote state, error: {e}"))?;
73
74        match vote_state {
75            pb::vote::Vote::Abstain => Ok(Vote::Abstain),
76            pb::vote::Vote::Yes => Ok(Vote::Yes),
77            pb::vote::Vote::No => Ok(Vote::No),
78            pb::vote::Vote::Unspecified => Err(anyhow!("unspecified vote state")),
79        }
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use proptest::proptest;
86
87    proptest! {
88        #[test]
89        fn vote_roundtrip_serialize(vote: super::Vote) {
90            let pb_vote: super::pb::Vote = vote.into();
91            let vote2 = super::Vote::try_from(pb_vote).unwrap();
92            assert_eq!(vote, vote2);
93        }
94    }
95}
96
97impl DomainType for Vote {
98    type Proto = pb::Vote;
99}