penumbra_sdk_governance/proposal_submit/
action.rs

1use serde::{Deserialize, Serialize};
2
3use penumbra_sdk_asset::{Balance, Value, STAKING_TOKEN_ASSET_ID};
4use penumbra_sdk_num::Amount;
5use penumbra_sdk_proto::{penumbra::core::component::governance::v1 as pb, DomainType};
6use penumbra_sdk_txhash::{EffectHash, EffectingData};
7
8use crate::proposal::Proposal;
9
10use crate::ProposalNft;
11
12/// A proposal submission describes the proposal to propose, and the (transparent, ephemeral) refund
13/// address for the proposal deposit, along with a key to be used to verify the signature for a
14/// withdrawal of that proposal.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(try_from = "pb::ProposalSubmit", into = "pb::ProposalSubmit")]
17pub struct ProposalSubmit {
18    /// The proposal to propose.
19    pub proposal: Proposal,
20    /// The amount deposited for the proposal.
21    pub deposit_amount: Amount,
22}
23
24impl EffectingData for ProposalSubmit {
25    fn effect_hash(&self) -> EffectHash {
26        EffectHash::from_proto_effecting_data(&self.to_proto())
27    }
28}
29
30impl ProposalSubmit {
31    /// Compute a commitment to the value contributed to a transaction by this proposal submission.
32    pub fn balance(&self) -> Balance {
33        let deposit = self.deposit_value();
34        let proposal_nft = self.proposal_nft_value();
35
36        // Proposal submissions *require* the deposit amount in order to be accepted, so they
37        // contribute (-deposit) to the value balance of the transaction, and they contribute a
38        // single proposal NFT to the value balance:
39        Balance::from(proposal_nft) - Balance::from(deposit)
40    }
41
42    /// Returns the [`Value`] of this proposal submission's deposit.
43    fn deposit_value(&self) -> Value {
44        Value {
45            amount: self.deposit_amount,
46            asset_id: *STAKING_TOKEN_ASSET_ID,
47        }
48    }
49
50    /// Returns the [`Value`] of the proposal NFT.
51    pub fn proposal_nft_value(&self) -> Value {
52        Value {
53            amount: Amount::from(1u64),
54            asset_id: ProposalNft::deposit(self.proposal.id).denom().into(),
55        }
56    }
57}
58
59impl From<ProposalSubmit> for pb::ProposalSubmit {
60    fn from(value: ProposalSubmit) -> pb::ProposalSubmit {
61        pb::ProposalSubmit {
62            proposal: Some(value.proposal.into()),
63            deposit_amount: Some(value.deposit_amount.into()),
64        }
65    }
66}
67
68impl TryFrom<pb::ProposalSubmit> for ProposalSubmit {
69    type Error = anyhow::Error;
70
71    fn try_from(msg: pb::ProposalSubmit) -> Result<Self, Self::Error> {
72        Ok(ProposalSubmit {
73            proposal: msg
74                .proposal
75                .ok_or_else(|| anyhow::anyhow!("missing proposal in `Propose`"))?
76                .try_into()?,
77            deposit_amount: msg
78                .deposit_amount
79                .ok_or_else(|| anyhow::anyhow!("missing deposit amount in `Propose`"))?
80                .try_into()?,
81        })
82    }
83}
84
85impl DomainType for ProposalSubmit {
86    type Proto = pb::ProposalSubmit;
87}