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"))]
108#[strum_discriminants(doc = "The discriminants of the TxDatumFieldValue enum.")]
109pub enum TxDatumFieldValue {
110 Bytes(String),
112 #[allow(dead_code)]
114 Int(u32),
115}
116
117#[derive(Debug, Serialize)]
119pub struct TxDatumBuilder {
120 constructor: usize,
121 fields: Vec<HashMap<TxDatumFieldTypeName, TxDatumFieldValue>>,
122}
123
124impl TxDatumBuilder {
125 pub fn new() -> Self {
127 Self {
128 constructor: 0,
129 fields: Vec::new(),
130 }
131 }
132
133 pub fn add_field(&mut self, field_value: TxDatumFieldValue) -> &mut TxDatumBuilder {
135 match &field_value {
136 TxDatumFieldValue::Bytes(datum_str) => {
137 let field_type = TxDatumFieldTypeName::from(&field_value);
140 let field_value_chunks = datum_str.as_bytes().chunks(128);
141 for field_value_chunk in field_value_chunks {
142 let mut field = HashMap::new();
143 field.insert(
144 field_type,
145 TxDatumFieldValue::Bytes(
146 std::str::from_utf8(field_value_chunk).unwrap().to_string(),
147 ),
148 );
149 self.fields.push(field);
150 }
151 }
152 _ => {
153 let mut field = HashMap::new();
154 field.insert(TxDatumFieldTypeName::from(&field_value), field_value);
155 self.fields.push(field);
156 }
157 }
158
159 self
160 }
161
162 pub fn build(&self) -> Result<TxDatum, TxDatumError> {
164 Ok(TxDatum(
165 serde_json::to_string(&self).map_err(TxDatumError::Build)?,
166 ))
167 }
168}
169
170impl Default for TxDatumBuilder {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use super::*;
179
180 fn dummy_tx_datum() -> TxDatum {
181 let mut tx_datum_builder = TxDatumBuilder::new();
182
183 tx_datum_builder
184 .add_field(TxDatumFieldValue::Bytes("bytes0".to_string()))
185 .add_field(TxDatumFieldValue::Int(0))
186 .add_field(TxDatumFieldValue::Int(1))
187 .add_field(TxDatumFieldValue::Bytes("bytes1".to_string()))
188 .add_field(TxDatumFieldValue::Bytes("bytes2".to_string()))
189 .add_field(TxDatumFieldValue::Int(2))
190 .add_field(TxDatumFieldValue::Bytes("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".to_string()))
191 .build()
192 .expect("tx_datum build should not fail")
193 }
194
195 #[test]
196 fn test_build_tx_datum() {
197 let tx_datum = dummy_tx_datum();
198 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());
199 assert_eq!(tx_datum_expected, tx_datum);
200 }
201
202 #[test]
203 fn test_can_retrieve_field_raw_value_bytes() {
204 let tx_datum = dummy_tx_datum();
205 assert_eq!(
206 "bytes0",
207 tx_datum
208 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 0)
209 .unwrap()
210 .as_str()
211 .unwrap()
212 );
213 assert_eq!(
214 "bytes1",
215 tx_datum
216 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 1)
217 .unwrap()
218 .as_str()
219 .unwrap()
220 );
221 assert_eq!(
222 "bytes2",
223 tx_datum
224 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 2)
225 .unwrap()
226 .as_str()
227 .unwrap()
228 );
229 tx_datum
230 .get_nth_field_by_type(&TxDatumFieldTypeName::Bytes, 100)
231 .expect_err("should have returned an error");
232 }
233
234 #[test]
235 fn test_can_retrieve_field_raw_value_int() {
236 let tx_datum = dummy_tx_datum();
237 assert_eq!(
238 0,
239 tx_datum
240 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 0)
241 .unwrap()
242 .as_u64()
243 .unwrap()
244 );
245 assert_eq!(
246 1,
247 tx_datum
248 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 1)
249 .unwrap()
250 .as_u64()
251 .unwrap()
252 );
253 assert_eq!(
254 2,
255 tx_datum
256 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 2)
257 .unwrap()
258 .as_u64()
259 .unwrap()
260 );
261 tx_datum
262 .get_nth_field_by_type(&TxDatumFieldTypeName::Int, 100)
263 .expect_err("should have returned an error");
264 }
265}