drive/util/batch/drive_op_batch/
token.rs

1use crate::drive::Drive;
2use crate::error::Error;
3use crate::fees::op::LowLevelDriveOperation;
4use crate::util::batch::drive_op_batch::DriveLowLevelOperationConverter;
5use dpp::balances::credits::TokenAmount;
6use dpp::block::block_info::BlockInfo;
7use dpp::identifier::Identifier;
8use dpp::prelude::{IdentityNonce, TimestampMillis};
9use dpp::tokens::status::TokenStatus;
10use dpp::tokens::token_event::TokenEvent;
11use grovedb::batch::KeyInfoPath;
12use grovedb::{EstimatedLayerInformation, TransactionArg};
13use platform_version::version::PlatformVersion;
14use std::collections::HashMap;
15use dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment;
16use dpp::tokens::token_pricing_schedule::TokenPricingSchedule;
17
18/// Operations on Tokens
19#[derive(Clone, Debug)]
20pub enum TokenOperationType {
21    /// Burns token from the account issuing the action.
22    TokenBurn {
23        /// The token id
24        token_id: Identifier,
25        /// The identity to burn from
26        identity_balance_holder_id: Identifier,
27        /// The amount to burn
28        burn_amount: TokenAmount,
29    },
30    /// Mints tokens
31    TokenMint {
32        /// The token id
33        token_id: Identifier,
34        /// The identity to mint to
35        identity_balance_holder_id: Identifier,
36        /// The amount to issue
37        mint_amount: TokenAmount,
38        /// Should we allow this to be the first ever mint
39        allow_first_mint: bool,
40        /// Should we allow a mint to saturate the upper bounds instead of giving an error?
41        /// For example if we were to add 10 to i64::Max - 5 we would get i64::Max
42        allow_saturation: bool,
43    },
44    /// Mints tokens to many recipients
45    TokenMintMany {
46        /// The token id
47        token_id: Identifier,
48        /// The identities that will receive this amount along with their weight
49        recipients: Vec<(Identifier, u64)>,
50        /// The amount to issue
51        mint_amount: TokenAmount,
52        /// Should we allow this to be the first ever mint
53        allow_first_mint: bool,
54    },
55    /// Marks the perpetual release as distributed
56    /// This removes the references in the queue
57    TokenMarkPerpetualReleaseAsDistributed {
58        /// The token id
59        token_id: Identifier,
60        /// The recipient of this operation, generally the person making the claim state transition
61        recipient_id: Identifier,
62        /// The beginning of the current perpetual release cycle.
63        /// For example if we pay every 10 blocks, and we are on block 54, this would be 50.
64        cycle_start_moment: RewardDistributionMoment,
65    },
66    /// Marks the pre-programmed release as distributed
67    /// This removes the references in the queue
68    TokenMarkPreProgrammedReleaseAsDistributed {
69        /// The token id
70        token_id: Identifier,
71        /// The recipient of this operation, generally the person making the state transition
72        recipient_id: Identifier,
73        /// The last release time, block or epoch
74        release_time: TimestampMillis,
75    },
76    /// Performs a token transfer
77    TokenTransfer {
78        /// The token id
79        token_id: Identifier,
80        /// The token id
81        sender_id: Identifier,
82        /// The recipient of the transfer
83        recipient_id: Identifier,
84        /// The amount to transfer
85        amount: TokenAmount,
86    },
87    /// Freezes an identity's token balance so money can no longer be sent out.
88    TokenFreeze {
89        /// The token id
90        token_id: Identifier,
91        /// The frozen identity id
92        frozen_identity_id: Identifier,
93    },
94    /// Unfreezes an identity's token balance so money can be sent out again.
95    TokenUnfreeze {
96        /// The token id
97        token_id: Identifier,
98        /// The frozen identity id
99        frozen_identity_id: Identifier,
100    },
101    /// Sets the status of the token.
102    TokenSetStatus {
103        /// The token id
104        token_id: Identifier,
105        /// The status
106        status: TokenStatus,
107    },
108    /// Adds a historical document explaining a token action.
109    TokenHistory {
110        /// The token id
111        token_id: Identifier,
112        /// The identity making the event
113        owner_id: Identifier,
114        /// The nonce
115        nonce: IdentityNonce,
116        /// The token event
117        event: TokenEvent,
118    },
119    /// Sets the price of a token for direct purchase
120    TokenSetPriceForDirectPurchase {
121        /// The token id
122        token_id: Identifier,
123        /// The price we are setting to
124        /// None means it's not currently for sale
125        price: Option<TokenPricingSchedule>,
126    },
127}
128
129impl DriveLowLevelOperationConverter for TokenOperationType {
130    fn into_low_level_drive_operations(
131        self,
132        drive: &Drive,
133        estimated_costs_only_with_layer_info: &mut Option<
134            HashMap<KeyInfoPath, EstimatedLayerInformation>,
135        >,
136        block_info: &BlockInfo,
137        transaction: TransactionArg,
138        platform_version: &PlatformVersion,
139    ) -> Result<Vec<LowLevelDriveOperation>, Error> {
140        match self {
141            TokenOperationType::TokenBurn {
142                token_id,
143                identity_balance_holder_id,
144                burn_amount,
145            } => {
146                let token_id_bytes: [u8; 32] = token_id.to_buffer();
147                let identity_id_bytes: [u8; 32] = identity_balance_holder_id.to_buffer();
148                let batch_operations = drive.token_burn_operations(
149                    token_id_bytes,
150                    identity_id_bytes,
151                    burn_amount,
152                    estimated_costs_only_with_layer_info,
153                    transaction,
154                    platform_version,
155                )?;
156                Ok(batch_operations)
157            }
158            TokenOperationType::TokenMint {
159                token_id,
160                identity_balance_holder_id,
161                mint_amount,
162                allow_first_mint,
163                allow_saturation,
164            } => {
165                let token_id_bytes: [u8; 32] = token_id.to_buffer();
166                let identity_id_bytes: [u8; 32] = identity_balance_holder_id.to_buffer();
167                let batch_operations = drive.token_mint_operations(
168                    token_id_bytes,
169                    identity_id_bytes,
170                    mint_amount,
171                    allow_first_mint,
172                    allow_saturation,
173                    estimated_costs_only_with_layer_info,
174                    transaction,
175                    platform_version,
176                )?;
177                Ok(batch_operations)
178            }
179            TokenOperationType::TokenMintMany {
180                token_id,
181                recipients,
182                mint_amount,
183                allow_first_mint,
184            } => {
185                let batch_operations = drive.token_mint_many_operations(
186                    token_id,
187                    recipients,
188                    mint_amount,
189                    allow_first_mint,
190                    estimated_costs_only_with_layer_info,
191                    transaction,
192                    platform_version,
193                )?;
194                Ok(batch_operations)
195            }
196            TokenOperationType::TokenTransfer {
197                token_id,
198                sender_id,
199                recipient_id,
200                amount,
201            } => {
202                let token_id_bytes: [u8; 32] = token_id.to_buffer();
203                let sender_id_bytes: [u8; 32] = sender_id.to_buffer();
204                let recipient_id_bytes: [u8; 32] = recipient_id.to_buffer();
205
206                let batch_operations = drive.token_transfer_operations(
207                    token_id_bytes,
208                    sender_id_bytes,
209                    recipient_id_bytes,
210                    amount,
211                    estimated_costs_only_with_layer_info,
212                    transaction,
213                    platform_version,
214                )?;
215                Ok(batch_operations)
216            }
217            TokenOperationType::TokenHistory {
218                token_id,
219                owner_id,
220                nonce,
221                event,
222            } => {
223                let batch_operations = drive.add_token_transaction_history_operations(
224                    token_id,
225                    owner_id,
226                    nonce,
227                    event,
228                    block_info,
229                    estimated_costs_only_with_layer_info,
230                    transaction,
231                    platform_version,
232                )?;
233                Ok(batch_operations)
234            }
235            TokenOperationType::TokenFreeze {
236                token_id,
237                frozen_identity_id,
238            } => {
239                let batch_operations = drive.token_freeze_operations(
240                    token_id,
241                    frozen_identity_id,
242                    estimated_costs_only_with_layer_info,
243                    transaction,
244                    platform_version,
245                )?;
246                Ok(batch_operations)
247            }
248            TokenOperationType::TokenUnfreeze {
249                token_id,
250                frozen_identity_id,
251            } => {
252                let batch_operations = drive.token_unfreeze_operations(
253                    token_id,
254                    frozen_identity_id,
255                    estimated_costs_only_with_layer_info,
256                    transaction,
257                    platform_version,
258                )?;
259                Ok(batch_operations)
260            }
261            TokenOperationType::TokenSetStatus { token_id, status } => {
262                let batch_operations = drive.token_apply_status_operations(
263                    token_id.to_buffer(),
264                    status,
265                    estimated_costs_only_with_layer_info,
266                    platform_version,
267                )?;
268                Ok(batch_operations)
269            }
270            TokenOperationType::TokenMarkPerpetualReleaseAsDistributed {
271                token_id,
272                recipient_id,
273                cycle_start_moment,
274            } => {
275                let batch_operations = drive.mark_perpetual_release_as_distributed_operations(
276                    token_id.to_buffer(),
277                    recipient_id.to_buffer(),
278                    cycle_start_moment,
279                    estimated_costs_only_with_layer_info,
280                    platform_version,
281                )?;
282                Ok(batch_operations)
283            }
284            TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed {
285                token_id,
286                recipient_id,
287                release_time,
288            } => {
289                let batch_operations = drive
290                    .mark_pre_programmed_release_as_distributed_operations(
291                        token_id.to_buffer(),
292                        recipient_id.to_buffer(),
293                        release_time,
294                        block_info,
295                        estimated_costs_only_with_layer_info,
296                        transaction,
297                        platform_version,
298                    )?;
299                Ok(batch_operations)
300            }
301            TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => {
302                let batch_operations = drive.token_set_direct_purchase_price_operations(
303                    token_id.to_buffer(),
304                    price,
305                    estimated_costs_only_with_layer_info,
306                    platform_version,
307                )?;
308                Ok(batch_operations)
309            }
310        }
311    }
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    fn test_token_id() -> Identifier {
319        Identifier::new([1u8; 32])
320    }
321
322    fn test_identity_id() -> Identifier {
323        Identifier::new([2u8; 32])
324    }
325
326    fn test_recipient_id() -> Identifier {
327        Identifier::new([3u8; 32])
328    }
329
330    // ---------------------------------------------------------------
331    // TokenBurn construction
332    // ---------------------------------------------------------------
333
334    #[test]
335    fn test_token_burn_construction() {
336        let op = TokenOperationType::TokenBurn {
337            token_id: test_token_id(),
338            identity_balance_holder_id: test_identity_id(),
339            burn_amount: 500,
340        };
341
342        match op {
343            TokenOperationType::TokenBurn {
344                token_id,
345                identity_balance_holder_id,
346                burn_amount,
347            } => {
348                assert_eq!(token_id, test_token_id());
349                assert_eq!(identity_balance_holder_id, test_identity_id());
350                assert_eq!(burn_amount, 500);
351            }
352            _ => panic!("expected TokenBurn variant"),
353        }
354    }
355
356    // ---------------------------------------------------------------
357    // TokenMint construction
358    // ---------------------------------------------------------------
359
360    #[test]
361    fn test_token_mint_construction() {
362        let op = TokenOperationType::TokenMint {
363            token_id: test_token_id(),
364            identity_balance_holder_id: test_identity_id(),
365            mint_amount: 1_000_000,
366            allow_first_mint: true,
367            allow_saturation: false,
368        };
369
370        match op {
371            TokenOperationType::TokenMint {
372                token_id,
373                identity_balance_holder_id,
374                mint_amount,
375                allow_first_mint,
376                allow_saturation,
377            } => {
378                assert_eq!(token_id, test_token_id());
379                assert_eq!(identity_balance_holder_id, test_identity_id());
380                assert_eq!(mint_amount, 1_000_000);
381                assert!(allow_first_mint);
382                assert!(!allow_saturation);
383            }
384            _ => panic!("expected TokenMint variant"),
385        }
386    }
387
388    #[test]
389    fn test_token_mint_with_saturation_enabled() {
390        let op = TokenOperationType::TokenMint {
391            token_id: test_token_id(),
392            identity_balance_holder_id: test_identity_id(),
393            mint_amount: u64::MAX,
394            allow_first_mint: false,
395            allow_saturation: true,
396        };
397
398        match op {
399            TokenOperationType::TokenMint {
400                allow_saturation,
401                allow_first_mint,
402                mint_amount,
403                ..
404            } => {
405                assert!(allow_saturation);
406                assert!(!allow_first_mint);
407                assert_eq!(mint_amount, u64::MAX);
408            }
409            _ => panic!("expected TokenMint variant"),
410        }
411    }
412
413    // ---------------------------------------------------------------
414    // TokenMintMany construction
415    // ---------------------------------------------------------------
416
417    #[test]
418    fn test_token_mint_many_construction() {
419        let recipients = vec![
420            (Identifier::new([10u8; 32]), 50),
421            (Identifier::new([11u8; 32]), 30),
422            (Identifier::new([12u8; 32]), 20),
423        ];
424        let op = TokenOperationType::TokenMintMany {
425            token_id: test_token_id(),
426            recipients: recipients.clone(),
427            mint_amount: 100_000,
428            allow_first_mint: true,
429        };
430
431        match op {
432            TokenOperationType::TokenMintMany {
433                token_id,
434                recipients: r,
435                mint_amount,
436                allow_first_mint,
437            } => {
438                assert_eq!(token_id, test_token_id());
439                assert_eq!(r.len(), 3);
440                assert_eq!(r[0].1, 50);
441                assert_eq!(r[1].1, 30);
442                assert_eq!(r[2].1, 20);
443                assert_eq!(mint_amount, 100_000);
444                assert!(allow_first_mint);
445            }
446            _ => panic!("expected TokenMintMany variant"),
447        }
448    }
449
450    // ---------------------------------------------------------------
451    // TokenTransfer construction
452    // ---------------------------------------------------------------
453
454    #[test]
455    fn test_token_transfer_construction() {
456        let sender = Identifier::new([4u8; 32]);
457        let recipient = Identifier::new([5u8; 32]);
458        let op = TokenOperationType::TokenTransfer {
459            token_id: test_token_id(),
460            sender_id: sender,
461            recipient_id: recipient,
462            amount: 250,
463        };
464
465        match op {
466            TokenOperationType::TokenTransfer {
467                token_id,
468                sender_id,
469                recipient_id,
470                amount,
471            } => {
472                assert_eq!(token_id, test_token_id());
473                assert_eq!(sender_id, Identifier::new([4u8; 32]));
474                assert_eq!(recipient_id, Identifier::new([5u8; 32]));
475                assert_eq!(amount, 250);
476            }
477            _ => panic!("expected TokenTransfer variant"),
478        }
479    }
480
481    // ---------------------------------------------------------------
482    // TokenFreeze / TokenUnfreeze construction
483    // ---------------------------------------------------------------
484
485    #[test]
486    fn test_token_freeze_construction() {
487        let frozen = Identifier::new([6u8; 32]);
488        let op = TokenOperationType::TokenFreeze {
489            token_id: test_token_id(),
490            frozen_identity_id: frozen,
491        };
492
493        match op {
494            TokenOperationType::TokenFreeze {
495                token_id,
496                frozen_identity_id,
497            } => {
498                assert_eq!(token_id, test_token_id());
499                assert_eq!(frozen_identity_id, Identifier::new([6u8; 32]));
500            }
501            _ => panic!("expected TokenFreeze variant"),
502        }
503    }
504
505    #[test]
506    fn test_token_unfreeze_construction() {
507        let frozen = Identifier::new([7u8; 32]);
508        let op = TokenOperationType::TokenUnfreeze {
509            token_id: test_token_id(),
510            frozen_identity_id: frozen,
511        };
512
513        match op {
514            TokenOperationType::TokenUnfreeze {
515                token_id,
516                frozen_identity_id,
517            } => {
518                assert_eq!(token_id, test_token_id());
519                assert_eq!(frozen_identity_id, Identifier::new([7u8; 32]));
520            }
521            _ => panic!("expected TokenUnfreeze variant"),
522        }
523    }
524
525    // ---------------------------------------------------------------
526    // TokenSetPriceForDirectPurchase construction
527    // ---------------------------------------------------------------
528
529    #[test]
530    fn test_token_set_price_none() {
531        let op = TokenOperationType::TokenSetPriceForDirectPurchase {
532            token_id: test_token_id(),
533            price: None,
534        };
535
536        match op {
537            TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => {
538                assert_eq!(token_id, test_token_id());
539                assert!(price.is_none());
540            }
541            _ => panic!("expected TokenSetPriceForDirectPurchase variant"),
542        }
543    }
544
545    // ---------------------------------------------------------------
546    // TokenMarkPreProgrammedReleaseAsDistributed construction
547    // ---------------------------------------------------------------
548
549    #[test]
550    fn test_token_mark_pre_programmed_release_construction() {
551        let op = TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed {
552            token_id: test_token_id(),
553            recipient_id: test_recipient_id(),
554            release_time: 1_700_000_000_000,
555        };
556
557        match op {
558            TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed {
559                token_id,
560                recipient_id,
561                release_time,
562            } => {
563                assert_eq!(token_id, test_token_id());
564                assert_eq!(recipient_id, test_recipient_id());
565                assert_eq!(release_time, 1_700_000_000_000);
566            }
567            _ => panic!("expected TokenMarkPreProgrammedReleaseAsDistributed variant"),
568        }
569    }
570
571    // ---------------------------------------------------------------
572    // Clone behavior
573    // ---------------------------------------------------------------
574
575    #[test]
576    fn test_token_operation_clone() {
577        let op = TokenOperationType::TokenBurn {
578            token_id: test_token_id(),
579            identity_balance_holder_id: test_identity_id(),
580            burn_amount: 100,
581        };
582        let cloned = op.clone();
583        match cloned {
584            TokenOperationType::TokenBurn { burn_amount, .. } => {
585                assert_eq!(burn_amount, 100);
586            }
587            _ => panic!("clone should preserve variant"),
588        }
589    }
590
591    // ---------------------------------------------------------------
592    // Debug trait
593    // ---------------------------------------------------------------
594
595    #[test]
596    fn test_token_set_status_construction() {
597        use dpp::tokens::status::v0::TokenStatusV0;
598        let op = TokenOperationType::TokenSetStatus {
599            token_id: test_token_id(),
600            status: TokenStatus::V0(TokenStatusV0 { paused: true }),
601        };
602        match op {
603            TokenOperationType::TokenSetStatus { token_id, .. } => {
604                assert_eq!(token_id, test_token_id());
605            }
606            _ => panic!("expected TokenSetStatus variant"),
607        }
608    }
609
610    #[test]
611    fn test_token_history_construction() {
612        use dpp::tokens::token_event::TokenEvent;
613
614        let op = TokenOperationType::TokenHistory {
615            token_id: test_token_id(),
616            owner_id: test_identity_id(),
617            nonce: 42,
618            event: TokenEvent::Mint(1000, test_identity_id(), None),
619        };
620        match op {
621            TokenOperationType::TokenHistory {
622                token_id,
623                owner_id,
624                nonce,
625                ..
626            } => {
627                assert_eq!(token_id, test_token_id());
628                assert_eq!(owner_id, test_identity_id());
629                assert_eq!(nonce, 42);
630            }
631            _ => panic!("expected TokenHistory variant"),
632        }
633    }
634
635    #[test]
636    fn test_token_mark_perpetual_release_construction() {
637        let op = TokenOperationType::TokenMarkPerpetualReleaseAsDistributed {
638            token_id: test_token_id(),
639            recipient_id: test_recipient_id(),
640            cycle_start_moment: RewardDistributionMoment::BlockBasedMoment(100),
641        };
642        match op {
643            TokenOperationType::TokenMarkPerpetualReleaseAsDistributed {
644                token_id,
645                recipient_id,
646                ..
647            } => {
648                assert_eq!(token_id, test_token_id());
649                assert_eq!(recipient_id, test_recipient_id());
650            }
651            _ => panic!("expected TokenMarkPerpetualReleaseAsDistributed variant"),
652        }
653    }
654
655    #[test]
656    fn test_token_set_price_some() {
657        use dpp::tokens::token_pricing_schedule::TokenPricingSchedule;
658
659        let pricing = TokenPricingSchedule::SinglePrice(5000);
660        let op = TokenOperationType::TokenSetPriceForDirectPurchase {
661            token_id: test_token_id(),
662            price: Some(pricing),
663        };
664        match op {
665            TokenOperationType::TokenSetPriceForDirectPurchase { token_id, price } => {
666                assert_eq!(token_id, test_token_id());
667                assert!(price.is_some());
668            }
669            _ => panic!("expected TokenSetPriceForDirectPurchase variant"),
670        }
671    }
672
673    #[test]
674    fn test_token_operation_debug() {
675        let op = TokenOperationType::TokenBurn {
676            token_id: test_token_id(),
677            identity_balance_holder_id: test_identity_id(),
678            burn_amount: 42,
679        };
680        let debug_str = format!("{:?}", op);
681        assert!(debug_str.contains("TokenBurn"));
682        assert!(debug_str.contains("42"));
683    }
684}