1use crate::drive::Drive;
2use crate::error::Error;
3use crate::fees::op::LowLevelDriveOperation;
4use crate::state_transition_action::shielded::ShieldedActionNote;
5use crate::util::batch::drive_op_batch::DriveLowLevelOperationConverter;
6use dpp::balances::credits::TokenAmount;
7use dpp::block::block_info::BlockInfo;
8use dpp::identifier::Identifier;
9use dpp::prelude::{IdentityNonce, TimestampMillis};
10use dpp::tokens::status::TokenStatus;
11use dpp::tokens::token_event::TokenEvent;
12use grovedb::batch::KeyInfoPath;
13use grovedb::{EstimatedLayerInformation, TransactionArg};
14use platform_version::version::PlatformVersion;
15use std::collections::HashMap;
16use dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment;
17use dpp::tokens::token_pricing_schedule::TokenPricingSchedule;
18
19#[derive(Clone, Debug)]
21pub enum TokenOperationType {
22 TokenBurn {
24 token_id: Identifier,
26 identity_balance_holder_id: Identifier,
28 burn_amount: TokenAmount,
30 },
31 TokenMint {
33 token_id: Identifier,
35 identity_balance_holder_id: Identifier,
37 mint_amount: TokenAmount,
39 allow_first_mint: bool,
41 allow_saturation: bool,
44 },
45 TokenMintMany {
47 token_id: Identifier,
49 recipients: Vec<(Identifier, u64)>,
51 mint_amount: TokenAmount,
53 allow_first_mint: bool,
55 },
56 TokenMarkPerpetualReleaseAsDistributed {
59 token_id: Identifier,
61 recipient_id: Identifier,
63 cycle_start_moment: RewardDistributionMoment,
66 },
67 TokenMarkPreProgrammedReleaseAsDistributed {
70 token_id: Identifier,
72 recipient_id: Identifier,
74 release_time: TimestampMillis,
76 },
77 TokenMarkOncePerIdentityReleaseAsDistributed {
79 token_id: Identifier,
81 recipient_id: Identifier,
83 claimed_at_ms: TimestampMillis,
85 },
86 TokenTransfer {
88 token_id: Identifier,
90 sender_id: Identifier,
92 recipient_id: Identifier,
94 amount: TokenAmount,
96 },
97 TokenFreeze {
99 token_id: Identifier,
101 frozen_identity_id: Identifier,
103 },
104 TokenUnfreeze {
106 token_id: Identifier,
108 frozen_identity_id: Identifier,
110 },
111 TokenSetStatus {
113 token_id: Identifier,
115 status: TokenStatus,
117 },
118 TokenHistory {
120 token_id: Identifier,
122 owner_id: Identifier,
124 nonce: IdentityNonce,
126 event: TokenEvent,
128 },
129 TokenSetPriceForDirectPurchase {
131 token_id: Identifier,
133 price: Option<TokenPricingSchedule>,
136 },
137 TokenShield {
139 token_id: Identifier,
141 identity_id: Identifier,
143 amount: TokenAmount,
145 notes: Vec<ShieldedActionNote>,
148 },
149 TokenUnshield {
151 token_id: Identifier,
153 recipient_id: Identifier,
155 amount: TokenAmount,
157 nullifiers: Vec<[u8; 32]>,
159 notes: Vec<ShieldedActionNote>,
161 },
162 TokenShieldedTransfer {
164 token_id: Identifier,
166 nullifiers: Vec<[u8; 32]>,
168 notes: Vec<ShieldedActionNote>,
170 },
171 TokenMintToPool {
173 token_id: Identifier,
175 amount: TokenAmount,
177 allow_first_mint: bool,
179 notes: Vec<ShieldedActionNote>,
182 },
183 TokenBurnFromPool {
185 token_id: Identifier,
187 amount: TokenAmount,
189 nullifiers: Vec<[u8; 32]>,
191 notes: Vec<ShieldedActionNote>,
193 },
194}
195
196impl DriveLowLevelOperationConverter for TokenOperationType {
197 fn into_low_level_drive_operations(
198 self,
199 drive: &Drive,
200 estimated_costs_only_with_layer_info: &mut Option<
201 HashMap<KeyInfoPath, EstimatedLayerInformation>,
202 >,
203 block_info: &BlockInfo,
204 transaction: TransactionArg,
205 platform_version: &PlatformVersion,
206 ) -> Result<Vec<LowLevelDriveOperation>, Error> {
207 match self {
208 TokenOperationType::TokenBurn {
209 token_id,
210 identity_balance_holder_id,
211 burn_amount,
212 } => {
213 let token_id_bytes: [u8; 32] = token_id.to_buffer();
214 let identity_id_bytes: [u8; 32] = identity_balance_holder_id.to_buffer();
215 let batch_operations = drive.token_burn_operations(
216 token_id_bytes,
217 identity_id_bytes,
218 burn_amount,
219 estimated_costs_only_with_layer_info,
220 transaction,
221 platform_version,
222 )?;
223 Ok(batch_operations)
224 }
225 TokenOperationType::TokenMint {
226 token_id,
227 identity_balance_holder_id,
228 mint_amount,
229 allow_first_mint,
230 allow_saturation,
231 } => {
232 let token_id_bytes: [u8; 32] = token_id.to_buffer();
233 let identity_id_bytes: [u8; 32] = identity_balance_holder_id.to_buffer();
234 let batch_operations = drive.token_mint_operations(
235 token_id_bytes,
236 identity_id_bytes,
237 mint_amount,
238 allow_first_mint,
239 allow_saturation,
240 estimated_costs_only_with_layer_info,
241 transaction,
242 platform_version,
243 )?;
244 Ok(batch_operations)
245 }
246 TokenOperationType::TokenMintMany {
247 token_id,
248 recipients,
249 mint_amount,
250 allow_first_mint,
251 } => {
252 let batch_operations = drive.token_mint_many_operations(
253 token_id,
254 recipients,
255 mint_amount,
256 allow_first_mint,
257 estimated_costs_only_with_layer_info,
258 transaction,
259 platform_version,
260 )?;
261 Ok(batch_operations)
262 }
263 TokenOperationType::TokenTransfer {
264 token_id,
265 sender_id,
266 recipient_id,
267 amount,
268 } => {
269 let token_id_bytes: [u8; 32] = token_id.to_buffer();
270 let sender_id_bytes: [u8; 32] = sender_id.to_buffer();
271 let recipient_id_bytes: [u8; 32] = recipient_id.to_buffer();
272
273 let batch_operations = drive.token_transfer_operations(
274 token_id_bytes,
275 sender_id_bytes,
276 recipient_id_bytes,
277 amount,
278 estimated_costs_only_with_layer_info,
279 transaction,
280 platform_version,
281 )?;
282 Ok(batch_operations)
283 }
284 TokenOperationType::TokenHistory {
285 token_id,
286 owner_id,
287 nonce,
288 event,
289 } => {
290 let batch_operations = drive.add_token_transaction_history_operations(
291 token_id,
292 owner_id,
293 nonce,
294 event,
295 block_info,
296 estimated_costs_only_with_layer_info,
297 transaction,
298 platform_version,
299 )?;
300 Ok(batch_operations)
301 }
302 TokenOperationType::TokenFreeze {
303 token_id,
304 frozen_identity_id,
305 } => {
306 let batch_operations = drive.token_freeze_operations(
307 token_id,
308 frozen_identity_id,
309 estimated_costs_only_with_layer_info,
310 transaction,
311 platform_version,
312 )?;
313 Ok(batch_operations)
314 }
315 TokenOperationType::TokenUnfreeze {
316 token_id,
317 frozen_identity_id,
318 } => {
319 let batch_operations = drive.token_unfreeze_operations(
320 token_id,
321 frozen_identity_id,
322 estimated_costs_only_with_layer_info,
323 transaction,
324 platform_version,
325 )?;
326 Ok(batch_operations)
327 }
328 TokenOperationType::TokenSetStatus { token_id, status } => {
329 let batch_operations = drive.token_apply_status_operations(
330 token_id.to_buffer(),
331 status,
332 estimated_costs_only_with_layer_info,
333 platform_version,
334 )?;
335 Ok(batch_operations)
336 }
337 TokenOperationType::TokenMarkPerpetualReleaseAsDistributed {
338 token_id,
339 recipient_id,
340 cycle_start_moment,
341 } => {
342 let batch_operations = drive.mark_perpetual_release_as_distributed_operations(
343 token_id.to_buffer(),
344 recipient_id.to_buffer(),
345 cycle_start_moment,
346 estimated_costs_only_with_layer_info,
347 platform_version,
348 )?;
349 Ok(batch_operations)
350 }
351 TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed {
352 token_id,
353 recipient_id,
354 release_time,
355 } => {
356 let batch_operations = drive
357 .mark_pre_programmed_release_as_distributed_operations(
358 token_id.to_buffer(),
359 recipient_id.to_buffer(),
360 release_time,
361 block_info,
362 estimated_costs_only_with_layer_info,
363 transaction,
364 platform_version,
365 )?;
366 Ok(batch_operations)
367 }
368 TokenOperationType::TokenMarkOncePerIdentityReleaseAsDistributed {
369 token_id,
370 recipient_id,
371 claimed_at_ms,
372 } => {
373 let batch_operations = drive
374 .mark_once_per_identity_release_as_distributed_operations(
375 token_id.to_buffer(),
376 recipient_id.to_buffer(),
377 claimed_at_ms,
378 block_info,
379 estimated_costs_only_with_layer_info,
380 platform_version,
381 )?;
382 Ok(batch_operations)
383 }
384 TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => {
385 let batch_operations = drive.token_set_direct_purchase_price_operations(
386 token_id.to_buffer(),
387 price,
388 estimated_costs_only_with_layer_info,
389 platform_version,
390 )?;
391 Ok(batch_operations)
392 }
393 TokenOperationType::TokenShield {
394 token_id,
395 identity_id,
396 amount,
397 notes,
398 } => drive.token_shield_operations(
399 token_id.to_buffer(),
400 identity_id.to_buffer(),
401 amount,
402 ¬es,
403 estimated_costs_only_with_layer_info,
404 transaction,
405 platform_version,
406 ),
407 TokenOperationType::TokenUnshield {
408 token_id,
409 recipient_id,
410 amount,
411 nullifiers,
412 notes,
413 } => drive.token_unshield_operations(
414 token_id.to_buffer(),
415 recipient_id.to_buffer(),
416 amount,
417 &nullifiers,
418 ¬es,
419 estimated_costs_only_with_layer_info,
420 transaction,
421 platform_version,
422 ),
423 TokenOperationType::TokenShieldedTransfer {
424 token_id,
425 nullifiers,
426 notes,
427 } => drive.token_shielded_transfer_operations(
428 token_id.to_buffer(),
429 &nullifiers,
430 ¬es,
431 estimated_costs_only_with_layer_info,
432 transaction,
433 platform_version,
434 ),
435 TokenOperationType::TokenMintToPool {
436 token_id,
437 amount,
438 allow_first_mint,
439 notes,
440 } => drive.token_mint_to_pool_operations(
441 token_id.to_buffer(),
442 amount,
443 allow_first_mint,
444 ¬es,
445 estimated_costs_only_with_layer_info,
446 transaction,
447 platform_version,
448 ),
449 TokenOperationType::TokenBurnFromPool {
450 token_id,
451 amount,
452 nullifiers,
453 notes,
454 } => drive.token_burn_from_pool_operations(
455 token_id.to_buffer(),
456 amount,
457 &nullifiers,
458 ¬es,
459 estimated_costs_only_with_layer_info,
460 transaction,
461 platform_version,
462 ),
463 }
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 fn test_token_id() -> Identifier {
472 Identifier::new([1u8; 32])
473 }
474
475 fn test_identity_id() -> Identifier {
476 Identifier::new([2u8; 32])
477 }
478
479 fn test_recipient_id() -> Identifier {
480 Identifier::new([3u8; 32])
481 }
482
483 #[test]
488 fn test_token_burn_construction() {
489 let op = TokenOperationType::TokenBurn {
490 token_id: test_token_id(),
491 identity_balance_holder_id: test_identity_id(),
492 burn_amount: 500,
493 };
494
495 match op {
496 TokenOperationType::TokenBurn {
497 token_id,
498 identity_balance_holder_id,
499 burn_amount,
500 } => {
501 assert_eq!(token_id, test_token_id());
502 assert_eq!(identity_balance_holder_id, test_identity_id());
503 assert_eq!(burn_amount, 500);
504 }
505 _ => panic!("expected TokenBurn variant"),
506 }
507 }
508
509 #[test]
514 fn test_token_mint_construction() {
515 let op = TokenOperationType::TokenMint {
516 token_id: test_token_id(),
517 identity_balance_holder_id: test_identity_id(),
518 mint_amount: 1_000_000,
519 allow_first_mint: true,
520 allow_saturation: false,
521 };
522
523 match op {
524 TokenOperationType::TokenMint {
525 token_id,
526 identity_balance_holder_id,
527 mint_amount,
528 allow_first_mint,
529 allow_saturation,
530 } => {
531 assert_eq!(token_id, test_token_id());
532 assert_eq!(identity_balance_holder_id, test_identity_id());
533 assert_eq!(mint_amount, 1_000_000);
534 assert!(allow_first_mint);
535 assert!(!allow_saturation);
536 }
537 _ => panic!("expected TokenMint variant"),
538 }
539 }
540
541 #[test]
542 fn test_token_mint_with_saturation_enabled() {
543 let op = TokenOperationType::TokenMint {
544 token_id: test_token_id(),
545 identity_balance_holder_id: test_identity_id(),
546 mint_amount: u64::MAX,
547 allow_first_mint: false,
548 allow_saturation: true,
549 };
550
551 match op {
552 TokenOperationType::TokenMint {
553 allow_saturation,
554 allow_first_mint,
555 mint_amount,
556 ..
557 } => {
558 assert!(allow_saturation);
559 assert!(!allow_first_mint);
560 assert_eq!(mint_amount, u64::MAX);
561 }
562 _ => panic!("expected TokenMint variant"),
563 }
564 }
565
566 #[test]
571 fn test_token_mint_many_construction() {
572 let recipients = vec![
573 (Identifier::new([10u8; 32]), 50),
574 (Identifier::new([11u8; 32]), 30),
575 (Identifier::new([12u8; 32]), 20),
576 ];
577 let op = TokenOperationType::TokenMintMany {
578 token_id: test_token_id(),
579 recipients: recipients.clone(),
580 mint_amount: 100_000,
581 allow_first_mint: true,
582 };
583
584 match op {
585 TokenOperationType::TokenMintMany {
586 token_id,
587 recipients: r,
588 mint_amount,
589 allow_first_mint,
590 } => {
591 assert_eq!(token_id, test_token_id());
592 assert_eq!(r.len(), 3);
593 assert_eq!(r[0].1, 50);
594 assert_eq!(r[1].1, 30);
595 assert_eq!(r[2].1, 20);
596 assert_eq!(mint_amount, 100_000);
597 assert!(allow_first_mint);
598 }
599 _ => panic!("expected TokenMintMany variant"),
600 }
601 }
602
603 #[test]
608 fn test_token_transfer_construction() {
609 let sender = Identifier::new([4u8; 32]);
610 let recipient = Identifier::new([5u8; 32]);
611 let op = TokenOperationType::TokenTransfer {
612 token_id: test_token_id(),
613 sender_id: sender,
614 recipient_id: recipient,
615 amount: 250,
616 };
617
618 match op {
619 TokenOperationType::TokenTransfer {
620 token_id,
621 sender_id,
622 recipient_id,
623 amount,
624 } => {
625 assert_eq!(token_id, test_token_id());
626 assert_eq!(sender_id, Identifier::new([4u8; 32]));
627 assert_eq!(recipient_id, Identifier::new([5u8; 32]));
628 assert_eq!(amount, 250);
629 }
630 _ => panic!("expected TokenTransfer variant"),
631 }
632 }
633
634 #[test]
639 fn test_token_freeze_construction() {
640 let frozen = Identifier::new([6u8; 32]);
641 let op = TokenOperationType::TokenFreeze {
642 token_id: test_token_id(),
643 frozen_identity_id: frozen,
644 };
645
646 match op {
647 TokenOperationType::TokenFreeze {
648 token_id,
649 frozen_identity_id,
650 } => {
651 assert_eq!(token_id, test_token_id());
652 assert_eq!(frozen_identity_id, Identifier::new([6u8; 32]));
653 }
654 _ => panic!("expected TokenFreeze variant"),
655 }
656 }
657
658 #[test]
659 fn test_token_unfreeze_construction() {
660 let frozen = Identifier::new([7u8; 32]);
661 let op = TokenOperationType::TokenUnfreeze {
662 token_id: test_token_id(),
663 frozen_identity_id: frozen,
664 };
665
666 match op {
667 TokenOperationType::TokenUnfreeze {
668 token_id,
669 frozen_identity_id,
670 } => {
671 assert_eq!(token_id, test_token_id());
672 assert_eq!(frozen_identity_id, Identifier::new([7u8; 32]));
673 }
674 _ => panic!("expected TokenUnfreeze variant"),
675 }
676 }
677
678 #[test]
683 fn test_token_set_price_none() {
684 let op = TokenOperationType::TokenSetPriceForDirectPurchase {
685 token_id: test_token_id(),
686 price: None,
687 };
688
689 match op {
690 TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => {
691 assert_eq!(token_id, test_token_id());
692 assert!(price.is_none());
693 }
694 _ => panic!("expected TokenSetPriceForDirectPurchase variant"),
695 }
696 }
697
698 #[test]
703 fn test_token_mark_pre_programmed_release_construction() {
704 let op = TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed {
705 token_id: test_token_id(),
706 recipient_id: test_recipient_id(),
707 release_time: 1_700_000_000_000,
708 };
709
710 match op {
711 TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed {
712 token_id,
713 recipient_id,
714 release_time,
715 } => {
716 assert_eq!(token_id, test_token_id());
717 assert_eq!(recipient_id, test_recipient_id());
718 assert_eq!(release_time, 1_700_000_000_000);
719 }
720 _ => panic!("expected TokenMarkPreProgrammedReleaseAsDistributed variant"),
721 }
722 }
723
724 #[test]
729 fn test_token_operation_clone() {
730 let op = TokenOperationType::TokenBurn {
731 token_id: test_token_id(),
732 identity_balance_holder_id: test_identity_id(),
733 burn_amount: 100,
734 };
735 let cloned = op.clone();
736 match cloned {
737 TokenOperationType::TokenBurn { burn_amount, .. } => {
738 assert_eq!(burn_amount, 100);
739 }
740 _ => panic!("clone should preserve variant"),
741 }
742 }
743
744 #[test]
749 fn test_token_set_status_construction() {
750 use dpp::tokens::status::v0::TokenStatusV0;
751 let op = TokenOperationType::TokenSetStatus {
752 token_id: test_token_id(),
753 status: TokenStatus::V0(TokenStatusV0 { paused: true }),
754 };
755 match op {
756 TokenOperationType::TokenSetStatus { token_id, .. } => {
757 assert_eq!(token_id, test_token_id());
758 }
759 _ => panic!("expected TokenSetStatus variant"),
760 }
761 }
762
763 #[test]
764 fn test_token_history_construction() {
765 use dpp::tokens::token_event::TokenEvent;
766
767 let op = TokenOperationType::TokenHistory {
768 token_id: test_token_id(),
769 owner_id: test_identity_id(),
770 nonce: 42,
771 event: TokenEvent::Mint(1000, test_identity_id(), None),
772 };
773 match op {
774 TokenOperationType::TokenHistory {
775 token_id,
776 owner_id,
777 nonce,
778 ..
779 } => {
780 assert_eq!(token_id, test_token_id());
781 assert_eq!(owner_id, test_identity_id());
782 assert_eq!(nonce, 42);
783 }
784 _ => panic!("expected TokenHistory variant"),
785 }
786 }
787
788 #[test]
789 fn test_token_mark_perpetual_release_construction() {
790 let op = TokenOperationType::TokenMarkPerpetualReleaseAsDistributed {
791 token_id: test_token_id(),
792 recipient_id: test_recipient_id(),
793 cycle_start_moment: RewardDistributionMoment::BlockBasedMoment(100),
794 };
795 match op {
796 TokenOperationType::TokenMarkPerpetualReleaseAsDistributed {
797 token_id,
798 recipient_id,
799 ..
800 } => {
801 assert_eq!(token_id, test_token_id());
802 assert_eq!(recipient_id, test_recipient_id());
803 }
804 _ => panic!("expected TokenMarkPerpetualReleaseAsDistributed variant"),
805 }
806 }
807
808 #[test]
809 fn test_token_set_price_some() {
810 use dpp::tokens::token_pricing_schedule::TokenPricingSchedule;
811
812 let pricing = TokenPricingSchedule::SinglePrice(5000);
813 let op = TokenOperationType::TokenSetPriceForDirectPurchase {
814 token_id: test_token_id(),
815 price: Some(pricing),
816 };
817 match op {
818 TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => {
819 assert_eq!(token_id, test_token_id());
820 assert!(price.is_some());
821 }
822 _ => panic!("expected TokenSetPriceForDirectPurchase variant"),
823 }
824 }
825
826 #[test]
827 fn test_token_operation_debug() {
828 let op = TokenOperationType::TokenBurn {
829 token_id: test_token_id(),
830 identity_balance_holder_id: test_identity_id(),
831 burn_amount: 42,
832 };
833 let debug_str = format!("{:?}", op);
834 assert!(debug_str.contains("TokenBurn"));
835 assert!(debug_str.contains("42"));
836 }
837}