drive_proof_verifier/proof/
token_pre_programmed_distributions.rs1use crate::error::MapGroveDbError;
2use crate::verify::{
3 supported_grovedb_proof_bytes, verify_page_drive_proves, verify_tenderdash_proof,
4};
5use crate::{types::TokenPreProgrammedDistributions, ContextProvider, Error};
6use dapi_grpc::platform::v0::{
7 get_token_pre_programmed_distributions_request, GetTokenPreProgrammedDistributionsRequest,
8 GetTokenPreProgrammedDistributionsResponse, Proof, ResponseMetadata,
9};
10use dapi_grpc::platform::VersionedGrpcResponse;
11use dpp::dashcore::Network;
12use dpp::prelude::Identifier;
13use dpp::version::PlatformVersion;
14use drive::drive::tokens::distribution::queries::QueryPreProgrammedDistributionStartAt;
15use drive::drive::Drive;
16
17use super::FromProof;
18
19impl FromProof<GetTokenPreProgrammedDistributionsRequest> for TokenPreProgrammedDistributions {
20 type Request = GetTokenPreProgrammedDistributionsRequest;
21 type Response = GetTokenPreProgrammedDistributionsResponse;
22
23 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
24 request: I,
25 response: O,
26 _network: Network,
27 platform_version: &PlatformVersion,
28 provider: &'a dyn ContextProvider,
29 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
30 where
31 Self: Sized + 'a,
32 {
33 let request: Self::Request = request.into();
34 let response: Self::Response = response.into();
35
36 let get_token_pre_programmed_distributions_request::Version::V0(req_v0) =
37 request.version.ok_or(Error::EmptyVersion)?;
38
39 let token_id: [u8; 32] =
40 req_v0
41 .token_id
42 .as_slice()
43 .try_into()
44 .map_err(|_| Error::RequestError {
45 error: "token_id must be 32 bytes".into(),
46 })?;
47
48 let start_at = match req_v0.start_at_info {
51 Some(start_at_info) => {
52 let start_at_recipient = match start_at_info.start_recipient {
53 Some(recipient_bytes) => {
54 let recipient_id =
55 Identifier::from_bytes(&recipient_bytes).map_err(|_| {
56 Error::RequestError {
57 error: "start_recipient must be 32 bytes".into(),
58 }
59 })?;
60 let included = start_at_info.start_recipient_included.unwrap_or(true);
62 Some((recipient_id, included))
63 }
64 None => None,
65 };
66
67 Some((start_at_info.start_time_ms, start_at_recipient))
68 }
69 None => None,
70 };
71
72 let limit = req_v0
73 .limit
74 .map(|l| {
75 u16::try_from(l).map_err(|_| Error::RequestError {
76 error: "limit exceeds u16::MAX".into(),
77 })
78 })
79 .transpose()?;
80
81 let metadata = response
82 .metadata()
83 .or(Err(Error::EmptyResponseMetadata))?
84 .clone();
85
86 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
87
88 let grovedb_proof = supported_grovedb_proof_bytes(&proof)?;
89 let (root_hash, result): ([u8; 32], TokenPreProgrammedDistributions) =
90 verify_page_drive_proves(limit, |limit| {
91 Drive::verify_token_pre_programmed_distributions(
92 grovedb_proof,
93 token_id,
94 start_at.map(|(start_at_time, start_at_recipient)| {
95 QueryPreProgrammedDistributionStartAt {
96 start_at_time,
97 start_at_recipient,
98 }
99 }),
100 limit,
101 false,
102 platform_version,
103 )
104 })
105 .map_drive_error(&proof, &metadata)?;
106
107 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
108
109 if result.0.is_empty() {
110 Ok((None, metadata, proof))
111 } else {
112 Ok((Some(result), metadata, proof))
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::FromProof;
121 use dapi_grpc::platform::v0::get_token_pre_programmed_distributions_request::{
122 get_token_pre_programmed_distributions_request_v0::StartAtInfo,
123 GetTokenPreProgrammedDistributionsRequestV0, Version as ReqVersion,
124 };
125 use dapi_grpc::platform::v0::get_token_pre_programmed_distributions_response::{
126 get_token_pre_programmed_distributions_response_v0::Result as RespResult,
127 GetTokenPreProgrammedDistributionsResponseV0, Version as RespVersion,
128 };
129 use dash_context_provider::ContextProviderError;
130 use dpp::data_contract::TokenConfiguration;
131 use dpp::prelude::{CoreBlockHeight, DataContract};
132 use std::sync::Arc;
133
134 struct UnreachableProvider;
135
136 impl ContextProvider for UnreachableProvider {
137 fn get_data_contract(
138 &self,
139 _id: &Identifier,
140 _pv: &PlatformVersion,
141 ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
142 panic!("should not be called")
143 }
144 fn get_token_configuration(
145 &self,
146 _id: &Identifier,
147 ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
148 panic!("should not be called")
149 }
150 fn get_quorum_public_key(
151 &self,
152 _qt: u32,
153 _qh: [u8; 32],
154 _h: u32,
155 ) -> Result<[u8; 48], ContextProviderError> {
156 panic!("should not be called")
157 }
158 fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
159 panic!("should not be called")
160 }
161 }
162
163 fn pv() -> &'static PlatformVersion {
164 PlatformVersion::latest()
165 }
166
167 fn response_with_proof() -> GetTokenPreProgrammedDistributionsResponse {
168 GetTokenPreProgrammedDistributionsResponse {
169 version: Some(RespVersion::V0(
170 GetTokenPreProgrammedDistributionsResponseV0 {
171 result: Some(RespResult::Proof(Proof::default())),
172 metadata: Some(ResponseMetadata::default()),
173 },
174 )),
175 }
176 }
177
178 fn req_v0_defaults() -> GetTokenPreProgrammedDistributionsRequestV0 {
179 GetTokenPreProgrammedDistributionsRequestV0 {
180 token_id: vec![0u8; 32],
181 start_at_info: None,
182 limit: None,
183 prove: true,
184 }
185 }
186
187 #[test]
188 fn empty_version_when_request_has_no_version() {
189 let request = GetTokenPreProgrammedDistributionsRequest { version: None };
190 let response = response_with_proof();
191 let err = <TokenPreProgrammedDistributions as FromProof<_>>::maybe_from_proof(
192 request,
193 response,
194 Network::Testnet,
195 pv(),
196 &UnreachableProvider,
197 )
198 .unwrap_err();
199 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
200 }
201
202 #[test]
203 fn request_error_when_token_id_wrong_length() {
204 let request = GetTokenPreProgrammedDistributionsRequest {
205 version: Some(ReqVersion::V0(
206 GetTokenPreProgrammedDistributionsRequestV0 {
207 token_id: vec![0u8; 5], ..req_v0_defaults()
209 },
210 )),
211 };
212 let response = response_with_proof();
213 let err = <TokenPreProgrammedDistributions as FromProof<_>>::maybe_from_proof(
214 request,
215 response,
216 Network::Testnet,
217 pv(),
218 &UnreachableProvider,
219 )
220 .unwrap_err();
221 match err {
222 Error::RequestError { error } => assert!(error.contains("token_id"), "got: {error}"),
223 other => panic!("expected RequestError, got: {other:?}"),
224 }
225 }
226
227 #[test]
228 fn request_error_when_start_recipient_wrong_length() {
229 let request = GetTokenPreProgrammedDistributionsRequest {
230 version: Some(ReqVersion::V0(
231 GetTokenPreProgrammedDistributionsRequestV0 {
232 token_id: vec![1u8; 32],
233 start_at_info: Some(StartAtInfo {
234 start_time_ms: 10_000,
235 start_recipient: Some(vec![1u8; 16]), start_recipient_included: Some(true),
237 }),
238 limit: None,
239 prove: true,
240 },
241 )),
242 };
243 let response = response_with_proof();
244 let err = <TokenPreProgrammedDistributions as FromProof<_>>::maybe_from_proof(
245 request,
246 response,
247 Network::Testnet,
248 pv(),
249 &UnreachableProvider,
250 )
251 .unwrap_err();
252 match err {
253 Error::RequestError { error } => {
254 assert!(error.contains("start_recipient"), "got: {error}")
255 }
256 other => panic!("expected RequestError, got: {other:?}"),
257 }
258 }
259
260 #[test]
261 fn request_error_when_limit_exceeds_u16_max() {
262 let request = GetTokenPreProgrammedDistributionsRequest {
263 version: Some(ReqVersion::V0(
264 GetTokenPreProgrammedDistributionsRequestV0 {
265 token_id: vec![2u8; 32],
266 start_at_info: None,
267 limit: Some(u32::MAX), prove: true,
269 },
270 )),
271 };
272 let response = response_with_proof();
273 let err = <TokenPreProgrammedDistributions as FromProof<_>>::maybe_from_proof(
274 request,
275 response,
276 Network::Testnet,
277 pv(),
278 &UnreachableProvider,
279 )
280 .unwrap_err();
281 match err {
282 Error::RequestError { error } => {
283 assert!(error.contains("limit"), "got: {error}");
284 assert!(error.contains("u16"), "got: {error}");
285 }
286 other => panic!("expected RequestError, got: {other:?}"),
287 }
288 }
289
290 #[test]
291 fn empty_response_metadata_when_metadata_missing() {
292 let request = GetTokenPreProgrammedDistributionsRequest {
293 version: Some(ReqVersion::V0(req_v0_defaults())),
294 };
295 let response = GetTokenPreProgrammedDistributionsResponse { version: None };
297 let err = <TokenPreProgrammedDistributions as FromProof<_>>::maybe_from_proof(
298 request,
299 response,
300 Network::Testnet,
301 pv(),
302 &UnreachableProvider,
303 )
304 .unwrap_err();
305 assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
306 }
307
308 #[test]
309 fn no_proof_in_result_when_result_missing() {
310 let request = GetTokenPreProgrammedDistributionsRequest {
311 version: Some(ReqVersion::V0(req_v0_defaults())),
312 };
313 let response = GetTokenPreProgrammedDistributionsResponse {
314 version: Some(RespVersion::V0(
315 GetTokenPreProgrammedDistributionsResponseV0 {
316 result: None,
317 metadata: Some(ResponseMetadata::default()),
318 },
319 )),
320 };
321 let err = <TokenPreProgrammedDistributions as FromProof<_>>::maybe_from_proof(
322 request,
323 response,
324 Network::Testnet,
325 pv(),
326 &UnreachableProvider,
327 )
328 .unwrap_err();
329 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
330 }
331
332 #[test]
333 fn limit_at_u16_max_does_not_error_on_conversion() {
334 let request = GetTokenPreProgrammedDistributionsRequest {
345 version: Some(ReqVersion::V0(
346 GetTokenPreProgrammedDistributionsRequestV0 {
347 token_id: vec![3u8; 32],
348 start_at_info: None,
349 limit: Some(u16::MAX as u32),
350 prove: true,
351 },
352 )),
353 };
354 let response = GetTokenPreProgrammedDistributionsResponse { version: None };
358 let err = <TokenPreProgrammedDistributions as FromProof<_>>::maybe_from_proof(
359 request,
360 response,
361 Network::Testnet,
362 pv(),
363 &UnreachableProvider,
364 )
365 .unwrap_err();
366 assert!(
367 matches!(err, Error::EmptyResponseMetadata),
368 "expected EmptyResponseMetadata (proves limit conversion succeeded), got: {err:?}"
369 );
370 }
371}