penumbra_sdk_stake/
delegation_token.rs

1use std::str::FromStr;
2
3use regex::Regex;
4
5use penumbra_sdk_asset::asset;
6
7use super::IdentityKey;
8
9/// Delegation tokens represent a share of a particular validator's delegation pool.
10pub 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    /// Returns a new [`DelegationToken`] with the provided identity.
29    pub fn new(validator_identity: IdentityKey) -> Self {
30        // This format string needs to be in sync with the asset registry
31        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    /// Returns the base denomination for this delegation token.
41    pub fn denom(&self) -> asset::Metadata {
42        self.base_denom.clone()
43    }
44
45    /// Returns the default display denomination for this delegation token.
46    pub fn default_unit(&self) -> asset::Unit {
47        self.base_denom.default_unit()
48    }
49
50    /// Returns the asset ID for this delegation token.
51    pub fn id(&self) -> asset::Id {
52        self.base_denom.id()
53    }
54
55    /// Returns the identity key of the validator this delegation token is associated with.
56    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        // Note: this regex must be in sync with both asset::REGISTRY
65        // and VALIDATOR_IDENTITY_BECH32_PREFIX
66        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}