Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion packages/dapi-grpc/protos/platform/v0/platform.proto
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ service Platform {
rpc getTokenPreProgrammedDistributions(
GetTokenPreProgrammedDistributionsRequest)
returns (GetTokenPreProgrammedDistributionsResponse);
rpc getTokenPerpetualDistributionLastClaim(
GetTokenPerpetualDistributionLastClaimRequest)
returns (GetTokenPerpetualDistributionLastClaimResponse);
rpc getTokenTotalSupply(GetTokenTotalSupplyRequest)
returns (GetTokenTotalSupplyResponse);
rpc getGroupInfo(GetGroupInfoRequest) returns (GetGroupInfoResponse);
Expand Down Expand Up @@ -1476,12 +1479,63 @@ message GetTokenPreProgrammedDistributionsResponse {
oneof version { GetTokenPreProgrammedDistributionsResponseV0 v0 = 1; }
}

// ──────────────────────────────────────────────────────────────────────────────
// Get the last‑claim information for a token’s perpetual distribution
// ──────────────────────────────────────────────────────────────────────────────

message GetTokenPerpetualDistributionLastClaimRequest {

message ContractTokenInfo {
bytes contract_id = 1;
// the token contract position
uint32 token_contract_position = 2;
}

message GetTokenPerpetualDistributionLastClaimRequestV0 {
// 32‑byte token identifier
bytes token_id = 1;

// This should be set if you wish to get back the last claim info as a specific type
optional ContractTokenInfo contract_info = 2;

// Identity whose last‑claim timestamp is requested
bytes identity_id = 4;
// Return GroveDB / signature proof instead of raw value
bool prove = 5;
}

oneof version { GetTokenPerpetualDistributionLastClaimRequestV0 v0 = 1; }
}

message GetTokenPerpetualDistributionLastClaimResponse {
message GetTokenPerpetualDistributionLastClaimResponseV0 {
message LastClaimInfo {

oneof paid_at {
uint64 timestamp_ms = 1 [ jstype = JS_STRING ]; // Unix epoch, ms
uint64 block_height = 2 [ jstype = JS_STRING ]; // Core‑block height
uint32 epoch = 3; // Epoch index
bytes raw_bytes = 4; // Arbitrary encoding
}
}

oneof result {
LastClaimInfo last_claim = 1; // Direct answer
Proof proof = 2; // GroveDB / quorum proof
}

ResponseMetadata metadata = 3; // Chain context
}

oneof version { GetTokenPerpetualDistributionLastClaimResponseV0 v0 = 1; }
}

message GetTokenTotalSupplyRequest {
message GetTokenTotalSupplyRequestV0 {
bytes token_id = 1;
bool prove = 2;
}
oneof version { GetTokenTotalSupplyRequestV0 v0 = 1; }
oneof version {GetTokenTotalSupplyRequestV0 v0 = 1;}
}

message GetTokenTotalSupplyResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ pub enum DistributionFunction {
/// The emission at period `x` is given by:
///
/// ```text
/// f(x) = (a * (x - start_moment) / d) + starting_amount
/// f(x) = (a * (x - start_step) / d) + starting_amount
/// ```
///
/// # Parameters
Expand Down Expand Up @@ -372,7 +372,7 @@ pub enum DistributionFunction {
/// The emission at period `x` is given by:
///
/// ```text
/// f(x) = (a * e^(m * (x - s) / n)) / d + b
/// f(x) = (a * e^(m * (x - s + o) / n)) / d + b
/// ```
///
/// # Parameters
Expand Down Expand Up @@ -431,7 +431,7 @@ pub enum DistributionFunction {
/// The emission at period `x` is computed as:
///
/// ```text
/// f(x) = (a * log(m * (x - s + o) / n)) / d + b
/// f(x) = (a * ln(m * (x - s + o) / n)) / d + b
/// ```
///
/// # Parameters
Expand Down
13 changes: 13 additions & 0 deletions packages/rs-drive-abci/src/query/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use dapi_grpc::platform::v0::{
GetProtocolVersionUpgradeStateResponse, GetProtocolVersionUpgradeVoteStatusRequest,
GetProtocolVersionUpgradeVoteStatusResponse, GetStatusRequest, GetStatusResponse,
GetTokenDirectPurchasePricesRequest, GetTokenDirectPurchasePricesResponse,
GetTokenPerpetualDistributionLastClaimRequest, GetTokenPerpetualDistributionLastClaimResponse,
GetTokenPreProgrammedDistributionsRequest, GetTokenPreProgrammedDistributionsResponse,
GetTokenStatusesRequest, GetTokenStatusesResponse, GetTokenTotalSupplyRequest,
GetTokenTotalSupplyResponse, GetTotalCreditsInPlatformRequest,
Expand Down Expand Up @@ -764,6 +765,18 @@ impl PlatformService for QueryService {
)
.await
}

async fn get_token_perpetual_distribution_last_claim(
&self,
request: Request<GetTokenPerpetualDistributionLastClaimRequest>,
) -> Result<Response<GetTokenPerpetualDistributionLastClaimResponse>, Status> {
self.handle_blocking_query(
request,
Platform::<DefaultCoreRPC>::query_token_perpetual_distribution_last_claim,
"get_token_perpetual_distribution_last_claim",
)
.await
}
}

#[async_trait]
Expand Down
1 change: 1 addition & 0 deletions packages/rs-drive-abci/src/query/token_queries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod identities_token_infos;
mod identity_token_balances;
mod identity_token_infos;
mod token_direct_purchase_prices;
mod token_perpetual_distribution_last_claim;
mod token_pre_programmed_distributions;
mod token_status;
mod token_total_supply;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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_perpetual_distribution_last_claim_request::Version as RequestVersion;
use dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::Version as ResponseVersion;
use dapi_grpc::platform::v0::{
GetTokenPerpetualDistributionLastClaimRequest, GetTokenPerpetualDistributionLastClaimResponse,
};
use dpp::version::PlatformVersion;

mod v0;

impl<C> Platform<C> {
/// Query the last perpetual distribution claim for a given token and identity
pub fn query_token_perpetual_distribution_last_claim(
&self,
GetTokenPerpetualDistributionLastClaimRequest { version }: GetTokenPerpetualDistributionLastClaimRequest,
platform_state: &PlatformState,
platform_version: &PlatformVersion,
) -> Result<QueryValidationResult<GetTokenPerpetualDistributionLastClaimResponse>, Error> {
let Some(version) = version else {
return Ok(QueryValidationResult::new_with_error(
QueryError::DecodingError(
"could not decode token perpetual distribution last claim query".to_string(),
),
));
};

let feature_version_bounds = &platform_version
.drive_abci
.query
.token_queries
.token_perpetual_distribution_last_claim;

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_perpetual_distribution_last_claim".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_perpetual_distribution_last_claim_v0(
request_v0,
platform_state,
platform_version,
)?;
Ok(result.map(
|response_v0| GetTokenPerpetualDistributionLastClaimResponse {
version: Some(ResponseVersion::V0(response_v0)),
},
))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
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_perpetual_distribution_last_claim_request::{ContractTokenInfo, GetTokenPerpetualDistributionLastClaimRequestV0};
use dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::{
get_token_perpetual_distribution_last_claim_response_v0,
GetTokenPerpetualDistributionLastClaimResponseV0,
};
use dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::{last_claim_info, LastClaimInfo};
use dpp::check_validation_result_with_data;
use dpp::data_contract::accessors::v1::DataContractV1Getters;
use dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters;
use dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters;
use dpp::data_contract::associated_token::token_perpetual_distribution::methods::v0::TokenPerpetualDistributionV0Accessors;
use dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment;
use dpp::identifier::Identifier;
use dpp::validation::ValidationResult;
use dpp::version::PlatformVersion;

impl<C> Platform<C> {
pub(super) fn query_token_perpetual_distribution_last_claim_v0(
&self,
GetTokenPerpetualDistributionLastClaimRequestV0 {
token_id,
contract_info,
identity_id,
prove,
}: GetTokenPerpetualDistributionLastClaimRequestV0,
platform_state: &PlatformState,
platform_version: &PlatformVersion,
) -> Result<QueryValidationResult<GetTokenPerpetualDistributionLastClaimResponseV0>, Error>
{
// ── Basic argument validation ──────────────────────────────────────────
let token_id: Identifier =
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 identity_id: Identifier =
check_validation_result_with_data!(identity_id.try_into().map_err(|_| {
QueryError::InvalidArgument(
"identity_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_perpetual_distribution_last_paid_moment(
token_id.into_buffer(),
identity_id,
None,
platform_version,
));

GetTokenPerpetualDistributionLastClaimResponseV0 {
result: Some(
get_token_perpetual_distribution_last_claim_response_v0::Result::Proof(
self.response_proof_v0(platform_state, proof),
),
),
metadata: Some(self.response_metadata_v0(platform_state)),
}
} else if let Some(ContractTokenInfo {
contract_id,
token_contract_position,
}) = contract_info
{
let contract_id: Identifier =
check_validation_result_with_data!(contract_id.try_into().map_err(|_| {
QueryError::InvalidArgument(
"contract_id must be a valid identifier (32 bytes long)".to_string(),
)
}));
let Some(contract) = check_validation_result_with_data!(self
.drive
.get_contract_with_fetch_info(
contract_id.into_buffer(),
false,
None,
platform_version
)
.map_err(QueryError::Drive))
else {
return Ok(QueryValidationResult::new_with_error(QueryError::NotFound(
format!("contract with identifier {} not found", contract_id),
)));
};

if token_contract_position > u16::MAX as u32 {
return Ok(QueryValidationResult::new_with_error(
QueryError::InvalidArgument(
"token_contract_position must be less than u16::MAX".to_string(),
),
));
}

let token = check_validation_result_with_data!(contract
.contract
.expected_token_configuration(token_contract_position as u16)
.map_err(QueryError::Protocol));
let token_distribution_rules = token.distribution_rules();
let Some(token_perpetual_distribution_rules) =
token_distribution_rules.perpetual_distribution()
else {
return Ok(QueryValidationResult::new_with_error(
QueryError::InvalidArgument(format!(
"contract with identifier {} does not have perpetual distribution rules",
contract_id
)),
));
};

let paid_at = self
.drive
.fetch_perpetual_distribution_last_paid_moment(
token_id.into_buffer(),
identity_id,
token_perpetual_distribution_rules.distribution_type(),
None,
platform_version,
)?
.map(|moment| match moment {
RewardDistributionMoment::BlockBasedMoment(height) => {
last_claim_info::PaidAt::BlockHeight(height)
}
RewardDistributionMoment::TimeBasedMoment(timestamp) => {
last_claim_info::PaidAt::TimestampMs(timestamp)
}
RewardDistributionMoment::EpochBasedMoment(epoch) => {
last_claim_info::PaidAt::Epoch(epoch as u32)
}
});

GetTokenPerpetualDistributionLastClaimResponseV0 {
result: Some(
get_token_perpetual_distribution_last_claim_response_v0::Result::LastClaim(
LastClaimInfo { paid_at },
),
),
metadata: Some(self.response_metadata_v0(platform_state)),
}
} else {
let paid_at = self
.drive
.fetch_perpetual_distribution_last_paid_moment_raw(
token_id.into_buffer(),
identity_id,
None,
platform_version,
)?
.map(|moment| last_claim_info::PaidAt::RawBytes(moment));

Check failure on line 157 in packages/rs-drive-abci/src/query/token_queries/token_perpetual_distribution_last_claim/v0/mod.rs

View workflow job for this annotation

GitHub Actions / Rust packages (drive-abci) / Linting

redundant closure

error: redundant closure --> packages/rs-drive-abci/src/query/token_queries/token_perpetual_distribution_last_claim/v0/mod.rs:157:22 | 157 | .map(|moment| last_claim_info::PaidAt::RawBytes(moment)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `last_claim_info::PaidAt::RawBytes` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure = note: `-D clippy::redundant-closure` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]`

GetTokenPerpetualDistributionLastClaimResponseV0 {
result: Some(
get_token_perpetual_distribution_last_claim_response_v0::Result::LastClaim(
LastClaimInfo { paid_at },
),
),
metadata: Some(self.response_metadata_v0(platform_state)),
}
};

Ok(QueryValidationResult::new_with_data(response))
}
}
Loading
Loading