partner_chains_plutus_data/
lib.rs

1//! This crate contains datum types used by Plutus smart contracts from [raw_scripts::SCRIPTS].
2#![deny(missing_docs)]
3use cardano_serialization_lib::{PlutusData, PlutusList};
4
5pub mod candidate_keys;
6pub mod d_param;
7pub mod governed_map;
8pub mod permissioned_candidates;
9pub mod registered_candidates;
10pub mod reserve;
11pub mod version_oracle;
12
13#[derive(Debug, PartialEq, thiserror::Error)]
14#[error("Could not decode {datum:?} to {to}: {msg}")]
15/// Error type for decoding failures of [PlutusData].
16pub struct DataDecodingError {
17	datum: PlutusData,
18	to: String,
19	msg: String,
20}
21
22type DecodingResult<T> = std::result::Result<T, DataDecodingError>;
23
24/// Extension trait for [PlutusData].
25pub trait PlutusDataExtensions {
26	/// Tries to interpret a [PlutusData] as [u64].
27	fn as_u64(&self) -> Option<u64>;
28	/// Tries to interpret a [PlutusData] as [u32].
29	fn as_u32(&self) -> Option<u32>;
30	/// Tries to interpret a [PlutusData] as [u16].
31	fn as_u16(&self) -> Option<u16>;
32}
33
34impl PlutusDataExtensions for PlutusData {
35	fn as_u64(&self) -> Option<u64> {
36		self.as_integer()?.as_u64().map(u64::from)
37	}
38	fn as_u32(&self) -> Option<u32> {
39		u32::try_from(self.as_integer()?.as_u64()?).ok()
40	}
41	fn as_u16(&self) -> Option<u16> {
42		u16::try_from(self.as_u32()?).ok()
43	}
44}
45
46/// Trait that provides decoding of versioned generic plutus data.
47///
48/// Versioned generic plutus data contain a version number and two data sections:
49/// - datum - the data with stable schema, read and validated by smart contracts
50/// - appendix - generic data with evolving schema indicated by the version number, not used by smart contracts
51///
52/// The corresponding definition in the smart contracts repo is:
53/// ```haskell
54/// data VersionedGenericDatum a = VersionedGenericDatum
55///     { datum :: a
56///     , genericData :: BuiltinData
57///     , version :: Integer
58///     }
59/// ```
60pub(crate) trait VersionedDatum: Sized {
61	/// Parses versioned plutus data.
62	fn decode(data: &PlutusData) -> DecodingResult<Self>;
63}
64
65/// Trait that provides decoding of versioned generic plutus data with a legacy schema support.
66///
67/// It is assumed that versions 0 and legacy are equivalent.
68pub(crate) trait VersionedDatumWithLegacy: Sized {
69	const NAME: &str;
70
71	/// Parses plutus data schema that was used before datum versioning was added. Kept for backwards compatibility.
72	fn decode_legacy(data: &PlutusData) -> Result<Self, String>;
73
74	/// Parses versioned plutus data.
75	///
76	/// Parameters:
77	/// * `version` - version number
78	/// * `datum` - datum with schema specified by smart-contract
79	/// * `appendix` - generic data ignored by smart-contract logic, schema is version dependent
80	fn decode_versioned(
81		version: u64,
82		datum: &PlutusData,
83		appendix: &PlutusData,
84	) -> Result<Self, String>;
85}
86
87impl<T: VersionedDatumWithLegacy> VersionedDatum for T {
88	fn decode(data: &PlutusData) -> DecodingResult<Self> {
89		(match plutus_data_version_and_payload(data) {
90			None => Self::decode_legacy(data),
91			Some(VersionedGenericDatum { datum, appendix, version }) => {
92				Self::decode_versioned(version, &datum, &appendix)
93			},
94		})
95		.map_err(|msg| decoding_error_and_log(data, Self::NAME, &msg))
96	}
97}
98
99fn plutus_data_version_and_payload(data: &PlutusData) -> Option<VersionedGenericDatum> {
100	let fields = data.as_list().filter(|outer_list| outer_list.len() == 3)?;
101
102	Some(VersionedGenericDatum {
103		datum: fields.get(0),
104		appendix: fields.get(1),
105		version: fields.get(2).as_u64()?,
106	})
107}
108
109fn decoding_error_and_log(data: &PlutusData, to: &str, msg: &str) -> DataDecodingError {
110	log::error!("Could not decode {data:?} to {to}: {msg}");
111	DataDecodingError { datum: data.clone(), to: to.to_string(), msg: msg.to_string() }
112}
113
114/// This struct has the same shape as `VersionedGenericDatum` from smart-contracts.
115/// It is used to help implementing a proper `From` trait for `PlutusData` for
116/// datum types.
117pub struct VersionedGenericDatum {
118	/// data that is interpreted by smart-contract
119	pub datum: PlutusData,
120	/// generic data ignored by smart-contract logic, schema is version dependent
121	pub appendix: PlutusData,
122	/// version number
123	pub version: u64,
124}
125
126impl From<VersionedGenericDatum> for PlutusData {
127	fn from(value: VersionedGenericDatum) -> Self {
128		let mut list = PlutusList::new();
129		list.add(&value.datum);
130		list.add(&value.appendix);
131		list.add(&PlutusData::new_integer(&value.version.into()));
132		PlutusData::new_list(&list)
133	}
134}
135
136#[cfg(test)]
137pub(crate) mod test_helpers {
138	use cardano_serialization_lib::PlutusData;
139	macro_rules! test_plutus_data {
140		($json:tt) => {
141			cardano_serialization_lib::encode_json_value_to_plutus_datum(
142				serde_json::json!($json),
143				cardano_serialization_lib::PlutusDatumSchema::DetailedSchema,
144			)
145			.expect("test data is valid")
146		};
147	}
148	pub(crate) use test_plutus_data;
149
150	pub(crate) fn json_to_plutus_data(json: serde_json::Value) -> PlutusData {
151		cardano_serialization_lib::encode_json_value_to_plutus_datum(
152			json,
153			cardano_serialization_lib::PlutusDatumSchema::DetailedSchema,
154		)
155		.expect("test data is valid")
156	}
157
158	pub(crate) fn plutus_data_to_json(data: PlutusData) -> serde_json::Value {
159		cardano_serialization_lib::decode_plutus_datum_to_json_value(
160			&data,
161			cardano_serialization_lib::PlutusDatumSchema::DetailedSchema,
162		)
163		.expect("test data is valid")
164	}
165}