diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index 5a0d8842a61..44d27fc8be2 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -446,10 +446,27 @@ message GetProofsRequest { oneof request_type { ContestedResourceVoteStatusRequest contested_resource_vote_status_request = 1; } } + message IdentityTokenBalanceRequest { + bytes token_id = 1; + bytes identity_id = 2; + } + + message IdentityTokenInfoRequest { + bytes token_id = 1; + bytes identity_id = 2; + } + + message TokenStatusRequest { + bytes token_id = 1; + } + repeated IdentityRequest identities = 1; // List of identity requests repeated ContractRequest contracts = 2; // List of contract requests repeated DocumentRequest documents = 3; // List of document requests repeated VoteStatusRequest votes = 4; + repeated IdentityTokenBalanceRequest identity_token_balances = 5; + repeated IdentityTokenInfoRequest identity_token_infos = 6; + repeated TokenStatusRequest token_statuses = 7; } oneof version { GetProofsRequestV0 v0 = 1; } diff --git a/packages/rs-dpp/src/state_transition/proof_result.rs b/packages/rs-dpp/src/state_transition/proof_result.rs index 012326ac300..b20938b49de 100644 --- a/packages/rs-dpp/src/state_transition/proof_result.rs +++ b/packages/rs-dpp/src/state_transition/proof_result.rs @@ -1,6 +1,9 @@ +use crate::balances::credits::TokenAmount; use crate::data_contract::DataContract; use crate::document::Document; use crate::identity::{Identity, PartialIdentity}; +use crate::tokens::info::IdentityTokenInfo; +use crate::tokens::status::TokenStatus; use crate::voting::votes::Vote; use platform_value::Identifier; use std::collections::BTreeMap; @@ -9,8 +12,14 @@ use std::collections::BTreeMap; pub enum StateTransitionProofResult { VerifiedDataContract(DataContract), VerifiedIdentity(Identity), + VerifiedTokenBalanceAbsence(Identifier), + VerifiedTokenBalance(Identifier, TokenAmount), + VerifiedTokenIdentityInfo(Identifier, IdentityTokenInfo), + VerifiedTokenStatus(TokenStatus), + VerifiedTokenIdentitiesBalances(BTreeMap), VerifiedPartialIdentity(PartialIdentity), VerifiedBalanceTransfer(PartialIdentity, PartialIdentity), //from/to VerifiedDocuments(BTreeMap>), + VerifiedTokenActionWithDocument(Document), VerifiedMasternodeVote(Vote), } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs index 9bb315fd6ef..1973836f536 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0/v0_methods.rs @@ -1,10 +1,14 @@ use platform_value::Identifier; +use crate::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; +use crate::data_contract::associated_token::token_configuration::TokenConfiguration; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; use crate::state_transition::batch_transition::token_base_transition::v0::v0_methods::TokenBaseTransitionV0Methods; use crate::state_transition::batch_transition::token_mint_transition::TokenMintTransitionV0; use crate::state_transition::batch_transition::TokenMintTransition; +use crate::tokens::errors::TokenError; impl TokenBaseTransitionAccessors for TokenMintTransitionV0 { fn base(&self) -> &TokenBaseTransition { @@ -38,6 +42,10 @@ pub trait TokenMintTransitionV0Methods: /// Returns the `issued_to_identity_id` field of the `TokenMintTransitionV0`. fn issued_to_identity_id(&self) -> Option; + fn recipient_id( + &self, + token_configuration: &TokenConfiguration, + ) -> Result; /// Sets the value of the `issued_to_identity_id` field in the `TokenMintTransitionV0`. fn set_issued_to_identity_id(&mut self, issued_to_identity_id: Option); @@ -67,6 +75,23 @@ impl TokenMintTransitionV0Methods for TokenMintTransitionV0 { fn issued_to_identity_id(&self) -> Option { self.issued_to_identity_id } + + fn recipient_id( + &self, + token_configuration: &TokenConfiguration, + ) -> Result { + match self.issued_to_identity_id() { + None => { + token_configuration + .new_tokens_destination_identity() + .ok_or(ProtocolError::Token( + TokenError::TokenNoMintingRecipient.into(), + )) + } + Some(recipient) => Ok(recipient), + } + } + fn set_issued_to_identity_id(&mut self, issued_to_identity_id: Option) { self.issued_to_identity_id = issued_to_identity_id; } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs index 245242e452d..b956f1f71c4 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_mint_transition/v0_methods.rs @@ -1,6 +1,8 @@ use platform_value::Identifier; use crate::balances::credits::TokenAmount; +use crate::data_contract::associated_token::token_configuration::TokenConfiguration; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; @@ -65,6 +67,15 @@ impl TokenMintTransitionV0Methods for TokenMintTransition { } } + fn recipient_id( + &self, + token_configuration: &TokenConfiguration, + ) -> Result { + match self { + TokenMintTransition::V0(v0) => v0.recipient_id(token_configuration), + } + } + fn set_issued_to_identity_id(&mut self, issued_to_identity_id: Option) { match self { TokenMintTransition::V0(v0) => v0.set_issued_to_identity_id(issued_to_identity_id), diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs index ec31b70b1c5..ce0c6d2723c 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0/v0_methods.rs @@ -95,6 +95,19 @@ pub trait TokenTransferTransitionV0Methods: TokenBaseTransitionAccessors { Vec, )>, ); + + /// Returns all notes (public, shared, and private) as cloned values in a tuple. + fn notes( + &self, + ) -> ( + Option, + Option<(SenderKeyIndex, RecipientKeyIndex, Vec)>, + Option<( + RootEncryptionKeyIndex, + DerivationEncryptionKeyIndex, + Vec, + )>, + ); } impl TokenTransferTransitionV0Methods for TokenTransferTransitionV0 { @@ -192,4 +205,22 @@ impl TokenTransferTransitionV0Methods for TokenTransferTransitionV0 { self.private_encrypted_note, ) } + + fn notes( + &self, + ) -> ( + Option, + Option<(SenderKeyIndex, RecipientKeyIndex, Vec)>, + Option<( + RootEncryptionKeyIndex, + DerivationEncryptionKeyIndex, + Vec, + )>, + ) { + ( + self.public_note.clone(), + self.shared_encrypted_note.clone(), + self.private_encrypted_note.clone(), + ) + } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs index 414e53f5d42..12da036c9f4 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transfer_transition/v0_methods.rs @@ -153,4 +153,24 @@ impl TokenTransferTransitionV0Methods for TokenTransferTransition { ), } } + + fn notes( + &self, + ) -> ( + Option, + Option<(SenderKeyIndex, RecipientKeyIndex, Vec)>, + Option<( + RootEncryptionKeyIndex, + DerivationEncryptionKeyIndex, + Vec, + )>, + ) { + match self { + TokenTransferTransition::V0(v0) => ( + v0.public_note.clone(), + v0.shared_encrypted_note.clone(), + v0.private_encrypted_note.clone(), + ), + } + } } diff --git a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs index 0ca461a184d..91371bde326 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/document/batch_transition/batched_transition/token_transition.rs @@ -3,7 +3,17 @@ use derive_more::{Display, From}; use serde::{Deserialize, Serialize}; use platform_value::Identifier; use bincode::{Encode, Decode}; +use data_contracts::SystemDataContract; +use crate::balances::credits::TokenAmount; +use crate::block::block_info::BlockInfo; +use crate::data_contract::accessors::v0::DataContractV0Getters; +use crate::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; +use crate::data_contract::associated_token::token_configuration::TokenConfiguration; +use crate::data_contract::DataContract; +use crate::data_contract::document_type::DocumentTypeRef; +use crate::document::Document; use crate::prelude::IdentityNonce; +use crate::ProtocolError; use crate::state_transition::batch_transition::{DocumentCreateTransition, DocumentDeleteTransition, DocumentReplaceTransition, TokenBurnTransition, TokenDestroyFrozenFundsTransition, TokenEmergencyActionTransition, TokenFreezeTransition, TokenMintTransition, TokenTransferTransition}; use crate::state_transition::batch_transition::batched_transition::{DocumentPurchaseTransition, DocumentTransferTransition}; use crate::state_transition::batch_transition::batched_transition::multi_party_action::AllowedAsMultiPartyAction; @@ -12,6 +22,14 @@ use crate::state_transition::batch_transition::resolvers::v0::BatchTransitionRes use crate::state_transition::batch_transition::token_base_transition::token_base_transition_accessors::TokenBaseTransitionAccessors; use crate::state_transition::batch_transition::token_base_transition::TokenBaseTransition; use crate::state_transition::batch_transition::token_base_transition::v0::v0_methods::TokenBaseTransitionV0Methods; +use crate::state_transition::batch_transition::token_burn_transition::v0::v0_methods::TokenBurnTransitionV0Methods; +use crate::state_transition::batch_transition::token_destroy_frozen_funds_transition::v0::v0_methods::TokenDestroyFrozenFundsTransitionV0Methods; +use crate::state_transition::batch_transition::token_emergency_action_transition::v0::v0_methods::TokenEmergencyActionTransitionV0Methods; +use crate::state_transition::batch_transition::token_freeze_transition::v0::v0_methods::TokenFreezeTransitionV0Methods; +use crate::state_transition::batch_transition::token_mint_transition::v0::v0_methods::TokenMintTransitionV0Methods; +use crate::state_transition::batch_transition::token_transfer_transition::v0::v0_methods::TokenTransferTransitionV0Methods; +use crate::state_transition::batch_transition::token_unfreeze_transition::v0::v0_methods::TokenUnfreezeTransitionV0Methods; +use crate::tokens::token_event::TokenEvent; #[derive(Debug, Clone, Encode, Decode, From, PartialEq, Display)] #[cfg_attr( @@ -141,6 +159,33 @@ pub trait TokenTransitionV0Methods { fn calculate_action_id(&self, owner_id: Identifier) -> Option; fn can_calculate_action_id(&self) -> bool; + /// Historical document type name for the token history contract + fn historical_document_type_name(&self) -> &str; + /// Historical document type for the token history contract + fn historical_document_type<'a>( + &self, + token_history_contract: &'a DataContract, + ) -> Result, ProtocolError>; + /// Historical document id + fn historical_document_id( + &self, + owner_id: Identifier, + owner_nonce: IdentityNonce, + ) -> Identifier; + fn associated_token_event( + &self, + token_configuration: &TokenConfiguration, + ) -> Result; + /// Historical document id + fn build_historical_document( + &self, + token_historical_contract: &DataContract, + token_id: Identifier, + owner_id: Identifier, + owner_nonce: IdentityNonce, + block_info: &BlockInfo, + token_configuration: &TokenConfiguration, + ) -> Result; } impl TokenTransitionV0Methods for TokenTransition { @@ -215,4 +260,106 @@ impl TokenTransitionV0Methods for TokenTransition { fn set_identity_contract_nonce(&mut self, nonce: IdentityNonce) { self.base_mut().set_identity_contract_nonce(nonce); } + + /// Historical document type name for the token history contract + fn historical_document_type_name(&self) -> &str { + match self { + TokenTransition::Burn(_) => "burn", + TokenTransition::Mint(_) => "mint", + TokenTransition::Transfer(_) => "transfer", + TokenTransition::Freeze(_) => "freeze", + TokenTransition::Unfreeze(_) => "unfreeze", + TokenTransition::EmergencyAction(_) => "emergencyAction", + TokenTransition::DestroyFrozenFunds(_) => "destroyFrozenFunds", + } + } + + /// Historical document type for the token history contract + fn historical_document_type<'a>( + &self, + token_history_contract: &'a DataContract, + ) -> Result, ProtocolError> { + Ok(token_history_contract.document_type_for_name(self.historical_document_type_name())?) + } + + /// Historical document id + fn historical_document_id( + &self, + owner_id: Identifier, + owner_nonce: IdentityNonce, + ) -> Identifier { + let name = self.historical_document_type_name(); + Document::generate_document_id_v0( + &SystemDataContract::TokenHistory.id(), + &owner_id, + name, + owner_nonce.to_be_bytes().as_slice(), + ) + } + + /// Historical document id + fn build_historical_document( + &self, + token_historical_contract: &DataContract, + token_id: Identifier, + owner_id: Identifier, + owner_nonce: IdentityNonce, + block_info: &BlockInfo, + token_configuration: &TokenConfiguration, + ) -> Result { + self.associated_token_event(token_configuration)? + .build_historical_document_owned( + token_historical_contract, + token_id, + owner_id, + owner_nonce, + block_info, + ) + } + + fn associated_token_event( + &self, + token_configuration: &TokenConfiguration, + ) -> Result { + Ok(match self { + TokenTransition::Burn(burn) => { + TokenEvent::Burn(burn.burn_amount(), burn.public_note().cloned()) + } + TokenTransition::Mint(mint) => { + let recipient = match mint.issued_to_identity_id() { + None => token_configuration.new_tokens_destination_identity().ok_or(ProtocolError::NotSupported("either the mint destination must be set or the contract must have a destination set for new tokens".to_string()))?, + Some(recipient) => recipient, + }; + TokenEvent::Mint(mint.amount(), recipient, mint.public_note().cloned()) + } + TokenTransition::Transfer(transfer) => { + let (public_note, shared_encrypted_note, private_encrypted_note) = transfer.notes(); + TokenEvent::Transfer( + transfer.recipient_id(), + public_note, + shared_encrypted_note, + private_encrypted_note, + transfer.amount(), + ) + } + TokenTransition::Freeze(freeze) => { + TokenEvent::Freeze(freeze.frozen_identity_id(), freeze.public_note().cloned()) + } + TokenTransition::Unfreeze(unfreeze) => TokenEvent::Unfreeze( + unfreeze.frozen_identity_id(), + unfreeze.public_note().cloned(), + ), + TokenTransition::EmergencyAction(emergency_action) => TokenEvent::EmergencyAction( + emergency_action.emergency_action(), + emergency_action.public_note().cloned(), + ), + TokenTransition::DestroyFrozenFunds(destroy) => { + TokenEvent::DestroyFrozenFunds( + destroy.frozen_identity_id(), + TokenAmount::MAX, // we do not know how much will be destroyed + destroy.public_note().cloned(), + ) + } + }) + } } diff --git a/packages/rs-dpp/src/tokens/emergency_action.rs b/packages/rs-dpp/src/tokens/emergency_action.rs index 7e18e6f4268..8bac9c8ea68 100644 --- a/packages/rs-dpp/src/tokens/emergency_action.rs +++ b/packages/rs-dpp/src/tokens/emergency_action.rs @@ -18,6 +18,9 @@ pub enum TokenEmergencyAction { } impl TokenEmergencyAction { + pub fn paused(&self) -> bool { + matches!(self, TokenEmergencyAction::Pause) + } pub fn resulting_status( &self, platform_version: &PlatformVersion, diff --git a/packages/rs-dpp/src/tokens/errors.rs b/packages/rs-dpp/src/tokens/errors.rs index c23e6194103..536534ddded 100644 --- a/packages/rs-dpp/src/tokens/errors.rs +++ b/packages/rs-dpp/src/tokens/errors.rs @@ -6,4 +6,6 @@ pub enum TokenError { TokenNotFoundAtPositionError, #[error("The contract version does not allow tokens")] TokenNotFoundOnContractVersion, + #[error("There is no minting recipient set")] + TokenNoMintingRecipient, } diff --git a/packages/rs-dpp/src/tokens/token_event.rs b/packages/rs-dpp/src/tokens/token_event.rs index df805788b1f..c0313efaeec 100644 --- a/packages/rs-dpp/src/tokens/token_event.rs +++ b/packages/rs-dpp/src/tokens/token_event.rs @@ -164,7 +164,7 @@ impl TokenEvent { let mut properties = BTreeMap::from([ ("tokenId".to_string(), token_id.into()), ("frozenIdentityId".to_string(), frozen_identity_id.into()), - ("amount".to_string(), amount.into()), + ("destroyedAmount".to_string(), amount.into()), ]); if let Some(note) = public_note { properties.insert("note".to_string(), note.into()); diff --git a/packages/rs-drive-abci/src/query/proofs/v0/mod.rs b/packages/rs-drive-abci/src/query/proofs/v0/mod.rs index 7db4d012276..bdd37055003 100644 --- a/packages/rs-drive-abci/src/query/proofs/v0/mod.rs +++ b/packages/rs-drive-abci/src/query/proofs/v0/mod.rs @@ -4,6 +4,9 @@ use crate::platform_types::platform::Platform; use crate::platform_types::platform_state::PlatformState; use crate::query::QueryValidationResult; use dapi_grpc::platform::v0::get_proofs_request::get_proofs_request_v0::vote_status_request::RequestType; +use dapi_grpc::platform::v0::get_proofs_request::get_proofs_request_v0::{ + IdentityTokenBalanceRequest, IdentityTokenInfoRequest, TokenStatusRequest, +}; use dapi_grpc::platform::v0::get_proofs_request::GetProofsRequestV0; use dapi_grpc::platform::v0::get_proofs_response::{get_proofs_response_v0, GetProofsResponseV0}; use dpp::check_validation_result_with_data; @@ -13,6 +16,9 @@ use dpp::validation::ValidationResult; use dpp::version::PlatformVersion; use dpp::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll; use drive::drive::identity::{IdentityDriveQuery, IdentityProveRequestType}; +use drive::query::identity_token_balance_drive_query::IdentityTokenBalanceDriveQuery; +use drive::query::identity_token_info_drive_query::IdentityTokenInfoDriveQuery; +use drive::query::token_status_drive_query::TokenStatusDriveQuery; use drive::query::{IdentityBasedVoteDriveQuery, SingleDocumentDriveQuery}; impl Platform { @@ -23,6 +29,9 @@ impl Platform { contracts, documents, votes, + identity_token_balances, + identity_token_infos, + token_statuses, }: GetProofsRequestV0, platform_state: &PlatformState, platform_version: &PlatformVersion, @@ -145,11 +154,72 @@ impl Platform { }) .collect::, QueryError>>()); + let token_balance_queries = check_validation_result_with_data!(identity_token_balances + .into_iter() + .map(|identity_token_balance_request| { + let IdentityTokenBalanceRequest { + token_id, + identity_id, + } = identity_token_balance_request; + Ok(IdentityTokenBalanceDriveQuery { + identity_id: Identifier::try_from(identity_id).map_err(|_| { + QueryError::InvalidArgument( + "identity_id must be a valid identifier (32 bytes long)".to_string(), + ) + })?, + token_id: Identifier::try_from(token_id).map_err(|_| { + QueryError::InvalidArgument( + "token_id must be a valid identifier (32 bytes long)".to_string(), + ) + })?, + }) + }) + .collect::, QueryError>>()); + + let token_info_queries = check_validation_result_with_data!(identity_token_infos + .into_iter() + .map(|identity_token_info_request| { + let IdentityTokenInfoRequest { + token_id, + identity_id, + } = identity_token_info_request; + Ok(IdentityTokenInfoDriveQuery { + identity_id: Identifier::try_from(identity_id).map_err(|_| { + QueryError::InvalidArgument( + "identity_id must be a valid identifier (32 bytes long)".to_string(), + ) + })?, + token_id: Identifier::try_from(token_id).map_err(|_| { + QueryError::InvalidArgument( + "token_id must be a valid identifier (32 bytes long)".to_string(), + ) + })?, + }) + }) + .collect::, QueryError>>()); + + let token_status_queries = check_validation_result_with_data!(token_statuses + .into_iter() + .map(|token_status_request| { + let TokenStatusRequest { token_id } = token_status_request; + Ok(TokenStatusDriveQuery { + token_id: Identifier::try_from(token_id).map_err(|_| { + QueryError::InvalidArgument( + "token_id must be a valid identifier (32 bytes long)".to_string(), + ) + })?, + }) + }) + .collect::, QueryError>>()); + let proof = self.drive.prove_multiple_state_transition_results( &identity_requests, &contract_ids, &document_queries, &vote_queries, + &token_balance_queries, + &token_info_queries, + &token_status_queries, None, platform_version, )?; @@ -190,6 +260,9 @@ mod tests { contracts: vec![], documents: vec![], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let result = platform @@ -213,6 +286,9 @@ mod tests { contracts: vec![], documents: vec![], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let result = platform @@ -239,6 +315,9 @@ mod tests { }], documents: vec![], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let result = platform @@ -263,6 +342,9 @@ mod tests { document_contested_status: 0, }], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let result = platform @@ -287,6 +369,9 @@ mod tests { document_contested_status: 0, }], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let result = platform @@ -311,6 +396,9 @@ mod tests { document_contested_status: 0, }], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let validation_result = platform @@ -363,6 +451,9 @@ mod tests { }, )), }], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let validation_result = platform @@ -395,6 +486,9 @@ mod tests { document_contested_status: 0, }], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; let validation_result = platform 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 484e517ece6..af9dddbee27 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/token_tests.rs @@ -12,7 +12,6 @@ mod tests { use dpp::state_transition::StateTransition; use dpp::tests::json_document::json_document_to_created_contract; use dpp::tokens::token_event::TokenEvent; - use drive::query::PathQuery; use drive_abci::config::{ ChainLockConfig, ExecutionConfig, InstantLockConfig, PlatformConfig, PlatformTestConfig, ValidatorSetConfig, @@ -68,7 +67,7 @@ mod tests { .collect(); let contract = created_contract.data_contract_mut(); - let mut token_configuration = contract + let token_configuration = contract .token_configuration_mut(0) .expect("expected to get token configuration"); token_configuration.set_minting_allow_choosing_destination(true); @@ -216,7 +215,7 @@ mod tests { .collect(); let contract = created_contract.data_contract_mut(); - let mut token_configuration = contract + let token_configuration = contract .token_configuration_mut(0) .expect("expected to get token configuration"); token_configuration.set_minting_allow_choosing_destination(true); diff --git a/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs b/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs index 64701edcfa8..4cd29a9d942 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs @@ -113,6 +113,9 @@ pub(crate) fn verify_state_transitions_were_or_were_not_executed( contracts: vec![], documents: vec![], votes: vec![], + identity_token_balances: vec![], + identity_token_infos: vec![], + token_statuses: vec![], }; if let Some(action) = action { diff --git a/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/v0/mod.rs b/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/v0/mod.rs index c0a64a9b632..864a6fff3ed 100644 --- a/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/v0/mod.rs +++ b/packages/rs-drive/src/drive/group/fetch/fetch_action_signers/v0/mod.rs @@ -63,7 +63,7 @@ impl Drive { .map(|(key, element)| match element { SumItem(value, ..) => Ok(( key.try_into()?, - value.try_into().map_err(|e| { + value.try_into().map_err(|_| { Error::Drive(DriveError::CorruptedDriveState( "signed power should be encodable on a u32 integer".to_string(), )) diff --git a/packages/rs-drive/src/drive/tokens/info/queries.rs b/packages/rs-drive/src/drive/tokens/info/queries.rs index 8f5a4443806..e4e6e5c09db 100644 --- a/packages/rs-drive/src/drive/tokens/info/queries.rs +++ b/packages/rs-drive/src/drive/tokens/info/queries.rs @@ -1,5 +1,5 @@ use crate::drive::tokens::paths::{ - token_identity_infos_path_vec, token_identity_infos_root_path_vec, token_statuses_root_path_vec, + token_identity_infos_path_vec, token_identity_infos_root_path_vec, }; use crate::drive::Drive; use crate::query::{Query, QueryItem}; @@ -55,22 +55,6 @@ impl Drive { ) } - /// The query getting a token statuses - pub fn token_statuses_query(token_ids: &[[u8; 32]]) -> PathQuery { - let tokens_root = token_statuses_root_path_vec(); - - let mut query = Query::new(); - - for token_id in token_ids { - query.insert_key(token_id.to_vec()); - } - - PathQuery::new( - tokens_root, - SizedQuery::new(query, Some(token_ids.len() as u16), None), - ) - } - /// The query getting token infos for identities in a range pub fn token_infos_for_range_query( token_id: [u8; 32], diff --git a/packages/rs-drive/src/drive/tokens/status/mod.rs b/packages/rs-drive/src/drive/tokens/status/mod.rs index 57d88f594e0..4824bb8f87e 100644 --- a/packages/rs-drive/src/drive/tokens/status/mod.rs +++ b/packages/rs-drive/src/drive/tokens/status/mod.rs @@ -5,3 +5,4 @@ mod fetch_token_statuses; mod fetch_token_status; #[cfg(feature = "server")] mod prove_token_statuses; +mod queries; diff --git a/packages/rs-drive/src/drive/tokens/status/queries.rs b/packages/rs-drive/src/drive/tokens/status/queries.rs new file mode 100644 index 00000000000..f15f67db307 --- /dev/null +++ b/packages/rs-drive/src/drive/tokens/status/queries.rs @@ -0,0 +1,33 @@ +use crate::drive::tokens::paths::token_statuses_root_path_vec; +use crate::drive::Drive; +use crate::query::Query; +use grovedb::{PathQuery, SizedQuery}; + +impl Drive { + /// The query getting a token statuses + pub fn token_statuses_query(token_ids: &[[u8; 32]]) -> PathQuery { + let tokens_root = token_statuses_root_path_vec(); + + let mut query = Query::new(); + + for token_id in token_ids { + query.insert_key(token_id.to_vec()); + } + + PathQuery::new( + tokens_root, + SizedQuery::new(query, Some(token_ids.len() as u16), None), + ) + } + + /// The query getting a token statuses + pub fn token_status_query(token_id: [u8; 32]) -> PathQuery { + let tokens_root = token_statuses_root_path_vec(); + + let mut query = Query::new(); + + query.insert_key(token_id.to_vec()); + + PathQuery::new(tokens_root, SizedQuery::new(query, Some(1), None)) + } +} diff --git a/packages/rs-drive/src/prove/prove_multiple_state_transition_results/mod.rs b/packages/rs-drive/src/prove/prove_multiple_state_transition_results/mod.rs index 309cecc47ea..646894b25cd 100644 --- a/packages/rs-drive/src/prove/prove_multiple_state_transition_results/mod.rs +++ b/packages/rs-drive/src/prove/prove_multiple_state_transition_results/mod.rs @@ -4,6 +4,9 @@ use crate::error::drive::DriveError; use crate::error::Error; use crate::query::{IdentityBasedVoteDriveQuery, SingleDocumentDriveQuery}; +use crate::query::identity_token_balance_drive_query::IdentityTokenBalanceDriveQuery; +use crate::query::identity_token_info_drive_query::IdentityTokenInfoDriveQuery; +use crate::query::token_status_drive_query::TokenStatusDriveQuery; use dpp::version::PlatformVersion; use grovedb::TransactionArg; @@ -21,6 +24,14 @@ impl Drive { /// if the contract is historical. /// - `document_queries`: A list of [SingleDocumentDriveQuery]. These specify the documents /// to be proven. + /// - `vote_queries`: A list of [IdentityBasedVoteDriveQuery]. These would be to figure out the + /// result of votes based on identities making them. + /// - `token_balance_queries`: A slice of [IdentityTokenBalanceDriveQuery] objects specifying + /// token balance queries for identities. + /// - `token_info_queries`: A slice of [IdentityTokenInfoDriveQuery] objects specifying + /// token information queries for identities. + /// - `token_status_queries`: A slice of [TokenStatusDriveQuery] objects specifying token + /// status queries. /// - `transaction`: An optional grovedb transaction /// - `drive_version`: A reference to the [DriveVersion] object that specifies the version of /// the function to call. @@ -34,6 +45,9 @@ impl Drive { contract_ids: &[([u8; 32], Option)], //bool represents if it is historical document_queries: &[SingleDocumentDriveQuery], vote_queries: &[IdentityBasedVoteDriveQuery], + token_balance_queries: &[IdentityTokenBalanceDriveQuery], + token_info_queries: &[IdentityTokenInfoDriveQuery], + token_status_queries: &[TokenStatusDriveQuery], transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { @@ -48,6 +62,9 @@ impl Drive { contract_ids, document_queries, vote_queries, + token_balance_queries, + token_info_queries, + token_status_queries, transaction, platform_version, ), diff --git a/packages/rs-drive/src/prove/prove_multiple_state_transition_results/v0/mod.rs b/packages/rs-drive/src/prove/prove_multiple_state_transition_results/v0/mod.rs index a348c2d1400..4c0c8058b1c 100644 --- a/packages/rs-drive/src/prove/prove_multiple_state_transition_results/v0/mod.rs +++ b/packages/rs-drive/src/prove/prove_multiple_state_transition_results/v0/mod.rs @@ -3,6 +3,9 @@ use crate::drive::Drive; use crate::error::Error; use crate::query::{IdentityBasedVoteDriveQuery, SingleDocumentDriveQuery}; +use crate::query::identity_token_balance_drive_query::IdentityTokenBalanceDriveQuery; +use crate::query::identity_token_info_drive_query::IdentityTokenInfoDriveQuery; +use crate::query::token_status_drive_query::TokenStatusDriveQuery; use dpp::version::PlatformVersion; use grovedb::{PathQuery, TransactionArg}; use itertools::{Either, Itertools}; @@ -18,6 +21,12 @@ impl Drive { /// to be proven. /// - `vote_queries`: A list of [IdentityBasedVoteDriveQuery]. These would be to figure out the /// result of votes based on identities making them. + /// - `token_balance_queries`: A slice of [IdentityTokenBalanceDriveQuery] objects specifying + /// token balance queries for identities. + /// - `token_info_queries`: A slice of [IdentityTokenInfoDriveQuery] objects specifying + /// token information queries for identities. + /// - `token_status_queries`: A slice of [TokenStatusDriveQuery] objects specifying token + /// status queries. /// - `transaction`: An optional grovedb transaction /// - `platform_version`: A reference to the [PlatformVersion] object that specifies the version of /// the function to call. @@ -32,6 +41,9 @@ impl Drive { contract_ids: &[([u8; 32], Option)], //bool is history document_queries: &[SingleDocumentDriveQuery], vote_queries: &[IdentityBasedVoteDriveQuery], + token_balance_queries: &[IdentityTokenBalanceDriveQuery], + token_info_queries: &[IdentityTokenInfoDriveQuery], + token_status_queries: &[TokenStatusDriveQuery], transaction: TransactionArg, platform_version: &PlatformVersion, ) -> Result, Error> { @@ -108,6 +120,44 @@ impl Drive { })); } + if !token_balance_queries.is_empty() { + path_queries.extend( + token_balance_queries + .iter() + .filter_map(|token_balance_query| { + // The path query construction can only fail if the serialization fails. + // Because the serialization will pretty much never fail, we can do this. + let mut path_query = token_balance_query.construct_path_query(); + path_query.query.limit = None; + Some(path_query) + }), + ); + } + + if !token_info_queries.is_empty() { + path_queries.extend(token_info_queries.iter().filter_map(|token_info_query| { + // The path query construction can only fail if the serialization fails. + // Because the serialization will pretty much never fail, we can do this. + let mut path_query = token_info_query.construct_path_query(); + path_query.query.limit = None; + Some(path_query) + })); + } + + if !token_status_queries.is_empty() { + path_queries.extend( + token_status_queries + .iter() + .filter_map(|token_status_query| { + // The path query construction can only fail if the serialization fails. + // Because the serialization will pretty much never fail, we can do this. + let mut path_query = token_status_query.construct_path_query(); + path_query.query.limit = None; + Some(path_query) + }), + ); + } + let path_query = PathQuery::merge( path_queries.iter().collect(), &platform_version.drive.grove_version, diff --git a/packages/rs-drive/src/query/identity_token_balance_drive_query.rs b/packages/rs-drive/src/query/identity_token_balance_drive_query.rs new file mode 100644 index 00000000000..568fb176cf7 --- /dev/null +++ b/packages/rs-drive/src/query/identity_token_balance_drive_query.rs @@ -0,0 +1,23 @@ +use crate::drive::Drive; +use bincode::{Decode, Encode}; +use dpp::identifier::Identifier; +use grovedb::PathQuery; + +/// Identity token balance drive query struct +#[derive(Debug, PartialEq, Clone, Encode, Decode)] +pub struct IdentityTokenBalanceDriveQuery { + /// The identity who we are querying for + pub identity_id: Identifier, + /// the token id + pub token_id: Identifier, +} + +impl IdentityTokenBalanceDriveQuery { + /// Operations to construct a path query. + pub fn construct_path_query(&self) -> PathQuery { + Drive::token_balance_for_identity_id_query( + self.token_id.to_buffer(), + self.identity_id.to_buffer(), + ) + } +} diff --git a/packages/rs-drive/src/query/identity_token_info_drive_query.rs b/packages/rs-drive/src/query/identity_token_info_drive_query.rs new file mode 100644 index 00000000000..1cb78c7731f --- /dev/null +++ b/packages/rs-drive/src/query/identity_token_info_drive_query.rs @@ -0,0 +1,23 @@ +use crate::drive::Drive; +use bincode::{Decode, Encode}; +use dpp::identifier::Identifier; +use grovedb::PathQuery; + +/// Identity token info drive query struct +#[derive(Debug, PartialEq, Clone, Encode, Decode)] +pub struct IdentityTokenInfoDriveQuery { + /// The identity who we are querying for + pub identity_id: Identifier, + /// The token id + pub token_id: Identifier, +} + +impl IdentityTokenInfoDriveQuery { + /// Operations to construct a path query. + pub fn construct_path_query(&self) -> PathQuery { + Drive::token_info_for_identity_id_query( + self.token_id.to_buffer(), + self.identity_id.to_buffer(), + ) + } +} diff --git a/packages/rs-drive/src/query/mod.rs b/packages/rs-drive/src/query/mod.rs index 5cdc6dcca6a..7c959cbd536 100644 --- a/packages/rs-drive/src/query/mod.rs +++ b/packages/rs-drive/src/query/mod.rs @@ -130,17 +130,28 @@ pub fn contract_lookup_fn_for_contract<'a>( Box::new(func) } -#[cfg(any(feature = "server", feature = "verify"))] /// A query to get the votes given out by an identity -pub mod contested_resource_votes_given_by_identity_query; #[cfg(any(feature = "server", feature = "verify"))] +pub mod contested_resource_votes_given_by_identity_query; /// A query to get contested documents before they have been awarded +#[cfg(any(feature = "server", feature = "verify"))] pub mod drive_contested_document_query; -#[cfg(any(feature = "server", feature = "verify"))] /// A query to get the block counts of proposers in an epoch +#[cfg(any(feature = "server", feature = "verify"))] pub mod proposer_block_count_query; +/// A query to get the identity's token balance +#[cfg(any(feature = "server", feature = "verify"))] +pub mod identity_token_balance_drive_query; +/// A query to get the identity's token info +#[cfg(any(feature = "server", feature = "verify"))] +pub mod identity_token_info_drive_query; + +/// A query to get the token's status +#[cfg(any(feature = "server", feature = "verify"))] +pub mod token_status_drive_query; + #[cfg(any(feature = "server", feature = "verify"))] /// Represents a starting point for a query based on a specific document. /// diff --git a/packages/rs-drive/src/query/token_status_drive_query.rs b/packages/rs-drive/src/query/token_status_drive_query.rs new file mode 100644 index 00000000000..d4712b3545c --- /dev/null +++ b/packages/rs-drive/src/query/token_status_drive_query.rs @@ -0,0 +1,18 @@ +use crate::drive::Drive; +use bincode::{Decode, Encode}; +use dpp::identifier::Identifier; +use grovedb::PathQuery; + +/// Token status drive query struct +#[derive(Debug, PartialEq, Clone, Encode, Decode)] +pub struct TokenStatusDriveQuery { + /// the token id + pub token_id: Identifier, +} + +impl TokenStatusDriveQuery { + /// Operations to construct a path query. + pub fn construct_path_query(&self) -> PathQuery { + Drive::token_status_query(self.token_id.to_buffer()) + } +} diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index 8225f4f6662..640961d6566 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -1,6 +1,9 @@ use std::collections::BTreeMap; +use dpp::balances::credits::TokenAmount; use dpp::block::block_info::BlockInfo; use dpp::data_contract::accessors::v0::DataContractV0Getters; +use dpp::data_contract::accessors::v1::DataContractV1Getters; +use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters; use dpp::data_contract::serialized_version::DataContractInSerializationFormat; use dpp::document::{Document, DocumentV0Getters}; @@ -9,6 +12,7 @@ use dpp::document::property_names::PRICE; use dpp::fee::Credits; use dpp::identity::PartialIdentity; use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dpp::prelude::Identifier; use dpp::state_transition::data_contract_create_transition::accessors::DataContractCreateTransitionAccessorsV0; use dpp::state_transition::data_contract_update_transition::accessors::DataContractUpdateTransitionAccessorsV0; use dpp::state_transition::batch_transition::accessors::DocumentsBatchTransitionAccessorsV0; @@ -27,9 +31,20 @@ use dpp::state_transition::batch_transition::document_replace_transition::Docume use dpp::state_transition::batch_transition::batched_transition::document_transfer_transition::v0::v0_methods::DocumentTransferTransitionV0Methods; use dpp::state_transition::batch_transition::batched_transition::document_transition::{DocumentTransition, DocumentTransitionV0Methods}; use dpp::state_transition::batch_transition::batched_transition::document_update_price_transition::v0::v0_methods::DocumentUpdatePriceTransitionV0Methods; +use dpp::state_transition::batch_transition::batched_transition::token_transition::{TokenTransition, TokenTransitionV0Methods}; +use dpp::state_transition::batch_transition::token_base_transition::v0::v0_methods::TokenBaseTransitionV0Methods; +use dpp::state_transition::batch_transition::token_destroy_frozen_funds_transition::v0::v0_methods::TokenDestroyFrozenFundsTransitionV0Methods; +use dpp::state_transition::batch_transition::token_emergency_action_transition::v0::v0_methods::TokenEmergencyActionTransitionV0Methods; +use dpp::state_transition::batch_transition::token_freeze_transition::v0::v0_methods::TokenFreezeTransitionV0Methods; +use dpp::state_transition::batch_transition::token_mint_transition::v0::v0_methods::TokenMintTransitionV0Methods; +use dpp::state_transition::batch_transition::token_transfer_transition::v0::v0_methods::TokenTransferTransitionV0Methods; +use dpp::state_transition::batch_transition::token_unfreeze_transition::v0::v0_methods::TokenUnfreezeTransitionV0Methods; use dpp::state_transition::masternode_vote_transition::accessors::MasternodeVoteTransitionAccessorsV0; use dpp::state_transition::proof_result::StateTransitionProofResult; -use dpp::state_transition::proof_result::StateTransitionProofResult::{VerifiedBalanceTransfer, VerifiedDataContract, VerifiedDocuments, VerifiedIdentity, VerifiedMasternodeVote, VerifiedPartialIdentity}; +use dpp::state_transition::proof_result::StateTransitionProofResult::{VerifiedBalanceTransfer, VerifiedDataContract, VerifiedDocuments, VerifiedIdentity, VerifiedMasternodeVote, VerifiedPartialIdentity, VerifiedTokenActionWithDocument, VerifiedTokenBalance, VerifiedTokenBalanceAbsence, VerifiedTokenIdentitiesBalances, VerifiedTokenIdentityInfo, VerifiedTokenStatus}; +use dpp::system_data_contracts::SystemDataContract; +use dpp::tokens::info::v0::IdentityTokenInfoV0Accessors; +use dpp::tokens::status::v0::TokenStatusV0Accessors; use dpp::voting::vote_polls::VotePoll; use dpp::voting::votes::resource_vote::accessors::v0::ResourceVoteGettersV0; use dpp::voting::votes::Vote; @@ -278,17 +293,215 @@ impl Drive { } } BatchedTransitionRef::Token(token_transition) => { - todo!() - //todo - // we need to check if the contract has history - // if it has history we verify the proof of the history - // known_contracts_provider_fn - // let token_id = token_transition.token_id(); - // match token_transition { - // TokenTransition::Burn(_) => {} - // TokenTransition::Issuance(_) => {} - // TokenTransition::Transfer(_) => {} - // } + //todo group actions + let data_contract_id = token_transition.data_contract_id(); + let token_id = token_transition.token_id(); + + let contract = known_contracts_provider_fn(&data_contract_id)?.ok_or( + Error::Proof(ProofError::UnknownContract(format!( + "unknown contract with id {}", + data_contract_id + ))), + )?; + + let identity_contract_nonce = + token_transition.base().identity_contract_nonce(); + + let document_type_name = + token_transition.historical_document_type_name().to_string(); + let document_type = token_transition.historical_document_type(&contract)?; + + let token_config = contract.expected_token_configuration( + token_transition.base().token_contract_position(), + )?; + let keeps_historical_document = token_config.keeps_history(); + if keeps_historical_document { + let query = SingleDocumentDriveQuery { + contract_id: SystemDataContract::TokenHistory.id().to_buffer(), + document_type_name, + document_type_keeps_history: false, + document_id: token_transition + .historical_document_id(owner_id, identity_contract_nonce) + .to_buffer(), + block_time_ms: None, //None because we want latest + contested_status: + SingleDocumentDriveQueryContestedStatus::NotContested, + }; + + let expected_document = token_transition.build_historical_document( + &contract, + token_id, + owner_id, + identity_contract_nonce, + &BlockInfo::default(), + token_config, + )?; + let (root_hash, document) = query.verify_proof( + false, + proof, + document_type, + platform_version, + )?; + let document = document.ok_or(Error::Proof(ProofError::IncorrectProof(format!("proof did not contain document with id {} expected to exist because the token keeps historical documents", token_transition.historical_document_type_name()))))?; + if !document.is_equal_ignoring_time_based_fields( + &expected_document, + Some(vec!["destroyedAmount"]), + platform_version, + )? { + return Err(Error::Proof(ProofError::IncorrectProof(format!("proof of state transition execution did not show the correct historical document {}, {}", document, expected_document)))); + } + Ok((root_hash, VerifiedTokenActionWithDocument(document))) + } else { + match token_transition { + TokenTransition::Burn(_) => { + let (root_hash, Some(balance)) = + Drive::verify_token_balance_for_identity_id( + proof, + token_id.into_buffer(), + owner_id.into_buffer(), + false, + platform_version, + )? + else { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof did not contain token balance for identity {} expected to exist because of state transition (token burn)", owner_id)))); + }; + Ok((root_hash, VerifiedTokenBalance(owner_id, balance))) + } + TokenTransition::Mint(token_mint_transition) => { + let recipient_id = + token_mint_transition.recipient_id(token_config)?; + let (root_hash, Some(balance)) = + Drive::verify_token_balance_for_identity_id( + proof, + token_id.into_buffer(), + recipient_id.into_buffer(), + false, + platform_version, + )? + else { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof did not contain token balance for identity {} expected to exist because of state transition (token mint)", recipient_id)))); + }; + Ok((root_hash, VerifiedTokenBalance(recipient_id, balance))) + } + TokenTransition::Transfer(token_transfer_transition) => { + let recipient_id = token_transfer_transition.recipient_id(); + let identity_ids = + [owner_id.to_buffer(), recipient_id.to_buffer()]; + let (root_hash, balances): ( + RootHash, + BTreeMap>, + ) = Drive::verify_token_balances_for_identity_ids( + proof, + token_id.into_buffer(), + &identity_ids, + false, + platform_version, + )?; + + let balances = balances.into_iter().map(|(id, maybe_balance)| { + let balance = maybe_balance.ok_or(Error::Proof(ProofError::IncorrectProof( + format!("proof did not contain token balance for identity {} expected to exist because of state transition (token transfer)", id))))?; + Ok((id, balance)) + }).collect::>()?; + + Ok((root_hash, VerifiedTokenIdentitiesBalances(balances))) + } + TokenTransition::Freeze(token_freeze_transition) => { + let (root_hash, Some(identity_token_info)) = + Drive::verify_token_info_for_identity_id( + proof, + token_id.into_buffer(), + token_freeze_transition + .frozen_identity_id() + .into_buffer(), + false, + platform_version, + )? + else { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof did not contain token info for identity {} expected to exist because of state transition (token freeze)", token_freeze_transition.frozen_identity_id())))); + }; + if !identity_token_info.frozen() { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof contained token info saying this token was not frozen for identity {}", token_freeze_transition.frozen_identity_id())))); + } + Ok(( + root_hash, + VerifiedTokenIdentityInfo(owner_id, identity_token_info), + )) + } + TokenTransition::Unfreeze(token_unfreeze_transition) => { + let (root_hash, Some(identity_token_info)) = + Drive::verify_token_info_for_identity_id( + proof, + token_id.into_buffer(), + token_unfreeze_transition + .frozen_identity_id() + .into_buffer(), + false, + platform_version, + )? + else { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof did not contain token info for identity {} expected to exist because of state transition (token freeze)", token_unfreeze_transition.frozen_identity_id())))); + }; + if identity_token_info.frozen() { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof contained token info saying this token was frozen for identity {} when we just unfroze it", token_unfreeze_transition.frozen_identity_id())))); + } + Ok(( + root_hash, + VerifiedTokenIdentityInfo(owner_id, identity_token_info), + )) + } + TokenTransition::DestroyFrozenFunds( + destroy_frozen_funds_transition, + ) => { + let (root_hash, None) = + Drive::verify_token_balance_for_identity_id( + proof, + token_id.into_buffer(), + destroy_frozen_funds_transition + .frozen_identity_id() + .into_buffer(), + false, + platform_version, + )? + else { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof contained token balance for identity {} expected to not exist because of state transition (token destroy frozen funds)", owner_id)))); + }; + Ok(( + root_hash, + VerifiedTokenBalanceAbsence( + destroy_frozen_funds_transition.frozen_identity_id(), + ), + )) + } + TokenTransition::EmergencyAction(emergency_action_transition) => { + let (root_hash, Some(token_status)) = + Drive::verify_token_status( + proof, + token_id.into_buffer(), + false, + platform_version, + )? + else { + return Err(Error::Proof(ProofError::IncorrectProof( + "proof did not contain token status expected to exist because of state transition (token emergency action)".to_string()))); + }; + if token_status.paused() + != emergency_action_transition.emergency_action().paused() + { + return Err(Error::Proof(ProofError::IncorrectProof( + format!("proof contained token status saying this token is {}paused, but we expected {}paused", if token_status.paused() {""} else {"not "}, if emergency_action_transition.emergency_action().paused() {""} else {"not "})))); + } + Ok((root_hash, VerifiedTokenStatus(token_status))) + } + } + } } } } diff --git a/packages/rs-drive/src/verify/tokens/mod.rs b/packages/rs-drive/src/verify/tokens/mod.rs index 91e8d10e099..22103115109 100644 --- a/packages/rs-drive/src/verify/tokens/mod.rs +++ b/packages/rs-drive/src/verify/tokens/mod.rs @@ -1,6 +1,9 @@ +mod verify_token_balance_for_identity_id; mod verify_token_balances_for_identity_id; mod verify_token_balances_for_identity_ids; +mod verify_token_info_for_identity_id; mod verify_token_infos_for_identity_id; mod verify_token_infos_for_identity_ids; +mod verify_token_status; mod verify_token_statuses; mod verify_token_total_supply_and_aggregated_identity_balance; diff --git a/packages/rs-drive/src/verify/tokens/verify_token_balance_for_identity_id/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_balance_for_identity_id/mod.rs new file mode 100644 index 00000000000..900dc9c637a --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_balance_for_identity_id/mod.rs @@ -0,0 +1,75 @@ +mod v0; + +use crate::drive::Drive; +use dpp::balances::credits::TokenAmount; + +use crate::error::drive::DriveError; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the token balance of a specific identity using a cryptographic proof. + /// + /// This function validates the token balance associated with an identity by verifying + /// the provided cryptographic proof. It ensures the correctness of the balance stored + /// for the given identity and token combination. + /// + /// # Parameters + /// + /// - `proof`: A slice of bytes containing the cryptographic proof of the token balance. + /// - `token_id`: A 32-byte identifier representing the unique ID of the token to verify. + /// - `identity_id`: A 32-byte identifier representing the identity whose token balance + /// is to be verified. + /// - `verify_subset_of_proof`: A boolean indicating whether to verify only a subset of + /// the provided proof. + /// - `platform_version`: A reference to the [PlatformVersion] object specifying which + /// implementation version of the function to invoke. + /// + /// # Returns + /// + /// Returns a `Result` containing: + /// - `Ok((RootHash, Option))`: A tuple where: + /// - `RootHash`: The root hash of the data structure at the time the proof was generated. + /// - `Option`: The token balance if it exists, or `None` if the balance is absent. + /// - `Err(Error)`: An error if the verification fails due to an invalid proof, incorrect data, + /// or version mismatch. + /// + /// # Errors + /// + /// This function may return an `Error` in the following cases: + /// - The provided proof is invalid or corrupted. + /// - The token balance data is missing, inconsistent, or does not match the proof. + /// - The specified platform version does not match any known or supported implementations. + pub fn verify_token_balance_for_identity_id( + proof: &[u8], + token_id: [u8; 32], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + match platform_version + .drive + .methods + .verify + .token + .verify_token_balance_for_identity_id + { + 0 => Self::verify_token_balance_for_identity_id_v0( + proof, + token_id, + identity_id, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_token_balance_for_identity_id".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_balance_for_identity_id/v0/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_balance_for_identity_id/v0/mod.rs new file mode 100644 index 00000000000..e65abd89ed8 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_balance_for_identity_id/v0/mod.rs @@ -0,0 +1,51 @@ +use crate::drive::Drive; +use grovedb::Element::SumItem; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::balances::credits::TokenAmount; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_token_balance_for_identity_id_v0( + proof: &[u8], + token_id: [u8; 32], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + let path_query = Self::token_balance_for_identity_id_query(token_id, identity_id); + let (root_hash, mut proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + } else { + GroveDb::verify_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + }; + if proved_key_values.len() == 1 { + let proved_key_value = proved_key_values.remove(0); + match proved_key_value.2 { + Some(SumItem(value, ..)) => Ok((root_hash, Some(value as TokenAmount))), + None => Ok((root_hash, None)), + _ => Err(Error::Proof(ProofError::IncorrectValueSize( + "proof did not point to a sum item", + ))), + } + } else { + Err(Error::Proof(ProofError::WrongElementCount { + expected: 1, + got: proved_key_values.len(), + })) + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_info_for_identity_id/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_info_for_identity_id/mod.rs new file mode 100644 index 00000000000..d61562f6875 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_info_for_identity_id/mod.rs @@ -0,0 +1,73 @@ +mod v0; + +use crate::drive::Drive; +use dpp::tokens::info::IdentityTokenInfo; + +use crate::error::drive::DriveError; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the token information for a specific identity using a cryptographic proof. + /// + /// This function verifies the association between a token and an identity by processing the provided + /// cryptographic proof. It checks the existence and correctness of the token's information in the + /// context of the specified identity. + /// + /// # Parameters + /// + /// - `proof`: A slice of bytes containing the cryptographic proof of the token's information. + /// - `token_id`: A 32-byte identifier representing the unique ID of the token to verify. + /// - `identity_id`: A 32-byte identifier representing the identity associated with the token. + /// - `verify_subset_of_proof`: A boolean indicating whether to verify only a subset of the provided proof. + /// - `platform_version`: A reference to the [PlatformVersion] object specifying which implementation + /// version of the function to invoke. + /// + /// # Returns + /// + /// Returns a `Result` containing: + /// - `Ok((RootHash, Option))`: A tuple where: + /// - `RootHash`: The root hash of the data structure at the time the proof was generated. + /// - `Option`: The token information if it exists, or `None` if the token information + /// is absent. + /// - `Err(Error)`: An error if the verification fails due to an invalid proof, incorrect data, or version mismatch. + /// + /// # Errors + /// + /// This function may return an `Error` in the following cases: + /// - The provided proof is invalid or corrupted. + /// - The token's information is missing, inconsistent, or does not match the proof. + /// - The specified platform version does not match any known or supported implementations. + pub fn verify_token_info_for_identity_id( + proof: &[u8], + token_id: [u8; 32], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + match platform_version + .drive + .methods + .verify + .token + .verify_token_info_for_identity_id + { + 0 => Self::verify_token_info_for_identity_id_v0( + proof, + token_id, + identity_id, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_token_info_for_identity_id".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_info_for_identity_id/v0/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_info_for_identity_id/v0/mod.rs new file mode 100644 index 00000000000..0d90766948a --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_info_for_identity_id/v0/mod.rs @@ -0,0 +1,55 @@ +use crate::drive::Drive; +use grovedb::Element::Item; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::serialization::PlatformDeserializable; +use dpp::tokens::info::IdentityTokenInfo; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_token_info_for_identity_id_v0( + proof: &[u8], + token_id: [u8; 32], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + let path_query = Self::token_info_for_identity_id_query(token_id, identity_id); + let (root_hash, mut proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + } else { + GroveDb::verify_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + }; + if proved_key_values.len() == 1 { + let proved_key_value = proved_key_values.remove(0); + match proved_key_value.2 { + Some(Item(value, ..)) => Ok(( + root_hash, + Some(IdentityTokenInfo::deserialize_from_bytes(&value)?), + )), + None => Ok((root_hash, None)), + _ => Err(Error::Proof(ProofError::IncorrectValueSize( + "proof did not point to an item", + ))), + } + } else { + Err(Error::Proof(ProofError::WrongElementCount { + expected: 1, + got: proved_key_values.len(), + })) + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_status/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_status/mod.rs new file mode 100644 index 00000000000..d88fdf14bd9 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_status/mod.rs @@ -0,0 +1,68 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use dpp::tokens::status::TokenStatus; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the status of a token using a provided cryptographic proof. + /// + /// This function takes a cryptographic proof, a token ID, and other parameters to verify + /// the existence and status of a token in the data structure. It delegates the verification + /// process to the appropriate versioned implementation based on the platform version. + /// + /// # Parameters + /// + /// - `proof`: A slice of bytes representing the cryptographic proof of the token's status. + /// - `token_id`: A 32-byte identifier representing the unique ID of the token. + /// - `verify_subset_of_proof`: A boolean indicating whether to verify a subset of the provided proof. + /// - `platform_version`: A reference to the [PlatformVersion] object that specifies which version + /// of the function implementation to invoke. + /// + /// # Returns + /// + /// Returns a `Result` containing: + /// - `Ok((RootHash, Option))`: A tuple where: + /// - `RootHash`: The root hash of the data structure at the time the proof was created. + /// - `Option`: The status of the token if it exists, or `None` if the token does not exist. + /// - `Err(Error)`: An error if the verification fails due to an invalid proof, incorrect data, or version mismatch. + /// + /// # Errors + /// + /// This function can return an `Error` in the following cases: + /// - The proof is invalid or corrupted. + /// - The token's status data is missing or inconsistent. + /// - The platform version does not match any of the known implementations. + pub fn verify_token_status( + proof: &[u8], + token_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + match platform_version + .drive + .methods + .verify + .token + .verify_token_status + { + 0 => Self::verify_token_status_v0( + proof, + token_id, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_token_status".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_status/v0/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_status/v0/mod.rs new file mode 100644 index 00000000000..0ed0bc36c9c --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_status/v0/mod.rs @@ -0,0 +1,54 @@ +use crate::drive::Drive; +use grovedb::Element::Item; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::serialization::PlatformDeserializable; +use dpp::tokens::status::TokenStatus; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_token_status_v0( + proof: &[u8], + token_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, Option), Error> { + let path_query = Self::token_status_query(token_id); + let (root_hash, mut proved_key_values) = if verify_subset_of_proof { + GroveDb::verify_subset_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + } else { + GroveDb::verify_query_with_absence_proof( + proof, + &path_query, + &platform_version.drive.grove_version, + )? + }; + if proved_key_values.len() == 1 { + let proved_key_value = proved_key_values.remove(0); + match proved_key_value.2 { + Some(Item(value, ..)) => Ok(( + root_hash, + Some(TokenStatus::deserialize_from_bytes(&value)?), + )), + None => Ok((root_hash, None)), + _ => Err(Error::Proof(ProofError::IncorrectValueSize( + "proof did not point to an item", + ))), + } + } else { + Err(Error::Proof(ProofError::WrongElementCount { + expected: 1, + got: proved_key_values.len(), + })) + } + } +} diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs index e7b9919c935..d371eabce62 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs @@ -49,6 +49,7 @@ pub struct DriveVerifyGroupMethodVersions { pub verify_group_info: FeatureVersion, pub verify_group_infos_in_contract: FeatureVersion, pub verify_action_infos: FeatureVersion, + pub verify_action_signers: FeatureVersion, } #[derive(Clone, Debug, Default)] @@ -59,6 +60,9 @@ pub struct DriveVerifyTokenMethodVersions { pub verify_token_infos_for_identity_id: FeatureVersion, pub verify_token_statuses: FeatureVersion, pub verify_token_total_supply_and_aggregated_identity_balance: FeatureVersion, + pub verify_token_balance_for_identity_id: FeatureVersion, + pub verify_token_info_for_identity_id: FeatureVersion, + pub verify_token_status: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs index 4872470b08e..9df4fc8dfd6 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs @@ -33,6 +33,7 @@ pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVeri verify_group_info: 0, verify_group_infos_in_contract: 0, verify_action_infos: 0, + verify_action_signers: 0, }, token: DriveVerifyTokenMethodVersions { verify_token_balances_for_identity_ids: 0, @@ -41,6 +42,9 @@ pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVeri verify_token_infos_for_identity_id: 0, verify_token_statuses: 0, verify_token_total_supply_and_aggregated_identity_balance: 0, + verify_token_balance_for_identity_id: 0, + verify_token_info_for_identity_id: 0, + verify_token_status: 0, }, single_document: DriveVerifySingleDocumentMethodVersions { verify_proof: 0, diff --git a/packages/token-history-contract/schema/v1/token-history-contract-documents.json b/packages/token-history-contract/schema/v1/token-history-contract-documents.json index 0946d7a29ea..e6d96fd8f73 100644 --- a/packages/token-history-contract/schema/v1/token-history-contract-documents.json +++ b/packages/token-history-contract/schema/v1/token-history-contract-documents.json @@ -460,7 +460,7 @@ "tokenId": "asc" }, { - "amount": "asc" + "destroyedAmount": "asc" } ] }, @@ -498,7 +498,7 @@ "position": 1, "contentMediaType": "application/x.dash.dpp.identifier" }, - "amount": { + "destroyedAmount": { "type": "integer", "minimum": 0, "description": "The amount that was frost burned", @@ -508,7 +508,7 @@ "required": [ "tokenId", "frozenIdentityId", - "amount", + "destroyedAmount", "$createdAt", "$createdAtBlockHeight" ],