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