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#[derive(Clone, Debug)]
20pub enum TokenOperationType {
21 TokenBurn {
23 token_id: Identifier,
25 identity_balance_holder_id: Identifier,
27 burn_amount: TokenAmount,
29 },
30 TokenMint {
32 token_id: Identifier,
34 identity_balance_holder_id: Identifier,
36 mint_amount: TokenAmount,
38 allow_first_mint: bool,
40 allow_saturation: bool,
43 },
44 TokenMintMany {
46 token_id: Identifier,
48 recipients: Vec<(Identifier, u64)>,
50 mint_amount: TokenAmount,
52 allow_first_mint: bool,
54 },
55 TokenMarkPerpetualReleaseAsDistributed {
58 token_id: Identifier,
60 recipient_id: Identifier,
62 cycle_start_moment: RewardDistributionMoment,
65 },
66 TokenMarkPreProgrammedReleaseAsDistributed {
69 token_id: Identifier,
71 recipient_id: Identifier,
73 release_time: TimestampMillis,
75 },
76 TokenTransfer {
78 token_id: Identifier,
80 sender_id: Identifier,
82 recipient_id: Identifier,
84 amount: TokenAmount,
86 },
87 TokenFreeze {
89 token_id: Identifier,
91 frozen_identity_id: Identifier,
93 },
94 TokenUnfreeze {
96 token_id: Identifier,
98 frozen_identity_id: Identifier,
100 },
101 TokenSetStatus {
103 token_id: Identifier,
105 status: TokenStatus,
107 },
108 TokenHistory {
110 token_id: Identifier,
112 owner_id: Identifier,
114 nonce: IdentityNonce,
116 event: TokenEvent,
118 },
119 TokenSetPriceForDirectPurchase {
121 token_id: Identifier,
123 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}