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