penumbra_sdk_governance/
voting_receipt_token.rs1use std::str::FromStr;
2
3use regex::Regex;
4
5use penumbra_sdk_asset::asset;
6
7pub struct VotingReceiptToken {
13 proposal_id: u64,
14 base_denom: asset::Metadata,
15}
16
17impl VotingReceiptToken {
18 pub fn new(proposal_id: u64) -> Self {
19 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 pub fn denom(&self) -> asset::Metadata {
31 self.base_denom.clone()
32 }
33
34 pub fn default_unit(&self) -> asset::Unit {
36 self.base_denom.default_unit()
37 }
38
39 pub fn id(&self) -> asset::Id {
41 self.base_denom.id()
42 }
43
44 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 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}