1use crate::{
2 json::json_serialize::{JsonParseError, Value as JSONValue},
3 plutus::{ConstrPlutusData, PlutusData, PlutusMap},
4 utils::BigInteger,
5};
6use std::collections::BTreeMap;
7use std::str::FromStr;
8
9#[cfg(not(feature = "used_from_wasm"))]
10use noop_proc_macro::wasm_bindgen;
11#[cfg(feature = "used_from_wasm")]
12use wasm_bindgen::prelude::wasm_bindgen;
13
14#[wasm_bindgen]
24#[derive(Copy, Clone, Debug, Eq, PartialEq)]
25pub enum CardanoNodePlutusDatumSchema {
26 BasicConversions,
39 DetailedSchema,
63}
64
65#[derive(Debug, thiserror::Error)]
66pub enum PlutusJsonError {
67 #[error("JSON Parsing: {0}")]
68 JsonParse(#[from] JsonParseError),
69 #[error("JSON printing: {0}")]
70 JsonPrinting(#[from] serde_json::Error),
71 #[error("null not allowed in plutus datums")]
72 NullFound,
73 #[error("bools not allowed in plutus datums")]
74 BoolFound,
75 #[error(
76 "DetailedSchema requires ALL JSON to be tagged objects, found: {:?}",
77 0
78 )]
79 DetailedNonObject(JSONValue),
80 #[error("Hex byte strings in detailed schema should NOT start with 0x and should just contain the hex characters")]
81 DetailedHexWith0x,
82 #[error("DetailedSchema key {0} does not match type {1:?}")]
83 DetailedKeyMismatch(String, JSONValue),
84 #[error("Invalid hex string: {0}")]
85 InvalidHex(#[from] hex::FromHexError),
86 #[error("entry format in detailed schema map object not correct. Needs to be of form {{\"k\": {{\"key_type\": key}}, \"v\": {{\"value_type\", value}}}}")]
87 InvalidMapEntry,
88 #[error("key '{0}' in tagged object not valid")]
89 InvalidTag(String),
90 #[error("Key requires DetailedSchema: {:?}", 0)]
91 DetailedKeyInBasicSchema(PlutusData),
92 #[error("detailed schemas must either have only one of the following keys: \"int\", \"bytes\", \"list\" or \"map\", or both of these 2 keys: \"constructor\" + \"fields\"")]
93 InvalidTaggedConstructor,
94}
95
96pub fn encode_json_str_to_plutus_datum(
97 json: &str,
98 schema: CardanoNodePlutusDatumSchema,
99) -> Result<PlutusData, PlutusJsonError> {
100 let value = JSONValue::from_string(json)?;
101 encode_json_value_to_plutus_datum(value, schema)
102}
103
104pub fn encode_json_value_to_plutus_datum(
105 value: JSONValue,
106 schema: CardanoNodePlutusDatumSchema,
107) -> Result<PlutusData, PlutusJsonError> {
108 fn encode_string(
109 s: &str,
110 schema: CardanoNodePlutusDatumSchema,
111 is_key: bool,
112 ) -> Result<PlutusData, PlutusJsonError> {
113 if schema == CardanoNodePlutusDatumSchema::BasicConversions {
114 if let Some(stripped) = s.strip_prefix("0x") {
115 hex::decode(stripped)
117 .map(PlutusData::new_bytes)
118 .map_err(Into::into)
119 } else if is_key {
120 match BigInteger::from_str(s) {
122 Ok(x) => Ok(PlutusData::new_integer(x)),
123 Err(_err) => Ok(PlutusData::new_bytes(s.as_bytes().to_vec())),
125 }
126 } else {
127 Ok(PlutusData::new_bytes(s.as_bytes().to_vec()))
129 }
130 } else if s.starts_with("0x") {
131 Err(PlutusJsonError::DetailedHexWith0x)
132 } else {
133 hex::decode(s)
134 .map(PlutusData::new_bytes)
135 .map_err(Into::into)
136 }
137 }
138 fn encode_array(
139 json_arr: Vec<JSONValue>,
140 schema: CardanoNodePlutusDatumSchema,
141 ) -> Result<PlutusData, PlutusJsonError> {
142 let mut arr = Vec::new();
143 for value in json_arr {
144 arr.push(encode_json_value_to_plutus_datum(value, schema)?);
145 }
146 Ok(PlutusData::new_list(arr))
147 }
148 match schema {
149 CardanoNodePlutusDatumSchema::BasicConversions => match value {
150 JSONValue::Null => Err(PlutusJsonError::NullFound),
151 JSONValue::Bool(_) => Err(PlutusJsonError::BoolFound),
152 JSONValue::Number(x) => Ok(PlutusData::new_integer(x)),
153 JSONValue::String(s) => encode_string(&s, schema, false),
155 JSONValue::Array(json_arr) => encode_array(json_arr, schema),
156 JSONValue::Object(json_obj) => {
157 let mut map = PlutusMap::new();
158 for (raw_key, raw_value) in json_obj {
159 let key = encode_string(&raw_key, schema, true)?;
160 let value = encode_json_value_to_plutus_datum(raw_value, schema)?;
161 map.set(key, value);
162 }
163 Ok(PlutusData::new_map(map))
164 }
165 },
166 CardanoNodePlutusDatumSchema::DetailedSchema => match value {
167 JSONValue::Object(obj) => {
168 if obj.len() == 1 {
169 let (k, v) = obj.into_iter().next().unwrap();
171 match k.as_str() {
172 "int" => match v {
173 JSONValue::Number(x) => Ok(PlutusData::new_integer(x)),
174 _ => Err(PlutusJsonError::DetailedKeyMismatch(k, v)),
175 },
176 "bytes" => match v {
177 JSONValue::String(s) => encode_string(&s, schema, false),
178 _ => Err(PlutusJsonError::DetailedKeyMismatch(k, v)),
179 },
180 "list" => match v {
181 JSONValue::Array(arr) => encode_array(arr, schema),
182 _ => Err(PlutusJsonError::DetailedKeyMismatch(k, v)),
183 },
184 "map" => {
185 let mut map = PlutusMap::new();
186 let array = match v {
187 JSONValue::Array(array) => Ok(array),
188 _ => Err(PlutusJsonError::DetailedKeyMismatch(k, v)),
189 }?;
190
191 for entry in array {
192 let entry_obj = match entry {
193 JSONValue::Object(obj) => Ok(obj),
194 _ => Err(PlutusJsonError::InvalidMapEntry),
195 }?;
196 let raw_key =
197 entry_obj.get("k").ok_or(PlutusJsonError::InvalidMapEntry)?;
198 let value =
199 entry_obj.get("v").ok_or(PlutusJsonError::InvalidMapEntry)?;
200 let key =
201 encode_json_value_to_plutus_datum(raw_key.clone(), schema)?;
202 map.set(
203 key,
204 encode_json_value_to_plutus_datum(value.clone(), schema)?,
205 );
206 }
207 Ok(PlutusData::new_map(map))
208 }
209 _invalid_key => Err(PlutusJsonError::InvalidTag(k)),
210 }
211 } else {
212 let variant = obj.get("constructor").and_then(|v| match v {
214 JSONValue::Number(number) => number.as_u64(),
215 _ => None,
216 });
217 let fields_json = obj.get("fields").and_then(|f| match f {
218 JSONValue::Array(arr) => Some(arr),
219 _ => None,
220 });
221 match (obj.len(), variant, fields_json) {
222 (2, Some(variant), Some(fields_json)) => {
223 let mut fields = Vec::new();
224 for field_json in fields_json {
225 let field =
226 encode_json_value_to_plutus_datum(field_json.clone(), schema)?;
227 fields.push(field);
228 }
229 Ok(PlutusData::new_constr_plutus_data(ConstrPlutusData::new(
230 variant, fields,
231 )))
232 }
233 _ => Err(PlutusJsonError::InvalidTaggedConstructor),
234 }
235 }
236 }
237 _ => Err(PlutusJsonError::DetailedNonObject(value)),
238 },
239 }
240}
241
242pub fn decode_plutus_datum_to_json_str(
243 datum: &PlutusData,
244 schema: CardanoNodePlutusDatumSchema,
245) -> Result<String, PlutusJsonError> {
246 decode_plutus_datum_to_json_value(datum, schema).and_then(|v| v.to_string().map_err(Into::into))
247}
248
249pub fn decode_plutus_datum_to_json_value(
250 datum: &PlutusData,
251 schema: CardanoNodePlutusDatumSchema,
252) -> Result<JSONValue, PlutusJsonError> {
253 let (type_tag, json_value) = match datum {
254 PlutusData::ConstrPlutusData(constr) => {
255 let mut obj = BTreeMap::new();
256 obj.insert(
257 String::from("constructor"),
258 JSONValue::from(constr.alternative),
259 );
260 let mut fields = Vec::new();
261 for field in constr.fields.iter() {
262 fields.push(decode_plutus_datum_to_json_value(field, schema)?);
263 }
264 obj.insert(String::from("fields"), JSONValue::from(fields));
265 (None, JSONValue::from(obj))
266 }
267 PlutusData::Map(map) => match schema {
268 CardanoNodePlutusDatumSchema::BasicConversions => (
269 None,
270 JSONValue::from(
271 map.entries
272 .iter()
273 .map(|(key, value)| {
274 let json_key: String = match key {
275 PlutusData::ConstrPlutusData(_)
276 | PlutusData::Map(_)
277 | PlutusData::List { .. } => {
278 Err(PlutusJsonError::DetailedKeyInBasicSchema(key.clone()))
279 }
280 PlutusData::Integer(x) => Ok(x.to_string()),
281 PlutusData::Bytes { bytes, .. } => String::from_utf8(bytes.clone())
282 .or_else(|_err| Ok(format!("0x{}", hex::encode(bytes)))),
283 }?;
284 let json_value = decode_plutus_datum_to_json_value(value, schema)?;
285 Ok((json_key, json_value))
286 })
287 .collect::<Result<BTreeMap<String, JSONValue>, PlutusJsonError>>()?,
288 ),
289 ),
290 CardanoNodePlutusDatumSchema::DetailedSchema => (
291 Some("map"),
292 JSONValue::from(
293 map.entries
294 .iter()
295 .map(|(key, value)| {
296 let k = decode_plutus_datum_to_json_value(key, schema)?;
297 let v = decode_plutus_datum_to_json_value(value, schema)?;
298 let mut kv_obj = BTreeMap::new();
299 kv_obj.insert(String::from("k"), k);
300 kv_obj.insert(String::from("v"), v);
301 Ok(JSONValue::from(kv_obj))
302 })
303 .collect::<Result<Vec<_>, PlutusJsonError>>()?,
304 ),
305 ),
306 },
307 PlutusData::List { list, .. } => {
308 let mut elems = Vec::new();
309 for elem in list.iter() {
310 elems.push(decode_plutus_datum_to_json_value(elem, schema)?);
311 }
312 (Some("list"), JSONValue::from(elems))
313 }
314 PlutusData::Integer(bigint) => (Some("int"), JSONValue::from(bigint.clone())),
315 PlutusData::Bytes { bytes, .. } => (
316 Some("bytes"),
317 JSONValue::from(match schema {
318 CardanoNodePlutusDatumSchema::BasicConversions => {
319 String::from_utf8(bytes.clone())
321 .ok()
322 .filter(|utf8| utf8.chars().all(|c| !c.is_control()))
323 .unwrap_or_else(|| format!("0x{}", hex::encode(bytes)))
325 }
326 CardanoNodePlutusDatumSchema::DetailedSchema => hex::encode(bytes),
327 }),
328 ),
329 };
330 match (type_tag, schema) {
331 (Some(type_tag), CardanoNodePlutusDatumSchema::DetailedSchema) => {
332 let mut wrapper = BTreeMap::new();
333 wrapper.insert(String::from(type_tag), json_value);
334 Ok(JSONValue::from(wrapper))
335 }
336 _ => Ok(json_value),
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use crate::plutus::PlutusData;
343
344 #[test]
345 fn plutus_datum_json() {
346 let json = "{\"map\":[{\"k\":{\"int\":100},\"v\":{\"list\":[{\"map\":[{\"k\":{\"bytes\":\"78\"},\"v\":{\"bytes\":\"30\"}},{\"k\":{\"bytes\":\"79\"},\"v\":{\"int\":1}}]}]}},{\"k\":{\"bytes\":\"666f6f\"},\"v\":{\"bytes\":\"0000baadf00d0000cafed00d0000deadbeef0000\"}}]}";
347 let datum: PlutusData = serde_json::from_str(json).unwrap();
349 assert_eq!(json, serde_json::to_string(&datum).unwrap());
350 }
351}