mithril_cardano_node_chain/entities/
datum.rs1use anyhow::{Context, anyhow};
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)
35 .map_err(|e| anyhow!(e))
36 .with_context(|| format!("failed to decode datum: {}", hex::encode(&inner)))
37}
38
39pub type Datums = Vec<TxDatum>;
41
42#[derive(Debug, Error)]
44pub enum TxDatumError {
45 #[error("could not parse tx datum")]
47 InvalidContent(#[source] StdError),
48
49 #[error("could not build tx datum")]
51 Build(#[source] serde_json::Error),
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct TxDatum(pub String);
57
58impl TxDatum {
59 pub fn get_fields_by_type(&self, type_name: &TxDatumFieldTypeName) -> StdResult<Vec<Value>> {
61 let tx_datum_raw = &self.0;
62 let v: HashMap<String, Value> = serde_json::from_str(tx_datum_raw).map_err(|e| {
64 TxDatumError::InvalidContent(anyhow!(e).context("tx datum was = '{tx_datum_raw}'"))
65 })?;
66 let fields = v.get("fields").ok_or_else(|| {
68 TxDatumError::InvalidContent(
69 anyhow!("Error: missing 'fields' entry, tx datum was = '{tx_datum_raw}'"),
70 )
71 })?.as_array().ok_or_else(|| {
72 TxDatumError::InvalidContent(
73 anyhow!("Error: 'fields' entry is not correctly structured, tx datum was = '{tx_datum_raw}'"),
74 )
75 })?;
76 Ok(fields
78 .iter()
79 .filter(|&field| field.get(type_name.to_string()).is_some())
80 .map(|field| field.get(type_name.to_string()).unwrap().to_owned())
81 .collect::<_>())
82 }
83
84 pub fn get_nth_field_by_type(
86 &self,
87 type_name: &TxDatumFieldTypeName,
88 index: usize,
89 ) -> StdResult<Value> {
90 Ok(self
91 .get_fields_by_type(type_name)?
92 .get(index)
93 .ok_or_else(|| {
94 TxDatumError::InvalidContent(anyhow!("Error: missing field at index {index}"))
95 })?
96 .to_owned())
97 }
98}
99
100#[derive(Debug, EnumDiscriminants, Serialize, Display)]
102#[serde(untagged, rename_all = "lowercase")]
103#[strum(serialize_all = "lowercase")]
104#[strum_discriminants(derive(Serialize, Hash, Display))]
105#[strum_discriminants(name(TxDatumFieldTypeName))]
106#[strum_discriminants(strum(serialize_all = "lowercase"))]
107#[strum_discriminants(serde(rename_all = "lowercase"))]
108pub enum TxDatumFieldValue {
109 Bytes(String),
111 #[allow(dead_code)]
113 Int(u32),
114}
115
116#[derive(Debug, Serialize)]
118pub struct TxDatumBuilder {
119 constructor: usize,
120 fields: Vec<HashMap<TxDatumFieldTypeName, TxDatumFieldValue>>,
121}
122
123impl TxDatumBuilder {
124 pub fn new() -> Self {
126 Self {
127 constructor: 0,
128 fields: Vec::new(),
129 }
130 }
131
132 pub fn add_field(&mut self, field_value: TxDatumFieldValue) -> &mut TxDatumBuilder {
134 match &field_value {
135 TxDatumFieldValue::Bytes(datum_str) => {
136 let field_type = TxDatumFieldTypeName::from(&field_value);
139 let field_value_chunks = datum_str.as_bytes().chunks(128);
140 for field_value_chunk in field_value_chunks {
141 let mut field = HashMap::new();
142 field.insert(
143 field_type,
144 TxDatumFieldValue::Bytes(
145 std::str::from_utf8(field_value_chunk).unwrap().to_string(),
146 ),
147 );
148 self.fields.push(field);
149 }
150 }
151 _ => {
152 let mut field = HashMap::new();
153 field.insert(TxDatumFieldTypeName::from(&field_value), field_value);
154 self.fields.push(field);
155 }
156 }
157
158 self
159 }
160
161 pub fn build(&self) -> Result<TxDatum, TxDatumError> {
163 Ok(TxDatum(
164 serde_json::to_string(&self).map_err(TxDatumError::Build)?,
165 ))
166 }
167}
168
169impl Default for TxDatumBuilder {
170 fn default() -> Self {
171 Self::new()
172 }
173}
174
175#[cfg(test)]
176mod test {
177 use super::*;
178
179 fn dummy_tx_datum() -> TxDatum {
180 let mut tx_datum_builder = TxDatumBuilder::new();
181
182 tx_datum_builder
183 .add_field(TxDatumFieldValue::Bytes("bytes0".to_string()))
184 .add_field(TxDatumFieldValue::Int(0))
185 .add_field(TxDatumFieldValue::Int(1))
186 .add_field(TxDatumFieldValue::Bytes("bytes1".to_string()))
187 .add_field(TxDatumFieldValue::Bytes("bytes2".to_string()))
188 .add_field(TxDatumFieldValue::Int(2))
189 .add_field(TxDatumFieldValue::Bytes("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".to_string()))
190 .build()
191 .expect("tx_datum build should not fail")
192 }
193
194 #[test]
195 fn test_build_tx_datum() {
196 let tx_datum = dummy_tx_datum();
197 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());
198 assert_eq!(tx_datum_expected, tx_datum);
199 }
200
201 #[test]
202 fn test_can_retrieve_field_raw_value_bytes() {
203 let tx_datum = dummy_tx_datum();
204 assert_eq!(
205 "bytes0",
206 tx_datum
207 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 0)
208 .unwrap()
209 .as_str()
210 .unwrap()
211 );
212 assert_eq!(
213 "bytes1",
214 tx_datum
215 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 1)
216 .unwrap()
217 .as_str()
218 .unwrap()
219 );
220 assert_eq!(
221 "bytes2",
222 tx_datum
223 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 2)
224 .unwrap()
225 .as_str()
226 .unwrap()
227 );
228 tx_datum
229 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 100)
230 .expect_err("should have returned an error");
231 }
232
233 #[test]
234 fn test_can_retrieve_field_raw_value_int() {
235 let tx_datum = dummy_tx_datum();
236 assert_eq!(
237 0,
238 tx_datum
239 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 0)
240 .unwrap()
241 .as_u64()
242 .unwrap()
243 );
244 assert_eq!(
245 1,
246 tx_datum
247 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 1)
248 .unwrap()
249 .as_u64()
250 .unwrap()
251 );
252 assert_eq!(
253 2,
254 tx_datum
255 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 2)
256 .unwrap()
257 .as_u64()
258 .unwrap()
259 );
260 tx_datum
261 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 100)
262 .expect_err("should have returned an error");
263 }
264}