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 53aa0098976..7f6c78dbe97 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 0dd315cd4a2..c78b6bcf5f8 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 @@ -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 5466529cc63..26adb849ec5 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..a9dd5022c87 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::IdentityInTokenConfigurationNotFoundError(_) => 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_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 072bbda4ac3..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,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_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 8151d017fee..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,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_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; @@ -298,6 +299,9 @@ pub enum StateError { #[error(transparent)] TokenNotForDirectSale(TokenNotForDirectSale), + + #[error(transparent)] + IdentityInTokenConfigurationNotFoundError(IdentityInTokenConfigurationNotFoundError), } 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..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 @@ -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,10 @@ 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; + 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( ) { @@ -1427,7 +1536,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 +1647,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 +1761,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 +1797,348 @@ 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::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(_) + ), + _ + )] + ); + + 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 +2238,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 +2587,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 +2717,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 +2845,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 +2974,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 +3102,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 +3230,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 +3957,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..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 @@ -2,25 +2,36 @@ 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_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; use dpp::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0; use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; 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, }; 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 +78,134 @@ 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() { + // 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() + { + // 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, + )?; + + 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::IdentityInTokenConfigurationNotFoundError( + IdentityInTokenConfigurationNotFoundError::new( + self.data_contract().id(), + *position, + TokenConfigurationIdentityContext::ChangeControlRule(name.to_string()), + *identity_id, + ), + ) + .into()], + )); + } else { + validated_identities.insert(*identity_id); + } + } + } + } + // 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() + { + 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() { + 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()], + )); + } else { + validated_identities.insert(*minting_recipient); + } + } + } + + 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, + 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()], + )); + } 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 { @@ -79,10 +216,45 @@ 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() { + 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); + } + } + } + } } } @@ -276,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()); } @@ -369,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()); } @@ -449,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()); } @@ -512,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()); } } @@ -582,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()); } @@ -630,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/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..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 @@ -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,190 @@ 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() + { + 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::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); + } + } + } + } + } + + // 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() + { + 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, + ), + ); + + 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); + } + } + } + } + } + Ok(action) } @@ -299,7 +497,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 +570,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 +655,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 +736,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 +811,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 +862,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 877634eec00..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 @@ -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, @@ -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/rs-drive/src/drive/identity/key/fetch/mod.rs b/packages/rs-drive/src/drive/identity/key/fetch/mod.rs index 44d104288a5..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, @@ -14,22 +15,18 @@ use { }, dpp::{ identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0, - identity::{KeyID, Purpose}, + identity::{KeyID, Purpose, SecurityLevel}, }, grovedb::{PathQuery, SizedQuery}, integer_encoding::VarInt, std::{collections::BTreeMap, ops::RangeFull}, }; -// Conditional imports for the feature "server" -use crate::drive::identity::identity_transfer_keys_path_vec; #[cfg(feature = "server")] 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::{ @@ -81,6 +78,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 +272,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 +326,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 +376,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 +402,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 +428,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 +454,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 +480,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 +558,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 +679,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 +724,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 +868,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 +879,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 +953,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 +982,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 +993,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 +1021,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/rs-drive/src/drive/identity/mod.rs b/packages/rs-drive/src/drive/identity/mod.rs index a75be0ea563..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; @@ -262,7 +264,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, diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 65823940f40..065858f06c2 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_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; @@ -408,6 +409,9 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::TokenNotForDirectSale(e) => { generic_consensus_error!(TokenNotForDirectSale, e).into() } + StateError::IdentityInTokenConfigurationNotFoundError(e) => { + generic_consensus_error!(IdentityInTokenConfigurationNotFoundError, e).into() + } } }