penumbra_sdk_stake/
delegate.rs

1use penumbra_sdk_asset::{Balance, Value, STAKING_TOKEN_ASSET_ID};
2use penumbra_sdk_num::Amount;
3use penumbra_sdk_proto::{penumbra::core::component::stake::v1 as pb, DomainType};
4use penumbra_sdk_txhash::{EffectHash, EffectingData};
5use serde::{Deserialize, Serialize};
6
7use crate::{DelegationToken, IdentityKey};
8
9/// A transaction action adding stake to a validator's delegation pool.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(try_from = "pb::Delegate", into = "pb::Delegate")]
12pub struct Delegate {
13    /// The identity key of the validator to delegate to.
14    pub validator_identity: IdentityKey,
15    /// The index of the epoch in which this delegation was performed.
16    /// The delegation takes effect in the next epoch.
17    pub epoch_index: u64,
18    /// The delegation amount, in units of unbonded stake.
19    /// TODO: use flow aggregation to hide this, replacing it with bytes amount_ciphertext;
20    pub unbonded_amount: Amount,
21    /// The amount of delegation tokens produced by this action.
22    ///
23    /// This is implied by the validator's exchange rate in the specified epoch
24    /// (and should be checked in transaction validation!), but including it allows
25    /// stateless verification that the transaction is internally consistent.
26    pub delegation_amount: Amount,
27}
28
29impl EffectingData for Delegate {
30    fn effect_hash(&self) -> EffectHash {
31        // For delegations, the entire action is considered effecting data.
32        EffectHash::from_proto_effecting_data(&self.to_proto())
33    }
34}
35
36impl Delegate {
37    /// Return the balance resulting from issuing delegation tokens from staking tokens.
38    pub fn balance(&self) -> Balance {
39        let stake: Balance = self.unbonded_value().into();
40        let delegation: Balance = self.delegation_value().into();
41
42        // We produce the delegation tokens and consume the staking tokens.
43        delegation - stake
44    }
45
46    /// Returns the [`Value`] of the delegation [`Amount`].
47    pub fn delegation_value(&self) -> Value {
48        Value {
49            amount: self.delegation_amount,
50            asset_id: DelegationToken::new(self.validator_identity.clone()).id(),
51        }
52    }
53
54    /// Returns the [`Value`] of the unbonded [`Amount`].
55    pub fn unbonded_value(&self) -> Value {
56        Value {
57            amount: self.unbonded_amount,
58            asset_id: STAKING_TOKEN_ASSET_ID.clone(),
59        }
60    }
61}
62
63impl DomainType for Delegate {
64    type Proto = pb::Delegate;
65}
66
67impl From<Delegate> for pb::Delegate {
68    fn from(d: Delegate) -> Self {
69        pb::Delegate {
70            validator_identity: Some(d.validator_identity.into()),
71            epoch_index: d.epoch_index,
72            unbonded_amount: Some(d.unbonded_amount.into()),
73            delegation_amount: Some(d.delegation_amount.into()),
74        }
75    }
76}
77
78impl TryFrom<pb::Delegate> for Delegate {
79    type Error = anyhow::Error;
80    fn try_from(d: pb::Delegate) -> Result<Self, Self::Error> {
81        Ok(Self {
82            validator_identity: d
83                .validator_identity
84                .ok_or_else(|| anyhow::anyhow!("missing validator identity"))?
85                .try_into()?,
86            epoch_index: d.epoch_index,
87            unbonded_amount: d
88                .unbonded_amount
89                .ok_or_else(|| anyhow::anyhow!("missing unbonded amount"))?
90                .try_into()?,
91            delegation_amount: d
92                .delegation_amount
93                .ok_or_else(|| anyhow::anyhow!("missing delegation amount"))?
94                .try_into()?,
95        })
96    }
97}