penumbra_sdk_auction/component/action_handler/dutch/
schedule.rs

1use crate::auction::dutch::actions::schedule::MAX_AUCTION_AMOUNT_RESERVES;
2use crate::auction::dutch::DutchAuctionDescription;
3use crate::component::AuctionStoreRead;
4use anyhow::{ensure, Result};
5use async_trait::async_trait;
6use cnidarium::StateWrite;
7use cnidarium_component::ActionHandler;
8use penumbra_sdk_num::Amount;
9use penumbra_sdk_sct::component::clock::EpochRead;
10
11use crate::auction::dutch::ActionDutchAuctionSchedule;
12use crate::component::DutchAuctionManager;
13
14#[async_trait]
15impl ActionHandler for ActionDutchAuctionSchedule {
16    type CheckStatelessContext = ();
17    async fn check_stateless(&self, _context: ()) -> Result<()> {
18        let DutchAuctionDescription {
19            input,
20            output_id,
21            max_output,
22            min_output,
23            start_height,
24            end_height,
25            step_count,
26            nonce: _,
27        } = self.description;
28
29        // Fail fast if the input is zero.
30        ensure!(
31            input.amount > Amount::zero(),
32            "input amount MUST be positive (got zero)"
33        );
34
35        // Fail fast if the step count is zero.
36        ensure!(step_count > 0, "step count MUST be positive (got zero)");
37
38        // Check that the input amount is less than 52 bits wide.
39        ensure!(
40            input.amount <= MAX_AUCTION_AMOUNT_RESERVES.into(),
41            "input amount MUST be less than 52 bits wide"
42        );
43
44        // Check that we disallow identical input/output ids.
45        ensure!(
46            input.asset_id != output_id,
47            "input id MUST be different from output id"
48        );
49
50        // Check that the `max_output` is greater than the `min_output`
51        ensure!(
52            max_output > min_output,
53            "max_output MUST be greater than min_output"
54        );
55
56        // Check that the max output is greater than zero.
57        ensure!(max_output > 0u128.into(), "max output MUST be positive");
58
59        // Check that the max output is less than 52 bits wide.
60        ensure!(
61            max_output <= MAX_AUCTION_AMOUNT_RESERVES.into(),
62            "max output amount MUST be less than 52 bits wide"
63        );
64
65        // Check that the min output is greater than zero.
66        ensure!(min_output > 0u128.into(), "min output MUST be positive");
67
68        // Check that the min output is less than 52 bits wide.
69        ensure!(
70            min_output <= MAX_AUCTION_AMOUNT_RESERVES.into(),
71            "min output amount MUST be less than 52 bits wide"
72        );
73
74        // Check that the start and end height are valid.
75        ensure!(
76            start_height < end_height,
77            "the start height MUST be strictly less than the end height (got: start={} >= end={})",
78            start_height,
79            end_height
80        );
81
82        // Check that the step count is at least 2. This is important
83        // because DA price interpolation assumes that `step_count-1` is positive.
84        ensure!(
85            step_count >= 2,
86            "step count MUST be at least two (got: {step_count})"
87        );
88
89        // Check that the step count is less than 255.
90        ensure!(
91            step_count <= 255,
92            "the dutch auction step count MUST be less than 255 (got: {step_count})",
93        );
94
95        // Check that height delta is a multiple of `step_count`.
96        let block_window = end_height.checked_sub(start_height).ok_or_else(|| {
97            anyhow::anyhow!(
98                "underflow ({end_height} < {start_height}) - the validation rules are incoherent!"
99            )
100        })?;
101        ensure!(
102            (block_window % step_count) == 0,
103            "the block window ({block_window}) MUST be a multiple of the step count ({step_count})"
104        );
105
106        Ok(())
107    }
108
109    async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
110        let schedule = self;
111
112        // Check that `start_height` is in the future.
113        let current_height = state.get_block_height().await?;
114        let start_height = schedule.description.start_height;
115        ensure!(
116            start_height > current_height,
117            "dutch auction MUST start in the future (start={}, current={})",
118            start_height,
119            current_height
120        );
121
122        // Check that the `auction_id` is unused.
123        let id = schedule.description.id();
124        ensure!(
125            !state.auction_id_exists(id).await,
126            "the supplied auction id is already known to the chain (id={id})"
127        );
128
129        state.schedule_auction(schedule.description.clone()).await?;
130        Ok(())
131    }
132}