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)]
28#[serde(untagged)]
29#[allow(clippy::large_enum_variant)]
30pub enum AssetLockProof {
31 Instant(#[bincode(with_serde)] InstantAssetLockProof),
32 Chain(#[bincode(with_serde)] ChainAssetLockProof),
33}
34
35#[derive(Deserialize)]
36#[serde(untagged)]
37enum RawAssetLockProof {
38 Instant(RawInstantLockProof),
39 Chain(ChainAssetLockProof),
40}
41
42impl TryFrom<RawAssetLockProof> for AssetLockProof {
43 type Error = ProtocolError;
44
45 fn try_from(value: RawAssetLockProof) -> Result<Self, Self::Error> {
46 match value {
47 RawAssetLockProof::Instant(raw_instant_lock) => {
48 let instant_lock = raw_instant_lock.try_into()?;
49
50 Ok(AssetLockProof::Instant(instant_lock))
51 }
52 RawAssetLockProof::Chain(chain) => Ok(AssetLockProof::Chain(chain)),
53 }
54 }
55}
56
57impl<'de> Deserialize<'de> for AssetLockProof {
58 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59 where
60 D: Deserializer<'de>,
61 {
62 let raw = RawAssetLockProof::deserialize(deserializer)?;
78 raw.try_into().map_err(|e: ProtocolError| {
79 D::Error::custom(format!(
80 "expected to be able to deserialize asset lock proof: {}",
81 e
82 ))
83 })
84 }
85}
86
87impl Default for AssetLockProof {
88 fn default() -> Self {
89 Self::Instant(InstantAssetLockProof::default())
90 }
91}
92
93impl AsRef<AssetLockProof> for AssetLockProof {
94 fn as_ref(&self) -> &AssetLockProof {
95 self
96 }
97}
98pub enum AssetLockProofType {
136 Instant = 0,
137 Chain = 1,
138}
139
140impl TryFrom<u8> for AssetLockProofType {
141 type Error = SerdeParsingError;
142
143 fn try_from(value: u8) -> Result<Self, Self::Error> {
144 match value {
145 0 => Ok(Self::Instant),
146 1 => Ok(Self::Chain),
147 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
148 }
149 }
150}
151
152impl TryFrom<u64> for AssetLockProofType {
153 type Error = SerdeParsingError;
154
155 fn try_from(value: u64) -> Result<Self, Self::Error> {
156 match value {
157 0 => Ok(Self::Instant),
158 1 => Ok(Self::Chain),
159 _ => Err(SerdeParsingError::new("Unexpected asset lock proof type")),
160 }
161 }
162}
163
164impl AssetLockProof {
166 pub fn type_from_raw_value(value: &Value) -> Option<AssetLockProofType> {
167 let proof_type_res = value.get_integer::<u8>("type");
168
169 match proof_type_res {
170 Ok(proof_type_int) => {
171 let proof_type = AssetLockProofType::try_from(proof_type_int);
172 proof_type.ok()
173 }
174 Err(_) => None,
175 }
176 }
177
178 pub fn create_identifier(&self) -> Result<Identifier, ProtocolError> {
179 match self {
180 AssetLockProof::Instant(instant_proof) => instant_proof.create_identifier(),
181 AssetLockProof::Chain(chain_proof) => Ok(chain_proof.create_identifier()),
182 }
183 }
184
185 pub fn output_index(&self) -> u32 {
186 match self {
187 AssetLockProof::Instant(proof) => proof.output_index(),
188 AssetLockProof::Chain(proof) => proof.out_point.vout,
189 }
190 }
191
192 pub fn out_point(&self) -> Option<OutPoint> {
193 match self {
194 AssetLockProof::Instant(proof) => proof.out_point(),
195 AssetLockProof::Chain(proof) => Some(proof.out_point),
196 }
197 }
198
199 pub fn transaction(&self) -> Option<&Transaction> {
200 match self {
201 AssetLockProof::Instant(is_lock) => Some(is_lock.transaction()),
202 AssetLockProof::Chain(_chain_lock) => None,
203 }
204 }
205
206 pub fn to_raw_object(&self) -> Result<Value, ProtocolError> {
207 match self {
208 AssetLockProof::Instant(is) => {
209 platform_value::to_value(is).map_err(ProtocolError::ValueError)
210 }
211 AssetLockProof::Chain(cl) => {
212 platform_value::to_value(cl).map_err(ProtocolError::ValueError)
213 }
214 }
215 }
216
217 #[cfg(feature = "validation")]
219 pub fn validate_structure(
220 &self,
221 platform_version: &PlatformVersion,
222 ) -> Result<SimpleConsensusValidationResult, ProtocolError> {
223 match self {
224 AssetLockProof::Instant(proof) => proof.validate_structure(platform_version),
225 AssetLockProof::Chain(_) => Ok(SimpleConsensusValidationResult::default()),
226 }
227 }
228}
229
230impl TryFrom<&Value> for AssetLockProof {
231 type Error = ProtocolError;
232
233 fn try_from(value: &Value) -> Result<Self, Self::Error> {
234 let proof_type_int: Option<u8> = value
238 .get_optional_integer("type")
239 .map_err(ProtocolError::ValueError)?;
240 if let Some(proof_type_int) = proof_type_int {
241 let proof_type = AssetLockProofType::try_from(proof_type_int)?;
242
243 match proof_type {
244 AssetLockProofType::Instant => Ok(Self::Instant(value.clone().try_into()?)),
245 AssetLockProofType::Chain => Ok(Self::Chain(value.clone().try_into()?)),
246 }
247 } else {
248 let map = value.as_map().ok_or(ProtocolError::DecodingError(
249 "error decoding asset lock proof".to_string(),
250 ))?;
251 let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError(
252 "error decoding asset lock proof as it was empty".to_string(),
253 ))?;
254 match key.as_str().ok_or(ProtocolError::DecodingError(
255 "error decoding asset lock proof".to_string(),
256 ))? {
257 "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)),
258 "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)),
259 _ => Err(ProtocolError::DecodingError(
260 "error decoding asset lock proof".to_string(),
261 )),
262 }
263 }
264 }
265}
266
267impl TryFrom<Value> for AssetLockProof {
268 type Error = ProtocolError;
269
270 fn try_from(value: Value) -> Result<Self, Self::Error> {
271 let proof_type_int: Option<u8> = value
272 .get_optional_integer("type")
273 .map_err(ProtocolError::ValueError)?;
274 if let Some(proof_type_int) = proof_type_int {
275 let proof_type = AssetLockProofType::try_from(proof_type_int)?;
276
277 match proof_type {
278 AssetLockProofType::Instant => Ok(Self::Instant(value.try_into()?)),
279 AssetLockProofType::Chain => Ok(Self::Chain(value.try_into()?)),
280 }
281 } else {
282 let map = value.as_map().ok_or(ProtocolError::DecodingError(
283 "error decoding asset lock proof".to_string(),
284 ))?;
285 let (key, asset_lock_value) = map.first().ok_or(ProtocolError::DecodingError(
286 "error decoding asset lock proof as it was empty".to_string(),
287 ))?;
288 match key.as_str().ok_or(ProtocolError::DecodingError(
289 "error decoding asset lock proof".to_string(),
290 ))? {
291 "Instant" => Ok(Self::Instant(asset_lock_value.clone().try_into()?)),
292 "Chain" => Ok(Self::Chain(asset_lock_value.clone().try_into()?)),
293 _ => Err(ProtocolError::DecodingError(
294 "error decoding asset lock proof".to_string(),
295 )),
296 }
297 }
298 }
299}
300
301impl TryInto<Value> for AssetLockProof {
302 type Error = ProtocolError;
303
304 fn try_into(self) -> Result<Value, Self::Error> {
305 match self {
306 AssetLockProof::Instant(instant_proof) => {
307 platform_value::to_value(instant_proof).map_err(ProtocolError::ValueError)
308 }
309 AssetLockProof::Chain(chain_proof) => {
310 platform_value::to_value(chain_proof).map_err(ProtocolError::ValueError)
311 }
312 }
313 }
314}
315
316impl TryInto<Value> for &AssetLockProof {
317 type Error = ProtocolError;
318
319 fn try_into(self) -> Result<Value, Self::Error> {
320 match self {
321 AssetLockProof::Instant(instant_proof) => {
322 platform_value::to_value(instant_proof).map_err(ProtocolError::ValueError)
323 }
324 AssetLockProof::Chain(chain_proof) => {
325 platform_value::to_value(chain_proof).map_err(ProtocolError::ValueError)
326 }
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof;
335 use dashcore::{OutPoint, Txid};
336
337 mod asset_lock_proof_type_try_from {
338 use super::*;
339
340 #[test]
341 fn u8_instant_type() {
342 let proof_type = AssetLockProofType::try_from(0u8).expect("should parse type 0");
343 assert!(matches!(proof_type, AssetLockProofType::Instant));
344 }
345
346 #[test]
347 fn u8_chain_type() {
348 let proof_type = AssetLockProofType::try_from(1u8).expect("should parse type 1");
349 assert!(matches!(proof_type, AssetLockProofType::Chain));
350 }
351
352 #[test]
353 fn u8_invalid_type() {
354 let result = AssetLockProofType::try_from(2u8);
355 assert!(result.is_err());
356 }
357
358 #[test]
359 fn u8_max_invalid_type() {
360 let result = AssetLockProofType::try_from(255u8);
361 assert!(result.is_err());
362 }
363
364 #[test]
365 fn u64_instant_type() {
366 let proof_type = AssetLockProofType::try_from(0u64).expect("should parse type 0");
367 assert!(matches!(proof_type, AssetLockProofType::Instant));
368 }
369
370 #[test]
371 fn u64_chain_type() {
372 let proof_type = AssetLockProofType::try_from(1u64).expect("should parse type 1");
373 assert!(matches!(proof_type, AssetLockProofType::Chain));
374 }
375
376 #[test]
377 fn u64_invalid_type() {
378 let result = AssetLockProofType::try_from(2u64);
379 assert!(result.is_err());
380 }
381
382 #[test]
383 fn u64_large_invalid_type() {
384 let result = AssetLockProofType::try_from(u64::MAX);
385 assert!(result.is_err());
386 }
387 }
388
389 mod chain_asset_lock_proof {
390 use super::*;
391
392 fn make_chain_proof() -> ChainAssetLockProof {
393 ChainAssetLockProof::new(100, [0xAB; 36])
394 }
395
396 #[test]
397 fn chain_proof_construction() {
398 let proof = ChainAssetLockProof::new(42, [0x01; 36]);
399 assert_eq!(proof.core_chain_locked_height, 42);
400 }
401
402 #[test]
403 fn chain_proof_create_identifier_deterministic() {
404 let proof = make_chain_proof();
405 let id1 = proof.create_identifier();
406 let id2 = proof.create_identifier();
407 assert_eq!(id1, id2);
408 }
409
410 #[test]
411 fn different_outpoints_produce_different_identifiers() {
412 let proof_a = ChainAssetLockProof::new(100, [0xAA; 36]);
413 let proof_b = ChainAssetLockProof::new(100, [0xBB; 36]);
414 assert_ne!(proof_a.create_identifier(), proof_b.create_identifier());
415 }
416
417 #[test]
418 fn chain_proof_equality() {
419 let a = ChainAssetLockProof::new(10, [0x01; 36]);
420 let b = ChainAssetLockProof::new(10, [0x01; 36]);
421 assert_eq!(a, b);
422 }
423
424 #[test]
425 fn chain_proof_inequality_height() {
426 let a = ChainAssetLockProof::new(10, [0x01; 36]);
427 let b = ChainAssetLockProof::new(20, [0x01; 36]);
428 assert_ne!(a, b);
429 }
430 }
431
432 mod asset_lock_proof_methods {
433 use super::*;
434
435 fn make_chain_lock_proof() -> AssetLockProof {
436 let chain_proof = ChainAssetLockProof::new(50, [0xCC; 36]);
437 AssetLockProof::Chain(chain_proof)
438 }
439
440 #[test]
441 fn default_is_instant() {
442 let proof = AssetLockProof::default();
443 assert!(matches!(proof, AssetLockProof::Instant(_)));
444 }
445
446 #[test]
447 fn as_ref_returns_self() {
448 let proof = make_chain_lock_proof();
449 let reference: &AssetLockProof = proof.as_ref();
450 assert_eq!(&proof, reference);
451 }
452
453 #[test]
454 fn chain_proof_output_index() {
455 let mut out_point_bytes = [0u8; 36];
456 out_point_bytes[32] = 3;
458 let chain_proof = ChainAssetLockProof::new(50, out_point_bytes);
459 let proof = AssetLockProof::Chain(chain_proof);
460 assert_eq!(proof.output_index(), 3);
461 }
462
463 #[test]
464 fn chain_proof_out_point_is_some() {
465 let proof = make_chain_lock_proof();
466 assert!(proof.out_point().is_some());
467 }
468
469 #[test]
470 fn chain_proof_transaction_is_none() {
471 let proof = make_chain_lock_proof();
472 assert!(proof.transaction().is_none());
473 }
474
475 #[test]
476 fn chain_proof_to_raw_object() {
477 let proof = make_chain_lock_proof();
478 let result = proof.to_raw_object();
479 assert!(result.is_ok());
480 }
481
482 #[test]
483 fn chain_proof_create_identifier() {
484 let proof = make_chain_lock_proof();
485 let id = proof.create_identifier();
486 assert!(id.is_ok());
487 }
488 }
489
490 mod try_from_value {
491 use super::*;
492
493 #[test]
494 fn chain_proof_value_round_trip() {
495 let chain_proof = ChainAssetLockProof::new(100, [0x42; 36]);
496 let proof = AssetLockProof::Chain(chain_proof);
497
498 let value: Value = (&proof).try_into().expect("should convert to Value");
500
501 let type_from_value = AssetLockProof::type_from_raw_value(&value);
503 let raw_value = proof.to_raw_object().expect("should convert to raw object");
510 assert!(!raw_value.is_null());
511 }
512
513 #[test]
514 fn type_from_raw_value_returns_none_for_missing_type() {
515 let value = Value::Map(vec![]);
516 let result = AssetLockProof::type_from_raw_value(&value);
517 assert!(result.is_none());
518 }
519
520 #[test]
521 fn try_from_empty_map_fails() {
522 let value = Value::Map(vec![]);
523 let result = AssetLockProof::try_from(&value);
524 assert!(result.is_err());
525 }
526
527 #[test]
528 fn try_from_value_with_unknown_key_fails() {
529 let value = Value::Map(vec![(
530 Value::Text("Unknown".to_string()),
531 Value::Map(vec![]),
532 )]);
533 let result = AssetLockProof::try_from(&value);
534 assert!(result.is_err());
535 }
536 }
537
538 mod try_into_value {
539 use super::*;
540
541 #[test]
542 fn chain_proof_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 #[test]
551 fn chain_proof_ref_try_into_value() {
552 let chain_proof = ChainAssetLockProof::new(200, [0xDD; 36]);
553 let proof = AssetLockProof::Chain(chain_proof);
554
555 let value: Result<Value, ProtocolError> = (&proof).try_into();
556 assert!(value.is_ok());
557 }
558 }
559}