dpp/identity/state_transition/asset_lock_proof/
mod.rs1use std::convert::{TryFrom, TryInto};
2
3use dashcore::{OutPoint, Transaction};
4
5use serde::{Deserialize, Deserializer, Serialize};
6
7use bincode::{Decode, Encode};
8
9pub use instant::*;
10use platform_value::Value;
11#[cfg(feature = "validation")]
12use platform_version::version::PlatformVersion;
13use serde::de::Error;
14
15use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
16use crate::prelude::Identifier;
17#[cfg(feature = "validation")]
18use crate::validation::SimpleConsensusValidationResult;
19use crate::{ProtocolError, SerdeParsingError};
20
21pub mod chain;
22pub mod instant;
23pub mod validate_asset_lock_transaction_structure;
24
25#[derive(Clone, Debug, Eq, PartialEq, Serialize, Encode, Decode)]
38#[serde(tag = "type", rename_all = "camelCase")]
39#[allow(clippy::large_enum_variant)]
40pub enum AssetLockProof {
41 Instant(#[bincode(with_serde)] InstantAssetLockProof),
42 Chain(#[bincode(with_serde)] ChainAssetLockProof),
43}
44
45#[derive(Deserialize)]
49#[serde(tag = "type", rename_all = "camelCase")]
50enum RawAssetLockProof {
51 Instant(RawInstantLockProof),
52 Chain(ChainAssetLockProof),
53}
54
55impl TryFrom<RawAssetLockProof> for AssetLockProof {
56 type Error = ProtocolError;
57
58 fn try_from(value: RawAssetLockProof) -> Result<Self, Self::Error> {
59 match value {
60 RawAssetLockProof::Instant(raw_instant_lock) => {
61 let instant_lock = raw_instant_lock.try_into()?;
62
63 Ok(AssetLockProof::Instant(instant_lock))
64 }
65 RawAssetLockProof::Chain(chain) => Ok(AssetLockProof::Chain(chain)),
66 }
67 }
68}
69
70impl<'de> Deserialize<'de> for AssetLockProof {
71 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72 where
73 D: Deserializer<'de>,
74 {
75 let raw = RawAssetLockProof::deserialize(deserializer)?;
76 raw.try_into().map_err(|e: ProtocolError| {
77 D::Error::custom(format!(
78 "expected to be able to deserialize asset lock proof: {}",
79 e
80 ))
81 })
82 }
83}
84
85impl Default for AssetLockProof {
86 fn default() -> Self {
87 Self::Instant(InstantAssetLockProof::default())
88 }
89}
90
91impl AsRef<AssetLockProof> for AssetLockProof {
92 fn as_ref(&self) -> &AssetLockProof {
93 self
94 }
95}
96pub enum AssetLockProofType {
97 Instant = 0,
98 Chain = 1,
99}
100
101impl TryFrom<u8> for AssetLockProofType {
102 type Error = SerdeParsingError;
103
104 fn try_from(value: u8) -> Result<Self, Self::Error> {
105 match value {
106 0 => Ok(Self::Instant),
107 1 => Ok(Self::Chain),
108 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
109 }
110 }
111}
112
113impl TryFrom<u64> for AssetLockProofType {
114 type Error = SerdeParsingError;
115
116 fn try_from(value: u64) -> Result<Self, Self::Error> {
117 match value {
118 0 => Ok(Self::Instant),
119 1 => Ok(Self::Chain),
120 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
121 }
122 }
123}
124
125impl AssetLockProof {
127 pub fn type_from_raw_value(value: &Value) -> Option<AssetLockProofType> {
128 let proof_type_res = value.get_integer::<u8>("type");
129
130 match proof_type_res {
131 Ok(proof_type_int) => {
132 let proof_type = AssetLockProofType::try_from(proof_type_int);
133 proof_type.ok()
134 }
135 Err(_) => None,
136 }
137 }
138
139 pub fn create_identifier(&self) -> Result<Identifier, ProtocolError> {
140 match self {
141 AssetLockProof::Instant(instant_proof) => instant_proof.create_identifier(),
142 AssetLockProof::Chain(chain_proof) => Ok(chain_proof.create_identifier()),
143 }
144 }
145
146 pub fn output_index(&self) -> u32 {
147 match self {
148 AssetLockProof::Instant(proof) => proof.output_index(),
149 AssetLockProof::Chain(proof) => proof.out_point.vout,
150 }
151 }
152
153 pub fn out_point(&self) -> Option<OutPoint> {
154 match self {
155 AssetLockProof::Instant(proof) => proof.out_point(),
156 AssetLockProof::Chain(proof) => Some(proof.out_point),
157 }
158 }
159
160 pub fn transaction(&self) -> Option<&Transaction> {
161 match self {
162 AssetLockProof::Instant(is_lock) => Some(is_lock.transaction()),
163 AssetLockProof::Chain(_chain_lock) => None,
164 }
165 }
166
167 pub fn to_raw_object(&self) -> Result<Value, ProtocolError> {
168 match self {
169 AssetLockProof::Instant(is) => {
170 platform_value::to_value(is).map_err(ProtocolError::ValueError)
171 }
172 AssetLockProof::Chain(cl) => {
173 platform_value::to_value(cl).map_err(ProtocolError::ValueError)
174 }
175 }
176 }
177
178 #[cfg(feature = "validation")]
180 pub fn validate_structure(
181 &self,
182 platform_version: &PlatformVersion,
183 ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
184 match self {
185 AssetLockProof::Instant(proof) => proof.validate_structure(platform_version),
186 AssetLockProof::Chain(_) => Ok(SimpleConsensusValidationResult::default()),
187 }
188 }
189}
190
191impl TryFrom<&Value> for AssetLockProof {
192 type Error = ProtocolError;
193
194 fn try_from(value: &Value) -> Result<Self, Self::Error> {
195 let proof_type_int: Option<u8> = value
199 .get_optional_integer("type")
200 .map_err(ProtocolError::ValueError)?;
201 if let Some(proof_type_int) = proof_type_int {
202 let proof_type = AssetLockProofType::try_from(proof_type_int)?;
203
204 match proof_type {
205 AssetLockProofType::Instant => Ok(Self::Instant(value.clone().try_into()?)),
206 AssetLockProofType::Chain => Ok(Self::Chain(value.clone().try_into()?)),
207 }
208 } else {
209 let map = value.as_map().ok_or(ProtocolError::DecodingError(
210 "error decoding asset lock proof".to_string(),
211 ))?;
212 let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError(
213 "error decoding asset lock proof as it was empty".to_string(),
214 ))?;
215 match key.as_str().ok_or(ProtocolError::DecodingError(
216 "error decoding asset lock proof".to_string(),
217 ))? {
218 "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)),
219 "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)),
220 _ => Err(ProtocolError::DecodingError(
221 "error decoding asset lock proof".to_string(),
222 )),
223 }
224 }
225 }
226}
227
228impl TryFrom<Value> for AssetLockProof {
229 type Error = ProtocolError;
230
231 fn try_from(value: Value) -> Result<Self, Self::Error> {
232 let proof_type_int: Option<u8> = value
233 .get_optional_integer("type")
234 .map_err(ProtocolError::ValueError)?;
235 if let Some(proof_type_int) = proof_type_int {
236 let proof_type = AssetLockProofType::try_from(proof_type_int)?;
237
238 match proof_type {
239 AssetLockProofType::Instant => Ok(Self::Instant(value.try_into()?)),
240 AssetLockProofType::Chain => Ok(Self::Chain(value.try_into()?)),
241 }
242 } else {
243 let map = value.as_map().ok_or(ProtocolError::DecodingError(
244 "error decoding asset lock proof".to_string(),
245 ))?;
246 let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError(
247 "error decoding asset lock proof as it was empty".to_string(),
248 ))?;
249 match key.as_str().ok_or(ProtocolError::DecodingError(
250 "error decoding asset lock proof".to_string(),
251 ))? {
252 "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)),
253 "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)),
254 _ => Err(ProtocolError::DecodingError(
255 "error decoding asset lock proof".to_string(),
256 )),
257 }
258 }
259 }
260}
261
262impl TryInto<Value> for AssetLockProof {
263 type Error = ProtocolError;
264
265 fn try_into(self) -> Result<Value, Self::Error> {
266 match self {
267 AssetLockProof::Instant(instant_proof) => {
268 platform_value::to_value(instant_proof).map_err(ProtocolError::ValueError)
269 }
270 AssetLockProof::Chain(chain_proof) => {
271 platform_value::to_value(chain_proof).map_err(ProtocolError::ValueError)
272 }
273 }
274 }
275}
276
277impl TryInto<Value> for &AssetLockProof {
278 type Error = ProtocolError;
279
280 fn try_into(self) -> Result<Value, Self::Error> {
281 match self {
282 AssetLockProof::Instant(instant_proof) => {
283 platform_value::to_value(instant_proof).map_err(ProtocolError::ValueError)
284 }
285 AssetLockProof::Chain(chain_proof) => {
286 platform_value::to_value(chain_proof).map_err(ProtocolError::ValueError)
287 }
288 }
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
296 use dashcore::{OutPoint, Txid};
297 use std::str::FromStr;
298
299 #[test]
304 fn chain_variant_serializes_with_internal_tag() {
305 let txid =
306 Txid::from_str("e8b43025641eea4fd21190f01bd870ef90f1a8b199d8fc3376c5b62c0b1a179d")
307 .unwrap();
308 let proof = AssetLockProof::Chain(ChainAssetLockProof {
309 core_chain_locked_height: 11,
310 out_point: OutPoint { txid, vout: 1 },
311 });
312
313 let json = serde_json::to_value(&proof).expect("serialize");
314
315 assert_eq!(json["type"], "chain");
316 assert_eq!(json["coreChainLockedHeight"], 11);
317 assert!(
318 json.get("data").is_none(),
319 "should not have a `data` wrapper, got: {}",
320 json
321 );
322
323 let restored: AssetLockProof = serde_json::from_value(json).expect("deserialize");
325 assert_eq!(proof, restored);
326 }
327
328 mod asset_lock_proof_type_try_from {
329 use super::*;
330
331 #[test]
332 fn u8_instant_type() {
333 let proof_type = AssetLockProofType::try_from(0u8).expect("should parse type 0");
334 assert!(matches!(proof_type, AssetLockProofType::Instant));
335 }
336
337 #[test]
338 fn u8_chain_type() {
339 let proof_type = AssetLockProofType::try_from(1u8).expect("should parse type 1");
340 assert!(matches!(proof_type, AssetLockProofType::Chain));
341 }
342
343 #[test]
344 fn u8_invalid_type() {
345 let result = AssetLockProofType::try_from(2u8);
346 assert!(result.is_err());
347 }
348
349 #[test]
350 fn u8_max_invalid_type() {
351 let result = AssetLockProofType::try_from(255u8);
352 assert!(result.is_err());
353 }
354
355 #[test]
356 fn u64_instant_type() {
357 let proof_type = AssetLockProofType::try_from(0u64).expect("should parse type 0");
358 assert!(matches!(proof_type, AssetLockProofType::Instant));
359 }
360
361 #[test]
362 fn u64_chain_type() {
363 let proof_type = AssetLockProofType::try_from(1u64).expect("should parse type 1");
364 assert!(matches!(proof_type, AssetLockProofType::Chain));
365 }
366
367 #[test]
368 fn u64_invalid_type() {
369 let result = AssetLockProofType::try_from(2u64);
370 assert!(result.is_err());
371 }
372
373 #[test]
374 fn u64_large_invalid_type() {
375 let result = AssetLockProofType::try_from(u64::MAX);
376 assert!(result.is_err());
377 }
378 }
379
380 mod chain_asset_lock_proof {
381 use super::*;
382
383 fn make_chain_proof() -> ChainAssetLockProof {
384 ChainAssetLockProof::new(100, [0xAB; 36])
385 }
386
387 #[test]
388 fn chain_proof_construction() {
389 let proof = ChainAssetLockProof::new(42, [0x01; 36]);
390 assert_eq!(proof.core_chain_locked_height, 42);
391 }
392
393 #[test]
394 fn chain_proof_create_identifier_deterministic() {
395 let proof = make_chain_proof();
396 let id1 = proof.create_identifier();
397 let id2 = proof.create_identifier();
398 assert_eq!(id1, id2);
399 }
400
401 #[test]
402 fn different_outpoints_produce_different_identifiers() {
403 let proof_a = ChainAssetLockProof::new(100, [0xAA; 36]);
404 let proof_b = ChainAssetLockProof::new(100, [0xBB; 36]);
405 assert_ne!(proof_a.create_identifier(), proof_b.create_identifier());
406 }
407
408 #[test]
409 fn chain_proof_equality() {
410 let a = ChainAssetLockProof::new(10, [0x01; 36]);
411 let b = ChainAssetLockProof::new(10, [0x01; 36]);
412 assert_eq!(a, b);
413 }
414
415 #[test]
416 fn chain_proof_inequality_height() {
417 let a = ChainAssetLockProof::new(10, [0x01; 36]);
418 let b = ChainAssetLockProof::new(20, [0x01; 36]);
419 assert_ne!(a, b);
420 }
421 }
422
423 mod asset_lock_proof_methods {
424 use super::*;
425
426 fn make_chain_lock_proof() -> AssetLockProof {
427 let chain_proof = ChainAssetLockProof::new(50, [0xCC; 36]);
428 AssetLockProof::Chain(chain_proof)
429 }
430
431 #[test]
432 fn default_is_instant() {
433 let proof = AssetLockProof::default();
434 assert!(matches!(proof, AssetLockProof::Instant(_)));
435 }
436
437 #[test]
438 fn as_ref_returns_self() {
439 let proof = make_chain_lock_proof();
440 let reference: &AssetLockProof = proof.as_ref();
441 assert_eq!(&proof, reference);
442 }
443
444 #[test]
445 fn chain_proof_output_index() {
446 let mut out_point_bytes = [0u8; 36];
447 out_point_bytes[32] = 3;
449 let chain_proof = ChainAssetLockProof::new(50, out_point_bytes);
450 let proof = AssetLockProof::Chain(chain_proof);
451 assert_eq!(proof.output_index(), 3);
452 }
453
454 #[test]
455 fn chain_proof_out_point_is_some() {
456 let proof = make_chain_lock_proof();
457 assert!(proof.out_point().is_some());
458 }
459
460 #[test]
461 fn chain_proof_transaction_is_none() {
462 let proof = make_chain_lock_proof();
463 assert!(proof.transaction().is_none());
464 }
465
466 #[test]
467 fn chain_proof_to_raw_object() {
468 let proof = make_chain_lock_proof();
469 let result = proof.to_raw_object();
470 assert!(result.is_ok());
471 }
472
473 #[test]
474 fn chain_proof_create_identifier() {
475 let proof = make_chain_lock_proof();
476 let id = proof.create_identifier();
477 assert!(id.is_ok());
478 }
479 }
480
481 mod try_from_value {
482 use super::*;
483
484 #[test]
485 fn chain_proof_value_round_trip() {
486 let chain_proof = ChainAssetLockProof::new(100, [0x42; 36]);
487 let proof = AssetLockProof::Chain(chain_proof);
488
489 let value: Value = (&proof).try_into().expect("should convert to Value");
491
492 let _type_from_value = AssetLockProof::type_from_raw_value(&value);
494 let raw_value = proof.to_raw_object().expect("should convert to raw object");
501 assert!(!raw_value.is_null());
502 }
503
504 #[test]
505 fn type_from_raw_value_returns_none_for_missing_type() {
506 let value = Value::Map(vec![]);
507 let result = AssetLockProof::type_from_raw_value(&value);
508 assert!(result.is_none());
509 }
510
511 #[test]
512 fn try_from_empty_map_fails() {
513 let value = Value::Map(vec![]);
514 let result = AssetLockProof::try_from(&value);
515 assert!(result.is_err());
516 }
517
518 #[test]
519 fn try_from_value_with_unknown_key_fails() {
520 let value = Value::Map(vec![(
521 Value::Text("Unknown".to_string()),
522 Value::Map(vec![]),
523 )]);
524 let result = AssetLockProof::try_from(&value);
525 assert!(result.is_err());
526 }
527 }
528
529 mod try_into_value {
530 use super::*;
531
532 #[test]
533 fn chain_proof_try_into_value() {
534 let chain_proof = ChainAssetLockProof::new(200, [0xDD; 36]);
535 let proof = AssetLockProof::Chain(chain_proof);
536
537 let value: Result<Value, ProtocolError> = proof.try_into();
538 assert!(value.is_ok());
539 }
540
541 #[test]
542 fn chain_proof_ref_try_into_value() {
543 let chain_proof = ChainAssetLockProof::new(200, [0xDD; 36]);
544 let proof = AssetLockProof::Chain(chain_proof);
545
546 let value: Result<Value, ProtocolError> = (&proof).try_into();
547 assert!(value.is_ok());
548 }
549 }
550}