Skip to main content

drive/util/batch/drive_op_batch/
token.rs

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/// Operations on Tokens
20#[derive(Clone, Debug)]
21pub enum TokenOperationType {
22    /// Burns token from the account issuing the action.
23    TokenBurn {
24        /// The token id
25        token_id: Identifier,
26        /// The identity to burn from
27        identity_balance_holder_id: Identifier,
28        /// The amount to burn
29        burn_amount: TokenAmount,
30    },
31    /// Mints tokens
32    TokenMint {
33        /// The token id
34        token_id: Identifier,
35        /// The identity to mint to
36        identity_balance_holder_id: Identifier,
37        /// The amount to issue
38        mint_amount: TokenAmount,
39        /// Should we allow this to be the first ever mint
40        allow_first_mint: bool,
41        /// Should we allow a mint to saturate the upper bounds instead of giving an error?
42        /// For example if we were to add 10 to i64::Max - 5 we would get i64::Max
43        allow_saturation: bool,
44    },
45    /// Mints tokens to many recipients
46    TokenMintMany {
47        /// The token id
48        token_id: Identifier,
49        /// The identities that will receive this amount along with their weight
50        recipients: Vec<(Identifier, u64)>,
51        /// The amount to issue
52        mint_amount: TokenAmount,
53        /// Should we allow this to be the first ever mint
54        allow_first_mint: bool,
55    },
56    /// Marks the perpetual release as distributed
57    /// This removes the references in the queue
58    TokenMarkPerpetualReleaseAsDistributed {
59        /// The token id
60        token_id: Identifier,
61        /// The recipient of this operation, generally the person making the claim state transition
62        recipient_id: Identifier,
63        /// The beginning of the current perpetual release cycle.
64        /// For example if we pay every 10 blocks, and we are on block 54, this would be 50.
65        cycle_start_moment: RewardDistributionMoment,
66    },
67    /// Marks the pre-programmed release as distributed
68    /// This removes the references in the queue
69    TokenMarkPreProgrammedReleaseAsDistributed {
70        /// The token id
71        token_id: Identifier,
72        /// The recipient of this operation, generally the person making the state transition
73        recipient_id: Identifier,
74        /// The last release time, block or epoch
75        release_time: TimestampMillis,
76    },
77    /// Records a once-per-identity distribution claim so the claimant can not claim again
78    TokenMarkOncePerIdentityReleaseAsDistributed {
79        /// The token id
80        token_id: Identifier,
81        /// The identity that claimed, the one making the claim state transition
82        recipient_id: Identifier,
83        /// The block time of the claim
84        claimed_at_ms: TimestampMillis,
85    },
86    /// Performs a token transfer
87    TokenTransfer {
88        /// The token id
89        token_id: Identifier,
90        /// The token id
91        sender_id: Identifier,
92        /// The recipient of the transfer
93        recipient_id: Identifier,
94        /// The amount to transfer
95        amount: TokenAmount,
96    },
97    /// Freezes an identity's token balance so money can no longer be sent out.
98    TokenFreeze {
99        /// The token id
100        token_id: Identifier,
101        /// The frozen identity id
102        frozen_identity_id: Identifier,
103    },
104    /// Unfreezes an identity's token balance so money can be sent out again.
105    TokenUnfreeze {
106        /// The token id
107        token_id: Identifier,
108        /// The frozen identity id
109        frozen_identity_id: Identifier,
110    },
111    /// Sets the status of the token.
112    TokenSetStatus {
113        /// The token id
114        token_id: Identifier,
115        /// The status
116        status: TokenStatus,
117    },
118    /// Adds a historical document explaining a token action.
119    TokenHistory {
120        /// The token id
121        token_id: Identifier,
122        /// The identity making the event
123        owner_id: Identifier,
124        /// The nonce
125        nonce: IdentityNonce,
126        /// The token event
127        event: TokenEvent,
128    },
129    /// Sets the price of a token for direct purchase
130    TokenSetPriceForDirectPurchase {
131        /// The token id
132        token_id: Identifier,
133        /// The price we are setting to
134        /// None means it's not currently for sale
135        price: Option<TokenPricingSchedule>,
136    },
137    /// Moves tokens from an identity balance into the token's shielded pool.
138    TokenShield {
139        /// The token id
140        token_id: Identifier,
141        /// The identity whose balance is debited
142        identity_id: Identifier,
143        /// The amount entering the pool
144        amount: TokenAmount,
145        /// The bundle's output notes, appended to the pool's commitment tree; their dummy
146        /// nullifiers are recorded in the pool's nullifier set
147        notes: Vec<ShieldedActionNote>,
148    },
149    /// Moves tokens from the token's shielded pool into an identity balance.
150    TokenUnshield {
151        /// The token id
152        token_id: Identifier,
153        /// The identity whose balance is credited
154        recipient_id: Identifier,
155        /// The amount leaving the pool
156        amount: TokenAmount,
157        /// The spent nullifiers, recorded in the pool's nullifier set
158        nullifiers: Vec<[u8; 32]>,
159        /// The bundle's output (change) notes, appended to the pool's commitment tree
160        notes: Vec<ShieldedActionNote>,
161    },
162    /// A transfer inside the token's shielded pool (the pool balance is unchanged).
163    TokenShieldedTransfer {
164        /// The token id
165        token_id: Identifier,
166        /// The spent nullifiers, recorded in the pool's nullifier set
167        nullifiers: Vec<[u8; 32]>,
168        /// The bundle's output notes, appended to the pool's commitment tree
169        notes: Vec<ShieldedActionNote>,
170    },
171    /// Mints tokens straight into the token's shielded pool (supply and pool balance grow).
172    TokenMintToPool {
173        /// The token id
174        token_id: Identifier,
175        /// The amount minted
176        amount: TokenAmount,
177        /// Should we allow this to be the first ever mint
178        allow_first_mint: bool,
179        /// The output notes of the bundle; their dummy nullifiers are recorded in the pool's
180        /// nullifier set
181        notes: Vec<ShieldedActionNote>,
182    },
183    /// Burns tokens held in the token's shielded pool (pool balance and supply shrink).
184    TokenBurnFromPool {
185        /// The token id
186        token_id: Identifier,
187        /// The amount destroyed
188        amount: TokenAmount,
189        /// The spent nullifiers
190        nullifiers: Vec<[u8; 32]>,
191        /// The change notes of the bundle
192        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                &notes,
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                &notes,
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                &notes,
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                &notes,
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                &notes,
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    // ---------------------------------------------------------------
484    // TokenBurn construction
485    // ---------------------------------------------------------------
486
487    #[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    // ---------------------------------------------------------------
510    // TokenMint construction
511    // ---------------------------------------------------------------
512
513    #[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    // ---------------------------------------------------------------
567    // TokenMintMany construction
568    // ---------------------------------------------------------------
569
570    #[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    // ---------------------------------------------------------------
604    // TokenTransfer construction
605    // ---------------------------------------------------------------
606
607    #[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    // ---------------------------------------------------------------
635    // TokenFreeze / TokenUnfreeze construction
636    // ---------------------------------------------------------------
637
638    #[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    // ---------------------------------------------------------------
679    // TokenSetPriceForDirectPurchase construction
680    // ---------------------------------------------------------------
681
682    #[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    // ---------------------------------------------------------------
699    // TokenMarkPreProgrammedReleaseAsDistributed construction
700    // ---------------------------------------------------------------
701
702    #[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    // ---------------------------------------------------------------
725    // Clone behavior
726    // ---------------------------------------------------------------
727
728    #[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    // ---------------------------------------------------------------
745    // Debug trait
746    // ---------------------------------------------------------------
747
748    #[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}