1use crate::error::MapGroveDbError;
2use crate::types::groups::{GroupActionSigners, GroupActions, Groups};
3use crate::verify::{
4 supported_grovedb_proof_bytes, verify_page_drive_proves, verify_tenderdash_proof,
5};
6use crate::{ContextProvider, Error, FromProof};
7use dapi_grpc::platform::v0::{
8 get_group_action_signers_request, get_group_actions_request, get_group_info_request,
9 get_group_infos_request, GetGroupActionSignersRequest, GetGroupActionSignersResponse,
10 GetGroupActionsRequest, GetGroupActionsResponse, GetGroupInfoRequest, GetGroupInfoResponse,
11 GetGroupInfosRequest, GetGroupInfosResponse, Proof, ResponseMetadata,
12};
13use dapi_grpc::platform::VersionedGrpcResponse;
14use dpp::dashcore::Network;
15use dpp::data_contract::group::{Group, GroupMemberPower};
16use dpp::data_contract::GroupContractPosition;
17use dpp::group::group_action::GroupAction;
18use dpp::group::group_action_status::GroupActionStatus;
19use dpp::identifier::Identifier;
20use dpp::version::PlatformVersion;
21use drive::drive::Drive;
22use indexmap::IndexMap;
23
24impl FromProof<GetGroupInfoRequest> for Group {
25 type Request = GetGroupInfoRequest;
26 type Response = GetGroupInfoResponse;
27
28 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
29 request: I,
30 response: O,
31 _network: Network,
32 platform_version: &PlatformVersion,
33 provider: &'a dyn ContextProvider,
34 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
35 where
36 Self: Sized + 'a,
37 {
38 let request: Self::Request = request.into();
39 let response: Self::Response = response.into();
40
41 let (contract_id, group_contract_position) = match request
42 .version
43 .ok_or(Error::EmptyVersion)?
44 {
45 get_group_info_request::Version::V0(v0) => {
46 let contract_id =
47 Identifier::try_from(v0.contract_id).map_err(|error| Error::RequestError {
48 error: format!("can't convert contract_id to identifier: {error}"),
49 })?;
50
51 let group_contract_position = v0.group_contract_position as GroupContractPosition;
52
53 (contract_id, group_contract_position)
54 }
55 };
56
57 let metadata = response
58 .metadata()
59 .or(Err(Error::EmptyResponseMetadata))?
60 .clone();
61
62 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
63
64 let (root_hash, result) = Drive::verify_group_info(
65 supported_grovedb_proof_bytes(&proof)?,
66 contract_id,
67 group_contract_position,
68 false,
69 platform_version,
70 )
71 .map_drive_error(&proof, &metadata)?;
72
73 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
74
75 Ok((result, metadata, proof))
76 }
77}
78
79impl FromProof<GetGroupInfosRequest> for Groups {
80 type Request = GetGroupInfosRequest;
81 type Response = GetGroupInfosResponse;
82
83 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
84 request: I,
85 response: O,
86 _network: Network,
87 platform_version: &PlatformVersion,
88 provider: &'a dyn ContextProvider,
89 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
90 where
91 Self: Sized + 'a,
92 {
93 let request: Self::Request = request.into();
94 let response: Self::Response = response.into();
95
96 let (contract_id, start_at_group_contract_position, count) = match request
97 .version
98 .ok_or(Error::EmptyVersion)?
99 {
100 get_group_infos_request::Version::V0(v0) => {
101 let contract_id =
102 Identifier::try_from(v0.contract_id).map_err(|error| Error::RequestError {
103 error: format!("can't convert contract_id to identifier: {error}"),
104 })?;
105
106 let start_group_contract_position =
107 v0.start_at_group_contract_position.map(|start_position| {
108 (
109 start_position.start_group_contract_position as GroupContractPosition,
110 start_position.start_group_contract_position_included,
111 )
112 });
113
114 let count = v0.count.map(|count| count as u16);
115
116 (contract_id, start_group_contract_position, count)
117 }
118 };
119
120 let metadata = response
121 .metadata()
122 .or(Err(Error::EmptyResponseMetadata))?
123 .clone();
124
125 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
126
127 let grovedb_proof = supported_grovedb_proof_bytes(&proof)?;
128 let (root_hash, result) = verify_page_drive_proves(count, |count| {
129 Drive::verify_group_infos_in_contract(
130 grovedb_proof,
131 contract_id,
132 start_at_group_contract_position,
133 count,
134 false,
135 platform_version,
136 )
137 })
138 .map(
140 |(root_hash, result): (_, IndexMap<GroupContractPosition, Group>)| {
141 let optional_value_map = result
142 .into_iter()
143 .map(|(action_id, group_action)| (action_id, Some(group_action)))
144 .collect::<Groups>();
145 (root_hash, optional_value_map)
146 },
147 )
148 .map_drive_error(&proof, &metadata)?;
149
150 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
151
152 Ok((Some(result), metadata, proof))
153 }
154}
155
156impl FromProof<GetGroupActionsRequest> for GroupActions {
157 type Request = GetGroupActionsRequest;
158 type Response = GetGroupActionsResponse;
159
160 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
161 request: I,
162 response: O,
163 _network: Network,
164 platform_version: &PlatformVersion,
165 provider: &'a dyn ContextProvider,
166 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
167 where
168 Self: Sized + 'a,
169 {
170 let request: Self::Request = request.into();
171 let response: Self::Response = response.into();
172
173 let (contract_id, group_contract_position, status, start_at_action_id, count) =
174 match request.version.ok_or(Error::EmptyVersion)? {
175 get_group_actions_request::Version::V0(v0) => {
176 let contract_id = Identifier::try_from(v0.contract_id).map_err(|error| {
177 Error::RequestError {
178 error: format!("can't convert contract_id to identifier: {error}"),
179 }
180 })?;
181
182 let start_at_action_id =
183 v0.start_at_action_id
184 .map(|start_at_action_id| {
185 let start_action_id =
186 Identifier::try_from(start_at_action_id.start_action_id)
187 .map_err(|error| Error::RequestError {
188 error: format!(
189 "can't convert start_action_id to identifier: {error}"
190 ),
191 })?;
192
193 Ok::<_, Error>((
194 start_action_id,
195 start_at_action_id.start_action_id_included,
196 ))
197 })
198 .transpose()?;
199
200 let group_contract_position =
201 v0.group_contract_position as GroupContractPosition;
202
203 let count = v0.count.map(|count| count as u16);
204
205 let status = GroupActionStatus::try_from(v0.status).map_err(|error| {
206 Error::RequestError {
207 error: format!("can't convert status to GroupActionStatus: {error}"),
208 }
209 })?;
210
211 (
212 contract_id,
213 group_contract_position,
214 status,
215 start_at_action_id,
216 count,
217 )
218 }
219 };
220
221 let metadata = response
222 .metadata()
223 .or(Err(Error::EmptyResponseMetadata))?
224 .clone();
225
226 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
227
228 let grovedb_proof = supported_grovedb_proof_bytes(&proof)?;
229 let (root_hash, result) = verify_page_drive_proves(count, |count| {
230 Drive::verify_action_infos_in_contract(
231 grovedb_proof,
232 contract_id,
233 group_contract_position,
234 status,
235 start_at_action_id,
236 count,
237 false,
238 platform_version,
239 )
240 })
241 .map(
243 |(root_hash, result): (_, IndexMap<Identifier, GroupAction>)| {
244 let optional_value_map = result
245 .into_iter()
246 .map(|(action_id, group_action)| (action_id, Some(group_action)))
247 .collect::<GroupActions>();
248 (root_hash, optional_value_map)
249 },
250 )
251 .map_drive_error(&proof, &metadata)?;
252
253 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
254
255 Ok((Some(result), metadata, proof))
256 }
257}
258
259impl FromProof<GetGroupActionSignersRequest> for GroupActionSigners {
260 type Request = GetGroupActionSignersRequest;
261 type Response = GetGroupActionSignersResponse;
262
263 fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
264 request: I,
265 response: O,
266 _network: Network,
267 platform_version: &PlatformVersion,
268 provider: &'a dyn ContextProvider,
269 ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
270 where
271 Self: Sized + 'a,
272 {
273 let request: Self::Request = request.into();
274 let response: Self::Response = response.into();
275
276 let (contract_id, group_contract_position, status, action_id) = match request
277 .version
278 .ok_or(Error::EmptyVersion)?
279 {
280 get_group_action_signers_request::Version::V0(v0) => {
281 let contract_id =
282 Identifier::try_from(v0.contract_id).map_err(|error| Error::RequestError {
283 error: format!("can't convert contract_id to identifier: {error}"),
284 })?;
285
286 let action_id =
287 Identifier::try_from(v0.action_id).map_err(|error| Error::RequestError {
288 error: format!("can't convert action_id to identifier: {error}"),
289 })?;
290
291 let group_contract_position = v0.group_contract_position as GroupContractPosition;
292
293 let status = GroupActionStatus::try_from(v0.status).map_err(|error| {
294 Error::RequestError {
295 error: format!("can't convert status to GroupActionStatus: {error}"),
296 }
297 })?;
298
299 (contract_id, group_contract_position, status, action_id)
300 }
301 };
302
303 let metadata = response
304 .metadata()
305 .or(Err(Error::EmptyResponseMetadata))?
306 .clone();
307
308 let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
309
310 let (root_hash, result) = Drive::verify_action_signers(
311 supported_grovedb_proof_bytes(&proof)?,
312 contract_id,
313 group_contract_position,
314 status,
315 action_id,
316 false,
317 platform_version,
318 )
319 .map(
321 |(root_hash, result): (_, IndexMap<Identifier, GroupMemberPower>)| {
322 let optional_value_map = result
323 .into_iter()
324 .map(|(action_id, group_action)| (action_id, Some(group_action)))
325 .collect::<GroupActionSigners>();
326 (root_hash, optional_value_map)
327 },
328 )
329 .map_drive_error(&proof, &metadata)?;
330
331 verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
332
333 Ok((Some(result), metadata, proof))
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use dapi_grpc::platform::v0::get_group_action_signers_request::{
341 GetGroupActionSignersRequestV0, Version as SignersReqVersion,
342 };
343 use dapi_grpc::platform::v0::get_group_actions_request::{
344 GetGroupActionsRequestV0, StartAtActionId, Version as ActionsReqVersion,
345 };
346 use dapi_grpc::platform::v0::get_group_actions_response::{
347 get_group_actions_response_v0::Result as ActionsRespResult, GetGroupActionsResponseV0,
348 Version as ActionsRespVersion,
349 };
350 use dapi_grpc::platform::v0::get_group_info_request::{
351 GetGroupInfoRequestV0, Version as InfoReqVersion,
352 };
353 use dapi_grpc::platform::v0::get_group_info_response::{
354 get_group_info_response_v0::Result as InfoRespResult, GetGroupInfoResponseV0,
355 Version as InfoRespVersion,
356 };
357 use dapi_grpc::platform::v0::get_group_infos_request::{
358 GetGroupInfosRequestV0, StartAtGroupContractPosition, Version as InfosReqVersion,
359 };
360 use dapi_grpc::platform::v0::get_group_infos_response::{
361 get_group_infos_response_v0::Result as InfosRespResult, GetGroupInfosResponseV0,
362 Version as InfosRespVersion,
363 };
364 use dash_context_provider::ContextProviderError;
365 use dpp::data_contract::TokenConfiguration;
366 use dpp::prelude::{CoreBlockHeight, DataContract};
367 use std::sync::Arc;
368
369 struct UnreachableProvider;
370
371 impl ContextProvider for UnreachableProvider {
372 fn get_data_contract(
373 &self,
374 _id: &Identifier,
375 _pv: &PlatformVersion,
376 ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
377 panic!("should not be called")
378 }
379 fn get_token_configuration(
380 &self,
381 _id: &Identifier,
382 ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
383 panic!("should not be called")
384 }
385 fn get_quorum_public_key(
386 &self,
387 _qt: u32,
388 _qh: [u8; 32],
389 _h: u32,
390 ) -> Result<[u8; 48], ContextProviderError> {
391 panic!("should not be called")
392 }
393 fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
394 panic!("should not be called")
395 }
396 }
397
398 fn pv() -> &'static PlatformVersion {
399 PlatformVersion::latest()
400 }
401
402 #[test]
405 fn group_info_empty_version_on_request_missing() {
406 let request = GetGroupInfoRequest { version: None };
407 let response = GetGroupInfoResponse {
408 version: Some(InfoRespVersion::V0(GetGroupInfoResponseV0 {
409 result: Some(InfoRespResult::Proof(Proof::default())),
410 metadata: Some(ResponseMetadata::default()),
411 })),
412 };
413 let err = <Group as FromProof<_>>::maybe_from_proof(
414 request,
415 response,
416 Network::Testnet,
417 pv(),
418 &UnreachableProvider,
419 )
420 .unwrap_err();
421 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
422 }
423
424 #[test]
425 fn group_info_request_error_on_bad_contract_id() {
426 let request = GetGroupInfoRequest {
427 version: Some(InfoReqVersion::V0(GetGroupInfoRequestV0 {
428 contract_id: vec![0u8; 7], group_contract_position: 0,
430 prove: true,
431 })),
432 };
433 let response = GetGroupInfoResponse::default();
434 let err = <Group as FromProof<_>>::maybe_from_proof(
435 request,
436 response,
437 Network::Testnet,
438 pv(),
439 &UnreachableProvider,
440 )
441 .unwrap_err();
442 match err {
443 Error::RequestError { error } => assert!(error.contains("contract_id"), "got: {error}"),
444 other => panic!("expected RequestError, got: {other:?}"),
445 }
446 }
447
448 #[test]
449 fn group_info_empty_response_metadata() {
450 let request = GetGroupInfoRequest {
451 version: Some(InfoReqVersion::V0(GetGroupInfoRequestV0 {
452 contract_id: vec![0u8; 32],
453 group_contract_position: 0,
454 prove: true,
455 })),
456 };
457 let response = GetGroupInfoResponse { version: None };
458 let err = <Group as FromProof<_>>::maybe_from_proof(
459 request,
460 response,
461 Network::Testnet,
462 pv(),
463 &UnreachableProvider,
464 )
465 .unwrap_err();
466 assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
467 }
468
469 #[test]
470 fn group_info_no_proof_when_result_missing() {
471 let request = GetGroupInfoRequest {
472 version: Some(InfoReqVersion::V0(GetGroupInfoRequestV0 {
473 contract_id: vec![0u8; 32],
474 group_contract_position: 0,
475 prove: true,
476 })),
477 };
478 let response = GetGroupInfoResponse {
479 version: Some(InfoRespVersion::V0(GetGroupInfoResponseV0 {
480 result: None,
481 metadata: Some(ResponseMetadata::default()),
482 })),
483 };
484 let err = <Group as FromProof<_>>::maybe_from_proof(
485 request,
486 response,
487 Network::Testnet,
488 pv(),
489 &UnreachableProvider,
490 )
491 .unwrap_err();
492 assert!(matches!(err, Error::NoProofInResult), "got: {err:?}");
493 }
494
495 #[test]
498 fn group_infos_empty_version_on_request_missing() {
499 let request = GetGroupInfosRequest { version: None };
500 let response = GetGroupInfosResponse::default();
501 let err = <Groups as FromProof<_>>::maybe_from_proof(
502 request,
503 response,
504 Network::Testnet,
505 pv(),
506 &UnreachableProvider,
507 )
508 .unwrap_err();
509 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
510 }
511
512 #[test]
513 fn group_infos_request_error_on_bad_contract_id() {
514 let request = GetGroupInfosRequest {
515 version: Some(InfosReqVersion::V0(GetGroupInfosRequestV0 {
516 contract_id: vec![0u8; 12], start_at_group_contract_position: Some(StartAtGroupContractPosition {
518 start_group_contract_position: 0,
519 start_group_contract_position_included: true,
520 }),
521 count: Some(10),
522 prove: true,
523 })),
524 };
525 let response = GetGroupInfosResponse::default();
526 let err = <Groups as FromProof<_>>::maybe_from_proof(
527 request,
528 response,
529 Network::Testnet,
530 pv(),
531 &UnreachableProvider,
532 )
533 .unwrap_err();
534 match err {
535 Error::RequestError { error } => assert!(error.contains("contract_id"), "got: {error}"),
536 other => panic!("expected RequestError, got: {other:?}"),
537 }
538 }
539
540 #[test]
541 fn group_infos_empty_response_metadata() {
542 let request = GetGroupInfosRequest {
543 version: Some(InfosReqVersion::V0(GetGroupInfosRequestV0 {
544 contract_id: vec![0u8; 32],
545 start_at_group_contract_position: None,
546 count: None,
547 prove: true,
548 })),
549 };
550 let response = GetGroupInfosResponse {
551 version: Some(InfosRespVersion::V0(GetGroupInfosResponseV0 {
552 result: Some(InfosRespResult::Proof(Proof::default())),
553 metadata: None,
554 })),
555 };
556 let err = <Groups as FromProof<_>>::maybe_from_proof(
557 request,
558 response,
559 Network::Testnet,
560 pv(),
561 &UnreachableProvider,
562 )
563 .unwrap_err();
564 assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
565 }
566
567 #[test]
570 fn group_actions_empty_version_on_request_missing() {
571 let request = GetGroupActionsRequest { version: None };
572 let response = GetGroupActionsResponse::default();
573 let err = <GroupActions as FromProof<_>>::maybe_from_proof(
574 request,
575 response,
576 Network::Testnet,
577 pv(),
578 &UnreachableProvider,
579 )
580 .unwrap_err();
581 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
582 }
583
584 #[test]
585 fn group_actions_request_error_on_bad_contract_id() {
586 let request = GetGroupActionsRequest {
587 version: Some(ActionsReqVersion::V0(GetGroupActionsRequestV0 {
588 contract_id: vec![0u8; 3],
589 group_contract_position: 0,
590 status: 0,
591 start_at_action_id: None,
592 count: None,
593 prove: true,
594 })),
595 };
596 let response = GetGroupActionsResponse::default();
597 let err = <GroupActions as FromProof<_>>::maybe_from_proof(
598 request,
599 response,
600 Network::Testnet,
601 pv(),
602 &UnreachableProvider,
603 )
604 .unwrap_err();
605 match err {
606 Error::RequestError { error } => assert!(error.contains("contract_id"), "got: {error}"),
607 other => panic!("expected RequestError, got: {other:?}"),
608 }
609 }
610
611 #[test]
612 fn group_actions_request_error_on_bad_start_action_id() {
613 let request = GetGroupActionsRequest {
614 version: Some(ActionsReqVersion::V0(GetGroupActionsRequestV0 {
615 contract_id: vec![0u8; 32],
616 group_contract_position: 0,
617 status: 0,
618 start_at_action_id: Some(StartAtActionId {
619 start_action_id: vec![0u8; 9], start_action_id_included: true,
621 }),
622 count: None,
623 prove: true,
624 })),
625 };
626 let response = GetGroupActionsResponse::default();
627 let err = <GroupActions as FromProof<_>>::maybe_from_proof(
628 request,
629 response,
630 Network::Testnet,
631 pv(),
632 &UnreachableProvider,
633 )
634 .unwrap_err();
635 match err {
636 Error::RequestError { error } => {
637 assert!(error.contains("start_action_id"), "got: {error}")
638 }
639 other => panic!("expected RequestError, got: {other:?}"),
640 }
641 }
642
643 #[test]
644 fn group_actions_request_error_on_bad_status() {
645 let request = GetGroupActionsRequest {
646 version: Some(ActionsReqVersion::V0(GetGroupActionsRequestV0 {
647 contract_id: vec![0u8; 32],
648 group_contract_position: 0,
649 status: 999, start_at_action_id: None,
651 count: None,
652 prove: true,
653 })),
654 };
655 let response = GetGroupActionsResponse::default();
656 let err = <GroupActions as FromProof<_>>::maybe_from_proof(
657 request,
658 response,
659 Network::Testnet,
660 pv(),
661 &UnreachableProvider,
662 )
663 .unwrap_err();
664 match err {
665 Error::RequestError { error } => {
666 assert!(error.contains("GroupActionStatus"), "got: {error}")
667 }
668 other => panic!("expected RequestError, got: {other:?}"),
669 }
670 }
671
672 #[test]
673 fn group_actions_empty_response_metadata() {
674 let request = GetGroupActionsRequest {
675 version: Some(ActionsReqVersion::V0(GetGroupActionsRequestV0 {
676 contract_id: vec![0u8; 32],
677 group_contract_position: 0,
678 status: 0,
679 start_at_action_id: None,
680 count: None,
681 prove: true,
682 })),
683 };
684 let response = GetGroupActionsResponse {
685 version: Some(ActionsRespVersion::V0(GetGroupActionsResponseV0 {
686 result: Some(ActionsRespResult::Proof(Proof::default())),
687 metadata: None,
688 })),
689 };
690 let err = <GroupActions as FromProof<_>>::maybe_from_proof(
691 request,
692 response,
693 Network::Testnet,
694 pv(),
695 &UnreachableProvider,
696 )
697 .unwrap_err();
698 assert!(matches!(err, Error::EmptyResponseMetadata), "got: {err:?}");
699 }
700
701 #[test]
704 fn group_action_signers_empty_version_on_request_missing() {
705 let request = GetGroupActionSignersRequest { version: None };
706 let response = GetGroupActionSignersResponse::default();
707 let err = <GroupActionSigners as FromProof<_>>::maybe_from_proof(
708 request,
709 response,
710 Network::Testnet,
711 pv(),
712 &UnreachableProvider,
713 )
714 .unwrap_err();
715 assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
716 }
717
718 #[test]
719 fn group_action_signers_request_error_on_bad_contract_id() {
720 let request = GetGroupActionSignersRequest {
721 version: Some(SignersReqVersion::V0(GetGroupActionSignersRequestV0 {
722 contract_id: vec![0u8; 9], group_contract_position: 0,
724 status: 0,
725 action_id: vec![1u8; 32],
726 prove: true,
727 })),
728 };
729 let response = GetGroupActionSignersResponse::default();
730 let err = <GroupActionSigners as FromProof<_>>::maybe_from_proof(
731 request,
732 response,
733 Network::Testnet,
734 pv(),
735 &UnreachableProvider,
736 )
737 .unwrap_err();
738 match err {
739 Error::RequestError { error } => assert!(error.contains("contract_id"), "got: {error}"),
740 other => panic!("expected RequestError, got: {other:?}"),
741 }
742 }
743
744 #[test]
745 fn group_action_signers_request_error_on_bad_action_id() {
746 let request = GetGroupActionSignersRequest {
747 version: Some(SignersReqVersion::V0(GetGroupActionSignersRequestV0 {
748 contract_id: vec![0u8; 32],
749 group_contract_position: 0,
750 status: 0,
751 action_id: vec![1u8; 3], prove: true,
753 })),
754 };
755 let response = GetGroupActionSignersResponse::default();
756 let err = <GroupActionSigners as FromProof<_>>::maybe_from_proof(
757 request,
758 response,
759 Network::Testnet,
760 pv(),
761 &UnreachableProvider,
762 )
763 .unwrap_err();
764 match err {
765 Error::RequestError { error } => assert!(error.contains("action_id"), "got: {error}"),
766 other => panic!("expected RequestError, got: {other:?}"),
767 }
768 }
769
770 #[test]
771 fn group_action_signers_request_error_on_bad_status() {
772 let request = GetGroupActionSignersRequest {
773 version: Some(SignersReqVersion::V0(GetGroupActionSignersRequestV0 {
774 contract_id: vec![0u8; 32],
775 group_contract_position: 0,
776 status: 42, action_id: vec![1u8; 32],
778 prove: true,
779 })),
780 };
781 let response = GetGroupActionSignersResponse::default();
782 let err = <GroupActionSigners as FromProof<_>>::maybe_from_proof(
783 request,
784 response,
785 Network::Testnet,
786 pv(),
787 &UnreachableProvider,
788 )
789 .unwrap_err();
790 match err {
791 Error::RequestError { error } => {
792 assert!(error.contains("GroupActionStatus"), "got: {error}")
793 }
794 other => panic!("expected RequestError, got: {other:?}"),
795 }
796 }
797}