penumbra_sdk_stake/component/action_handler/
delegate.rs

1use anyhow::{ensure, Result};
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use cnidarium_component::ActionHandler;
5use penumbra_sdk_num::Amount;
6use penumbra_sdk_proto::{DomainType, StateWriteProto};
7use penumbra_sdk_sct::component::clock::EpochRead;
8
9use crate::{
10    component::validator_handler::ValidatorDataRead, event, validator::State::*, Delegate,
11    StateReadExt as _, StateWriteExt as _,
12};
13
14#[async_trait]
15impl ActionHandler for Delegate {
16    type CheckStatelessContext = ();
17    async fn check_stateless(&self, _context: ()) -> Result<()> {
18        // There are no stateless checks specific to this action.
19        Ok(())
20    }
21
22    async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
23        // These checks all formerly happened in the `check_historical` method,
24        // if profiling shows that they cause a bottleneck we could (CAREFULLY)
25        // move some of them back.
26
27        let d = self;
28
29        // We check if the rate data is for the current epoch to provide a helpful
30        // error message if there is a mismatch.
31        let current_epoch = state.get_current_epoch().await?;
32        ensure!(
33            d.epoch_index == current_epoch.index,
34            "delegation was prepared for epoch {} but the current epoch is {}",
35            d.epoch_index,
36            current_epoch.index
37        );
38
39        // For delegations, we enforce correct computation (with rounding)
40        // of the *delegation amount based on the unbonded amount*, because
41        // users (should be) starting with the amount of unbonded stake they
42        // wish to delegate, and computing the amount of delegation tokens
43        // they receive.
44        //
45        // The direction of the computation matters because the computation
46        // involves rounding, so while both
47        //
48        // (unbonded amount, rates) -> delegation amount
49        // (delegation amount, rates) -> unbonded amount
50        //
51        // should give approximately the same results, they may not give
52        // exactly the same results.
53        let validator_rate = state
54            .get_validator_rate(&d.validator_identity)
55            .await?
56            .ok_or_else(|| anyhow::anyhow!("unknown validator identity {}", d.validator_identity))?
57            .clone();
58
59        let expected_delegation_amount = validator_rate.delegation_amount(d.unbonded_amount);
60
61        ensure!(
62            expected_delegation_amount == d.delegation_amount,
63            "given {} unbonded stake, expected {} delegation tokens but description produces {}",
64            d.unbonded_amount,
65            expected_delegation_amount,
66            d.delegation_amount,
67        );
68
69        // The delegation is only allowed if both conditions are met:
70        // - the validator definition is `enabled` by the operator
71        // - the validator is not jailed or tombstoned
72        let validator = state
73            .get_validator_definition(&d.validator_identity)
74            .await?
75            .ok_or_else(|| anyhow::anyhow!("missing definition for validator"))?;
76        let validator_state = state
77            .get_validator_state(&d.validator_identity)
78            .await?
79            .ok_or_else(|| anyhow::anyhow!("missing state for validator"))?;
80
81        ensure!(
82            validator.enabled,
83            "delegations are only allowed to enabled validators, but {} is disabled",
84            d.validator_identity,
85        );
86
87        ensure!(
88            matches!(validator_state, Defined | Inactive | Active),
89            "delegations are only allowed to active or inactive validators, but {} is in state {:?}",
90            d.validator_identity,
91            validator_state,
92        );
93
94        // (end of former check_historical checks)
95
96        let validator = self.validator_identity;
97        let unbonded_delegation = self.unbonded_amount;
98
99        // This action is executed in two phases:
100        // 1. We check if the self-delegation requirement is met.
101        // 2. We queue the delegation for the next epoch.
102        let validator_state = state
103            .get_validator_state(&self.validator_identity)
104            .await?
105            .ok_or_else(|| anyhow::anyhow!("missing state for validator"))?;
106
107        // When a validator definition is published, it starts in a `Defined` state
108        // where it is unindexed by the staking module. We transition validator with
109        // too little stake to the `Defined` state as well. See #2921 for more details.
110        if validator_state == Defined {
111            let min_stake = state.get_stake_params().await?.min_validator_stake;
112            // With #3853, we impose a minimum self-delegation requirement to simplify
113            // end-epoch handling. The first delegation" to a `Defined` validator must
114            // be at least `min_validator_stake`.
115            //
116            // Note: Validators can be demoted to `Defined` if they have too little stake,
117            // if we don't check that the pool is empty, we could trap delegations.
118            let validator_pool_size = state
119                .get_validator_pool_size(&validator)
120                .await
121                .unwrap_or_else(Amount::zero);
122
123            if validator_pool_size == Amount::zero() {
124                ensure!(
125                    unbonded_delegation >= min_stake,
126                    "first delegation to a `Defined` validator must be at least {min_stake}"
127                );
128                tracing::debug!(%validator, %unbonded_delegation, "first delegation to validator recorded");
129            }
130        }
131
132        // We queue the delegation so it can be processed at the epoch boundary.
133        tracing::debug!(?self, "queuing delegation for next epoch");
134        state.push_delegation(self.clone());
135        state.record_proto(event::EventDelegate::from(self).to_proto());
136        Ok(())
137    }
138}