penumbra_sdk_transaction/plan/
action.rs

1use crate::Action;
2use crate::WitnessData;
3use anyhow::{anyhow, Context, Result};
4use ark_ff::Zero;
5use decaf377::Fr;
6use penumbra_sdk_asset::Balance;
7use penumbra_sdk_auction::auction::dutch::actions::ActionDutchAuctionEnd;
8use penumbra_sdk_auction::auction::dutch::actions::ActionDutchAuctionSchedule;
9use penumbra_sdk_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan;
10use penumbra_sdk_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend};
11use penumbra_sdk_dex::PositionOpen;
12use penumbra_sdk_dex::{
13    lp::{
14        action::PositionClose,
15        plan::{PositionOpenPlan, PositionWithdrawPlan},
16    },
17    swap::SwapPlan,
18    swap_claim::SwapClaimPlan,
19};
20use penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVotePlan;
21use penumbra_sdk_governance::{
22    delegator_vote::DelegatorVotePlan, ProposalDepositClaim, ProposalSubmit, ProposalWithdraw,
23    ValidatorVote,
24};
25use penumbra_sdk_txhash::{EffectHash, EffectingData};
26
27use penumbra_sdk_ibc::IbcRelay;
28use penumbra_sdk_keys::{symmetric::PayloadKey, FullViewingKey};
29use penumbra_sdk_proto::{core::transaction::v1 as pb_t, DomainType};
30use penumbra_sdk_shielded_pool::{Ics20Withdrawal, OutputPlan, SpendPlan};
31use penumbra_sdk_stake::{Delegate, Undelegate, UndelegateClaimPlan};
32use serde::{Deserialize, Serialize};
33
34/// A declaration of a planned [`Action`], for use in transaction creation.
35///
36/// Actions that don't have any private data are passed through, while
37/// actions that do are replaced by a "Plan" analogue that includes
38/// openings of commitments and other private data.
39#[derive(Clone, Debug, Deserialize, Serialize)]
40#[serde(try_from = "pb_t::ActionPlan", into = "pb_t::ActionPlan")]
41#[allow(clippy::large_enum_variant)]
42pub enum ActionPlan {
43    /// Describes a proposed spend.
44    Spend(SpendPlan),
45    /// Describes a proposed output.
46    Output(OutputPlan),
47    /// We don't need any extra information (yet) to understand delegations,
48    /// because we don't yet use flow encryption.
49    Delegate(Delegate),
50    /// We don't need any extra information (yet) to understand undelegations,
51    /// because we don't yet use flow encryption.
52    Undelegate(Undelegate),
53    UndelegateClaim(UndelegateClaimPlan),
54    ValidatorDefinition(penumbra_sdk_stake::validator::Definition),
55    /// Describes a proposed swap.
56    Swap(SwapPlan),
57    /// Describes a swap claim.
58    SwapClaim(SwapClaimPlan),
59    IbcAction(IbcRelay),
60    /// Propose a governance vote.
61    ProposalSubmit(ProposalSubmit),
62    /// Withdraw a proposed vote.
63    ProposalWithdraw(ProposalWithdraw),
64    /// Vote on a proposal as a delegator.
65    DelegatorVote(DelegatorVotePlan),
66    /// Vote on a proposal as a validator.
67    ValidatorVote(ValidatorVote),
68    /// Claim the deposit for a finished proposal.
69    ProposalDepositClaim(ProposalDepositClaim),
70    /// We need a full plan here, because the metadata will be encrypted.
71    PositionOpen(PositionOpenPlan),
72    PositionClose(PositionClose),
73    // PositionWithdrawPlan requires the balance of the funds to be withdrawn, so
74    // a plan must be used.
75    PositionWithdraw(PositionWithdrawPlan),
76
77    CommunityPoolSpend(CommunityPoolSpend),
78    CommunityPoolOutput(CommunityPoolOutput),
79    CommunityPoolDeposit(CommunityPoolDeposit),
80
81    Ics20Withdrawal(Ics20Withdrawal),
82
83    ActionDutchAuctionSchedule(ActionDutchAuctionSchedule),
84    ActionDutchAuctionEnd(ActionDutchAuctionEnd),
85    ActionDutchAuctionWithdraw(ActionDutchAuctionWithdrawPlan),
86
87    ActionLiquidityTournamentVote(ActionLiquidityTournamentVotePlan),
88}
89
90impl ActionPlan {
91    /// Builds a planned [`Action`] specified by this [`ActionPlan`].
92    ///
93    /// The resulting action is `unauth` in the sense that this method does not
94    /// have access to authorization data, so any required authorization data
95    /// will be filled in with dummy zero values, to be replaced later.
96    ///
97    /// This method is useful for controlling how a transaction's actions are
98    /// built (e.g., building them in parallel, or via Web Workers).
99    pub fn build_unauth(
100        action_plan: ActionPlan,
101        fvk: &FullViewingKey,
102        witness_data: &WitnessData,
103        memo_key: Option<PayloadKey>,
104    ) -> Result<Action> {
105        use ActionPlan::*;
106
107        Ok(match action_plan {
108            Spend(spend_plan) => {
109                let note_commitment = spend_plan.note.commit();
110                let auth_path = witness_data
111                    .state_commitment_proofs
112                    .get(&note_commitment)
113                    .context(format!("could not get proof for {note_commitment:?}"))?;
114
115                Action::Spend(spend_plan.spend(
116                    fvk,
117                    [0; 64].into(),
118                    auth_path.clone(),
119                    // FIXME: why does this need the anchor? isn't that implied by the auth_path?
120                    // cf. delegator_vote
121                    witness_data.anchor,
122                ))
123            }
124            Output(output_plan) => {
125                let dummy_payload_key: PayloadKey = [0u8; 32].into();
126                Action::Output(output_plan.output(
127                    fvk.outgoing(),
128                    memo_key.as_ref().unwrap_or(&dummy_payload_key),
129                ))
130            }
131            Swap(swap_plan) => Action::Swap(swap_plan.swap(fvk)),
132            SwapClaim(swap_claim_plan) => {
133                let note_commitment = swap_claim_plan.swap_plaintext.swap_commitment();
134                let auth_path = witness_data
135                    .state_commitment_proofs
136                    .get(&note_commitment)
137                    .context(format!("could not get proof for {note_commitment:?}"))?;
138
139                Action::SwapClaim(swap_claim_plan.swap_claim(fvk, auth_path))
140            }
141            Delegate(plan) => Action::Delegate(plan.clone()),
142            Undelegate(plan) => Action::Undelegate(plan.clone()),
143            UndelegateClaim(plan) => Action::UndelegateClaim(plan.undelegate_claim()),
144            ValidatorDefinition(plan) => Action::ValidatorDefinition(plan.clone()),
145            // Fixme: action name
146            IbcAction(plan) => Action::IbcRelay(plan.clone()),
147            ProposalSubmit(plan) => Action::ProposalSubmit(plan.clone()),
148            ProposalWithdraw(plan) => Action::ProposalWithdraw(plan.clone()),
149            DelegatorVote(plan) => {
150                let note_commitment = plan.staked_note.commit();
151                let auth_path = witness_data
152                    .state_commitment_proofs
153                    .get(&note_commitment)
154                    .context(format!("could not get proof for {note_commitment:?}"))?;
155                Action::DelegatorVote(plan.delegator_vote(fvk, [0; 64].into(), auth_path.clone()))
156            }
157            ValidatorVote(plan) => Action::ValidatorVote(plan.clone()),
158            ProposalDepositClaim(plan) => Action::ProposalDepositClaim(plan.clone()),
159            PositionOpen(plan) => Action::PositionOpen(plan.position_open(fvk, None)),
160            PositionClose(plan) => Action::PositionClose(plan.clone()),
161            PositionWithdraw(plan) => Action::PositionWithdraw(plan.position_withdraw()),
162            CommunityPoolSpend(plan) => Action::CommunityPoolSpend(plan.clone()),
163            CommunityPoolOutput(plan) => Action::CommunityPoolOutput(plan.clone()),
164            CommunityPoolDeposit(plan) => Action::CommunityPoolDeposit(plan.clone()),
165            Ics20Withdrawal(plan) => Action::Ics20Withdrawal(plan.clone()),
166            ActionDutchAuctionSchedule(plan) => Action::ActionDutchAuctionSchedule(plan.clone()),
167            ActionDutchAuctionEnd(plan) => Action::ActionDutchAuctionEnd(plan.clone()),
168            ActionDutchAuctionWithdraw(plan) => {
169                Action::ActionDutchAuctionWithdraw(plan.to_action())
170            }
171            ActionLiquidityTournamentVote(plan) => {
172                let note_commitment = plan.staked_note.commit();
173                let auth_path = witness_data
174                    .state_commitment_proofs
175                    .get(&note_commitment)
176                    .context(format!("could not get proof for {note_commitment:?}"))?;
177                Action::ActionLiquidityTournamentVote(plan.to_action(
178                    fvk,
179                    [0; 64].into(),
180                    auth_path.clone(),
181                ))
182            }
183        })
184    }
185
186    /// Canonical action plan ordering according to protobuf definitions
187    pub fn variant_index(&self) -> usize {
188        match self {
189            ActionPlan::Spend(_) => 1,
190            ActionPlan::Output(_) => 2,
191            ActionPlan::Swap(_) => 3,
192            ActionPlan::SwapClaim(_) => 4,
193            ActionPlan::ValidatorDefinition(_) => 16,
194            ActionPlan::IbcAction(_) => 17,
195            ActionPlan::ProposalSubmit(_) => 18,
196            ActionPlan::ProposalWithdraw(_) => 19,
197            ActionPlan::ValidatorVote(_) => 20,
198            ActionPlan::DelegatorVote(_) => 21,
199            ActionPlan::ProposalDepositClaim(_) => 22,
200            ActionPlan::PositionOpen(_) => 30,
201            ActionPlan::PositionClose(_) => 31,
202            ActionPlan::PositionWithdraw(_) => 32,
203            ActionPlan::Delegate(_) => 40,
204            ActionPlan::Undelegate(_) => 41,
205            ActionPlan::UndelegateClaim(_) => 42,
206            ActionPlan::CommunityPoolSpend(_) => 50,
207            ActionPlan::CommunityPoolOutput(_) => 51,
208            ActionPlan::CommunityPoolDeposit(_) => 52,
209            ActionPlan::Ics20Withdrawal(_) => 200,
210            ActionPlan::ActionDutchAuctionSchedule(_) => 53,
211            ActionPlan::ActionDutchAuctionEnd(_) => 54,
212            ActionPlan::ActionDutchAuctionWithdraw(_) => 55,
213            ActionPlan::ActionLiquidityTournamentVote(_) => 70,
214        }
215    }
216
217    pub fn balance(&self) -> Balance {
218        use ActionPlan::*;
219
220        match self {
221            Spend(spend) => spend.balance(),
222            Output(output) => output.balance(),
223            Delegate(delegate) => delegate.balance(),
224            Undelegate(undelegate) => undelegate.balance(),
225            UndelegateClaim(undelegate_claim) => undelegate_claim.balance(),
226            Swap(swap) => swap.balance(),
227            SwapClaim(swap_claim) => swap_claim.balance(),
228            ProposalSubmit(proposal_submit) => proposal_submit.balance(),
229            ProposalWithdraw(proposal_withdraw) => proposal_withdraw.balance(),
230            ProposalDepositClaim(proposal_deposit_claim) => proposal_deposit_claim.balance(),
231            DelegatorVote(delegator_vote) => delegator_vote.balance(),
232            CommunityPoolSpend(community_pool_spend) => community_pool_spend.balance(),
233            CommunityPoolOutput(community_pool_output) => community_pool_output.balance(),
234            CommunityPoolDeposit(community_pool_deposit) => community_pool_deposit.balance(),
235            PositionOpen(position_open) => position_open.balance(),
236            PositionClose(position_close) => position_close.balance(),
237            PositionWithdraw(position_withdraw) => position_withdraw.balance(),
238            Ics20Withdrawal(withdrawal) => withdrawal.balance(),
239            ActionDutchAuctionSchedule(action) => action.balance(),
240            ActionDutchAuctionEnd(action) => action.balance(),
241            ActionDutchAuctionWithdraw(action) => action.balance(),
242
243            // None of these contribute to transaction balance:
244            IbcAction(_)
245            | ValidatorDefinition(_)
246            | ValidatorVote(_)
247            | ActionLiquidityTournamentVote(_) => Balance::default(),
248        }
249    }
250
251    pub fn value_blinding(&self) -> Fr {
252        use ActionPlan::*;
253
254        match self {
255            Spend(spend) => spend.value_blinding,
256            Output(output) => output.value_blinding,
257            Delegate(_) => Fr::zero(),
258            Undelegate(_) => Fr::zero(),
259            UndelegateClaim(undelegate_claim) => undelegate_claim.balance_blinding,
260            ValidatorDefinition(_) => Fr::zero(),
261            Swap(swap) => swap.fee_blinding,
262            SwapClaim(_) => Fr::zero(),
263            IbcAction(_) => Fr::zero(),
264            ProposalSubmit(_) => Fr::zero(),
265            ProposalWithdraw(_) => Fr::zero(),
266            DelegatorVote(_) => Fr::zero(),
267            ValidatorVote(_) => Fr::zero(),
268            ProposalDepositClaim(_) => Fr::zero(),
269            PositionOpen(_) => Fr::zero(),
270            PositionClose(_) => Fr::zero(),
271            PositionWithdraw(_) => Fr::zero(),
272            CommunityPoolSpend(_) => Fr::zero(),
273            CommunityPoolOutput(_) => Fr::zero(),
274            CommunityPoolDeposit(_) => Fr::zero(),
275            Ics20Withdrawal(_) => Fr::zero(),
276            ActionDutchAuctionSchedule(_) => Fr::zero(),
277            ActionDutchAuctionEnd(_) => Fr::zero(),
278            ActionDutchAuctionWithdraw(_) => Fr::zero(),
279            ActionLiquidityTournamentVote(_) => Fr::zero(),
280        }
281    }
282
283    /// Compute the effect hash of the action this plan will produce.
284    pub fn effect_hash(&self, fvk: &FullViewingKey, memo_key: &PayloadKey) -> EffectHash {
285        use ActionPlan::*;
286
287        match self {
288            Spend(plan) => plan.spend_body(fvk).effect_hash(),
289            Output(plan) => plan.output_body(fvk.outgoing(), memo_key).effect_hash(),
290            Delegate(plan) => plan.effect_hash(),
291            Undelegate(plan) => plan.effect_hash(),
292            UndelegateClaim(plan) => plan.undelegate_claim_body().effect_hash(),
293            ValidatorDefinition(plan) => plan.effect_hash(),
294            Swap(plan) => plan.swap_body(fvk).effect_hash(),
295            SwapClaim(plan) => plan.swap_claim_body(fvk).effect_hash(),
296            IbcAction(plan) => plan.effect_hash(),
297            ProposalSubmit(plan) => plan.effect_hash(),
298            ProposalWithdraw(plan) => plan.effect_hash(),
299            DelegatorVote(plan) => plan.delegator_vote_body(fvk).effect_hash(),
300            ValidatorVote(plan) => plan.effect_hash(),
301            ProposalDepositClaim(plan) => plan.effect_hash(),
302            PositionOpen(plan) => plan.position_open(fvk, None).effect_hash(),
303            PositionClose(plan) => plan.effect_hash(),
304            PositionWithdraw(plan) => plan.position_withdraw().effect_hash(),
305            CommunityPoolSpend(plan) => plan.effect_hash(),
306            CommunityPoolOutput(plan) => plan.effect_hash(),
307            CommunityPoolDeposit(plan) => plan.effect_hash(),
308            Ics20Withdrawal(plan) => plan.effect_hash(),
309            ActionDutchAuctionSchedule(plan) => plan.effect_hash(),
310            ActionDutchAuctionEnd(plan) => plan.effect_hash(),
311            ActionDutchAuctionWithdraw(plan) => plan.to_action().effect_hash(),
312            ActionLiquidityTournamentVote(plan) => plan.to_body(fvk).effect_hash(),
313        }
314    }
315}
316
317// Convenience impls that make declarative transaction construction easier.
318
319impl From<SpendPlan> for ActionPlan {
320    fn from(inner: SpendPlan) -> ActionPlan {
321        ActionPlan::Spend(inner)
322    }
323}
324
325impl From<OutputPlan> for ActionPlan {
326    fn from(inner: OutputPlan) -> ActionPlan {
327        ActionPlan::Output(inner)
328    }
329}
330
331impl From<SwapPlan> for ActionPlan {
332    fn from(inner: SwapPlan) -> ActionPlan {
333        ActionPlan::Swap(inner)
334    }
335}
336
337impl From<SwapClaimPlan> for ActionPlan {
338    fn from(inner: SwapClaimPlan) -> ActionPlan {
339        ActionPlan::SwapClaim(inner)
340    }
341}
342
343impl From<Delegate> for ActionPlan {
344    fn from(inner: Delegate) -> ActionPlan {
345        ActionPlan::Delegate(inner)
346    }
347}
348
349impl From<Undelegate> for ActionPlan {
350    fn from(inner: Undelegate) -> ActionPlan {
351        ActionPlan::Undelegate(inner)
352    }
353}
354
355impl From<UndelegateClaimPlan> for ActionPlan {
356    fn from(inner: UndelegateClaimPlan) -> ActionPlan {
357        ActionPlan::UndelegateClaim(inner)
358    }
359}
360
361impl From<penumbra_sdk_stake::validator::Definition> for ActionPlan {
362    fn from(inner: penumbra_sdk_stake::validator::Definition) -> ActionPlan {
363        ActionPlan::ValidatorDefinition(inner)
364    }
365}
366
367impl From<IbcRelay> for ActionPlan {
368    fn from(inner: IbcRelay) -> ActionPlan {
369        ActionPlan::IbcAction(inner)
370    }
371}
372
373impl From<ProposalSubmit> for ActionPlan {
374    fn from(inner: ProposalSubmit) -> ActionPlan {
375        ActionPlan::ProposalSubmit(inner)
376    }
377}
378
379impl From<DelegatorVotePlan> for ActionPlan {
380    fn from(inner: DelegatorVotePlan) -> ActionPlan {
381        ActionPlan::DelegatorVote(inner)
382    }
383}
384
385impl From<ValidatorVote> for ActionPlan {
386    fn from(inner: ValidatorVote) -> ActionPlan {
387        ActionPlan::ValidatorVote(inner)
388    }
389}
390
391impl From<PositionOpenPlan> for ActionPlan {
392    fn from(inner: PositionOpenPlan) -> ActionPlan {
393        ActionPlan::PositionOpen(inner)
394    }
395}
396
397impl From<PositionClose> for ActionPlan {
398    fn from(inner: PositionClose) -> ActionPlan {
399        ActionPlan::PositionClose(inner)
400    }
401}
402
403impl From<PositionWithdrawPlan> for ActionPlan {
404    fn from(inner: PositionWithdrawPlan) -> ActionPlan {
405        ActionPlan::PositionWithdraw(inner)
406    }
407}
408
409impl From<CommunityPoolSpend> for ActionPlan {
410    fn from(inner: CommunityPoolSpend) -> ActionPlan {
411        ActionPlan::CommunityPoolSpend(inner)
412    }
413}
414
415impl From<CommunityPoolOutput> for ActionPlan {
416    fn from(inner: CommunityPoolOutput) -> ActionPlan {
417        ActionPlan::CommunityPoolOutput(inner)
418    }
419}
420
421impl From<CommunityPoolDeposit> for ActionPlan {
422    fn from(inner: CommunityPoolDeposit) -> ActionPlan {
423        ActionPlan::CommunityPoolDeposit(inner)
424    }
425}
426
427impl From<Ics20Withdrawal> for ActionPlan {
428    fn from(inner: Ics20Withdrawal) -> ActionPlan {
429        ActionPlan::Ics20Withdrawal(inner)
430    }
431}
432
433impl From<ActionDutchAuctionSchedule> for ActionPlan {
434    fn from(inner: ActionDutchAuctionSchedule) -> ActionPlan {
435        ActionPlan::ActionDutchAuctionSchedule(inner)
436    }
437}
438
439impl From<ActionDutchAuctionEnd> for ActionPlan {
440    fn from(inner: ActionDutchAuctionEnd) -> ActionPlan {
441        ActionPlan::ActionDutchAuctionEnd(inner)
442    }
443}
444
445impl From<ActionDutchAuctionWithdrawPlan> for ActionPlan {
446    fn from(inner: ActionDutchAuctionWithdrawPlan) -> ActionPlan {
447        ActionPlan::ActionDutchAuctionWithdraw(inner)
448    }
449}
450
451impl From<ProposalWithdraw> for ActionPlan {
452    fn from(inner: ProposalWithdraw) -> ActionPlan {
453        ActionPlan::ProposalWithdraw(inner)
454    }
455}
456
457impl From<ProposalDepositClaim> for ActionPlan {
458    fn from(inner: ProposalDepositClaim) -> ActionPlan {
459        ActionPlan::ProposalDepositClaim(inner)
460    }
461}
462
463impl From<ActionLiquidityTournamentVotePlan> for ActionPlan {
464    fn from(inner: ActionLiquidityTournamentVotePlan) -> ActionPlan {
465        ActionPlan::ActionLiquidityTournamentVote(inner)
466    }
467}
468
469impl DomainType for ActionPlan {
470    type Proto = pb_t::ActionPlan;
471}
472
473impl From<ActionPlan> for pb_t::ActionPlan {
474    fn from(msg: ActionPlan) -> Self {
475        match msg {
476            ActionPlan::Output(inner) => pb_t::ActionPlan {
477                action: Some(pb_t::action_plan::Action::Output(inner.into())),
478            },
479            ActionPlan::Spend(inner) => pb_t::ActionPlan {
480                action: Some(pb_t::action_plan::Action::Spend(inner.into())),
481            },
482            ActionPlan::Delegate(inner) => pb_t::ActionPlan {
483                action: Some(pb_t::action_plan::Action::Delegate(inner.into())),
484            },
485            ActionPlan::Undelegate(inner) => pb_t::ActionPlan {
486                action: Some(pb_t::action_plan::Action::Undelegate(inner.into())),
487            },
488            ActionPlan::UndelegateClaim(inner) => pb_t::ActionPlan {
489                action: Some(pb_t::action_plan::Action::UndelegateClaim(inner.into())),
490            },
491            ActionPlan::ValidatorDefinition(inner) => pb_t::ActionPlan {
492                action: Some(pb_t::action_plan::Action::ValidatorDefinition(inner.into())),
493            },
494            ActionPlan::SwapClaim(inner) => pb_t::ActionPlan {
495                action: Some(pb_t::action_plan::Action::SwapClaim(inner.into())),
496            },
497            ActionPlan::Swap(inner) => pb_t::ActionPlan {
498                action: Some(pb_t::action_plan::Action::Swap(inner.into())),
499            },
500            ActionPlan::IbcAction(inner) => pb_t::ActionPlan {
501                action: Some(pb_t::action_plan::Action::IbcRelayAction(inner.into())),
502            },
503            ActionPlan::ProposalSubmit(inner) => pb_t::ActionPlan {
504                action: Some(pb_t::action_plan::Action::ProposalSubmit(inner.into())),
505            },
506            ActionPlan::ProposalWithdraw(inner) => pb_t::ActionPlan {
507                action: Some(pb_t::action_plan::Action::ProposalWithdraw(inner.into())),
508            },
509            ActionPlan::DelegatorVote(inner) => pb_t::ActionPlan {
510                action: Some(pb_t::action_plan::Action::DelegatorVote(inner.into())),
511            },
512            ActionPlan::ValidatorVote(inner) => pb_t::ActionPlan {
513                action: Some(pb_t::action_plan::Action::ValidatorVote(inner.into())),
514            },
515            ActionPlan::ProposalDepositClaim(inner) => pb_t::ActionPlan {
516                action: Some(pb_t::action_plan::Action::ProposalDepositClaim(
517                    inner.into(),
518                )),
519            },
520            ActionPlan::PositionOpen(inner) => pb_t::ActionPlan {
521                action: Some(pb_t::action_plan::Action::PositionOpenPlan(inner.into())),
522            },
523            ActionPlan::PositionClose(inner) => pb_t::ActionPlan {
524                action: Some(pb_t::action_plan::Action::PositionClose(inner.into())),
525            },
526            ActionPlan::PositionWithdraw(inner) => pb_t::ActionPlan {
527                action: Some(pb_t::action_plan::Action::PositionWithdraw(Into::<
528                    penumbra_sdk_proto::core::component::dex::v1::PositionWithdrawPlan,
529                >::into(
530                    inner
531                ))),
532            },
533            ActionPlan::CommunityPoolDeposit(inner) => pb_t::ActionPlan {
534                action: Some(pb_t::action_plan::Action::CommunityPoolDeposit(
535                    inner.into(),
536                )),
537            },
538            ActionPlan::CommunityPoolSpend(inner) => pb_t::ActionPlan {
539                action: Some(pb_t::action_plan::Action::CommunityPoolSpend(inner.into())),
540            },
541            ActionPlan::CommunityPoolOutput(inner) => pb_t::ActionPlan {
542                action: Some(pb_t::action_plan::Action::CommunityPoolOutput(inner.into())),
543            },
544            ActionPlan::Ics20Withdrawal(inner) => pb_t::ActionPlan {
545                action: Some(pb_t::action_plan::Action::Ics20Withdrawal(inner.into())),
546            },
547            ActionPlan::ActionDutchAuctionSchedule(inner) => pb_t::ActionPlan {
548                action: Some(pb_t::action_plan::Action::ActionDutchAuctionSchedule(
549                    inner.into(),
550                )),
551            },
552            ActionPlan::ActionDutchAuctionEnd(inner) => pb_t::ActionPlan {
553                action: Some(pb_t::action_plan::Action::ActionDutchAuctionEnd(
554                    inner.into(),
555                )),
556            },
557            ActionPlan::ActionDutchAuctionWithdraw(inner) => pb_t::ActionPlan {
558                action: Some(pb_t::action_plan::Action::ActionDutchAuctionWithdraw(
559                    inner.into(),
560                )),
561            },
562            ActionPlan::ActionLiquidityTournamentVote(inner) => pb_t::ActionPlan {
563                action: Some(pb_t::action_plan::Action::ActionLiquidityTournamentVote(
564                    inner.into(),
565                )),
566            },
567        }
568    }
569}
570
571impl TryFrom<pb_t::ActionPlan> for ActionPlan {
572    type Error = anyhow::Error;
573
574    fn try_from(proto: pb_t::ActionPlan) -> anyhow::Result<Self, Self::Error> {
575        if proto.action.is_none() {
576            anyhow::bail!("missing action content");
577        }
578
579        match proto
580            .action
581            .ok_or_else(|| anyhow!("missing action in ActionPlan proto"))?
582        {
583            pb_t::action_plan::Action::Output(inner) => Ok(ActionPlan::Output(inner.try_into()?)),
584            pb_t::action_plan::Action::Spend(inner) => Ok(ActionPlan::Spend(inner.try_into()?)),
585            pb_t::action_plan::Action::Delegate(inner) => {
586                Ok(ActionPlan::Delegate(inner.try_into()?))
587            }
588            pb_t::action_plan::Action::Undelegate(inner) => {
589                Ok(ActionPlan::Undelegate(inner.try_into()?))
590            }
591            pb_t::action_plan::Action::UndelegateClaim(inner) => {
592                Ok(ActionPlan::UndelegateClaim(inner.try_into()?))
593            }
594            pb_t::action_plan::Action::ValidatorDefinition(inner) => {
595                Ok(ActionPlan::ValidatorDefinition(inner.try_into()?))
596            }
597            pb_t::action_plan::Action::Swap(inner) => Ok(ActionPlan::Swap(inner.try_into()?)),
598            pb_t::action_plan::Action::SwapClaim(inner) => {
599                Ok(ActionPlan::SwapClaim(inner.try_into()?))
600            }
601            pb_t::action_plan::Action::IbcRelayAction(inner) => {
602                Ok(ActionPlan::IbcAction(inner.try_into()?))
603            }
604            pb_t::action_plan::Action::ProposalSubmit(inner) => {
605                Ok(ActionPlan::ProposalSubmit(inner.try_into()?))
606            }
607            pb_t::action_plan::Action::ProposalWithdraw(inner) => {
608                Ok(ActionPlan::ProposalWithdraw(inner.try_into()?))
609            }
610            pb_t::action_plan::Action::ValidatorVote(inner) => {
611                Ok(ActionPlan::ValidatorVote(inner.try_into()?))
612            }
613            pb_t::action_plan::Action::DelegatorVote(inner) => {
614                Ok(ActionPlan::DelegatorVote(inner.try_into()?))
615            }
616            pb_t::action_plan::Action::ProposalDepositClaim(inner) => {
617                Ok(ActionPlan::ProposalDepositClaim(inner.try_into()?))
618            }
619            // Deprecated, but we gracefully handle it anyways.
620            pb_t::action_plan::Action::PositionOpen(inner) => {
621                let position_open = PositionOpen::try_from(inner)?;
622                Ok(ActionPlan::PositionOpen(PositionOpenPlan {
623                    position: position_open.position,
624                    metadata: None,
625                }))
626            }
627            pb_t::action_plan::Action::PositionOpenPlan(inner) => {
628                Ok(ActionPlan::PositionOpen(inner.try_into()?))
629            }
630            pb_t::action_plan::Action::PositionClose(inner) => {
631                Ok(ActionPlan::PositionClose(inner.try_into()?))
632            }
633            pb_t::action_plan::Action::PositionWithdraw(inner) => {
634                Ok(ActionPlan::PositionWithdraw(inner.try_into()?))
635            }
636            pb_t::action_plan::Action::PositionRewardClaim(_) => {
637                Err(anyhow!("PositionRewardClaim is deprecated and unsupported"))
638            }
639            pb_t::action_plan::Action::CommunityPoolSpend(inner) => {
640                Ok(ActionPlan::CommunityPoolSpend(inner.try_into()?))
641            }
642            pb_t::action_plan::Action::CommunityPoolDeposit(inner) => {
643                Ok(ActionPlan::CommunityPoolDeposit(inner.try_into()?))
644            }
645            pb_t::action_plan::Action::CommunityPoolOutput(inner) => {
646                Ok(ActionPlan::CommunityPoolOutput(inner.try_into()?))
647            }
648            pb_t::action_plan::Action::ActionDutchAuctionSchedule(inner) => {
649                Ok(ActionPlan::ActionDutchAuctionSchedule(inner.try_into()?))
650            }
651            pb_t::action_plan::Action::ActionDutchAuctionEnd(inner) => {
652                Ok(ActionPlan::ActionDutchAuctionEnd(inner.try_into()?))
653            }
654            pb_t::action_plan::Action::ActionDutchAuctionWithdraw(inner) => {
655                Ok(ActionPlan::ActionDutchAuctionWithdraw(inner.try_into()?))
656            }
657            pb_t::action_plan::Action::Ics20Withdrawal(inner) => {
658                Ok(ActionPlan::Ics20Withdrawal(inner.try_into()?))
659            }
660            pb_t::action_plan::Action::ActionLiquidityTournamentVote(inner) => {
661                Ok(ActionPlan::ActionLiquidityTournamentVote(inner.try_into()?))
662            }
663        }
664    }
665}