Skip to main content

drive_abci/query/
mod.rs

1mod address_funds;
2mod contract_group_queries;
3mod contract_moderation_queries;
4mod data_contract_based_queries;
5mod document_history;
6mod document_query;
7mod group_queries;
8mod identity_based_queries;
9mod prefunded_specialized_balances;
10mod proofs;
11mod response_metadata;
12mod service;
13mod shielded;
14mod system;
15mod token_queries;
16mod validator_queries;
17mod voting;
18
19use crate::error::query::QueryError;
20
21use dpp::validation::ValidationResult;
22
23pub use service::QueryService;
24
25/// A query validation result
26pub type QueryValidationResult<TData> = ValidationResult<TData, QueryError>;
27
28#[cfg(test)]
29pub(crate) mod tests {
30    use crate::error::query::QueryError;
31    use crate::error::Error;
32    use crate::platform_types::platform::Platform;
33    use crate::platform_types::platform_state::PlatformState;
34    use crate::platform_types::platform_state::PlatformStateV0Methods;
35    use crate::query::service::query_result_into_response;
36    use crate::query::QueryValidationResult;
37    use crate::rpc::core::MockCoreRPCLike;
38    use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder};
39    use dapi_grpc::tonic::{Code, Status};
40    use dpp::block::block_info::BlockInfo;
41    use dpp::data_contract::{DataContract, TokenConfiguration};
42    use drive::config::DEFAULT_QUERY_LIMIT;
43    use drive_proof_verifier::{ContextProvider, ContextProviderError};
44
45    use crate::config::PlatformConfig;
46    use dpp::dashcore::Network;
47    use dpp::data_contract::accessors::v0::{DataContractV0Getters, DataContractV0Setters};
48    use dpp::data_contract::config::moderation::{
49        ContractDocumentRemoval, ContractModerationConfig, ContractModerationReason,
50        ContractModerators,
51    };
52    use dpp::data_contract::document_type::DocumentTypeRef;
53    use dpp::data_contract::schema::DataContractSchemaMethodsV0;
54    use dpp::document::Document;
55    use dpp::identifier::Identifier;
56    use dpp::platform_value::Value;
57    use dpp::prelude::{CoreBlockHeight, TimestampMillis};
58    use drive::util::batch::DriveOperation::{
59        ContractModerationOperation, DataContractOperation, DocumentOperation,
60    };
61    use drive::util::batch::{
62        ContractModerationOperationType, DataContractOperationType, DocumentOperationType,
63    };
64    use drive::util::object_size_info::{
65        DataContractInfo, DocumentInfo, DocumentTypeInfo, OwnedDocumentInfo,
66    };
67    use drive::util::storage_flags::StorageFlags;
68    use platform_version::version::{PlatformVersion, ProtocolVersion};
69    use std::borrow::Cow;
70    use std::collections::BTreeMap;
71    use std::sync::Arc;
72
73    pub fn setup_platform<'a>(
74        with_genesis_state: Option<(TimestampMillis, CoreBlockHeight)>,
75        network: Network,
76        initial_protocol_version: Option<ProtocolVersion>,
77    ) -> (
78        TempPlatform<MockCoreRPCLike>,
79        Arc<PlatformState>,
80        &'a PlatformVersion,
81    ) {
82        setup_platform_with_config(
83            with_genesis_state,
84            PlatformConfig::default_for_network(network),
85            initial_protocol_version,
86        )
87    }
88
89    /// [`setup_platform`] on testnet for a node whose operator set Drive's default page, the
90    /// limit a request naming none is answered with, to `default_query_limit`
91    pub fn setup_platform_with_default_query_limit<'a>(
92        with_genesis_state: Option<(TimestampMillis, CoreBlockHeight)>,
93        default_query_limit: u16,
94    ) -> (
95        TempPlatform<MockCoreRPCLike>,
96        Arc<PlatformState>,
97        &'a PlatformVersion,
98    ) {
99        let mut config = PlatformConfig::default_for_network(Network::Testnet);
100        config.drive.default_query_limit = default_query_limit;
101        setup_platform_with_config(with_genesis_state, config, None)
102    }
103
104    fn setup_platform_with_config<'a>(
105        with_genesis_state: Option<(TimestampMillis, CoreBlockHeight)>,
106        config: PlatformConfig,
107        initial_protocol_version: Option<ProtocolVersion>,
108    ) -> (
109        TempPlatform<MockCoreRPCLike>,
110        Arc<PlatformState>,
111        &'a PlatformVersion,
112    ) {
113        let mut platform_builder = TestPlatformBuilder::new().with_config(config);
114
115        if let Some(initial_protocol_version) = initial_protocol_version {
116            platform_builder =
117                platform_builder.with_initial_protocol_version(initial_protocol_version);
118        }
119
120        let platform = platform_builder.build_with_mock_rpc();
121        let platform = if let Some((timestamp, activation_core_block_height)) = with_genesis_state {
122            platform.set_genesis_state_with_activation_info(timestamp, activation_core_block_height)
123        } else {
124            platform.set_initial_state_structure()
125        };
126
127        // We can't return a reference to Arc (`load` method) so we clone Arc (`load_full`).
128        // This is a bit slower, but we don't care since we are in test environment
129        let platform_state = platform.platform.state.load_full();
130
131        let platform_version = platform_state.current_platform_version().unwrap();
132
133        (platform, platform_state, platform_version)
134    }
135
136    pub fn store_data_contract(
137        platform: &Platform<MockCoreRPCLike>,
138        data_contract: &DataContract,
139        platform_version: &PlatformVersion,
140    ) {
141        let operation = DataContractOperation(DataContractOperationType::ApplyContract {
142            contract: Cow::Owned(data_contract.to_owned()),
143            storage_flags: None,
144        });
145
146        let block_info = BlockInfo::genesis();
147
148        platform
149            .drive
150            .apply_drive_operations(
151                vec![operation],
152                true,
153                &block_info,
154                None,
155                platform_version,
156                None,
157            )
158            .expect("expected to apply drive operations");
159    }
160
161    /// `contract` with its `post` type left to the contract owner's moderators alone, on the
162    /// record (`canBeDeleted: false` beside `moderatorAbilities.delete`, whose removals keep
163    /// a record by default), and every reference to it a `refersTo: moderatedDocument` one.
164    /// `preallocated` indexes, which need a `permanentDocument` edge, are dropped.
165    pub fn with_moderated_posts(
166        mut contract: DataContract,
167        platform_version: &PlatformVersion,
168    ) -> DataContract {
169        contract.set_config(contract.config().clone().with_moderation(Some(
170            ContractModerationConfig {
171                banlist: false,
172                suspensions: false,
173                warnings: false,
174                moderators: ContractModerators::ContractOwner,
175            },
176        )));
177        let mut schemas = BTreeMap::new();
178        for (name, schema) in contract.document_schemas() {
179            let mut json: serde_json::Value = schema.clone().try_into().expect("a JSON schema");
180            if name == "post" {
181                json["canBeDeleted"] = false.into();
182                json["moderatorAbilities"] = serde_json::json!({ "delete": true });
183            }
184            if let Some(properties) = json
185                .get_mut("properties")
186                .and_then(|properties| properties.as_object_mut())
187            {
188                for property in properties.values_mut() {
189                    if property["refersTo"]["documentType"] == "post" {
190                        property["refersTo"]["type"] = "moderatedDocument".into();
191                    }
192                }
193            }
194            if let Some(indices) = json
195                .get_mut("indices")
196                .and_then(|indices| indices.as_array_mut())
197            {
198                for index in indices {
199                    if let Some(index) = index.as_object_mut() {
200                        index.remove("preallocated");
201                    }
202                }
203            }
204            schemas.insert(name, Value::from(json));
205        }
206        let defs = contract.schema_defs().cloned();
207        contract
208            .set_document_schemas(schemas, defs, true, &mut vec![], platform_version)
209            .expect("expected the moderated variant to parse");
210        contract
211    }
212
213    /// The record a moderator's removal of the post `id`, owned by `owner`, leaves.
214    pub fn removal_of(id: [u8; 32], owner: [u8; 32]) -> ContractDocumentRemoval {
215        ContractDocumentRemoval {
216            document_owner_id: Identifier::from(owner),
217            moderator_id: Identifier::from([0x77; 32]),
218            reason: ContractModerationReason::from_text("spam"),
219            removed_at: 1_000 + id[0] as u64,
220            document_hash: id,
221            restoration: None,
222            kept_fields: Default::default(),
223        }
224    }
225
226    /// A moderator's removal of the post `id`, as the moderation transition writes it: the
227    /// post deleted past `canBeDeleted: false`, and its removal record.
228    pub fn remove_post_by_moderator(
229        platform: &Platform<MockCoreRPCLike>,
230        data_contract: &DataContract,
231        id: [u8; 32],
232        removal: ContractDocumentRemoval,
233        platform_version: &PlatformVersion,
234    ) {
235        let moderator_id = removal.moderator_id;
236        let operations = vec![
237            DocumentOperation(DocumentOperationType::ForceDeleteDocument {
238                document_id: Identifier::from(id),
239                contract_info: DataContractInfo::BorrowedDataContract(data_contract),
240                document_type_info: DocumentTypeInfo::DocumentTypeName("post".to_string()),
241            }),
242            ContractModerationOperation(ContractModerationOperationType::AddDocumentRemoval {
243                contract_id: data_contract.id(),
244                document_type_name: "post".to_string(),
245                document_id: Identifier::from(id),
246                removal: Box::new(removal),
247                replaced_record_size: None,
248                estimated_kept_fields_size: 0,
249                moderator_id,
250            }),
251            ContractModerationOperation(ContractModerationOperationType::ForfeitStorageRefunds),
252        ];
253        platform
254            .drive
255            .apply_drive_operations(
256                operations,
257                true,
258                &BlockInfo::genesis(),
259                None,
260                platform_version,
261                None,
262            )
263            .expect("expected to remove the post");
264    }
265
266    pub fn store_document(
267        platform: &Platform<MockCoreRPCLike>,
268        data_contract: &DataContract,
269        document_type: DocumentTypeRef,
270        document: &Document,
271        platform_version: &PlatformVersion,
272    ) {
273        let storage_flags = Some(Cow::Owned(StorageFlags::SingleEpoch(0)));
274
275        let operation = DocumentOperation(DocumentOperationType::AddDocument {
276            owned_document_info: OwnedDocumentInfo {
277                document_info: DocumentInfo::DocumentRefInfo((document, storage_flags)),
278                owner_id: None,
279            },
280            contract_info: DataContractInfo::BorrowedDataContract(data_contract),
281            document_type_info: DocumentTypeInfo::DocumentTypeRef(document_type),
282            override_document: false,
283        });
284
285        let block_info = BlockInfo::genesis();
286
287        platform
288            .drive
289            .apply_drive_operations(
290                vec![operation],
291                true,
292                &block_info,
293                None,
294                platform_version,
295                None,
296            )
297            .expect("expected to apply drive operations");
298    }
299
300    /// Asserts the service refuses `result` with INVALID_ARGUMENT, the code that tells a client
301    /// the request itself is wrong, so it neither tries another node nor stops using this one.
302    pub fn assert_invalid_argument_status<TData: Clone>(
303        result: Result<QueryValidationResult<TData>, Error>,
304    ) -> Status {
305        let status = query_result_into_response(result)
306            .err()
307            .expect("expected the request to be refused");
308        assert_eq!(status.code(), Code::InvalidArgument, "{}", status.message());
309        status
310    }
311
312    pub fn assert_invalid_identifier<TData: Clone>(
313        validation_result: QueryValidationResult<TData>,
314    ) {
315        assert!(matches!(
316            validation_result.errors.as_slice(),
317            [QueryError::InvalidArgument(msg)] if msg.contains("id must be a valid identifier (32 bytes long)")
318        ));
319    }
320
321    /// Serves no contract and no quorum key, so the `FromProof` of a query that needs neither
322    /// checks the Drive part of a proof and then stops at the quorum key the signature check
323    /// asks for
324    pub struct NoQuorumKeysContextProvider;
325
326    impl ContextProvider for NoQuorumKeysContextProvider {
327        fn get_data_contract(
328            &self,
329            _id: &Identifier,
330            _platform_version: &PlatformVersion,
331        ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
332            Ok(None)
333        }
334
335        fn get_token_configuration(
336            &self,
337            _token_id: &Identifier,
338        ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
339            Ok(None)
340        }
341
342        fn get_quorum_public_key(
343            &self,
344            _quorum_type: u32,
345            _quorum_hash: [u8; 32],
346            _core_chain_locked_height: u32,
347        ) -> Result<[u8; 48], ContextProviderError> {
348            Err(ContextProviderError::Config(
349                "no quorum keys in this test".to_string(),
350            ))
351        }
352
353        fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
354            Ok(1)
355        }
356    }
357
358    /// The nodes a proved request naming no limit is checked against, as (the node's default
359    /// page, results stored): a node left at the default with one result more than its page,
360    /// and a node whose operator set a larger default with every result inside it
361    pub const NO_LIMIT_PROOF_CASES: [(u16, u16); 2] = [
362        (DEFAULT_QUERY_LIMIT, DEFAULT_QUERY_LIMIT + 1),
363        (
364            2 * DEFAULT_QUERY_LIMIT,
365            DEFAULT_QUERY_LIMIT + DEFAULT_QUERY_LIMIT / 2,
366        ),
367    ];
368
369    /// `FromProof` checks the GroveDB proof before the Tenderdash signature, so with
370    /// [`NoQuorumKeysContextProvider`] `QuorumKeyUnavailable` means the Drive part verified
371    pub fn assert_drive_part_of_proof_verified(error: drive_proof_verifier::Error) {
372        assert!(
373            matches!(
374                error,
375                drive_proof_verifier::Error::QuorumKeyUnavailable { .. }
376            ),
377            "expected the Drive part of the proof to verify and the signature check to stop at \
378             the quorum key, got {error:?}"
379        );
380    }
381
382    /// Set up a platform with token state for testing token queries.
383    ///
384    /// Creates 3 identities and a data contract with 3 tokens.
385    /// Token 0: minted 100 to identity 1, 100 to identity 2. Identity 2 frozen on token 0.
386    /// Token 1: minted 200 to identity 1, 100 to identity 3. Token 1 is paused.
387    /// Token 2: minted 300 to identity 1, 200 to identity 2. Has pre-programmed distributions.
388    ///
389    /// Returns (platform, state, version, contract_id, [token_id_0, token_id_1, token_id_2],
390    ///          [identity_id_1, identity_id_2, identity_id_3])
391    #[allow(clippy::type_complexity)]
392    pub fn setup_platform_with_token_state<'a>() -> (
393        TempPlatform<MockCoreRPCLike>,
394        Arc<PlatformState>,
395        &'a PlatformVersion,
396        [u8; 32],
397        [[u8; 32]; 3],
398        [[u8; 32]; 3],
399    ) {
400        use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0;
401        use dpp::data_contract::associated_token::token_configuration_convention::v0::TokenConfigurationConventionV0;
402        use dpp::data_contract::associated_token::token_distribution_rules::v0::TokenDistributionRulesV0;
403        use dpp::data_contract::associated_token::token_distribution_rules::TokenDistributionRules;
404        use dpp::data_contract::associated_token::token_keeps_history_rules::v0::TokenKeepsHistoryRulesV0;
405        use dpp::data_contract::associated_token::token_marketplace_rules::v0::TokenMarketplaceRulesV0;
406        use dpp::data_contract::associated_token::token_perpetual_distribution::distribution_function::DistributionFunction;
407        use dpp::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::TokenDistributionRecipient;
408        use dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_type::RewardDistributionType;
409        use dpp::data_contract::associated_token::token_perpetual_distribution::v0::TokenPerpetualDistributionV0;
410        use dpp::data_contract::associated_token::token_perpetual_distribution::TokenPerpetualDistribution;
411        use dpp::data_contract::associated_token::token_pre_programmed_distribution::v0::TokenPreProgrammedDistributionV0;
412        use dpp::data_contract::associated_token::token_pre_programmed_distribution::TokenPreProgrammedDistribution;
413        use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers;
414        use dpp::data_contract::change_control_rules::v0::ChangeControlRulesV0;
415        use dpp::data_contract::config::DataContractConfig;
416        use dpp::data_contract::group::v0::GroupV0;
417        use dpp::data_contract::group::Group;
418        use dpp::data_contract::v1::DataContractV1;
419        use dpp::data_contract::{TokenConfiguration, INITIAL_DATA_CONTRACT_VERSION};
420        use dpp::identifier::Identifier;
421        use dpp::identity::accessors::IdentitySettersV0;
422        use dpp::identity::Identity;
423        use dpp::prelude::IdentityPublicKey;
424        use dpp::tokens::calculate_token_id;
425        use dpp::tokens::status::v0::TokenStatusV0;
426        use dpp::tokens::status::TokenStatus;
427        use dpp::tokens::token_pricing_schedule::TokenPricingSchedule;
428        use rand::rngs::StdRng;
429        use rand::SeedableRng;
430        use std::collections::BTreeMap;
431        use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
432
433        let (platform, state, version) = setup_platform(Some((1, 1)), Network::Testnet, None);
434
435        let identity_id_1 = [1u8; 32];
436        let identity_id_2 = [2u8; 32];
437        let identity_id_3 = [3u8; 32];
438        let contract_id = [3u8; 32];
439
440        let token_id_0 = calculate_token_id(&contract_id, 0);
441        let token_id_1 = calculate_token_id(&contract_id, 1);
442        let token_id_2 = calculate_token_id(&contract_id, 2);
443
444        // Register identities
445        let credits_per_identity: u64 = 10_000_000_000_000;
446        let total_credits = credits_per_identity * 3;
447        platform
448            .platform
449            .drive
450            .add_to_system_credits(total_credits, None, version)
451            .expect("expected to add system credits");
452
453        let mut rng = StdRng::seed_from_u64(0u64);
454        let non_unique_key = IdentityPublicKey::random_voting_key_with_rng(11, &mut rng, version)
455            .expect("expected to create voting key");
456
457        for id_bytes in [identity_id_1, identity_id_2, identity_id_3] {
458            let id = Identifier::new(id_bytes);
459            let mut identity =
460                Identity::create_basic_identity(id, version).expect("expected to create identity");
461            identity.set_balance(credits_per_identity);
462
463            let seed = id_bytes[0];
464            let mut rng = StdRng::seed_from_u64(seed as u64);
465            let mut keys =
466                IdentityPublicKey::main_keys_with_random_authentication_keys_with_private_keys_with_rng(
467                    3, &mut rng, version,
468                )
469                .expect("expected to create keys");
470
471            let transfer_key =
472                IdentityPublicKey::random_masternode_transfer_key_with_rng(3, &mut rng, version)
473                    .expect("expected to create transfer key");
474            keys.push(transfer_key);
475            keys.push(non_unique_key.clone());
476
477            identity.set_public_keys(keys.into_iter().map(|(key, _)| (key.id(), key)).collect());
478
479            platform
480                .platform
481                .drive
482                .add_new_identity(identity, false, &BlockInfo::genesis(), true, None, version)
483                .expect("expected to add identity");
484        }
485
486        // Create data contract with tokens
487        let groups = [
488            (
489                0,
490                Group::V0(GroupV0 {
491                    members: [
492                        (Identifier::new(identity_id_1), 1),
493                        (Identifier::new(identity_id_2), 1),
494                    ]
495                    .into(),
496                    required_power: 1,
497                }),
498            ),
499            (
500                1,
501                Group::V0(GroupV0 {
502                    members: [
503                        (Identifier::new(identity_id_1), 1),
504                        (Identifier::new(identity_id_2), 1),
505                        (Identifier::new(identity_id_3), 1),
506                    ]
507                    .into(),
508                    required_power: 3,
509                }),
510            ),
511            (
512                2,
513                Group::V0(GroupV0 {
514                    members: [
515                        (Identifier::new(identity_id_1), 1),
516                        (Identifier::new(identity_id_3), 1),
517                    ]
518                    .into(),
519                    required_power: 1,
520                }),
521            ),
522        ]
523        .into();
524
525        let token_configuration = TokenConfiguration::V0(TokenConfigurationV0 {
526            conventions: TokenConfigurationConventionV0 {
527                localizations: Default::default(),
528                decimals: 8,
529            }
530            .into(),
531            conventions_change_rules: ChangeControlRulesV0::default().into(),
532            base_supply: 100000,
533            max_supply: None,
534            keeps_history: TokenKeepsHistoryRulesV0::default().into(),
535            start_as_paused: false,
536            allow_transfer_to_frozen_balance: true,
537            max_supply_change_rules: ChangeControlRulesV0 {
538                authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
539                admin_action_takers: Default::default(),
540                changing_authorized_action_takers_to_no_one_allowed: false,
541                changing_admin_action_takers_to_no_one_allowed: false,
542                self_changing_admin_action_takers_allowed: false,
543            }
544            .into(),
545            distribution_rules: TokenDistributionRulesV0 {
546                perpetual_distribution: Some(TokenPerpetualDistribution::V0(
547                    TokenPerpetualDistributionV0 {
548                        distribution_type: RewardDistributionType::BlockBasedDistribution {
549                            interval: 10,
550                            function: DistributionFunction::FixedAmount { amount: 100 },
551                        },
552                        distribution_recipient: TokenDistributionRecipient::ContractOwner,
553                    },
554                )),
555                perpetual_distribution_rules: ChangeControlRulesV0::default().into(),
556                pre_programmed_distribution: None,
557                new_tokens_destination_identity: None,
558                new_tokens_destination_identity_rules: ChangeControlRulesV0::default().into(),
559                minting_allow_choosing_destination: true,
560                minting_allow_choosing_destination_rules: ChangeControlRulesV0::default().into(),
561                change_direct_purchase_pricing_rules: ChangeControlRulesV0 {
562                    authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
563                    admin_action_takers: Default::default(),
564                    changing_authorized_action_takers_to_no_one_allowed: false,
565                    changing_admin_action_takers_to_no_one_allowed: false,
566                    self_changing_admin_action_takers_allowed: false,
567                }
568                .into(),
569            }
570            .into(),
571            marketplace_rules: TokenMarketplaceRulesV0 {
572                trade_mode: Default::default(),
573                trade_mode_change_rules: ChangeControlRulesV0::default().into(),
574            }
575            .into(),
576            manual_minting_rules: ChangeControlRulesV0 {
577                authorized_to_make_change: AuthorizedActionTakers::Group(0),
578                admin_action_takers: Default::default(),
579                changing_authorized_action_takers_to_no_one_allowed: false,
580                changing_admin_action_takers_to_no_one_allowed: false,
581                self_changing_admin_action_takers_allowed: false,
582            }
583            .into(),
584            manual_burning_rules: ChangeControlRulesV0 {
585                authorized_to_make_change: AuthorizedActionTakers::Group(2),
586                admin_action_takers: Default::default(),
587                changing_authorized_action_takers_to_no_one_allowed: false,
588                changing_admin_action_takers_to_no_one_allowed: false,
589                self_changing_admin_action_takers_allowed: false,
590            }
591            .into(),
592            freeze_rules: ChangeControlRulesV0 {
593                authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
594                admin_action_takers: Default::default(),
595                changing_authorized_action_takers_to_no_one_allowed: false,
596                changing_admin_action_takers_to_no_one_allowed: false,
597                self_changing_admin_action_takers_allowed: false,
598            }
599            .into(),
600            unfreeze_rules: ChangeControlRulesV0 {
601                authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
602                admin_action_takers: Default::default(),
603                changing_authorized_action_takers_to_no_one_allowed: false,
604                changing_admin_action_takers_to_no_one_allowed: false,
605                self_changing_admin_action_takers_allowed: false,
606            }
607            .into(),
608            destroy_frozen_funds_rules: ChangeControlRulesV0 {
609                authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
610                admin_action_takers: Default::default(),
611                changing_authorized_action_takers_to_no_one_allowed: false,
612                changing_admin_action_takers_to_no_one_allowed: false,
613                self_changing_admin_action_takers_allowed: false,
614            }
615            .into(),
616            emergency_action_rules: ChangeControlRulesV0 {
617                authorized_to_make_change: AuthorizedActionTakers::ContractOwner,
618                admin_action_takers: Default::default(),
619                changing_authorized_action_takers_to_no_one_allowed: false,
620                changing_admin_action_takers_to_no_one_allowed: false,
621                self_changing_admin_action_takers_allowed: false,
622            }
623            .into(),
624            main_control_group: None,
625            main_control_group_can_be_modified: Default::default(),
626            description: Some("Some token description".to_string()),
627        });
628
629        // Token 2 gets pre-programmed distributions
630        let mut token_configuration_2 = token_configuration.clone();
631        if let TokenConfiguration::V0(ref mut cfg) = token_configuration_2 {
632            if let TokenDistributionRules::V0(ref mut rules) = cfg.distribution_rules {
633                rules.pre_programmed_distribution = Some(TokenPreProgrammedDistribution::V0(
634                    TokenPreProgrammedDistributionV0 {
635                        distributions: BTreeMap::from([
636                            (
637                                1000,
638                                BTreeMap::from([
639                                    (Identifier::new(identity_id_1), 500),
640                                    (Identifier::new(identity_id_2), 300),
641                                ]),
642                            ),
643                            (
644                                5000,
645                                BTreeMap::from([(Identifier::new(identity_id_1), 1000)]),
646                            ),
647                            (
648                                10000,
649                                BTreeMap::from([
650                                    (Identifier::new(identity_id_2), 750),
651                                    (Identifier::new(identity_id_3), 250),
652                                ]),
653                            ),
654                        ]),
655                    },
656                ));
657            }
658        }
659
660        let tokens = [
661            (0, token_configuration.clone()),
662            (1, token_configuration),
663            (2, token_configuration_2),
664        ]
665        .into();
666
667        let data_contract = DataContract::V1(DataContractV1 {
668            id: Identifier::new(contract_id),
669            config: DataContractConfig::default_for_version(version)
670                .expect("expected contract config"),
671            version: INITIAL_DATA_CONTRACT_VERSION,
672            owner_id: Identifier::new(identity_id_1),
673            document_types: Default::default(),
674            schema_defs: Default::default(),
675            created_at: None,
676            updated_at: None,
677            created_at_block_height: None,
678            updated_at_block_height: None,
679            created_at_epoch: None,
680            updated_at_epoch: None,
681            groups,
682            tokens,
683            keywords: vec!["cat".into(), "white".into()],
684            description: Some("Some contract description".to_string()),
685        });
686
687        let block_info = BlockInfo::genesis();
688
689        platform
690            .platform
691            .drive
692            .apply_contract(&data_contract, block_info, true, None, None, version)
693            .expect("expected to apply contract");
694
695        // Mint tokens
696        // Token 0: 100 to identity_1, 100 to identity_2
697        // Token 1: 200 to identity_1, 100 to identity_3
698        // Token 2: 300 to identity_1, 200 to identity_2
699        for (token_id, id, amount) in [
700            (token_id_0, identity_id_1, 100u64),
701            (token_id_0, identity_id_2, 100),
702            (token_id_1, identity_id_1, 200),
703            (token_id_1, identity_id_3, 100),
704            (token_id_2, identity_id_1, 300),
705            (token_id_2, identity_id_2, 200),
706        ] {
707            platform
708                .platform
709                .drive
710                .token_mint(
711                    token_id,
712                    id,
713                    amount,
714                    false,
715                    false,
716                    &block_info,
717                    true,
718                    None,
719                    version,
720                )
721                .expect("expected to mint tokens");
722        }
723
724        // Freeze identity 2 on token 0
725        platform
726            .platform
727            .drive
728            .token_freeze(
729                Identifier::new(token_id_0),
730                Identifier::new(identity_id_2),
731                &block_info,
732                true,
733                None,
734                version,
735            )
736            .expect("expected to freeze");
737
738        // Pause token 1
739        let status = TokenStatus::V0(TokenStatusV0 { paused: true });
740        platform
741            .platform
742            .drive
743            .token_apply_status(token_id_1, status, &block_info, true, None, version)
744            .expect("expected to apply status");
745
746        // Set direct purchase prices
747        platform
748            .platform
749            .drive
750            .token_set_direct_purchase_price(
751                token_id_1,
752                Some(TokenPricingSchedule::SinglePrice(25)),
753                &block_info,
754                true,
755                None,
756                version,
757            )
758            .expect("expected to set direct purchase price");
759
760        let pricing = TokenPricingSchedule::SetPrices(
761            (0..=900)
762                .step_by(100)
763                .map(|amount| (amount, 1000 - amount))
764                .collect(),
765        );
766        platform
767            .platform
768            .drive
769            .token_set_direct_purchase_price(
770                token_id_2,
771                Some(pricing),
772                &block_info,
773                true,
774                None,
775                version,
776            )
777            .expect("expected to set direct purchase price");
778
779        (
780            platform,
781            state,
782            version,
783            contract_id,
784            [token_id_0, token_id_1, token_id_2],
785            [identity_id_1, identity_id_2, identity_id_3],
786        )
787    }
788}