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