diff --git a/Cargo.lock b/Cargo.lock index 930e2769baf..5964aaa3fb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2099,7 +2099,7 @@ dependencies = [ [[package]] name = "grovedb" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "axum", "bincode", @@ -2130,7 +2130,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "integer-encoding", "intmap", @@ -2140,7 +2140,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "grovedb-costs", "hex", @@ -2152,7 +2152,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "bincode", "blake3", @@ -2175,7 +2175,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "hex", ] @@ -2183,7 +2183,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "blake3", "grovedb-costs", @@ -2202,7 +2202,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "thiserror 2.0.11", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2211,7 +2211,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "hex", "itertools 0.14.0", @@ -2220,7 +2220,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "3.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=d8ae2d95f56381b4d104d3983b2f11ae3a968dc7#d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" +source = "git+https://github.com/dashpay/grovedb?rev=e3ac3f7883bb0f2e25936153f76d447b42ebc9c0#e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" dependencies = [ "serde", "serde_with 3.9.0", diff --git a/packages/dapi-grpc/protos/platform/v0/platform.proto b/packages/dapi-grpc/protos/platform/v0/platform.proto index 81da1144434..76b2cce1054 100644 --- a/packages/dapi-grpc/protos/platform/v0/platform.proto +++ b/packages/dapi-grpc/protos/platform/v0/platform.proto @@ -60,7 +60,9 @@ service Platform { rpc getIdentityTokenInfos(GetIdentityTokenInfosRequest) returns (GetIdentityTokenInfosResponse); rpc getIdentitiesTokenInfos(GetIdentitiesTokenInfosRequest) returns (GetIdentitiesTokenInfosResponse); rpc getTokenStatuses(GetTokenStatusesRequest) returns (GetTokenStatusesResponse); + rpc getTokenTotalSupply(GetTokenTotalSupplyRequest) returns (GetTokenTotalSupplyResponse); rpc getGroupInfo(GetGroupInfoRequest) returns (GetGroupInfoResponse); + rpc getGroupInfos(GetGroupInfosRequest) returns (GetGroupInfosResponse); // rpc getActiveGroupActions(GetActiveGroupActionsRequest) returns (GetActiveGroupActionsResponse); // rpc getClosedGroupActions(GetClosedGroupActionsRequest) returns (GetClosedGroupActionsResponse); } @@ -1398,6 +1400,35 @@ message GetTokenStatusesResponse { } } +message GetTokenTotalSupplyRequest { + message GetTokenTotalSupplyRequestV0 { + bytes token_id = 1; + bool prove = 2; + } + oneof version { + GetTokenTotalSupplyRequestV0 v0 = 1; + } +} + +message GetTokenTotalSupplyResponse { + message GetTokenTotalSupplyResponseV0 { + message TokenTotalSupplyEntry { + bytes token_id = 1; + uint64 total_aggregated_amount_in_user_accounts = 2; + uint64 total_system_amount = 3; + } + + oneof result { + TokenTotalSupplyEntry token_total_supply = 1; + Proof proof = 2; + } + ResponseMetadata metadata = 3; + } + oneof version { + GetTokenTotalSupplyResponseV0 v0 = 1; + } +} + message GetGroupInfoRequest { message GetGroupInfoRequestV0 { bytes contract_id = 1; diff --git a/packages/rs-dpp/src/balances/credits.rs b/packages/rs-dpp/src/balances/credits.rs index 4376686a967..c69a02668f8 100644 --- a/packages/rs-dpp/src/balances/credits.rs +++ b/packages/rs-dpp/src/balances/credits.rs @@ -22,6 +22,9 @@ pub type Credits = u64; /// Token Amount type pub type TokenAmount = u64; +/// Signed Token Amount type +pub type SignedTokenAmount = i64; + /// Sum token amount pub type SumTokenAmount = i128; diff --git a/packages/rs-dpp/src/balances/mod.rs b/packages/rs-dpp/src/balances/mod.rs index c0f505ac786..6878bd6d8b8 100644 --- a/packages/rs-dpp/src/balances/mod.rs +++ b/packages/rs-dpp/src/balances/mod.rs @@ -1,4 +1,5 @@ pub mod total_credits_balance; pub mod credits; +pub mod total_single_token_balance; pub mod total_tokens_balance; diff --git a/packages/rs-dpp/src/balances/total_single_token_balance/mod.rs b/packages/rs-dpp/src/balances/total_single_token_balance/mod.rs new file mode 100644 index 00000000000..e7b0eb0c8ca --- /dev/null +++ b/packages/rs-dpp/src/balances/total_single_token_balance/mod.rs @@ -0,0 +1,49 @@ +use crate::balances::credits::SignedTokenAmount; +use crate::ProtocolError; +use std::fmt; + +/// A structure where the token supply and the aggregated token account balances should always be equal +#[derive(Copy, Clone, Debug)] +pub struct TotalSingleTokenBalance { + /// the token supply + pub token_supply: SignedTokenAmount, + /// the sum of all user account balances + pub aggregated_token_account_balances: SignedTokenAmount, +} + +impl fmt::Display for TotalSingleTokenBalance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "TotalSingleTokenBalance {{")?; + writeln!(f, " token_supply: {},", self.token_supply)?; + writeln!( + f, + " aggregated_token_account_balances: {}", + self.aggregated_token_account_balances + )?; + write!(f, "}}") + } +} +impl TotalSingleTokenBalance { + /// Is the outcome okay? basically do the values match up + /// Errors in case of overflow + pub fn ok(&self) -> Result { + let TotalSingleTokenBalance { + token_supply, + aggregated_token_account_balances, + } = *self; + + if token_supply < 0 { + return Err(ProtocolError::Generic( + "Token in platform are less than 0".to_string(), + )); + } + + if aggregated_token_account_balances < 0 { + return Err(ProtocolError::Generic( + "Token in aggregated identity balances are less than 0".to_string(), + )); + } + + Ok(token_supply == aggregated_token_account_balances) + } +} diff --git a/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs index 072d8dfc7e8..b4538389f21 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/tokens/validate_token_aggregated_balance/v0/mod.rs @@ -16,7 +16,7 @@ impl Platform { // Verify sum trees let token_balance = self .drive - .calculate_total_tokens_balance(Some(transaction), &platform_version.drive) + .calculate_total_tokens_balance(Some(transaction), platform_version) .map_err(Error::Drive)?; if !token_balance.ok()? { diff --git a/packages/rs-drive-abci/src/query/service.rs b/packages/rs-drive-abci/src/query/service.rs index 9ce866c7cf5..7ac860eac19 100644 --- a/packages/rs-drive-abci/src/query/service.rs +++ b/packages/rs-drive-abci/src/query/service.rs @@ -22,7 +22,8 @@ use dapi_grpc::platform::v0::{ GetDocumentsRequest, GetDocumentsResponse, GetEpochsInfoRequest, GetEpochsInfoResponse, GetEvonodesProposedEpochBlocksByIdsRequest, GetEvonodesProposedEpochBlocksByRangeRequest, GetEvonodesProposedEpochBlocksResponse, GetGroupInfoRequest, GetGroupInfoResponse, - GetIdentitiesBalancesRequest, GetIdentitiesBalancesResponse, GetIdentitiesContractKeysRequest, + GetGroupInfosRequest, GetGroupInfosResponse, GetIdentitiesBalancesRequest, + GetIdentitiesBalancesResponse, GetIdentitiesContractKeysRequest, GetIdentitiesContractKeysResponse, GetIdentitiesTokenBalancesRequest, GetIdentitiesTokenBalancesResponse, GetIdentitiesTokenInfosRequest, GetIdentitiesTokenInfosResponse, GetIdentityBalanceAndRevisionRequest, @@ -37,8 +38,8 @@ use dapi_grpc::platform::v0::{ GetProtocolVersionUpgradeStateRequest, GetProtocolVersionUpgradeStateResponse, GetProtocolVersionUpgradeVoteStatusRequest, GetProtocolVersionUpgradeVoteStatusResponse, GetStatusRequest, GetStatusResponse, GetTokenStatusesRequest, GetTokenStatusesResponse, - GetTotalCreditsInPlatformRequest, GetTotalCreditsInPlatformResponse, - GetVotePollsByEndDateRequest, GetVotePollsByEndDateResponse, + GetTokenTotalSupplyRequest, GetTokenTotalSupplyResponse, GetTotalCreditsInPlatformRequest, + GetTotalCreditsInPlatformResponse, GetVotePollsByEndDateRequest, GetVotePollsByEndDateResponse, WaitForStateTransitionResultRequest, WaitForStateTransitionResultResponse, }; use dapi_grpc::tonic::{Code, Request, Response, Status}; @@ -675,6 +676,18 @@ impl PlatformService for QueryService { .await } + async fn get_token_total_supply( + &self, + request: Request, + ) -> Result, Status> { + self.handle_blocking_query( + request, + Platform::::query_token_total_supply, + "get_token_total_supply", + ) + .await + } + async fn get_group_info( &self, request: Request, @@ -687,6 +700,18 @@ impl PlatformService for QueryService { .await } + async fn get_group_infos( + &self, + request: Request, + ) -> Result, Status> { + self.handle_blocking_query( + request, + Platform::::query_group_infos, + "get_group_infos", + ) + .await + } + // async fn get_active_group_actions( // &self, // request: Request, diff --git a/packages/rs-drive-abci/src/query/token_queries/mod.rs b/packages/rs-drive-abci/src/query/token_queries/mod.rs index 70f261b3831..fe97942dd10 100644 --- a/packages/rs-drive-abci/src/query/token_queries/mod.rs +++ b/packages/rs-drive-abci/src/query/token_queries/mod.rs @@ -3,3 +3,4 @@ mod identities_token_infos; mod identity_token_balances; mod identity_token_infos; mod token_status; +mod token_total_supply; diff --git a/packages/rs-drive-abci/src/query/token_queries/token_status/mod.rs b/packages/rs-drive-abci/src/query/token_queries/token_status/mod.rs index 555fce502de..ba68930d0d7 100644 --- a/packages/rs-drive-abci/src/query/token_queries/token_status/mod.rs +++ b/packages/rs-drive-abci/src/query/token_queries/token_status/mod.rs @@ -20,7 +20,7 @@ impl Platform { let Some(version) = version else { return Ok(QueryValidationResult::new_with_error( QueryError::DecodingError( - "could not decode identity token infos query".to_string(), + "could not decode identity token statuses query".to_string(), ), )); }; diff --git a/packages/rs-drive-abci/src/query/token_queries/token_total_supply/mod.rs b/packages/rs-drive-abci/src/query/token_queries/token_total_supply/mod.rs new file mode 100644 index 00000000000..2100f9bfe77 --- /dev/null +++ b/packages/rs-drive-abci/src/query/token_queries/token_total_supply/mod.rs @@ -0,0 +1,59 @@ +use crate::error::query::QueryError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::PlatformState; +use crate::query::QueryValidationResult; +use dapi_grpc::platform::v0::get_token_total_supply_request::Version as RequestVersion; +use dapi_grpc::platform::v0::get_token_total_supply_response::Version as ResponseVersion; +use dapi_grpc::platform::v0::{GetTokenTotalSupplyRequest, GetTokenTotalSupplyResponse}; +use dpp::version::PlatformVersion; +mod v0; + +impl Platform { + /// Querying of token total supply + pub fn query_token_total_supply( + &self, + GetTokenTotalSupplyRequest { version }: GetTokenTotalSupplyRequest, + platform_state: &PlatformState, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let Some(version) = version else { + return Ok(QueryValidationResult::new_with_error( + QueryError::DecodingError( + "could not decode identity token total supply query".to_string(), + ), + )); + }; + + let feature_version_bounds = &platform_version + .drive_abci + .query + .token_queries + .token_total_supply; + + let feature_version = match &version { + RequestVersion::V0(_) => 0, + }; + if !feature_version_bounds.check_version(feature_version) { + return Ok(QueryValidationResult::new_with_error( + QueryError::UnsupportedQueryVersion( + "token_total_supply".to_string(), + feature_version_bounds.min_version, + feature_version_bounds.max_version, + platform_version.protocol_version, + feature_version, + ), + )); + } + + match version { + RequestVersion::V0(request_v0) => { + let result = + self.query_token_total_supply_v0(request_v0, platform_state, platform_version)?; + Ok(result.map(|response_v0| GetTokenTotalSupplyResponse { + version: Some(ResponseVersion::V0(response_v0)), + })) + } + } + } +} diff --git a/packages/rs-drive-abci/src/query/token_queries/token_total_supply/v0/mod.rs b/packages/rs-drive-abci/src/query/token_queries/token_total_supply/v0/mod.rs new file mode 100644 index 00000000000..d24a5533089 --- /dev/null +++ b/packages/rs-drive-abci/src/query/token_queries/token_total_supply/v0/mod.rs @@ -0,0 +1,81 @@ +use crate::error::query::QueryError; +use crate::error::Error; +use crate::platform_types::platform::Platform; +use crate::platform_types::platform_state::PlatformState; +use crate::query::QueryValidationResult; +use dapi_grpc::platform::v0::get_token_total_supply_request::GetTokenTotalSupplyRequestV0; +use dapi_grpc::platform::v0::get_token_total_supply_response::get_token_total_supply_response_v0::TokenTotalSupplyEntry; +use dapi_grpc::platform::v0::get_token_total_supply_response::{ + get_token_total_supply_response_v0, GetTokenTotalSupplyResponseV0, +}; +use dpp::check_validation_result_with_data; +use dpp::identifier::Identifier; +use dpp::validation::ValidationResult; +use dpp::version::PlatformVersion; + +impl Platform { + pub(super) fn query_token_total_supply_v0( + &self, + GetTokenTotalSupplyRequestV0 { token_id, prove }: GetTokenTotalSupplyRequestV0, + platform_state: &PlatformState, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let token_id: [u8; 32] = + check_validation_result_with_data!(token_id.try_into().map_err(|_| { + QueryError::InvalidArgument( + "token_id must be a valid identifier (32 bytes long)".to_string(), + ) + })); + + let response = if prove { + let proof = check_validation_result_with_data!(self + .drive + .prove_token_total_supply_and_aggregated_identity_balances( + token_id, + None, + platform_version, + )); + + GetTokenTotalSupplyResponseV0 { + result: Some(get_token_total_supply_response_v0::Result::Proof( + self.response_proof_v0(platform_state, proof), + )), + metadata: Some(self.response_metadata_v0(platform_state)), + } + } else { + let Some(token_total_aggregated_identity_balances) = self + .drive + .fetch_token_total_aggregated_identity_balances(token_id, None, platform_version)? + else { + return Ok(QueryValidationResult::new_with_error(QueryError::NotFound( + format!("Token {} not found", Identifier::new(token_id)), + ))); + }; + + let Some(token_total_supply) = + self.drive + .fetch_token_total_supply(token_id, None, platform_version)? + else { + return Ok(QueryValidationResult::new_with_error(QueryError::NotFound( + format!("Token {} total supply not found", Identifier::new(token_id)), + ))); + }; + + GetTokenTotalSupplyResponseV0 { + result: Some( + get_token_total_supply_response_v0::Result::TokenTotalSupply( + TokenTotalSupplyEntry { + token_id: token_id.to_vec(), + total_aggregated_amount_in_user_accounts: + token_total_aggregated_identity_balances, + total_system_amount: token_total_supply, + }, + ), + ), + metadata: Some(self.response_metadata_v0(platform_state)), + } + }; + + Ok(QueryValidationResult::new_with_data(response)) + } +} diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 9b140c4e555..be5ae7dedbc 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev= "d8ae2d95f56381b4d104d3983b2f11ae3a968dc7", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev= "d8ae2d95f56381b4d104d3983b2f11ae3a968dc7", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev= "d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev= "d8ae2d95f56381b4d104d3983b2f11ae3a968dc7", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev= "d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev= "d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev= "e3ac3f7883bb0f2e25936153f76d447b42ebc9c0", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev= "e3ac3f7883bb0f2e25936153f76d447b42ebc9c0", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev= "e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev= "e3ac3f7883bb0f2e25936153f76d447b42ebc9c0", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev= "e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev= "e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/shared/shared_estimation_costs/add_estimation_costs_for_contested_document_tree_levels_up_to_contract_document_type_excluded/v0/mod.rs b/packages/rs-drive/src/drive/shared/shared_estimation_costs/add_estimation_costs_for_contested_document_tree_levels_up_to_contract_document_type_excluded/v0/mod.rs index aff3c4d5fc2..7dd930ddc67 100644 --- a/packages/rs-drive/src/drive/shared/shared_estimation_costs/add_estimation_costs_for_contested_document_tree_levels_up_to_contract_document_type_excluded/v0/mod.rs +++ b/packages/rs-drive/src/drive/shared/shared_estimation_costs/add_estimation_costs_for_contested_document_tree_levels_up_to_contract_document_type_excluded/v0/mod.rs @@ -82,7 +82,7 @@ impl Drive { KeyInfoPath::from_known_path(vote_contested_resource_tree_path()), EstimatedLayerInformation { tree_type: TreeType::NormalTree, - // active poll "p", with "e" and "i" first so it should be on the second layer of the merk + // active poll "p", with "e" and "i" first, so it should be on the second layer of the merk estimated_layer_count: EstimatedLevel(1, false), estimated_layer_sizes: AllSubtrees(1, NoSumTrees, None), }, diff --git a/packages/rs-drive/src/drive/tokens/balance/fetch_identity_token_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/balance/fetch_identity_token_balances/v0/mod.rs index 8084608b815..5d5692630b0 100644 --- a/packages/rs-drive/src/drive/tokens/balance/fetch_identity_token_balances/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/balance/fetch_identity_token_balances/v0/mod.rs @@ -1,4 +1,3 @@ -use crate::drive::tokens::paths::{tokens_root_path_vec, TOKEN_BALANCES_KEY}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; @@ -6,7 +5,7 @@ use crate::fees::op::LowLevelDriveOperation; use dpp::balances::credits::TokenAmount; use dpp::version::PlatformVersion; use grovedb::Element::SumItem; -use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use grovedb::TransactionArg; use std::collections::BTreeMap; impl Drive { @@ -34,20 +33,7 @@ impl Drive { drive_operations: &mut Vec, platform_version: &PlatformVersion, ) -> Result>, Error> { - let tokens_root = tokens_root_path_vec(); - - let mut query = Query::new(); - - for token_id in token_ids { - query.insert_key(token_id.to_vec()); - } - - query.set_subquery_path(vec![vec![TOKEN_BALANCES_KEY], identity_id.to_vec()]); - - let path_query = PathQuery::new( - tokens_root, - SizedQuery::new(query, Some(token_ids.len() as u16), None), - ); + let path_query = Drive::token_balances_for_identity_id_query(token_ids, identity_id); self.grove_get_raw_path_query_with_optional( &path_query, @@ -59,9 +45,9 @@ impl Drive { .into_iter() .map(|(path, _, element)| { let token_id: [u8; 32] = path - .get(1) + .get(2) .ok_or(Error::Drive(DriveError::CorruptedDriveState( - "returned path item should always have a second part at index 1".to_string(), + "returned path item should always have a third part at index 2".to_string(), )))? .clone() .try_into() diff --git a/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs index 621e42c3702..fad0b5357b0 100644 --- a/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/balance/prove_identity_token_balances/v0/mod.rs @@ -1,9 +1,8 @@ -use crate::drive::tokens::paths::{tokens_root_path_vec, TOKEN_BALANCES_KEY}; use crate::drive::Drive; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; use dpp::version::PlatformVersion; -use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use grovedb::TransactionArg; impl Drive { pub(super) fn prove_identity_token_balances_v0( @@ -30,20 +29,7 @@ impl Drive { drive_operations: &mut Vec, platform_version: &PlatformVersion, ) -> Result, Error> { - let tokens_root = tokens_root_path_vec(); - - let mut query = Query::new(); - - for token_id in token_ids { - query.insert_key(token_id.to_vec()); - } - - query.set_subquery_path(vec![vec![TOKEN_BALANCES_KEY], identity_id.to_vec()]); - - let path_query = PathQuery::new( - tokens_root, - SizedQuery::new(query, Some(token_ids.len() as u16), None), - ); + let path_query = Self::token_balances_for_identity_id_query(token_ids, identity_id); self.grove_get_proved_path_query( &path_query, @@ -53,3 +39,344 @@ impl Drive { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::balances::credits::TokenAmount; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v1::DataContractV1Getters; + use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; + use dpp::data_contract::associated_token::token_configuration::TokenConfiguration; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::v1::DataContractV1; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::prelude::DataContract; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_multiple_token_balances_for_single_identity() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_id = identity.id().to_buffer(); + + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([ + ( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ( + 1, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ]), + }); + + let token_id_1 = contract.token_id(0).expect("expected token at position 0"); + let token_id_2 = contract.token_id(1).expect("expected token at position 1"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add an identity"); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + drive + .token_mint( + token_id_1.to_buffer(), + identity.id().to_buffer(), + 5000, + true, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to mint token 1"); + + let token_balance_1 = drive + .fetch_identity_token_balance( + token_id_1.to_buffer(), + identity_id, + None, + platform_version, + ) + .expect("should not error when fetching token balance"); + + assert_eq!(token_balance_1, Some(5000)); + + drive + .token_mint( + token_id_2.to_buffer(), + identity.id().to_buffer(), + 10000, + true, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to mint token 2"); + + let token_balance_2 = drive + .fetch_identity_token_balance( + token_id_2.to_buffer(), + identity_id, + None, + platform_version, + ) + .expect("should not error when fetching token balance"); + + assert_eq!(token_balance_2, Some(10000)); + + let token_balances = drive + .fetch_identity_token_balances( + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + identity_id, + None, + platform_version, + ) + .expect("should not error when fetching token balances"); + + assert_eq!( + token_balances, + BTreeMap::from([ + (token_id_1.to_buffer(), Some(5000)), + (token_id_2.to_buffer(), Some(10000)), + ]) + ); + + let proof = drive + .prove_identity_token_balances_v0( + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + identity_id, + None, + platform_version, + ) + .expect("should not error when proving token balances"); + + let proved_identity_balance: BTreeMap<[u8; 32], Option> = + Drive::verify_token_balances_for_identity_id( + proof.as_slice(), + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + identity.id().to_buffer(), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + assert_eq!( + proved_identity_balance, + BTreeMap::from([ + (token_id_1.to_buffer(), Some(5000)), + (token_id_2.to_buffer(), Some(10000)), + ]) + ); + } + + #[test] + fn should_prove_no_token_balances_for_single_identity() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let identity = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_id = identity.id().to_buffer(); + + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + )]), + }); + + let token_id = contract.token_id(0).expect("expected token at position 0"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add an identity"); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + let proof = drive + .prove_identity_token_balances_v0( + &[token_id.to_buffer()], + identity_id, + None, + platform_version, + ) + .expect("should not error when proving token balances"); + + let proved_identity_balance: BTreeMap<[u8; 32], Option> = + Drive::verify_token_balances_for_identity_id( + proof.as_slice(), + &[token_id.to_buffer()], + identity.id().to_buffer(), + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + assert_eq!( + proved_identity_balance, + BTreeMap::from([(token_id.to_buffer(), None)]) + ); + } + + #[test] + fn should_prove_no_token_balances_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + // Create a random identity ID that is not added to the drive + let non_existent_identity_id = [1u8; 32]; // An example of a non-existent identity ID + + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + )]), + }); + + let token_id = contract.token_id(0).expect("expected token at position 0"); + + // Insert the contract but no identities + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Attempt to prove token balances for a non-existent identity + let proof_result = drive.prove_identity_token_balances_v0( + &[token_id.to_buffer()], + non_existent_identity_id, + None, + platform_version, + ); + + // Ensure proof generation succeeds even for non-existent identities + assert!(proof_result.is_ok(), "proof generation should succeed"); + + let proof = proof_result.expect("expected valid proof"); + + // Verify the proof + let proved_identity_balance: BTreeMap<[u8; 32], Option> = + Drive::verify_token_balances_for_identity_id( + proof.as_slice(), + &[token_id.to_buffer()], + non_existent_identity_id, + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Verify the balance is `None` for the non-existent identity + assert_eq!( + proved_identity_balance, + BTreeMap::from([(token_id.to_buffer(), None)]), + "expected no balances for non-existent identity" + ); + } +} diff --git a/packages/rs-drive/src/drive/tokens/balance/queries.rs b/packages/rs-drive/src/drive/tokens/balance/queries.rs index d15202311a5..a02a3216db4 100644 --- a/packages/rs-drive/src/drive/tokens/balance/queries.rs +++ b/packages/rs-drive/src/drive/tokens/balance/queries.rs @@ -1,7 +1,12 @@ -use crate::drive::tokens::paths::token_balances_path_vec; +use crate::drive::balances::total_tokens_root_supply_path_vec; +use crate::drive::tokens::paths::{ + token_balances_path_vec, token_balances_root_path_vec, tokens_root_path_vec, TOKEN_BALANCES_KEY, +}; use crate::drive::Drive; +use crate::error::Error; use crate::query::{Query, QueryItem}; use grovedb::{PathQuery, SizedQuery}; +use platform_version::version::PlatformVersion; use std::ops::RangeFull; impl Drive { @@ -32,6 +37,27 @@ impl Drive { } } + /// The query getting token balances for a single identity and many tokens + pub fn token_balances_for_identity_id_query( + token_ids: &[[u8; 32]], + identity_id: [u8; 32], + ) -> PathQuery { + let tokens_root = token_balances_root_path_vec(); + + let mut query = Query::new(); + + for token_id in token_ids { + query.insert_key(token_id.to_vec()); + } + + query.set_subquery_path(vec![identity_id.to_vec()]); + + PathQuery::new( + tokens_root, + SizedQuery::new(query, Some(token_ids.len() as u16), None), + ) + } + /// The query getting token balances for identities in a range pub fn token_balances_for_range_query( token_id: [u8; 32], @@ -69,4 +95,26 @@ impl Drive { }, } } + + /// The query getting token balances for identities in a range + pub fn token_total_supply_and_aggregated_identity_balances_query( + token_id: [u8; 32], + platform_version: &PlatformVersion, + ) -> Result { + let path_holding_total_token_supply = total_tokens_root_supply_path_vec(); + let token_supply_query = + PathQuery::new_single_key(path_holding_total_token_supply, token_id.to_vec()); + let tokens_root_path = token_balances_root_path_vec(); + let token_aggregated_identity_balances_query = + PathQuery::new_single_key(tokens_root_path, token_id.to_vec()); + let mut path_query = PathQuery::merge( + vec![ + &token_aggregated_identity_balances_query, + &token_supply_query, + ], + &platform_version.drive.grove_version, + )?; + path_query.query.limit = Some(2); + Ok(path_query) + } } diff --git a/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs b/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs index 8799421a9db..60b85c9dbb6 100644 --- a/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs +++ b/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/mod.rs @@ -6,6 +6,7 @@ use crate::error::Error; use dpp::balances::total_tokens_balance::TotalTokensBalance; use dpp::version::drive_versions::DriveVersion; use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; impl Drive { /// Calculates the total credits balance. @@ -28,10 +29,15 @@ impl Drive { pub fn calculate_total_tokens_balance( &self, transaction: TransactionArg, - drive_version: &DriveVersion, + platform_version: &PlatformVersion, ) -> Result { - match drive_version.methods.token.calculate_total_tokens_balance { - 0 => self.calculate_total_tokens_balance_v0(transaction, drive_version), + match platform_version + .drive + .methods + .token + .calculate_total_tokens_balance + { + 0 => self.calculate_total_tokens_balance_v0(transaction, platform_version), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "calculate_total_tokens_balance".to_string(), known_versions: vec![0], diff --git a/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/v0/mod.rs b/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/v0/mod.rs index 6cefd4ce28a..6d4acc61691 100644 --- a/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/calculate_total_tokens_balance/v0/mod.rs @@ -5,8 +5,8 @@ use crate::drive::Drive; use crate::error::Error; use crate::util::grove_operations::DirectQueryType; use dpp::balances::total_tokens_balance::TotalTokensBalance; -use dpp::version::drive_versions::DriveVersion; use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; impl Drive { /// Verify that the sum tree identity credits + pool credits + refunds are equal to the @@ -15,7 +15,7 @@ impl Drive { pub(super) fn calculate_total_tokens_balance_v0( &self, transaction: TransactionArg, - drive_version: &DriveVersion, + platform_version: &PlatformVersion, ) -> Result { let mut drive_operations = vec![]; let path_holding_total_credits = misc_path(); @@ -25,7 +25,7 @@ impl Drive { DirectQueryType::StatefulDirectQuery, transaction, &mut drive_operations, - drive_version, + &platform_version.drive, )?; let tokens_root_path = tokens_root_path(); @@ -36,7 +36,7 @@ impl Drive { DirectQueryType::StatefulDirectQuery, transaction, &mut drive_operations, - drive_version, + &platform_version.drive, )?; Ok(TotalTokensBalance { diff --git a/packages/rs-drive/src/drive/tokens/info/fetch_identity_token_infos/v0/mod.rs b/packages/rs-drive/src/drive/tokens/info/fetch_identity_token_infos/v0/mod.rs index 6779f880382..21ab3474ba6 100644 --- a/packages/rs-drive/src/drive/tokens/info/fetch_identity_token_infos/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/info/fetch_identity_token_infos/v0/mod.rs @@ -1,4 +1,3 @@ -use crate::drive::tokens::paths::{tokens_root_path_vec, TOKEN_IDENTITY_INFO_KEY}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; @@ -7,7 +6,7 @@ use dpp::serialization::PlatformDeserializable; use dpp::tokens::info::IdentityTokenInfo; use dpp::version::PlatformVersion; use grovedb::Element::Item; -use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use grovedb::TransactionArg; use std::collections::BTreeMap; impl Drive { @@ -35,20 +34,7 @@ impl Drive { drive_operations: &mut Vec, platform_version: &PlatformVersion, ) -> Result>, Error> { - let tokens_root = tokens_root_path_vec(); - - let mut query = Query::new(); - - for token_id in token_ids { - query.insert_key(token_id.to_vec()); - } - - query.set_subquery_path(vec![vec![TOKEN_IDENTITY_INFO_KEY], identity_id.to_vec()]); - - let path_query = PathQuery::new( - tokens_root, - SizedQuery::new(query, Some(token_ids.len() as u16), None), - ); + let path_query = Drive::token_infos_for_identity_id_query(token_ids, identity_id); self.grove_get_raw_path_query_with_optional( &path_query, @@ -60,9 +46,9 @@ impl Drive { .into_iter() .map(|(path, _, element)| { let token_id: [u8; 32] = path - .get(1) + .get(2) .ok_or(Error::Drive(DriveError::CorruptedDriveState( - "returned path item should always have a second part at index 1".to_string(), + "returned path item should always have a third part at index 2".to_string(), )))? .clone() .try_into() diff --git a/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs b/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs index a0e4e71ac91..e6d7247d861 100644 --- a/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/info/prove_identity_token_infos/v0/mod.rs @@ -1,9 +1,8 @@ -use crate::drive::tokens::paths::{tokens_root_path_vec, TOKEN_IDENTITY_INFO_KEY}; use crate::drive::Drive; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; use dpp::version::PlatformVersion; -use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use grovedb::TransactionArg; impl Drive { pub(super) fn prove_identity_token_infos_v0( @@ -30,20 +29,7 @@ impl Drive { drive_operations: &mut Vec, platform_version: &PlatformVersion, ) -> Result, Error> { - let tokens_root = tokens_root_path_vec(); - - let mut query = Query::new(); - - for token_id in token_ids { - query.insert_key(token_id.to_vec()); - } - - query.set_subquery_path(vec![vec![TOKEN_IDENTITY_INFO_KEY], identity_id.to_vec()]); - - let path_query = PathQuery::new( - tokens_root, - SizedQuery::new(query, Some(token_ids.len() as u16), None), - ); + let path_query = Self::token_infos_for_identity_id_query(token_ids, identity_id); self.grove_get_proved_path_query( &path_query, @@ -53,3 +39,247 @@ impl Drive { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v1::DataContractV1Getters; + use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; + use dpp::data_contract::associated_token::token_configuration::TokenConfiguration; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::v1::DataContractV1; + use dpp::identity::accessors::IdentityGettersV0; + use dpp::identity::Identity; + use dpp::prelude::DataContract; + use dpp::tokens::info::v0::IdentityTokenInfoV0; + use dpp::tokens::info::IdentityTokenInfo; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_token_infos_with_one_token_frozen() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + // Create a random identity + let identity = Identity::random_identity(3, Some(14), platform_version) + .expect("expected a platform identity"); + + let identity_id = identity.id().to_buffer(); + + // Create a data contract with multiple tokens + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([ + ( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ( + 1, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ]), + }); + + let token_id_1 = contract.token_id(0).expect("expected token at position 0"); + let token_id_2 = contract.token_id(1).expect("expected token at position 1"); + + drive + .add_new_identity( + identity.clone(), + false, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to add an identity"); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Freeze one token for the identity + drive + .token_freeze( + token_id_1, + identity.id(), + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to freeze token"); + + let fetched_token_info_1 = drive + .fetch_identity_token_info(token_id_1.to_buffer(), identity_id, None, platform_version) + .expect("expected to fetch token info"); + + assert_eq!( + fetched_token_info_1, + Some(IdentityTokenInfo::V0(IdentityTokenInfoV0 { frozen: true })) + ); + + let fetched_token_info_2 = drive + .fetch_identity_token_info(token_id_2.to_buffer(), identity_id, None, platform_version) + .expect("expected to fetch token info"); + + assert_eq!(fetched_token_info_2, None); + + // Fetch identity token infos before proving + let fetched_token_infos = drive + .fetch_identity_token_infos( + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + identity_id, + None, + platform_version, + ) + .expect("expected to fetch token infos"); + + // Verify fetched token infos + assert_eq!( + fetched_token_infos, + BTreeMap::from([ + ( + token_id_1.to_buffer(), + Some(IdentityTokenInfo::V0(IdentityTokenInfoV0 { frozen: true })), + ), + (token_id_2.to_buffer(), None,), + ]), + "unexpected fetched token infos" + ); + + // Generate proof + let proof = drive + .prove_identity_token_infos_v0( + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + identity_id, + None, + platform_version, + ) + .expect("should not error when proving token infos"); + + // Verify proof + let proved_token_infos: BTreeMap<[u8; 32], Option> = + Drive::verify_token_infos_for_identity_id( + proof.as_slice(), + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + identity_id, + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert proved token infos match the fetched ones + assert_eq!( + proved_token_infos, + BTreeMap::from([ + ( + token_id_1.to_buffer(), + Some(IdentityTokenInfo::V0(IdentityTokenInfoV0 { frozen: true })), + ), + (token_id_2.to_buffer(), None,), + ]), + "unexpected fetched token infos" + ); + } + + #[test] + fn should_prove_no_token_infos_for_non_existent_identity() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let non_existent_identity_id = [0u8; 32]; // An identity that doesn't exist in the database + + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + )]), + }); + + let token_id = contract.token_id(0).expect("expected token at position 0"); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + let proof = drive + .prove_identity_token_infos_v0( + &[token_id.to_buffer()], + non_existent_identity_id, + None, + platform_version, + ) + .expect("should not error when proving token infos for non-existent identity"); + + let proved_token_infos: BTreeMap<[u8; 32], Option> = + Drive::verify_token_infos_for_identity_id( + proof.as_slice(), + &[token_id.to_buffer()], + non_existent_identity_id, + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert that no token infos exist for the non-existent identity + assert_eq!( + proved_token_infos, + BTreeMap::from([(token_id.to_buffer(), None)]) + ); + } +} diff --git a/packages/rs-drive/src/drive/tokens/info/queries.rs b/packages/rs-drive/src/drive/tokens/info/queries.rs index 3b2303932d4..c4e80a5af11 100644 --- a/packages/rs-drive/src/drive/tokens/info/queries.rs +++ b/packages/rs-drive/src/drive/tokens/info/queries.rs @@ -1,4 +1,7 @@ -use crate::drive::tokens::paths::token_identity_infos_path_vec; +use crate::drive::tokens::paths::{ + token_identity_infos_path_vec, token_identity_infos_root_path_vec, + token_statuses_root_path_vec, tokens_root_path_vec, TOKEN_STATUS_INFO_KEY, +}; use crate::drive::Drive; use crate::query::{Query, QueryItem}; use grovedb::{PathQuery, SizedQuery}; @@ -32,6 +35,43 @@ impl Drive { } } + /// The query getting a token infos for one identity + pub fn token_infos_for_identity_id_query( + token_ids: &[[u8; 32]], + identity_id: [u8; 32], + ) -> PathQuery { + let tokens_root = token_identity_infos_root_path_vec(); + + let mut query = Query::new(); + + for token_id in token_ids { + query.insert_key(token_id.to_vec()); + } + + query.set_subquery_path(vec![identity_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_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/fetch_token_statuses/v0/mod.rs b/packages/rs-drive/src/drive/tokens/status/fetch_token_statuses/v0/mod.rs index 486ebde1288..8a9e1f23c3b 100644 --- a/packages/rs-drive/src/drive/tokens/status/fetch_token_statuses/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/status/fetch_token_statuses/v0/mod.rs @@ -1,4 +1,3 @@ -use crate::drive::tokens::paths::{tokens_root_path_vec, TOKEN_STATUS_INFO_KEY}; use crate::drive::Drive; use crate::error::drive::DriveError; use crate::error::Error; @@ -7,7 +6,7 @@ use dpp::serialization::PlatformDeserializable; use dpp::tokens::status::TokenStatus; use dpp::version::PlatformVersion; use grovedb::Element::Item; -use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use grovedb::TransactionArg; use std::collections::BTreeMap; impl Drive { @@ -32,20 +31,7 @@ impl Drive { drive_operations: &mut Vec, platform_version: &PlatformVersion, ) -> Result>, Error> { - let tokens_root = tokens_root_path_vec(); - - let mut query = Query::new(); - - for token_id in token_ids { - query.insert_key(token_id.to_vec()); - } - - query.set_subquery_path(vec![vec![TOKEN_STATUS_INFO_KEY]]); - - let path_query = PathQuery::new( - tokens_root, - SizedQuery::new(query, Some(token_ids.len() as u16), None), - ); + let path_query = Drive::token_statuses_query(token_ids); self.grove_get_raw_path_query_with_optional( &path_query, diff --git a/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs b/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs index baae7936be2..07b6fef19c7 100644 --- a/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs +++ b/packages/rs-drive/src/drive/tokens/status/prove_token_statuses/v0/mod.rs @@ -1,9 +1,8 @@ -use crate::drive::tokens::paths::{tokens_root_path_vec, TOKEN_IDENTITY_INFO_KEY}; use crate::drive::Drive; use crate::error::Error; use crate::fees::op::LowLevelDriveOperation; use dpp::version::PlatformVersion; -use grovedb::{PathQuery, Query, SizedQuery, TransactionArg}; +use grovedb::TransactionArg; impl Drive { pub(super) fn prove_token_statuses_v0( @@ -27,20 +26,7 @@ impl Drive { drive_operations: &mut Vec, platform_version: &PlatformVersion, ) -> Result, Error> { - let tokens_root = tokens_root_path_vec(); - - let mut query = Query::new(); - - for token_id in token_ids { - query.insert_key(token_id.to_vec()); - } - - query.set_subquery_path(vec![vec![TOKEN_IDENTITY_INFO_KEY]]); - - let path_query = PathQuery::new( - tokens_root, - SizedQuery::new(query, Some(token_ids.len() as u16), None), - ); + let path_query = Self::token_statuses_query(token_ids); self.grove_get_proved_path_query( &path_query, @@ -50,3 +36,186 @@ impl Drive { ) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v1::DataContractV1Getters; + use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; + use dpp::data_contract::associated_token::token_configuration::TokenConfiguration; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::v1::DataContractV1; + use dpp::prelude::DataContract; + use dpp::tokens::status::v0::TokenStatusV0; + use dpp::tokens::status::TokenStatus; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_token_statuses_for_multiple_tokens() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + // Create a data contract with multiple tokens + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([ + ( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ( + 1, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + ), + ]), + }); + + let token_id_1 = contract.token_id(0).expect("expected token at position 0"); + let token_id_2 = contract.token_id(1).expect("expected token at position 1"); + + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Freeze the first token + drive + .token_apply_status( + token_id_1.to_buffer(), + TokenStatus::new(true, platform_version).expect("expected token status"), + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to freeze token"); + + let fetched_token_info_1 = drive + .fetch_token_status(token_id_1.to_buffer(), None, platform_version) + .expect("expected to fetch token status"); + + assert_eq!( + fetched_token_info_1, + Some(TokenStatus::V0(TokenStatusV0 { paused: true })) + ); + + let fetched_token_info_2 = drive + .fetch_token_status(token_id_2.to_buffer(), None, platform_version) + .expect("expected to fetch token status"); + + assert_eq!(fetched_token_info_2, None); + + // Fetch token statuses before proving + let fetched_token_statuses = drive + .fetch_token_statuses( + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + None, + platform_version, + ) + .expect("expected to fetch token statuses"); + + // Verify fetched token statuses + assert_eq!( + fetched_token_statuses, + BTreeMap::from([ + ( + token_id_1.to_buffer(), + Some(TokenStatus::V0(TokenStatusV0 { paused: true })), + ), + (token_id_2.to_buffer(), None,), + ]), + "unexpected fetched token infos" + ); + + // Generate proof + let proof = drive + .prove_token_statuses( + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + None, + platform_version, + ) + .expect("should not error when proving token statuses"); + + // Verify proof + let proved_token_statuses: BTreeMap<[u8; 32], Option> = + Drive::verify_token_statuses( + proof.as_slice(), + &[token_id_1.to_buffer(), token_id_2.to_buffer()], + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert the token statuses match expected values + assert_eq!( + proved_token_statuses, + BTreeMap::from([ + ( + token_id_1.to_buffer(), + Some(TokenStatus::V0(TokenStatusV0 { paused: true })) + ), + (token_id_2.to_buffer(), None), + ]), + "unexpected token statuses" + ); + } + + #[test] + fn should_prove_no_token_statuses_for_non_existent_tokens() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + let non_existent_token_id = [0u8; 32]; // A token ID that doesn't exist in the database + + // Generate proof + let proof = drive + .prove_token_statuses_v0(&[non_existent_token_id], None, platform_version) + .expect("should not error when proving token statuses for non-existent tokens"); + + // Verify proof + let proved_token_statuses: BTreeMap<[u8; 32], Option> = + Drive::verify_token_statuses( + proof.as_slice(), + &[non_existent_token_id], + false, + platform_version, + ) + .expect("expected proof verification to succeed") + .1; + + // Assert that the token status is `None` for the non-existent token + assert_eq!( + proved_token_statuses, + BTreeMap::from([(non_existent_token_id, None)]), + "unexpected token statuses for non-existent tokens" + ); + } +} diff --git a/packages/rs-drive/src/drive/tokens/system/fetch_token_total_aggregated_identity_balances/mod.rs b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_aggregated_identity_balances/mod.rs new file mode 100644 index 00000000000..aac5a63ff62 --- /dev/null +++ b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_aggregated_identity_balances/mod.rs @@ -0,0 +1,74 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::balances::credits::TokenAmount; +use dpp::version::PlatformVersion; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +impl Drive { + /// Fetches token's total aggregated_identity_balances + pub fn fetch_token_total_aggregated_identity_balances( + &self, + token_id: [u8; 32], + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .token + .fetch + .token_total_aggregated_identity_balances + { + 0 => self.fetch_token_total_aggregated_identity_balances_v0( + token_id, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_token_total_aggregated_identity_balances".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Adds the operations of fetching the token total aggregated_identity_balances + pub fn fetch_token_total_aggregated_identity_balances_add_to_operations( + &self, + token_id: [u8; 32], + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .token + .fetch + .token_total_aggregated_identity_balances + { + 0 => self.fetch_token_total_aggregated_identity_balances_add_to_operations_v0( + token_id, + estimated_costs_only_with_layer_info, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_token_total_aggregated_identity_balances_add_to_operations" + .to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/tokens/system/fetch_token_total_aggregated_identity_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_aggregated_identity_balances/v0/mod.rs new file mode 100644 index 00000000000..035091d0527 --- /dev/null +++ b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_aggregated_identity_balances/v0/mod.rs @@ -0,0 +1,75 @@ +use crate::drive::tokens::paths::token_balances_root_path; +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::util::grove_operations::DirectQueryType; +use crate::util::grove_operations::QueryTarget::QueryTargetValue; +use dpp::balances::credits::TokenAmount; +use dpp::version::PlatformVersion; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg, TreeType}; +use std::collections::HashMap; + +impl Drive { + pub(super) fn fetch_token_total_aggregated_identity_balances_v0( + &self, + token_id: [u8; 32], + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut drive_operations = vec![]; + + self.fetch_token_total_aggregated_identity_balances_add_to_operations_v0( + token_id, + &mut None, + transaction, + &mut drive_operations, + platform_version, + ) + } + + pub(super) fn fetch_token_total_aggregated_identity_balances_add_to_operations_v0( + &self, + token_id: [u8; 32], + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + // If we only estimate, add estimation costs + if let Some(estimated_costs_only_with_layer_info) = estimated_costs_only_with_layer_info { + // Add your estimation logic similar to add_to_system_credits_operations_v0 + // For example: + Self::add_estimation_costs_for_token_balances( + token_id, + estimated_costs_only_with_layer_info, + &platform_version.drive, + )?; + } + + let direct_query_type = if estimated_costs_only_with_layer_info.is_none() { + DirectQueryType::StatefulDirectQuery + } else { + DirectQueryType::StatelessDirectQuery { + in_tree_type: TreeType::BigSumTree, + query_target: QueryTargetValue(8), + } + }; + + let tokens_root_path = token_balances_root_path(); + + let total_token_aggregated_identity_balances_in_platform = self + .grove_get_raw_value_u64_from_encoded_var_vec( + (&tokens_root_path).into(), + &token_id, + direct_query_type, + transaction, + drive_operations, + &platform_version.drive, + )?; + + Ok(total_token_aggregated_identity_balances_in_platform) + } +} diff --git a/packages/rs-drive/src/drive/tokens/system/fetch_token_total_supply/mod.rs b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_supply/mod.rs new file mode 100644 index 00000000000..fac0478a0e5 --- /dev/null +++ b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_supply/mod.rs @@ -0,0 +1,69 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::balances::credits::TokenAmount; +use dpp::version::PlatformVersion; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +impl Drive { + /// Fetches token's total supply + pub fn fetch_token_total_supply( + &self, + token_id: [u8; 32], + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .token + .fetch + .token_total_supply + { + 0 => self.fetch_token_total_supply_v0(token_id, transaction, platform_version), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_token_total_supply".to_string(), + known_versions: vec![0], + received: version, + })), + } + } + + /// Adds the operations of fetching the token total supply + pub fn fetch_token_total_supply_add_to_operations( + &self, + token_id: [u8; 32], + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .token + .fetch + .token_total_supply + { + 0 => self.fetch_token_total_supply_add_to_operations_v0( + token_id, + estimated_costs_only_with_layer_info, + transaction, + drive_operations, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "fetch_token_total_supply_add_to_operations".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/tokens/system/fetch_token_total_supply/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_supply/v0/mod.rs new file mode 100644 index 00000000000..2d92c49bf37 --- /dev/null +++ b/packages/rs-drive/src/drive/tokens/system/fetch_token_total_supply/v0/mod.rs @@ -0,0 +1,72 @@ +use crate::drive::balances::total_tokens_root_supply_path; +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use crate::util::grove_operations::DirectQueryType; +use crate::util::grove_operations::QueryTarget::QueryTargetValue; +use dpp::balances::credits::TokenAmount; +use dpp::version::PlatformVersion; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg, TreeType}; +use std::collections::HashMap; + +impl Drive { + pub(super) fn fetch_token_total_supply_v0( + &self, + token_id: [u8; 32], + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let mut drive_operations = vec![]; + + self.fetch_token_total_supply_add_to_operations_v0( + token_id, + &mut None, + transaction, + &mut drive_operations, + platform_version, + ) + } + + pub(super) fn fetch_token_total_supply_add_to_operations_v0( + &self, + token_id: [u8; 32], + estimated_costs_only_with_layer_info: &mut Option< + HashMap, + >, + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + // If we only estimate, add estimation costs + if let Some(estimated_costs_only_with_layer_info) = estimated_costs_only_with_layer_info { + // Add your estimation logic similar to add_to_system_credits_operations_v0 + // For example: + Self::add_estimation_costs_for_token_total_supply( + estimated_costs_only_with_layer_info, + &platform_version.drive, + )?; + } + + let direct_query_type = if estimated_costs_only_with_layer_info.is_none() { + DirectQueryType::StatefulDirectQuery + } else { + DirectQueryType::StatelessDirectQuery { + in_tree_type: TreeType::BigSumTree, + query_target: QueryTargetValue(8), + } + }; + + 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(), + &token_id, + direct_query_type, + transaction, + drive_operations, + &platform_version.drive, + )?; + + Ok(total_token_supply_in_platform) + } +} diff --git a/packages/rs-drive/src/drive/tokens/system/mod.rs b/packages/rs-drive/src/drive/tokens/system/mod.rs index 8d0f456d613..e9fcbd0c304 100644 --- a/packages/rs-drive/src/drive/tokens/system/mod.rs +++ b/packages/rs-drive/src/drive/tokens/system/mod.rs @@ -1,3 +1,6 @@ mod add_to_token_total_supply; mod create_token_trees; +mod fetch_token_total_aggregated_identity_balances; +mod fetch_token_total_supply; +mod prove_token_total_supply_and_aggregated_identity_balances; mod remove_from_token_total_supply; diff --git a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/mod.rs b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/mod.rs new file mode 100644 index 00000000000..26f2ad535fc --- /dev/null +++ b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/mod.rs @@ -0,0 +1,40 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use dpp::balances::credits::TokenAmount; +use dpp::version::PlatformVersion; +use grovedb::batch::KeyInfoPath; +use grovedb::{EstimatedLayerInformation, TransactionArg}; +use std::collections::HashMap; + +impl Drive { + /// Proves token's total supply and aggregated identity balances + pub fn prove_token_total_supply_and_aggregated_identity_balances( + &self, + token_id: [u8; 32], + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + match platform_version + .drive + .methods + .token + .prove + .total_supply_and_aggregated_identity_balances + { + 0 => self.prove_token_total_supply_and_aggregated_identity_balances_v0( + token_id, + transaction, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "prove_token_total_supply_and_aggregated_identity_balances".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs new file mode 100644 index 00000000000..7dde90d7ef3 --- /dev/null +++ b/packages/rs-drive/src/drive/tokens/system/prove_token_total_supply_and_aggregated_identity_balances/v0/mod.rs @@ -0,0 +1,257 @@ +use crate::drive::Drive; +use crate::error::Error; +use crate::fees::op::LowLevelDriveOperation; +use grovedb::TransactionArg; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn prove_token_total_supply_and_aggregated_identity_balances_v0( + &self, + token_id: [u8; 32], + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result, Error> { + self.prove_token_total_supply_and_aggregated_identity_balances_add_operations_v0( + token_id, + transaction, + &mut vec![], + platform_version, + ) + } + + pub(super) fn prove_token_total_supply_and_aggregated_identity_balances_add_operations_v0( + &self, + token_id: [u8; 32], + transaction: TransactionArg, + drive_operations: &mut Vec, + platform_version: &PlatformVersion, + ) -> Result, Error> { + let combined_path_query = Drive::token_total_supply_and_aggregated_identity_balances_query( + token_id, + platform_version, + )?; + self.grove_get_proved_path_query( + &combined_path_query, + transaction, + drive_operations, + &platform_version.drive, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::util::test_helpers::setup::setup_drive_with_initial_state_structure; + use dpp::block::block_info::BlockInfo; + use dpp::data_contract::accessors::v1::DataContractV1Getters; + use dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationV0; + use dpp::data_contract::associated_token::token_configuration::TokenConfiguration; + use dpp::data_contract::config::v0::DataContractConfigV0; + use dpp::data_contract::config::DataContractConfig; + use dpp::data_contract::v1::DataContractV1; + use dpp::prelude::DataContract; + use dpp::version::PlatformVersion; + use std::collections::BTreeMap; + + #[test] + fn should_prove_token_total_supply_and_aggregated_identity_balances() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + // Create a data contract with a token + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + )]), + }); + + let token_id = contract.token_id(0).expect("expected token at position 0"); + + // Insert contract into Drive + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Mint tokens for identity accounts + let identity_1 = [1u8; 32]; + let identity_2 = [2u8; 32]; + + drive + .token_mint( + token_id.to_buffer(), + identity_1, + 50_000, + true, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to mint tokens for identity 1"); + + drive + .token_mint( + token_id.to_buffer(), + identity_2, + 50_000, + true, + &BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to mint tokens for identity 2"); + + // Prove token total supply and aggregated identity balances + let proof = drive + .prove_token_total_supply_and_aggregated_identity_balances( + token_id.to_buffer(), + None, + platform_version, + ) + .expect("should not error when proving total supply and balances"); + + // Verify proof + let (root_hash, total_single_token_balance) = + Drive::verify_token_total_supply_and_aggregated_identity_balance( + proof.as_slice(), + token_id.to_buffer(), + false, + platform_version, + ) + .expect("expected proof verification to succeed"); + + // Assert root hash is not empty + assert!(!root_hash.is_empty(), "expected a valid root hash"); + + // Assert total supply matches aggregated identity balances + // The contract was created with 100k, then 50k to each identity + assert_eq!( + total_single_token_balance.token_supply, 200_000, + "unexpected token supply" + ); + assert_eq!( + total_single_token_balance.aggregated_token_account_balances, 200_000, + "unexpected aggregated token account balances" + ); + + // Assert that the total balance is valid + assert!( + total_single_token_balance + .ok() + .expect("expected total balance to be valid"), + "unexpected total balance validation failure" + ); + } + + #[test] + fn should_prove_token_total_supply_and_aggregated_identity_balances_for_empty_token() { + let drive = setup_drive_with_initial_state_structure(None); + + let platform_version = PlatformVersion::latest(); + + // Create a data contract with a token + let contract = DataContract::V1(DataContractV1 { + id: Default::default(), + version: 0, + owner_id: Default::default(), + document_types: Default::default(), + metadata: None, + config: DataContractConfig::V0(DataContractConfigV0 { + can_be_deleted: false, + readonly: false, + keeps_history: false, + documents_keep_history_contract_default: false, + documents_mutable_contract_default: false, + documents_can_be_deleted_contract_default: false, + requires_identity_encryption_bounded_key: None, + requires_identity_decryption_bounded_key: None, + }), + schema_defs: None, + groups: Default::default(), + tokens: BTreeMap::from([( + 0, + TokenConfiguration::V0(TokenConfigurationV0::default_most_restrictive()), + )]), + }); + + let token_id = contract.token_id(0).expect("expected token at position 0"); + + // Insert contract into Drive + drive + .insert_contract( + &contract, + BlockInfo::default(), + true, + None, + platform_version, + ) + .expect("expected to insert contract"); + + // Prove token total supply and aggregated identity balances for a token with no supply + let proof = drive + .prove_token_total_supply_and_aggregated_identity_balances_v0( + token_id.to_buffer(), + None, + platform_version, + ) + .expect("should not error when proving total supply and balances"); + + // Verify proof + let (root_hash, total_single_token_balance) = + Drive::verify_token_total_supply_and_aggregated_identity_balance( + proof.as_slice(), + token_id.to_buffer(), + false, + platform_version, + ) + .expect("expected proof verification to succeed"); + + // Assert root hash is not empty + assert!(!root_hash.is_empty(), "expected a valid root hash"); + + // Assert total supply matches aggregated identity balances + assert_eq!( + total_single_token_balance.token_supply, 0, + "unexpected token supply" + ); + assert_eq!( + total_single_token_balance.aggregated_token_account_balances, 0, + "unexpected aggregated token account balances" + ); + + // Assert that the total balance is valid + assert!( + total_single_token_balance + .ok() + .expect("expected total balance to be valid"), + "unexpected total balance validation failure" + ); + } +} diff --git a/packages/rs-drive/src/error/proof.rs b/packages/rs-drive/src/error/proof.rs index 504aa11f559..b2c14a57ed7 100644 --- a/packages/rs-drive/src/error/proof.rs +++ b/packages/rs-drive/src/error/proof.rs @@ -30,6 +30,13 @@ pub enum ProofError { #[error("incorrect proof error: {0}")] IncorrectProof(String), + /// The proof returned is said to be valid, data is what we asked for, but is not what was + /// expected, for example we ask for token balance, and we get that the token does not exist. + /// UnexpectedResultProof is most likely is a User error. + /// IncorrectProof is most likely a system error. + #[error("unexpected result in proof error: {0}")] + UnexpectedResultProof(String), + /// The transition we are trying to prove was executed is invalid #[error("invalid transition error: {0}")] InvalidTransition(String), @@ -85,5 +92,6 @@ fn get_error_code(error: &ProofError) -> u32 { ProofError::ErrorRetrievingContract(_) => 6010, ProofError::InvalidMetadata(_) => 6011, ProofError::MissingContextRequirement(_) => 6012, + ProofError::UnexpectedResultProof(_) => 6013, } } diff --git a/packages/rs-drive/src/verify/tokens/mod.rs b/packages/rs-drive/src/verify/tokens/mod.rs index 9110a47eed3..91e8d10e099 100644 --- a/packages/rs-drive/src/verify/tokens/mod.rs +++ b/packages/rs-drive/src/verify/tokens/mod.rs @@ -1,2 +1,6 @@ +mod verify_token_balances_for_identity_id; mod verify_token_balances_for_identity_ids; +mod verify_token_infos_for_identity_id; mod verify_token_infos_for_identity_ids; +mod verify_token_statuses; +mod verify_token_total_supply_and_aggregated_identity_balance; diff --git a/packages/rs-drive/src/verify/tokens/verify_token_balances_for_identity_id/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_balances_for_identity_id/mod.rs new file mode 100644 index 00000000000..dd2c0888597 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_balances_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 balances of tokens held by a specific identity using a cryptographic proof. + /// + /// This method checks the cryptographic proof to verify the balances of a list of tokens + /// associated with the given identity ID. It dispatches to version-specific implementations + /// based on the platform version. + /// + /// # Parameters + /// - `proof`: The cryptographic proof to verify. + /// - `token_ids`: A list of token IDs to verify (each a 32-byte array). + /// - `identity_id`: The unique identifier of the identity (32-byte array). + /// - `verify_subset_of_proof`: Whether to verify only a subset of the proof. + /// - `platform_version`: The current platform version. + /// + /// # Returns + /// - `Ok((RootHash, T))`: + /// - `RootHash`: The verified root hash of the database. + /// - `T`: A collection of `(token ID, token balance)` pairs. + /// + /// # Errors + /// - `Error::Drive(DriveError::UnknownVersionMismatch)`: + /// - Occurs when the platform version does not match any known version for this method. + /// - `Error::Proof(ProofError::WrongElementCount)`: + /// - If the number of elements in the proof does not match the number of token IDs. + /// - `Error::Proof(ProofError::IncorrectValueSize)`: + /// - If the token ID size or proof value size is invalid. + /// - `Error::Proof(ProofError::InvalidSumItemValue)`: + /// - If the proof element does not represent a valid sum item. + /// - `Error::Proof(ProofError::InvalidItemType)`: + /// - If the proof element is not a sum item as expected for balances. + pub fn verify_token_balances_for_identity_id< + T: FromIterator<(I, Option)>, + I: From<[u8; 32]>, + >( + proof: &[u8], + token_ids: &[[u8; 32]], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + match platform_version + .drive + .methods + .verify + .token + .verify_token_balances_for_identity_id + { + 0 => Self::verify_token_balances_for_identity_id_v0( + proof, + token_ids, + identity_id, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_token_balances_for_identity_id".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_balances_for_identity_id/v0/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_balances_for_identity_id/v0/mod.rs new file mode 100644 index 00000000000..a8943461320 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_balances_for_identity_id/v0/mod.rs @@ -0,0 +1,72 @@ +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_balances_for_identity_id_v0< + T: FromIterator<(I, Option)>, + I: From<[u8; 32]>, + >( + proof: &[u8], + token_ids: &[[u8; 32]], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + let path_query = Self::token_balances_for_identity_id_query(token_ids, identity_id); + let (root_hash, 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() == token_ids.len() { + let values = proved_key_values + .into_iter() + .map(|proved_key_value| { + let token_id: [u8; 32] = proved_key_value + .0 + .get(2) + .ok_or(Error::Proof(ProofError::IncorrectValueSize( + "path should have at least 3 elements in returned proof", + )))? + .clone() + .try_into() + .map_err(|_| { + Error::Proof(ProofError::IncorrectValueSize("token id size")) + })?; + match proved_key_value.2 { + Some(SumItem(value, ..)) => { + Ok((token_id.into(), Some(value as TokenAmount))) + } + None => Ok((token_id.into(), None)), + _ => Err(Error::Proof(ProofError::IncorrectValueSize( + "proof did not point to a sum item", + ))), + } + }) + .collect::>()?; + Ok((root_hash, values)) + } else { + Err(Error::Proof(ProofError::WrongElementCount { + expected: token_ids.len(), + got: proved_key_values.len(), + })) + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_infos_for_identity_id/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_infos_for_identity_id/mod.rs new file mode 100644 index 00000000000..e0a39d6263a --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_infos_for_identity_id/mod.rs @@ -0,0 +1,74 @@ +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 token information for a specific identity using a cryptographic proof. + /// + /// This method retrieves information about the specified tokens for a given identity ID from the + /// cryptographic proof. It dispatches to version-specific implementations based on the platform version. + /// + /// # Parameters + /// - `proof`: The cryptographic proof to verify. + /// - `token_ids`: A list of token IDs to verify (each a 32-byte array). + /// - `identity_id`: The unique identifier of the identity (32-byte array). + /// - `verify_subset_of_proof`: Whether to verify only a subset of the proof. + /// - `platform_version`: The current platform version. + /// + /// # Returns + /// - `Ok((RootHash, T))`: + /// - `RootHash`: The verified root hash of the database. + /// - `T`: A collection of `(token ID, token info)` pairs. + /// + /// # Errors + /// - `Error::Drive(DriveError::UnknownVersionMismatch)`: + /// - Occurs when the platform version does not match any known version for this method. + /// - `Error::Proof(ProofError::WrongElementCount)`: + /// - If the number of elements in the proof does not match the number of token IDs. + /// - `Error::Proof(ProofError::IncorrectValueSize)`: + /// - If the token ID size or proof value size is invalid. + /// - `Error::Proof(ProofError::DeserializationFailed)`: + /// - If the token info cannot be deserialized from the proof. + /// - `Error::Proof(ProofError::InvalidItemType)`: + /// - If the proof element is not an expected item type (e.g., `Item`). + pub fn verify_token_infos_for_identity_id< + T: FromIterator<(I, Option)>, + I: From<[u8; 32]>, + >( + proof: &[u8], + token_ids: &[[u8; 32]], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + match platform_version + .drive + .methods + .verify + .token + .verify_token_infos_for_identity_id + { + 0 => Self::verify_token_infos_for_identity_id_v0( + proof, + token_ids, + identity_id, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_token_infos_for_identity_id".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_infos_for_identity_id/v0/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_infos_for_identity_id/v0/mod.rs new file mode 100644 index 00000000000..e94d164d311 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_infos_for_identity_id/v0/mod.rs @@ -0,0 +1,74 @@ +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_infos_for_identity_id_v0< + T: FromIterator<(I, Option)>, + I: From<[u8; 32]>, + >( + proof: &[u8], + token_ids: &[[u8; 32]], + identity_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + let path_query = Self::token_infos_for_identity_id_query(token_ids, identity_id); + let (root_hash, 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() == token_ids.len() { + let values = proved_key_values + .into_iter() + .map(|proved_key_value| { + let token_id: [u8; 32] = proved_key_value + .0 + .get(2) + .ok_or(Error::Proof(ProofError::IncorrectProof( + "path should have at least 3 elements in returned proof".to_string(), + )))? + .clone() + .try_into() + .map_err(|_| { + Error::Proof(ProofError::IncorrectValueSize("token id size")) + })?; + match proved_key_value.2 { + Some(Item(value, ..)) => Ok(( + token_id.into(), + Some(IdentityTokenInfo::deserialize_from_bytes(&value)?), + )), + None => Ok((token_id.into(), None)), + _ => Err(Error::Proof(ProofError::IncorrectProof( + "proof did not point to an item as expected for token info".to_string(), + ))), + } + }) + .collect::>()?; + Ok((root_hash, values)) + } else { + Err(Error::Proof(ProofError::WrongElementCount { + expected: token_ids.len(), + got: proved_key_values.len(), + })) + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_statuses/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_statuses/mod.rs new file mode 100644 index 00000000000..7b7e9b82878 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_statuses/mod.rs @@ -0,0 +1,68 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use dpp::tokens::info::IdentityTokenInfo; +use dpp::tokens::status::TokenStatus; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the statuses of multiple tokens using a cryptographic proof. + /// + /// This method validates the cryptographic proof to retrieve the statuses of the specified token IDs. + /// It dispatches to version-specific implementations based on the provided platform version. + /// + /// # Parameters + /// - `proof`: The cryptographic proof to verify. + /// - `token_ids`: A list of token IDs to verify (each a 32-byte array). + /// - `verify_subset_of_proof`: Whether to verify only a subset of the proof. + /// - `platform_version`: The current platform version. + /// + /// # Returns + /// - `Ok((RootHash, T))`: + /// - `RootHash`: The verified root hash of the database. + /// - `T`: A collection of `(token ID, token status)` pairs. + /// + /// # Errors + /// - `Error::Drive(DriveError::UnknownVersionMismatch)`: + /// - Occurs when the platform version does not match any known version for this method. + /// - `Error::Proof(ProofError::WrongElementCount)`: + /// - If the number of elements in the proof does not match the number of token IDs. + /// - `Error::Proof(ProofError::IncorrectValueSize)`: + /// - If the token ID size or proof value size is invalid. + /// - `Error::Proof(ProofError::DeserializationFailed)`: + /// - If the token status cannot be deserialized from the proof. + /// - `Error::Proof(ProofError::InvalidItemType)`: + /// - If the proof element is not an expected item type (e.g., `Item`). + pub fn verify_token_statuses)>, I: From<[u8; 32]>>( + proof: &[u8], + token_ids: &[[u8; 32]], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + match platform_version + .drive + .methods + .verify + .token + .verify_token_statuses + { + 0 => Self::verify_token_statuses_v0( + proof, + token_ids, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_token_statuses".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_statuses/v0/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_statuses/v0/mod.rs new file mode 100644 index 00000000000..aa6ad189c02 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_statuses/v0/mod.rs @@ -0,0 +1,65 @@ +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_statuses_v0< + T: FromIterator<(I, Option)>, + I: From<[u8; 32]>, + >( + proof: &[u8], + token_ids: &[[u8; 32]], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, T), Error> { + let path_query = Self::token_statuses_query(token_ids); + let (root_hash, 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() == token_ids.len() { + let values = proved_key_values + .into_iter() + .map(|proved_key_value| { + let token_id: [u8; 32] = proved_key_value.1.try_into().map_err(|_| { + Error::Proof(ProofError::IncorrectValueSize("token id size")) + })?; + match proved_key_value.2 { + Some(Item(value, ..)) => Ok(( + token_id.into(), + Some(TokenStatus::deserialize_from_bytes(&value)?), + )), + None => Ok((token_id.into(), None)), + _ => Err(Error::Proof(ProofError::IncorrectValueSize( + "proof did not point to an item as expected for token info", + ))), + } + }) + .collect::>()?; + Ok((root_hash, values)) + } else { + Err(Error::Proof(ProofError::WrongElementCount { + expected: token_ids.len(), + got: proved_key_values.len(), + })) + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/mod.rs new file mode 100644 index 00000000000..1c2b950104d --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/mod.rs @@ -0,0 +1,69 @@ +mod v0; + +use crate::drive::Drive; +use crate::error::drive::DriveError; +use dpp::balances::credits::TokenAmount; +use dpp::balances::total_single_token_balance::TotalSingleTokenBalance; +use dpp::balances::total_tokens_balance::TotalTokensBalance; + +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::version::PlatformVersion; + +impl Drive { + /// Verifies the total token supply and aggregated identity balances for a given token. + /// + /// This method checks the cryptographic proof to verify the total supply of a token and the + /// aggregated balances of identities associated with that token. It dispatches to version-specific + /// implementations based on the provided platform version. + /// + /// # Parameters + /// - `proof`: The cryptographic proof to verify. + /// - `token_id`: The unique identifier of the token (32-byte array). + /// - `verify_subset_of_proof`: Whether to verify only a subset of the proof. + /// - `platform_version`: The current platform version. + /// + /// # Returns + /// - `Ok((RootHash, TotalSingleTokenBalance))`: + /// - `RootHash`: The verified root hash of the database. + /// - `TotalSingleTokenBalance`: The total supply and aggregated identity balances of the token. + /// + /// # Errors + /// - `Error::Drive(DriveError::UnknownVersionMismatch)`: + /// - Occurs when the platform version does not match any known version for this method. + /// - `Error::Proof(ProofError::UnexpectedResultProof)`: + /// - If the token does not exist in the proof. + /// - If the token's supply is not found in the proof. + /// - `Error::Proof(ProofError::WrongElementCount)`: + /// - If the proof does not contain exactly two expected elements (total supply and aggregated balances). + /// - `Error::Proof(ProofError::InvalidSumItemValue)`: + /// - If the retrieved proof element is not a valid sum item. + pub fn verify_token_total_supply_and_aggregated_identity_balance( + proof: &[u8], + token_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, TotalSingleTokenBalance), Error> { + match platform_version + .drive + .methods + .verify + .token + .verify_token_total_supply_and_aggregated_identity_balance + { + 0 => Self::verify_token_total_supply_and_aggregated_identity_balance_v0( + proof, + token_id, + verify_subset_of_proof, + platform_version, + ), + version => Err(Error::Drive(DriveError::UnknownVersionMismatch { + method: "verify_token_total_supply_and_aggregated_identity_balance".to_string(), + known_versions: vec![0], + received: version, + })), + } + } +} diff --git a/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/v0/mod.rs b/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/v0/mod.rs new file mode 100644 index 00000000000..5fd81f013f3 --- /dev/null +++ b/packages/rs-drive/src/verify/tokens/verify_token_total_supply_and_aggregated_identity_balance/v0/mod.rs @@ -0,0 +1,73 @@ +use crate::drive::Drive; + +use crate::error::proof::ProofError; +use crate::error::Error; + +use crate::verify::RootHash; + +use dpp::balances::total_single_token_balance::TotalSingleTokenBalance; +use dpp::prelude::Identifier; +use grovedb::GroveDb; +use platform_version::version::PlatformVersion; + +impl Drive { + pub(super) fn verify_token_total_supply_and_aggregated_identity_balance_v0( + proof: &[u8], + token_id: [u8; 32], + verify_subset_of_proof: bool, + platform_version: &PlatformVersion, + ) -> Result<(RootHash, TotalSingleTokenBalance), Error> { + let path_query = Self::token_total_supply_and_aggregated_identity_balances_query( + token_id, + platform_version, + )?; + let (root_hash, 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() == 2 { + let ( + _aggregated_identity_balances_path, + _aggregated_identity_balances_key, + Some(aggregated_identity_balances_element), + ) = proved_key_values.get(0).unwrap() + else { + return Err(Error::Proof(ProofError::UnexpectedResultProof(format!( + "Token {} most likely does not exist", + Identifier::new(token_id) + )))); + }; + let (_total_supply_path, _total_supply_key, Some(total_supply_element)) = + proved_key_values.get(1).unwrap() + else { + return Err(Error::Proof(ProofError::UnexpectedResultProof(format!( + "Token {} has no known supply", + Identifier::new(token_id) + )))); + }; + + Ok(( + root_hash, + TotalSingleTokenBalance { + token_supply: total_supply_element.as_sum_item_value()?, + aggregated_token_account_balances: aggregated_identity_balances_element + .as_sum_tree_value()?, + }, + )) + } else { + Err(Error::Proof(ProofError::WrongElementCount { + expected: 2, + got: proved_key_values.len(), + })) + } + } +} diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index e7e66db2dfb..f7b1c45da6e 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "1.0.63" } bincode = { version = "2.0.0-rc.3" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev= "d8ae2d95f56381b4d104d3983b2f11ae3a968dc7" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev= "e3ac3f7883bb0f2e25936153f76d447b42ebc9c0" } once_cell = "1.19.0" [features] diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs index 7faf75df460..bb2e8559b74 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs @@ -30,6 +30,7 @@ pub struct DriveAbciQueryTokenVersions { pub identities_token_infos: FeatureVersionBounds, pub identity_token_infos: FeatureVersionBounds, pub token_statuses: FeatureVersionBounds, + pub token_total_supply: FeatureVersionBounds, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs index e4bb8e03fb3..2db3bde39c3 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs @@ -99,6 +99,11 @@ pub const DRIVE_ABCI_QUERY_VERSIONS_V1: DriveAbciQueryVersions = DriveAbciQueryV max_version: 0, default_current_version: 0, }, + token_total_supply: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }, validator_queries: DriveAbciQueryValidatorVersions { proposed_block_counts_by_evonode_ids: FeatureVersionBounds { diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/mod.rs index e10ff813167..e7341f1f6fd 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/mod.rs @@ -20,6 +20,8 @@ pub struct DriveTokenFetchMethodVersions { pub identities_token_infos: FeatureVersion, pub token_statuses: FeatureVersion, pub token_status: FeatureVersion, + pub token_total_supply: FeatureVersion, + pub token_total_aggregated_identity_balances: FeatureVersion, } #[derive(Clone, Debug, Default)] @@ -31,6 +33,7 @@ pub struct DriveTokenProveMethodVersions { pub identity_token_infos: FeatureVersion, pub identities_token_infos: FeatureVersion, pub token_statuses: FeatureVersion, + pub total_supply_and_aggregated_identity_balances: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/v1.rs index 9b8f9d83adf..0d552a43136 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_token_method_versions/v1.rs @@ -13,6 +13,8 @@ pub const DRIVE_TOKEN_METHOD_VERSIONS_V1: DriveTokenMethodVersions = DriveTokenM identities_token_infos: 0, token_statuses: 0, token_status: 0, + token_total_supply: 0, + token_total_aggregated_identity_balances: 0, }, prove: DriveTokenProveMethodVersions { identity_token_balance: 0, @@ -22,6 +24,7 @@ pub const DRIVE_TOKEN_METHOD_VERSIONS_V1: DriveTokenMethodVersions = DriveTokenM identity_token_infos: 0, identities_token_infos: 0, token_statuses: 0, + total_supply_and_aggregated_identity_balances: 0, }, update: DriveTokenUpdateMethodVersions { create_token_trees: 0, 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 e23fd54276e..5b2309458d4 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 @@ -48,6 +48,9 @@ pub struct DriveVerifyTokenMethodVersions { pub verify_token_balances_for_identity_ids: FeatureVersion, pub verify_token_balances_for_identity_id: FeatureVersion, pub verify_token_infos_for_identity_ids: FeatureVersion, + pub verify_token_infos_for_identity_id: FeatureVersion, + pub verify_token_statuses: FeatureVersion, + pub verify_token_total_supply_and_aggregated_identity_balance: 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 56813f3b0b0..0daa0cdd0b6 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,9 @@ pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVeri verify_token_balances_for_identity_ids: 0, verify_token_balances_for_identity_id: 0, verify_token_infos_for_identity_ids: 0, + verify_token_infos_for_identity_id: 0, + verify_token_statuses: 0, + verify_token_total_supply_and_aggregated_identity_balance: 0, }, single_document: DriveVerifySingleDocumentMethodVersions { verify_proof: 0, diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index 26c64a4d7c7..9dc5f2d253c 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -237,6 +237,11 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { max_version: 0, default_current_version: 0, }, + token_total_supply: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }, validator_queries: DriveAbciQueryValidatorVersions { proposed_block_counts_by_evonode_ids: FeatureVersionBounds {