pcli/command/tx/
proposal.rs

1use anyhow::{Context, Result};
2
3use penumbra_sdk_app::params::AppParameters;
4use penumbra_sdk_governance::{change::ParameterChange, Proposal, ProposalPayload};
5use penumbra_sdk_proto::DomainType;
6use penumbra_sdk_transaction::TransactionPlan;
7
8use super::FeeTier;
9
10#[derive(Debug, clap::Subcommand)]
11pub enum ProposalCmd {
12    /// Make a template file for a new proposal.
13    Template {
14        /// The file to output the template to.
15        #[clap(long, global = true)]
16        file: Option<camino::Utf8PathBuf>,
17        /// The kind of the proposal to template [one of: signaling, emergency, parameter-change, or community-pool-spend].
18        #[clap(subcommand)]
19        kind: ProposalKindCmd,
20    },
21    /// Submit a new governance proposal.
22    Submit {
23        /// The proposal to vote on, in TOML format.
24        #[clap(long)]
25        file: camino::Utf8PathBuf,
26        /// Only spend funds originally received by the given account.
27        #[clap(long, default_value = "0")]
28        source: u32,
29        /// The amount of the staking token to deposit alongside the proposal.
30        #[clap(long, default_value = "")]
31        deposit_amount: String,
32        /// The selected fee tier to multiply the fee amount by.
33        #[clap(short, long, default_value_t)]
34        fee_tier: FeeTier,
35    },
36    /// Withdraw a governance proposal that you previously submitted.
37    Withdraw {
38        /// The proposal id to withdraw.
39        proposal_id: u64,
40        /// A short description of the reason for the proposal being withdrawn, meant to be
41        /// displayed to users.
42        #[clap(long)]
43        reason: String,
44        /// Only spend funds originally received by the given account.
45        #[clap(long, default_value = "0")]
46        source: u32,
47        /// The selected fee tier to multiply the fee amount by.
48        #[clap(short, long, default_value_t)]
49        fee_tier: FeeTier,
50    },
51    /// Claim a governance proposal deposit for a proposal you submitted that has finished voting.
52    ///
53    /// This consumes the voting or withdrawn proposal NFT and mints an NFT representing whether the
54    /// proposal passed, failed, or was slashed. In the case of a slash, the deposit is not returned
55    /// by this action; in other cases, it is returned to you.
56    DepositClaim {
57        /// The proposal id to claim the deposit for.
58        proposal_id: u64,
59        /// Only spend funds originally received by the given account.
60        #[clap(long, default_value = "0")]
61        source: u32,
62        /// The selected fee tier to multiply the fee amount by.
63        #[clap(short, long, default_value_t)]
64        fee_tier: FeeTier,
65    },
66}
67
68#[derive(Debug, clap::Subcommand)]
69pub enum ProposalKindCmd {
70    /// Generate a template for a signaling proposal.
71    Signaling,
72    /// Generate a template for an emergency proposal.
73    Emergency,
74    /// Generate a template for a parameter change proposal.
75    ParameterChange,
76    /// Generate a template for a Community Pool spend proposal.
77    CommunityPoolSpend {
78        /// The transaction plan to include in the proposal, in JSON format.
79        ///
80        /// If not specified, the default empty transaction plan will be included, to be replaced
81        /// in the template before submission.
82        #[clap(long)]
83        transaction_plan: Option<camino::Utf8PathBuf>,
84    },
85    /// Generate a template for an upgrade proposal,
86    UpgradePlan,
87}
88
89impl ProposalKindCmd {
90    /// Generate a default proposal of a particular kind.
91    pub fn template_proposal(&self, app_params: &AppParameters, id: u64) -> Result<Proposal> {
92        let title = "A short title (at most 80 characters)".to_string();
93        let description = "A longer description (at most 10,000 characters)".to_string();
94        let payload = match self {
95            ProposalKindCmd::Signaling => ProposalPayload::Signaling { commit: None },
96            ProposalKindCmd::Emergency => ProposalPayload::Emergency { halt_chain: false },
97            ProposalKindCmd::ParameterChange => {
98                ProposalPayload::ParameterChange(ParameterChange::encode_parameters(
99                    serde_json::value::to_value(app_params.clone())?,
100                ))
101            }
102            ProposalKindCmd::CommunityPoolSpend { transaction_plan } => {
103                if let Some(file) = transaction_plan {
104                    ProposalPayload::CommunityPoolSpend {
105                        transaction_plan: serde_json::from_reader(
106                            std::fs::File::open(file).with_context(|| {
107                                format!("Failed to open transaction plan file {:?}", file)
108                            })?,
109                        )
110                        .with_context(|| {
111                            format!("Failed to parse transaction plan file {:?}", file)
112                        })?,
113                    }
114                } else {
115                    ProposalPayload::CommunityPoolSpend {
116                        transaction_plan: TransactionPlan::default().encode_to_vec(),
117                    }
118                }
119            }
120            ProposalKindCmd::UpgradePlan { .. } => ProposalPayload::UpgradePlan { height: 0 },
121        };
122
123        Ok(Proposal {
124            id,
125            title,
126            description,
127            payload,
128        })
129    }
130}
131
132impl ProposalCmd {
133    pub fn offline(&self) -> bool {
134        match self {
135            ProposalCmd::Template { .. } => false,
136            ProposalCmd::Submit { .. } => false,
137            ProposalCmd::Withdraw { .. } => false,
138            ProposalCmd::DepositClaim { .. } => false,
139        }
140    }
141}