amaru_kernel/cardano/memoized/
transaction_output.rs1use pallas_primitives::conway::Multiasset;
16
17use crate::{
18 Address, Bytes, Hash, Legacy, MemoizedDatum, MemoizedScript, NonEmptyKeyValuePairs, PositiveCoin,
19 ShelleyDelegationPart, StakeCredential, Value, cbor, decode_script, encode_script, serialize_memoized_script,
20 size::CREDENTIAL,
21};
22
23#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
24pub struct MemoizedTransactionOutput {
25 #[serde(skip)]
26 pub is_legacy: bool,
27
28 #[serde(serialize_with = "serialize_address")]
29 #[serde(deserialize_with = "deserialize_address")]
30 pub address: Address,
31
32 #[serde(serialize_with = "serialize_value")]
33 #[serde(deserialize_with = "deserialize_value")]
34 pub value: Value,
35
36 pub datum: MemoizedDatum,
37
38 #[serde(serialize_with = "serialize_script")]
39 #[serde(deserialize_with = "deserialize_script")]
40 pub script: Option<MemoizedScript>,
41}
42
43impl MemoizedTransactionOutput {
44 pub fn delegate(&self) -> Option<StakeCredential> {
45 match &self.address {
46 Address::Shelley(shelley) => match shelley.delegation() {
47 ShelleyDelegationPart::Key(key) => Some(StakeCredential::AddrKeyhash(*key)),
48 ShelleyDelegationPart::Script(script) => Some(StakeCredential::ScriptHash(*script)),
49 ShelleyDelegationPart::Pointer(..) | ShelleyDelegationPart::Null => None,
50 },
51 Address::Byron(..) => None,
52 Address::Stake(..) => unreachable!("stake address inside output?"),
53 }
54 }
55}
56
57impl<'b, C> cbor::Decode<'b, C> for MemoizedTransactionOutput {
58 fn decode(d: &mut cbor::Decoder<'b>, ctx: &mut C) -> Result<Self, cbor::decode::Error> {
59 let data_type = d.datatype()?;
60
61 if matches!(data_type, cbor::Type::MapIndef | cbor::Type::Map) {
62 decode_modern_output(d, ctx)
63 } else if matches!(data_type, cbor::Type::ArrayIndef | cbor::Type::Array) {
64 decode_legacy_output(d, ctx)
65 } else {
66 Err(cbor::decode::Error::type_mismatch(data_type))
67 }
68 }
69}
70
71fn decode_legacy_output<C>(
72 d: &mut cbor::Decoder<'_>,
73 ctx: &mut C,
74) -> Result<MemoizedTransactionOutput, cbor::decode::Error> {
75 let len = d.array()?;
76
77 Ok(MemoizedTransactionOutput {
78 is_legacy: true,
79 address: decode_address(d.bytes()?)?,
80 value: d.decode_with(ctx)?,
81 datum: match len {
82 Some(2) => MemoizedDatum::None,
83 Some(3) => d.decode_with::<_, Legacy<_>>(ctx)?.0,
84 Some(_) => {
85 return Err(cbor::decode::Error::message(format!(
86 "expected legacy transaction output array length of 2 or 3, got {len:?}",
87 )));
88 }
89 None => {
90 if cbor::decode_break(d, len)? {
91 MemoizedDatum::None
92 } else {
93 let datum: Legacy<MemoizedDatum> = d.decode_with(ctx)?;
94 if !cbor::decode_break(d, len)? {
95 return Err(cbor::decode::Error::message(
96 "expected break after legacy transaction output datum",
97 ));
98 }
99 datum.0
100 }
101 }
102 },
103 script: None,
104 })
105}
106
107fn decode_modern_output<C>(
108 d: &mut cbor::Decoder<'_>,
109 ctx: &mut C,
110) -> Result<MemoizedTransactionOutput, cbor::decode::Error> {
111 let (address, value, datum, script) = cbor::heterogeneous_map(
112 d,
113 (None, None, MemoizedDatum::None, None),
114 |d| d.u8(),
115 |d, state, field| {
116 match field {
117 0 => state.0 = Some(decode_address(d.bytes()?)?),
118 1 => state.1 = Some(d.decode_with(ctx)?),
119 2 => state.2 = d.decode_with(ctx)?,
120 3 => state.3 = Some(decode_script(d, ctx)?),
121 _ => return cbor::unexpected_field::<MemoizedTransactionOutput, _>(field),
122 }
123 Ok(())
124 },
125 )?;
126
127 Ok(MemoizedTransactionOutput {
128 is_legacy: false,
129 address: address.ok_or_else(|| cbor::missing_field::<MemoizedTransactionOutput, Address>(0))?,
130 value: value.ok_or_else(|| cbor::missing_field::<MemoizedTransactionOutput, Value>(1))?,
131 datum,
132 script,
133 })
134}
135
136fn decode_address(address_bytes: &[u8]) -> Result<Address, cbor::decode::Error> {
137 Address::from_bytes(address_bytes).map_err(|e| cbor::decode::Error::message(format!("invalid address: {e:?}")))
138}
139
140impl<C> cbor::Encode<C> for MemoizedTransactionOutput {
141 fn encode<W: cbor::encode::Write>(
142 &self,
143 e: &mut cbor::Encoder<W>,
144 ctx: &mut C,
145 ) -> Result<(), cbor::encode::Error<W::Error>> {
146 if self.is_legacy {
147 e.begin_array()?;
148 e.bytes(&self.address.to_vec())?;
149 e.encode_with(&self.value, ctx)?;
150 match self.datum {
151 MemoizedDatum::None => (),
152 MemoizedDatum::Hash(hash) => {
153 e.bytes(&hash[..])?;
154 }
155 MemoizedDatum::Inline(..) => unreachable!("legacy output with inline datum ?!"),
156 }
157 e.end()?;
158 } else {
159 e.begin_map()?;
160
161 e.u8(0)?;
162 e.bytes(&self.address.to_vec())?;
163
164 e.u8(1)?;
165 e.encode_with(&self.value, ctx)?;
166
167 if !matches!(&self.datum, &MemoizedDatum::None) {
168 e.u8(2)?;
169 }
170 e.encode_with(&self.datum, ctx)?;
171
172 match &self.script {
173 None => (),
174 Some(script) => {
175 e.u8(3)?;
176 encode_script(script, e)?;
177 }
178 }
179
180 e.end()?;
181 }
182
183 Ok(())
184 }
185}
186
187fn serialize_address<S: serde::ser::Serializer>(addr: &Address, serializer: S) -> Result<S::Ok, S::Error> {
190 serializer.serialize_str(&addr.to_hex())
191}
192
193fn deserialize_address<'de, D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Address, D::Error> {
194 let bytes: &str = serde::Deserialize::deserialize(deserializer)?;
195 Address::from_hex(bytes).map_err(serde::de::Error::custom)
196}
197
198fn serialize_value<S: serde::ser::Serializer>(value: &Value, serializer: S) -> Result<S::Ok, S::Error> {
200 match value {
201 Value::Coin(coin) => serializer.serialize_u64(*coin),
202 Value::Multiasset(coin, _) => serializer.serialize_u64(*coin),
203 }
204}
205
206fn deserialize_value<'de, D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Value, D::Error> {
207 #[derive(serde::Deserialize)]
208 enum ValueHelper {
209 Coin(u64),
210 Multiasset(u64, Vec<(String, Vec<(String, u64)>)>),
211 }
212
213 let helper: ValueHelper = serde::Deserialize::deserialize(deserializer)?;
214
215 match helper {
216 ValueHelper::Coin(coin) => Ok(Value::Coin(coin)),
217 ValueHelper::Multiasset(coin, multiasset_data) => {
218 let mut converted_multiasset = Vec::new();
219
220 for (policy_id, assets) in multiasset_data {
221 let policy_id = hex::decode(&policy_id)
222 .map_err(|_| serde::de::Error::custom(format!("invalid hex string: {policy_id}")))?;
223
224 let mut converted_assets = Vec::new();
225 for (asset_name, quantity) in assets {
226 let asset_name = hex::decode(&asset_name)
227 .map_err(|_| serde::de::Error::custom(format!("invalid hex string: {asset_name}")))?;
228
229 converted_assets.push((
230 Bytes::from(asset_name),
231 quantity
232 .try_into()
233 .map_err(|_| serde::de::Error::custom(format!("invalid quantity value: {quantity}")))?,
234 ));
235 }
236
237 let policy_id: Hash<CREDENTIAL> = Hash::from(policy_id.as_slice());
238
239 let pairs = NonEmptyKeyValuePairs::try_from(converted_assets)
240 .map_err(|e| serde::de::Error::custom(format!("invalid asset bundle: {e}")))?
241 .as_pallas();
242
243 converted_multiasset.push((policy_id, pairs));
244 }
245
246 let multiasset: Multiasset<PositiveCoin> =
247 Multiasset::from_vec(converted_multiasset).ok_or(serde::de::Error::custom("empty multiasset"))?;
248 Ok(Value::Multiasset(coin, multiasset))
249 }
250 }
251}
252
253pub fn serialize_script<S: serde::ser::Serializer>(
254 opt: &Option<MemoizedScript>,
255 serializer: S,
256) -> Result<S::Ok, S::Error> {
257 match opt {
258 None => serializer.serialize_none(),
259 Some(script) => serialize_memoized_script(script, serializer),
260 }
261}
262
263pub fn deserialize_script<'de, D: serde::de::Deserializer<'de>>(
264 deserializer: D,
265) -> Result<Option<MemoizedScript>, D::Error> {
266 match serde::Deserialize::deserialize(deserializer)? {
267 None::<super::PlaceholderScript> => Ok(None),
268 Some(placeholder) => Ok(Some(MemoizedScript::try_from(placeholder).map_err(serde::de::Error::custom)?)),
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::{
276 Hash,
277 cbor::{self, Encode},
278 };
279
280 #[test]
281 fn test_encode_decode_output_with_datum_hash() {
282 let hash_bytes = [1u8; 32];
283 let datum_hash = Hash::<32>::from(hash_bytes);
284
285 let datum = MemoizedDatum::Hash(datum_hash);
286
287 let original = MemoizedTransactionOutput {
288 is_legacy: false,
289 address: Address::from_hex("61bbe56449ba4ee08c471d69978e01db384d31e29133af4546e6057335").unwrap(),
290 value: Value::Coin(1500000),
291 datum,
292 script: None,
293 };
294
295 let mut encoder = cbor::Encoder::new(Vec::new());
296 let mut ctx = ();
297 original.encode(&mut encoder, &mut ctx).unwrap();
298 let encoded_bytes = encoder.writer().clone();
299
300 let mut decoder = cbor::Decoder::new(&encoded_bytes);
301 let decoded: MemoizedTransactionOutput = decoder.decode_with(&mut ctx).unwrap();
302
303 assert_eq!(original, decoded);
304 }
305
306 #[test]
307 fn test_encode_decode_output_with_datum_hash_legacy() {
308 let hash_bytes = [1u8; 32];
309 let datum_hash = Hash::<32>::from(hash_bytes);
310
311 let datum = MemoizedDatum::Hash(datum_hash);
312
313 let original = MemoizedTransactionOutput {
314 is_legacy: true,
315 address: Address::from_hex("61bbe56449ba4ee08c471d69978e01db384d31e29133af4546e6057335").unwrap(),
316 value: Value::Coin(1500000),
317 datum,
318 script: None,
319 };
320
321 let mut encoder = cbor::Encoder::new(Vec::new());
322 let mut ctx = ();
323 original.encode(&mut encoder, &mut ctx).unwrap();
324 let encoded_bytes = encoder.writer().clone();
325
326 let mut decoder = cbor::Decoder::new(&encoded_bytes);
327 let decoded: MemoizedTransactionOutput = decoder.decode_with(&mut ctx).unwrap();
328
329 assert_eq!(original, decoded);
330 }
331
332 #[test]
333 fn test_encode_decode_output_no_datum_no_script() {
334 let original = MemoizedTransactionOutput {
335 is_legacy: false,
336 address: Address::from_hex("61bbe56449ba4ee08c471d69978e01db384d31e29133af4546e6057335").unwrap(),
337 value: Value::Coin(1500000),
338 datum: MemoizedDatum::None,
339 script: None,
340 };
341
342 let mut encoder = cbor::Encoder::new(Vec::new());
343 let mut ctx = ();
344 original.encode(&mut encoder, &mut ctx).unwrap();
345 let encoded_bytes = encoder.writer().clone();
346
347 let mut decoder = cbor::Decoder::new(&encoded_bytes);
348 let decoded: MemoizedTransactionOutput = decoder.decode_with(&mut ctx).unwrap();
349
350 assert_eq!(original, decoded);
351 }
352}