penumbra_sdk_auction/auction/
dutch.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
use std::num::NonZeroU64;

use anyhow::anyhow;
use penumbra_sdk_asset::{asset, Value};
use penumbra_sdk_dex::lp::position::{self};
use penumbra_sdk_num::Amount;
use penumbra_sdk_proto::{core::component::auction::v1 as pb, DomainType};
use serde::{Deserialize, Serialize};

use crate::auction::AuctionId;

pub mod actions;
pub use actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule, ActionDutchAuctionWithdraw};

pub const DUTCH_AUCTION_DOMAIN_SEP: &[u8] = b"penumbra_DA_nft";

/// A deployed Dutch Auction, containing an immutable description
/// and stateful data about its current state.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(try_from = "pb::DutchAuction", into = "pb::DutchAuction")]
pub struct DutchAuction {
    pub description: DutchAuctionDescription,
    pub state: DutchAuctionState,
}

/* Protobuf impls for `DutchAuction` */
impl DomainType for DutchAuction {
    type Proto = pb::DutchAuction;
}

impl From<DutchAuction> for pb::DutchAuction {
    fn from(domain: DutchAuction) -> Self {
        pb::DutchAuction {
            description: Some(domain.description.into()),
            state: Some(domain.state.into()),
        }
    }
}

impl TryFrom<pb::DutchAuction> for DutchAuction {
    type Error = anyhow::Error;

    fn try_from(msg: pb::DutchAuction) -> Result<Self, Self::Error> {
        Ok(DutchAuction {
            description: msg
                .description
                .ok_or_else(|| anyhow!("DutchAuction is missing description"))?
                .try_into()?,
            state: msg
                .state
                .ok_or_else(|| anyhow!("DutchAuction is missing a state field"))?
                .try_into()?,
        })
    }
}
/* ********************************** */

/// A description of the immutable parts of a dutch auction.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(
    try_from = "pb::DutchAuctionDescription",
    into = "pb::DutchAuctionDescription"
)]
pub struct DutchAuctionDescription {
    pub input: Value,
    pub output_id: asset::Id,
    pub max_output: Amount,
    pub min_output: Amount,
    pub start_height: u64,
    pub end_height: u64,
    pub step_count: u64,
    pub nonce: [u8; 32],
}

impl DutchAuctionDescription {
    /// Compute the unique identifier for the auction description.
    pub fn id(&self) -> AuctionId {
        let mut state = blake2b_simd::Params::default()
            .personal(DUTCH_AUCTION_DOMAIN_SEP)
            .to_state();

        state.update(&self.nonce);
        state.update(&self.input.asset_id.to_bytes());
        state.update(&self.input.amount.to_le_bytes());
        state.update(&self.max_output.to_le_bytes());
        state.update(&self.start_height.to_le_bytes());
        state.update(&self.end_height.to_le_bytes());
        state.update(&self.step_count.to_le_bytes());

        let hash = state.finalize();
        let mut bytes = [0; 32];
        bytes[0..32].copy_from_slice(&hash.as_bytes()[0..32]);
        AuctionId(bytes)
    }
}

/* Protobuf impls */
impl DomainType for DutchAuctionDescription {
    type Proto = pb::DutchAuctionDescription;
}

impl From<DutchAuctionDescription> for pb::DutchAuctionDescription {
    fn from(domain: DutchAuctionDescription) -> Self {
        Self {
            input: Some(domain.input.into()),
            output_id: Some(domain.output_id.into()),
            max_output: Some(domain.max_output.into()),
            min_output: Some(domain.min_output.into()),
            start_height: domain.start_height,
            end_height: domain.end_height,
            step_count: domain.step_count,
            nonce: domain.nonce.as_slice().to_vec(),
        }
    }
}

impl TryFrom<pb::DutchAuctionDescription> for DutchAuctionDescription {
    type Error = anyhow::Error;

    fn try_from(msg: pb::DutchAuctionDescription) -> Result<Self, Self::Error> {
        let d = DutchAuctionDescription {
            input: msg
                .input
                .ok_or_else(|| anyhow!("DutchAuctionDescription message is missing input"))?
                .try_into()?,
            output_id: msg
                .output_id
                .ok_or_else(|| {
                    anyhow!("DutchAuctionDescription message is missing an output identifier")
                })?
                .try_into()?,
            max_output: msg
                .max_output
                .ok_or_else(|| anyhow!("DutchAuctionDescription message is missing max output"))?
                .try_into()?,
            min_output: msg
                .min_output
                .ok_or_else(|| anyhow!("DutchAuctionDescription message is missing min output"))?
                .try_into()?,
            start_height: msg.start_height,
            end_height: msg.end_height,
            step_count: msg.step_count,
            nonce: msg.nonce.as_slice().try_into()?,
        };
        Ok(d)
    }
}
/* ********************************** */

/// A stateful description of a dutch auction, recording its state (via a sequence number),
/// the current position id associated to it (if any), and its amount IO.
/// # State
/// We record the state of the dutch auction via an untyped `u64` instead of an enum.
/// This futureproof support for auction types that have a richer state machine e.g. allows
/// claiming a withdrawn auction multiple times, burning and minting a new withdrawn auction
/// with an incremented sequence number.
///
/// For Dutch auctions:
///
///   ┌───┐            ┌───┐             ┌───┐
///   │ 0 │───Closed──▶│ 1 │──Withdrawn─▶│ 2 │
///   └───┘            └───┘             └───┘
///     ▲                                     
///     │                                     
///  Opened                                   
///     │                                     
///
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)]
#[serde(try_from = "pb::DutchAuctionState", into = "pb::DutchAuctionState")]
pub struct DutchAuctionState {
    pub sequence: u64,
    pub current_position: Option<position::Id>,
    pub next_trigger: Option<NonZeroU64>,
    pub input_reserves: Amount,
    pub output_reserves: Amount,
}

/* Protobuf impls for `DutchAuctionState` */
impl DomainType for DutchAuctionState {
    type Proto = pb::DutchAuctionState;
}

impl From<DutchAuctionState> for pb::DutchAuctionState {
    fn from(domain: DutchAuctionState) -> Self {
        Self {
            seq: domain.sequence,
            current_position: domain.current_position.map(Into::into),
            next_trigger: domain.next_trigger.map_or(0u64, Into::into),
            input_reserves: Some(domain.input_reserves.into()),
            output_reserves: Some(domain.output_reserves.into()),
        }
    }
}

impl TryFrom<pb::DutchAuctionState> for DutchAuctionState {
    type Error = anyhow::Error;

    fn try_from(msg: pb::DutchAuctionState) -> Result<Self, Self::Error> {
        Ok(DutchAuctionState {
            sequence: msg.seq,
            current_position: msg.current_position.map(TryInto::try_into).transpose()?,
            next_trigger: NonZeroU64::new(msg.next_trigger),
            input_reserves: msg
                .input_reserves
                .ok_or_else(|| anyhow!("DutchAuctionState message is missing input reserves"))?
                .try_into()?,
            output_reserves: msg
                .output_reserves
                .ok_or_else(|| anyhow!("DutchAuctionState message is missing output reserves"))?
                .try_into()?,
        })
    }
}
/* ********************************** */