penumbra_sdk_stake/component/action_handler/
delegate.rs1use 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 Ok(())
20 }
21
22 async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
23 let d = self;
28
29 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 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 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 let validator = self.validator_identity;
97 let unbonded_delegation = self.unbonded_amount;
98
99 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 if validator_state == Defined {
111 let min_stake = state.get_stake_params().await?.min_validator_stake;
112 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 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}