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 d_param;
6pub mod governed_map;
7pub mod permissioned_candidates;
8pub mod registered_candidates;
9pub mod reserve;
10pub mod version_oracle;
11
12#[derive(Debug, PartialEq, thiserror::Error)]
13#[error("Could not decode {datum:?} to {to}: {msg}")]
14/// Error type for decoding failures of [PlutusData].
15pub struct DataDecodingError {
16	datum: PlutusData,
17	to: String,
18	msg: String,
19}
20
21type DecodingResult<T> = std::result::Result<T, DataDecodingError>;
22
23/// Extension trait for [PlutusData].
24pub trait PlutusDataExtensions {
25	/// Tries to interpret a [PlutusData] as [u64].
26	fn as_u64(&self) -> Option<u64>;
27	/// Tries to interpret a [PlutusData] as [u32].
28	fn as_u32(&self) -> Option<u32>;
29	/// Tries to interpret a [PlutusData] as [u16].
30	fn as_u16(&self) -> Option<u16>;
31}
32
33impl PlutusDataExtensions for PlutusData {
34	fn as_u64(&self) -> Option<u64> {
35		self.as_integer()?.as_u64().map(u64::from)
36	}
37	fn as_u32(&self) -> Option<u32> {
38		u32::try_from(self.as_integer()?.as_u64()?).ok()
39	}
40	fn as_u16(&self) -> Option<u16> {
41		u16::try_from(self.as_u32()?).ok()
42	}
43}
44
45/// Trait that provides decoding of versioned generic plutus data.
46///
47/// Versioned generic plutus data contain a version number and two data sections:
48/// - datum - the data with stable schema, read and validated by smart contracts
49/// - appendix - generic data with evolving schema indicated by the version number, not used by smart contracts
50///
51/// The corresponding definition in the smart contracts repo is:
52/// ```haskell
53/// data VersionedGenericDatum a = VersionedGenericDatum
54///     { datum :: a
55///     , genericData :: BuiltinData
56///     , version :: Integer
57///     }
58/// ```
59pub(crate) trait VersionedDatum: Sized {
60	const NAME: &str;
61
62	/// Parses versioned plutus data.
63	fn decode(data: &PlutusData) -> DecodingResult<Self>;
64}
65
66/// Trait that provides decoding of versioned generic plutus data with a legacy schema support.
67///
68/// It is assumed that versions 0 and legacy are equivalent.
69pub(crate) trait VersionedDatumWithLegacy: Sized {
70	const NAME: &str;
71
72	/// Parses plutus data schema that was used before datum versioning was added. Kept for backwards compatibility.
73	fn decode_legacy(data: &PlutusData) -> Result<Self, String>;
74
75	/// Parses versioned plutus data.
76	///
77	/// Parameters:
78	/// * `version` - version number
79	/// * `datum` - datum with schema specified by smart-contract
80	/// * `appendix` - generic data ignored by smart-contract logic, schema is version dependent
81	fn decode_versioned(
82		version: u64,
83		datum: &PlutusData,
84		appendix: &PlutusData,
85	) -> Result<Self, String>;
86}
87
88impl<T: VersionedDatumWithLegacy> VersionedDatum for T {
89	const NAME: &str = <Self as VersionedDatumWithLegacy>::NAME;
90
91	fn decode(data: &PlutusData) -> DecodingResult<Self> {
92		(match plutus_data_version_and_payload(data) {
93			None => Self::decode_legacy(data),
94			Some(VersionedGenericDatum { datum, appendix, version }) => {
95				Self::decode_versioned(version, &datum, &appendix)
96			},
97		})
98		.map_err(|msg| decoding_error_and_log(data, Self::NAME, &msg))
99	}
100}
101
102fn plutus_data_version_and_payload(data: &PlutusData) -> Option<VersionedGenericDatum> {
103	let fields = data.as_list().filter(|outer_list| outer_list.len() == 3)?;
104
105	Some(VersionedGenericDatum {
106		datum: fields.get(0),
107		appendix: fields.get(1),
108		version: fields.get(2).as_u64()?,
109	})
110}
111
112fn decoding_error_and_log(data: &PlutusData, to: &str, msg: &str) -> DataDecodingError {
113	log::error!("Could not decode {data:?} to {to}: {msg}");
114	DataDecodingError { datum: data.clone(), to: to.to_string(), msg: msg.to_string() }
115}
116
117/// This struct has the same shape as `VersionedGenericDatum` from smart-contracts.
118/// It is used to help implementing a proper `From` trait for `PlutusData` for
119/// datum types.
120pub(crate) struct VersionedGenericDatum {
121	pub datum: PlutusData,
122	pub appendix: PlutusData,
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}