1use crate::error::MapGroveDbError;
4use crate::types::contract_moderation::{
5 entries_query_from_request, identifier_from_request, lists_from_request,
6 removals_query_from_request, team_action_status_from_request, team_actions_query_from_request,
7 ContractDocumentRemovals, ContractFeePots, ContractModerationActionCounts,
8 ContractModerationEntries, ContractModerationListStatuses, ContractTeamActionSigners,
9 ContractTeamActions, CONTRACT_FEE_POTS_QUERIED,
10};
11use crate::verify::{supported_grovedb_proof_bytes, verify_tenderdash_proof};
12use crate::{ContextProvider, Error, FromProof};
13use dapi_grpc::platform::v0::{
14 get_contract_document_removals_request, get_contract_fee_pots_request,
15 get_contract_moderation_action_counts_request, get_contract_moderation_entries_request,
16 get_contract_moderation_status_request, get_contract_team_action_signers_request,
17 get_contract_team_actions_request, GetContractDocumentRemovalsRequest,
18 GetContractDocumentRemovalsResponse, GetContractFeePotsRequest, GetContractFeePotsResponse,
19 GetContractModerationActionCountsRequest, GetContractModerationActionCountsResponse,
20 GetContractModerationEntriesRequest, GetContractModerationEntriesResponse,
21 GetContractModerationStatusRequest, GetContractModerationStatusResponse,
22 GetContractTeamActionSignersRequest, GetContractTeamActionSignersResponse,
23 GetContractTeamActionsRequest, GetContractTeamActionsResponse, Proof, ResponseMetadata,
24};
25use dapi_grpc::platform::VersionedGrpcResponse;
26use dpp::dashcore::Network;
27use dpp::version::PlatformVersion;
28use drive::drive::Drive;
29
30impl FromProof<GetContractModerationStatusRequest> for ContractModerationListStatuses {
31 type Request = GetContractModerationStatusRequest;
32 type Response = GetContractModerationStatusResponse;
33
34 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
35 request: I,
36 response: O,
37 _network: Network,
38 platform_version: &PlatformVersion,
39 provider: &'a dyn ContextProvider,
40 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
41 where
42 Self: Sized + 'a,
43 {
44 let request: Self::Request = request.into();
45 let response: Self::Response = response.into();
46
47 let get_contract_moderation_status_request::Version::V0(v0) =
48 request.version.ok_or(Error::EmptyVersion)?;
49 let contract_id = identifier_from_request(&v0.contract_id, "contract_id")?;
50 let identity_id = identifier_from_request(&v0.identity_id, "identity_id")?;
51 let lists = lists_from_request(&v0.lists)?;
52
53 let metadata = response
54 .metadata()
55 .or(Err(Error::EmptyResponseMetadata))?
56 .clone();
57 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
58
59 let (root_hash, statuses) = Drive::verify_contract_moderation_status(
60 supported_grovedb_proof_bytes(&proof)?,
61 contract_id,
62 identity_id,
63 &lists,
64 false,
65 platform_version,
66 )
67 .map_drive_error(&proof, &metadata)?;
68
69 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
70
71 Ok((Some(statuses), metadata, proof))
74 }
75}
76
77impl FromProof<GetContractModerationEntriesRequest> for ContractModerationEntries {
78 type Request = GetContractModerationEntriesRequest;
79 type Response = GetContractModerationEntriesResponse;
80
81 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
82 request: I,
83 response: O,
84 _network: Network,
85 platform_version: &PlatformVersion,
86 provider: &'a dyn ContextProvider,
87 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
88 where
89 Self: Sized + 'a,
90 {
91 let request: Self::Request = request.into();
92 let response: Self::Response = response.into();
93
94 let get_contract_moderation_entries_request::Version::V0(v0) =
95 request.version.ok_or(Error::EmptyVersion)?;
96 let contract_id = identifier_from_request(&v0.contract_id, "contract_id")?;
97 let query = entries_query_from_request(
98 v0.list,
99 v0.start_after.as_deref(),
100 v0.limit,
101 platform_version,
102 )?;
103
104 let metadata = response
105 .metadata()
106 .or(Err(Error::EmptyResponseMetadata))?
107 .clone();
108 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
109
110 let (root_hash, entries) = Drive::verify_contract_moderation_entries(
111 supported_grovedb_proof_bytes(&proof)?,
112 contract_id,
113 &query,
114 platform_version,
115 )
116 .map_drive_error(&proof, &metadata)?;
117
118 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
119
120 Ok((Some(ContractModerationEntries(entries)), metadata, proof))
122 }
123}
124
125impl FromProof<GetContractFeePotsRequest> for ContractFeePots {
126 type Request = GetContractFeePotsRequest;
127 type Response = GetContractFeePotsResponse;
128
129 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
130 request: I,
131 response: O,
132 _network: Network,
133 platform_version: &PlatformVersion,
134 provider: &'a dyn ContextProvider,
135 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
136 where
137 Self: Sized + 'a,
138 {
139 let request: Self::Request = request.into();
140 let response: Self::Response = response.into();
141
142 let get_contract_fee_pots_request::Version::V0(v0) =
143 request.version.ok_or(Error::EmptyVersion)?;
144 let contract_id = identifier_from_request(&v0.contract_id, "contract_id")?;
145
146 let metadata = response
147 .metadata()
148 .or(Err(Error::EmptyResponseMetadata))?
149 .clone();
150 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
151
152 let (root_hash, pots) = Drive::verify_contract_fee_pots(
153 supported_grovedb_proof_bytes(&proof)?,
154 contract_id,
155 &CONTRACT_FEE_POTS_QUERIED,
156 false,
157 platform_version,
158 )
159 .map_drive_error(&proof, &metadata)?;
160
161 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
162
163 Ok((Some(pots), metadata, proof))
167 }
168}
169
170impl FromProof<GetContractDocumentRemovalsRequest> for ContractDocumentRemovals {
171 type Request = GetContractDocumentRemovalsRequest;
172 type Response = GetContractDocumentRemovalsResponse;
173
174 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
175 request: I,
176 response: O,
177 _network: Network,
178 platform_version: &PlatformVersion,
179 provider: &'a dyn ContextProvider,
180 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
181 where
182 Self: Sized + 'a,
183 {
184 let request: Self::Request = request.into();
185 let response: Self::Response = response.into();
186
187 let get_contract_document_removals_request::Version::V0(v0) =
188 request.version.ok_or(Error::EmptyVersion)?;
189 let contract_id = identifier_from_request(&v0.contract_id, "contract_id")?;
190 let query =
191 removals_query_from_request(v0.document_type_name, v0.selection, platform_version)?;
192
193 let metadata = response
194 .metadata()
195 .or(Err(Error::EmptyResponseMetadata))?
196 .clone();
197 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
198
199 let (root_hash, removals) = Drive::verify_contract_document_removals(
200 supported_grovedb_proof_bytes(&proof)?,
201 contract_id,
202 &query,
203 false,
204 platform_version,
205 )
206 .map_drive_error(&proof, &metadata)?;
207
208 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
209
210 Ok((Some(ContractDocumentRemovals(removals)), metadata, proof))
213 }
214}
215
216impl FromProof<GetContractTeamActionsRequest> for ContractTeamActions {
217 type Request = GetContractTeamActionsRequest;
218 type Response = GetContractTeamActionsResponse;
219
220 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
221 request: I,
222 response: O,
223 _network: Network,
224 platform_version: &PlatformVersion,
225 provider: &'a dyn ContextProvider,
226 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
227 where
228 Self: Sized + 'a,
229 {
230 let request: Self::Request = request.into();
231 let response: Self::Response = response.into();
232
233 let get_contract_team_actions_request::Version::V0(v0) =
234 request.version.ok_or(Error::EmptyVersion)?;
235 let contract_id = identifier_from_request(&v0.contract_id, "contract_id")?;
236 let query = team_actions_query_from_request(
237 v0.status,
238 v0.start_at_action_id,
239 v0.count,
240 platform_version,
241 )?;
242
243 let metadata = response
244 .metadata()
245 .or(Err(Error::EmptyResponseMetadata))?
246 .clone();
247 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
248
249 let (root_hash, actions) = Drive::verify_contract_team_actions(
250 supported_grovedb_proof_bytes(&proof)?,
251 contract_id,
252 &query,
253 false,
254 platform_version,
255 )
256 .map_drive_error(&proof, &metadata)?;
257
258 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
259
260 Ok((Some(ContractTeamActions(actions)), metadata, proof))
263 }
264}
265
266impl FromProof<GetContractTeamActionSignersRequest> for ContractTeamActionSigners {
267 type Request = GetContractTeamActionSignersRequest;
268 type Response = GetContractTeamActionSignersResponse;
269
270 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
271 request: I,
272 response: O,
273 _network: Network,
274 platform_version: &PlatformVersion,
275 provider: &'a dyn ContextProvider,
276 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
277 where
278 Self: Sized + 'a,
279 {
280 let request: Self::Request = request.into();
281 let response: Self::Response = response.into();
282
283 let get_contract_team_action_signers_request::Version::V0(v0) =
284 request.version.ok_or(Error::EmptyVersion)?;
285 let contract_id = identifier_from_request(&v0.contract_id, "contract_id")?;
286 let action_id = identifier_from_request(&v0.action_id, "action_id")?;
287 let status = team_action_status_from_request(v0.status)?;
288
289 let metadata = response
290 .metadata()
291 .or(Err(Error::EmptyResponseMetadata))?
292 .clone();
293 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
294
295 let (root_hash, signers) = Drive::verify_contract_team_action_signers(
296 supported_grovedb_proof_bytes(&proof)?,
297 contract_id,
298 status,
299 action_id,
300 false,
301 platform_version,
302 )
303 .map_drive_error(&proof, &metadata)?;
304
305 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
306
307 Ok((Some(ContractTeamActionSigners(signers)), metadata, proof))
310 }
311}
312
313impl FromProof<GetContractModerationActionCountsRequest> for ContractModerationActionCounts {
314 type Request = GetContractModerationActionCountsRequest;
315 type Response = GetContractModerationActionCountsResponse;
316
317 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
318 request: I,
319 response: O,
320 _network: Network,
321 platform_version: &PlatformVersion,
322 provider: &'a dyn ContextProvider,
323 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
324 where
325 Self: Sized + 'a,
326 {
327 let request: Self::Request = request.into();
328 let response: Self::Response = response.into();
329
330 let get_contract_moderation_action_counts_request::Version::V0(v0) =
331 request.version.ok_or(Error::EmptyVersion)?;
332 let contract_id = identifier_from_request(&v0.contract_id, "contract_id")?;
333
334 let metadata = response
335 .metadata()
336 .or(Err(Error::EmptyResponseMetadata))?
337 .clone();
338 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
339
340 let (root_hash, counts) = Drive::verify_contract_moderation_action_counts(
341 supported_grovedb_proof_bytes(&proof)?,
342 contract_id,
343 false,
344 platform_version,
345 )
346 .map_drive_error(&proof, &metadata)?;
347
348 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
349
350 Ok((
353 Some(ContractModerationActionCounts(counts)),
354 metadata,
355 proof,
356 ))
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363 use dapi_grpc::platform::v0::get_contract_document_removals_request::get_contract_document_removals_request_v0::Selection;
364 use dapi_grpc::platform::v0::get_contract_document_removals_request::{
365 DocumentIds, GetContractDocumentRemovalsRequestV0, Page,
366 };
367 use dapi_grpc::platform::v0::get_contract_document_removals_response::{
368 get_contract_document_removals_response_v0::Result as RemovalsResult,
369 GetContractDocumentRemovalsResponseV0, Version as RemovalsResponseVersion,
370 };
371 use dapi_grpc::platform::v0::get_contract_fee_pots_request::GetContractFeePotsRequestV0;
372 use dapi_grpc::platform::v0::get_contract_fee_pots_response::{
373 get_contract_fee_pots_response_v0::Result as FeePotsResult, GetContractFeePotsResponseV0,
374 Version as FeePotsResponseVersion,
375 };
376 use dapi_grpc::platform::v0::get_contract_moderation_action_counts_request::GetContractModerationActionCountsRequestV0;
377 use dapi_grpc::platform::v0::get_contract_moderation_action_counts_response::{
378 get_contract_moderation_action_counts_response_v0::Result as CountsResult,
379 GetContractModerationActionCountsResponseV0, Version as CountsResponseVersion,
380 };
381 use dapi_grpc::platform::v0::get_contract_moderation_entries_request::GetContractModerationEntriesRequestV0;
382 use dapi_grpc::platform::v0::get_contract_moderation_entries_response::{
383 get_contract_moderation_entries_response_v0::Result as EntriesResult,
384 GetContractModerationEntriesResponseV0, Version as EntriesResponseVersion,
385 };
386 use dapi_grpc::platform::v0::get_contract_moderation_status_request::GetContractModerationStatusRequestV0;
387 use dapi_grpc::platform::v0::get_contract_moderation_status_response::{
388 get_contract_moderation_status_response_v0::Result as StatusResult,
389 GetContractModerationStatusResponseV0, Version as StatusResponseVersion,
390 };
391 use dapi_grpc::platform::v0::get_contract_team_action_signers_request::GetContractTeamActionSignersRequestV0;
392 use dapi_grpc::platform::v0::get_contract_team_action_signers_response::{
393 get_contract_team_action_signers_response_v0::Result as SignersResult,
394 GetContractTeamActionSignersResponseV0, Version as SignersResponseVersion,
395 };
396 use dapi_grpc::platform::v0::get_contract_team_actions_request::{
397 ActionStatus, GetContractTeamActionsRequestV0,
398 };
399 use dapi_grpc::platform::v0::get_contract_team_actions_response::{
400 get_contract_team_actions_response_v0::Result as TeamActionsResult,
401 GetContractTeamActionsResponseV0, Version as TeamActionsResponseVersion,
402 };
403 use dash_context_provider::ContextProviderError;
404 use dpp::data_contract::TokenConfiguration;
405 use dpp::identifier::Identifier;
406 use dpp::prelude::{CoreBlockHeight, DataContract};
407 use std::sync::Arc;
408
409 struct UnreachableProvider;
412
413 impl ContextProvider for UnreachableProvider {
414 fn get_data_contract(
415 &self,
416 _id: &Identifier,
417 _pv: &PlatformVersion,
418 ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
419 panic!("context provider should not be called")
420 }
421
422 fn get_token_configuration(
423 &self,
424 _id: &Identifier,
425 ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
426 panic!("context provider should not be called")
427 }
428
429 fn get_quorum_public_key(
430 &self,
431 _qt: u32,
432 _qh: [u8; 32],
433 _h: u32,
434 ) -> Result<[u8; 48], ContextProviderError> {
435 panic!("context provider should not be called")
436 }
437
438 fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
439 panic!("context provider should not be called")
440 }
441 }
442
443 fn status_request(
444 contract_id: Vec<u8>,
445 identity_id: Vec<u8>,
446 lists: Vec<i32>,
447 ) -> GetContractModerationStatusRequest {
448 GetContractModerationStatusRequest {
449 version: Some(get_contract_moderation_status_request::Version::V0(
450 GetContractModerationStatusRequestV0 {
451 contract_id,
452 identity_id,
453 lists,
454 prove: true,
455 },
456 )),
457 }
458 }
459
460 fn status_response(result: Option<StatusResult>) -> GetContractModerationStatusResponse {
461 GetContractModerationStatusResponse {
462 version: Some(StatusResponseVersion::V0(
463 GetContractModerationStatusResponseV0 {
464 result,
465 metadata: Some(ResponseMetadata::default()),
466 },
467 )),
468 }
469 }
470
471 fn entries_request(
472 contract_id: Vec<u8>,
473 list: i32,
474 start_after: Option<Vec<u8>>,
475 limit: Option<u32>,
476 ) -> GetContractModerationEntriesRequest {
477 GetContractModerationEntriesRequest {
478 version: Some(get_contract_moderation_entries_request::Version::V0(
479 GetContractModerationEntriesRequestV0 {
480 contract_id,
481 list,
482 start_after,
483 limit,
484 prove: true,
485 },
486 )),
487 }
488 }
489
490 fn entries_response(result: Option<EntriesResult>) -> GetContractModerationEntriesResponse {
491 GetContractModerationEntriesResponse {
492 version: Some(EntriesResponseVersion::V0(
493 GetContractModerationEntriesResponseV0 {
494 result,
495 metadata: Some(ResponseMetadata::default()),
496 },
497 )),
498 }
499 }
500
501 fn status_error(
502 request: GetContractModerationStatusRequest,
503 response: GetContractModerationStatusResponse,
504 ) -> Error {
505 <ContractModerationListStatuses as FromProof<_>>::maybe_from_proof(
506 request,
507 response,
508 Network::Testnet,
509 PlatformVersion::latest(),
510 &UnreachableProvider,
511 )
512 .unwrap_err()
513 }
514
515 fn entries_error(
516 request: GetContractModerationEntriesRequest,
517 response: GetContractModerationEntriesResponse,
518 ) -> Error {
519 <ContractModerationEntries as FromProof<_>>::maybe_from_proof(
520 request,
521 response,
522 Network::Testnet,
523 PlatformVersion::latest(),
524 &UnreachableProvider,
525 )
526 .unwrap_err()
527 }
528
529 #[test]
530 fn status_should_fail_with_empty_version_when_request_has_no_version() {
531 let err = status_error(
532 GetContractModerationStatusRequest { version: None },
533 status_response(Some(StatusResult::Proof(Proof::default()))),
534 );
535 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
536 }
537
538 #[test]
539 fn status_should_reject_malformed_requests() {
540 let id = vec![1; 32];
541 for (request, needle) in [
542 (
543 status_request(vec![1; 5], id.clone(), vec![1]),
544 "contract_id",
545 ),
546 (
547 status_request(id.clone(), vec![1; 5], vec![1]),
548 "identity_id",
549 ),
550 (
551 status_request(id.clone(), id.clone(), vec![]),
552 "at least one",
553 ),
554 (status_request(id.clone(), id.clone(), vec![1, 1]), "twice"),
555 (
556 status_request(id.clone(), id.clone(), vec![9]),
557 "not a moderation list",
558 ),
559 (
560 status_request(id.clone(), id.clone(), vec![0]),
561 "not a moderation list",
562 ),
563 ] {
564 let err = status_error(
565 request,
566 status_response(Some(StatusResult::Proof(Proof::default()))),
567 );
568 assert!(
569 matches!(&err, Error::RequestError { error } if error.contains(needle)),
570 "{needle}: {err:?}"
571 );
572 }
573 }
574
575 #[test]
576 fn status_should_fail_without_proof_when_response_carries_none() {
577 let err = status_error(
578 status_request(vec![1; 32], vec![2; 32], vec![1]),
579 status_response(None),
580 );
581 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
582 }
583
584 #[test]
585 fn status_should_fail_on_a_proof_that_does_not_verify() {
586 let err = status_error(
587 status_request(vec![1; 32], vec![2; 32], vec![1, 2]),
588 status_response(Some(StatusResult::Proof(Proof::default()))),
589 );
590 assert!(
591 !matches!(err, Error::RequestError { .. } | Error::NoProofInResult),
592 "got: {err:?}"
593 );
594 }
595
596 #[test]
597 fn entries_should_fail_with_empty_version_when_request_has_no_version() {
598 let err = entries_error(
599 GetContractModerationEntriesRequest { version: None },
600 entries_response(Some(EntriesResult::Proof(Proof::default()))),
601 );
602 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
603 }
604
605 #[test]
606 fn entries_should_reject_malformed_requests() {
607 let id = vec![1; 32];
608 for (request, needle) in [
609 (entries_request(vec![1; 5], 1, None, None), "contract_id"),
610 (
611 entries_request(id.clone(), 9, None, None),
612 "not a moderation list",
613 ),
614 (
615 entries_request(id.clone(), 0, None, None),
616 "not a moderation list",
617 ),
618 (
619 entries_request(id.clone(), 1, Some(vec![1; 5]), None),
620 "start_after",
621 ),
622 (
623 entries_request(id.clone(), 1, None, Some(70_000)),
624 "out of bounds",
625 ),
626 ] {
627 let err = entries_error(
628 request,
629 entries_response(Some(EntriesResult::Proof(Proof::default()))),
630 );
631 assert!(
632 matches!(&err, Error::RequestError { error } if error.contains(needle)),
633 "{needle}: {err:?}"
634 );
635 }
636 }
637
638 #[test]
639 fn entries_should_fail_without_proof_when_response_carries_none() {
640 let err = entries_error(
641 entries_request(vec![1; 32], 1, None, None),
642 entries_response(None),
643 );
644 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
645 }
646
647 #[test]
648 fn entries_should_fail_on_a_proof_that_does_not_verify() {
649 let err = entries_error(
650 entries_request(vec![1; 32], 2, Some(vec![2; 32]), Some(10)),
651 entries_response(Some(EntriesResult::Proof(Proof::default()))),
652 );
653 assert!(
654 !matches!(err, Error::RequestError { .. } | Error::NoProofInResult),
655 "got: {err:?}"
656 );
657 }
658
659 fn fee_pots_request(contract_id: Vec<u8>) -> GetContractFeePotsRequest {
660 GetContractFeePotsRequest {
661 version: Some(get_contract_fee_pots_request::Version::V0(
662 GetContractFeePotsRequestV0 {
663 contract_id,
664 prove: true,
665 },
666 )),
667 }
668 }
669
670 fn fee_pots_response(result: Option<FeePotsResult>) -> GetContractFeePotsResponse {
671 GetContractFeePotsResponse {
672 version: Some(FeePotsResponseVersion::V0(GetContractFeePotsResponseV0 {
673 result,
674 metadata: Some(ResponseMetadata::default()),
675 })),
676 }
677 }
678
679 fn fee_pots_error(
680 request: GetContractFeePotsRequest,
681 response: GetContractFeePotsResponse,
682 ) -> Error {
683 <ContractFeePots as FromProof<_>>::maybe_from_proof(
684 request,
685 response,
686 Network::Testnet,
687 PlatformVersion::latest(),
688 &UnreachableProvider,
689 )
690 .unwrap_err()
691 }
692
693 #[test]
694 fn fee_pots_should_fail_with_empty_version_when_request_has_no_version() {
695 let err = fee_pots_error(
696 GetContractFeePotsRequest { version: None },
697 fee_pots_response(Some(FeePotsResult::Proof(Proof::default()))),
698 );
699 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
700 }
701
702 #[test]
703 fn fee_pots_should_reject_a_malformed_contract_id() {
704 let err = fee_pots_error(
705 fee_pots_request(vec![1; 5]),
706 fee_pots_response(Some(FeePotsResult::Proof(Proof::default()))),
707 );
708 assert!(
709 matches!(&err, Error::RequestError { error } if error.contains("contract_id")),
710 "got: {err:?}"
711 );
712 }
713
714 #[test]
715 fn fee_pots_should_fail_without_proof_when_response_carries_none() {
716 let err = fee_pots_error(fee_pots_request(vec![1; 32]), fee_pots_response(None));
717 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
718 }
719
720 #[test]
721 fn fee_pots_should_fail_on_a_proof_that_does_not_verify() {
722 let err = fee_pots_error(
723 fee_pots_request(vec![1; 32]),
724 fee_pots_response(Some(FeePotsResult::Proof(Proof::default()))),
725 );
726 assert!(
727 !matches!(err, Error::RequestError { .. } | Error::NoProofInResult),
728 "got: {err:?}"
729 );
730 }
731
732 fn removals_request(
733 contract_id: Vec<u8>,
734 document_type_name: &str,
735 selection: Option<Selection>,
736 ) -> GetContractDocumentRemovalsRequest {
737 GetContractDocumentRemovalsRequest {
738 version: Some(get_contract_document_removals_request::Version::V0(
739 GetContractDocumentRemovalsRequestV0 {
740 contract_id,
741 document_type_name: document_type_name.to_string(),
742 selection,
743 prove: true,
744 },
745 )),
746 }
747 }
748
749 fn removals_response(result: Option<RemovalsResult>) -> GetContractDocumentRemovalsResponse {
750 GetContractDocumentRemovalsResponse {
751 version: Some(RemovalsResponseVersion::V0(
752 GetContractDocumentRemovalsResponseV0 {
753 result,
754 metadata: Some(ResponseMetadata::default()),
755 },
756 )),
757 }
758 }
759
760 fn removals_error(
761 request: GetContractDocumentRemovalsRequest,
762 response: GetContractDocumentRemovalsResponse,
763 ) -> Error {
764 <ContractDocumentRemovals as FromProof<_>>::maybe_from_proof(
765 request,
766 response,
767 Network::Testnet,
768 PlatformVersion::latest(),
769 &UnreachableProvider,
770 )
771 .unwrap_err()
772 }
773
774 fn removals_page(start_after: Option<Vec<u8>>, limit: Option<u32>) -> Option<Selection> {
775 Some(Selection::Page(Page { start_after, limit }))
776 }
777
778 #[test]
779 fn removals_should_fail_with_empty_version_when_request_has_no_version() {
780 let err = removals_error(
781 GetContractDocumentRemovalsRequest { version: None },
782 removals_response(Some(RemovalsResult::Proof(Proof::default()))),
783 );
784 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
785 }
786
787 #[test]
788 fn removals_should_reject_malformed_requests() {
789 let id = vec![1; 32];
790 for (request, needle) in [
791 (
792 removals_request(vec![1; 5], "post", removals_page(None, None)),
793 "contract_id",
794 ),
795 (
796 removals_request(id.clone(), "post", None),
797 "either document_ids or page must be set",
798 ),
799 (
800 removals_request(
801 id.clone(),
802 "post",
803 Some(Selection::DocumentIds(DocumentIds {
804 document_ids: vec![],
805 })),
806 ),
807 "must name between 1 and",
808 ),
809 (
810 removals_request(
811 id.clone(),
812 "post",
813 Some(Selection::DocumentIds(DocumentIds {
814 document_ids: vec![id.clone(), id.clone()],
815 })),
816 ),
817 "name a document id twice",
818 ),
819 (
820 removals_request(id.clone(), "post", removals_page(Some(vec![1; 5]), None)),
821 "start_after",
822 ),
823 (
824 removals_request(id.clone(), "post", removals_page(None, Some(70_000))),
825 "limit must be between 1 and",
826 ),
827 ] {
828 let err = removals_error(
829 request,
830 removals_response(Some(RemovalsResult::Proof(Proof::default()))),
831 );
832 assert!(
833 matches!(&err, Error::RequestError { error } if error.contains(needle)),
834 "{needle}: {err:?}"
835 );
836 }
837 }
838
839 #[test]
840 fn removals_should_fail_without_proof_when_response_carries_none() {
841 let err = removals_error(
842 removals_request(vec![1; 32], "post", removals_page(None, None)),
843 removals_response(None),
844 );
845 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
846 }
847
848 #[test]
849 fn removals_should_fail_on_a_proof_that_does_not_verify() {
850 let err = removals_error(
851 removals_request(
852 vec![1; 32],
853 "post",
854 Some(Selection::DocumentIds(DocumentIds {
855 document_ids: vec![vec![2; 32]],
856 })),
857 ),
858 removals_response(Some(RemovalsResult::Proof(Proof::default()))),
859 );
860 assert!(
861 !matches!(err, Error::RequestError { .. } | Error::NoProofInResult),
862 "got: {err:?}"
863 );
864 }
865
866 fn team_actions_request(
867 contract_id: Vec<u8>,
868 status: i32,
869 count: Option<u32>,
870 ) -> GetContractTeamActionsRequest {
871 GetContractTeamActionsRequest {
872 version: Some(get_contract_team_actions_request::Version::V0(
873 GetContractTeamActionsRequestV0 {
874 contract_id,
875 status,
876 start_at_action_id: None,
877 count,
878 prove: true,
879 },
880 )),
881 }
882 }
883
884 fn team_actions_response(result: Option<TeamActionsResult>) -> GetContractTeamActionsResponse {
885 GetContractTeamActionsResponse {
886 version: Some(TeamActionsResponseVersion::V0(
887 GetContractTeamActionsResponseV0 {
888 result,
889 metadata: Some(ResponseMetadata::default()),
890 },
891 )),
892 }
893 }
894
895 fn team_actions_error(
896 request: GetContractTeamActionsRequest,
897 response: GetContractTeamActionsResponse,
898 ) -> Error {
899 <ContractTeamActions as FromProof<_>>::maybe_from_proof(
900 request,
901 response,
902 Network::Testnet,
903 PlatformVersion::latest(),
904 &UnreachableProvider,
905 )
906 .unwrap_err()
907 }
908
909 fn signers_request(
910 contract_id: Vec<u8>,
911 status: i32,
912 action_id: Vec<u8>,
913 ) -> GetContractTeamActionSignersRequest {
914 GetContractTeamActionSignersRequest {
915 version: Some(get_contract_team_action_signers_request::Version::V0(
916 GetContractTeamActionSignersRequestV0 {
917 contract_id,
918 status,
919 action_id,
920 prove: true,
921 },
922 )),
923 }
924 }
925
926 fn signers_response(result: Option<SignersResult>) -> GetContractTeamActionSignersResponse {
927 GetContractTeamActionSignersResponse {
928 version: Some(SignersResponseVersion::V0(
929 GetContractTeamActionSignersResponseV0 {
930 result,
931 metadata: Some(ResponseMetadata::default()),
932 },
933 )),
934 }
935 }
936
937 fn signers_error(
938 request: GetContractTeamActionSignersRequest,
939 response: GetContractTeamActionSignersResponse,
940 ) -> Error {
941 <ContractTeamActionSigners as FromProof<_>>::maybe_from_proof(
942 request,
943 response,
944 Network::Testnet,
945 PlatformVersion::latest(),
946 &UnreachableProvider,
947 )
948 .unwrap_err()
949 }
950
951 #[test]
952 fn should_fail_team_action_queries_with_empty_version_when_request_has_no_version() {
953 let err = team_actions_error(
954 GetContractTeamActionsRequest { version: None },
955 team_actions_response(Some(TeamActionsResult::Proof(Proof::default()))),
956 );
957 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
958 let err = signers_error(
959 GetContractTeamActionSignersRequest { version: None },
960 signers_response(Some(SignersResult::Proof(Proof::default()))),
961 );
962 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
963 }
964
965 #[test]
966 fn should_reject_malformed_team_action_requests() {
967 let id = vec![1; 32];
968 let active = ActionStatus::Active as i32;
969 for (request, needle) in [
970 (
971 team_actions_request(vec![1; 5], active, None),
972 "contract_id",
973 ),
974 (
975 team_actions_request(id.clone(), 7, None),
976 "is not an action status",
977 ),
978 (
979 team_actions_request(id.clone(), active, Some(70_000)),
980 "limit must be between 1 and",
981 ),
982 ] {
983 let err = team_actions_error(
984 request,
985 team_actions_response(Some(TeamActionsResult::Proof(Proof::default()))),
986 );
987 assert!(
988 matches!(&err, Error::RequestError { error } if error.contains(needle)),
989 "{needle}: {err:?}"
990 );
991 }
992 for (request, needle) in [
993 (
994 signers_request(vec![1; 5], active, id.clone()),
995 "contract_id",
996 ),
997 (signers_request(id.clone(), active, vec![1; 5]), "action_id"),
998 (
999 signers_request(id.clone(), 7, id.clone()),
1000 "is not an action status",
1001 ),
1002 ] {
1003 let err = signers_error(
1004 request,
1005 signers_response(Some(SignersResult::Proof(Proof::default()))),
1006 );
1007 assert!(
1008 matches!(&err, Error::RequestError { error } if error.contains(needle)),
1009 "{needle}: {err:?}"
1010 );
1011 }
1012 }
1013
1014 #[test]
1015 fn should_fail_team_action_queries_without_proof_when_response_carries_none() {
1016 let active = ActionStatus::Active as i32;
1017 let err = team_actions_error(
1018 team_actions_request(vec![1; 32], active, None),
1019 team_actions_response(None),
1020 );
1021 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
1022 let err = signers_error(
1023 signers_request(vec![1; 32], active, vec![2; 32]),
1024 signers_response(None),
1025 );
1026 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
1027 }
1028
1029 #[test]
1030 fn should_fail_on_team_action_proofs_that_do_not_verify() {
1031 let active = ActionStatus::Active as i32;
1032 let err = team_actions_error(
1033 team_actions_request(vec![1; 32], active, None),
1034 team_actions_response(Some(TeamActionsResult::Proof(Proof::default()))),
1035 );
1036 assert!(
1037 !matches!(err, Error::RequestError { .. } | Error::NoProofInResult),
1038 "got: {err:?}"
1039 );
1040 let err = signers_error(
1041 signers_request(vec![1; 32], active, vec![2; 32]),
1042 signers_response(Some(SignersResult::Proof(Proof::default()))),
1043 );
1044 assert!(
1045 !matches!(err, Error::RequestError { .. } | Error::NoProofInResult),
1046 "got: {err:?}"
1047 );
1048 }
1049
1050 fn counts_request(contract_id: Vec<u8>) -> GetContractModerationActionCountsRequest {
1051 GetContractModerationActionCountsRequest {
1052 version: Some(get_contract_moderation_action_counts_request::Version::V0(
1053 GetContractModerationActionCountsRequestV0 {
1054 contract_id,
1055 prove: true,
1056 },
1057 )),
1058 }
1059 }
1060
1061 fn counts_response(result: Option<CountsResult>) -> GetContractModerationActionCountsResponse {
1062 GetContractModerationActionCountsResponse {
1063 version: Some(CountsResponseVersion::V0(
1064 GetContractModerationActionCountsResponseV0 {
1065 result,
1066 metadata: Some(ResponseMetadata::default()),
1067 },
1068 )),
1069 }
1070 }
1071
1072 fn counts_error(
1073 request: GetContractModerationActionCountsRequest,
1074 response: GetContractModerationActionCountsResponse,
1075 ) -> Error {
1076 <ContractModerationActionCounts as FromProof<_>>::maybe_from_proof(
1077 request,
1078 response,
1079 Network::Testnet,
1080 PlatformVersion::latest(),
1081 &UnreachableProvider,
1082 )
1083 .unwrap_err()
1084 }
1085
1086 #[test]
1087 fn should_check_the_request_and_the_proof_of_moderation_action_counts() {
1088 let proof = || counts_response(Some(CountsResult::Proof(Proof::default())));
1089 let err = counts_error(
1090 GetContractModerationActionCountsRequest { version: None },
1091 proof(),
1092 );
1093 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
1094
1095 let err = counts_error(counts_request(vec![1; 5]), proof());
1096 assert!(
1097 matches!(&err, Error::RequestError { error } if error.contains("contract_id")),
1098 "got: {err:?}"
1099 );
1100
1101 let err = counts_error(counts_request(vec![1; 32]), counts_response(None));
1102 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
1103
1104 let err = counts_error(counts_request(vec![1; 32]), proof());
1106 assert!(
1107 !matches!(err, Error::RequestError { .. } | Error::NoProofInResult),
1108 "got: {err:?}"
1109 );
1110 }
1111}