penumbra_sdk_transaction/
action.rs

1use anyhow::anyhow;
2use penumbra_sdk_auction::auction::dutch::actions::{
3    ActionDutchAuctionEnd, ActionDutchAuctionSchedule, ActionDutchAuctionWithdraw,
4};
5use penumbra_sdk_txhash::{EffectHash, EffectingData};
6use std::convert::{TryFrom, TryInto};
7
8use penumbra_sdk_asset::balance;
9use penumbra_sdk_proto::{core::transaction::v1 as pb, DomainType};
10
11use crate::{ActionView, IsAction, TransactionPerspective};
12use serde::{Deserialize, Serialize};
13
14/// An action performed by a Penumbra transaction.
15#[derive(Clone, Debug, Deserialize, Serialize)]
16#[serde(try_from = "pb::Action", into = "pb::Action")]
17#[allow(clippy::large_enum_variant)]
18pub enum Action {
19    Output(penumbra_sdk_shielded_pool::Output),
20    Spend(penumbra_sdk_shielded_pool::Spend),
21    ValidatorDefinition(penumbra_sdk_stake::validator::Definition),
22    IbcRelay(penumbra_sdk_ibc::IbcRelay),
23    Swap(penumbra_sdk_dex::swap::Swap),
24    SwapClaim(penumbra_sdk_dex::swap_claim::SwapClaim),
25    ProposalSubmit(penumbra_sdk_governance::ProposalSubmit),
26    ProposalWithdraw(penumbra_sdk_governance::ProposalWithdraw),
27    DelegatorVote(penumbra_sdk_governance::DelegatorVote),
28    ValidatorVote(penumbra_sdk_governance::ValidatorVote),
29    ProposalDepositClaim(penumbra_sdk_governance::ProposalDepositClaim),
30
31    PositionOpen(penumbra_sdk_dex::lp::action::PositionOpen),
32    PositionClose(penumbra_sdk_dex::lp::action::PositionClose),
33    PositionWithdraw(penumbra_sdk_dex::lp::action::PositionWithdraw),
34
35    Delegate(penumbra_sdk_stake::Delegate),
36    Undelegate(penumbra_sdk_stake::Undelegate),
37    UndelegateClaim(penumbra_sdk_stake::UndelegateClaim),
38
39    Ics20Withdrawal(penumbra_sdk_shielded_pool::Ics20Withdrawal),
40
41    CommunityPoolSpend(penumbra_sdk_community_pool::CommunityPoolSpend),
42    CommunityPoolOutput(penumbra_sdk_community_pool::CommunityPoolOutput),
43    CommunityPoolDeposit(penumbra_sdk_community_pool::CommunityPoolDeposit),
44
45    ActionDutchAuctionSchedule(ActionDutchAuctionSchedule),
46    ActionDutchAuctionEnd(ActionDutchAuctionEnd),
47    ActionDutchAuctionWithdraw(ActionDutchAuctionWithdraw),
48    ActionLiquidityTournamentVote(
49        penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVote,
50    ),
51}
52
53impl EffectingData for Action {
54    fn effect_hash(&self) -> EffectHash {
55        match self {
56            Action::Output(output) => output.effect_hash(),
57            Action::Spend(spend) => spend.effect_hash(),
58            Action::Delegate(delegate) => delegate.effect_hash(),
59            Action::Undelegate(undelegate) => undelegate.effect_hash(),
60            Action::UndelegateClaim(claim) => claim.effect_hash(),
61            Action::ProposalSubmit(submit) => submit.effect_hash(),
62            Action::ProposalWithdraw(withdraw) => withdraw.effect_hash(),
63            Action::ProposalDepositClaim(claim) => claim.effect_hash(),
64            Action::DelegatorVote(vote) => vote.effect_hash(),
65            Action::ValidatorVote(vote) => vote.effect_hash(),
66            Action::SwapClaim(swap_claim) => swap_claim.effect_hash(),
67            Action::Swap(swap) => swap.effect_hash(),
68            Action::ValidatorDefinition(defn) => defn.effect_hash(),
69            Action::IbcRelay(payload) => payload.effect_hash(),
70            Action::PositionOpen(p) => p.effect_hash(),
71            Action::PositionClose(p) => p.effect_hash(),
72            Action::PositionWithdraw(p) => p.effect_hash(),
73            Action::Ics20Withdrawal(w) => w.effect_hash(),
74            Action::CommunityPoolSpend(d) => d.effect_hash(),
75            Action::CommunityPoolOutput(d) => d.effect_hash(),
76            Action::CommunityPoolDeposit(d) => d.effect_hash(),
77            Action::ActionDutchAuctionSchedule(a) => a.effect_hash(),
78            Action::ActionDutchAuctionEnd(a) => a.effect_hash(),
79            Action::ActionDutchAuctionWithdraw(a) => a.effect_hash(),
80            Action::ActionLiquidityTournamentVote(a) => a.effect_hash(),
81        }
82    }
83}
84
85impl Action {
86    /// Create a tracing span to track execution related to this action.
87    ///
88    /// The `idx` parameter is the index of this action in the transaction.
89    pub fn create_span(&self, idx: usize) -> tracing::Span {
90        match self {
91            Action::Output(_) => tracing::info_span!("Output", ?idx),
92            Action::Spend(_) => tracing::info_span!("Spend", ?idx),
93            Action::ValidatorDefinition(_) => {
94                tracing::info_span!("ValidatorDefinition", ?idx)
95            }
96            Action::IbcRelay(msg) => {
97                // Construct a nested span, identifying the IbcAction within
98                // the transaction but also the message within the IbcAction.
99                let action_span = tracing::info_span!("IbcAction", ?idx);
100                msg.create_span(&action_span)
101            }
102            Action::Swap(_) => tracing::info_span!("Swap", ?idx),
103            Action::SwapClaim(_) => tracing::info_span!("SwapClaim", ?idx),
104            Action::ProposalSubmit(_) => tracing::info_span!("ProposalSubmit", ?idx),
105            Action::ProposalWithdraw(_) => {
106                tracing::info_span!("ProposalWithdraw", ?idx)
107            }
108            Action::DelegatorVote(_) => tracing::info_span!("DelegatorVote", ?idx),
109            Action::ValidatorVote(_) => tracing::info_span!("ValidatorVote", ?idx),
110            Action::ProposalDepositClaim(_) => {
111                tracing::info_span!("ProposalDepositClaim", ?idx)
112            }
113            Action::PositionOpen(_) => tracing::info_span!("PositionOpen", ?idx),
114            Action::PositionClose(_) => tracing::info_span!("PositionClose", ?idx),
115            Action::PositionWithdraw(_) => {
116                tracing::info_span!("PositionWithdraw", ?idx)
117            }
118            Action::Delegate(_) => tracing::info_span!("Delegate", ?idx),
119            Action::Undelegate(_) => tracing::info_span!("Undelegate", ?idx),
120            Action::UndelegateClaim(_) => tracing::info_span!("UndelegateClaim", ?idx),
121            Action::Ics20Withdrawal(_) => tracing::info_span!("Ics20Withdrawal", ?idx),
122            Action::CommunityPoolDeposit(_) => tracing::info_span!("CommunityPoolDeposit", ?idx),
123            Action::CommunityPoolSpend(_) => tracing::info_span!("CommunityPoolSpend", ?idx),
124            Action::CommunityPoolOutput(_) => tracing::info_span!("CommunityPoolOutput", ?idx),
125            Action::ActionDutchAuctionSchedule(_) => {
126                tracing::info_span!("ActionDutchAuctionSchedule", ?idx)
127            }
128            Action::ActionDutchAuctionEnd(_) => tracing::info_span!("ActionDutchAuctionEnd", ?idx),
129            Action::ActionDutchAuctionWithdraw(_) => {
130                tracing::info_span!("ActionDutchAuctionWithdraw", ?idx)
131            }
132            Action::ActionLiquidityTournamentVote(_) => {
133                tracing::info_span!("ActionLiquidityTournamentVote", ?idx)
134            }
135        }
136    }
137
138    /// Canonical action ordering according to protobuf definitions
139    pub fn variant_index(&self) -> usize {
140        match self {
141            Action::Spend(_) => 1,
142            Action::Output(_) => 2,
143            Action::Swap(_) => 3,
144            Action::SwapClaim(_) => 4,
145            Action::ValidatorDefinition(_) => 16,
146            Action::IbcRelay(_) => 17,
147            Action::ProposalSubmit(_) => 18,
148            Action::ProposalWithdraw(_) => 19,
149            Action::ValidatorVote(_) => 20,
150            Action::DelegatorVote(_) => 21,
151            Action::ProposalDepositClaim(_) => 22,
152            Action::PositionOpen(_) => 30,
153            Action::PositionClose(_) => 31,
154            Action::PositionWithdraw(_) => 32,
155            Action::Delegate(_) => 40,
156            Action::Undelegate(_) => 41,
157            Action::UndelegateClaim(_) => 42,
158            Action::CommunityPoolSpend(_) => 50,
159            Action::CommunityPoolOutput(_) => 51,
160            Action::CommunityPoolDeposit(_) => 52,
161            Action::Ics20Withdrawal(_) => 200,
162            Action::ActionDutchAuctionSchedule(_) => 53,
163            Action::ActionDutchAuctionEnd(_) => 54,
164            Action::ActionDutchAuctionWithdraw(_) => 55,
165            Action::ActionLiquidityTournamentVote(_) => 70,
166        }
167    }
168}
169
170impl IsAction for Action {
171    fn balance_commitment(&self) -> balance::Commitment {
172        match self {
173            Action::Output(output) => output.balance_commitment(),
174            Action::Spend(spend) => spend.balance_commitment(),
175            Action::Delegate(delegate) => delegate.balance_commitment(),
176            Action::Undelegate(undelegate) => undelegate.balance_commitment(),
177            Action::UndelegateClaim(undelegate_claim) => undelegate_claim.balance_commitment(),
178            Action::Swap(swap) => swap.balance_commitment(),
179            Action::SwapClaim(swap_claim) => swap_claim.balance_commitment(),
180            Action::ProposalSubmit(submit) => submit.balance_commitment(),
181            Action::ProposalWithdraw(withdraw) => withdraw.balance_commitment(),
182            Action::DelegatorVote(delegator_vote) => delegator_vote.balance_commitment(),
183            Action::ValidatorVote(validator_vote) => validator_vote.balance_commitment(),
184            Action::ProposalDepositClaim(p) => p.balance_commitment(),
185            Action::PositionOpen(p) => p.balance_commitment(),
186            Action::PositionClose(p) => p.balance_commitment(),
187            Action::PositionWithdraw(p) => p.balance_commitment(),
188            Action::Ics20Withdrawal(withdrawal) => withdrawal.balance_commitment(),
189            Action::CommunityPoolDeposit(deposit) => deposit.balance_commitment(),
190            Action::CommunityPoolSpend(spend) => spend.balance_commitment(),
191            Action::CommunityPoolOutput(output) => output.balance_commitment(),
192            // These actions just post Protobuf data to the chain, and leave the
193            // value balance unchanged.
194            Action::IbcRelay(x) => x.balance_commitment(),
195            Action::ValidatorDefinition(_) => balance::Commitment::default(),
196            Action::ActionDutchAuctionSchedule(action) => action.balance_commitment(),
197            Action::ActionDutchAuctionEnd(action) => action.balance_commitment(),
198            Action::ActionDutchAuctionWithdraw(action) => action.balance_commitment(),
199            Action::ActionLiquidityTournamentVote(action) => action.balance_commitment(),
200        }
201    }
202
203    fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
204        match self {
205            Action::Swap(x) => x.view_from_perspective(txp),
206            Action::SwapClaim(x) => x.view_from_perspective(txp),
207            Action::Output(x) => x.view_from_perspective(txp),
208            Action::Spend(x) => x.view_from_perspective(txp),
209            Action::Delegate(x) => x.view_from_perspective(txp),
210            Action::Undelegate(x) => x.view_from_perspective(txp),
211            Action::UndelegateClaim(x) => x.view_from_perspective(txp),
212            Action::ProposalSubmit(x) => x.view_from_perspective(txp),
213            Action::ProposalWithdraw(x) => x.view_from_perspective(txp),
214            Action::DelegatorVote(x) => x.view_from_perspective(txp),
215            Action::ValidatorVote(x) => x.view_from_perspective(txp),
216            Action::ProposalDepositClaim(x) => x.view_from_perspective(txp),
217            Action::PositionOpen(x) => x.view_from_perspective(txp),
218            Action::PositionClose(x) => x.view_from_perspective(txp),
219            Action::PositionWithdraw(x) => x.view_from_perspective(txp),
220            Action::Ics20Withdrawal(x) => x.view_from_perspective(txp),
221            Action::CommunityPoolSpend(x) => x.view_from_perspective(txp),
222            Action::CommunityPoolOutput(x) => x.view_from_perspective(txp),
223            Action::CommunityPoolDeposit(x) => x.view_from_perspective(txp),
224            Action::ValidatorDefinition(x) => ActionView::ValidatorDefinition(x.to_owned()),
225            Action::IbcRelay(x) => ActionView::IbcRelay(x.to_owned()),
226            Action::ActionDutchAuctionSchedule(x) => x.view_from_perspective(txp),
227            Action::ActionDutchAuctionEnd(x) => x.view_from_perspective(txp),
228            Action::ActionDutchAuctionWithdraw(x) => x.view_from_perspective(txp),
229            Action::ActionLiquidityTournamentVote(x) => x.view_from_perspective(txp),
230        }
231    }
232}
233
234impl DomainType for Action {
235    type Proto = pb::Action;
236}
237
238impl From<Action> for pb::Action {
239    fn from(msg: Action) -> Self {
240        match msg {
241            Action::Output(inner) => pb::Action {
242                action: Some(pb::action::Action::Output(inner.into())),
243            },
244            Action::Spend(inner) => pb::Action {
245                action: Some(pb::action::Action::Spend(inner.into())),
246            },
247            Action::Delegate(inner) => pb::Action {
248                action: Some(pb::action::Action::Delegate(inner.into())),
249            },
250            Action::Undelegate(inner) => pb::Action {
251                action: Some(pb::action::Action::Undelegate(inner.into())),
252            },
253            Action::UndelegateClaim(inner) => pb::Action {
254                action: Some(pb::action::Action::UndelegateClaim(inner.into())),
255            },
256            Action::ValidatorDefinition(inner) => pb::Action {
257                action: Some(pb::action::Action::ValidatorDefinition(inner.into())),
258            },
259            Action::SwapClaim(inner) => pb::Action {
260                action: Some(pb::action::Action::SwapClaim(inner.into())),
261            },
262            Action::Swap(inner) => pb::Action {
263                action: Some(pb::action::Action::Swap(inner.into())),
264            },
265            Action::IbcRelay(inner) => pb::Action {
266                action: Some(pb::action::Action::IbcRelayAction(inner.into())),
267            },
268            Action::ProposalSubmit(inner) => pb::Action {
269                action: Some(pb::action::Action::ProposalSubmit(inner.into())),
270            },
271            Action::ProposalWithdraw(inner) => pb::Action {
272                action: Some(pb::action::Action::ProposalWithdraw(inner.into())),
273            },
274            Action::DelegatorVote(inner) => pb::Action {
275                action: Some(pb::action::Action::DelegatorVote(inner.into())),
276            },
277            Action::ValidatorVote(inner) => pb::Action {
278                action: Some(pb::action::Action::ValidatorVote(inner.into())),
279            },
280            Action::ProposalDepositClaim(inner) => pb::Action {
281                action: Some(pb::action::Action::ProposalDepositClaim(inner.into())),
282            },
283            Action::PositionOpen(inner) => pb::Action {
284                action: Some(pb::action::Action::PositionOpen(inner.into())),
285            },
286            Action::PositionClose(inner) => pb::Action {
287                action: Some(pb::action::Action::PositionClose(inner.into())),
288            },
289            Action::PositionWithdraw(inner) => pb::Action {
290                action: Some(pb::action::Action::PositionWithdraw(inner.into())),
291            },
292            Action::Ics20Withdrawal(withdrawal) => pb::Action {
293                action: Some(pb::action::Action::Ics20Withdrawal(withdrawal.into())),
294            },
295            Action::CommunityPoolSpend(inner) => pb::Action {
296                action: Some(pb::action::Action::CommunityPoolSpend(inner.into())),
297            },
298            Action::CommunityPoolOutput(inner) => pb::Action {
299                action: Some(pb::action::Action::CommunityPoolOutput(inner.into())),
300            },
301            Action::CommunityPoolDeposit(inner) => pb::Action {
302                action: Some(pb::action::Action::CommunityPoolDeposit(inner.into())),
303            },
304            Action::ActionDutchAuctionSchedule(inner) => pb::Action {
305                action: Some(pb::action::Action::ActionDutchAuctionSchedule(inner.into())),
306            },
307            Action::ActionDutchAuctionEnd(inner) => pb::Action {
308                action: Some(pb::action::Action::ActionDutchAuctionEnd(inner.into())),
309            },
310            Action::ActionDutchAuctionWithdraw(inner) => pb::Action {
311                action: Some(pb::action::Action::ActionDutchAuctionWithdraw(inner.into())),
312            },
313            Action::ActionLiquidityTournamentVote(inner) => pb::Action {
314                action: Some(pb::action::Action::ActionLiquidityTournamentVote(
315                    inner.into(),
316                )),
317            },
318        }
319    }
320}
321
322impl TryFrom<pb::Action> for Action {
323    type Error = anyhow::Error;
324    fn try_from(proto: pb::Action) -> anyhow::Result<Self, Self::Error> {
325        if proto.action.is_none() {
326            anyhow::bail!("missing action content");
327        }
328        match proto
329            .action
330            .ok_or_else(|| anyhow!("missing action in Action protobuf"))?
331        {
332            pb::action::Action::Output(inner) => Ok(Action::Output(inner.try_into()?)),
333            pb::action::Action::Spend(inner) => Ok(Action::Spend(inner.try_into()?)),
334            pb::action::Action::Delegate(inner) => Ok(Action::Delegate(inner.try_into()?)),
335            pb::action::Action::Undelegate(inner) => Ok(Action::Undelegate(inner.try_into()?)),
336            pb::action::Action::UndelegateClaim(inner) => {
337                Ok(Action::UndelegateClaim(inner.try_into()?))
338            }
339            pb::action::Action::ValidatorDefinition(inner) => {
340                Ok(Action::ValidatorDefinition(inner.try_into()?))
341            }
342            pb::action::Action::SwapClaim(inner) => Ok(Action::SwapClaim(inner.try_into()?)),
343            pb::action::Action::Swap(inner) => Ok(Action::Swap(inner.try_into()?)),
344            pb::action::Action::IbcRelayAction(inner) => Ok(Action::IbcRelay(inner.try_into()?)),
345            pb::action::Action::ProposalSubmit(inner) => {
346                Ok(Action::ProposalSubmit(inner.try_into()?))
347            }
348            pb::action::Action::ProposalWithdraw(inner) => {
349                Ok(Action::ProposalWithdraw(inner.try_into()?))
350            }
351            pb::action::Action::DelegatorVote(inner) => {
352                Ok(Action::DelegatorVote(inner.try_into()?))
353            }
354            pb::action::Action::ValidatorVote(inner) => {
355                Ok(Action::ValidatorVote(inner.try_into()?))
356            }
357            pb::action::Action::ProposalDepositClaim(inner) => {
358                Ok(Action::ProposalDepositClaim(inner.try_into()?))
359            }
360
361            pb::action::Action::PositionOpen(inner) => Ok(Action::PositionOpen(inner.try_into()?)),
362            pb::action::Action::PositionClose(inner) => {
363                Ok(Action::PositionClose(inner.try_into()?))
364            }
365            pb::action::Action::PositionWithdraw(inner) => {
366                Ok(Action::PositionWithdraw(inner.try_into()?))
367            }
368            pb::action::Action::PositionRewardClaim(_) => {
369                Err(anyhow!("PositionRewardClaim is deprecated and unsupported"))
370            }
371            pb::action::Action::Ics20Withdrawal(inner) => {
372                Ok(Action::Ics20Withdrawal(inner.try_into()?))
373            }
374            pb::action::Action::CommunityPoolSpend(inner) => {
375                Ok(Action::CommunityPoolSpend(inner.try_into()?))
376            }
377            pb::action::Action::CommunityPoolOutput(inner) => {
378                Ok(Action::CommunityPoolOutput(inner.try_into()?))
379            }
380            pb::action::Action::CommunityPoolDeposit(inner) => {
381                Ok(Action::CommunityPoolDeposit(inner.try_into()?))
382            }
383            pb::action::Action::ActionDutchAuctionSchedule(inner) => {
384                Ok(Action::ActionDutchAuctionSchedule(inner.try_into()?))
385            }
386            pb::action::Action::ActionDutchAuctionEnd(inner) => {
387                Ok(Action::ActionDutchAuctionEnd(inner.try_into()?))
388            }
389            pb::action::Action::ActionDutchAuctionWithdraw(inner) => {
390                Ok(Action::ActionDutchAuctionWithdraw(inner.try_into()?))
391            }
392            pb::action::Action::ActionLiquidityTournamentVote(inner) => {
393                Ok(Action::ActionLiquidityTournamentVote(inner.try_into()?))
394            }
395        }
396    }
397}