Skip to main content

drive_proof_verifier/proof/
identity_token_balance.rs

1use crate::error::MapGroveDbError;
2use crate::types::identity_token_balance::{IdentitiesTokenBalances, IdentityTokenBalances};
3use crate::verify::verify_tenderdash_proof;
4use crate::{ContextProvider, Error, FromProof};
5use dapi_grpc::platform::v0::{
6    get_identities_token_balances_request, get_identity_token_balances_request,
7    GetIdentitiesTokenBalancesRequest, GetIdentitiesTokenBalancesResponse,
8    GetIdentityTokenBalancesRequest, GetIdentityTokenBalancesResponse, Proof, ResponseMetadata,
9};
10use dapi_grpc::platform::VersionedGrpcResponse;
11use dpp::dashcore::Network;
12use dpp::version::PlatformVersion;
13use drive::drive::Drive;
14
15impl FromProof<GetIdentityTokenBalancesRequest> for IdentityTokenBalances {
16    type Request = GetIdentityTokenBalancesRequest;
17    type Response = GetIdentityTokenBalancesResponse;
18
19    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
20        request: I,
21        response: O,
22        _network: Network,
23        platform_version: &PlatformVersion,
24        provider: &'a dyn ContextProvider,
25    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
26    where
27        Self: Sized + 'a,
28    {
29        let request: Self::Request = request.into();
30        let response: Self::Response = response.into();
31
32        let (token_ids, identity_id) = match request.version.ok_or(Error::EmptyVersion)? {
33            get_identity_token_balances_request::Version::V0(v0) => {
34                let identity_id =
35                    <[u8; 32]>::try_from(v0.identity_id).map_err(|_| Error::RequestError {
36                        error: "can't convert identity_id to [u8; 32]".to_string(),
37                    })?;
38
39                let token_ids = v0
40                    .token_ids
41                    .into_iter()
42                    .map(<[u8; 32]>::try_from)
43                    .collect::<Result<Vec<_>, _>>()
44                    .map_err(|_| Error::RequestError {
45                        error: "can't convert token_id to [u8; 32]".to_string(),
46                    })?;
47
48                (token_ids, identity_id)
49            }
50        };
51
52        let metadata = response
53            .metadata()
54            .or(Err(Error::EmptyResponseMetadata))?
55            .clone();
56
57        let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
58
59        let (root_hash, result) = Drive::verify_token_balances_for_identity_id(
60            &proof.grovedb_proof,
61            &token_ids,
62            identity_id,
63            false,
64            platform_version,
65        )
66        .map_drive_error(&proof, &metadata)?;
67
68        verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
69
70        Ok((Some(result), metadata, proof))
71    }
72}
73
74impl FromProof<GetIdentitiesTokenBalancesRequest> for IdentitiesTokenBalances {
75    type Request = GetIdentitiesTokenBalancesRequest;
76    type Response = GetIdentitiesTokenBalancesResponse;
77
78    fn maybe_from_proof_with_metadata<'a, I: Into<Self::Request>, O: Into<Self::Response>>(
79        request: I,
80        response: O,
81        _network: Network,
82        platform_version: &PlatformVersion,
83        provider: &'a dyn ContextProvider,
84    ) -> Result<(Option<Self>, ResponseMetadata, Proof), Error>
85    where
86        Self: Sized + 'a,
87    {
88        let request: Self::Request = request.into();
89        let response: Self::Response = response.into();
90
91        let (token_id, identity_ids) = match request.version.ok_or(Error::EmptyVersion)? {
92            get_identities_token_balances_request::Version::V0(v0) => {
93                let token_id = <[u8; 32]>::try_from(v0.token_id.as_slice()).map_err(|error| {
94                    Error::RequestError {
95                        error: error.to_string(),
96                    }
97                })?;
98
99                let identity_ids = v0
100                    .identity_ids
101                    .into_iter()
102                    .map(<[u8; 32]>::try_from)
103                    .collect::<Result<Vec<_>, _>>()
104                    .map_err(|_| Error::RequestError {
105                        error: "can't convert identity_id to [u8; 32]".to_string(),
106                    })?;
107
108                (token_id, identity_ids)
109            }
110        };
111
112        let metadata = response
113            .metadata()
114            .or(Err(Error::EmptyResponseMetadata))?
115            .clone();
116
117        let proof = response.proof_owned().or(Err(Error::NoProofInResult))?;
118
119        let (root_hash, result) = Drive::verify_token_balances_for_identity_ids(
120            &proof.grovedb_proof,
121            token_id,
122            &identity_ids,
123            false,
124            platform_version,
125        )
126        .map_drive_error(&proof, &metadata)?;
127
128        verify_tenderdash_proof(&proof, &metadata, &root_hash, provider)?;
129
130        Ok((Some(result), metadata, proof))
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use dapi_grpc::platform::v0::get_identities_token_balances_request::{
138        GetIdentitiesTokenBalancesRequestV0, Version as IdsReqVersion,
139    };
140    use dapi_grpc::platform::v0::get_identity_token_balances_request::{
141        GetIdentityTokenBalancesRequestV0, Version as IdReqVersion,
142    };
143    use dash_context_provider::ContextProviderError;
144    use dpp::data_contract::TokenConfiguration;
145    use dpp::prelude::{CoreBlockHeight, DataContract, Identifier};
146    use std::sync::Arc;
147
148    struct UnreachableProvider;
149
150    impl ContextProvider for UnreachableProvider {
151        fn get_data_contract(
152            &self,
153            _id: &Identifier,
154            _pv: &PlatformVersion,
155        ) -> Result<Option<Arc<DataContract>>, ContextProviderError> {
156            panic!("should not be called")
157        }
158        fn get_token_configuration(
159            &self,
160            _id: &Identifier,
161        ) -> Result<Option<TokenConfiguration>, ContextProviderError> {
162            panic!("should not be called")
163        }
164        fn get_quorum_public_key(
165            &self,
166            _qt: u32,
167            _qh: [u8; 32],
168            _h: u32,
169        ) -> Result<[u8; 48], ContextProviderError> {
170            panic!("should not be called")
171        }
172        fn get_platform_activation_height(&self) -> Result<CoreBlockHeight, ContextProviderError> {
173            panic!("should not be called")
174        }
175    }
176
177    fn pv() -> &'static PlatformVersion {
178        PlatformVersion::latest()
179    }
180
181    // -------- IdentityTokenBalances --------
182
183    #[test]
184    fn identity_token_balances_empty_version_on_request_missing() {
185        let request = GetIdentityTokenBalancesRequest { version: None };
186        let response = GetIdentityTokenBalancesResponse::default();
187        let err = <IdentityTokenBalances as FromProof<_>>::maybe_from_proof(
188            request,
189            response,
190            Network::Testnet,
191            pv(),
192            &UnreachableProvider,
193        )
194        .unwrap_err();
195        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
196    }
197
198    #[test]
199    fn identity_token_balances_request_error_on_bad_identity_id() {
200        let request = GetIdentityTokenBalancesRequest {
201            version: Some(IdReqVersion::V0(GetIdentityTokenBalancesRequestV0 {
202                identity_id: vec![0u8; 10], // bad
203                token_ids: vec![vec![0u8; 32]],
204                prove: true,
205            })),
206        };
207        let response = GetIdentityTokenBalancesResponse::default();
208        let err = <IdentityTokenBalances as FromProof<_>>::maybe_from_proof(
209            request,
210            response,
211            Network::Testnet,
212            pv(),
213            &UnreachableProvider,
214        )
215        .unwrap_err();
216        match err {
217            Error::RequestError { error } => assert!(error.contains("identity_id"), "got: {error}"),
218            other => panic!("expected RequestError, got: {other:?}"),
219        }
220    }
221
222    #[test]
223    fn identity_token_balances_request_error_on_bad_token_id() {
224        let request = GetIdentityTokenBalancesRequest {
225            version: Some(IdReqVersion::V0(GetIdentityTokenBalancesRequestV0 {
226                identity_id: vec![0u8; 32],
227                token_ids: vec![vec![0u8; 32], vec![1u8; 5]], // second token_id bad
228                prove: true,
229            })),
230        };
231        let response = GetIdentityTokenBalancesResponse::default();
232        let err = <IdentityTokenBalances as FromProof<_>>::maybe_from_proof(
233            request,
234            response,
235            Network::Testnet,
236            pv(),
237            &UnreachableProvider,
238        )
239        .unwrap_err();
240        match err {
241            Error::RequestError { error } => assert!(error.contains("token_id"), "got: {error}"),
242            other => panic!("expected RequestError, got: {other:?}"),
243        }
244    }
245
246    // -------- IdentitiesTokenBalances --------
247
248    #[test]
249    fn identities_token_balances_empty_version_on_request_missing() {
250        let request = GetIdentitiesTokenBalancesRequest { version: None };
251        let response = GetIdentitiesTokenBalancesResponse::default();
252        let err = <IdentitiesTokenBalances as FromProof<_>>::maybe_from_proof(
253            request,
254            response,
255            Network::Testnet,
256            pv(),
257            &UnreachableProvider,
258        )
259        .unwrap_err();
260        assert!(matches!(err, Error::EmptyVersion), "got: {err:?}");
261    }
262
263    #[test]
264    fn identities_token_balances_request_error_on_bad_token_id() {
265        let request = GetIdentitiesTokenBalancesRequest {
266            version: Some(IdsReqVersion::V0(GetIdentitiesTokenBalancesRequestV0 {
267                token_id: vec![0u8; 7], // bad
268                identity_ids: vec![vec![1u8; 32]],
269                prove: true,
270            })),
271        };
272        let response = GetIdentitiesTokenBalancesResponse::default();
273        let err = <IdentitiesTokenBalances as FromProof<_>>::maybe_from_proof(
274            request,
275            response,
276            Network::Testnet,
277            pv(),
278            &UnreachableProvider,
279        )
280        .unwrap_err();
281        assert!(matches!(err, Error::RequestError { .. }), "got: {err:?}");
282    }
283
284    #[test]
285    fn identities_token_balances_request_error_on_bad_identity_id() {
286        let request = GetIdentitiesTokenBalancesRequest {
287            version: Some(IdsReqVersion::V0(GetIdentitiesTokenBalancesRequestV0 {
288                token_id: vec![0u8; 32],
289                identity_ids: vec![vec![1u8; 32], vec![2u8; 33]], // too long
290                prove: true,
291            })),
292        };
293        let response = GetIdentitiesTokenBalancesResponse::default();
294        let err = <IdentitiesTokenBalances as FromProof<_>>::maybe_from_proof(
295            request,
296            response,
297            Network::Testnet,
298            pv(),
299            &UnreachableProvider,
300        )
301        .unwrap_err();
302        match err {
303            Error::RequestError { error } => assert!(error.contains("identity_id"), "got: {error}"),
304            other => panic!("expected RequestError, got: {other:?}"),
305        }
306    }
307}