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