mithril_cardano_node_chain/entities/
datum.rs1use anyhow::Context;
2use pallas_codec::minicbor::{Decode, Decoder, decode};
3use pallas_primitives::{ToCanonicalJson, alonzo::PlutusData};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7use strum::{Display, EnumDiscriminants};
8use thiserror::Error;
9
10use mithril_common::{StdError, StdResult};
11
12#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
14#[serde(rename_all = "lowercase")]
15pub struct Datum(pub PlutusData);
16
17impl ToCanonicalJson for Datum {
18 fn to_json(&self) -> serde_json::Value {
19 self.0.to_json()
20 }
21}
22
23impl<'a, C> Decode<'a, C> for Datum {
24 fn decode(d: &mut Decoder<'a>, ctx: &mut C) -> Result<Self, decode::Error> {
25 PlutusData::decode(d, ctx).map(Datum)
26 }
27}
28
29pub fn try_inspect<R>(inner: Vec<u8>) -> StdResult<R>
31where
32 for<'b> R: Decode<'b, ()>,
33{
34 decode(&inner).with_context(|| format!("failed to decode datum: {}", hex::encode(&inner)))
35}
36
37pub type Datums = Vec<TxDatum>;
39
40#[derive(Debug, Error)]
42pub enum TxDatumError {
43 #[error("could not parse tx datum")]
45 InvalidContent(#[source] StdError),
46
47 #[error("could not build tx datum")]
49 Build(#[source] serde_json::Error),
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct TxDatum(pub String);
55
56impl TxDatum {
57 pub fn get_fields_by_type(&self, type_name: &TxDatumFieldTypeName) -> StdResult<Vec<Value>> {
59 let tx_datum_raw = &self.0;
60 let v: HashMap<String, Value> = serde_json::from_str(tx_datum_raw)
62 .with_context(|| format!("tx datum was = '{tx_datum_raw}'"))
63 .map_err(TxDatumError::InvalidContent)?;
64
65 let fields = v.get("fields")
67 .with_context(|| format!("Error: missing 'fields' entry, tx datum was = '{tx_datum_raw}'"))
68 .map_err(TxDatumError::InvalidContent)?
69 .as_array()
70 .with_context(|| format!("Error: 'fields' entry is not correctly structured, tx datum was = '{tx_datum_raw}'"))
71 .map_err(TxDatumError::InvalidContent)?;
72
73 Ok(fields
75 .iter()
76 .filter(|&field| field.get(type_name.to_string()).is_some())
77 .map(|field| field.get(type_name.to_string()).unwrap().to_owned())
78 .collect())
79 }
80
81 pub fn get_nth_field_by_type(
83 &self,
84 type_name: &TxDatumFieldTypeName,
85 index: usize,
86 ) -> StdResult<Value> {
87 Ok(self
88 .get_fields_by_type(type_name)?
89 .get(index)
90 .with_context(|| format!("Error: missing field at index {index}"))
91 .map_err(TxDatumError::InvalidContent)?
92 .to_owned())
93 }
94}
95
96#[derive(Debug, EnumDiscriminants, Serialize, Display)]
98#[serde(untagged, rename_all = "lowercase")]
99#[strum(serialize_all = "lowercase")]
100#[strum_discriminants(derive(Serialize, Hash, Display))]
101#[strum_discriminants(name(TxDatumFieldTypeName))]
102#[strum_discriminants(strum(serialize_all = "lowercase"))]
103#[strum_discriminants(serde(rename_all = "lowercase"))]
104#[strum_discriminants(doc = "The discriminants of the TxDatumFieldValue enum.")]
105pub enum TxDatumFieldValue {
106 Bytes(String),
108 #[allow(dead_code)]
110 Int(u32),
111}
112
113#[derive(Debug, Serialize)]
115pub struct TxDatumBuilder {
116 constructor: usize,
117 fields: Vec<HashMap<TxDatumFieldTypeName, TxDatumFieldValue>>,
118}
119
120impl TxDatumBuilder {
121 pub fn new() -> Self {
123 Self {
124 constructor: 0,
125 fields: Vec::new(),
126 }
127 }
128
129 pub fn add_field(&mut self, field_value: TxDatumFieldValue) -> &mut TxDatumBuilder {
131 match &field_value {
132 TxDatumFieldValue::Bytes(datum_str) => {
133 let field_type = TxDatumFieldTypeName::from(&field_value);
136 let field_value_chunks = datum_str.as_bytes().chunks(128);
137 for field_value_chunk in field_value_chunks {
138 let mut field = HashMap::new();
139 field.insert(
140 field_type,
141 TxDatumFieldValue::Bytes(
142 std::str::from_utf8(field_value_chunk).unwrap().to_string(),
143 ),
144 );
145 self.fields.push(field);
146 }
147 }
148 _ => {
149 let mut field = HashMap::new();
150 field.insert(TxDatumFieldTypeName::from(&field_value), field_value);
151 self.fields.push(field);
152 }
153 }
154
155 self
156 }
157
158 pub fn build(&self) -> Result<TxDatum, TxDatumError> {
160 Ok(TxDatum(
161 serde_json::to_string(&self).map_err(TxDatumError::Build)?,
162 ))
163 }
164}
165
166impl Default for TxDatumBuilder {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172#[cfg(test)]
173mod test {
174 use super::*;
175
176 fn dummy_tx_datum() -> TxDatum {
177 let mut tx_datum_builder = TxDatumBuilder::new();
178
179 tx_datum_builder
180 .add_field(TxDatumFieldValue::Bytes("bytes0".to_string()))
181 .add_field(TxDatumFieldValue::Int(0))
182 .add_field(TxDatumFieldValue::Int(1))
183 .add_field(TxDatumFieldValue::Bytes("bytes1".to_string()))
184 .add_field(TxDatumFieldValue::Bytes("bytes2".to_string()))
185 .add_field(TxDatumFieldValue::Int(2))
186 .add_field(TxDatumFieldValue::Bytes("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".to_string()))
187 .build()
188 .expect("tx_datum build should not fail")
189 }
190
191 #[test]
192 fn test_build_tx_datum() {
193 let tx_datum = dummy_tx_datum();
194 let tx_datum_expected = TxDatum(r#"{"constructor":0,"fields":[{"bytes":"bytes0"},{"int":0},{"int":1},{"bytes":"bytes1"},{"bytes":"bytes2"},{"int":2},{"bytes":"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"},{"bytes":"8901234567890123456789"}]}"#.to_string());
195 assert_eq!(tx_datum_expected, tx_datum);
196 }
197
198 #[test]
199 fn test_can_retrieve_field_raw_value_bytes() {
200 let tx_datum = dummy_tx_datum();
201 assert_eq!(
202 "bytes0",
203 tx_datum
204 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 0)
205 .unwrap()
206 .as_str()
207 .unwrap()
208 );
209 assert_eq!(
210 "bytes1",
211 tx_datum
212 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 1)
213 .unwrap()
214 .as_str()
215 .unwrap()
216 );
217 assert_eq!(
218 "bytes2",
219 tx_datum
220 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 2)
221 .unwrap()
222 .as_str()
223 .unwrap()
224 );
225 tx_datum
226 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 100)
227 .expect_err("should have returned an error");
228 }
229
230 #[test]
231 fn test_can_retrieve_field_raw_value_int() {
232 let tx_datum = dummy_tx_datum();
233 assert_eq!(
234 0,
235 tx_datum
236 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 0)
237 .unwrap()
238 .as_u64()
239 .unwrap()
240 );
241 assert_eq!(
242 1,
243 tx_datum
244 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 1)
245 .unwrap()
246 .as_u64()
247 .unwrap()
248 );
249 assert_eq!(
250 2,
251 tx_datum
252 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 2)
253 .unwrap()
254 .as_u64()
255 .unwrap()
256 );
257 tx_datum
258 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 100)
259 .expect_err("should have returned an error");
260 }
261}