penumbra_sdk_governance/
voting_receipt_token.rs

1use std::str::FromStr;
2
3use regex::Regex;
4
5use penumbra_sdk_asset::asset;
6
7/// Unbonding tokens represent staking tokens that are currently unbonding and
8/// subject to slashing.
9///
10/// Unbonding tokens are parameterized by the validator identity, the epoch at
11/// which unbonding began, and the epoch at which unbonding ends.
12pub struct VotingReceiptToken {
13    proposal_id: u64,
14    base_denom: asset::Metadata,
15}
16
17impl VotingReceiptToken {
18    pub fn new(proposal_id: u64) -> Self {
19        // This format string needs to be in sync with the asset registry
20        let base_denom = asset::REGISTRY
21            .parse_denom(&format!("uvoted_on_{proposal_id}"))
22            .expect("base denom format is valid");
23        VotingReceiptToken {
24            proposal_id,
25            base_denom,
26        }
27    }
28
29    /// Get the base denomination for this delegation token.
30    pub fn denom(&self) -> asset::Metadata {
31        self.base_denom.clone()
32    }
33
34    /// Get the default display denomination for this delegation token.
35    pub fn default_unit(&self) -> asset::Unit {
36        self.base_denom.default_unit()
37    }
38
39    /// Get the asset ID for this delegation token.
40    pub fn id(&self) -> asset::Id {
41        self.base_denom.id()
42    }
43
44    /// Get the proposal ID this delegation token is associated with.
45    pub fn proposal_id(&self) -> u64 {
46        self.proposal_id
47    }
48}
49
50impl TryFrom<asset::Metadata> for VotingReceiptToken {
51    type Error = anyhow::Error;
52
53    fn try_from(base_denom: asset::Metadata) -> Result<Self, Self::Error> {
54        let base_string = base_denom.to_string();
55
56        // Note: this regex must be in sync with both asset::REGISTRY
57        // and VALIDATOR_IDENTITY_BECH32_PREFIX
58        // The data capture group is used by asset::REGISTRY
59        let captures = Regex::new("^uvoted_on_(?P<data>(?P<proposal_id>[0-9]+))$")
60            .expect("regex is valid")
61            .captures(base_string.as_ref())
62            .ok_or_else(|| {
63                anyhow::anyhow!(
64                    "base denom {} is not an unbonding token",
65                    base_denom.to_string()
66                )
67            })?;
68
69        let proposal_id: u64 = captures
70            .name("proposal_id")
71            .expect("proposal_id is a named capture")
72            .as_str()
73            .parse()?;
74
75        Ok(Self {
76            proposal_id,
77            base_denom,
78        })
79    }
80}
81
82impl FromStr for VotingReceiptToken {
83    type Err = anyhow::Error;
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        asset::REGISTRY
86            .parse_denom(s)
87            .ok_or_else(|| anyhow::anyhow!("could not parse {} as base denomination", s))?
88            .try_into()
89    }
90}
91
92impl std::fmt::Display for VotingReceiptToken {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        self.base_denom.fmt(f)
95    }
96}
97
98impl std::fmt::Debug for VotingReceiptToken {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        self.base_denom.fmt(f)
101    }
102}
103
104impl PartialEq for VotingReceiptToken {
105    fn eq(&self, other: &Self) -> bool {
106        self.base_denom.eq(&other.base_denom)
107    }
108}
109
110impl Eq for VotingReceiptToken {}
111
112impl std::hash::Hash for VotingReceiptToken {
113    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
114        self.base_denom.hash(state)
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn unbonding_token_denomination_round_trip() {
124        let proposal_id: u64 = 1;
125
126        let token = VotingReceiptToken::new(proposal_id);
127
128        let denom = token.to_string();
129        println!("denom: {denom}");
130        let token2 = VotingReceiptToken::from_str(&denom).unwrap();
131        let denom2 = token2.to_string();
132
133        assert_eq!(denom, denom2);
134        assert_eq!(token, token2);
135    }
136}