partner_chains_plutus_data/
permissioned_candidates.rs

1//! Plutus data types for permissioned candidates.
2use cardano_serialization_lib::{BigNum, PlutusData, PlutusList};
3use sidechain_domain::*;
4
5use crate::{
6	DataDecodingError, DecodingResult, VersionedDatum, VersionedDatumWithLegacy,
7	VersionedGenericDatum,
8};
9
10#[derive(Clone, Debug, PartialEq)]
11/// Datum representing a list of permissioned candidates.
12pub enum PermissionedCandidateDatums {
13	/// Initial/legacy datum schema. If a datum doesn't contain a version, it is assumed to be V0
14	V0(Vec<PermissionedCandidateDatumV0>),
15}
16
17#[derive(Clone, Debug, PartialEq)]
18/// Datum representing a permissioned candidate.
19pub struct PermissionedCandidateDatumV0 {
20	/// Sidechain public key of the trustless candidate
21	pub sidechain_public_key: SidechainPublicKey,
22	/// Aura public key of the trustless candidate
23	pub aura_public_key: AuraPublicKey,
24	/// GRANDPA public key of the trustless candidate
25	pub grandpa_public_key: GrandpaPublicKey,
26}
27
28impl TryFrom<PlutusData> for PermissionedCandidateDatums {
29	type Error = DataDecodingError;
30	fn try_from(datum: PlutusData) -> DecodingResult<Self> {
31		Self::decode(&datum)
32	}
33}
34
35impl From<PermissionedCandidateDatumV0> for PermissionedCandidateData {
36	fn from(value: PermissionedCandidateDatumV0) -> Self {
37		Self {
38			sidechain_public_key: value.sidechain_public_key,
39			aura_public_key: value.aura_public_key,
40			grandpa_public_key: value.grandpa_public_key,
41		}
42	}
43}
44
45impl From<PermissionedCandidateDatums> for Vec<PermissionedCandidateData> {
46	fn from(value: PermissionedCandidateDatums) -> Self {
47		match value {
48			PermissionedCandidateDatums::V0(v) => v.into_iter().map(|d| d.into()).collect(),
49		}
50	}
51}
52
53/// Converts a list of [PermissionedCandidateData] values to [VersionedGenericDatum] encoded as [PlutusData].
54///
55/// Encoding:
56///   VersionedGenericDatum:
57///   - datum: ()
58///   - appendix:
59///     [
60///       [ candidates[0].sidechain_public_key
61///       , candidates[0].aura_public_key
62///       , candidates[0].grandpa_public_key
63///       ]
64///     ,
65///       [ candidates[1].sidechain_public_key
66///       , candidates[1].aura_public_key
67///       , candidates[1].grandpa_public_key
68///       ]
69///       // etc.
70///     ]
71///   - version: 0
72pub fn permissioned_candidates_to_plutus_data(
73	candidates: &[PermissionedCandidateData],
74) -> PlutusData {
75	let mut list = PlutusList::new();
76	for candidate in candidates {
77		let mut candidate_datum = PlutusList::new();
78		candidate_datum.add(&PlutusData::new_bytes(candidate.sidechain_public_key.0.clone()));
79		candidate_datum.add(&PlutusData::new_bytes(candidate.aura_public_key.0.clone()));
80		candidate_datum.add(&PlutusData::new_bytes(candidate.grandpa_public_key.0.clone()));
81		list.add(&PlutusData::new_list(&candidate_datum));
82	}
83	let appendix = PlutusData::new_list(&list);
84	VersionedGenericDatum {
85		datum: PlutusData::new_empty_constr_plutus_data(&BigNum::zero()),
86		appendix,
87		version: 0,
88	}
89	.into()
90}
91
92impl VersionedDatumWithLegacy for PermissionedCandidateDatums {
93	const NAME: &str = "PermissionedCandidateDatums";
94
95	/// Parses plutus data schema that was used before datum versioning was added. Kept for backwards compatibility.
96	fn decode_legacy(data: &PlutusData) -> Result<Self, String> {
97		let permissioned_candidates = data
98			.as_list()
99			.and_then(|list_datums| {
100				list_datums
101					.into_iter()
102					.map(decode_legacy_candidate_datum)
103					.collect::<Option<Vec<PermissionedCandidateDatumV0>>>()
104			})
105			.ok_or("Expected [[ByteString, ByteString, ByteString]]")?;
106
107		Ok(Self::V0(permissioned_candidates))
108	}
109
110	fn decode_versioned(
111		version: u64,
112		_datum: &PlutusData,
113		appendix: &PlutusData,
114	) -> Result<Self, String> {
115		match version {
116			0 => PermissionedCandidateDatums::decode_legacy(appendix)
117				.map_err(|msg| format!("Cannot parse appendix: {msg}")),
118			_ => Err(format!("Unknown version: {version}")),
119		}
120	}
121}
122
123fn decode_legacy_candidate_datum(datum: &PlutusData) -> Option<PermissionedCandidateDatumV0> {
124	let datums = datum.as_list().filter(|datums| datums.len() == 3)?;
125
126	let sc = datums.get(0).as_bytes()?;
127	let aura = datums.get(1).as_bytes()?;
128	let grandpa = datums.get(2).as_bytes()?;
129
130	Some(PermissionedCandidateDatumV0 {
131		sidechain_public_key: SidechainPublicKey(sc),
132		aura_public_key: AuraPublicKey(aura),
133		grandpa_public_key: GrandpaPublicKey(grandpa),
134	})
135}
136
137#[cfg(test)]
138mod tests {
139	use super::*;
140	use crate::test_helpers::*;
141	use hex_literal::hex;
142	use pretty_assertions::assert_eq;
143
144	#[test]
145	fn valid_legacy_permissioned_candidates() {
146		let plutus_data = test_plutus_data!({"list": [
147			{"list": [
148				{"bytes": "cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"},
149				{"bytes": "bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec"},
150				{"bytes": "9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d"}
151			]},
152			{"list": [
153				{"bytes": "79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf"},
154				{"bytes": "56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19"},
155				{"bytes": "7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32"}
156			]}
157		]});
158
159		let expected_datum = PermissionedCandidateDatums::V0(vec![
160			PermissionedCandidateDatumV0 {
161				sidechain_public_key: SidechainPublicKey(
162					hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854").into(),
163				),
164				aura_public_key: AuraPublicKey(
165					hex!("bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec").into(),
166				),
167				grandpa_public_key: GrandpaPublicKey(
168					hex!("9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d").into(),
169				),
170			},
171			PermissionedCandidateDatumV0 {
172				sidechain_public_key: SidechainPublicKey(
173					hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf").into(),
174				),
175				aura_public_key: AuraPublicKey(
176					hex!("56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19").into(),
177				),
178				grandpa_public_key: GrandpaPublicKey(
179					hex!("7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32").into(),
180				),
181			},
182		]);
183
184		assert_eq!(PermissionedCandidateDatums::try_from(plutus_data).unwrap(), expected_datum)
185	}
186
187	fn v0_datum_json() -> serde_json::Value {
188		serde_json::json!({
189			"list": [
190				{ "constructor": 0, "fields": [] },
191				{ "list": [
192					{"list": [
193						{"bytes": "cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"},
194						{"bytes": "bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec"},
195						{"bytes": "9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d"}
196					]},
197					{"list": [
198						{"bytes": "79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf"},
199						{"bytes": "56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19"},
200						{"bytes": "7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32"}
201					]}
202				]},
203				{ "int": 0 }
204			]
205		})
206	}
207
208	#[test]
209	fn test_permissioned_candidates_to_plutus_data() {
210		let expected_plutus_data = json_to_plutus_data(v0_datum_json());
211
212		let domain_data = vec![
213			PermissionedCandidateData {
214				sidechain_public_key: SidechainPublicKey(
215					hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854")
216						.to_vec(),
217				),
218				aura_public_key: AuraPublicKey(
219					hex!("bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec")
220						.to_vec(),
221				),
222				grandpa_public_key: GrandpaPublicKey(
223					hex!("9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d")
224						.to_vec(),
225				),
226			},
227			PermissionedCandidateData {
228				sidechain_public_key: SidechainPublicKey(
229					hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf")
230						.to_vec(),
231				),
232				aura_public_key: AuraPublicKey(
233					hex!("56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19")
234						.to_vec(),
235				),
236				grandpa_public_key: GrandpaPublicKey(
237					hex!("7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32")
238						.to_vec(),
239				),
240			},
241		];
242		assert_eq!(permissioned_candidates_to_plutus_data(&domain_data), expected_plutus_data)
243	}
244
245	#[test]
246	fn valid_v0_permissioned_candidates() {
247		let plutus_data = json_to_plutus_data(v0_datum_json());
248
249		let expected_datum = PermissionedCandidateDatums::V0(vec![
250			PermissionedCandidateDatumV0 {
251				sidechain_public_key: SidechainPublicKey(
252					hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854").into(),
253				),
254				aura_public_key: AuraPublicKey(
255					hex!("bf20afa1c1a72af3341fa7a447e3f9eada9f3d054a7408fb9e49ad4d6e6559ec").into(),
256				),
257				grandpa_public_key: GrandpaPublicKey(
258					hex!("9042a40b0b1baa9adcead024432a923eac706be5e1a89d7f2f2d58bfa8f3c26d").into(),
259				),
260			},
261			PermissionedCandidateDatumV0 {
262				sidechain_public_key: SidechainPublicKey(
263					hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf").into(),
264				),
265				aura_public_key: AuraPublicKey(
266					hex!("56d1da82e56e4cb35b13de25f69a3e9db917f3e13d6f786321f4b0a9dc153b19").into(),
267				),
268				grandpa_public_key: GrandpaPublicKey(
269					hex!("7392f3ea668aa2be7997d82c07bcfbec3ee4a9a4e01e3216d92b8f0d0a086c32").into(),
270				),
271			},
272		]);
273
274		assert_eq!(PermissionedCandidateDatums::try_from(plutus_data).unwrap(), expected_datum)
275	}
276}