penumbra_sdk_stake/
delegation_token.rs1use std::str::FromStr;
2
3use regex::Regex;
4
5use penumbra_sdk_asset::asset;
6
7use super::IdentityKey;
8
9pub struct DelegationToken {
11 validator_identity: IdentityKey,
12 base_denom: asset::Metadata,
13}
14
15impl From<IdentityKey> for DelegationToken {
16 fn from(v: IdentityKey) -> Self {
17 DelegationToken::new(v)
18 }
19}
20
21impl From<&IdentityKey> for DelegationToken {
22 fn from(v: &IdentityKey) -> Self {
23 DelegationToken::new(*v)
24 }
25}
26
27impl DelegationToken {
28 pub fn new(validator_identity: IdentityKey) -> Self {
30 let base_denom = asset::REGISTRY
32 .parse_denom(&format!("udelegation_{validator_identity}"))
33 .expect("base denom format is valid");
34 DelegationToken {
35 validator_identity,
36 base_denom,
37 }
38 }
39
40 pub fn denom(&self) -> asset::Metadata {
42 self.base_denom.clone()
43 }
44
45 pub fn default_unit(&self) -> asset::Unit {
47 self.base_denom.default_unit()
48 }
49
50 pub fn id(&self) -> asset::Id {
52 self.base_denom.id()
53 }
54
55 pub fn validator(&self) -> IdentityKey {
57 self.validator_identity
58 }
59}
60
61impl TryFrom<asset::Metadata> for DelegationToken {
62 type Error = anyhow::Error;
63 fn try_from(base_denom: asset::Metadata) -> Result<Self, Self::Error> {
64 let validator_identity =
67 Regex::new("^udelegation_(?P<data>penumbravalid1[a-zA-HJ-NP-Z0-9]+)$")
68 .expect("regex is valid")
69 .captures(&base_denom.to_string())
70 .ok_or_else(|| {
71 anyhow::anyhow!(
72 "base denom {} is not a delegation token",
73 base_denom.to_string()
74 )
75 })?
76 .name("data")
77 .expect("data is a named capture")
78 .as_str()
79 .parse()?;
80
81 Ok(Self {
82 base_denom,
83 validator_identity,
84 })
85 }
86}
87
88impl FromStr for DelegationToken {
89 type Err = anyhow::Error;
90 fn from_str(s: &str) -> Result<Self, Self::Err> {
91 asset::REGISTRY
92 .parse_denom(s)
93 .ok_or_else(|| anyhow::anyhow!("could not parse {} as base denomination", s))?
94 .try_into()
95 }
96}
97
98impl std::fmt::Display for DelegationToken {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 self.base_denom.fmt(f)
101 }
102}
103
104impl std::fmt::Debug for DelegationToken {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 self.base_denom.fmt(f)
107 }
108}
109
110impl PartialEq for DelegationToken {
111 fn eq(&self, other: &Self) -> bool {
112 self.base_denom.eq(&other.base_denom)
113 }
114}
115
116impl Eq for DelegationToken {}
117
118impl std::hash::Hash for DelegationToken {
119 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
120 self.base_denom.hash(state)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey};
127
128 use super::*;
129
130 #[test]
131 fn delegation_token_denomination_round_trip() {
132 use rand_core::OsRng;
133
134 let vk = VerificationKey::from(SigningKey::<SpendAuth>::new(OsRng));
135 let ik = IdentityKey(vk.into());
136
137 let token = DelegationToken::new(ik);
138
139 let denom = token.to_string();
140 let token2 = DelegationToken::from_str(&denom).unwrap();
141 let denom2 = token2.to_string();
142
143 assert_eq!(denom, denom2);
144 assert_eq!(token, token2);
145 }
146}