penumbra_stake/
delegation_token.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use std::str::FromStr;

use regex::Regex;

use penumbra_asset::asset;

use super::IdentityKey;

/// Delegation tokens represent a share of a particular validator's delegation pool.
pub struct DelegationToken {
    validator_identity: IdentityKey,
    base_denom: asset::Metadata,
}

impl From<IdentityKey> for DelegationToken {
    fn from(v: IdentityKey) -> Self {
        DelegationToken::new(v)
    }
}

impl From<&IdentityKey> for DelegationToken {
    fn from(v: &IdentityKey) -> Self {
        DelegationToken::new(*v)
    }
}

impl DelegationToken {
    /// Returns a new [`DelegationToken`] with the provided identity.
    pub fn new(validator_identity: IdentityKey) -> Self {
        // This format string needs to be in sync with the asset registry
        let base_denom = asset::REGISTRY
            .parse_denom(&format!("udelegation_{validator_identity}"))
            .expect("base denom format is valid");
        DelegationToken {
            validator_identity,
            base_denom,
        }
    }

    /// Returns the base denomination for this delegation token.
    pub fn denom(&self) -> asset::Metadata {
        self.base_denom.clone()
    }

    /// Returns the default display denomination for this delegation token.
    pub fn default_unit(&self) -> asset::Unit {
        self.base_denom.default_unit()
    }

    /// Returns the asset ID for this delegation token.
    pub fn id(&self) -> asset::Id {
        self.base_denom.id()
    }

    /// Returns the identity key of the validator this delegation token is associated with.
    pub fn validator(&self) -> IdentityKey {
        self.validator_identity
    }
}

impl TryFrom<asset::Metadata> for DelegationToken {
    type Error = anyhow::Error;
    fn try_from(base_denom: asset::Metadata) -> Result<Self, Self::Error> {
        // Note: this regex must be in sync with both asset::REGISTRY
        // and VALIDATOR_IDENTITY_BECH32_PREFIX
        let validator_identity =
            Regex::new("^udelegation_(?P<data>penumbravalid1[a-zA-HJ-NP-Z0-9]+)$")
                .expect("regex is valid")
                .captures(&base_denom.to_string())
                .ok_or_else(|| {
                    anyhow::anyhow!(
                        "base denom {} is not a delegation token",
                        base_denom.to_string()
                    )
                })?
                .name("data")
                .expect("data is a named capture")
                .as_str()
                .parse()?;

        Ok(Self {
            base_denom,
            validator_identity,
        })
    }
}

impl FromStr for DelegationToken {
    type Err = anyhow::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        asset::REGISTRY
            .parse_denom(s)
            .ok_or_else(|| anyhow::anyhow!("could not parse {} as base denomination", s))?
            .try_into()
    }
}

impl std::fmt::Display for DelegationToken {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.base_denom.fmt(f)
    }
}

impl std::fmt::Debug for DelegationToken {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.base_denom.fmt(f)
    }
}

impl PartialEq for DelegationToken {
    fn eq(&self, other: &Self) -> bool {
        self.base_denom.eq(&other.base_denom)
    }
}

impl Eq for DelegationToken {}

impl std::hash::Hash for DelegationToken {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.base_denom.hash(state)
    }
}

#[cfg(test)]
mod tests {
    use decaf377_rdsa::{SigningKey, SpendAuth, VerificationKey};

    use super::*;

    #[test]
    fn delegation_token_denomination_round_trip() {
        use rand_core::OsRng;

        let vk = VerificationKey::from(SigningKey::<SpendAuth>::new(OsRng));
        let ik = IdentityKey(vk.into());

        let token = DelegationToken::new(ik);

        let denom = token.to_string();
        let token2 = DelegationToken::from_str(&denom).unwrap();
        let denom2 = token2.to_string();

        assert_eq!(denom, denom2);
        assert_eq!(token, token2);
    }
}