penumbra_sdk_transaction/
is_action.rs1use ark_ff::Zero;
2use decaf377::Fr;
3use penumbra_sdk_asset::{balance, Value};
4use penumbra_sdk_auction::auction::dutch::actions::{
5 view::{ActionDutchAuctionScheduleView, ActionDutchAuctionWithdrawView},
6 ActionDutchAuctionEnd, ActionDutchAuctionSchedule, ActionDutchAuctionWithdraw,
7};
8use penumbra_sdk_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend};
9use penumbra_sdk_dex::{
10 lp::{
11 action::{PositionClose, PositionOpen, PositionWithdraw},
12 position, LpNft,
13 },
14 swap::{Swap, SwapCiphertext, SwapView},
15 swap_claim::{SwapClaim, SwapClaimView},
16};
17use penumbra_sdk_governance::{
18 DelegatorVote, DelegatorVoteView, ProposalDepositClaim, ProposalSubmit, ProposalWithdraw,
19 ValidatorVote, VotingReceiptToken,
20};
21use penumbra_sdk_ibc::IbcRelay;
22use penumbra_sdk_shielded_pool::{Ics20Withdrawal, Note, Output, OutputView, Spend, SpendView};
23use penumbra_sdk_stake::{Delegate, Undelegate, UndelegateClaim};
24
25use crate::{Action, ActionView, TransactionPerspective};
26
27pub trait IsAction {
32 fn balance_commitment(&self) -> balance::Commitment;
33 fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView;
34}
35
36impl From<DelegatorVote> for Action {
39 fn from(value: DelegatorVote) -> Self {
40 Action::DelegatorVote(value)
41 }
42}
43
44impl IsAction for DelegatorVote {
45 fn balance_commitment(&self) -> balance::Commitment {
46 Value {
47 amount: self.body.unbonded_amount,
48 asset_id: VotingReceiptToken::new(self.body.proposal).id(),
49 }
50 .commit(Fr::zero())
51 }
52
53 fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
54 let delegator_vote_view = match txp.spend_nullifiers.get(&self.body.nullifier) {
55 Some(note) => DelegatorVoteView::Visible {
56 delegator_vote: self.to_owned(),
57 note: txp.view_note(note.to_owned()),
58 },
59 None => DelegatorVoteView::Opaque {
60 delegator_vote: self.to_owned(),
61 },
62 };
63
64 ActionView::DelegatorVote(delegator_vote_view)
65 }
66}
67
68impl IsAction for ProposalDepositClaim {
69 fn balance_commitment(&self) -> balance::Commitment {
70 self.balance().commit(Fr::zero())
71 }
72
73 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
74 ActionView::ProposalDepositClaim(self.clone())
75 }
76}
77
78impl IsAction for ProposalSubmit {
79 fn balance_commitment(&self) -> balance::Commitment {
80 self.balance().commit(Fr::zero())
81 }
82
83 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
84 ActionView::ProposalSubmit(self.to_owned())
85 }
86}
87
88impl IsAction for ProposalWithdraw {
89 fn balance_commitment(&self) -> balance::Commitment {
90 self.balance().commit(Fr::zero())
91 }
92
93 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
94 ActionView::ProposalWithdraw(self.to_owned())
95 }
96}
97
98impl IsAction for ValidatorVote {
99 fn balance_commitment(&self) -> balance::Commitment {
100 Default::default()
101 }
102
103 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
104 ActionView::ValidatorVote(self.to_owned())
105 }
106}
107
108impl IsAction for Output {
109 fn balance_commitment(&self) -> balance::Commitment {
110 self.body.balance_commitment
111 }
112
113 fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
114 let note_commitment = self.body.note_payload.note_commitment;
115 let epk = self.body.note_payload.ephemeral_key;
116 let output_view = if let Some(payload_key) = txp.payload_keys.get(¬e_commitment) {
118 let decrypted_note = Note::decrypt_with_payload_key(
119 &self.body.note_payload.encrypted_note,
120 payload_key,
121 &epk,
122 );
123
124 let decrypted_memo_key = self.body.wrapped_memo_key.decrypt_outgoing(payload_key);
125
126 if let (Ok(decrypted_note), Ok(decrypted_memo_key)) =
127 (decrypted_note, decrypted_memo_key)
128 {
129 OutputView::Visible {
131 output: self.to_owned(),
132 note: txp.view_note(decrypted_note),
133 payload_key: decrypted_memo_key,
134 }
135 } else {
136 OutputView::Opaque {
138 output: self.to_owned(),
139 }
140 }
141 } else {
142 OutputView::Opaque {
144 output: self.to_owned(),
145 }
146 };
147
148 ActionView::Output(output_view)
149 }
150}
151
152impl IsAction for Spend {
153 fn balance_commitment(&self) -> balance::Commitment {
154 self.body.balance_commitment
155 }
156
157 fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
158 let spend_view = match txp.spend_nullifiers.get(&self.body.nullifier) {
159 Some(note) => SpendView::Visible {
160 spend: self.to_owned(),
161 note: txp.view_note(note.to_owned()),
162 },
163 None => SpendView::Opaque {
164 spend: self.to_owned(),
165 },
166 };
167
168 ActionView::Spend(spend_view)
169 }
170}
171
172impl IsAction for Delegate {
173 fn balance_commitment(&self) -> balance::Commitment {
174 self.balance().commit(Fr::zero())
175 }
176
177 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
178 ActionView::Delegate(self.to_owned())
179 }
180}
181
182impl IsAction for Undelegate {
183 fn balance_commitment(&self) -> balance::Commitment {
184 self.balance().commit(Fr::zero())
185 }
186
187 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
188 ActionView::Undelegate(self.to_owned())
189 }
190}
191
192impl IsAction for UndelegateClaim {
193 fn balance_commitment(&self) -> balance::Commitment {
194 self.body.balance_commitment
195 }
196
197 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
198 ActionView::UndelegateClaim(self.to_owned())
199 }
200}
201
202impl IsAction for IbcRelay {
203 fn balance_commitment(&self) -> balance::Commitment {
204 Default::default()
205 }
206
207 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
208 ActionView::IbcRelay(self.clone())
209 }
210}
211
212impl IsAction for Ics20Withdrawal {
213 fn balance_commitment(&self) -> balance::Commitment {
214 self.balance().commit(Fr::zero())
215 }
216
217 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
218 ActionView::Ics20Withdrawal(self.to_owned())
219 }
220}
221
222impl IsAction for CommunityPoolDeposit {
223 fn balance_commitment(&self) -> balance::Commitment {
224 self.balance().commit(Fr::zero())
225 }
226
227 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
228 ActionView::CommunityPoolDeposit(self.clone())
229 }
230}
231
232impl IsAction for CommunityPoolOutput {
233 fn balance_commitment(&self) -> balance::Commitment {
234 self.balance().commit(Fr::zero())
236 }
237
238 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
239 ActionView::CommunityPoolOutput(self.clone())
240 }
241}
242
243impl IsAction for CommunityPoolSpend {
244 fn balance_commitment(&self) -> balance::Commitment {
245 self.balance().commit(Fr::zero())
246 }
247
248 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
249 ActionView::CommunityPoolSpend(self.clone())
250 }
251}
252
253impl IsAction for PositionOpen {
254 fn balance_commitment(&self) -> balance::Commitment {
255 self.balance().commit(Fr::zero())
256 }
257
258 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
259 ActionView::PositionOpen(self.to_owned())
260 }
261}
262
263impl IsAction for PositionClose {
264 fn balance_commitment(&self) -> balance::Commitment {
265 self.balance().commit(Fr::zero())
266 }
267
268 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
269 ActionView::PositionClose(self.to_owned())
270 }
271}
272
273impl IsAction for PositionWithdraw {
274 fn balance_commitment(&self) -> balance::Commitment {
275 let prev_state_nft = if self.sequence == 0 {
276 Value {
277 amount: 1u64.into(),
278 asset_id: LpNft::new(self.position_id, position::State::Closed).asset_id(),
279 }
280 } else {
281 Value {
282 amount: 1u64.into(),
283 asset_id: LpNft::new(
284 self.position_id,
285 position::State::Withdrawn {
286 sequence: self.sequence - 1,
287 },
288 )
289 .asset_id(),
290 }
291 }
292 .commit(Fr::zero());
293
294 let next_state_nft = Value {
295 amount: 1u64.into(),
296 asset_id: LpNft::new(
297 self.position_id,
298 position::State::Withdrawn {
299 sequence: self.sequence,
300 },
301 )
302 .asset_id(),
303 }
304 .commit(Fr::zero());
305
306 self.reserves_commitment - prev_state_nft + next_state_nft
308 }
309
310 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
311 ActionView::PositionWithdraw(self.to_owned())
312 }
313}
314
315impl IsAction for Swap {
316 fn balance_commitment(&self) -> balance::Commitment {
319 self.balance_commitment_inner()
320 }
321
322 fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
323 let commitment = self.body.payload.commitment;
324
325 let plaintext = txp.payload_keys.get(&commitment).and_then(|payload_key| {
326 SwapCiphertext::decrypt_with_payload_key(&self.body.payload.encrypted_swap, payload_key)
328 .ok()
329 });
330
331 ActionView::Swap(match plaintext {
332 Some(swap_plaintext) => {
333 let bsod = txp
336 .batch_swap_output_data
337 .iter()
338 .find(|bsod| bsod.trading_pair == swap_plaintext.trading_pair);
341
342 let (output_1, output_2) = match bsod.map(|bsod| swap_plaintext.output_notes(bsod))
343 {
344 Some((output_1, output_2)) => {
345 (Some(txp.view_note(output_1)), Some(txp.view_note(output_2)))
346 }
347 None => (None, None),
348 };
349
350 SwapView::Visible {
351 swap: self.to_owned(),
352 swap_plaintext: swap_plaintext.clone(),
353 output_1,
354 output_2,
355 claim_tx: txp
356 .nullification_transaction_ids_by_commitment
357 .get(&commitment)
358 .cloned(),
359 batch_swap_output_data: bsod.cloned(),
360 asset_1_metadata: txp
361 .denoms
362 .get(&swap_plaintext.trading_pair.asset_1())
363 .cloned(),
364 asset_2_metadata: txp
365 .denoms
366 .get(&swap_plaintext.trading_pair.asset_2())
367 .cloned(),
368 }
369 }
370 None => {
371 let bsod = txp
374 .batch_swap_output_data
375 .iter()
376 .find(|bsod| bsod.trading_pair == self.body.trading_pair);
379
380 let denom_1 = txp.denoms.get(&self.body.trading_pair.asset_1()).cloned();
382 let denom_2 = txp.denoms.get(&self.body.trading_pair.asset_2()).cloned();
383
384 match bsod {
385 None => {
386 SwapView::Opaque {
389 swap: self.to_owned(),
390 batch_swap_output_data: None,
391 output_1: None,
392 output_2: None,
393 asset_1_metadata: denom_1.clone(),
394 asset_2_metadata: denom_2.clone(),
395 }
396 }
397 Some(bsod) => {
398 let (lambda_1_i, lambda_2_i) =
401 bsod.pro_rata_outputs((self.body.delta_1_i, self.body.delta_2_i));
402 SwapView::Opaque {
403 swap: self.to_owned(),
404 batch_swap_output_data: Some(bsod.clone()),
405 asset_1_metadata: denom_1.clone(),
406 asset_2_metadata: denom_2.clone(),
407 output_1: Some(
408 Value {
409 amount: lambda_1_i,
410 asset_id: self.body.trading_pair.asset_1(),
411 }
412 .view_with_cache(&txp.denoms),
413 ),
414 output_2: Some(
415 Value {
416 amount: lambda_2_i,
417 asset_id: self.body.trading_pair.asset_2(),
418 }
419 .view_with_cache(&txp.denoms),
420 ),
421 }
422 }
423 }
424 }
425 })
426 }
427}
428
429impl IsAction for SwapClaim {
430 fn balance_commitment(&self) -> balance::Commitment {
431 self.balance().commit(Fr::zero())
432 }
433
434 fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
435 let output_1 = txp.advice_notes.get(&self.body.output_1_commitment);
437 let output_2 = txp.advice_notes.get(&self.body.output_2_commitment);
438
439 match (output_1, output_2) {
440 (Some(output_1), Some(output_2)) => {
441 let swap_claim_view = SwapClaimView::Visible {
442 swap_claim: self.to_owned(),
443 output_1: txp.view_note(output_1.to_owned()),
444 output_2: txp.view_note(output_2.to_owned()),
445 swap_tx: txp
446 .creation_transaction_ids_by_nullifier
447 .get(&self.body.nullifier)
448 .cloned(),
449 };
450 ActionView::SwapClaim(swap_claim_view)
451 }
452 _ => {
453 let swap_claim_view = SwapClaimView::Opaque {
454 swap_claim: self.to_owned(),
455 };
456 ActionView::SwapClaim(swap_claim_view)
457 }
458 }
459 }
460}
461
462impl IsAction for ActionDutchAuctionSchedule {
463 fn balance_commitment(&self) -> balance::Commitment {
464 self.balance().commit(Fr::zero())
465 }
466
467 fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView {
468 let view = ActionDutchAuctionScheduleView {
469 action: self.to_owned(),
470 auction_id: self.description.id(),
471 input_metadata: txp.denoms.get_by_id(self.description.input.asset_id),
472 output_metadata: txp.denoms.get_by_id(self.description.output_id),
473 };
474 ActionView::ActionDutchAuctionSchedule(view)
475 }
476}
477
478impl IsAction for ActionDutchAuctionEnd {
479 fn balance_commitment(&self) -> balance::Commitment {
480 self.balance().commit(Fr::zero())
481 }
482
483 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
484 ActionView::ActionDutchAuctionEnd(self.to_owned())
485 }
486}
487
488impl IsAction for ActionDutchAuctionWithdraw {
489 fn balance_commitment(&self) -> balance::Commitment {
490 self.balance_commitment()
491 }
492
493 fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView {
494 let view = ActionDutchAuctionWithdrawView {
495 action: self.to_owned(),
496 reserves: vec![],
497 };
498 ActionView::ActionDutchAuctionWithdraw(view)
499 }
500}