From 5f0776e69fe9b8d56d84133a8d752308c1466b82 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Thu, 1 May 2025 22:28:36 +0700 Subject: [PATCH 1/5] fix(drive-abci): make sure identities in token config exist --- .../token_configuration/accessors/mod.rs | 7 + .../token_configuration/accessors/v0/mod.rs | 5 +- .../token_configuration/v0/accessors.rs | 35 +++ packages/rs-dpp/src/errors/consensus/codes.rs | 9 +- ...ntity_for_change_control_rule_not_found.rs | 57 ++++ .../errors/consensus/state/identity/mod.rs | 2 + .../src/errors/consensus/state/state_error.rs | 4 + .../distribution/perpetual/block_based.rs | 2 +- .../data_contract_create/mod.rs | 258 ++++++++++++++++-- .../data_contract_create/state/v0/mod.rs | 49 ++++ .../key/fetch/fetch_identity_keys/v0/mod.rs | 6 +- .../src/drive/identity/key/fetch/mod.rs | 58 ++-- .../src/errors/consensus/consensus_error.rs | 4 + 13 files changed, 448 insertions(+), 48 deletions(-) create mode 100644 packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs index de7763b8c0b..4865980986c 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/mod.rs @@ -156,6 +156,13 @@ impl TokenConfigurationV0Getters for TokenConfiguration { } } + /// Returns all the change contract rules, including those from the distribution rules + fn all_change_control_rules(&self) -> Vec<(&str, &ChangeControlRules)> { + match self { + TokenConfiguration::V0(v0) => v0.all_change_control_rules(), + } + } + /// Returns the token description. fn description(&self) -> &Option { match self { diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs index e158a26d26d..1c095268b13 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs @@ -5,7 +5,7 @@ use crate::data_contract::associated_token::token_keeps_history_rules::TokenKeep use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use crate::data_contract::change_control_rules::ChangeControlRules; use crate::data_contract::GroupContractPosition; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; /// Accessor trait for getters of `TokenConfigurationV0` pub trait TokenConfigurationV0Getters { @@ -71,6 +71,9 @@ pub trait TokenConfigurationV0Getters { /// Returns all group positions used in the token configuration fn all_used_group_positions(&self) -> BTreeSet; + /// Returns all the change contract rules, including those from the distribution rules + fn all_change_control_rules(&self) -> Vec<(&str, &ChangeControlRules)>; + /// Returns the token description. fn description(&self) -> &Option; } diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs index ada9e92d36e..f992d730374 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/v0/accessors.rs @@ -160,6 +160,41 @@ impl TokenConfigurationV0Getters for TokenConfigurationV0 { group_positions } + fn all_change_control_rules(&self) -> Vec<(&str, &ChangeControlRules)> { + vec![ + ("max_supply_change_rules", &self.max_supply_change_rules), + ("conventions_change_rules", &self.conventions_change_rules), + ( + "distribution_rules.new_tokens_destination_identity_rules", + self.distribution_rules + .new_tokens_destination_identity_rules(), + ), + ( + "distribution_rules.minting_allow_choosing_destination_rules", + self.distribution_rules + .minting_allow_choosing_destination_rules(), + ), + ( + "distribution_rules.perpetual_distribution_rules", + self.distribution_rules.perpetual_distribution_rules(), + ), + ( + "distribution_rules.change_direct_purchase_pricing_rules", + self.distribution_rules + .change_direct_purchase_pricing_rules(), + ), + ("manual_minting_rules", &self.manual_minting_rules), + ("manual_burning_rules", &self.manual_burning_rules), + ("freeze_rules", &self.freeze_rules), + ("unfreeze_rules", &self.unfreeze_rules), + ( + "destroy_frozen_funds_rules", + &self.destroy_frozen_funds_rules, + ), + ("emergency_action_rules", &self.emergency_action_rules), + ] + } + /// Returns the token description. fn description(&self) -> &Option { &self.description diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index 430f82cb02b..2370f1c0875 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -235,6 +235,8 @@ impl ErrorWithCode for StateError { Self::DataContractConfigUpdateError { .. } => 40002, Self::DataContractUpdatePermissionError(_) => 40003, Self::DataContractUpdateActionNotAllowedError(_) => 40004, + Self::PreProgrammedDistributionTimestampInPastError(_) => 40005, + Self::IdentityForChangeControlRuleNotFoundError(_) => 40006, // Document Errors: 40100-40199 Self::DocumentAlreadyPresentError { .. } => 40100, @@ -310,10 +312,9 @@ impl ErrorWithCode for StateError { Self::InvalidTokenClaimNoCurrentRewards(_) => 40716, Self::InvalidTokenClaimWrongClaimant(_) => 40717, Self::TokenTransferRecipientIdentityNotExistError(_) => 40718, - Self::PreProgrammedDistributionTimestampInPastError(_) => 40719, - Self::TokenDirectPurchaseUserPriceTooLow(_) => 40720, - Self::TokenAmountUnderMinimumSaleAmount(_) => 40721, - Self::TokenNotForDirectSale(_) => 40722, + Self::TokenDirectPurchaseUserPriceTooLow(_) => 40719, + Self::TokenAmountUnderMinimumSaleAmount(_) => 40720, + Self::TokenNotForDirectSale(_) => 40721, // Group errors: 40800-40899 Self::IdentityNotMemberOfGroupError(_) => 40800, diff --git a/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs b/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs new file mode 100644 index 00000000000..912110628e8 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs @@ -0,0 +1,57 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use platform_value::Identifier; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Identity {identity_id} required by change control rule \"{rule_path}\" in token position {token_position} of contract {contract_id} does not exist")] +#[platform_serialize(unversioned)] +pub struct IdentityForChangeControlRuleNotFoundError { + contract_id: Identifier, + token_position: u16, + rule_path: String, + identity_id: Identifier, +} + +impl IdentityForChangeControlRuleNotFoundError { + pub fn new( + contract_id: Identifier, + token_position: u16, + rule_path: String, + identity_id: Identifier, + ) -> Self { + Self { + contract_id, + token_position, + rule_path, + identity_id, + } + } + + pub fn contract_id(&self) -> Identifier { + self.contract_id + } + + pub fn token_position(&self) -> u16 { + self.token_position + } + + pub fn rule_path(&self) -> &str { + &self.rule_path + } + + pub fn identity_id(&self) -> Identifier { + self.identity_id + } +} + +impl From for ConsensusError { + fn from(err: IdentityForChangeControlRuleNotFoundError) -> Self { + Self::StateError(StateError::IdentityForChangeControlRuleNotFoundError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs b/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs index 072bbda4ac3..f633fe671de 100644 --- a/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs @@ -17,5 +17,7 @@ pub mod missing_identity_public_key_ids_error; pub mod missing_transfer_key_error; pub mod no_transfer_key_for_core_withdrawal_available_error; +pub mod identity_for_change_control_rule_not_found; mod recipient_identity_does_not_exist_error; + pub use recipient_identity_does_not_exist_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/state/state_error.rs b/packages/rs-dpp/src/errors/consensus/state/state_error.rs index 8151d017fee..052cbde9def 100644 --- a/packages/rs-dpp/src/errors/consensus/state/state_error.rs +++ b/packages/rs-dpp/src/errors/consensus/state/state_error.rs @@ -35,6 +35,7 @@ use crate::consensus::state::document::document_contest_not_paid_for_error::Docu use crate::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; use crate::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; use crate::consensus::state::group::{GroupActionAlreadyCompletedError, GroupActionAlreadySignedByIdentityError, GroupActionDoesNotExistError, IdentityNotMemberOfGroupError}; +use crate::consensus::state::identity::identity_for_change_control_rule_not_found::IdentityForChangeControlRuleNotFoundError; use crate::consensus::state::identity::identity_public_key_already_exists_for_unique_contract_bounds_error::IdentityPublicKeyAlreadyExistsForUniqueContractBoundsError; use crate::consensus::state::identity::invalid_identity_contract_nonce_error::InvalidIdentityNonceError; use crate::consensus::state::identity::missing_transfer_key_error::MissingTransferKeyError; @@ -298,6 +299,9 @@ pub enum StateError { #[error(transparent)] TokenNotForDirectSale(TokenNotForDirectSale), + + #[error(transparent)] + IdentityForChangeControlRuleNotFoundError(IdentityForChangeControlRuleNotFoundError), } impl From for ConsensusError { diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/distribution/perpetual/block_based.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/distribution/perpetual/block_based.rs index 6f43fc1d44e..120933bcc22 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/distribution/perpetual/block_based.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/token/distribution/perpetual/block_based.rs @@ -755,7 +755,7 @@ mod random { #[test] #[ignore] - fn test_block_based_perpetual_random_0_MAX_distribution_param() { + fn test_block_based_perpetual_random_0_max_distribution_param() { check_heights( DistributionFunction::Random { min: 0, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs index dfc2f50980c..381328ee5ce 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs @@ -254,7 +254,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -317,7 +317,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -380,7 +380,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -477,7 +477,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -605,7 +605,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -698,7 +698,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -814,7 +814,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -838,7 +838,7 @@ mod tests { } #[test] - fn test_data_contract_creation_with_single_tokensetting_transfer_on_nft_purchase_with_internal_token_should_be_allowed( + fn test_data_contract_creation_with_single_token_setting_transfer_on_nft_purchase_with_internal_token_should_be_allowed( ) { let platform_version = PlatformVersion::latest(); let mut platform = TestPlatformBuilder::new() @@ -921,7 +921,101 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + } + + #[test] + fn test_data_contract_creation_with_single_token_setting_identifier_that_does_exist() { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let (identity_2, _signer_2, _key_2) = + setup_identity(&mut platform, 93, dash_to_credits!(0.5)); + + let mut data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/basic-token/basic-token.json", + None, + None, + false, //no need to validate the data contracts in tests for drive + platform_version, + ) + .expect("expected to get json based contract"); + + let identity_id = identity.id(); + + { + let token_config = data_contract + .tokens_mut() + .expect("expected tokens") + .get_mut(&0) + .expect("expected first token"); + token_config.set_manual_minting_rules(ChangeControlRules::V0( + ChangeControlRulesV0 { + authorized_to_make_change: AuthorizedActionTakers::Identity( + identity_2.id(), + ), + // We have no group at position 1, we should get an error + admin_action_takers: AuthorizedActionTakers::ContractOwner, + changing_authorized_action_takers_to_no_one_allowed: false, + changing_admin_action_takers_to_no_one_allowed: false, + self_changing_admin_action_takers_allowed: false, + }, + )); + } + + let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1); + + let token_id = calculate_token_id(data_contract_id.as_bytes(), 0); + + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -942,6 +1036,17 @@ mod tests { .commit_transaction(transaction) .unwrap() .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id, + identity_id.to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, Some(100_000)); } #[test] @@ -1045,7 +1150,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1285,7 +1390,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1367,6 +1472,7 @@ mod tests { use dpp::data_contract::associated_token::token_perpetual_distribution::TokenPerpetualDistribution; use dpp::data_contract::associated_token::token_perpetual_distribution::v0::TokenPerpetualDistributionV0; use super::*; + use dpp::consensus::state::state_error::StateError; #[test] fn test_data_contract_creation_with_single_token_with_starting_balance_over_limit_should_cause_error( ) { @@ -1427,7 +1533,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1538,7 +1644,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1652,7 +1758,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1688,6 +1794,114 @@ mod tests { assert_eq!(token_balance, None); } + #[test] + fn test_data_contract_creation_with_single_token_setting_identifier_that_does_not_exist( + ) { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let mut data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/basic-token/basic-token.json", + None, + None, + false, //no need to validate the data contracts in tests for drive + platform_version, + ) + .expect("expected to get json based contract"); + + let identity_id = identity.id(); + + { + let token_config = data_contract + .tokens_mut() + .expect("expected tokens") + .get_mut(&0) + .expect("expected first token"); + token_config.set_manual_minting_rules(ChangeControlRules::V0( + ChangeControlRulesV0 { + authorized_to_make_change: AuthorizedActionTakers::Identity( + Identifier::from([4; 32]), + ), + // We have no group at position 1, we should get an error + admin_action_takers: AuthorizedActionTakers::ContractOwner, + changing_authorized_action_takers_to_no_one_allowed: false, + changing_admin_action_takers_to_no_one_allowed: false, + self_changing_admin_action_takers_allowed: false, + }, + )); + } + + let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1); + + let token_id = calculate_token_id(data_contract_id.as_bytes(), 0); + + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::StateError( + StateError::IdentityForChangeControlRuleNotFoundError(_) + ), + _ + )] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id, + identity_id.to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, None); + } + #[test] fn test_data_contract_creation_with_single_token_setting_burn_of_external_token_not_allowed( ) { @@ -1787,7 +2001,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2136,7 +2350,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2266,7 +2480,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2394,7 +2608,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2523,7 +2737,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2651,7 +2865,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -2779,7 +2993,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_create_serialized_transition.clone()], + &[data_contract_create_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -3506,7 +3720,7 @@ mod tests { [StateTransitionExecutionResult::SuccessfulExecution(_, _)] ); - // Commit so we can query the state afterwards + // Commit so we can query the state afterward platform .drive .grove diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs index f002fe48f7f..36f31a6597f 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs @@ -2,13 +2,16 @@ use crate::error::Error; use crate::platform_types::platform::PlatformRef; use crate::rpc::core::CoreRPCLike; use dpp::block::block_info::BlockInfo; +use std::collections::BTreeSet; use dpp::consensus::state::data_contract::data_contract_already_present_error::DataContractAlreadyPresentError; +use dpp::consensus::state::identity::identity_for_change_control_rule_not_found::IdentityForChangeControlRuleNotFoundError; use dpp::consensus::state::state_error::StateError; use dpp::consensus::state::token::PreProgrammedDistributionTimestampInPastError; use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; use dpp::data_contract::associated_token::token_pre_programmed_distribution::accessors::v0::TokenPreProgrammedDistributionV0Methods; +use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0; use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; @@ -21,6 +24,10 @@ use crate::execution::types::state_transition_execution_context::{ }; use crate::execution::validation::state_transition::ValidationMode; use dpp::version::PlatformVersion; +use drive::drive::identity::key::fetch::KeyRequestType::LatestAuthenticationMasterKey; +use drive::drive::identity::key::fetch::{ + IdentityKeysRequest, OptionalSingleIdentityPublicKeyOutcome, +}; use drive::grovedb::TransactionArg; use drive::state_transition_action::contract::data_contract_create::DataContractCreateTransitionAction; use drive::state_transition_action::system::bump_identity_nonce_action::BumpIdentityNonceAction; @@ -67,8 +74,50 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr return Ok(action); } + let mut validated_identities = BTreeSet::new(); + // Validate token distribution rules for (position, config) in self.data_contract().tokens() { + for (name, change_control_rules) in config.all_change_control_rules() { + if let AuthorizedActionTakers::Identity(identity_id) = + change_control_rules.authorized_to_make_change_action_takers() + { + // we need to make sure this identity exists + if !validated_identities.contains(identity_id) { + let maybe_key = platform + .drive + .fetch_identity_keys::( + IdentityKeysRequest { + identity_id: identity_id.to_buffer(), + request_type: LatestAuthenticationMasterKey, + limit: Some(1), + offset: None, + }, + tx, + platform_version, + )?; + + if maybe_key.is_none() { + return Ok(ConsensusValidationResult::new_with_data_and_errors( + StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition(self), + ), + vec![StateError::IdentityForChangeControlRuleNotFoundError( + IdentityForChangeControlRuleNotFoundError::new( + self.data_contract().id(), + *position, + name.to_string(), + *identity_id, + ), + ) + .into()], + )); + } else { + validated_identities.insert(*identity_id); + } + } + } + } if let Some(distribution) = config.distribution_rules().pre_programmed_distribution() { if let Some((timestamp, _)) = distribution.distributions().iter().next() { if timestamp < &block_info.time_ms { diff --git a/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs b/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs index 877634eec00..976b0c24cfa 100644 --- a/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs @@ -1,6 +1,6 @@ use crate::drive::identity::key::fetch::KeyRequestType::{ - AllKeys, ContractBoundKey, ContractDocumentTypeBoundKey, RecentWithdrawalKeys, SearchKey, - SpecificKeys, + AllKeys, ContractBoundKey, ContractDocumentTypeBoundKey, LatestAuthenticationMasterKey, + RecentWithdrawalKeys, SearchKey, SpecificKeys, }; use crate::drive::identity::key::fetch::{ IdentityKeysRequest, IdentityPublicKeyResult, KeyKindRequestType, @@ -39,7 +39,7 @@ impl Drive { platform_version: &PlatformVersion, ) -> Result { match &key_request.request_type { - AllKeys => { + AllKeys | LatestAuthenticationMasterKey => { let path_query = key_request.into_path_query(); let (result, _) = self.grove_get_raw_path_query( diff --git a/packages/rs-drive/src/drive/identity/key/fetch/mod.rs b/packages/rs-drive/src/drive/identity/key/fetch/mod.rs index 44d104288a5..a645fa3a3b2 100644 --- a/packages/rs-drive/src/drive/identity/key/fetch/mod.rs +++ b/packages/rs-drive/src/drive/identity/key/fetch/mod.rs @@ -22,7 +22,9 @@ use { }; // Conditional imports for the feature "server" -use crate::drive::identity::identity_transfer_keys_path_vec; +use crate::drive::identity::{ + identity_query_keys_security_level_tree_path_vec, identity_transfer_keys_path_vec, +}; #[cfg(feature = "server")] use { crate::error::{drive::DriveError, fee::FeeError, identity::IdentityError, Error}, @@ -81,6 +83,8 @@ pub enum KeyRequestType { ContractBoundKey([u8; 32], Purpose, KeyKindRequestType), /// Search for contract bound keys ContractDocumentTypeBoundKey([u8; 32], String, Purpose, KeyKindRequestType), + /// Get Current Authentication Master Key + LatestAuthenticationMasterKey, } #[cfg(any(feature = "server", feature = "verify"))] @@ -273,7 +277,7 @@ impl IdentityPublicKeyResult for SingleIdentityPublicKeyOutcome { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence let mut keys = value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -327,7 +331,7 @@ impl IdentityPublicKeyResult for OptionalSingleIdentityPublicKeyOutcome { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence let mut keys = value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -377,7 +381,7 @@ impl IdentityPublicKeyResult for KeyIDHashSet { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -403,7 +407,7 @@ impl IdentityPublicKeyResult for KeyIDVec { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -429,7 +433,7 @@ impl IdentityPublicKeyResult for KeyVec { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -455,7 +459,7 @@ impl IdentityPublicKeyResult for SerializedKeyVec { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -481,7 +485,7 @@ impl IdentityPublicKeyResult for KeyIDIdentityPublicKeyPairVec { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -559,7 +563,7 @@ impl IdentityPublicKeyResult for KeyIDIdentityPublicKeyPairBTreeMap { value: Vec, _platform_version: &PlatformVersion, ) -> Result { - // We do not care about non existence + // We do not care about non-existence value .into_iter() .filter_map(|(_, _, maybe_element)| maybe_element) @@ -680,6 +684,10 @@ impl IdentityKeysRequest { .fee_version .processing .fetch_single_identity_key_processing_cost), + KeyRequestType::LatestAuthenticationMasterKey => Ok(platform_version + .fee_version + .processing + .fetch_single_identity_key_processing_cost), } } @@ -721,7 +729,7 @@ impl IdentityKeysRequest { } #[cfg(feature = "server")] - /// Make a request for an decryption key for a specific contract + /// Make a request for a decryption key for a specific contract pub fn new_contract_decryption_keys_query( identity_id: [u8; 32], contract_id: [u8; 32], @@ -865,7 +873,7 @@ impl IdentityKeysRequest { PathQuery { path: query_keys_path, query: SizedQuery { - query: Self::specific_keys_query(key_ids), + query: Self::specific_keys_query(key_ids.as_slice()), limit, offset: None, }, @@ -876,7 +884,7 @@ impl IdentityKeysRequest { PathQuery { path: query_keys_path, query: SizedQuery { - query: Self::construct_search_query(map), + query: Self::construct_search_query(&map), limit, offset, }, @@ -950,6 +958,22 @@ impl IdentityKeysRequest { }, } } + KeyRequestType::LatestAuthenticationMasterKey => { + let query_keys_path = identity_query_keys_security_level_tree_path_vec( + &identity_id, + SecurityLevel::MASTER, + ); + let mut query = Query::new_with_direction(false); + query.insert_all(); + PathQuery { + path: query_keys_path, + query: SizedQuery { + query, + limit: Some(1), + offset, + }, + } + } } } @@ -963,7 +987,7 @@ impl IdentityKeysRequest { #[cfg(any(feature = "server", feature = "verify"))] /// Fetch a specific key knowing the id - fn specific_keys_query(key_ids: Vec) -> Query { + fn specific_keys_query(key_ids: &[KeyID]) -> Query { let mut query = Query::new(); for key_id in key_ids { query.insert_key(key_id.encode_var_vec()); @@ -974,15 +998,15 @@ impl IdentityKeysRequest { #[cfg(any(feature = "server", feature = "verify"))] /// Construct the query for the request fn construct_search_query( - key_requests: BTreeMap>, + key_requests: &BTreeMap>, ) -> Query { fn construct_security_level_query( - key_requests: BTreeMap, + key_requests: &BTreeMap, ) -> Query { let mut query = Query::new(); for (security_level, key_request_type) in key_requests { - let key = vec![security_level]; + let key = vec![*security_level]; let subquery = match key_request_type { CurrentKeyOfKindRequest => { let mut subquery = Query::new(); @@ -1002,7 +1026,7 @@ impl IdentityKeysRequest { let mut query = Query::new(); for (purpose, leftover_query) in key_requests { - let key = vec![purpose]; + let key = vec![*purpose]; if !leftover_query.is_empty() { query.add_conditional_subquery( QueryItem::Key(key), diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 65823940f40..ef17b9a4b20 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -77,6 +77,7 @@ use dpp::consensus::state::document::document_contest_not_paid_for_error::Docume use dpp::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; use dpp::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; use dpp::consensus::state::group::{GroupActionAlreadyCompletedError, GroupActionAlreadySignedByIdentityError, GroupActionDoesNotExistError, IdentityNotMemberOfGroupError}; +use dpp::consensus::state::identity::identity_for_change_control_rule_not_found::IdentityForChangeControlRuleNotFoundError; use dpp::consensus::state::identity::identity_public_key_already_exists_for_unique_contract_bounds_error::IdentityPublicKeyAlreadyExistsForUniqueContractBoundsError; use dpp::consensus::state::identity::master_public_key_update_error::MasterPublicKeyUpdateError; use dpp::consensus::state::identity::missing_transfer_key_error::MissingTransferKeyError; @@ -408,6 +409,9 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::TokenNotForDirectSale(e) => { generic_consensus_error!(TokenNotForDirectSale, e).into() } + StateError::IdentityForChangeControlRuleNotFoundError(e) => { + generic_consensus_error!(IdentityForChangeControlRuleNotFoundError, e).into() + } } } From 04e71b4710d633751dde2a5e32cee710487e0e8c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 2 May 2025 00:06:43 +0700 Subject: [PATCH 2/5] a lot more tests --- .../token_configuration/accessors/v0/mod.rs | 2 +- packages/rs-dpp/src/errors/consensus/codes.rs | 2 +- ...ntity_for_change_control_rule_not_found.rs | 57 -- ...for_token_configuration_not_found_error.rs | 66 ++ .../errors/consensus/state/identity/mod.rs | 2 +- .../src/errors/consensus/state/state_error.rs | 4 +- .../data_contract_create/mod.rs | 239 ++++++- .../data_contract_create/state/v0/mod.rs | 122 +++- .../data_contract_update/mod.rs | 617 +++++++++++++++++- .../data_contract_update/state/v0/mod.rs | 203 +++++- .../key/fetch/fetch_identity_keys/v0/mod.rs | 5 +- .../src/errors/consensus/consensus_error.rs | 6 +- 12 files changed, 1230 insertions(+), 95 deletions(-) delete mode 100644 packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs create mode 100644 packages/rs-dpp/src/errors/consensus/state/identity/identity_for_token_configuration_not_found_error.rs diff --git a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs index 1c095268b13..38090f081de 100644 --- a/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs +++ b/packages/rs-dpp/src/data_contract/associated_token/token_configuration/accessors/v0/mod.rs @@ -5,7 +5,7 @@ use crate::data_contract::associated_token::token_keeps_history_rules::TokenKeep use crate::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use crate::data_contract::change_control_rules::ChangeControlRules; use crate::data_contract::GroupContractPosition; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; /// Accessor trait for getters of `TokenConfigurationV0` pub trait TokenConfigurationV0Getters { diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index 2370f1c0875..a9dd5022c87 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -236,7 +236,7 @@ impl ErrorWithCode for StateError { Self::DataContractUpdatePermissionError(_) => 40003, Self::DataContractUpdateActionNotAllowedError(_) => 40004, Self::PreProgrammedDistributionTimestampInPastError(_) => 40005, - Self::IdentityForChangeControlRuleNotFoundError(_) => 40006, + Self::IdentityInTokenConfigurationNotFoundError(_) => 40006, // Document Errors: 40100-40199 Self::DocumentAlreadyPresentError { .. } => 40100, diff --git a/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs b/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs deleted file mode 100644 index 912110628e8..00000000000 --- a/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_change_control_rule_not_found.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::consensus::state::state_error::StateError; -use crate::consensus::ConsensusError; -use crate::ProtocolError; -use bincode::{Decode, Encode}; -use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; -use platform_value::Identifier; -use thiserror::Error; - -#[derive( - Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, -)] -#[error("Identity {identity_id} required by change control rule \"{rule_path}\" in token position {token_position} of contract {contract_id} does not exist")] -#[platform_serialize(unversioned)] -pub struct IdentityForChangeControlRuleNotFoundError { - contract_id: Identifier, - token_position: u16, - rule_path: String, - identity_id: Identifier, -} - -impl IdentityForChangeControlRuleNotFoundError { - pub fn new( - contract_id: Identifier, - token_position: u16, - rule_path: String, - identity_id: Identifier, - ) -> Self { - Self { - contract_id, - token_position, - rule_path, - identity_id, - } - } - - pub fn contract_id(&self) -> Identifier { - self.contract_id - } - - pub fn token_position(&self) -> u16 { - self.token_position - } - - pub fn rule_path(&self) -> &str { - &self.rule_path - } - - pub fn identity_id(&self) -> Identifier { - self.identity_id - } -} - -impl From for ConsensusError { - fn from(err: IdentityForChangeControlRuleNotFoundError) -> Self { - Self::StateError(StateError::IdentityForChangeControlRuleNotFoundError(err)) - } -} diff --git a/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_token_configuration_not_found_error.rs b/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_token_configuration_not_found_error.rs new file mode 100644 index 00000000000..374a33f56b8 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/identity/identity_for_token_configuration_not_found_error.rs @@ -0,0 +1,66 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use platform_value::Identifier; +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize)] +#[platform_serialize(unversioned)] +pub enum TokenConfigurationIdentityContext { + ChangeControlRule(String), + DefaultMintingRecipient, + PerpetualDistributionRecipient, + PreProgrammedDistributionRecipient, +} + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Identity {identity_id} required in token position {token_position} of contract {contract_id} for {context:?} does not exist")] +#[platform_serialize(unversioned)] +pub struct IdentityInTokenConfigurationNotFoundError { + contract_id: Identifier, + token_position: u16, + context: TokenConfigurationIdentityContext, + identity_id: Identifier, +} + +impl IdentityInTokenConfigurationNotFoundError { + pub fn new( + contract_id: Identifier, + token_position: u16, + context: TokenConfigurationIdentityContext, + identity_id: Identifier, + ) -> Self { + Self { + contract_id, + token_position, + context, + identity_id, + } + } + + pub fn contract_id(&self) -> &Identifier { + &self.contract_id + } + + pub fn token_position(&self) -> u16 { + self.token_position + } + + pub fn context(&self) -> &TokenConfigurationIdentityContext { + &self.context + } + + pub fn identity_id(&self) -> &Identifier { + &self.identity_id + } +} + +impl From for ConsensusError { + fn from(err: IdentityInTokenConfigurationNotFoundError) -> Self { + Self::StateError(StateError::IdentityInTokenConfigurationNotFoundError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs b/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs index f633fe671de..8867a23a7fe 100644 --- a/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/state/identity/mod.rs @@ -17,7 +17,7 @@ pub mod missing_identity_public_key_ids_error; pub mod missing_transfer_key_error; pub mod no_transfer_key_for_core_withdrawal_available_error; -pub mod identity_for_change_control_rule_not_found; +pub mod identity_for_token_configuration_not_found_error; mod recipient_identity_does_not_exist_error; pub use recipient_identity_does_not_exist_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/state/state_error.rs b/packages/rs-dpp/src/errors/consensus/state/state_error.rs index 052cbde9def..b566ebe611d 100644 --- a/packages/rs-dpp/src/errors/consensus/state/state_error.rs +++ b/packages/rs-dpp/src/errors/consensus/state/state_error.rs @@ -35,7 +35,7 @@ use crate::consensus::state::document::document_contest_not_paid_for_error::Docu use crate::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; use crate::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; use crate::consensus::state::group::{GroupActionAlreadyCompletedError, GroupActionAlreadySignedByIdentityError, GroupActionDoesNotExistError, IdentityNotMemberOfGroupError}; -use crate::consensus::state::identity::identity_for_change_control_rule_not_found::IdentityForChangeControlRuleNotFoundError; +use crate::consensus::state::identity::identity_for_token_configuration_not_found_error::IdentityInTokenConfigurationNotFoundError; use crate::consensus::state::identity::identity_public_key_already_exists_for_unique_contract_bounds_error::IdentityPublicKeyAlreadyExistsForUniqueContractBoundsError; use crate::consensus::state::identity::invalid_identity_contract_nonce_error::InvalidIdentityNonceError; use crate::consensus::state::identity::missing_transfer_key_error::MissingTransferKeyError; @@ -301,7 +301,7 @@ pub enum StateError { TokenNotForDirectSale(TokenNotForDirectSale), #[error(transparent)] - IdentityForChangeControlRuleNotFoundError(IdentityForChangeControlRuleNotFoundError), + IdentityInTokenConfigurationNotFoundError(IdentityInTokenConfigurationNotFoundError), } impl From for ConsensusError { diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs index 381328ee5ce..9810a027ae2 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs @@ -1473,6 +1473,9 @@ mod tests { use dpp::data_contract::associated_token::token_perpetual_distribution::v0::TokenPerpetualDistributionV0; use super::*; use dpp::consensus::state::state_error::StateError; + use dpp::data_contract::associated_token::token_pre_programmed_distribution::TokenPreProgrammedDistribution; + use dpp::data_contract::associated_token::token_pre_programmed_distribution::v0::TokenPreProgrammedDistributionV0; + #[test] fn test_data_contract_creation_with_single_token_with_starting_balance_over_limit_should_cause_error( ) { @@ -1877,7 +1880,241 @@ mod tests { processing_result.execution_results().as_slice(), [StateTransitionExecutionResult::PaidConsensusError( ConsensusError::StateError( - StateError::IdentityForChangeControlRuleNotFoundError(_) + StateError::IdentityInTokenConfigurationNotFoundError(_) + ), + _ + )] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id, + identity_id.to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, None); + } + + #[test] + fn test_data_contract_creation_with_single_token_setting_minting_recipient_to_identity_that_does_not_exist( + ) { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let mut data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/basic-token/basic-token.json", + None, + None, + false, //no need to validate the data contracts in tests for drive + platform_version, + ) + .expect("expected to get json based contract"); + + let identity_id = identity.id(); + + { + let token_config = data_contract + .tokens_mut() + .expect("expected tokens") + .get_mut(&0) + .expect("expected first token"); + token_config + .distribution_rules_mut() + .set_new_tokens_destination_identity(Some(Identifier::from([4; 32]))); + } + + let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1); + + let token_id = calculate_token_id(data_contract_id.as_bytes(), 0); + + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::StateError( + StateError::IdentityInTokenConfigurationNotFoundError(_) + ), + _ + )] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("expected to commit transaction"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id, + identity_id.to_buffer(), + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, None); + } + + #[test] + fn test_data_contract_creation_with_single_token_setting_pre_programmed_distribution_to_identity_that_does_not_exist( + ) { + let platform_version = PlatformVersion::latest(); + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_genesis_state(); + + let platform_state = platform.state.load(); + + let (identity, signer, key) = + setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + + let mut data_contract = json_document_to_contract_with_ids( + "tests/supporting_files/contract/basic-token/basic-token.json", + None, + None, + false, //no need to validate the data contracts in tests for drive + platform_version, + ) + .expect("expected to get json based contract"); + + let (identity_2, _, _) = setup_identity(&mut platform, 5456, dash_to_credits!(0.1)); + + let (identity_3, _, _) = setup_identity(&mut platform, 123, dash_to_credits!(0.1)); + + let (identity_4, _, _) = setup_identity(&mut platform, 548, dash_to_credits!(0.1)); + + let identity_id = identity.id(); + + let base_supply_start_amount = 0; + + let token_config = data_contract + .tokens_mut() + .expect("expected tokens") + .get_mut(&0) + .expect("expected first token"); + token_config.set_base_supply(base_supply_start_amount); + + // Create a new BTreeMap to store distributions + let mut distributions: BTreeMap< + TimestampMillis, + BTreeMap, + > = BTreeMap::new(); + + // Create distributions for different timestamps + distributions.insert( + 1700000000000, // Example timestamp (milliseconds) + BTreeMap::from([ + (identity.id(), 10), // Identity 1 gets 10 tokens + (identity_2.id(), 5), // Identity 2 gets 5 tokens + ]), + ); + + distributions.insert( + 1700005000000, // Another timestamp + BTreeMap::from([ + (identity_3.id(), 15), // Identity 3 gets 15 tokens + (identity_4.id(), 20), // Identity 4 gets 20 tokens + (Identifier::new([6; 32]), 25), // Identifier does not exist + ]), + ); + + token_config + .distribution_rules_mut() + .set_pre_programmed_distribution(Some(TokenPreProgrammedDistribution::V0( + TokenPreProgrammedDistributionV0 { + distributions: distributions.clone(), + }, + ))); + + let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1); + + let token_id = calculate_token_id(data_contract_id.as_bytes(), 0); + + let data_contract_create_transition = + DataContractCreateTransition::new_from_data_contract( + data_contract, + 1, + &identity.into_partial_identity_info(), + key.id(), + &signer, + platform_version, + None, + ) + .expect("expect to create documents batch transition"); + + let data_contract_create_serialized_transition = data_contract_create_transition + .serialize_to_bytes() + .expect("expected documents batch serialized state transition"); + + let transaction = platform.drive.grove.start_transaction(); + + let processing_result = platform + .platform + .process_raw_state_transitions( + &[data_contract_create_serialized_transition.clone()], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected to process state transition"); + + assert_matches!( + processing_result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::StateError( + StateError::IdentityInTokenConfigurationNotFoundError(_) ), _ )] diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs index 36f31a6597f..b6bd9d9fbc6 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs @@ -5,11 +5,15 @@ use dpp::block::block_info::BlockInfo; use std::collections::BTreeSet; use dpp::consensus::state::data_contract::data_contract_already_present_error::DataContractAlreadyPresentError; -use dpp::consensus::state::identity::identity_for_change_control_rule_not_found::IdentityForChangeControlRuleNotFoundError; +use dpp::consensus::state::identity::identity_for_token_configuration_not_found_error::{ + IdentityInTokenConfigurationNotFoundError, TokenConfigurationIdentityContext, +}; use dpp::consensus::state::state_error::StateError; use dpp::consensus::state::token::PreProgrammedDistributionTimestampInPastError; use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; +use dpp::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::TokenDistributionRecipient; +use dpp::data_contract::associated_token::token_perpetual_distribution::methods::v0::TokenPerpetualDistributionV0Accessors; use dpp::data_contract::associated_token::token_pre_programmed_distribution::accessors::v0::TokenPreProgrammedDistributionV0Methods; use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use dpp::prelude::ConsensusValidationResult; @@ -18,7 +22,7 @@ use dpp::state_transition::data_contract_create_transition::DataContractCreateTr use dpp::ProtocolError; use crate::error::execution::ExecutionError; -use crate::execution::types::execution_operation::ValidationOperation; +use crate::execution::types::execution_operation::{RetrieveIdentityInfo, ValidationOperation}; use crate::execution::types::state_transition_execution_context::{ StateTransitionExecutionContext, StateTransitionExecutionContextMethodsV0, }; @@ -78,6 +82,8 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr // Validate token distribution rules for (position, config) in self.data_contract().tokens() { + // We validate that if for any change control rule set to an identity that that identity exists + // and can sign state transition (not an evonode identity) for (name, change_control_rules) in config.all_change_control_rules() { if let AuthorizedActionTakers::Identity(identity_id) = change_control_rules.authorized_to_make_change_action_takers() @@ -97,16 +103,20 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr platform_version, )?; + execution_context.add_operation(ValidationOperation::RetrieveIdentity( + RetrieveIdentityInfo::one_key(), + )); + if maybe_key.is_none() { return Ok(ConsensusValidationResult::new_with_data_and_errors( StateTransitionAction::BumpIdentityNonceAction( BumpIdentityNonceAction::from_borrowed_data_contract_create_transition(self), ), - vec![StateError::IdentityForChangeControlRuleNotFoundError( - IdentityForChangeControlRuleNotFoundError::new( + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( self.data_contract().id(), *position, - name.to_string(), + TokenConfigurationIdentityContext::ChangeControlRule(name.to_string()), *identity_id, ), ) @@ -118,6 +128,75 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr } } } + // We validate that if we set a minting distribution that this identity exists + // It can be an evonode, so we just use the balance as a check + + if let Some(minting_recipient) = config + .distribution_rules() + .new_tokens_destination_identity() + { + let maybe_balance = platform.drive.fetch_identity_balance( + minting_recipient.to_buffer(), + tx, + platform_version, + )?; + + execution_context.add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + return Ok(ConsensusValidationResult::new_with_data_and_errors( + StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition( + self, + ), + ), + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + self.data_contract().id(), + *position, + TokenConfigurationIdentityContext::DefaultMintingRecipient, + *minting_recipient, + ), + ) + .into()], + )); + } + } + + if let Some(distribution) = config.distribution_rules().perpetual_distribution() { + if let TokenDistributionRecipient::Identity(identifier) = + distribution.distribution_recipient() + { + let maybe_balance = platform.drive.fetch_identity_balance( + identifier.to_buffer(), + tx, + platform_version, + )?; + + execution_context + .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + return Ok(ConsensusValidationResult::new_with_data_and_errors( + StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition( + self, + ), + ), + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + self.data_contract().id(), + *position, + TokenConfigurationIdentityContext::PerpetualDistributionRecipient, + identifier, + ), + ) + .into()], + )); + } + } + } + if let Some(distribution) = config.distribution_rules().pre_programmed_distribution() { if let Some((timestamp, _)) = distribution.distributions().iter().next() { if timestamp < &block_info.time_ms { @@ -128,10 +207,41 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr vec![StateError::PreProgrammedDistributionTimestampInPastError( PreProgrammedDistributionTimestampInPastError::new(self.data_contract().id(), *position, *timestamp, block_info.time_ms), ) - .into()], + .into()], )); } } + for distribution in distribution.distributions().values() { + for identifier in distribution.keys() { + let maybe_balance = platform.drive.fetch_identity_balance( + identifier.to_buffer(), + tx, + platform_version, + )?; + + execution_context + .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + return Ok(ConsensusValidationResult::new_with_data_and_errors( + StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition( + self, + ), + ), + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + self.data_contract().id(), + *position, + TokenConfigurationIdentityContext::PreProgrammedDistributionRecipient, + *identifier, + ), + ) + .into()], + )); + } + } + } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs index 114a1864901..5f6663790c8 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/mod.rs @@ -669,7 +669,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_update_serialized_transition.clone()], + &[data_contract_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -791,7 +791,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_update_serialized_transition.clone()], + &[data_contract_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -920,7 +920,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_update_serialized_transition.clone()], + &[data_contract_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1049,7 +1049,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_update_serialized_transition.clone()], + &[data_contract_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1182,7 +1182,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![data_contract_update_serialized_transition.clone()], + &[data_contract_update_serialized_transition.clone()], &platform_state, &BlockInfo::default(), &transaction, @@ -1218,6 +1218,15 @@ mod tests { use dpp::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention; use dpp::data_contract::associated_token::token_configuration_localization::v0::TokenConfigurationLocalizationV0; use dpp::data_contract::associated_token::token_configuration_localization::TokenConfigurationLocalization; + use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Setters; + use dpp::data_contract::associated_token::token_perpetual_distribution::distribution_function::DistributionFunction; + use dpp::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::TokenDistributionRecipient; + use dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_type::RewardDistributionType; + use dpp::data_contract::associated_token::token_perpetual_distribution::TokenPerpetualDistribution; + use dpp::data_contract::associated_token::token_perpetual_distribution::v0::TokenPerpetualDistributionV0; + use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; + use dpp::data_contract::change_control_rules::ChangeControlRules; + use dpp::data_contract::change_control_rules::v0::ChangeControlRulesV0; #[test] fn test_data_contract_update_can_add_new_token() { @@ -1321,6 +1330,212 @@ mod tests { .expect("expected to commit transaction"); } + #[test] + fn test_data_contract_update_with_token_setting_identifier_that_does_exist() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let (identity2, _signer2, _key2) = + setup_identity(&mut platform, 93, dash_to_credits!(0.2)); + + let platform_state = platform.state.load(); + let platform_version = PlatformVersion::latest(); + + let mut original_contract = + get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned(); + original_contract.set_owner_id(identity.id()); + + platform + .drive + .apply_contract( + &original_contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract"); + + let mut updated_contract = original_contract.clone(); + updated_contract.set_version(2); + + let mut token_config = + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()); + token_config.set_base_supply(100_000); + token_config.set_manual_minting_rules(ChangeControlRules::V0(ChangeControlRulesV0 { + authorized_to_make_change: AuthorizedActionTakers::Identity(identity2.id()), + admin_action_takers: AuthorizedActionTakers::ContractOwner, + changing_authorized_action_takers_to_no_one_allowed: false, + changing_admin_action_takers_to_no_one_allowed: false, + self_changing_admin_action_takers_allowed: false, + })); + + token_config.set_conventions(TokenConfigurationConvention::V0( + TokenConfigurationConventionV0 { + localizations: BTreeMap::from([( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: true, + singular_form: "test".to_string(), + plural_form: "tests".to_string(), + }), + )]), + decimals: 8, + }, + )); + + updated_contract.add_token(0, token_config); + + let transition = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.into_partial_identity_info(), + key.id(), + 2, + 0, + &signer, + platform_version, + None, + ) + .expect("expected update transition"); + + let serialized = transition.serialize_to_bytes().expect("serialize"); + + let transaction = platform.drive.grove.start_transaction(); + let result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected processing"); + + assert_matches!( + result.execution_results().as_slice(), + [StateTransitionExecutionResult::SuccessfulExecution(_, _)] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("commit"); + } + #[test] + fn test_data_contract_update_with_token_setting_identifier_that_does_not_exist() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + + let (identity, signer, key) = setup_identity(&mut platform, 958, dash_to_credits!(0.1)); + let platform_state = platform.state.load(); + let platform_version = PlatformVersion::latest(); + + let mut original_contract = + get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned(); + original_contract.set_owner_id(identity.id()); + + platform + .drive + .apply_contract( + &original_contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .expect("expected to apply contract"); + + let mut updated_contract = original_contract.clone(); + updated_contract.set_version(2); + + let mut token_config = + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()); + token_config.set_base_supply(1_000_000); + + token_config.set_manual_minting_rules(ChangeControlRules::V0(ChangeControlRulesV0 { + authorized_to_make_change: AuthorizedActionTakers::Identity(Identifier::from( + [4; 32], + )), // doesn't exist + admin_action_takers: AuthorizedActionTakers::ContractOwner, + changing_authorized_action_takers_to_no_one_allowed: false, + changing_admin_action_takers_to_no_one_allowed: false, + self_changing_admin_action_takers_allowed: false, + })); + + token_config.set_conventions(TokenConfigurationConvention::V0( + TokenConfigurationConventionV0 { + localizations: BTreeMap::from([( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: true, + singular_form: "test".to_string(), + plural_form: "tests".to_string(), + }), + )]), + decimals: 8, + }, + )); + + updated_contract.add_token(0, token_config); + + let transition = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.into_partial_identity_info(), + key.id(), + 2, + 0, + &signer, + platform_version, + None, + ) + .expect("expected update transition"); + + let serialized = transition.serialize_to_bytes().expect("serialize"); + + let transaction = platform.drive.grove.start_transaction(); + let result = platform + .platform + .process_raw_state_transitions( + &[serialized], + &platform_state, + &BlockInfo::default(), + &transaction, + platform_version, + false, + None, + ) + .expect("expected processing"); + + assert_matches!( + result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::StateError( + StateError::IdentityInTokenConfigurationNotFoundError(_) + ), + _ + )] + ); + + platform + .drive + .grove + .commit_transaction(transaction) + .unwrap() + .expect("commit"); + } + #[test] fn test_data_contract_update_can_not_add_new_token_with_gap() { let mut platform = TestPlatformBuilder::new() @@ -1401,7 +1616,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![tx_bytes], + &[tx_bytes], &platform_state, &BlockInfo::default(), &transaction, @@ -1583,7 +1798,7 @@ mod tests { let processing_result = platform .platform .process_raw_state_transitions( - &vec![tx_bytes], + &[tx_bytes], &platform_state, &BlockInfo::default(), &transaction, @@ -1607,6 +1822,384 @@ mod tests { .unwrap() .expect("expected to commit transaction"); } + + #[test] + fn update_token_with_missing_main_group_should_fail() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let (identity, signer, key) = + setup_identity(&mut platform, 1234, dash_to_credits!(0.1)); + let platform_state = platform.state.load(); + let platform_version = PlatformVersion::latest(); + + let mut contract = + get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned(); + contract.set_owner_id(identity.id()); + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .unwrap(); + + let mut updated_contract = contract.clone(); + updated_contract.set_version(2); + + let mut config = + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()); + config.set_main_control_group(Some(1)); // Missing group + config.set_manual_minting_rules(ChangeControlRules::V0(ChangeControlRulesV0 { + authorized_to_make_change: AuthorizedActionTakers::MainGroup, + admin_action_takers: AuthorizedActionTakers::MainGroup, + changing_authorized_action_takers_to_no_one_allowed: false, + changing_admin_action_takers_to_no_one_allowed: false, + self_changing_admin_action_takers_allowed: false, + })); + config.set_conventions(TokenConfigurationConvention::V0( + TokenConfigurationConventionV0 { + localizations: BTreeMap::from([( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: true, + singular_form: "test".to_string(), + plural_form: "tests".to_string(), + }), + )]), + decimals: 8, + }, + )); + updated_contract.add_token(0, config); + + let transition = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.into_partial_identity_info(), + key.id(), + 2, + 0, + &signer, + platform_version, + None, + ) + .unwrap(); + let tx = platform.drive.grove.start_transaction(); + let result = platform + .platform + .process_raw_state_transitions( + &[transition.serialize_to_bytes().unwrap()], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .unwrap(); + + assert_matches!( + result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::GroupPositionDoesNotExistError(_)) + )] + ); + } + + #[test] + fn update_token_with_invalid_distribution_function_should_fail() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let (identity, signer, key) = + setup_identity(&mut platform, 1234, dash_to_credits!(0.1)); + let platform_state = platform.state.load(); + let platform_version = PlatformVersion::latest(); + + let mut contract = + get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned(); + contract.set_owner_id(identity.id()); + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .unwrap(); + + let mut updated_contract = contract.clone(); + updated_contract.set_version(2); + + let mut config = + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()); + config + .distribution_rules_mut() + .set_perpetual_distribution(Some(TokenPerpetualDistribution::V0( + TokenPerpetualDistributionV0 { + distribution_type: RewardDistributionType::BlockBasedDistribution { + interval: 10, + function: DistributionFunction::Exponential { + a: 0, + d: 0, + m: 0, + n: 0, + o: 0, + start_moment: None, + b: 0, + min_value: None, + max_value: None, + }, + }, + distribution_recipient: TokenDistributionRecipient::Identity(identity.id()), + }, + ))); + config.set_conventions(TokenConfigurationConvention::V0( + TokenConfigurationConventionV0 { + localizations: BTreeMap::from([( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: true, + singular_form: "test".to_string(), + plural_form: "tests".to_string(), + }), + )]), + decimals: 8, + }, + )); + updated_contract.add_token(0, config); + + let transition = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.into_partial_identity_info(), + key.id(), + 2, + 0, + &signer, + platform_version, + None, + ) + .unwrap(); + let tx = platform.drive.grove.start_transaction(); + let result = platform + .platform + .process_raw_state_transitions( + &[transition.serialize_to_bytes().unwrap()], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .unwrap(); + + assert_matches!( + result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError( + BasicError::InvalidTokenDistributionFunctionDivideByZeroError(_) + ) + )] + ); + } + + #[test] + fn update_token_with_random_distribution_should_fail() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let (identity, signer, key) = + setup_identity(&mut platform, 1234, dash_to_credits!(0.1)); + let platform_state = platform.state.load(); + let platform_version = PlatformVersion::latest(); + + let mut contract = + get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned(); + contract.set_owner_id(identity.id()); + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .unwrap(); + + let mut updated_contract = contract.clone(); + updated_contract.set_version(2); + + let mut config = + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()); + config + .distribution_rules_mut() + .set_perpetual_distribution(Some(TokenPerpetualDistribution::V0( + TokenPerpetualDistributionV0 { + distribution_type: RewardDistributionType::BlockBasedDistribution { + interval: 10, + function: DistributionFunction::Random { min: 0, max: 10 }, + }, + distribution_recipient: TokenDistributionRecipient::Identity(identity.id()), + }, + ))); + config.set_conventions(TokenConfigurationConvention::V0( + TokenConfigurationConventionV0 { + localizations: BTreeMap::from([( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: true, + singular_form: "test".to_string(), + plural_form: "tests".to_string(), + }), + )]), + decimals: 8, + }, + )); + updated_contract.add_token(0, config); + + let transition = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.into_partial_identity_info(), + key.id(), + 2, + 0, + &signer, + platform_version, + None, + ) + .unwrap(); + let tx = platform.drive.grove.start_transaction(); + let result = platform + .platform + .process_raw_state_transitions( + &[transition.serialize_to_bytes().unwrap()], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .unwrap(); + + assert_matches!( + result.execution_results().as_slice(), + [StateTransitionExecutionResult::UnpaidConsensusError( + ConsensusError::BasicError(BasicError::UnsupportedFeatureError(_)) + )] + ); + } + + #[test] + fn update_token_overwriting_existing_position_should_fail() { + let mut platform = TestPlatformBuilder::new() + .build_with_mock_rpc() + .set_initial_state_structure(); + let (identity, signer, key) = + setup_identity(&mut platform, 1234, dash_to_credits!(0.1)); + let platform_state = platform.state.load(); + let platform_version = PlatformVersion::latest(); + + let mut contract = + get_data_contract_fixture(None, 0, platform_version.protocol_version) + .data_contract_owned(); + contract.set_owner_id(identity.id()); + let mut config = + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()); + config.set_conventions(TokenConfigurationConvention::V0( + TokenConfigurationConventionV0 { + localizations: BTreeMap::from([( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: true, + singular_form: "test".to_string(), + plural_form: "tests".to_string(), + }), + )]), + decimals: 8, + }, + )); + + let mut config_2 = + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()); + config_2.set_conventions(TokenConfigurationConvention::V0( + TokenConfigurationConventionV0 { + localizations: BTreeMap::from([( + "en".to_string(), + TokenConfigurationLocalization::V0(TokenConfigurationLocalizationV0 { + should_capitalize: true, + singular_form: "test_1".to_string(), + plural_form: "tests_2".to_string(), + }), + )]), + decimals: 8, + }, + )); + contract.add_token(0, config); + + platform + .drive + .apply_contract( + &contract, + BlockInfo::default(), + true, + StorageFlags::optional_default_as_cow(), + None, + platform_version, + ) + .unwrap(); + + let mut updated_contract = contract.clone(); + updated_contract.set_version(2); + updated_contract.add_token(0, config_2); + + let transition = DataContractUpdateTransition::new_from_data_contract( + updated_contract, + &identity.into_partial_identity_info(), + key.id(), + 2, + 0, + &signer, + platform_version, + None, + ) + .unwrap(); + let tx = platform.drive.grove.start_transaction(); + let result = platform + .platform + .process_raw_state_transitions( + &[transition.serialize_to_bytes().unwrap()], + &platform_state, + &BlockInfo::default(), + &tx, + platform_version, + false, + None, + ) + .unwrap(); + + assert_matches!( + result.execution_results().as_slice(), + [StateTransitionExecutionResult::PaidConsensusError( + ConsensusError::StateError( + StateError::DataContractUpdateActionNotAllowedError(_) + ), + _ + )] + ); + } } mod keyword_updates { @@ -1945,7 +2538,7 @@ mod tests { &signer, &key, &["old1", "old2"], - &platform_version, + platform_version, ); // verify initial docs @@ -1960,14 +2553,14 @@ mod tests { &signer, &key, &["newA", "newB", "newC"], - &platform_version, + platform_version, ) .expect("update should succeed"); // fetch contract – keywords updated? let fetched = platform .drive - .fetch_contract(cid.into(), None, None, None, &platform_version) + .fetch_contract(cid.into(), None, None, None, platform_version) .value .unwrap() .unwrap(); @@ -1980,7 +2573,7 @@ mod tests { ); // search‑contract docs updated? - let docs_after = keyword_docs_for_contract(&platform, cid, &platform_version); + let docs_after = keyword_docs_for_contract(&platform, cid, platform_version); assert_eq!(docs_after.len(), 3); assert!(docs_after.contains(&"newA".to_string())); assert!(docs_after.contains(&"newB".to_string())); @@ -2314,7 +2907,7 @@ mod tests { ); // verify initial docs - let initial_docs = description_docs_for_contract(&platform, cid, &platform_version); + let initial_docs = description_docs_for_contract(&platform, cid, platform_version); assert_eq!(initial_docs, "old1".to_string()); // apply update to "newA" diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs index ab66daf9500..d7c534b1148 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs @@ -2,20 +2,34 @@ use crate::error::Error; use crate::platform_types::platform::PlatformRef; use crate::rpc::core::CoreRPCLike; use dpp::block::block_info::BlockInfo; +use std::collections::BTreeSet; use dpp::consensus::basic::document::DataContractNotPresentError; use dpp::consensus::basic::BasicError; +use dpp::consensus::state::identity::identity_for_token_configuration_not_found_error::{ + IdentityInTokenConfigurationNotFoundError, TokenConfigurationIdentityContext, +}; +use dpp::consensus::state::state_error::StateError; use dpp::data_contract::accessors::v0::DataContractV0Getters; use dpp::data_contract::accessors::v1::{DataContractV1Getters, DataContractV1Setters}; +use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; +use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; +use dpp::data_contract::associated_token::token_perpetual_distribution::distribution_recipient::TokenDistributionRecipient; +use dpp::data_contract::associated_token::token_perpetual_distribution::methods::v0::TokenPerpetualDistributionV0Accessors; +use dpp::data_contract::associated_token::token_pre_programmed_distribution::accessors::v0::TokenPreProgrammedDistributionV0Methods; +use dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use dpp::data_contract::validate_update::DataContractUpdateValidationMethodsV0; +use crate::error::execution::ExecutionError; +use crate::execution::validation::state_transition::ValidationMode; use dpp::prelude::ConsensusValidationResult; use dpp::state_transition::data_contract_update_transition::DataContractUpdateTransition; use dpp::version::PlatformVersion; use dpp::ProtocolError; - -use crate::error::execution::ExecutionError; -use crate::execution::validation::state_transition::ValidationMode; +use drive::drive::identity::key::fetch::KeyRequestType::LatestAuthenticationMasterKey; +use drive::drive::identity::key::fetch::{ + IdentityKeysRequest, OptionalSingleIdentityPublicKeyOutcome, +}; use drive::grovedb::TransactionArg; use drive::state_transition_action::contract::data_contract_update::DataContractUpdateTransitionAction; use drive::state_transition_action::system::bump_identity_data_contract_nonce_action::BumpIdentityDataContractNonceAction; @@ -152,6 +166,177 @@ impl DataContractUpdateStateTransitionStateValidationV0 for DataContractUpdateTr new_data_contract.set_created_at_block_height(old_data_contract.created_at_block_height()); new_data_contract.set_created_at_epoch(old_data_contract.created_at_epoch()); + let mut validated_identities = BTreeSet::new(); + + // Validate any newly added tokens + for (token_contract_position, token_configuration) in new_data_contract.tokens() { + if !old_data_contract + .tokens() + .contains_key(token_contract_position) + { + for (name, change_control_rules) in token_configuration.all_change_control_rules() { + if let AuthorizedActionTakers::Identity(identity_id) = + change_control_rules.authorized_to_make_change_action_takers() + { + // we need to make sure this identity exists + if !validated_identities.contains(identity_id) { + let maybe_key = platform + .drive + .fetch_identity_keys::( + IdentityKeysRequest { + identity_id: identity_id.to_buffer(), + request_type: LatestAuthenticationMasterKey, + limit: Some(1), + offset: None, + }, + tx, + platform_version, + )?; + + if maybe_key.is_none() { + let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( + BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( + self, + ), + ); + + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + old_data_contract.id(), + *token_contract_position, + TokenConfigurationIdentityContext::ChangeControlRule( + name.to_string(), + ), + *identity_id, + ), + ) + .into()], + )); + } else { + validated_identities.insert(*identity_id); + } + } + } + } + + if let Some(distribution) = token_configuration + .distribution_rules() + .perpetual_distribution() + { + if let TokenDistributionRecipient::Identity(identifier) = + distribution.distribution_recipient() + { + let maybe_balance = platform.drive.fetch_identity_balance( + identifier.to_buffer(), + tx, + platform_version, + )?; + + execution_context + .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( + BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( + self, + ), + ); + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + old_data_contract.id(), + *token_contract_position, + TokenConfigurationIdentityContext::PerpetualDistributionRecipient, + identifier, + ), + ) + .into()], + )); + } + } + } + + if let Some(distributions) = token_configuration + .distribution_rules() + .pre_programmed_distribution() + { + for distribution in distributions.distributions().values() { + for identifier in distribution.keys() { + let maybe_balance = platform.drive.fetch_identity_balance( + identifier.to_buffer(), + tx, + platform_version, + )?; + + execution_context + .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( + BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( + self, + ), + ); + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + old_data_contract.id(), + *token_contract_position, + TokenConfigurationIdentityContext::PreProgrammedDistributionRecipient, + *identifier, + ), + ) + .into()], + )); + } + } + } + } + + // We validate that if we set a minting distribution that this identity exists + // It can be an evonode, so we just use the balance as a check + + if let Some(minting_recipient) = token_configuration + .distribution_rules() + .new_tokens_destination_identity() + { + let maybe_balance = platform.drive.fetch_identity_balance( + minting_recipient.to_buffer(), + tx, + platform_version, + )?; + + execution_context + .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( + BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( + self, + ), + ); + + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + old_data_contract.id(), + *token_contract_position, + TokenConfigurationIdentityContext::DefaultMintingRecipient, + *minting_recipient, + ), + ) + .into()], + )); + } + } + } + } + Ok(action) } @@ -299,7 +484,7 @@ mod tests { if action.identity_id() == identity_id && action.identity_contract_nonce() == identity_contract_nonce && action.data_contract_id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -372,7 +557,7 @@ mod tests { if action.identity_id() == identity_id && action.identity_contract_nonce() == identity_contract_nonce && action.data_contract_id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -457,7 +642,7 @@ mod tests { if action.identity_id() == identity_id && action.identity_contract_nonce() == identity_contract_nonce && action.data_contract_id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -538,7 +723,7 @@ mod tests { if action.data_contract_ref().id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } } @@ -613,7 +798,7 @@ mod tests { if action.identity_id() == identity_id && action.identity_contract_nonce() == identity_contract_nonce && action.data_contract_id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -664,7 +849,7 @@ mod tests { Some(StateTransitionAction::DataContractUpdateAction(action)) if action.data_contract_ref().id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } } diff --git a/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs b/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs index 976b0c24cfa..18b2d17cf3b 100644 --- a/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/key/fetch/fetch_identity_keys/v0/mod.rs @@ -39,7 +39,7 @@ impl Drive { platform_version: &PlatformVersion, ) -> Result { match &key_request.request_type { - AllKeys | LatestAuthenticationMasterKey => { + AllKeys => { let path_query = key_request.into_path_query(); let (result, _) = self.grove_get_raw_path_query( @@ -81,7 +81,8 @@ impl Drive { } ContractBoundKey(_, _, KeyKindRequestType::AllKeysOfKindRequest) | ContractDocumentTypeBoundKey(_, _, _, KeyKindRequestType::AllKeysOfKindRequest) - | RecentWithdrawalKeys => { + | RecentWithdrawalKeys + | LatestAuthenticationMasterKey => { let path_query = key_request.into_path_query(); let (result, _) = self.grove_get_path_query( diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index ef17b9a4b20..065858f06c2 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -77,7 +77,7 @@ use dpp::consensus::state::document::document_contest_not_paid_for_error::Docume use dpp::consensus::state::document::document_incorrect_purchase_price_error::DocumentIncorrectPurchasePriceError; use dpp::consensus::state::document::document_not_for_sale_error::DocumentNotForSaleError; use dpp::consensus::state::group::{GroupActionAlreadyCompletedError, GroupActionAlreadySignedByIdentityError, GroupActionDoesNotExistError, IdentityNotMemberOfGroupError}; -use dpp::consensus::state::identity::identity_for_change_control_rule_not_found::IdentityForChangeControlRuleNotFoundError; +use dpp::consensus::state::identity::identity_for_token_configuration_not_found_error::IdentityInTokenConfigurationNotFoundError; use dpp::consensus::state::identity::identity_public_key_already_exists_for_unique_contract_bounds_error::IdentityPublicKeyAlreadyExistsForUniqueContractBoundsError; use dpp::consensus::state::identity::master_public_key_update_error::MasterPublicKeyUpdateError; use dpp::consensus::state::identity::missing_transfer_key_error::MissingTransferKeyError; @@ -409,8 +409,8 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::TokenNotForDirectSale(e) => { generic_consensus_error!(TokenNotForDirectSale, e).into() } - StateError::IdentityForChangeControlRuleNotFoundError(e) => { - generic_consensus_error!(IdentityForChangeControlRuleNotFoundError, e).into() + StateError::IdentityInTokenConfigurationNotFoundError(e) => { + generic_consensus_error!(IdentityInTokenConfigurationNotFoundError, e).into() } } } From 288e4f207e12bfa05e1e582feb58465fc42b6432 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 2 May 2025 00:57:56 +0700 Subject: [PATCH 3/5] fixed imports when dealing with features --- packages/rs-drive/src/drive/identity/key/fetch/mod.rs | 6 ++---- packages/rs-drive/src/drive/identity/mod.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/rs-drive/src/drive/identity/key/fetch/mod.rs b/packages/rs-drive/src/drive/identity/key/fetch/mod.rs index a645fa3a3b2..aeadeeff993 100644 --- a/packages/rs-drive/src/drive/identity/key/fetch/mod.rs +++ b/packages/rs-drive/src/drive/identity/key/fetch/mod.rs @@ -14,7 +14,7 @@ use { }, dpp::{ identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0, - identity::{KeyID, Purpose}, + identity::{KeyID, Purpose, SecurityLevel}, }, grovedb::{PathQuery, SizedQuery}, integer_encoding::VarInt, @@ -29,9 +29,7 @@ use crate::drive::identity::{ use { crate::error::{drive::DriveError, fee::FeeError, identity::IdentityError, Error}, dpp::{ - fee::Credits, - identity::{IdentityPublicKey, SecurityLevel}, - serialization::PlatformDeserializable, + fee::Credits, identity::IdentityPublicKey, serialization::PlatformDeserializable, version::PlatformVersion, }, grovedb::{ diff --git a/packages/rs-drive/src/drive/identity/mod.rs b/packages/rs-drive/src/drive/identity/mod.rs index a75be0ea563..922f0c6bbee 100644 --- a/packages/rs-drive/src/drive/identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/mod.rs @@ -262,7 +262,7 @@ pub(crate) fn identity_query_keys_purpose_tree_path_vec( } /// identity query keys security level tree path vec -#[cfg(feature = "server")] +#[cfg(any(feature = "server", feature = "verify"))] pub(crate) fn identity_query_keys_security_level_tree_path_vec( identity_id: &[u8], security_level: SecurityLevel, From 0c6e85c1d15de54894c630296fce98ecbed33028 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 2 May 2025 01:15:43 +0700 Subject: [PATCH 4/5] more fixes --- .../data_contract_create/state/v0/mod.rs | 131 +++++++++------- .../data_contract_update/state/v0/mod.rs | 145 ++++++++++-------- .../src/drive/identity/key/fetch/mod.rs | 7 +- packages/rs-drive/src/drive/identity/mod.rs | 8 +- 4 files changed, 158 insertions(+), 133 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs index b6bd9d9fbc6..f9c3b419ba6 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs @@ -135,40 +135,9 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr .distribution_rules() .new_tokens_destination_identity() { - let maybe_balance = platform.drive.fetch_identity_balance( - minting_recipient.to_buffer(), - tx, - platform_version, - )?; - - execution_context.add_operation(ValidationOperation::RetrieveIdentityTokenBalance); - - if maybe_balance.is_none() { - return Ok(ConsensusValidationResult::new_with_data_and_errors( - StateTransitionAction::BumpIdentityNonceAction( - BumpIdentityNonceAction::from_borrowed_data_contract_create_transition( - self, - ), - ), - vec![StateError::IdentityInTokenConfigurationNotFoundError( - IdentityInTokenConfigurationNotFoundError::new( - self.data_contract().id(), - *position, - TokenConfigurationIdentityContext::DefaultMintingRecipient, - *minting_recipient, - ), - ) - .into()], - )); - } - } - - if let Some(distribution) = config.distribution_rules().perpetual_distribution() { - if let TokenDistributionRecipient::Identity(identifier) = - distribution.distribution_recipient() - { + if !validated_identities.contains(minting_recipient) { let maybe_balance = platform.drive.fetch_identity_balance( - identifier.to_buffer(), + minting_recipient.to_buffer(), tx, platform_version, )?; @@ -187,32 +156,23 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr IdentityInTokenConfigurationNotFoundError::new( self.data_contract().id(), *position, - TokenConfigurationIdentityContext::PerpetualDistributionRecipient, - identifier, + TokenConfigurationIdentityContext::DefaultMintingRecipient, + *minting_recipient, ), ) .into()], )); + } else { + validated_identities.insert(*minting_recipient); } } } - if let Some(distribution) = config.distribution_rules().pre_programmed_distribution() { - if let Some((timestamp, _)) = distribution.distributions().iter().next() { - if timestamp < &block_info.time_ms { - return Ok(ConsensusValidationResult::new_with_data_and_errors( - StateTransitionAction::BumpIdentityNonceAction( - BumpIdentityNonceAction::from_borrowed_data_contract_create_transition(self), - ), - vec![StateError::PreProgrammedDistributionTimestampInPastError( - PreProgrammedDistributionTimestampInPastError::new(self.data_contract().id(), *position, *timestamp, block_info.time_ms), - ) - .into()], - )); - } - } - for distribution in distribution.distributions().values() { - for identifier in distribution.keys() { + if let Some(distribution) = config.distribution_rules().perpetual_distribution() { + if let TokenDistributionRecipient::Identity(identifier) = + distribution.distribution_recipient() + { + if !validated_identities.contains(&identifier) { let maybe_balance = platform.drive.fetch_identity_balance( identifier.to_buffer(), tx, @@ -233,12 +193,65 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr IdentityInTokenConfigurationNotFoundError::new( self.data_contract().id(), *position, - TokenConfigurationIdentityContext::PreProgrammedDistributionRecipient, - *identifier, + TokenConfigurationIdentityContext::PerpetualDistributionRecipient, + identifier, ), ) .into()], )); + } else { + validated_identities.insert(identifier); + } + } + } + } + + if let Some(distribution) = config.distribution_rules().pre_programmed_distribution() { + if let Some((timestamp, _)) = distribution.distributions().iter().next() { + if timestamp < &block_info.time_ms { + return Ok(ConsensusValidationResult::new_with_data_and_errors( + StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition(self), + ), + vec![StateError::PreProgrammedDistributionTimestampInPastError( + PreProgrammedDistributionTimestampInPastError::new(self.data_contract().id(), *position, *timestamp, block_info.time_ms), + ) + .into()], + )); + } + } + for distribution in distribution.distributions().values() { + for identifier in distribution.keys() { + if !validated_identities.contains(&identifier) { + let maybe_balance = platform.drive.fetch_identity_balance( + identifier.to_buffer(), + tx, + platform_version, + )?; + + execution_context + .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + return Ok(ConsensusValidationResult::new_with_data_and_errors( + StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition( + self, + ), + ), + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + self.data_contract().id(), + *position, + TokenConfigurationIdentityContext::PreProgrammedDistributionRecipient, + *identifier, + ), + ) + .into()], + )); + } else { + validated_identities.insert(*identifier); + } } } } @@ -435,7 +448,7 @@ mod tests { Some(StateTransitionAction::BumpIdentityNonceAction(action)) if action.identity_id() == identity_id && action.identity_nonce() == identity_nonce ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -528,7 +541,7 @@ mod tests { Some(StateTransitionAction::BumpIdentityNonceAction(action)) if action.identity_id() == identity_id && action.identity_nonce() == identity_nonce ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -608,7 +621,7 @@ mod tests { if action.identity_id() == identity_id && action.identity_nonce() == identity_nonce ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -671,7 +684,7 @@ mod tests { Some(StateTransitionAction::DataContractCreateAction(action)) if action.data_contract_ref().id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } } @@ -741,7 +754,7 @@ mod tests { Some(StateTransitionAction::BumpIdentityNonceAction(action)) if action.identity_id() == identity_id && action.identity_nonce() == identity_nonce ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } @@ -789,7 +802,7 @@ mod tests { Some(StateTransitionAction::DataContractCreateAction(action)) if action.data_contract_ref().id() == data_contract_id ); - // We have tons of operations here so not sure we want to assert all of them + // We have tons of operations here so not sure if we want to assert all of them assert!(!execution_context.operations_slice().is_empty()); } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs index d7c534b1148..7663d07ab19 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs @@ -228,43 +228,7 @@ impl DataContractUpdateStateTransitionStateValidationV0 for DataContractUpdateTr if let TokenDistributionRecipient::Identity(identifier) = distribution.distribution_recipient() { - let maybe_balance = platform.drive.fetch_identity_balance( - identifier.to_buffer(), - tx, - platform_version, - )?; - - execution_context - .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); - - if maybe_balance.is_none() { - let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( - BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( - self, - ), - ); - return Ok(ConsensusValidationResult::new_with_data_and_errors( - bump_action, - vec![StateError::IdentityInTokenConfigurationNotFoundError( - IdentityInTokenConfigurationNotFoundError::new( - old_data_contract.id(), - *token_contract_position, - TokenConfigurationIdentityContext::PerpetualDistributionRecipient, - identifier, - ), - ) - .into()], - )); - } - } - } - - if let Some(distributions) = token_configuration - .distribution_rules() - .pre_programmed_distribution() - { - for distribution in distributions.distributions().values() { - for identifier in distribution.keys() { + if !validated_identities.contains(&identifier) { let maybe_balance = platform.drive.fetch_identity_balance( identifier.to_buffer(), tx, @@ -286,12 +250,57 @@ impl DataContractUpdateStateTransitionStateValidationV0 for DataContractUpdateTr IdentityInTokenConfigurationNotFoundError::new( old_data_contract.id(), *token_contract_position, - TokenConfigurationIdentityContext::PreProgrammedDistributionRecipient, - *identifier, + TokenConfigurationIdentityContext::PerpetualDistributionRecipient, + identifier, ), ) .into()], )); + } else { + validated_identities.insert(identifier); + } + } + } + } + + if let Some(distributions) = token_configuration + .distribution_rules() + .pre_programmed_distribution() + { + for distribution in distributions.distributions().values() { + for identifier in distribution.keys() { + if !validated_identities.contains(&identifier) { + let maybe_balance = platform.drive.fetch_identity_balance( + identifier.to_buffer(), + tx, + platform_version, + )?; + + execution_context.add_operation( + ValidationOperation::RetrieveIdentityTokenBalance, + ); + + if maybe_balance.is_none() { + let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( + BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( + self, + ), + ); + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + old_data_contract.id(), + *token_contract_position, + TokenConfigurationIdentityContext::PreProgrammedDistributionRecipient, + *identifier, + ), + ) + .into()], + )); + } else { + validated_identities.insert(*identifier); + } } } } @@ -304,34 +313,38 @@ impl DataContractUpdateStateTransitionStateValidationV0 for DataContractUpdateTr .distribution_rules() .new_tokens_destination_identity() { - let maybe_balance = platform.drive.fetch_identity_balance( - minting_recipient.to_buffer(), - tx, - platform_version, - )?; - - execution_context - .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); - - if maybe_balance.is_none() { - let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( - BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( - self, - ), - ); - - return Ok(ConsensusValidationResult::new_with_data_and_errors( - bump_action, - vec![StateError::IdentityInTokenConfigurationNotFoundError( - IdentityInTokenConfigurationNotFoundError::new( - old_data_contract.id(), - *token_contract_position, - TokenConfigurationIdentityContext::DefaultMintingRecipient, - *minting_recipient, + if !validated_identities.contains(minting_recipient) { + let maybe_balance = platform.drive.fetch_identity_balance( + minting_recipient.to_buffer(), + tx, + platform_version, + )?; + + execution_context + .add_operation(ValidationOperation::RetrieveIdentityTokenBalance); + + if maybe_balance.is_none() { + let bump_action = StateTransitionAction::BumpIdentityDataContractNonceAction( + BumpIdentityDataContractNonceAction::from_borrowed_data_contract_update_transition( + self, ), - ) - .into()], - )); + ); + + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![StateError::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + old_data_contract.id(), + *token_contract_position, + TokenConfigurationIdentityContext::DefaultMintingRecipient, + *minting_recipient, + ), + ) + .into()], + )); + } else { + validated_identities.insert(*minting_recipient); + } } } } diff --git a/packages/rs-drive/src/drive/identity/key/fetch/mod.rs b/packages/rs-drive/src/drive/identity/key/fetch/mod.rs index aeadeeff993..fc86be91e84 100644 --- a/packages/rs-drive/src/drive/identity/key/fetch/mod.rs +++ b/packages/rs-drive/src/drive/identity/key/fetch/mod.rs @@ -4,7 +4,8 @@ use { crate::{ drive::identity::{ identity_contract_info_group_path_key_purpose_vec, identity_key_tree_path_vec, - identity_query_keys_tree_path_vec, + identity_query_keys_security_level_tree_path_vec, identity_query_keys_tree_path_vec, + identity_transfer_keys_path_vec, key::fetch::KeyKindRequestType::{AllKeysOfKindRequest, CurrentKeyOfKindRequest}, key::fetch::KeyRequestType::{ AllKeys, ContractBoundKey, ContractDocumentTypeBoundKey, SearchKey, SpecificKeys, @@ -21,10 +22,6 @@ use { std::{collections::BTreeMap, ops::RangeFull}, }; -// Conditional imports for the feature "server" -use crate::drive::identity::{ - identity_query_keys_security_level_tree_path_vec, identity_transfer_keys_path_vec, -}; #[cfg(feature = "server")] use { crate::error::{drive::DriveError, fee::FeeError, identity::IdentityError, Error}, diff --git a/packages/rs-drive/src/drive/identity/mod.rs b/packages/rs-drive/src/drive/identity/mod.rs index 922f0c6bbee..c4bcc1802dd 100644 --- a/packages/rs-drive/src/drive/identity/mod.rs +++ b/packages/rs-drive/src/drive/identity/mod.rs @@ -9,16 +9,18 @@ use crate::drive::RootTree; use crate::util::object_size_info::DriveKeyInfo; use std::fmt; +#[cfg(feature = "server")] +use dpp::identity::KeyID; #[cfg(any(feature = "server", feature = "verify"))] use dpp::identity::Purpose; -#[cfg(feature = "server")] -use dpp::identity::{KeyID, SecurityLevel}; +#[cfg(any(feature = "server", feature = "verify"))] +use dpp::identity::SecurityLevel; #[cfg(feature = "server")] /// Everything related to withdrawals pub mod withdrawals; -#[cfg(feature = "server")] +#[cfg(any(feature = "server", feature = "verify"))] use dpp::identity::Purpose::AUTHENTICATION; #[cfg(feature = "server")] use integer_encoding::VarInt; From 252183d6dae2d1096459a3d5def0a66113f5e6bf Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 2 May 2025 01:23:48 +0700 Subject: [PATCH 5/5] clippy fix --- .../state_transitions/data_contract_create/state/v0/mod.rs | 2 +- .../state_transitions/data_contract_update/state/v0/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs index f9c3b419ba6..8df664159cd 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/state/v0/mod.rs @@ -222,7 +222,7 @@ impl DataContractCreateStateTransitionStateValidationV0 for DataContractCreateTr } for distribution in distribution.distributions().values() { for identifier in distribution.keys() { - if !validated_identities.contains(&identifier) { + if !validated_identities.contains(identifier) { let maybe_balance = platform.drive.fetch_identity_balance( identifier.to_buffer(), tx, diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs index 7663d07ab19..e02813fc755 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/state/v0/mod.rs @@ -269,7 +269,7 @@ impl DataContractUpdateStateTransitionStateValidationV0 for DataContractUpdateTr { for distribution in distributions.distributions().values() { for identifier in distribution.keys() { - if !validated_identities.contains(&identifier) { + if !validated_identities.contains(identifier) { let maybe_balance = platform.drive.fetch_identity_balance( identifier.to_buffer(), tx,