From 39e6d5d58df1b7442949da3dd83b77fddc01873c Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 3 Jan 2025 06:09:30 +0700 Subject: [PATCH] more tests --- .../data_contract/serialized_version/mod.rs | 20 +- .../src/data_contract/v1/accessors/mod.rs | 10 +- .../src/errors/consensus/basic/basic_error.rs | 19 +- .../invalid_token_base_supply_error.rs | 41 +++ .../consensus/basic/data_contract/mod.rs | 6 + ...ntiguous_contract_group_positions_error.rs | 52 ++++ ...ntiguous_contract_token_positions_error.rs | 52 ++++ packages/rs-dpp/src/errors/consensus/codes.rs | 3 + .../token_base_transition/v0/v0_methods.rs | 10 +- packages/rs-dpp/src/tokens/mod.rs | 9 + .../advanced_structure/v0/mod.rs | 36 ++- .../data_contract_create/mod.rs | 284 ++++++++++++++++++ .../tests/strategy_tests/strategy.rs | 11 + .../tests/strategy_tests/token_tests.rs | 153 +++++++++- .../contract/insert/insert_contract/v1/mod.rs | 87 +++++- .../add_to_previous_token_balance/mod.rs | 3 +- .../add_to_token_total_supply/v0/mod.rs | 9 +- .../remove_from_token_total_supply/v0/mod.rs | 9 +- packages/strategy-tests/src/operations.rs | 8 +- 19 files changed, 782 insertions(+), 40 deletions(-) create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_base_supply_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_group_positions_error.rs create mode 100644 packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_token_positions_error.rs diff --git a/packages/rs-dpp/src/data_contract/serialized_version/mod.rs b/packages/rs-dpp/src/data_contract/serialized_version/mod.rs index fce0a9eb67..a88d93413b 100644 --- a/packages/rs-dpp/src/data_contract/serialized_version/mod.rs +++ b/packages/rs-dpp/src/data_contract/serialized_version/mod.rs @@ -1,8 +1,13 @@ use crate::data_contract::serialized_version::v0::DataContractInSerializationFormatV0; -use crate::data_contract::{DataContract, DefinitionName, DocumentName}; +use crate::data_contract::{ + DataContract, DefinitionName, DocumentName, GroupContractPosition, TokenContractPosition, + EMPTY_GROUPS, EMPTY_TOKENS, +}; use crate::version::PlatformVersion; use std::collections::BTreeMap; +use crate::data_contract::associated_token::token_configuration::TokenConfiguration; +use crate::data_contract::group::Group; use crate::data_contract::serialized_version::v1::DataContractInSerializationFormatV1; use crate::data_contract::v0::DataContractV0; use crate::data_contract::v1::DataContractV1; @@ -78,6 +83,19 @@ impl DataContractInSerializationFormat { DataContractInSerializationFormat::V1(v1) => v1.version, } } + + pub fn groups(&self) -> &BTreeMap { + match self { + DataContractInSerializationFormat::V0(_) => &EMPTY_GROUPS, + DataContractInSerializationFormat::V1(v1) => &v1.groups, + } + } + pub fn tokens(&self) -> &BTreeMap { + match self { + DataContractInSerializationFormat::V0(_) => &EMPTY_TOKENS, + DataContractInSerializationFormat::V1(v1) => &v1.tokens, + } + } } impl TryFromPlatformVersioned for DataContractInSerializationFormat { diff --git a/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs b/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs index a198712976..562c6cc118 100644 --- a/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs +++ b/packages/rs-dpp/src/data_contract/v1/accessors/mod.rs @@ -11,6 +11,7 @@ use crate::data_contract::accessors::v1::{DataContractV1Getters, DataContractV1S use crate::data_contract::associated_token::token_configuration::TokenConfiguration; use crate::data_contract::document_type::accessors::DocumentTypeV0Getters; use crate::data_contract::group::Group; +use crate::tokens::calculate_token_id; use crate::util::hash::hash_double; use crate::ProtocolError; use platform_value::Identifier; @@ -173,12 +174,9 @@ impl DataContractV1Getters for DataContractV1 { /// Returns the token id if a token exists at that position fn token_id(&self, position: TokenContractPosition) -> Option { - self.tokens.get(&position).map(|_| { - let mut bytes = b"dash_token".to_vec(); - bytes.extend_from_slice(self.id().as_bytes()); - bytes.extend_from_slice(&position.to_be_bytes()); - hash_double(bytes).into() - }) + self.tokens + .get(&position) + .map(|_| calculate_token_id(self.id.as_bytes(), position).into()) } } diff --git a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs index 668019c63d..df42e05e3f 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -15,10 +15,12 @@ use crate::consensus::basic::data_contract::{ IncompatibleRe2PatternError, InvalidCompoundIndexError, InvalidDataContractIdError, InvalidDataContractVersionError, InvalidDocumentTypeNameError, InvalidDocumentTypeRequiredSecurityLevelError, InvalidIndexPropertyTypeError, - InvalidIndexedPropertyConstraintError, SystemPropertyIndexAlreadyPresentError, - UndefinedIndexPropertyError, UniqueIndicesLimitReachedError, - UnknownDocumentCreationRestrictionModeError, UnknownSecurityLevelError, - UnknownStorageKeyRequirementsError, UnknownTradeModeError, UnknownTransferableTypeError, + InvalidIndexedPropertyConstraintError, InvalidTokenBaseSupplyError, + NonContiguousContractGroupPositionsError, NonContiguousContractTokenPositionsError, + SystemPropertyIndexAlreadyPresentError, UndefinedIndexPropertyError, + UniqueIndicesLimitReachedError, UnknownDocumentCreationRestrictionModeError, + UnknownSecurityLevelError, UnknownStorageKeyRequirementsError, UnknownTradeModeError, + UnknownTransferableTypeError, }; use crate::consensus::basic::decode::{ ProtocolVersionParsingError, SerializedObjectParsingError, VersionError, @@ -199,6 +201,15 @@ pub enum BasicError { #[error(transparent)] DataContractHaveNewUniqueIndexError(DataContractHaveNewUniqueIndexError), + #[error(transparent)] + NonContiguousContractTokenPositionsError(NonContiguousContractTokenPositionsError), + + #[error(transparent)] + NonContiguousContractGroupPositionsError(NonContiguousContractGroupPositionsError), + + #[error(transparent)] + InvalidTokenBaseSupplyError(InvalidTokenBaseSupplyError), + // Document #[error(transparent)] DataContractNotPresentError(DataContractNotPresentError), diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_base_supply_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_base_supply_error.rs new file mode 100644 index 0000000000..738be8b222 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/invalid_token_base_supply_error.rs @@ -0,0 +1,41 @@ +use crate::consensus::basic::BasicError; +use crate::errors::ProtocolError; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +use crate::consensus::ConsensusError; + +use bincode::{Decode, Encode}; +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error( + "Invalid token base supply. Given base supply: {}, Max allowed base supply: {}", + base_supply, + i64::MAX +)] +#[platform_serialize(unversioned)] +pub struct InvalidTokenBaseSupplyError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + base_supply: u64, +} + +impl InvalidTokenBaseSupplyError { + pub fn new(base_supply: u64) -> Self { + Self { base_supply } + } + + pub fn base_supply(&self) -> u64 { + self.base_supply + } +} + +impl From for ConsensusError { + fn from(err: InvalidTokenBaseSupplyError) -> Self { + Self::BasicError(BasicError::InvalidTokenBaseSupplyError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs index 1553f17998..2fc29ce57f 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/mod.rs @@ -21,6 +21,9 @@ mod invalid_index_property_type_error; mod invalid_indexed_property_constraint_error; #[cfg(feature = "json-schema-validation")] mod invalid_json_schema_ref_error; +mod invalid_token_base_supply_error; +mod non_contiguous_contract_group_positions_error; +mod non_contiguous_contract_token_positions_error; mod system_property_index_already_present_error; mod undefined_index_property_error; mod unique_indices_limit_reached_error; @@ -57,6 +60,9 @@ pub use contested_unique_index_on_mutable_document_type_error::*; pub use contested_unique_index_with_unique_index_error::*; pub use incompatible_document_type_schema_error::*; pub use invalid_document_type_name_error::*; +pub use invalid_token_base_supply_error::*; +pub use non_contiguous_contract_group_positions_error::*; +pub use non_contiguous_contract_token_positions_error::*; pub use unknown_document_creation_restriction_mode_error::*; pub use unknown_security_level_error::*; pub use unknown_storage_key_requirements_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_group_positions_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_group_positions_error.rs new file mode 100644 index 0000000000..3409e866f8 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_group_positions_error.rs @@ -0,0 +1,52 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::data_contract::GroupContractPosition; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error( + "Contract Group Positions are not contiguous. Missing position: {}, Followed position: {}", + missing_position, + followed_position +)] +#[platform_serialize(unversioned)] +pub struct NonContiguousContractGroupPositionsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + missing_position: GroupContractPosition, + followed_position: GroupContractPosition, +} + +impl NonContiguousContractGroupPositionsError { + pub fn new( + missing_position: GroupContractPosition, + followed_position: GroupContractPosition, + ) -> Self { + Self { + missing_position, + followed_position, + } + } + + pub fn missing_position(&self) -> u16 { + self.missing_position + } + + pub fn followed_position(&self) -> u16 { + self.followed_position + } +} + +impl From for ConsensusError { + fn from(err: NonContiguousContractGroupPositionsError) -> Self { + Self::BasicError(BasicError::NonContiguousContractGroupPositionsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_token_positions_error.rs b/packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_token_positions_error.rs new file mode 100644 index 0000000000..2875f2bb42 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/data_contract/non_contiguous_contract_token_positions_error.rs @@ -0,0 +1,52 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::data_contract::TokenContractPosition; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error( + "Contract Token Positions are not contiguous. Missing position: {}, Followed position: {}", + missing_position, + followed_position +)] +#[platform_serialize(unversioned)] +pub struct NonContiguousContractTokenPositionsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + missing_position: TokenContractPosition, + followed_position: TokenContractPosition, +} + +impl NonContiguousContractTokenPositionsError { + pub fn new( + missing_position: TokenContractPosition, + followed_position: TokenContractPosition, + ) -> Self { + Self { + missing_position, + followed_position, + } + } + + pub fn missing_position(&self) -> u16 { + self.missing_position + } + + pub fn followed_position(&self) -> u16 { + self.followed_position + } +} + +impl From for ConsensusError { + fn from(err: NonContiguousContractTokenPositionsError) -> Self { + Self::BasicError(BasicError::NonContiguousContractTokenPositionsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index cca9925917..aea1729769 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -99,6 +99,9 @@ impl ErrorWithCode for BasicError { Self::ContestedUniqueIndexOnMutableDocumentTypeError(_) => 10248, Self::ContestedUniqueIndexWithUniqueIndexError(_) => 10249, Self::DataContractTokenConfigurationUpdateError { .. } => 10250, + Self::InvalidTokenBaseSupplyError(_) => 10251, + Self::NonContiguousContractGroupPositionsError(_) => 10252, + Self::NonContiguousContractTokenPositionsError(_) => 10253, // Document Errors: 10400-10449 Self::DataContractNotPresentError { .. } => 10400, diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_base_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_base_transition/v0/v0_methods.rs index d98cd2f4d2..65b1cda7d0 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_base_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_base_transition/v0/v0_methods.rs @@ -2,6 +2,7 @@ use crate::data_contract::GroupContractPosition; use crate::group::GroupStateTransitionInfo; use crate::prelude::IdentityNonce; use crate::state_transition::batch_transition::token_base_transition::v0::TokenBaseTransitionV0; +use crate::tokens::calculate_token_id; use crate::util::hash::hash_double; use platform_value::Identifier; @@ -19,10 +20,11 @@ pub trait TokenBaseTransitionV0Methods { /// Calculates the token ID. fn calculate_token_id(&self) -> Identifier { - let mut bytes = b"token".to_vec(); - bytes.extend_from_slice(self.data_contract_id().as_bytes()); - bytes.extend_from_slice(&self.token_contract_position().to_be_bytes()); - hash_double(bytes).into() + calculate_token_id( + self.data_contract_id().as_bytes(), + self.token_contract_position(), + ) + .into() } /// Returns the token ID. diff --git a/packages/rs-dpp/src/tokens/mod.rs b/packages/rs-dpp/src/tokens/mod.rs index f5325ea437..c9d7ace118 100644 --- a/packages/rs-dpp/src/tokens/mod.rs +++ b/packages/rs-dpp/src/tokens/mod.rs @@ -1,3 +1,12 @@ +use crate::data_contract::TokenContractPosition; +use crate::util::hash::hash_double; + pub mod allowed_currency; pub mod errors; pub mod token_event; +pub fn calculate_token_id(contract_id: &[u8; 32], token_pos: TokenContractPosition) -> [u8; 32] { + let mut bytes = b"dash_token".to_vec(); + bytes.extend_from_slice(contract_id); + bytes.extend_from_slice(&token_pos.to_be_bytes()); + hash_double(bytes) +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs index 9bdc1119eb..6d81da251c 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/advanced_structure/v0/mod.rs @@ -4,9 +4,11 @@ use crate::execution::types::state_transition_execution_context::{ StateTransitionExecutionContext, StateTransitionExecutionContextMethodsV0, }; use dpp::consensus::basic::data_contract::{ - InvalidDataContractIdError, InvalidDataContractVersionError, + InvalidDataContractIdError, InvalidDataContractVersionError, InvalidTokenBaseSupplyError, + NonContiguousContractTokenPositionsError, }; use dpp::consensus::basic::BasicError; +use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::INITIAL_DATA_CONTRACT_VERSION; use dpp::prelude::DataContract; use dpp::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0; @@ -67,6 +69,38 @@ impl DataContractCreatedStateTransitionAdvancedStructureValidationV0 )); } + let expected_position = 0; + + for (token_contract_position, token_configuration) in self.data_contract().tokens() { + if expected_position != *token_contract_position { + let bump_action = StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition(self), + ); + + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![NonContiguousContractTokenPositionsError::new( + expected_position, + *token_contract_position, + ) + .into()], + )); + } + + if token_configuration.base_supply() > i64::MAX as u64 { + let bump_action = StateTransitionAction::BumpIdentityNonceAction( + BumpIdentityNonceAction::from_borrowed_data_contract_create_transition(self), + ); + + return Ok(ConsensusValidationResult::new_with_data_and_errors( + bump_action, + vec![ + InvalidTokenBaseSupplyError::new(token_configuration.base_supply()).into(), + ], + )); + } + } + Ok(ConsensusValidationResult::default()) } } 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 c22ef31de1..487841aae3 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 @@ -149,11 +149,16 @@ mod tests { use dpp::consensus::basic::BasicError; use dpp::consensus::ConsensusError; use dpp::dash_to_credits; + use dpp::data_contract::accessors::v1::DataContractV1Getters; + use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Setters; + use dpp::data_contract::DataContract; + use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dpp::serialization::PlatformSerializable; use dpp::state_transition::data_contract_create_transition::methods::DataContractCreateTransitionMethodsV0; use dpp::state_transition::data_contract_create_transition::DataContractCreateTransition; use dpp::tests::json_document::json_document_to_contract_with_ids; + use dpp::tokens::calculate_token_id; use platform_version::version::PlatformVersion; #[test] @@ -347,4 +352,283 @@ mod tests { .unwrap() .expect("expected to commit transaction"); } + + #[test] + fn test_data_contract_creation_with_single_token() { + 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 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); + } + + 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( + &vec![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"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id, + identity_id.to_buffer(), + true, + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, None); + } + + #[test] + fn test_data_contract_creation_with_single_token_with_starting_balance() { + 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 base_supply_start_amount = 10000; + + { + 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); + } + + let identity_id = identity.id(); + + let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1); + + 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 token_id = calculate_token_id(data_contract_id.as_bytes(), 0); + + 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( + &vec![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"); + + let token_balance = platform + .drive + .fetch_identity_token_balance( + token_id, + identity_id.to_buffer(), + true, + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, Some(base_supply_start_amount)); + } + + #[test] + fn test_data_contract_creation_with_single_token_with_starting_balance_over_limit_should_cause_error( + ) { + 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 base_supply_start_amount = u64::MAX; + + { + 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); + } + + let identity_id = identity.id(); + + let data_contract_id = DataContract::generate_data_contract_id_v0(identity_id, 1); + + 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 token_id = calculate_token_id(data_contract_id.as_bytes(), 0); + + 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( + &vec![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::BasicError(BasicError::InvalidTokenBaseSupplyError(_)), + _ + )] + ); + + 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(), + true, + None, + platform_version, + ) + .expect("expected to fetch token balance"); + assert_eq!(token_balance, None); + } } diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs index 578c279924..9dbbd3a1ed 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs @@ -63,6 +63,7 @@ use dpp::state_transition::data_contract_create_transition::methods::v0::DataCon use dpp::state_transition::data_contract_update_transition::methods::DataContractUpdateTransitionMethodsV0; use dpp::state_transition::masternode_vote_transition::methods::MasternodeVoteTransitionMethodsV0; use dpp::state_transition::masternode_vote_transition::MasternodeVoteTransition; +use dpp::tokens::calculate_token_id; use dpp::tokens::token_event::TokenEvent; use dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice; use dpp::voting::vote_polls::VotePoll; @@ -495,6 +496,15 @@ impl NetworkStrategy { .expect("document type must exist") .to_owned_document_type(); } + } else if let OperationType::Token(token_op) = &mut operation.op_type { + if token_op.contract.id() == old_id { + token_op.contract.set_id(contract.id()); + token_op.token_id = calculate_token_id( + contract.id_ref().as_bytes(), + token_op.token_pos, + ) + .into(); + } } }); @@ -1416,6 +1426,7 @@ impl NetworkStrategy { OperationType::Token(TokenOp { contract, token_id, + token_pos, action: TokenEvent::Mint(amount, note), }) if current_identities.len() > 1 => { let random_index = rng.gen_range(0..current_identities.len()); diff --git a/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs index 457d85f48f..d253aef146 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs @@ -3,8 +3,12 @@ mod tests { use crate::execution::run_chain_for_strategy; use crate::strategy::NetworkStrategy; use dpp::dash_to_duffs; + use dpp::data_contract::accessors::v0::DataContractV0Setters; use dpp::data_contract::accessors::v1::DataContractV1Getters; + use dpp::identity::accessors::IdentityGettersV0; use dpp::identity::Identity; + use dpp::platform_value::string_encoding::Encoding; + use dpp::prelude::Identifier; use dpp::state_transition::StateTransition; use dpp::tests::json_document::json_document_to_created_contract; use dpp::tokens::token_event::TokenEvent; @@ -23,7 +27,133 @@ mod tests { use strategy_tests::{IdentityInsertInfo, StartIdentities, Strategy}; #[test] - fn run_chain_insert_one_new_identity_per_block_and_a_token_transfer_with_epoch_change() { + fn run_chain_insert_one_new_identity_per_block_and_a_token_mint() { + let platform_version = PlatformVersion::latest(); + let mut created_contract = json_document_to_created_contract( + "tests/supporting_files/contract/basic-token/basic-token.json", + 1, + true, + platform_version, + ) + .expect("expected to get contract from a json document"); + + let mut rng = StdRng::seed_from_u64(567); + + let mut simple_signer = SimpleSigner::default(); + + let (identity1, keys1) = + Identity::random_identity_with_main_keys_with_private_key::>( + 2, + &mut rng, + platform_version, + ) + .unwrap(); + + let (identity2, keys2) = + Identity::random_identity_with_main_keys_with_private_key::>( + 2, + &mut rng, + platform_version, + ) + .unwrap(); + + simple_signer.add_keys(keys1); + simple_signer.add_keys(keys2); + + let start_identities: Vec<(Identity, Option)> = + create_state_transitions_for_identities( + vec![identity1.clone(), identity2.clone()], + &(dash_to_duffs!(1)..=dash_to_duffs!(1)), + &simple_signer, + &mut rng, + platform_version, + ) + .into_iter() + .map(|(identity, transition)| (identity, Some(transition))) + .collect(); + + let contract = created_contract.data_contract_mut(); + let token_id = contract.token_id(0).expect("expected to get token_id"); + + let token_op = TokenOp { + contract: contract.clone(), + token_id, + token_pos: 0, + action: TokenEvent::Mint(1000, None), + }; + + let strategy = NetworkStrategy { + strategy: Strategy { + start_contracts: vec![(created_contract, None)], + operations: vec![Operation { + op_type: OperationType::Token(token_op), + frequency: Frequency { + times_per_block_range: 1..2, + chance_per_block: None, + }, + }], + start_identities: StartIdentities { + hard_coded: start_identities.clone(), + ..Default::default() + }, + identity_inserts: IdentityInsertInfo::default(), + + identity_contract_nonce_gaps: None, + signer: Some(simple_signer), + }, + total_hpmns: 100, + extra_normal_mns: 0, + validator_quorum_count: 24, + chain_lock_quorum_count: 24, + upgrading_info: None, + + proposer_strategy: Default::default(), + rotate_quorums: false, + failure_testing: None, + query_testing: None, + verify_state_transition_results: true, + ..Default::default() + }; + let day_in_ms = 1000 * 60 * 60 * 24; + let config = PlatformConfig { + validator_set: ValidatorSetConfig::default_100_67(), + chain_lock: ChainLockConfig::default_100_67(), + instant_lock: InstantLockConfig::default_100_67(), + execution: ExecutionConfig { + verify_sum_trees: true, + + ..Default::default() + }, + block_spacing_ms: day_in_ms, + testing_configs: PlatformTestConfig::default_minimal_verifications(), + ..Default::default() + }; + let block_count = 12; + let mut platform = TestPlatformBuilder::new() + .with_config(config.clone()) + .build_with_mock_rpc(); + + let outcome = + run_chain_for_strategy(&mut platform, block_count, strategy, config, 15, &mut None); + + println!("{:?}", &outcome.state_transition_results_per_block); + + let drive = &outcome.abci_app.platform.drive; + let identity_ids = vec![identity1.id().to_buffer(), identity2.id().to_buffer()]; + let balances = drive + .fetch_identities_token_balances( + token_id.to_buffer(), + identity_ids.as_slice(), + None, + platform_version, + ) + .expect("expected to get balances"); + + println!("{:?}", balances); + } + + #[test] + fn run_chain_insert_one_new_identity_per_block_and_a_token_transfer() { let platform_version = PlatformVersion::latest(); let created_contract = json_document_to_created_contract( "tests/supporting_files/contract/basic-token/basic-token.json", @@ -58,7 +188,7 @@ mod tests { let start_identities: Vec<(Identity, Option)> = create_state_transitions_for_identities( - vec![identity1, identity2], + vec![identity1.clone(), identity2.clone()], &(dash_to_duffs!(1)..=dash_to_duffs!(1)), &simple_signer, &mut rng, @@ -70,9 +200,12 @@ mod tests { let contract = created_contract.data_contract(); + let token_id = contract.token_id(0).expect("expected to get token_id"); + let token_op = TokenOp { contract: contract.clone(), - token_id: contract.token_id(0).expect("expected to get token_id"), + token_id, + token_pos: 0, action: TokenEvent::Mint(1000, None), }; @@ -129,6 +262,18 @@ mod tests { let outcome = run_chain_for_strategy(&mut platform, block_count, strategy, config, 15, &mut None); - println!("ye") + + let drive = &outcome.abci_app.platform.drive; + let identity_ids = vec![identity1.id().to_buffer(), identity2.id().to_buffer()]; + let balances = drive + .fetch_identities_token_balances( + token_id.to_buffer(), + identity_ids.as_slice(), + None, + platform_version, + ) + .expect("expected to get balances"); + + println!("{:?}", balances); } } diff --git a/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs b/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs index b28f13f481..e0f615f470 100644 --- a/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs +++ b/packages/rs-drive/src/drive/contract/insert/insert_contract/v1/mod.rs @@ -9,15 +9,22 @@ use dpp::data_contract::config::v0::DataContractConfigGettersV0; use dpp::data_contract::DataContract; use dpp::fee::fee_result::FeeResult; -use crate::drive::balances::total_tokens_root_supply_path; -use crate::drive::tokens::{token_path, tokens_root_path, TOKEN_BALANCES_KEY}; +use crate::drive::balances::total_tokens_root_supply_path_vec; +use crate::drive::tokens::{ + token_balances_path_vec, token_path, tokens_root_path, TOKEN_BALANCES_KEY, +}; use crate::error::contract::DataContractError; use crate::util::object_size_info::DriveKeyInfo; +use crate::util::object_size_info::PathKeyElementInfo::PathKeyElement; use dpp::data_contract::accessors::v1::DataContractV1Getters; +use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::serialization::PlatformSerializableWithPlatformVersion; use dpp::version::PlatformVersion; +use dpp::ProtocolError; use grovedb::batch::KeyInfoPath; +use grovedb::Element::Item; use grovedb::{Element, EstimatedLayerInformation, TransactionArg}; +use integer_encoding::VarInt; use std::collections::HashMap; impl Drive { @@ -164,7 +171,7 @@ impl Drive { platform_version, )?; - for token_pos in contract.tokens().keys() { + for (token_pos, token_config) in contract.tokens() { let token_id = contract.token_id(*token_pos).ok_or(Error::DataContract( DataContractError::CorruptedDataContract(format!( "data contract has a token at position {}, but can not find it", @@ -172,29 +179,85 @@ impl Drive { )), ))?; + let token_id_bytes = token_id.to_buffer(); + + if let Some(estimated_costs_only_with_layer_info) = estimated_costs_only_with_layer_info + { + Drive::add_estimation_costs_for_token_balances( + token_id_bytes, + false, + estimated_costs_only_with_layer_info, + &platform_version.drive, + )?; + Drive::add_estimation_costs_for_token_total_supply( + estimated_costs_only_with_layer_info, + &platform_version.drive, + )?; + } + self.batch_insert_empty_tree( tokens_root_path(), - DriveKeyInfo::KeyRef(token_id.as_bytes()), + DriveKeyInfo::KeyRef(&token_id_bytes), None, &mut batch_operations, &platform_version.drive, )?; self.batch_insert_empty_tree( - token_path(token_id.as_bytes()), + token_path(&token_id_bytes), DriveKeyInfo::Key(vec![TOKEN_BALANCES_KEY]), None, &mut batch_operations, &platform_version.drive, )?; - self.batch_insert_empty_tree( - total_tokens_root_supply_path(), - DriveKeyInfo::KeyRef(token_id.as_bytes()), - None, - &mut batch_operations, - &platform_version.drive, - )?; + let path_holding_total_token_supply = total_tokens_root_supply_path_vec(); + + if token_config.base_supply() > 0 { + // We have a base supply that needs to be distributed on contract creation + let destination_identity_id = token_config + .new_tokens_destination_identity() + .unwrap_or(contract.owner_id()); + let token_balance_path = token_balances_path_vec(token_id_bytes); + + if token_config.base_supply() > i64::MAX as u64 { + return Err( + ProtocolError::CriticalCorruptedCreditsCodeExecution(format!( + "Token base supply over i64 max, is {}", + token_config.base_supply() + )) + .into(), + ); + } + self.batch_insert::<0>( + PathKeyElement(( + token_balance_path, + destination_identity_id.to_vec(), + Element::new_sum_item(token_config.base_supply() as i64), + )), + &mut batch_operations, + &platform_version.drive, + )?; + self.batch_insert::<0>( + PathKeyElement(( + path_holding_total_token_supply, + token_id.to_vec(), + Item(token_config.base_supply().encode_var_vec(), None), + )), + &mut batch_operations, + &platform_version.drive, + )?; + } else { + self.batch_insert::<0>( + PathKeyElement(( + path_holding_total_token_supply, + token_id.to_vec(), + Item(0u64.encode_var_vec(), None), + )), + &mut batch_operations, + &platform_version.drive, + )?; + } } if !contract.groups().is_empty() { diff --git a/packages/rs-drive/src/drive/tokens/balance/add_to_previous_token_balance/mod.rs b/packages/rs-drive/src/drive/tokens/balance/add_to_previous_token_balance/mod.rs index 24cf8ad32a..ba521297a2 100644 --- a/packages/rs-drive/src/drive/tokens/balance/add_to_previous_token_balance/mod.rs +++ b/packages/rs-drive/src/drive/tokens/balance/add_to_previous_token_balance/mod.rs @@ -8,6 +8,7 @@ use dpp::block::block_info::BlockInfo; use dpp::fee::fee_result::FeeResult; use dpp::fee::Credits; +use dpp::balances::credits::TokenAmount; use dpp::fee::default_costs::CachedEpochIndexFeeVersions; use dpp::version::PlatformVersion; use grovedb::batch::KeyInfoPath; @@ -86,7 +87,7 @@ impl Drive { &self, token_id: [u8; 32], identity_id: [u8; 32], - balance_to_add: Credits, + balance_to_add: TokenAmount, estimated_costs_only_with_layer_info: &mut Option< HashMap, >, diff --git a/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs index ddfc7ef1c8..97abd31e18 100644 --- a/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/system/add_to_token_total_supply/v0/mod.rs @@ -1,4 +1,7 @@ -use crate::drive::balances::{total_token_supply_path, total_token_supply_path_vec}; +use crate::drive::balances::{ + total_token_supply_path, total_token_supply_path_vec, total_tokens_root_supply_path, + total_tokens_root_supply_path_vec, +}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; @@ -103,8 +106,8 @@ impl Drive { )?; } - let path_holding_total_token_supply = total_token_supply_path(&token_id); - let path_holding_total_token_supply_vec = total_token_supply_path_vec(token_id); + let path_holding_total_token_supply = total_tokens_root_supply_path(); + let path_holding_total_token_supply_vec = total_tokens_root_supply_path_vec(); let total_token_supply_in_platform = self.grove_get_raw_value_u64_from_encoded_var_vec( (&path_holding_total_token_supply).into(), &token_id, diff --git a/packages/rs-drive/src/drive/tokens/system/remove_from_token_total_supply/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/remove_from_token_total_supply/v0/mod.rs index b5a94e7582..fa9c5bd3ea 100644 --- a/packages/rs-drive/src/drive/tokens/system/remove_from_token_total_supply/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/system/remove_from_token_total_supply/v0/mod.rs @@ -1,4 +1,7 @@ -use crate::drive::balances::{total_token_supply_path, total_token_supply_path_vec}; +use crate::drive::balances::{ + total_token_supply_path, total_token_supply_path_vec, total_tokens_root_supply_path, + total_tokens_root_supply_path_vec, +}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; @@ -98,7 +101,7 @@ impl Drive { )?; } - let path_holding_total_token_supply = total_token_supply_path(&token_id); + let path_holding_total_token_supply = total_tokens_root_supply_path(); let total_token_supply_in_platform = self .grove_get_raw_value_u64_from_encoded_var_vec( (&path_holding_total_token_supply).into(), @@ -116,7 +119,7 @@ impl Drive { .ok_or(Error::Drive(DriveError::CriticalCorruptedState( "trying to subtract an amount that would underflow total supply", )))?; - let path_holding_total_token_supply_vec = total_token_supply_path_vec(token_id); + let path_holding_total_token_supply_vec = total_tokens_root_supply_path_vec(); let replace_op = QualifiedGroveDbOp::replace_op( path_holding_total_token_supply_vec, token_id.to_vec(), diff --git a/packages/strategy-tests/src/operations.rs b/packages/strategy-tests/src/operations.rs index 7d354614a9..7f9728bb1b 100644 --- a/packages/strategy-tests/src/operations.rs +++ b/packages/strategy-tests/src/operations.rs @@ -8,7 +8,7 @@ use dpp::data_contract::document_type::random_document::{ use dpp::data_contract::document_type::v0::random_document_type::RandomDocumentTypeParameters; use dpp::data_contract::document_type::DocumentType; use dpp::data_contract::serialized_version::DataContractInSerializationFormat; -use dpp::data_contract::{DataContract as Contract, DataContract}; +use dpp::data_contract::{DataContract as Contract, DataContract, TokenContractPosition}; use dpp::fee::Credits; use dpp::identifier::Identifier; use dpp::identity::IdentityPublicKey; @@ -34,6 +34,7 @@ use std::ops::{Range, RangeInclusive}; pub struct TokenOp { pub contract: Contract, pub token_id: Identifier, + pub token_pos: TokenContractPosition, pub action: TokenEvent, } @@ -41,6 +42,7 @@ pub struct TokenOp { pub struct TokenOpInSerializationFormat { pub contract: DataContractInSerializationFormat, pub token_id: Identifier, + pub token_pos: TokenContractPosition, pub action: TokenEvent, } @@ -62,6 +64,7 @@ impl PlatformSerializableWithPlatformVersion for TokenOp { let TokenOp { contract, token_id, + token_pos, action, } = self; let data_contract_serialization_format: DataContractInSerializationFormat = @@ -70,6 +73,7 @@ impl PlatformSerializableWithPlatformVersion for TokenOp { let document_op = TokenOpInSerializationFormat { contract: data_contract_serialization_format, token_id, + token_pos, action, }; let config = bincode::config::standard() @@ -102,6 +106,7 @@ impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for Tok let TokenOpInSerializationFormat { contract, token_id, + token_pos, action, } = token_op_in_serialization_format; let data_contract = DataContract::try_from_platform_versioned( @@ -113,6 +118,7 @@ impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for Tok Ok(TokenOp { contract: data_contract, token_id, + token_pos, action, }) }