penumbra_sdk_auction/component/
auction.rs

1use crate::component::dutch_auction::HandleDutchTriggers;
2use crate::event;
3use anyhow::Result;
4use async_trait::async_trait;
5use cnidarium::{StateRead, StateWrite};
6use cnidarium_component::Component;
7use penumbra_sdk_asset::asset;
8use penumbra_sdk_asset::Value;
9use penumbra_sdk_num::Amount;
10use penumbra_sdk_proto::StateReadProto;
11use penumbra_sdk_proto::StateWriteProto;
12use std::sync::Arc;
13use tap::Tap;
14use tendermint::v0_37::abci;
15use tracing::instrument;
16
17use crate::{params::AuctionParameters, state_key};
18
19pub struct Auction {}
20
21#[async_trait]
22impl Component for Auction {
23    type AppState = crate::genesis::Content;
24
25    #[instrument(name = "auction", skip(state, app_state))]
26    async fn init_chain<S: StateWrite>(mut state: S, app_state: Option<&Self::AppState>) {
27        match app_state {
28            None => { /* perform upgrade specific check */ }
29            Some(content) => {
30                state.put_auction_params(content.auction_params.clone());
31            }
32        }
33    }
34
35    #[instrument(name = "auction", skip(_state, _begin_block))]
36    async fn begin_block<S: StateWrite + 'static>(
37        _state: &mut Arc<S>,
38        _begin_block: &abci::request::BeginBlock,
39    ) {
40    }
41
42    #[instrument(name = "auction", skip(state, end_block))]
43    async fn end_block<S: StateWrite + 'static>(
44        state: &mut Arc<S>,
45        end_block: &abci::request::EndBlock,
46    ) {
47        let state: &mut S = Arc::get_mut(state).expect("state should be unique");
48        let _ = state.process_triggers(end_block.height as u64).await;
49    }
50
51    #[instrument(name = "auction", skip(_state))]
52    async fn end_epoch<S: StateWrite + 'static>(_state: &mut Arc<S>) -> Result<()> {
53        Ok(())
54    }
55}
56
57/// Extension trait providing read access to auction data.
58#[async_trait]
59pub trait StateReadExt: StateRead {
60    async fn get_auction_params(&self) -> Result<AuctionParameters> {
61        self.get(state_key::parameters::key())
62            .await
63            .expect("no deserialization errors")
64            .ok_or_else(|| anyhow::anyhow!("Missing AuctionParameters"))
65    }
66
67    fn auction_params_updated(&self) -> bool {
68        self.object_get::<()>(state_key::parameters::updated_flag())
69            .is_some()
70    }
71
72    /// Fetch the current balance of the auction circuit breaker for a given asset,
73    /// returning zero if no balance is tracked yet.
74    #[instrument(skip(self))]
75    async fn get_auction_value_balance_for(&self, asset_id: &asset::Id) -> Amount {
76        self.get(&state_key::value_balance::for_asset(asset_id))
77            .await
78            .expect("failed to fetch auction value breaker balance")
79            .unwrap_or_else(Amount::zero)
80            .tap(|vcb| tracing::trace!(?vcb))
81    }
82}
83
84impl<T: StateRead + ?Sized> StateReadExt for T {}
85
86/// Extension trait providing write access to auction data.
87#[async_trait]
88pub trait StateWriteExt: StateWrite {
89    /// Writes the provided auction parameters to the chain state.
90    fn put_auction_params(&mut self, params: AuctionParameters) {
91        self.object_put(state_key::parameters::updated_flag(), ());
92        self.put(state_key::parameters::key().into(), params)
93    }
94}
95
96impl<T: StateWrite + ?Sized> StateWriteExt for T {}
97
98/// Internal trait implementing value flow tracking.
99/// # Overview
100///
101///                                                               
102///                                                  ║            
103///                                                  ║            
104///                                           User initiated      
105///      Auction                                     ║            
106///     component                                    ║            
107///   ┏━━━━━━━━━━━┓                                  ▼            
108///   ┃┌─────────┐┃                     ╔════════════════════════╗
109///   ┃│    A    │┃◀━━value in━━━━━━━━━━║   Schedule auction A   ║
110///   ┃└─────────┘┃                     ╚════════════════════════╝
111///   ┃           ┃                                               
112///   ┃     │     ┃                                               
113///   ┃           ┃                     ■■■■■■■■■■■■■■■■■■■■■■■■■■
114///   ┃  closed   ┃━━━value out by ━━━━▶■■■■■■Dex□black□box■■■■■■■
115///   ┃   then    ┃   creating lp       ■■■■■■■■■■■■■■■■■■■■■■■■■■
116///   ┃withdrawn  ┃                                  ┃            
117///   ┃           ┃                                  ┃            
118///   ┃     │     ┃                value in by       ┃            
119///   ┃           ┃◀━━━━━━━━━━━━━━━withdrawing lp━━━━┛            
120///   ┃     │     ┃                                               
121///   ┃     ▼     ┃                                               
122///   ┃┌ ─ ─ ─ ─ ┐┃                     ╔════════════════════════╗
123///   ┃     A     ┃━━━value out━━━━━━━━▶║   Withdraw auction A   ║
124///   ┃└ ─ ─ ─ ─ ┘┃                     ╚════════════════════════╝
125///   ┗━━━━━━━━━━━┛                                  ▲            
126///                                                  ║            
127///                                                  ║            
128///                                           User initiated      
129///                                              withdrawl        
130///                                                  ║            
131///                                                  ║            
132///                                                  ║            
133///
134pub(crate) trait AuctionCircuitBreaker: StateWrite {
135    /// Credit a deposit into the auction component.
136    #[instrument(skip(self))]
137    async fn auction_vcb_credit(&mut self, value: Value) -> Result<()> {
138        if value.amount == Amount::zero() {
139            tracing::trace!("short-circuit crediting zero-value");
140            return Ok(());
141        }
142
143        let prev_balance = self.get_auction_value_balance_for(&value.asset_id).await;
144        let new_balance = prev_balance.checked_add(&value.amount).ok_or_else(|| {
145            anyhow::anyhow!("overflowed balance while crediting auction circuit breaker (prev balance: {prev_balance:?}, credit: {value:?}")
146        })?;
147
148        tracing::trace!(
149            ?prev_balance,
150            ?new_balance,
151            "crediting the auction component VCB"
152        );
153
154        // Write the new balance to the chain state.
155        self.put(
156            state_key::value_balance::for_asset(&value.asset_id),
157            new_balance,
158        );
159        // And emit an event to trace the value flow.
160        self.record_proto(event::auction_vcb_credit(
161            value.asset_id,
162            prev_balance,
163            new_balance,
164        ));
165        Ok(())
166    }
167
168    /// Debit a balance from the auction component.
169    #[instrument(skip(self))]
170    async fn auction_vcb_debit(&mut self, value: Value) -> Result<()> {
171        if value.amount == Amount::zero() {
172            tracing::trace!("short-circuit debiting zero-value");
173            return Ok(());
174        }
175
176        let prev_balance = self.get_auction_value_balance_for(&value.asset_id).await;
177        let new_balance = prev_balance.checked_sub(&value.amount).ok_or_else(|| {
178            anyhow::anyhow!("underflowed balance while debiting auction circuit breaker (prev balance: {prev_balance:?}, debit={value:?}")
179        })?;
180
181        tracing::trace!(
182            ?prev_balance,
183            ?new_balance,
184            "debiting the auction component VCB"
185        );
186
187        self.put(
188            state_key::value_balance::for_asset(&value.asset_id),
189            new_balance,
190        );
191        // And emit an event to trace the value flow out of the component.
192        self.record_proto(event::auction_vcb_debit(
193            value.asset_id,
194            prev_balance,
195            new_balance,
196        ));
197        Ok(())
198    }
199}
200
201impl<T: StateWrite + ?Sized> AuctionCircuitBreaker for T {}
202
203#[cfg(test)]
204mod tests {}