penumbra_sdk_distributions/
component.rs1pub mod state_key;
2pub use view::{StateReadExt, StateWriteExt};
3pub mod rpc;
4
5mod view;
6
7use std::sync::Arc;
8
9use crate::event;
10use crate::genesis;
11use anyhow::{Context, Result};
12use async_trait::async_trait;
13use cnidarium::StateWrite;
14use cnidarium_component::Component;
15use penumbra_sdk_num::Amount;
16use penumbra_sdk_proto::{DomainType as _, StateWriteProto};
17use penumbra_sdk_sct::component::clock::EpochRead;
18use tendermint::v0_37::abci;
19use tracing::instrument;
20
21pub struct Distributions {}
22
23#[async_trait]
24impl Component for Distributions {
25 type AppState = genesis::Content;
26
27 #[instrument(name = "distributions", skip(state, app_state))]
28 async fn init_chain<S: StateWrite>(mut state: S, app_state: Option<&Self::AppState>) {
29 match app_state {
30 None => { }
31 Some(genesis) => {
32 state.put_distributions_params(genesis.distributions_params.clone());
33 }
34 };
35 }
36
37 #[instrument(name = "distributions", skip(_state, _begin_block))]
38 async fn begin_block<S: StateWrite + 'static>(
39 _state: &mut Arc<S>,
40 _begin_block: &abci::request::BeginBlock,
41 ) {
42 }
43
44 #[instrument(name = "distributions", skip(state))]
45 async fn end_block<S: StateWrite + 'static>(
46 state: &mut Arc<S>,
47 end_block: &abci::request::EndBlock,
48 ) {
49 let state = Arc::get_mut(state).expect("the state should not be shared");
50
51 let current_epoch = state
52 .get_current_epoch()
53 .await
54 .expect("failed to retrieve current epoch from state");
55 let epoch_index = current_epoch.index;
56
57 let params = state
58 .get_distributions_params()
59 .await
60 .expect("distribution parameters should be available");
61 let increase = Amount::from(params.liquidity_tournament_incentive_per_block);
62 let current_block = u64::try_from(end_block.height).unwrap_or_default();
63 if let Some(end_block) = params
66 .liquidity_tournament_end_block
67 .filter(|b| current_block >= b.get())
68 {
69 tracing::debug!(epoch_index, "zeroing out LQT reward issuance");
72 state.set_lqt_reward_issuance_for_epoch(epoch_index, 0u64.into());
73 if current_block == end_block.get() {
74 state.record_proto(
75 event::EventLqtPoolSizeIncrease {
76 epoch_index,
77 increase: 0u64.into(),
78 new_total: 0u64.into(),
79 }
80 .to_proto(),
81 )
82 }
83 return;
84 }
85
86 let new_total = state.increment_lqt_issuance(epoch_index, increase).await;
87
88 state.record_proto(
90 event::EventLqtPoolSizeIncrease {
91 epoch_index,
92 increase,
93 new_total,
94 }
95 .to_proto(),
96 )
97 }
98
99 #[instrument(name = "distributions", skip(state))]
100 async fn end_epoch<S: StateWrite + 'static>(state: &mut Arc<S>) -> Result<()> {
101 let state = Arc::get_mut(state).context("state should be unique")?;
102
103 state.define_staking_budget().await?;
105
106 Ok(())
109 }
110}
111
112#[async_trait]
113trait DistributionManager: StateWriteExt {
114 async fn compute_new_staking_issuance(&self) -> Result<Amount> {
116 use penumbra_sdk_sct::component::clock::EpochRead;
117
118 let current_block_height = self.get_block_height().await?;
119 let current_epoch = self.get_current_epoch().await?;
120 let num_blocks = current_block_height
121 .checked_sub(current_epoch.start_height)
122 .unwrap_or_else(|| panic!("epoch start height is greater than current block height (epoch_start={}, current_height={}", current_epoch.start_height, current_block_height));
123
124 let staking_issuance_per_block = self
127 .get_distributions_params()
128 .await?
129 .staking_issuance_per_block as u128;
130
131 tracing::debug!(
132 number_of_blocks_in_epoch = num_blocks,
133 staking_issuance_per_block,
134 "calculating staking issuance per epoch"
135 );
136
137 let new_staking_issuance_for_epoch = staking_issuance_per_block
138 .checked_mul(num_blocks as u128) .expect("infallible unless issuance is pathological");
140
141 tracing::debug!(
142 ?new_staking_issuance_for_epoch,
143 "computed new staking issuance for epoch"
144 );
145
146 Ok(Amount::from(new_staking_issuance_for_epoch))
147 }
148
149 async fn define_staking_budget(&mut self) -> Result<()> {
151 let new_issuance = self.compute_new_staking_issuance().await?;
152 tracing::debug!(
153 ?new_issuance,
154 "computed new staking issuance for current epoch"
155 );
156 Ok(self.set_staking_token_issuance_for_epoch(new_issuance))
157 }
158
159 async fn increment_lqt_issuance(&mut self, epoch_index: u64, by: Amount) -> Amount {
160 let current = self.get_lqt_reward_issuance_for_epoch(epoch_index).await;
161 let new = current
162 .unwrap_or_default()
163 .checked_add(&by)
164 .expect("LQT issuance should never exceed an Amount");
165 tracing::debug!(
166 ?by,
167 ?current,
168 ?new,
169 epoch_index,
170 "incrementing lqt issuance"
171 );
172 self.set_lqt_reward_issuance_for_epoch(epoch_index, new);
173 new
174 }
175}
176
177impl<T: StateWrite + ?Sized> DistributionManager for T {}