penumbra_sdk_auction/auction/dutch/actions/
withdraw.rs

1use crate::auction::{id::AuctionId, AuctionNft};
2use anyhow::anyhow;
3use ark_ff::Zero;
4use decaf377_rdsa::Fr;
5use penumbra_sdk_asset::{balance, Balance, Value};
6use penumbra_sdk_proto::{core::component::auction::v1 as pb, DomainType};
7use penumbra_sdk_txhash::{EffectHash, EffectingData};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(
12    try_from = "pb::ActionDutchAuctionWithdraw",
13    into = "pb::ActionDutchAuctionWithdraw"
14)]
15pub struct ActionDutchAuctionWithdraw {
16    pub auction_id: AuctionId,
17    pub seq: u64,
18    pub reserves_commitment: balance::Commitment,
19}
20
21impl ActionDutchAuctionWithdraw {
22    /// Compute a balance **commitment** for this action.
23    ///
24    /// # Diagram
25    ///
26    /// The value balance commitment is built from the balance:
27    ///  ┌────────────────────┬──────────────────────┐
28    ///  │      Burn (-)      │       Mint (+)       │
29    ///  ├────────────────────┼──────────────────────┤
30    ///  │    auction nft     │       auction        │
31    ///  │   with seq >= 1    │    value balance     │
32    ///  └────────────────────┼──────────────────────┤
33    ///                       │withdrawn auction nft │
34    ///                       │      with seq+1      │
35    ///                       └──────────────────────┘
36    ///
37    /// More context: [Actions and Value balance][protocol-spec]
38    /// [protocol-spec]: https://protocol.penumbra.zone/main/transactions.html#actions-and-value-balance
39    pub fn balance_commitment(&self) -> balance::Commitment {
40        let prev_auction_nft = Balance::from(Value {
41            amount: 1u128.into(),
42            // The sequence number should always be >= 1, because we can
43            // only withdraw an auction that has ended (i.e. with sequence number `>=1`).
44            // We use a saturating operation defensively so that we don't underflow.
45            asset_id: AuctionNft::new(self.auction_id, self.seq.saturating_sub(1)).asset_id(),
46        })
47        .commit(Fr::zero());
48
49        let next_auction_nft = Balance::from(Value {
50            amount: 1u128.into(),
51            asset_id: AuctionNft::new(self.auction_id, self.seq).asset_id(),
52        })
53        .commit(Fr::zero());
54
55        self.reserves_commitment + next_auction_nft - prev_auction_nft
56    }
57}
58
59/* Effect hash */
60impl EffectingData for ActionDutchAuctionWithdraw {
61    fn effect_hash(&self) -> EffectHash {
62        EffectHash::from_proto_effecting_data(&self.to_proto())
63    }
64}
65
66/* Protobuf impls */
67impl DomainType for ActionDutchAuctionWithdraw {
68    type Proto = pb::ActionDutchAuctionWithdraw;
69}
70
71impl From<ActionDutchAuctionWithdraw> for pb::ActionDutchAuctionWithdraw {
72    fn from(domain: ActionDutchAuctionWithdraw) -> Self {
73        pb::ActionDutchAuctionWithdraw {
74            auction_id: Some(domain.auction_id.into()),
75            seq: domain.seq,
76            reserves_commitment: Some(domain.reserves_commitment.into()),
77        }
78    }
79}
80
81impl TryFrom<pb::ActionDutchAuctionWithdraw> for ActionDutchAuctionWithdraw {
82    type Error = anyhow::Error;
83
84    fn try_from(msg: pb::ActionDutchAuctionWithdraw) -> Result<Self, Self::Error> {
85        Ok(ActionDutchAuctionWithdraw {
86            auction_id: msg
87                .auction_id
88                .ok_or_else(|| {
89                    anyhow!("ActionDutchAuctionWithdraw message is missing an auction_id")
90                })?
91                .try_into()?,
92            seq: msg.seq,
93            reserves_commitment: msg
94                .reserves_commitment
95                .ok_or_else(|| {
96                    anyhow!("ActionDutchAuctionWithdraw message is missing reserves_commitment")
97                })?
98                .try_into()?,
99        })
100    }
101}