1mod address_funds;
2mod contract;
3mod contract_fee_pot;
4mod contract_group;
5mod contract_moderation;
6mod document;
7mod drive_methods;
8pub(crate) mod finalize_task;
9mod group;
10mod identity;
11mod prefunded_specialized_balance;
12mod shielded;
13mod system;
14mod token;
15mod withdrawals;
16
17use crate::util::batch::GroveDbOpBatch;
18
19use crate::drive::contract::moderation::types::ContractTeamActionWrite;
20use crate::drive::document::preallocation_bindings_targeting;
21use crate::drive::Drive;
22use crate::error::Error;
23use crate::fees::op::LowLevelDriveOperation;
24use dpp::block::block_info::BlockInfo;
25use dpp::fee::Credits;
26
27pub use address_funds::AddressFundsOperationType;
28pub use contract::DataContractOperationType;
29pub use contract_fee_pot::ContractFeePotOperationType;
30pub use contract_group::ContractGroupOperationType;
31pub use contract_moderation::ContractModerationOperationType;
32pub use document::DocumentOperation;
33pub use document::DocumentOperationType;
34pub use document::DocumentOperationsForContractDocumentType;
35pub use document::UpdateOperationInfo;
36pub use group::GroupOperationType;
37pub use identity::IdentityOperationType;
38pub use prefunded_specialized_balance::PrefundedSpecializedBalanceOperationType;
39pub use shielded::ShieldedPoolOperationType;
40pub use system::SystemOperationType;
41pub use token::TokenOperationType;
42pub use withdrawals::WithdrawalOperationType;
43
44use grovedb::{EstimatedLayerInformation, TransactionArg};
45
46use crate::fees::op::LowLevelDriveOperation::GroveOperation;
47
48use dpp::version::PlatformVersion;
49use grovedb::batch::{KeyInfoPath, QualifiedGroveDbOp};
50
51use crate::error::drive::DriveError;
52use crate::error::fee::FeeError;
53use crate::util::batch::drive_op_batch::finalize_task::{
54 DriveOperationFinalizationTasks, DriveOperationFinalizeTask,
55};
56use dpp::data_contract::accessors::v0::DataContractV0Getters;
57use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
58use dpp::data_contract::document_type::action_fees::ContractFeePot;
59use dpp::data_contract::document_type::DocumentTypeRef;
60use dpp::data_contract::DataContract;
61use dpp::identifier::Identifier;
62
63use std::collections::{BTreeMap, BTreeSet, HashMap};
64
65pub trait DriveLowLevelOperationConverter {
67 fn into_low_level_drive_operations(
69 self,
70 drive: &Drive,
71 estimated_costs_only_with_layer_info: &mut Option<
72 HashMap<KeyInfoPath, EstimatedLayerInformation>,
73 >,
74 block_info: &BlockInfo,
75 transaction: TransactionArg,
76 platform_version: &PlatformVersion,
77 ) -> Result<Vec<LowLevelDriveOperation>, Error>;
78}
79
80pub struct DriveOperationContext {
83 #[allow(dead_code)]
84 #[deprecated(note = "This function is marked as unused.")]
85 #[allow(deprecated)]
86 identity_balance_changes: BTreeMap<[u8; 32], i64>,
87}
88
89#[allow(clippy::large_enum_variant)]
91#[derive(Clone, Debug)]
92pub enum DriveOperation<'a> {
93 DataContractOperation(DataContractOperationType<'a>),
95 DocumentOperation(DocumentOperationType<'a>),
97 TokenOperation(TokenOperationType),
99 WithdrawalOperation(WithdrawalOperationType),
101 IdentityOperation(IdentityOperationType),
103 PrefundedSpecializedBalanceOperation(PrefundedSpecializedBalanceOperationType),
105 SystemOperation(SystemOperationType),
107 GroupOperation(GroupOperationType),
109 ContractGroupOperation(ContractGroupOperationType),
111 ContractModerationOperation(ContractModerationOperationType),
113 ContractFeePotOperation(ContractFeePotOperationType),
115 AddressFundsOperation(AddressFundsOperationType),
117 ShieldedPoolOperation(ShieldedPoolOperationType),
119 GroveDBOperation(QualifiedGroveDbOp),
121 GroveDBOpBatch(GroveDbOpBatch),
123 FinalizeOperation(DriveOperationFinalizeTask),
125}
126
127impl DriveLowLevelOperationConverter for DriveOperation<'_> {
128 fn into_low_level_drive_operations(
129 self,
130 drive: &Drive,
131 estimated_costs_only_with_layer_info: &mut Option<
132 HashMap<KeyInfoPath, EstimatedLayerInformation>,
133 >,
134 block_info: &BlockInfo,
135 transaction: TransactionArg,
136 platform_version: &PlatformVersion,
137 ) -> Result<Vec<LowLevelDriveOperation>, Error> {
138 match self {
139 DriveOperation::DataContractOperation(contract_operation_type) => {
140 contract_operation_type.into_low_level_drive_operations(
141 drive,
142 estimated_costs_only_with_layer_info,
143 block_info,
144 transaction,
145 platform_version,
146 )
147 }
148 DriveOperation::DocumentOperation(document_operation_type) => document_operation_type
149 .into_low_level_drive_operations(
150 drive,
151 estimated_costs_only_with_layer_info,
152 block_info,
153 transaction,
154 platform_version,
155 ),
156 DriveOperation::WithdrawalOperation(withdrawal_operation_type) => {
157 withdrawal_operation_type.into_low_level_drive_operations(
158 drive,
159 estimated_costs_only_with_layer_info,
160 block_info,
161 transaction,
162 platform_version,
163 )
164 }
165 DriveOperation::IdentityOperation(identity_operation_type) => identity_operation_type
166 .into_low_level_drive_operations(
167 drive,
168 estimated_costs_only_with_layer_info,
169 block_info,
170 transaction,
171 platform_version,
172 ),
173 DriveOperation::PrefundedSpecializedBalanceOperation(
174 prefunded_balance_operation_type,
175 ) => prefunded_balance_operation_type.into_low_level_drive_operations(
176 drive,
177 estimated_costs_only_with_layer_info,
178 block_info,
179 transaction,
180 platform_version,
181 ),
182 DriveOperation::SystemOperation(system_operation_type) => system_operation_type
183 .into_low_level_drive_operations(
184 drive,
185 estimated_costs_only_with_layer_info,
186 block_info,
187 transaction,
188 platform_version,
189 ),
190 DriveOperation::ShieldedPoolOperation(shielded_pool_operation_type) => {
191 shielded_pool_operation_type.into_low_level_drive_operations(
192 drive,
193 estimated_costs_only_with_layer_info,
194 block_info,
195 transaction,
196 platform_version,
197 )
198 }
199 DriveOperation::GroveDBOperation(op) => Ok(vec![GroveOperation(op)]),
200 DriveOperation::GroveDBOpBatch(operations) => Ok(operations
201 .operations
202 .into_iter()
203 .map(GroveOperation)
204 .collect()),
205 DriveOperation::TokenOperation(token_operation_type) => token_operation_type
206 .into_low_level_drive_operations(
207 drive,
208 estimated_costs_only_with_layer_info,
209 block_info,
210 transaction,
211 platform_version,
212 ),
213 DriveOperation::GroupOperation(group_operation_type) => group_operation_type
214 .into_low_level_drive_operations(
215 drive,
216 estimated_costs_only_with_layer_info,
217 block_info,
218 transaction,
219 platform_version,
220 ),
221 DriveOperation::ContractGroupOperation(contract_group_operation_type) => {
222 contract_group_operation_type.into_low_level_drive_operations(
223 drive,
224 estimated_costs_only_with_layer_info,
225 block_info,
226 transaction,
227 platform_version,
228 )
229 }
230 DriveOperation::ContractModerationOperation(contract_moderation_operation_type) => {
231 contract_moderation_operation_type.into_low_level_drive_operations(
232 drive,
233 estimated_costs_only_with_layer_info,
234 block_info,
235 transaction,
236 platform_version,
237 )
238 }
239 DriveOperation::ContractFeePotOperation(contract_fee_pot_operation_type) => {
240 contract_fee_pot_operation_type.into_low_level_drive_operations(
241 drive,
242 estimated_costs_only_with_layer_info,
243 block_info,
244 transaction,
245 platform_version,
246 )
247 }
248 DriveOperation::AddressFundsOperation(address_funds_operation_type) => {
249 address_funds_operation_type.into_low_level_drive_operations(
250 drive,
251 estimated_costs_only_with_layer_info,
252 block_info,
253 transaction,
254 platform_version,
255 )
256 }
257 DriveOperation::FinalizeOperation(_) => Ok(vec![]),
258 }
259 }
260}
261
262impl DriveOperation<'_> {
263 pub fn forfeits_storage_refunds(&self) -> bool {
266 matches!(
267 self,
268 Self::ContractModerationOperation(
269 ContractModerationOperationType::ForfeitStorageRefunds
270 )
271 )
272 }
273
274 pub fn refunds_moderation_storage(&self) -> bool {
281 match self {
282 Self::ContractModerationOperation(
283 ContractModerationOperationType::AddDocumentRemoval {
284 replaced_record_size,
285 ..
286 },
287 ) => replaced_record_size.is_some(),
288 Self::ContractModerationOperation(
289 ContractModerationOperationType::AddTeamActionSignature { write, .. },
290 ) => match write {
291 ContractTeamActionWrite::Close { .. } => true,
292 ContractTeamActionWrite::Approve { dropped_signers } => !dropped_signers.is_empty(),
293 ContractTeamActionWrite::Propose { .. } => false,
294 },
295 _ => false,
296 }
297 }
298
299 pub(crate) fn into_low_level_drive_operations_after_ttl_drain(
301 self,
302 drive: &Drive,
303 estimated_costs_only_with_layer_info: &mut Option<
304 HashMap<KeyInfoPath, EstimatedLayerInformation>,
305 >,
306 block_info: &BlockInfo,
307 transaction: TransactionArg,
308 platform_version: &PlatformVersion,
309 ) -> Result<Vec<LowLevelDriveOperation>, Error> {
310 match self {
311 Self::DocumentOperation(operation) => operation
312 .into_low_level_drive_operations_after_ttl_drain(
313 drive,
314 estimated_costs_only_with_layer_info,
315 block_info,
316 transaction,
317 platform_version,
318 ),
319 operation => operation.into_low_level_drive_operations(
320 drive,
321 estimated_costs_only_with_layer_info,
322 block_info,
323 transaction,
324 platform_version,
325 ),
326 }
327 }
328}
329
330impl Drive {
331 pub(crate) fn prepare_drive_operations_time_range_ttl(
334 &self,
335 operations: &[DriveOperation],
336 block_info: &BlockInfo,
337 transaction: TransactionArg,
338 platform_version: &PlatformVersion,
339 ) -> Result<(), Error> {
340 for operation in operations {
341 if let DriveOperation::DocumentOperation(document) = operation {
342 document.prepare_time_range_ttl(self, block_info, transaction, platform_version)?;
343 }
344 }
345 Ok(())
346 }
347
348 pub(crate) fn refuse_repeated_counter_moves(
365 &self,
366 operations: &[DriveOperation],
367 block_info: &BlockInfo,
368 transaction: TransactionArg,
369 platform_version: &PlatformVersion,
370 ) -> Result<(), Error> {
371 let mut moved: BTreeSet<(Identifier, String)> = BTreeSet::new();
372 for operation in operations {
373 let DriveOperation::DocumentOperation(operation) = operation else {
374 continue;
375 };
376 operation.for_each_document_type(
377 self,
378 block_info,
379 transaction,
380 |contract, document_type, preallocates| {
381 for counter_type in counter_types_moved(contract, document_type, preallocates) {
382 if !moved.insert((contract.id(), counter_type)) {
383 return Err(Error::Drive(DriveError::CorruptedCodeExecution(
384 "a batch moves one document type's summableOffCountIndex \
385 counters for more than one document",
386 )));
387 }
388 }
389 Ok(())
390 },
391 platform_version,
392 )?;
393 }
394 Ok(())
395 }
396}
397
398fn counter_types_moved(
403 contract: &DataContract,
404 document_type: DocumentTypeRef,
405 preallocates: bool,
406) -> BTreeSet<String> {
407 let mut moved = BTreeSet::new();
408 if document_type
409 .indexes()
410 .values()
411 .any(|index| index.is_summable_off_count_index())
412 {
413 moved.insert(document_type.name().clone());
414 }
415 if preallocates {
416 moved.extend(
417 preallocation_bindings_targeting(contract, document_type)
418 .filter(|(_, index, _)| index.is_summable_off_count_index())
419 .map(|(referring_type, _, _)| referring_type.name().clone()),
420 );
421 }
422 moved
423}
424
425impl DriveOperationFinalizationTasks for DriveOperation<'_> {
426 fn finalization_tasks(
427 &self,
428 platform_version: &PlatformVersion,
429 ) -> Result<Option<Vec<DriveOperationFinalizeTask>>, Error> {
430 match platform_version
431 .drive
432 .methods
433 .state_transitions
434 .operations
435 .finalization_tasks
436 {
437 0 => self.finalization_tasks_v0(platform_version),
438 version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
439 method: "DriveOperation.finalization_tasks".to_string(),
440 known_versions: vec![0],
441 received: version,
442 })),
443 }
444 }
445}
446
447impl DriveOperation<'_> {
448 fn finalization_tasks_v0(
449 &self,
450 platform_version: &PlatformVersion,
451 ) -> Result<Option<Vec<DriveOperationFinalizeTask>>, Error> {
452 match self {
453 DriveOperation::DataContractOperation(o) => o.finalization_tasks(platform_version),
454 DriveOperation::FinalizeOperation(task) => Ok(Some(vec![task.clone()])),
455 _ => Ok(None),
456 }
457 }
458
459 pub fn credit_mints(operations: &[DriveOperation]) -> Credits {
464 operations
465 .iter()
466 .filter_map(|operation| match operation {
467 DriveOperation::SystemOperation(SystemOperationType::AddToSystemCredits {
468 amount,
469 }) => Some(*amount),
470 _ => None,
471 })
472 .fold(0u64, |total, amount| total.saturating_add(amount))
473 }
474
475 pub fn merge_balance_writes(operations: Vec<Self>) -> Result<Vec<Self>, Error> {
491 if operations
493 .iter()
494 .filter(|operation| balance_write(operation).is_some())
495 .nth(1)
496 .is_none()
497 {
498 return Ok(operations);
499 }
500 let mut writes: BTreeMap<BalanceKey, (usize, i128)> = BTreeMap::new();
501 for (key, change) in operations.iter().filter_map(balance_write) {
502 let (count, net) = writes.entry(key).or_default();
503 *count += 1;
504 *net = net
505 .checked_add(change)
506 .ok_or(Error::Fee(FeeError::Overflow(
507 "the writes one batch makes to one balance overflow",
508 )))?;
509 }
510 if writes.values().all(|(count, _)| *count == 1) {
511 return Ok(operations);
512 }
513 let mut merged = Vec::with_capacity(operations.len());
514 for operation in operations {
515 let Some((key, _)) = balance_write(&operation) else {
516 merged.push(operation);
517 continue;
518 };
519 match writes.get_mut(&key) {
520 Some((1, _)) => merged.push(operation),
521 Some((count, net)) if *count > 1 => {
524 merged.extend(net_balance_write(key, *net)?);
525 *count = 0;
526 }
527 _ => {}
528 }
529 }
530 Ok(merged)
531 }
532
533 pub fn refuse_repeated_token_balance_writes(operations: &[Self]) -> Result<(), Error> {
543 let mut written = BTreeSet::new();
544 for key in operations.iter().flat_map(token_balance_writes) {
545 if !written.insert(key) {
546 return Err(Error::Drive(DriveError::CorruptedCodeExecution(
547 "a batch writes one token balance, token supply or token shielded pool \
548 total more than once",
549 )));
550 }
551 }
552 Ok(())
553 }
554}
555
556#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
559enum BalanceKey {
560 Identity([u8; 32]),
561 FeePot(Identifier, ContractFeePot),
562 PrefundedSpecializedBalance(Identifier),
563}
564
565#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
568enum TokenBalanceKey {
569 Holder(Identifier, Identifier),
570 Supply(Identifier),
571 PoolTotal(Identifier),
572}
573
574fn token_balance_writes(operation: &DriveOperation) -> Vec<TokenBalanceKey> {
576 let DriveOperation::TokenOperation(operation) = operation else {
577 return vec![];
578 };
579 match operation {
580 TokenOperationType::TokenBurn {
581 token_id,
582 identity_balance_holder_id,
583 ..
584 }
585 | TokenOperationType::TokenMint {
586 token_id,
587 identity_balance_holder_id,
588 ..
589 } => vec![
590 TokenBalanceKey::Holder(*token_id, *identity_balance_holder_id),
591 TokenBalanceKey::Supply(*token_id),
592 ],
593 TokenOperationType::TokenMintMany {
594 token_id,
595 recipients,
596 ..
597 } => recipients
598 .iter()
599 .map(|(recipient_id, _)| TokenBalanceKey::Holder(*token_id, *recipient_id))
600 .chain([TokenBalanceKey::Supply(*token_id)])
601 .collect(),
602 TokenOperationType::TokenTransfer {
603 token_id,
604 sender_id,
605 recipient_id,
606 ..
607 } => vec![
608 TokenBalanceKey::Holder(*token_id, *sender_id),
609 TokenBalanceKey::Holder(*token_id, *recipient_id),
610 ],
611 TokenOperationType::TokenShield {
615 token_id,
616 identity_id,
617 ..
618 } => vec![
619 TokenBalanceKey::Holder(*token_id, *identity_id),
620 TokenBalanceKey::PoolTotal(*token_id),
621 ],
622 TokenOperationType::TokenUnshield {
623 token_id,
624 recipient_id,
625 ..
626 } => vec![
627 TokenBalanceKey::Holder(*token_id, *recipient_id),
628 TokenBalanceKey::PoolTotal(*token_id),
629 ],
630 TokenOperationType::TokenMintToPool { token_id, .. }
633 | TokenOperationType::TokenBurnFromPool { token_id, .. } => vec![
634 TokenBalanceKey::Supply(*token_id),
635 TokenBalanceKey::PoolTotal(*token_id),
636 ],
637 TokenOperationType::TokenShieldedTransfer { .. }
640 | TokenOperationType::TokenFreeze { .. }
643 | TokenOperationType::TokenUnfreeze { .. }
644 | TokenOperationType::TokenSetStatus { .. }
645 | TokenOperationType::TokenSetPriceForDirectPurchase { .. }
646 | TokenOperationType::TokenMarkPerpetualReleaseAsDistributed { .. }
647 | TokenOperationType::TokenMarkPreProgrammedReleaseAsDistributed { .. }
648 | TokenOperationType::TokenMarkOncePerIdentityReleaseAsDistributed { .. }
649 | TokenOperationType::TokenHistory { .. } => vec![],
650 }
651}
652
653fn balance_write(operation: &DriveOperation) -> Option<(BalanceKey, i128)> {
656 match operation {
657 DriveOperation::IdentityOperation(IdentityOperationType::AddToIdentityBalance {
658 identity_id,
659 added_balance,
660 }) => Some((BalanceKey::Identity(*identity_id), *added_balance as i128)),
661 DriveOperation::IdentityOperation(IdentityOperationType::RemoveFromIdentityBalance {
662 identity_id,
663 balance_to_remove,
664 }) => Some((
665 BalanceKey::Identity(*identity_id),
666 -(*balance_to_remove as i128),
667 )),
668 DriveOperation::ContractFeePotOperation(ContractFeePotOperationType::AddToPot {
669 contract_id,
670 pot,
671 amount,
672 }) => Some((BalanceKey::FeePot(*contract_id, *pot), *amount as i128)),
673 DriveOperation::ContractFeePotOperation(ContractFeePotOperationType::DeductFromPot {
674 contract_id,
675 pot,
676 amount,
677 }) => Some((BalanceKey::FeePot(*contract_id, *pot), -(*amount as i128))),
678 DriveOperation::PrefundedSpecializedBalanceOperation(
679 PrefundedSpecializedBalanceOperationType::CreateNewPrefundedBalance {
680 prefunded_specialized_balance_id,
681 add_balance,
682 },
683 ) => Some((
684 BalanceKey::PrefundedSpecializedBalance(*prefunded_specialized_balance_id),
685 *add_balance as i128,
686 )),
687 DriveOperation::PrefundedSpecializedBalanceOperation(
688 PrefundedSpecializedBalanceOperationType::DeductFromPrefundedBalance {
689 prefunded_specialized_balance_id,
690 remove_balance,
691 },
692 ) => Some((
693 BalanceKey::PrefundedSpecializedBalance(*prefunded_specialized_balance_id),
694 -(*remove_balance as i128),
695 )),
696 _ => None,
697 }
698}
699
700fn net_balance_write<'a>(key: BalanceKey, net: i128) -> Result<Option<DriveOperation<'a>>, Error> {
702 if net == 0 {
703 return Ok(None);
704 }
705 let amount = Credits::try_from(net.unsigned_abs()).map_err(|_| {
706 Error::Fee(FeeError::Overflow(
707 "the merged writes of one balance overflow credits",
708 ))
709 })?;
710 Ok(Some(match (key, net > 0) {
711 (BalanceKey::Identity(identity_id), true) => {
712 DriveOperation::IdentityOperation(IdentityOperationType::AddToIdentityBalance {
713 identity_id,
714 added_balance: amount,
715 })
716 }
717 (BalanceKey::Identity(identity_id), false) => {
718 DriveOperation::IdentityOperation(IdentityOperationType::RemoveFromIdentityBalance {
719 identity_id,
720 balance_to_remove: amount,
721 })
722 }
723 (BalanceKey::FeePot(contract_id, pot), true) => {
724 DriveOperation::ContractFeePotOperation(ContractFeePotOperationType::AddToPot {
725 contract_id,
726 pot,
727 amount,
728 })
729 }
730 (BalanceKey::FeePot(contract_id, pot), false) => {
731 DriveOperation::ContractFeePotOperation(ContractFeePotOperationType::DeductFromPot {
732 contract_id,
733 pot,
734 amount,
735 })
736 }
737 (BalanceKey::PrefundedSpecializedBalance(prefunded_specialized_balance_id), true) => {
739 DriveOperation::PrefundedSpecializedBalanceOperation(
740 PrefundedSpecializedBalanceOperationType::CreateNewPrefundedBalance {
741 prefunded_specialized_balance_id,
742 add_balance: amount,
743 },
744 )
745 }
746 (BalanceKey::PrefundedSpecializedBalance(prefunded_specialized_balance_id), false) => {
747 DriveOperation::PrefundedSpecializedBalanceOperation(
748 PrefundedSpecializedBalanceOperationType::DeductFromPrefundedBalance {
749 prefunded_specialized_balance_id,
750 remove_balance: amount,
751 },
752 )
753 }
754 }))
755}
756
757#[cfg(feature = "server")]
758#[cfg(test)]
759mod tests {
760 use grovedb::Element;
761 use std::borrow::Cow;
762 use std::option::Option::None;
763
764 use super::*;
765
766 use crate::util::test_helpers::setup_contract;
767 use dpp::block::block_info::BlockInfo;
768 use dpp::data_contract::accessors::v0::DataContractV0Getters;
769 use dpp::data_contract::DataContract;
770 use dpp::serialization::PlatformSerializableWithPlatformVersion;
771 use dpp::tests::json_document::{json_document_to_contract, json_document_to_document};
772 use dpp::util::cbor_serializer;
773 use rand::Rng;
774 use serde_json::json;
775
776 use crate::util::batch::drive_op_batch::document::DocumentOperation::{
777 AddOperation, UpdateOperation,
778 };
779 use crate::util::batch::drive_op_batch::document::DocumentOperationType::MultipleDocumentOperationsForSameContractDocumentType;
780 use crate::util::batch::drive_op_batch::document::{
781 DocumentOperationsForContractDocumentType, UpdateOperationInfo,
782 };
783 use crate::util::batch::DataContractOperationType::ApplyContract;
784 use crate::util::batch::DocumentOperationType::AddDocument;
785 use crate::util::batch::DriveOperation::{DataContractOperation, DocumentOperation};
786
787 use crate::drive::contract::paths::contract_root_path;
788 use crate::drive::Drive;
789 use crate::util::object_size_info::DocumentInfo::DocumentRefInfo;
790 use crate::util::object_size_info::{DataContractInfo, DocumentTypeInfo, OwnedDocumentInfo};
791 use crate::util::storage_flags::StorageFlags;
792 use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure;
793
794 use crate::state_transition_action::shielded::ShieldedActionNote;
795 use dpp::balances::credits::TokenAmount;
796 use dpp::identity::accessors::IdentityGettersV0;
797 use dpp::identity::Identity;
798
799 #[test]
800 fn test_add_dashpay_documents() {
801 let drive: Drive = setup_drive_with_initial_state_structure(None);
802 let platform_version = PlatformVersion::latest();
803
804 let mut drive_operations = vec![];
805 let db_transaction = drive.grove.start_transaction();
806
807 let contract = json_document_to_contract(
808 "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json",
809 false,
810 platform_version,
811 )
812 .expect("expected to get contract");
813
814 let _document_type = contract
815 .document_type_for_name("contactRequest")
816 .expect("expected to get document type");
817
818 drive_operations.push(DataContractOperation(ApplyContract {
819 contract: Cow::Borrowed(&contract),
820 storage_flags: None,
821 }));
822
823 let random_owner_id = rand::thread_rng().gen::<[u8; 32]>();
824
825 let document_type = contract
826 .document_type_for_name("contactRequest")
827 .expect("expected to get document type");
828
829 let dashpay_cr_document = json_document_to_document(
830 "tests/supporting_files/contract/dashpay/contact-request0.json",
831 Some(random_owner_id.into()),
832 document_type,
833 platform_version,
834 )
835 .expect("expected to get document");
836
837 drive_operations.push(DocumentOperation(AddDocument {
838 owned_document_info: OwnedDocumentInfo {
839 document_info: DocumentRefInfo((
840 &dashpay_cr_document,
841 StorageFlags::optional_default_as_cow(),
842 )),
843 owner_id: None,
844 },
845 contract_info: DataContractInfo::BorrowedDataContract(&contract),
846 document_type_info: DocumentTypeInfo::DocumentTypeRef(document_type),
847 override_document: false,
848 }));
849
850 drive
851 .apply_drive_operations(
852 drive_operations,
853 true,
854 &BlockInfo::default(),
855 Some(&db_transaction),
856 platform_version,
857 None,
858 )
859 .expect("expected to insert contract and document");
860
861 let element = drive
862 .grove
863 .get(
864 &contract_root_path(&contract.id().to_buffer()),
865 &[0],
866 Some(&db_transaction),
867 &platform_version.drive.grove_version,
868 )
869 .unwrap()
870 .expect("expected to get contract back");
871
872 assert_eq!(
873 element,
874 Element::Item(
875 contract
876 .serialize_to_bytes_with_platform_version(platform_version)
877 .expect("expected to serialize contract"),
878 None
879 )
880 );
881
882 let query_value = json!({
883 "where": [
884 ],
885 "limit": 100,
886 "orderBy": [
887 ["$ownerId", "asc"],
888 ]
889 });
890 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
891 .expect("expected to serialize to cbor");
892
893 let (docs, _, _) = drive
894 .query_documents_cbor_from_contract(
895 &contract,
896 document_type,
897 where_cbor.as_slice(),
898 None,
899 Some(&db_transaction),
900 Some(platform_version.protocol_version),
901 )
902 .expect("expected to query");
903 assert_eq!(docs.len(), 1);
904 }
905
906 #[test]
907 fn test_add_multiple_dashpay_documents_individually_should_succeed() {
908 let drive = setup_drive_with_initial_state_structure(None);
909
910 let platform_version = PlatformVersion::latest();
911
912 let mut drive_operations = vec![];
913 let db_transaction = drive.grove.start_transaction();
914
915 let contract = json_document_to_contract(
916 "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json",
917 false,
918 platform_version,
919 )
920 .expect("expected to get contract");
921
922 let document_type = contract
923 .document_type_for_name("contactRequest")
924 .expect("expected to get document type");
925
926 drive_operations.push(DataContractOperation(ApplyContract {
927 contract: Cow::Borrowed(&contract),
928 storage_flags: None,
929 }));
930 let random_owner_id = rand::thread_rng().gen::<[u8; 32]>();
931
932 let dashpay_cr_document = json_document_to_document(
933 "tests/supporting_files/contract/dashpay/contact-request0.json",
934 Some(random_owner_id.into()),
935 document_type,
936 platform_version,
937 )
938 .expect("expected to get contract");
939
940 drive_operations.push(DocumentOperation(AddDocument {
941 owned_document_info: OwnedDocumentInfo {
942 document_info: DocumentRefInfo((&dashpay_cr_document, None)),
943 owner_id: None,
944 },
945 contract_info: DataContractInfo::BorrowedDataContract(&contract),
946 document_type_info: DocumentTypeInfo::DocumentTypeNameAsStr("contactRequest"),
947 override_document: false,
948 }));
949
950 let random_owner_id = rand::thread_rng().gen::<[u8; 32]>();
951
952 let dashpay_cr_1_document = json_document_to_document(
953 "tests/supporting_files/contract/dashpay/contact-request1.json",
954 Some(random_owner_id.into()),
955 document_type,
956 platform_version,
957 )
958 .expect("expected to get contract");
959
960 drive_operations.push(DocumentOperation(AddDocument {
961 owned_document_info: OwnedDocumentInfo {
962 document_info: DocumentRefInfo((&dashpay_cr_1_document, None)),
963 owner_id: None,
964 },
965 contract_info: DataContractInfo::BorrowedDataContract(&contract),
966 document_type_info: DocumentTypeInfo::DocumentTypeNameAsStr("contactRequest"),
967 override_document: false,
968 }));
969
970 drive
971 .apply_drive_operations(
972 drive_operations,
973 true,
974 &BlockInfo::default(),
975 Some(&db_transaction),
976 platform_version,
977 None,
978 )
979 .expect("expected to be able to insert documents");
980
981 let query_value = json!({
982 "where": [
983 ],
984 "limit": 100,
985 "orderBy": [
986 ["$ownerId", "asc"],
987 ]
988 });
989 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
990 .expect("expected to serialize to cbor");
991
992 let (docs, _, _) = drive
993 .query_documents_cbor_from_contract(
994 &contract,
995 document_type,
996 where_cbor.as_slice(),
997 None,
998 Some(&db_transaction),
999 Some(platform_version.protocol_version),
1000 )
1001 .expect("expected to query");
1002 assert_eq!(docs.len(), 2);
1003 }
1004
1005 #[test]
1006 fn test_add_multiple_dashpay_documents() {
1007 let drive: Drive = setup_drive_with_initial_state_structure(None);
1008
1009 let platform_version = PlatformVersion::latest();
1010
1011 let mut drive_operations = vec![];
1012 let db_transaction = drive.grove.start_transaction();
1013
1014 let contract = json_document_to_contract(
1015 "tests/supporting_files/contract/dashpay/dashpay-contract-all-mutable.json",
1016 false,
1017 platform_version,
1018 )
1019 .expect("expected to get contract");
1020
1021 let document_type = contract
1022 .document_type_for_name("contactRequest")
1023 .expect("expected to get document type");
1024
1025 drive_operations.push(DataContractOperation(ApplyContract {
1026 contract: Cow::Borrowed(&contract),
1027 storage_flags: None,
1028 }));
1029
1030 let random_owner_id = rand::thread_rng().gen::<[u8; 32]>();
1031
1032 let document0 = json_document_to_document(
1033 "tests/supporting_files/contract/dashpay/contact-request0.json",
1034 Some(random_owner_id.into()),
1035 document_type,
1036 platform_version,
1037 )
1038 .expect("expected to get document 0");
1039
1040 let document1 = json_document_to_document(
1041 "tests/supporting_files/contract/dashpay/contact-request1.json",
1042 Some(random_owner_id.into()),
1043 document_type,
1044 platform_version,
1045 )
1046 .expect("expected to get document 1");
1047
1048 let operations = vec![
1049 AddOperation {
1050 owned_document_info: OwnedDocumentInfo {
1051 document_info: DocumentRefInfo((
1052 &document0,
1053 StorageFlags::optional_default_as_cow(),
1054 )),
1055 owner_id: Some(random_owner_id),
1056 },
1057 override_document: false,
1058 },
1059 AddOperation {
1060 owned_document_info: OwnedDocumentInfo {
1061 document_info: DocumentRefInfo((
1062 &document1,
1063 StorageFlags::optional_default_as_cow(),
1064 )),
1065 owner_id: Some(random_owner_id),
1066 },
1067 override_document: false,
1068 },
1069 ];
1070
1071 drive_operations.push(DocumentOperation(
1072 MultipleDocumentOperationsForSameContractDocumentType {
1073 document_operations: DocumentOperationsForContractDocumentType {
1074 operations,
1075 contract: &contract,
1076 document_type,
1077 },
1078 },
1079 ));
1080
1081 drive
1082 .apply_drive_operations(
1083 drive_operations,
1084 true,
1085 &BlockInfo::default(),
1086 Some(&db_transaction),
1087 platform_version,
1088 None,
1089 )
1090 .expect("expected to be able to insert documents");
1091
1092 let element = drive
1093 .grove
1094 .get(
1095 &contract_root_path(&contract.id().to_buffer()),
1096 &[0],
1097 Some(&db_transaction),
1098 &platform_version.drive.grove_version,
1099 )
1100 .unwrap()
1101 .expect("expected to get contract back");
1102
1103 assert_eq!(
1104 element,
1105 Element::Item(
1106 contract
1107 .serialize_to_bytes_with_platform_version(platform_version)
1108 .expect("expected to serialize contract"),
1109 None
1110 )
1111 );
1112
1113 let query_value = json!({
1114 "where": [
1115 ],
1116 "limit": 100,
1117 "orderBy": [
1118 ["$ownerId", "asc"],
1119 ]
1120 });
1121 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1122 .expect("expected to serialize to cbor");
1123
1124 let (docs, _, _) = drive
1125 .query_documents_cbor_from_contract(
1126 &contract,
1127 document_type,
1128 where_cbor.as_slice(),
1129 None,
1130 Some(&db_transaction),
1131 Some(platform_version.protocol_version),
1132 )
1133 .expect("expected to query");
1134 assert_eq!(docs.len(), 2);
1135 }
1136
1137 #[test]
1138 fn test_add_multiple_family_documents() {
1139 let drive: Drive = setup_drive_with_initial_state_structure(None);
1140
1141 let platform_version = PlatformVersion::latest();
1142
1143 let mut drive_operations = vec![];
1144 let db_transaction = drive.grove.start_transaction();
1145
1146 let contract = setup_contract(
1147 &drive,
1148 "tests/supporting_files/contract/family/family-contract.json",
1149 None,
1150 None,
1151 None::<fn(&mut DataContract)>,
1152 Some(&db_transaction),
1153 None,
1154 );
1155
1156 let document_type = contract
1157 .document_type_for_name("person")
1158 .expect("expected to get document type");
1159
1160 let random_owner_id0 = rand::thread_rng().gen::<[u8; 32]>();
1161
1162 let person_document0 = json_document_to_document(
1163 "tests/supporting_files/contract/family/person0.json",
1164 Some(random_owner_id0.into()),
1165 document_type,
1166 platform_version,
1167 )
1168 .expect("expected to get document");
1169
1170 let random_owner_id1 = rand::thread_rng().gen::<[u8; 32]>();
1171
1172 let person_document1 = json_document_to_document(
1173 "tests/supporting_files/contract/family/person3.json",
1174 Some(random_owner_id1.into()),
1175 document_type,
1176 platform_version,
1177 )
1178 .expect("expected to get document");
1179
1180 let mut operations = vec![];
1181
1182 operations.push(AddOperation {
1183 owned_document_info: OwnedDocumentInfo {
1184 document_info: DocumentRefInfo((
1185 &person_document0,
1186 StorageFlags::optional_default_as_cow(),
1187 )),
1188 owner_id: Some(random_owner_id0),
1189 },
1190 override_document: false,
1191 });
1192
1193 let random_owner_id1 = rand::thread_rng().gen::<[u8; 32]>();
1194
1195 operations.push(AddOperation {
1196 owned_document_info: OwnedDocumentInfo {
1197 document_info: DocumentRefInfo((
1198 &person_document1,
1199 StorageFlags::optional_default_as_cow(),
1200 )),
1201 owner_id: Some(random_owner_id1),
1202 },
1203 override_document: false,
1204 });
1205
1206 drive_operations.push(DocumentOperation(
1207 MultipleDocumentOperationsForSameContractDocumentType {
1208 document_operations: DocumentOperationsForContractDocumentType {
1209 operations,
1210 contract: &contract,
1211 document_type,
1212 },
1213 },
1214 ));
1215
1216 drive
1217 .apply_drive_operations(
1218 drive_operations,
1219 true,
1220 &BlockInfo::default(),
1221 Some(&db_transaction),
1222 platform_version,
1223 None,
1224 )
1225 .expect("expected to be able to insert documents");
1226
1227 let query_value = json!({
1228 "where": [
1229 ],
1230 "limit": 100,
1231 "orderBy": [
1232 ["$ownerId", "asc"],
1233 ]
1234 });
1235 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1236 .expect("expected to serialize to cbor");
1237
1238 let (docs, _, _) = drive
1239 .query_documents_cbor_from_contract(
1240 &contract,
1241 document_type,
1242 where_cbor.as_slice(),
1243 None,
1244 Some(&db_transaction),
1245 Some(platform_version.protocol_version),
1246 )
1247 .expect("expected to query");
1248 assert_eq!(docs.len(), 2);
1249 }
1250
1251 #[test]
1252 fn test_update_multiple_family_documents() {
1253 let drive: Drive = setup_drive_with_initial_state_structure(None);
1254
1255 let platform_version = PlatformVersion::latest();
1256
1257 let mut drive_operations = vec![];
1258 let db_transaction = drive.grove.start_transaction();
1259
1260 let contract = setup_contract(
1261 &drive,
1262 "tests/supporting_files/contract/family/family-contract-only-age-index.json",
1263 None,
1264 None,
1265 None::<fn(&mut DataContract)>,
1266 Some(&db_transaction),
1267 None,
1268 );
1269
1270 let document_type = contract
1271 .document_type_for_name("person")
1272 .expect("expected to get document type");
1273
1274 let random_owner_id0 = rand::thread_rng().gen::<[u8; 32]>();
1275
1276 let person_document0 = json_document_to_document(
1277 "tests/supporting_files/contract/family/person0.json",
1278 Some(random_owner_id0.into()),
1279 document_type,
1280 platform_version,
1281 )
1282 .expect("expected to get document");
1283
1284 let random_owner_id1 = rand::thread_rng().gen::<[u8; 32]>();
1285
1286 let person_document1 = json_document_to_document(
1287 "tests/supporting_files/contract/family/person3.json",
1288 Some(random_owner_id1.into()),
1289 document_type,
1290 platform_version,
1291 )
1292 .expect("expected to get document");
1293
1294 let operations = vec![
1295 AddOperation {
1296 owned_document_info: OwnedDocumentInfo {
1297 document_info: DocumentRefInfo((
1298 &person_document0,
1299 StorageFlags::optional_default_as_cow(),
1300 )),
1301 owner_id: Some(random_owner_id0),
1302 },
1303 override_document: false,
1304 },
1305 AddOperation {
1306 owned_document_info: OwnedDocumentInfo {
1307 document_info: DocumentRefInfo((
1308 &person_document1,
1309 StorageFlags::optional_default_as_cow(),
1310 )),
1311 owner_id: Some(random_owner_id1),
1312 },
1313 override_document: false,
1314 },
1315 ];
1316
1317 drive_operations.push(DocumentOperation(
1318 MultipleDocumentOperationsForSameContractDocumentType {
1319 document_operations: DocumentOperationsForContractDocumentType {
1320 operations,
1321 contract: &contract,
1322 document_type,
1323 },
1324 },
1325 ));
1326
1327 drive
1328 .apply_drive_operations(
1329 drive_operations,
1330 true,
1331 &BlockInfo::default(),
1332 Some(&db_transaction),
1333 platform_version,
1334 None,
1335 )
1336 .expect("expected to be able to insert documents");
1337
1338 drive_operations = vec![];
1341
1342 let random_owner_id0 = rand::thread_rng().gen::<[u8; 32]>();
1343
1344 let person_document0 = json_document_to_document(
1345 "tests/supporting_files/contract/family/person0-older.json",
1346 Some(random_owner_id0.into()),
1347 document_type,
1348 platform_version,
1349 )
1350 .expect("expected to get document");
1351
1352 let random_owner_id1 = rand::thread_rng().gen::<[u8; 32]>();
1353
1354 let person_document1 = json_document_to_document(
1355 "tests/supporting_files/contract/family/person3-older.json",
1356 Some(random_owner_id1.into()),
1357 document_type,
1358 platform_version,
1359 )
1360 .expect("expected to get document");
1361
1362 let operations = vec![
1363 UpdateOperation(UpdateOperationInfo {
1364 document: &person_document0,
1365 serialized_document: None,
1366 owner_id: Some(random_owner_id0),
1367 storage_flags: None,
1368 }),
1369 UpdateOperation(UpdateOperationInfo {
1370 document: &person_document1,
1371 serialized_document: None,
1372 owner_id: Some(random_owner_id1),
1373 storage_flags: None,
1374 }),
1375 ];
1376
1377 drive_operations.push(DocumentOperation(
1378 MultipleDocumentOperationsForSameContractDocumentType {
1379 document_operations: DocumentOperationsForContractDocumentType {
1380 operations,
1381 contract: &contract,
1382 document_type,
1383 },
1384 },
1385 ));
1386
1387 drive
1388 .apply_drive_operations(
1389 drive_operations,
1390 true,
1391 &BlockInfo::default(),
1392 Some(&db_transaction),
1393 platform_version,
1394 None,
1395 )
1396 .expect("expected to be able to update documents");
1397
1398 let query_value = json!({
1399 "where": [
1400 ],
1401 "limit": 100,
1402 "orderBy": [
1403 ["age", "asc"],
1404 ]
1405 });
1406 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1407 .expect("expected to serialize to cbor");
1408
1409 let (docs, _, _) = drive
1410 .query_documents_cbor_from_contract(
1411 &contract,
1412 document_type,
1413 where_cbor.as_slice(),
1414 None,
1415 Some(&db_transaction),
1416 Some(platform_version.protocol_version),
1417 )
1418 .expect("expected to query");
1419 assert_eq!(docs.len(), 2);
1420
1421 let query_value = json!({
1422 "where": [
1423 ["age", "==", 35]
1424 ],
1425 "limit": 100,
1426 "orderBy": [
1427 ["age", "asc"],
1428 ]
1429 });
1430 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1431 .expect("expected to serialize to cbor");
1432
1433 let (docs, _, _) = drive
1434 .query_documents_cbor_from_contract(
1435 &contract,
1436 document_type,
1437 where_cbor.as_slice(),
1438 None,
1439 Some(&db_transaction),
1440 Some(platform_version.protocol_version),
1441 )
1442 .expect("expected to query");
1443 assert_eq!(docs.len(), 0);
1444
1445 let query_value = json!({
1446 "where": [
1447 ["age", "==", 36]
1448 ],
1449 "limit": 100,
1450 "orderBy": [
1451 ["age", "asc"],
1452 ]
1453 });
1454 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1455 .expect("expected to serialize to cbor");
1456
1457 let (docs, _, _) = drive
1458 .query_documents_cbor_from_contract(
1459 &contract,
1460 document_type,
1461 where_cbor.as_slice(),
1462 None,
1463 Some(&db_transaction),
1464 Some(platform_version.protocol_version),
1465 )
1466 .expect("expected to query");
1467 assert_eq!(docs.len(), 2);
1468 }
1469
1470 #[test]
1471 fn test_update_multiple_family_documents_with_index_being_removed_and_added() {
1472 let drive: Drive = setup_drive_with_initial_state_structure(None);
1473
1474 let platform_version = PlatformVersion::latest();
1475
1476 let db_transaction = drive.grove.start_transaction();
1477
1478 let contract = setup_contract(
1479 &drive,
1480 "tests/supporting_files/contract/family/family-contract-only-age-index.json",
1481 None,
1482 None,
1483 None::<fn(&mut DataContract)>,
1484 Some(&db_transaction),
1485 None,
1486 );
1487
1488 let document_type = contract
1489 .document_type_for_name("person")
1490 .expect("expected to get document type");
1491
1492 let random_owner_id0 = rand::thread_rng().gen::<[u8; 32]>();
1493
1494 let person_document0 = json_document_to_document(
1495 "tests/supporting_files/contract/family/person0.json",
1496 Some(random_owner_id0.into()),
1497 document_type,
1498 platform_version,
1499 )
1500 .expect("expected to get document");
1501
1502 let random_owner_id1 = rand::thread_rng().gen::<[u8; 32]>();
1503
1504 let person_document1 = json_document_to_document(
1505 "tests/supporting_files/contract/family/person3-older.json",
1506 Some(random_owner_id1.into()),
1507 document_type,
1508 platform_version,
1509 )
1510 .expect("expected to get document");
1511
1512 let operations = vec![
1513 AddOperation {
1514 owned_document_info: OwnedDocumentInfo {
1515 document_info: DocumentRefInfo((
1516 &person_document0,
1517 StorageFlags::optional_default_as_cow(),
1518 )),
1519 owner_id: Some(random_owner_id0),
1520 },
1521 override_document: false,
1522 },
1523 AddOperation {
1524 owned_document_info: OwnedDocumentInfo {
1525 document_info: DocumentRefInfo((
1526 &person_document1,
1527 StorageFlags::optional_default_as_cow(),
1528 )),
1529 owner_id: Some(random_owner_id1),
1530 },
1531 override_document: false,
1532 },
1533 ];
1534 let drive_operations = vec![DocumentOperation(
1535 MultipleDocumentOperationsForSameContractDocumentType {
1536 document_operations: DocumentOperationsForContractDocumentType {
1537 operations,
1538 contract: &contract,
1539 document_type,
1540 },
1541 },
1542 )];
1543
1544 drive
1545 .apply_drive_operations(
1546 drive_operations,
1547 true,
1548 &BlockInfo::default(),
1549 Some(&db_transaction),
1550 platform_version,
1551 None,
1552 )
1553 .expect("expected to be able to insert documents");
1554
1555 let person_document0 = json_document_to_document(
1558 "tests/supporting_files/contract/family/person0-older.json",
1559 Some(random_owner_id0.into()),
1560 document_type,
1561 platform_version,
1562 )
1563 .expect("expected to get document");
1564
1565 let person_document1 = json_document_to_document(
1566 "tests/supporting_files/contract/family/person3.json",
1567 Some(random_owner_id1.into()),
1568 document_type,
1569 platform_version,
1570 )
1571 .expect("expected to get document");
1572
1573 let operations = vec![
1574 UpdateOperation(UpdateOperationInfo {
1575 document: &person_document0,
1576 serialized_document: None,
1577 owner_id: Some(random_owner_id0),
1578 storage_flags: None,
1579 }),
1580 UpdateOperation(UpdateOperationInfo {
1581 document: &person_document1,
1582 serialized_document: None,
1583 owner_id: Some(random_owner_id1),
1584 storage_flags: None,
1585 }),
1586 ];
1587
1588 let drive_operations = vec![DocumentOperation(
1589 MultipleDocumentOperationsForSameContractDocumentType {
1590 document_operations: DocumentOperationsForContractDocumentType {
1591 operations,
1592 contract: &contract,
1593 document_type,
1594 },
1595 },
1596 )];
1597
1598 drive
1599 .apply_drive_operations(
1600 drive_operations,
1601 true,
1602 &BlockInfo::default(),
1603 Some(&db_transaction),
1604 platform_version,
1605 None,
1606 )
1607 .expect("expected to be able to update documents");
1608
1609 let query_value = json!({
1610 "where": [
1611 ["age", ">=", 5]
1612 ],
1613 "limit": 100,
1614 "orderBy": [
1615 ["age", "asc"],
1616 ]
1617 });
1618 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1619 .expect("expected to serialize to cbor");
1620
1621 let (docs, _, _) = drive
1622 .query_documents_cbor_from_contract(
1623 &contract,
1624 document_type,
1625 where_cbor.as_slice(),
1626 None,
1627 Some(&db_transaction),
1628 Some(platform_version.protocol_version),
1629 )
1630 .expect("expected to query");
1631 assert_eq!(docs.len(), 2);
1632
1633 let query_value = json!({
1634 "where": [
1635 ["age", "==", 35]
1636 ],
1637 "limit": 100,
1638 "orderBy": [
1639 ["age", "asc"],
1640 ]
1641 });
1642 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1643 .expect("expected to serialize to cbor");
1644
1645 let (docs, _, _) = drive
1646 .query_documents_cbor_from_contract(
1647 &contract,
1648 document_type,
1649 where_cbor.as_slice(),
1650 None,
1651 Some(&db_transaction),
1652 Some(platform_version.protocol_version),
1653 )
1654 .expect("expected to query");
1655 assert_eq!(docs.len(), 1);
1656
1657 let query_value = json!({
1658 "where": [
1659 ["age", "==", 36]
1660 ],
1661 "limit": 100,
1662 "orderBy": [
1663 ["age", "asc"],
1664 ]
1665 });
1666 let where_cbor = cbor_serializer::serializable_value_to_cbor(&query_value, None)
1667 .expect("expected to serialize to cbor");
1668
1669 let (docs, _, _) = drive
1670 .query_documents_cbor_from_contract(
1671 &contract,
1672 document_type,
1673 where_cbor.as_slice(),
1674 None,
1675 Some(&db_transaction),
1676 Some(platform_version.protocol_version),
1677 )
1678 .expect("expected to query");
1679 assert_eq!(docs.len(), 1);
1680 }
1681
1682 fn add(identity: u8, added_balance: Credits) -> DriveOperation<'static> {
1683 DriveOperation::IdentityOperation(IdentityOperationType::AddToIdentityBalance {
1684 identity_id: [identity; 32],
1685 added_balance,
1686 })
1687 }
1688
1689 fn remove(identity: u8, balance_to_remove: Credits) -> DriveOperation<'static> {
1690 DriveOperation::IdentityOperation(IdentityOperationType::RemoveFromIdentityBalance {
1691 identity_id: [identity; 32],
1692 balance_to_remove,
1693 })
1694 }
1695
1696 fn add_to_pot(pot: ContractFeePot, amount: Credits) -> DriveOperation<'static> {
1697 DriveOperation::ContractFeePotOperation(ContractFeePotOperationType::AddToPot {
1698 contract_id: Identifier::new([9; 32]),
1699 pot,
1700 amount,
1701 })
1702 }
1703
1704 fn deduct_from_pot(pot: ContractFeePot, amount: Credits) -> DriveOperation<'static> {
1705 DriveOperation::ContractFeePotOperation(ContractFeePotOperationType::DeductFromPot {
1706 contract_id: Identifier::new([9; 32]),
1707 pot,
1708 amount,
1709 })
1710 }
1711
1712 fn merged(operations: Vec<DriveOperation<'static>>) -> String {
1713 format!(
1714 "{:?}",
1715 DriveOperation::merge_balance_writes(operations).expect("expected to merge")
1716 )
1717 }
1718
1719 #[test]
1720 fn should_merge_every_write_of_one_identity_balance_into_one_in_place_of_the_first() {
1721 assert_eq!(
1723 merged(vec![remove(1, 100), add(2, 100), remove(1, 7)]),
1724 format!("{:?}", vec![remove(1, 107), add(2, 100)])
1725 );
1726 assert_eq!(
1728 merged(vec![add(3, 100), remove(3, 7)]),
1729 format!("{:?}", vec![add(3, 93)])
1730 );
1731 }
1732
1733 #[test]
1734 fn should_merge_the_writes_of_one_fee_pot_and_drop_writes_that_change_nothing() {
1735 assert_eq!(
1736 merged(vec![
1737 add_to_pot(ContractFeePot::Moderators, 5),
1738 add_to_pot(ContractFeePot::Owner, 2),
1739 deduct_from_pot(ContractFeePot::Moderators, 8),
1740 add(4, 10),
1741 remove(4, 10),
1742 ]),
1743 format!(
1744 "{:?}",
1745 vec![
1746 deduct_from_pot(ContractFeePot::Moderators, 3),
1747 add_to_pot(ContractFeePot::Owner, 2),
1748 ]
1749 )
1750 );
1751 }
1752
1753 fn fund_vote_poll(add_balance: Credits) -> DriveOperation<'static> {
1754 DriveOperation::PrefundedSpecializedBalanceOperation(
1755 PrefundedSpecializedBalanceOperationType::CreateNewPrefundedBalance {
1756 prefunded_specialized_balance_id: Identifier::new([8; 32]),
1757 add_balance,
1758 },
1759 )
1760 }
1761
1762 fn pay_from_vote_poll(remove_balance: Credits) -> DriveOperation<'static> {
1763 DriveOperation::PrefundedSpecializedBalanceOperation(
1764 PrefundedSpecializedBalanceOperationType::DeductFromPrefundedBalance {
1765 prefunded_specialized_balance_id: Identifier::new([8; 32]),
1766 remove_balance,
1767 },
1768 )
1769 }
1770
1771 #[test]
1772 fn should_merge_the_writes_of_one_prefunded_specialized_balance() {
1773 assert_eq!(
1774 merged(vec![pay_from_vote_poll(3), pay_from_vote_poll(4)]),
1775 format!("{:?}", vec![pay_from_vote_poll(7)])
1776 );
1777 assert_eq!(
1778 merged(vec![fund_vote_poll(10), pay_from_vote_poll(4)]),
1779 format!("{:?}", vec![fund_vote_poll(6)])
1780 );
1781 }
1782
1783 fn token(position: u8) -> Identifier {
1784 Identifier::new([20 + position; 32])
1785 }
1786
1787 fn transfer(from: u8, to: u8) -> DriveOperation<'static> {
1788 DriveOperation::TokenOperation(TokenOperationType::TokenTransfer {
1789 token_id: token(0),
1790 sender_id: Identifier::new([from; 32]),
1791 recipient_id: Identifier::new([to; 32]),
1792 amount: 5,
1793 })
1794 }
1795
1796 fn burn(token_position: u8, holder: u8) -> DriveOperation<'static> {
1797 DriveOperation::TokenOperation(TokenOperationType::TokenBurn {
1798 token_id: token(token_position),
1799 identity_balance_holder_id: Identifier::new([holder; 32]),
1800 burn_amount: 5,
1801 })
1802 }
1803
1804 fn mint(token_position: u8, holder: u8) -> DriveOperation<'static> {
1805 DriveOperation::TokenOperation(TokenOperationType::TokenMint {
1806 token_id: token(token_position),
1807 identity_balance_holder_id: Identifier::new([holder; 32]),
1808 mint_amount: 5,
1809 allow_first_mint: false,
1810 allow_saturation: false,
1811 })
1812 }
1813
1814 #[test]
1815 fn should_refuse_a_batch_that_writes_one_token_balance_or_supply_twice() {
1816 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1818 transfer(1, 2),
1819 burn(0, 1)
1820 ])
1821 .is_err());
1822 assert!(
1824 DriveOperation::refuse_repeated_token_balance_writes(&[mint(0, 1), burn(0, 2)])
1825 .is_err()
1826 );
1827 }
1828
1829 #[test]
1830 fn should_admit_token_writes_that_each_touch_their_own_keys() {
1831 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1832 transfer(1, 2),
1833 mint(1, 1),
1834 burn(2, 2),
1835 add(1, 10),
1836 ])
1837 .is_ok());
1838 }
1839
1840 #[test]
1841 fn should_keep_a_batch_that_writes_every_key_once_as_it_is() {
1842 let operations = vec![
1843 remove(1, 100),
1844 add(2, 100),
1845 add_to_pot(ContractFeePot::Owner, 2),
1846 ];
1847 assert_eq!(merged(operations.clone()), format!("{operations:?}"));
1848 }
1849
1850 fn note(tag: u8) -> ShieldedActionNote {
1851 ShieldedActionNote {
1852 nullifier: [tag; 32],
1853 cmx: [tag.wrapping_add(1); 32],
1854 cv_net: [tag.wrapping_add(2); 32],
1855 encrypted_note: vec![tag; 216],
1856 }
1857 }
1858
1859 fn shield(token_position: u8, holder: u8, note_tag: u8) -> DriveOperation<'static> {
1860 DriveOperation::TokenOperation(TokenOperationType::TokenShield {
1861 token_id: token(token_position),
1862 identity_id: Identifier::new([holder; 32]),
1863 amount: 5,
1864 notes: vec![note(note_tag)],
1865 })
1866 }
1867
1868 fn unshield(token_position: u8, recipient: u8, note_tag: u8) -> DriveOperation<'static> {
1869 DriveOperation::TokenOperation(TokenOperationType::TokenUnshield {
1870 token_id: token(token_position),
1871 recipient_id: Identifier::new([recipient; 32]),
1872 amount: 5,
1873 nullifiers: vec![[note_tag; 32]],
1874 notes: vec![note(note_tag)],
1875 })
1876 }
1877
1878 fn shielded_transfer(token_position: u8, note_tag: u8) -> DriveOperation<'static> {
1879 DriveOperation::TokenOperation(TokenOperationType::TokenShieldedTransfer {
1880 token_id: token(token_position),
1881 nullifiers: vec![[note_tag; 32]],
1882 notes: vec![note(note_tag)],
1883 })
1884 }
1885
1886 fn mint_to_pool(token_position: u8, note_tag: u8) -> DriveOperation<'static> {
1887 DriveOperation::TokenOperation(TokenOperationType::TokenMintToPool {
1888 token_id: token(token_position),
1889 amount: 5,
1890 allow_first_mint: false,
1891 notes: vec![note(note_tag)],
1892 })
1893 }
1894
1895 fn burn_from_pool(token_position: u8, note_tag: u8) -> DriveOperation<'static> {
1896 DriveOperation::TokenOperation(TokenOperationType::TokenBurnFromPool {
1897 token_id: token(token_position),
1898 amount: 5,
1899 nullifiers: vec![[note_tag; 32]],
1900 notes: vec![note(note_tag)],
1901 })
1902 }
1903
1904 #[test]
1907 fn should_refuse_a_batch_that_writes_one_token_shielded_pool_total_twice() {
1908 for (pairing, first, second) in [
1909 (
1910 "a shield and an unshield",
1911 shield(0, 1, 1),
1912 unshield(0, 2, 2),
1913 ),
1914 (
1915 "a shield and a mint into the pool",
1916 shield(0, 1, 1),
1917 mint_to_pool(0, 2),
1918 ),
1919 (
1920 "an unshield and a burn from the pool",
1921 unshield(0, 1, 1),
1922 burn_from_pool(0, 2),
1923 ),
1924 (
1925 "a mint into and a burn from the pool",
1926 mint_to_pool(0, 1),
1927 burn_from_pool(0, 2),
1928 ),
1929 (
1930 "two mints into the pool",
1931 mint_to_pool(0, 1),
1932 mint_to_pool(0, 2),
1933 ),
1934 ] {
1935 assert!(
1936 DriveOperation::refuse_repeated_token_balance_writes(&[first, second]).is_err(),
1937 "{pairing} both write one pool total"
1938 );
1939 }
1940 }
1941
1942 #[test]
1945 fn should_refuse_a_batch_that_writes_one_shielding_identity_balance_twice() {
1946 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1947 shield(0, 1, 1),
1948 burn(0, 1)
1949 ])
1950 .is_err());
1951 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1952 unshield(0, 1, 1),
1953 mint(0, 1)
1954 ])
1955 .is_err());
1956 }
1957
1958 #[test]
1961 fn should_refuse_a_batch_that_writes_one_pool_minted_token_supply_twice() {
1962 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1963 mint_to_pool(0, 1),
1964 mint(0, 1)
1965 ])
1966 .is_err());
1967 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1968 burn_from_pool(0, 1),
1969 burn(0, 1)
1970 ])
1971 .is_err());
1972 }
1973
1974 #[test]
1975 fn should_admit_pool_operations_that_each_touch_their_own_keys() {
1976 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1978 shield(0, 1, 1),
1979 mint_to_pool(1, 2)
1980 ])
1981 .is_ok());
1982 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1985 shield(0, 1, 1),
1986 mint(0, 2)
1987 ])
1988 .is_ok());
1989 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1990 unshield(0, 1, 1),
1991 burn(0, 2)
1992 ])
1993 .is_ok());
1994 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
1996 mint_to_pool(0, 1),
1997 transfer(1, 2)
1998 ])
1999 .is_ok());
2000 assert!(DriveOperation::refuse_repeated_token_balance_writes(&[
2003 shielded_transfer(0, 1),
2004 shield(0, 1, 2),
2005 mint(0, 2),
2006 ])
2007 .is_ok());
2008 }
2009
2010 fn setup_drive_with_token_pool(balance: TokenAmount) -> (Drive, [u8; 32], [u8; 32], [u8; 32]) {
2013 let drive = setup_drive_with_initial_state_structure(None);
2014 let platform_version = PlatformVersion::latest();
2015 let block_info = BlockInfo::default();
2016 let token_id = [7u8; 32];
2017
2018 let mut holders = [[0u8; 32]; 2];
2019 for (seed, holder) in holders.iter_mut().enumerate() {
2020 let identity = Identity::random_identity(3, Some(seed as u64), platform_version)
2021 .expect("random identity");
2022 *holder = identity.id().to_buffer();
2023 drive
2024 .add_new_identity(identity, false, &block_info, true, None, platform_version)
2025 .expect("insert identity");
2026 }
2027
2028 drive
2029 .create_token_trees(
2030 Identifier::from([5u8; 32]),
2031 0,
2032 token_id,
2033 false,
2034 false,
2035 &block_info,
2036 true,
2037 None,
2038 platform_version,
2039 )
2040 .expect("create token trees");
2041 drive
2043 .add_to_token_total_supply(
2044 token_id,
2045 balance * 2,
2046 true,
2047 false,
2048 true,
2049 &block_info,
2050 None,
2051 platform_version,
2052 )
2053 .expect("add to total supply");
2054 for holder in holders {
2055 drive
2056 .add_to_identity_token_balance(
2057 token_id,
2058 holder,
2059 balance,
2060 &block_info,
2061 true,
2062 None,
2063 platform_version,
2064 None,
2065 )
2066 .expect("add token balance");
2067 }
2068
2069 let operations = drive
2070 .create_token_shielded_pool_trees_operations(
2071 token_id,
2072 false,
2073 &mut None,
2074 None,
2075 platform_version,
2076 )
2077 .expect("pool tree operations");
2078 drive
2079 .apply_batch_low_level_drive_operations(
2080 None,
2081 None,
2082 operations,
2083 &mut vec![],
2084 &platform_version.drive,
2085 )
2086 .expect("create pool trees");
2087
2088 (drive, token_id, holders[0], holders[1])
2089 }
2090
2091 #[test]
2095 fn should_refuse_a_batch_that_shields_into_one_token_pool_twice() {
2096 let (drive, token_id, first_holder, second_holder) = setup_drive_with_token_pool(1_000);
2097 let platform_version = PlatformVersion::latest();
2098
2099 let result = drive.apply_drive_operations(
2100 vec![
2101 DriveOperation::TokenOperation(TokenOperationType::TokenShield {
2102 token_id: Identifier::from(token_id),
2103 identity_id: Identifier::from(first_holder),
2104 amount: 400,
2105 notes: vec![note(1)],
2106 }),
2107 DriveOperation::TokenOperation(TokenOperationType::TokenShield {
2108 token_id: Identifier::from(token_id),
2109 identity_id: Identifier::from(second_holder),
2110 amount: 300,
2111 notes: vec![note(2)],
2112 }),
2113 ],
2114 true,
2115 &BlockInfo::default(),
2116 None,
2117 platform_version,
2118 None,
2119 );
2120
2121 assert!(
2122 matches!(
2123 &result,
2124 Err(Error::Drive(DriveError::CorruptedCodeExecution(message)))
2125 if message.contains("more than once")
2126 ),
2127 "a batch shielding into one pool twice must be refused, got {result:?}"
2128 );
2129 }
2130
2131 #[test]
2135 fn should_refuse_a_batch_that_mints_one_token_into_its_pool_and_to_a_holder() {
2136 let (drive, token_id, holder, _) = setup_drive_with_token_pool(1_000);
2137 let platform_version = PlatformVersion::latest();
2138
2139 let result = drive.apply_drive_operations(
2140 vec![
2141 DriveOperation::TokenOperation(TokenOperationType::TokenMintToPool {
2142 token_id: Identifier::from(token_id),
2143 amount: 400,
2144 allow_first_mint: false,
2145 notes: vec![note(1)],
2146 }),
2147 DriveOperation::TokenOperation(TokenOperationType::TokenMint {
2148 token_id: Identifier::from(token_id),
2149 identity_balance_holder_id: Identifier::from(holder),
2150 mint_amount: 300,
2151 allow_first_mint: false,
2152 allow_saturation: false,
2153 }),
2154 ],
2155 true,
2156 &BlockInfo::default(),
2157 None,
2158 platform_version,
2159 None,
2160 );
2161
2162 assert!(
2163 matches!(
2164 &result,
2165 Err(Error::Drive(DriveError::CorruptedCodeExecution(message)))
2166 if message.contains("more than once")
2167 ),
2168 "a batch minting one token into its pool and to a holder must be refused, got {result:?}"
2169 );
2170 }
2171}