1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
use anyhow::anyhow;
use penumbra_auction::auction::dutch::actions::{
    ActionDutchAuctionEnd, ActionDutchAuctionSchedule, ActionDutchAuctionWithdraw,
};
use penumbra_txhash::{EffectHash, EffectingData};
use std::convert::{TryFrom, TryInto};

use penumbra_asset::balance;
use penumbra_proto::{core::transaction::v1 as pb, DomainType};

use crate::{ActionView, IsAction, TransactionPerspective};
use serde::{Deserialize, Serialize};

/// An action performed by a Penumbra transaction.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(try_from = "pb::Action", into = "pb::Action")]
#[allow(clippy::large_enum_variant)]
pub enum Action {
    Output(penumbra_shielded_pool::Output),
    Spend(penumbra_shielded_pool::Spend),
    ValidatorDefinition(penumbra_stake::validator::Definition),
    IbcRelay(penumbra_ibc::IbcRelay),
    Swap(penumbra_dex::swap::Swap),
    SwapClaim(penumbra_dex::swap_claim::SwapClaim),
    ProposalSubmit(penumbra_governance::ProposalSubmit),
    ProposalWithdraw(penumbra_governance::ProposalWithdraw),
    DelegatorVote(penumbra_governance::DelegatorVote),
    ValidatorVote(penumbra_governance::ValidatorVote),
    ProposalDepositClaim(penumbra_governance::ProposalDepositClaim),

    PositionOpen(penumbra_dex::lp::action::PositionOpen),
    PositionClose(penumbra_dex::lp::action::PositionClose),
    PositionWithdraw(penumbra_dex::lp::action::PositionWithdraw),

    Delegate(penumbra_stake::Delegate),
    Undelegate(penumbra_stake::Undelegate),
    UndelegateClaim(penumbra_stake::UndelegateClaim),

    Ics20Withdrawal(penumbra_shielded_pool::Ics20Withdrawal),

    CommunityPoolSpend(penumbra_community_pool::CommunityPoolSpend),
    CommunityPoolOutput(penumbra_community_pool::CommunityPoolOutput),
    CommunityPoolDeposit(penumbra_community_pool::CommunityPoolDeposit),

    ActionDutchAuctionSchedule(ActionDutchAuctionSchedule),
    ActionDutchAuctionEnd(ActionDutchAuctionEnd),
    ActionDutchAuctionWithdraw(ActionDutchAuctionWithdraw),
}

impl EffectingData for Action {
    fn effect_hash(&self) -> EffectHash {
        match self {
            Action::Output(output) => output.effect_hash(),
            Action::Spend(spend) => spend.effect_hash(),
            Action::Delegate(delegate) => delegate.effect_hash(),
            Action::Undelegate(undelegate) => undelegate.effect_hash(),
            Action::UndelegateClaim(claim) => claim.effect_hash(),
            Action::ProposalSubmit(submit) => submit.effect_hash(),
            Action::ProposalWithdraw(withdraw) => withdraw.effect_hash(),
            Action::ProposalDepositClaim(claim) => claim.effect_hash(),
            Action::DelegatorVote(vote) => vote.effect_hash(),
            Action::ValidatorVote(vote) => vote.effect_hash(),
            Action::SwapClaim(swap_claim) => swap_claim.effect_hash(),
            Action::Swap(swap) => swap.effect_hash(),
            Action::ValidatorDefinition(defn) => defn.effect_hash(),
            Action::IbcRelay(payload) => payload.effect_hash(),
            Action::PositionOpen(p) => p.effect_hash(),
            Action::PositionClose(p) => p.effect_hash(),
            Action::PositionWithdraw(p) => p.effect_hash(),
            Action::Ics20Withdrawal(w) => w.effect_hash(),
            Action::CommunityPoolSpend(d) => d.effect_hash(),
            Action::CommunityPoolOutput(d) => d.effect_hash(),
            Action::CommunityPoolDeposit(d) => d.effect_hash(),
            Action::ActionDutchAuctionSchedule(a) => a.effect_hash(),
            Action::ActionDutchAuctionEnd(a) => a.effect_hash(),
            Action::ActionDutchAuctionWithdraw(a) => a.effect_hash(),
        }
    }
}

impl Action {
    /// Create a tracing span to track execution related to this action.
    ///
    /// The `idx` parameter is the index of this action in the transaction.
    pub fn create_span(&self, idx: usize) -> tracing::Span {
        match self {
            Action::Output(_) => tracing::info_span!("Output", ?idx),
            Action::Spend(_) => tracing::info_span!("Spend", ?idx),
            Action::ValidatorDefinition(_) => {
                tracing::info_span!("ValidatorDefinition", ?idx)
            }
            Action::IbcRelay(msg) => {
                // Construct a nested span, identifying the IbcAction within
                // the transaction but also the message within the IbcAction.
                let action_span = tracing::info_span!("IbcAction", ?idx);
                msg.create_span(&action_span)
            }
            Action::Swap(_) => tracing::info_span!("Swap", ?idx),
            Action::SwapClaim(_) => tracing::info_span!("SwapClaim", ?idx),
            Action::ProposalSubmit(_) => tracing::info_span!("ProposalSubmit", ?idx),
            Action::ProposalWithdraw(_) => {
                tracing::info_span!("ProposalWithdraw", ?idx)
            }
            Action::DelegatorVote(_) => tracing::info_span!("DelegatorVote", ?idx),
            Action::ValidatorVote(_) => tracing::info_span!("ValidatorVote", ?idx),
            Action::ProposalDepositClaim(_) => {
                tracing::info_span!("ProposalDepositClaim", ?idx)
            }
            Action::PositionOpen(_) => tracing::info_span!("PositionOpen", ?idx),
            Action::PositionClose(_) => tracing::info_span!("PositionClose", ?idx),
            Action::PositionWithdraw(_) => {
                tracing::info_span!("PositionWithdraw", ?idx)
            }
            Action::Delegate(_) => tracing::info_span!("Delegate", ?idx),
            Action::Undelegate(_) => tracing::info_span!("Undelegate", ?idx),
            Action::UndelegateClaim(_) => tracing::info_span!("UndelegateClaim", ?idx),
            Action::Ics20Withdrawal(_) => tracing::info_span!("Ics20Withdrawal", ?idx),
            Action::CommunityPoolDeposit(_) => tracing::info_span!("CommunityPoolDeposit", ?idx),
            Action::CommunityPoolSpend(_) => tracing::info_span!("CommunityPoolSpend", ?idx),
            Action::CommunityPoolOutput(_) => tracing::info_span!("CommunityPoolOutput", ?idx),
            Action::ActionDutchAuctionSchedule(_) => {
                tracing::info_span!("ActionDutchAuctionSchedule", ?idx)
            }
            Action::ActionDutchAuctionEnd(_) => tracing::info_span!("ActionDutchAuctionEnd", ?idx),
            Action::ActionDutchAuctionWithdraw(_) => {
                tracing::info_span!("ActionDutchAuctionWithdraw", ?idx)
            }
        }
    }
}

impl IsAction for Action {
    fn balance_commitment(&self) -> balance::Commitment {
        match self {
            Action::Output(output) => output.balance_commitment(),
            Action::Spend(spend) => spend.balance_commitment(),
            Action::Delegate(delegate) => delegate.balance_commitment(),
            Action::Undelegate(undelegate) => undelegate.balance_commitment(),
            Action::UndelegateClaim(undelegate_claim) => undelegate_claim.balance_commitment(),
            Action::Swap(swap) => swap.balance_commitment(),
            Action::SwapClaim(swap_claim) => swap_claim.balance_commitment(),
            Action::ProposalSubmit(submit) => submit.balance_commitment(),
            Action::ProposalWithdraw(withdraw) => withdraw.balance_commitment(),
            Action::DelegatorVote(delegator_vote) => delegator_vote.balance_commitment(),
            Action::ValidatorVote(validator_vote) => validator_vote.balance_commitment(),
            Action::ProposalDepositClaim(p) => p.balance_commitment(),
            Action::PositionOpen(p) => p.balance_commitment(),
            Action::PositionClose(p) => p.balance_commitment(),
            Action::PositionWithdraw(p) => p.balance_commitment(),
            Action::Ics20Withdrawal(withdrawal) => withdrawal.balance_commitment(),
            Action::CommunityPoolDeposit(deposit) => deposit.balance_commitment(),
            Action::CommunityPoolSpend(spend) => spend.balance_commitment(),
            Action::CommunityPoolOutput(output) => output.balance_commitment(),
            // These actions just post Protobuf data to the chain, and leave the
            // value balance unchanged.
            Action::IbcRelay(x) => x.balance_commitment(),
            Action::ValidatorDefinition(_) => balance::Commitment::default(),
            Action::ActionDutchAuctionSchedule(action) => action.balance_commitment(),
            Action::ActionDutchAuctionEnd(action) => action.balance_commitment(),
            Action::ActionDutchAuctionWithdraw(action) => action.balance_commitment(),
        }
    }

    fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
        match self {
            Action::Swap(x) => x.view_from_perspective(txp),
            Action::SwapClaim(x) => x.view_from_perspective(txp),
            Action::Output(x) => x.view_from_perspective(txp),
            Action::Spend(x) => x.view_from_perspective(txp),
            Action::Delegate(x) => x.view_from_perspective(txp),
            Action::Undelegate(x) => x.view_from_perspective(txp),
            Action::UndelegateClaim(x) => x.view_from_perspective(txp),
            Action::ProposalSubmit(x) => x.view_from_perspective(txp),
            Action::ProposalWithdraw(x) => x.view_from_perspective(txp),
            Action::DelegatorVote(x) => x.view_from_perspective(txp),
            Action::ValidatorVote(x) => x.view_from_perspective(txp),
            Action::ProposalDepositClaim(x) => x.view_from_perspective(txp),
            Action::PositionOpen(x) => x.view_from_perspective(txp),
            Action::PositionClose(x) => x.view_from_perspective(txp),
            Action::PositionWithdraw(x) => x.view_from_perspective(txp),
            Action::Ics20Withdrawal(x) => x.view_from_perspective(txp),
            Action::CommunityPoolSpend(x) => x.view_from_perspective(txp),
            Action::CommunityPoolOutput(x) => x.view_from_perspective(txp),
            Action::CommunityPoolDeposit(x) => x.view_from_perspective(txp),
            Action::ValidatorDefinition(x) => ActionView::ValidatorDefinition(x.to_owned()),
            Action::IbcRelay(x) => ActionView::IbcRelay(x.to_owned()),
            Action::ActionDutchAuctionSchedule(x) => x.view_from_perspective(txp),
            Action::ActionDutchAuctionEnd(x) => x.view_from_perspective(txp),
            Action::ActionDutchAuctionWithdraw(x) => x.view_from_perspective(txp),
        }
    }
}

impl DomainType for Action {
    type Proto = pb::Action;
}

impl From<Action> for pb::Action {
    fn from(msg: Action) -> Self {
        match msg {
            Action::Output(inner) => pb::Action {
                action: Some(pb::action::Action::Output(inner.into())),
            },
            Action::Spend(inner) => pb::Action {
                action: Some(pb::action::Action::Spend(inner.into())),
            },
            Action::Delegate(inner) => pb::Action {
                action: Some(pb::action::Action::Delegate(inner.into())),
            },
            Action::Undelegate(inner) => pb::Action {
                action: Some(pb::action::Action::Undelegate(inner.into())),
            },
            Action::UndelegateClaim(inner) => pb::Action {
                action: Some(pb::action::Action::UndelegateClaim(inner.into())),
            },
            Action::ValidatorDefinition(inner) => pb::Action {
                action: Some(pb::action::Action::ValidatorDefinition(inner.into())),
            },
            Action::SwapClaim(inner) => pb::Action {
                action: Some(pb::action::Action::SwapClaim(inner.into())),
            },
            Action::Swap(inner) => pb::Action {
                action: Some(pb::action::Action::Swap(inner.into())),
            },
            Action::IbcRelay(inner) => pb::Action {
                action: Some(pb::action::Action::IbcRelayAction(inner.into())),
            },
            Action::ProposalSubmit(inner) => pb::Action {
                action: Some(pb::action::Action::ProposalSubmit(inner.into())),
            },
            Action::ProposalWithdraw(inner) => pb::Action {
                action: Some(pb::action::Action::ProposalWithdraw(inner.into())),
            },
            Action::DelegatorVote(inner) => pb::Action {
                action: Some(pb::action::Action::DelegatorVote(inner.into())),
            },
            Action::ValidatorVote(inner) => pb::Action {
                action: Some(pb::action::Action::ValidatorVote(inner.into())),
            },
            Action::ProposalDepositClaim(inner) => pb::Action {
                action: Some(pb::action::Action::ProposalDepositClaim(inner.into())),
            },
            Action::PositionOpen(inner) => pb::Action {
                action: Some(pb::action::Action::PositionOpen(inner.into())),
            },
            Action::PositionClose(inner) => pb::Action {
                action: Some(pb::action::Action::PositionClose(inner.into())),
            },
            Action::PositionWithdraw(inner) => pb::Action {
                action: Some(pb::action::Action::PositionWithdraw(inner.into())),
            },
            Action::Ics20Withdrawal(withdrawal) => pb::Action {
                action: Some(pb::action::Action::Ics20Withdrawal(withdrawal.into())),
            },
            Action::CommunityPoolSpend(inner) => pb::Action {
                action: Some(pb::action::Action::CommunityPoolSpend(inner.into())),
            },
            Action::CommunityPoolOutput(inner) => pb::Action {
                action: Some(pb::action::Action::CommunityPoolOutput(inner.into())),
            },
            Action::CommunityPoolDeposit(inner) => pb::Action {
                action: Some(pb::action::Action::CommunityPoolDeposit(inner.into())),
            },
            Action::ActionDutchAuctionSchedule(inner) => pb::Action {
                action: Some(pb::action::Action::ActionDutchAuctionSchedule(inner.into())),
            },
            Action::ActionDutchAuctionEnd(inner) => pb::Action {
                action: Some(pb::action::Action::ActionDutchAuctionEnd(inner.into())),
            },
            Action::ActionDutchAuctionWithdraw(inner) => pb::Action {
                action: Some(pb::action::Action::ActionDutchAuctionWithdraw(inner.into())),
            },
        }
    }
}

impl TryFrom<pb::Action> for Action {
    type Error = anyhow::Error;
    fn try_from(proto: pb::Action) -> anyhow::Result<Self, Self::Error> {
        if proto.action.is_none() {
            anyhow::bail!("missing action content");
        }
        match proto
            .action
            .ok_or_else(|| anyhow!("missing action in Action protobuf"))?
        {
            pb::action::Action::Output(inner) => Ok(Action::Output(inner.try_into()?)),
            pb::action::Action::Spend(inner) => Ok(Action::Spend(inner.try_into()?)),
            pb::action::Action::Delegate(inner) => Ok(Action::Delegate(inner.try_into()?)),
            pb::action::Action::Undelegate(inner) => Ok(Action::Undelegate(inner.try_into()?)),
            pb::action::Action::UndelegateClaim(inner) => {
                Ok(Action::UndelegateClaim(inner.try_into()?))
            }
            pb::action::Action::ValidatorDefinition(inner) => {
                Ok(Action::ValidatorDefinition(inner.try_into()?))
            }
            pb::action::Action::SwapClaim(inner) => Ok(Action::SwapClaim(inner.try_into()?)),
            pb::action::Action::Swap(inner) => Ok(Action::Swap(inner.try_into()?)),
            pb::action::Action::IbcRelayAction(inner) => Ok(Action::IbcRelay(inner.try_into()?)),
            pb::action::Action::ProposalSubmit(inner) => {
                Ok(Action::ProposalSubmit(inner.try_into()?))
            }
            pb::action::Action::ProposalWithdraw(inner) => {
                Ok(Action::ProposalWithdraw(inner.try_into()?))
            }
            pb::action::Action::DelegatorVote(inner) => {
                Ok(Action::DelegatorVote(inner.try_into()?))
            }
            pb::action::Action::ValidatorVote(inner) => {
                Ok(Action::ValidatorVote(inner.try_into()?))
            }
            pb::action::Action::ProposalDepositClaim(inner) => {
                Ok(Action::ProposalDepositClaim(inner.try_into()?))
            }

            pb::action::Action::PositionOpen(inner) => Ok(Action::PositionOpen(inner.try_into()?)),
            pb::action::Action::PositionClose(inner) => {
                Ok(Action::PositionClose(inner.try_into()?))
            }
            pb::action::Action::PositionWithdraw(inner) => {
                Ok(Action::PositionWithdraw(inner.try_into()?))
            }
            pb::action::Action::PositionRewardClaim(_) => {
                Err(anyhow!("PositionRewardClaim is deprecated and unsupported"))
            }
            pb::action::Action::Ics20Withdrawal(inner) => {
                Ok(Action::Ics20Withdrawal(inner.try_into()?))
            }
            pb::action::Action::CommunityPoolSpend(inner) => {
                Ok(Action::CommunityPoolSpend(inner.try_into()?))
            }
            pb::action::Action::CommunityPoolOutput(inner) => {
                Ok(Action::CommunityPoolOutput(inner.try_into()?))
            }
            pb::action::Action::CommunityPoolDeposit(inner) => {
                Ok(Action::CommunityPoolDeposit(inner.try_into()?))
            }
            pb::action::Action::ActionDutchAuctionSchedule(inner) => {
                Ok(Action::ActionDutchAuctionSchedule(inner.try_into()?))
            }
            pb::action::Action::ActionDutchAuctionEnd(inner) => {
                Ok(Action::ActionDutchAuctionEnd(inner.try_into()?))
            }
            pb::action::Action::ActionDutchAuctionWithdraw(inner) => {
                Ok(Action::ActionDutchAuctionWithdraw(inner.try_into()?))
            }
        }
    }
}