1use base64::Engine as _;
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Number, Value};
4use thiserror::Error;
5
6use tx3_tir::model::assets::{AssetClass, CanonicalAssets};
7use tx3_tir::model::core::{Type, Utxo, UtxoRef};
8pub use tx3_tir::reduce::ArgValue;
9
10#[derive(Debug, Error)]
11pub enum Error {
12 #[error("invalid base64: {0}")]
13 InvalidBase64(#[from] base64::DecodeError),
14
15 #[error("invalid hex: {0}")]
16 InvalidHex(#[from] hex::FromHexError),
17
18 #[error("invalid bech32: {0}")]
19 InvalidBech32(#[from] bech32::DecodeError),
20
21 #[error("value is not a valid number: {0}")]
22 InvalidBytesForNumber(String),
23
24 #[error("value is null")]
25 ValueIsNull,
26
27 #[error("can't infer type for value: {0}")]
28 CantInferTypeForValue(Value),
29
30 #[error("value is not a number: {0}")]
31 ValueIsNotANumber(Value),
32
33 #[error("value can't fit: {0}")]
34 NumberCantFit(Number),
35
36 #[error("value is not a bool: {0}")]
37 ValueIsNotABool(Value),
38
39 #[error("value is not a string")]
40 ValueIsNotAString,
41
42 #[error("value is not bytes: {0}")]
43 ValueIsNotBytes(Value),
44
45 #[error("value is not a utxo ref: {0}")]
46 ValueIsNotUtxoRef(Value),
47
48 #[error("invalid bytes envelope: {0}")]
49 InvalidBytesEnvelope(serde_json::Error),
50
51 #[error("value is not an address: {0}")]
52 ValueIsNotAnAddress(Value),
53
54 #[error("invalid utxo ref: {0}")]
55 InvalidUtxoRef(String),
56
57 #[error("target type not supported: {0:?}")]
58 TargetTypeNotSupported(Type),
59
60 #[error("unknown tag: {0}")]
61 UnknownTag(String),
62
63 #[error("malformed tagged value (expected a single-key object): {0}")]
64 MalformedTaggedArg(Value),
65
66 #[error("malformed tagged map pair (expected a [key, value] array): {0}")]
67 MalformedMapPair(Value),
68
69 #[error("malformed tagged struct (expected {{ constructor, fields }}): {0}")]
70 MalformedStruct(Value),
71}
72
73#[derive(Debug, Deserialize, Serialize, Clone)]
74#[serde(rename_all = "lowercase")]
75pub enum BytesEncoding {
76 Base64,
77 Hex,
78}
79
80#[derive(Debug, Deserialize, Serialize, Clone)]
81pub struct BytesEnvelope {
82 #[serde(alias = "bytecode", alias = "payload")]
84 pub content: String,
85 #[serde(rename = "contentType", alias = "encoding")]
86 pub content_type: BytesEncoding,
87}
88
89#[derive(Debug, Deserialize, Serialize, Clone)]
92pub struct TirEnvelope {
93 #[serde(alias = "bytecode", alias = "payload")]
95 pub content: String,
96 pub encoding: BytesEncoding,
97 pub version: String,
98}
99
100impl BytesEnvelope {
101 pub fn from_hex(hex: &str) -> Result<Self, Error> {
102 Ok(Self {
103 content: hex.to_string(),
104 content_type: BytesEncoding::Hex,
105 })
106 }
107}
108
109impl From<BytesEnvelope> for Vec<u8> {
110 fn from(envelope: BytesEnvelope) -> Self {
111 match envelope.content_type {
112 BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(),
113 BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(),
114 }
115 }
116}
117
118impl From<TirEnvelope> for Vec<u8> {
119 fn from(envelope: TirEnvelope) -> Self {
120 match envelope.encoding {
121 BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(),
122 BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(),
123 }
124 }
125}
126
127impl TryFrom<TirEnvelope> for tx3_tir::encoding::AnyTir {
128 type Error = crate::Error;
129
130 fn try_from(envelope: TirEnvelope) -> Result<Self, Self::Error> {
131 let version = tx3_tir::encoding::TirVersion::try_from(envelope.version.as_str())?;
132 let bytes: Vec<u8> = envelope.into();
133 let tir = tx3_tir::encoding::from_bytes(&bytes, version)?;
134 Ok(tir)
135 }
136}
137
138impl From<tx3_tir::encoding::AnyTir> for TirEnvelope {
139 fn from(tir: tx3_tir::encoding::AnyTir) -> Self {
140 let (bytes, version) = match tir {
141 tx3_tir::encoding::AnyTir::V1Beta0(tx) => tx3_tir::encoding::to_bytes(&tx),
142 };
143 Self {
144 content: hex::encode(bytes),
145 encoding: BytesEncoding::Hex,
146 version: version.to_string(),
147 }
148 }
149}
150
151fn has_hex_prefix(s: &str) -> bool {
152 s.starts_with("0x")
153}
154
155pub fn string_to_bigint(s: String) -> Result<i128, Error> {
156 if has_hex_prefix(&s) {
157 let bytes = hex_to_bytes(&s)?;
158 let bytes = <[u8; 16]>::try_from(bytes)
159 .map_err(|x| Error::InvalidBytesForNumber(hex::encode(x)))?;
160 Ok(i128::from_be_bytes(bytes))
161 } else {
162 let i = s
163 .parse::<i128>()
164 .map_err(|x| Error::InvalidBytesForNumber(x.to_string()))?;
165 Ok(i)
166 }
167}
168
169pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
170 let s = if has_hex_prefix(s) {
171 s.trim_start_matches("0x")
172 } else {
173 s
174 };
175
176 let out = hex::decode(s)?;
177
178 Ok(out)
179}
180
181pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
182 let out = base64::engine::general_purpose::STANDARD.decode(s)?;
183
184 Ok(out)
185}
186
187pub fn bech32_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
188 let (_, data) = bech32::decode(s)?;
189
190 Ok(data)
191}
192
193fn number_to_bigint(x: Number) -> Result<i128, Error> {
194 x.as_i128().ok_or(Error::NumberCantFit(x))
195}
196
197fn value_to_bigint(value: Value) -> Result<i128, Error> {
198 let out = match value {
199 Value::Number(n) => number_to_bigint(n)?,
200 Value::String(s) => string_to_bigint(s)?,
201 Value::Null => return Err(Error::ValueIsNull),
202 x => return Err(Error::ValueIsNotANumber(x)),
203 };
204
205 Ok(out)
206}
207
208fn value_to_bool(value: Value) -> Result<bool, Error> {
209 match value {
210 Value::Bool(b) => Ok(b),
211 Value::Number(n) if n == Number::from(0) => Ok(false),
212 Value::Number(n) if n == Number::from(1) => Ok(true),
213 Value::String(s) if s == "true" => Ok(true),
214 Value::String(s) if s == "false" => Ok(false),
215 x => Err(Error::ValueIsNotABool(x)),
216 }
217}
218
219fn value_to_bytes(value: Value) -> Result<Vec<u8>, Error> {
220 let out = match value {
221 Value::String(s) => hex_to_bytes(&s)?,
222 Value::Object(_) => {
223 let envelope: BytesEnvelope =
224 serde_json::from_value(value).map_err(Error::InvalidBytesEnvelope)?;
225
226 match envelope.content_type {
227 BytesEncoding::Base64 => base64_to_bytes(&envelope.content)?,
228 BytesEncoding::Hex => hex_to_bytes(&envelope.content)?,
229 }
230 }
231 x => return Err(Error::ValueIsNotBytes(x)),
232 };
233
234 Ok(out)
235}
236
237fn value_to_address(value: Value) -> Result<Vec<u8>, Error> {
238 let out = match value {
239 Value::String(s) => match bech32_to_bytes(&s) {
240 Ok(data) => data,
241 Err(_) => hex_to_bytes(&s)?,
242 },
243 x => return Err(Error::ValueIsNotAnAddress(x)),
244 };
245
246 Ok(out)
247}
248
249fn value_to_underfined(value: Value) -> Result<ArgValue, Error> {
250 match value {
251 Value::Bool(b) => Ok(ArgValue::Bool(b)),
252 Value::Number(x) => Ok(ArgValue::Int(number_to_bigint(x)?)),
253 Value::String(s) => Ok(ArgValue::String(s)),
254 x => Err(Error::CantInferTypeForValue(x)),
255 }
256}
257
258pub fn string_to_utxo_ref(s: &str) -> Result<UtxoRef, Error> {
259 let (txid, index) = s
260 .split_once('#')
261 .ok_or(Error::InvalidUtxoRef(s.to_string()))?;
262
263 let txid = hex::decode(txid).map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
264 let index = index
265 .parse()
266 .map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
267
268 Ok(UtxoRef { txid, index })
269}
270
271fn value_to_utxo_ref(value: Value) -> Result<UtxoRef, Error> {
272 match value {
273 Value::String(s) => string_to_utxo_ref(&s),
274 x => Err(Error::ValueIsNotUtxoRef(x)),
275 }
276}
277
278fn single_key(value: &Value) -> Option<(&str, &Value)> {
280 let obj = value.as_object()?;
281 if obj.len() != 1 {
282 return None;
283 }
284 obj.iter().next().map(|(k, v)| (k.as_str(), v))
285}
286
287fn is_tagged(value: &Value) -> bool {
291 matches!(
292 single_key(value),
293 Some((
294 "int"
295 | "bool"
296 | "string"
297 | "bytes"
298 | "address"
299 | "utxoRef"
300 | "list"
301 | "tuple"
302 | "map"
303 | "struct",
304 _,
305 ))
306 )
307}
308
309pub fn from_json(value: Value, target: &Type) -> Result<ArgValue, Error> {
316 if is_tagged(&value) {
317 decode_tagged(value)
318 } else {
319 coerce_bare(value, target)
320 }
321}
322
323fn coerce_bare(value: Value, target: &Type) -> Result<ArgValue, Error> {
326 match target {
327 Type::Int => Ok(ArgValue::Int(value_to_bigint(value)?)),
328 Type::Bool => Ok(ArgValue::Bool(value_to_bool(value)?)),
329 Type::Bytes => Ok(ArgValue::Bytes(value_to_bytes(value)?)),
330 Type::Address => Ok(ArgValue::Address(value_to_address(value)?)),
331 Type::UtxoRef => Ok(ArgValue::UtxoRef(value_to_utxo_ref(value)?)),
332 Type::Undefined => value_to_underfined(value),
333 x => Err(Error::TargetTypeNotSupported(x.clone())),
334 }
335}
336
337fn decode_tagged(value: Value) -> Result<ArgValue, Error> {
341 let Some((tag, _)) = single_key(&value) else {
342 return Err(Error::MalformedTaggedArg(value));
343 };
344 let tag = tag.to_string();
345 let inner = match value {
347 Value::Object(mut m) => m.remove(&tag).expect("single key present"),
348 _ => unreachable!("single_key guaranteed an object"),
349 };
350
351 match tag.as_str() {
352 "int" => Ok(ArgValue::Int(value_to_bigint(inner)?)),
353 "bool" => Ok(ArgValue::Bool(value_to_bool(inner)?)),
354 "string" => match inner {
355 Value::String(s) => Ok(ArgValue::String(s)),
356 _ => Err(Error::ValueIsNotAString),
357 },
358 "bytes" => Ok(ArgValue::Bytes(value_to_bytes(inner)?)),
359 "address" => Ok(ArgValue::Address(value_to_address(inner)?)),
360 "utxoRef" => Ok(ArgValue::UtxoRef(value_to_utxo_ref(inner)?)),
361 "list" => Ok(ArgValue::List(decode_seq(inner)?)),
362 "tuple" => Ok(ArgValue::Tuple(decode_seq(inner)?)),
363 "map" => Ok(ArgValue::Map(decode_map(inner)?)),
364 "struct" => decode_struct(inner),
365 other => Err(Error::UnknownTag(other.to_string())),
366 }
367}
368
369fn decode_seq(value: Value) -> Result<Vec<ArgValue>, Error> {
371 match value {
372 Value::Array(items) => items.into_iter().map(decode_tagged).collect(),
373 x => Err(Error::MalformedTaggedArg(x)),
374 }
375}
376
377fn decode_map(value: Value) -> Result<Vec<(ArgValue, ArgValue)>, Error> {
379 let arr = match value {
380 Value::Array(a) => a,
381 x => return Err(Error::MalformedTaggedArg(x)),
382 };
383
384 arr.into_iter()
385 .map(|pair| match pair {
386 Value::Array(mut kv) if kv.len() == 2 => {
387 let v = kv.pop().unwrap();
388 let k = kv.pop().unwrap();
389 Ok((decode_tagged(k)?, decode_tagged(v)?))
390 }
391 x => Err(Error::MalformedMapPair(x)),
392 })
393 .collect()
394}
395
396fn decode_struct(value: Value) -> Result<ArgValue, Error> {
398 let mut obj = match value {
399 Value::Object(o) => o,
400 x => return Err(Error::MalformedStruct(x)),
401 };
402
403 let constructor =
404 obj.get("constructor")
405 .and_then(Value::as_u64)
406 .ok_or_else(|| Error::MalformedStruct(Value::Object(obj.clone())))? as usize;
407
408 let fields = obj
409 .remove("fields")
410 .ok_or_else(|| Error::MalformedStruct(Value::Object(obj.clone())))?;
411
412 Ok(ArgValue::Struct {
413 constructor,
414 fields: decode_seq(fields)?,
415 })
416}
417
418pub fn utxo_ref_to_json(r: &UtxoRef) -> Value {
423 Value::String(format!("{}#{}", hex::encode(&r.txid), r.index))
424}
425
426pub fn arg_to_json(arg: &ArgValue) -> Value {
427 match arg {
428 ArgValue::Int(i) => serde_json::json!(i),
429 ArgValue::Bool(b) => Value::Bool(*b),
430 ArgValue::String(s) => Value::String(s.clone()),
431 ArgValue::Bytes(v) => Value::String(hex::encode(v)),
432 ArgValue::Address(v) => Value::String(hex::encode(v)),
433 ArgValue::UtxoRef(r) => utxo_ref_to_json(r),
434 ArgValue::UtxoSet(_) => Value::Null,
435 ArgValue::List(_) | ArgValue::Tuple(_) | ArgValue::Map(_) | ArgValue::Struct { .. } => {
438 tagged_arg_to_json(arg)
439 }
440 }
441}
442
443pub fn tagged_arg_to_json(arg: &ArgValue) -> Value {
446 match arg {
447 ArgValue::Int(i) => serde_json::json!({ "int": i }),
448 ArgValue::Bool(b) => serde_json::json!({ "bool": b }),
449 ArgValue::String(s) => serde_json::json!({ "string": s }),
450 ArgValue::Bytes(v) => serde_json::json!({ "bytes": hex::encode(v) }),
451 ArgValue::Address(v) => serde_json::json!({ "address": hex::encode(v) }),
452 ArgValue::UtxoRef(r) => {
453 serde_json::json!({ "utxoRef": format!("{}#{}", hex::encode(&r.txid), r.index) })
454 }
455 ArgValue::UtxoSet(_) => Value::Null,
457 ArgValue::List(xs) => {
458 serde_json::json!({ "list": xs.iter().map(tagged_arg_to_json).collect::<Vec<_>>() })
459 }
460 ArgValue::Tuple(xs) => {
461 serde_json::json!({ "tuple": xs.iter().map(tagged_arg_to_json).collect::<Vec<_>>() })
462 }
463 ArgValue::Map(pairs) => serde_json::json!({
464 "map": pairs
465 .iter()
466 .map(|(k, v)| vec![tagged_arg_to_json(k), tagged_arg_to_json(v)])
467 .collect::<Vec<_>>()
468 }),
469 ArgValue::Struct {
470 constructor,
471 fields,
472 } => serde_json::json!({
473 "struct": {
474 "constructor": constructor,
475 "fields": fields.iter().map(tagged_arg_to_json).collect::<Vec<_>>(),
476 }
477 }),
478 }
479}
480
481pub fn utxo_to_json(utxo: &Utxo) -> Value {
482 let assets: Map<String, Value> = utxo
483 .assets
484 .iter()
485 .map(|(class, amount)| (class.to_string(), serde_json::json!(amount)))
486 .collect();
487
488 serde_json::json!({
489 "ref": utxo_ref_to_json(&utxo.r#ref),
490 "address": hex::encode(&utxo.address),
491 "assets": assets,
492 "datum": utxo.datum,
493 "script": utxo.script,
494 })
495}
496
497fn parse_asset_class(key: &str) -> AssetClass {
498 if key == "naked" {
499 AssetClass::Naked
500 } else if let Some((policy, name)) = key.split_once('.') {
501 let policy_bytes = hex::decode(policy).unwrap_or_default();
502 let name_bytes = hex::decode(name).unwrap_or_default();
503 AssetClass::Defined(policy_bytes, name_bytes)
504 } else {
505 let name_bytes = hex::decode(key).unwrap_or_default();
506 AssetClass::Named(name_bytes)
507 }
508}
509
510fn assets_from_json(value: &Value) -> Result<CanonicalAssets, Error> {
511 let obj = value.as_object().ok_or(Error::ValueIsNotAString)?;
512
513 let mut assets = CanonicalAssets::empty();
514 for (key, amount_val) in obj {
515 let class = parse_asset_class(key);
516 let amount = value_to_bigint(amount_val.clone())?;
517 assets = assets + CanonicalAssets::from_class_and_amount(class, amount);
518 }
519
520 Ok(assets)
521}
522
523pub fn utxo_from_json(value: &Value) -> Result<Utxo, Error> {
524 let ref_str = value["ref"].as_str().ok_or(Error::ValueIsNotAString)?;
525 let utxo_ref = string_to_utxo_ref(ref_str)?;
526
527 let address = hex_to_bytes(value["address"].as_str().ok_or(Error::ValueIsNotAString)?)?;
528
529 let assets = assets_from_json(&value["assets"])?;
530
531 let datum = value
532 .get("datum")
533 .filter(|v| !v.is_null())
534 .map(|v| serde_json::from_value(v.clone()))
535 .transpose()
536 .map_err(Error::InvalidBytesEnvelope)?;
537
538 let script = value
539 .get("script")
540 .filter(|v| !v.is_null())
541 .map(|v| serde_json::from_value(v.clone()))
542 .transpose()
543 .map_err(Error::InvalidBytesEnvelope)?;
544
545 Ok(Utxo {
546 r#ref: utxo_ref,
547 address,
548 assets,
549 datum,
550 script,
551 })
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use serde_json::json;
558
559 fn assert_from_json(provided: Value, target: Type, expected: ArgValue) {
560 let value = from_json(provided, &target).unwrap();
561 assert_eq!(value, expected);
562 }
563
564 #[test]
569 fn from_json_small_int() {
570 assert_from_json(json!(123456789), Type::Int, ArgValue::Int(123456789));
571 }
572
573 #[test]
574 fn from_json_negative_int() {
575 assert_from_json(json!(-123456789), Type::Int, ArgValue::Int(-123456789));
576 }
577
578 #[test]
579 fn from_json_big_int() {
580 assert_from_json(
581 json!("12345678901234567890"),
582 Type::Int,
583 ArgValue::Int(12345678901234567890),
584 );
585 }
586
587 #[test]
588 fn from_json_int_overflow() {
589 assert_from_json(
590 json!(i128::MIN.to_string()),
591 Type::Int,
592 ArgValue::Int(i128::MIN),
593 );
594 assert_from_json(
595 json!(i128::MAX.to_string()),
596 Type::Int,
597 ArgValue::Int(i128::MAX),
598 );
599 }
600
601 #[test]
602 fn from_json_bool() {
603 assert_from_json(json!(true), Type::Bool, ArgValue::Bool(true));
604 assert_from_json(json!(false), Type::Bool, ArgValue::Bool(false));
605 }
606
607 #[test]
608 fn from_json_bool_number() {
609 assert_from_json(json!(1), Type::Bool, ArgValue::Bool(true));
610 assert_from_json(json!(0), Type::Bool, ArgValue::Bool(false));
611 }
612
613 #[test]
614 fn from_json_bool_string() {
615 assert_from_json(json!("true"), Type::Bool, ArgValue::Bool(true));
616 assert_from_json(json!("false"), Type::Bool, ArgValue::Bool(false));
617 }
618
619 #[test]
620 fn from_json_bytes() {
621 assert_from_json(
622 json!(hex::encode("hello")),
623 Type::Bytes,
624 ArgValue::Bytes(b"hello".to_vec()),
625 );
626
627 assert_from_json(
628 json!(format!("0x{}", hex::encode("hello"))),
629 Type::Bytes,
630 ArgValue::Bytes(b"hello".to_vec()),
631 );
632 }
633
634 #[test]
635 fn from_json_bytes_base64() {
636 let json = json!(BytesEnvelope {
637 content: "aGVsbG8=".to_string(),
638 content_type: BytesEncoding::Base64,
639 });
640
641 assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
642 }
643
644 #[test]
645 fn from_json_bytes_hex() {
646 let json = json!(BytesEnvelope {
647 content: "68656c6c6f".to_string(),
648 content_type: BytesEncoding::Hex,
649 });
650
651 assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
652 }
653
654 #[test]
655 fn from_json_address() {
656 assert_from_json(
657 json!(hex::encode("abc123")),
658 Type::Address,
659 ArgValue::Address(b"abc123".to_vec()),
660 );
661 }
662
663 #[test]
664 fn from_json_address_bech32() {
665 let json = json!("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8");
666 let bytes =
667 hex::decode("619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e").unwrap();
668 assert_from_json(json, Type::Address, ArgValue::Address(bytes));
669 }
670
671 #[test]
672 fn from_json_utxo_ref() {
673 let json = json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#0");
674
675 let utxo_ref = UtxoRef {
676 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
677 .unwrap(),
678 index: 0,
679 };
680
681 assert_from_json(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref));
682 }
683
684 #[test]
685 fn from_json_undefined_infers_from_shape() {
686 assert_from_json(json!(true), Type::Undefined, ArgValue::Bool(true));
688 assert_from_json(json!(42), Type::Undefined, ArgValue::Int(42));
689 assert_from_json(json!("hi"), Type::Undefined, ArgValue::String("hi".into()));
690 }
691
692 #[test]
693 fn bytes_envelope_object_is_not_mistaken_for_a_tag() {
694 let envelope = json!({ "content": "aGVsbG8=", "contentType": "base64" });
697 assert_from_json(envelope, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
698 }
699
700 #[test]
701 fn decode_list_of_int() {
702 assert_from_json(
703 json!({ "list": [{ "int": 1 }, { "int": 2 }, { "int": 3 }] }),
704 Type::List,
705 ArgValue::List(vec![ArgValue::Int(1), ArgValue::Int(2), ArgValue::Int(3)]),
706 );
707 }
708
709 #[test]
710 fn decode_tuple_int_bytes() {
711 assert_from_json(
712 json!({ "tuple": [{ "int": 42 }, { "bytes": "cafe" }] }),
713 Type::Tuple,
714 ArgValue::Tuple(vec![ArgValue::Int(42), ArgValue::Bytes(vec![0xca, 0xfe])]),
715 );
716 }
717
718 #[test]
719 fn decode_map_string_keys() {
720 assert_from_json(
721 json!({ "map": [[{ "string": "1" }, { "int": 100 }], [{ "string": "2" }, { "int": 200 }]] }),
722 Type::Map,
723 ArgValue::Map(vec![
724 (ArgValue::String("1".into()), ArgValue::Int(100)),
725 (ArgValue::String("2".into()), ArgValue::Int(200)),
726 ]),
727 );
728 }
729
730 #[test]
731 fn decode_struct_record() {
732 assert_from_json(
734 json!({ "struct": { "constructor": 0, "fields": [{ "bytes": "aabb" }, { "bytes": "0011" }] } }),
735 Type::Custom("AssetClass".into()),
736 ArgValue::Struct {
737 constructor: 0,
738 fields: vec![
739 ArgValue::Bytes(vec![0xaa, 0xbb]),
740 ArgValue::Bytes(vec![0x00, 0x11]),
741 ],
742 },
743 );
744 }
745
746 #[test]
747 fn decode_nested_struct_05_invoke() {
748 assert_from_json(
750 json!({
751 "struct": {
752 "constructor": 0,
753 "fields": [
754 { "list": [{ "int": 1 }, { "int": 2 }, { "int": 3 }] },
755 { "int": 7 }
756 ]
757 }
758 }),
759 Type::Custom("Meta".into()),
760 ArgValue::Struct {
761 constructor: 0,
762 fields: vec![
763 ArgValue::List(vec![ArgValue::Int(1), ArgValue::Int(2), ArgValue::Int(3)]),
764 ArgValue::Int(7),
765 ],
766 },
767 );
768 }
769
770 #[test]
771 fn decode_accepts_tagged_scalar_leniently() {
772 assert_from_json(json!({ "int": 5 }), Type::Int, ArgValue::Int(5));
773 assert_from_json(
774 json!({ "bytes": "cafe" }),
775 Type::Bytes,
776 ArgValue::Bytes(vec![0xca, 0xfe]),
777 );
778 }
779
780 #[test]
781 fn decode_round_trips_through_encoded_json() {
782 let encoded = json!({
783 "struct": {
784 "constructor": 0,
785 "fields": [{ "list": [{ "int": 1 }, { "int": 2 }] }, { "int": 7 }]
786 }
787 });
788 let arg = from_json(encoded.clone(), &Type::Custom("Meta".into())).unwrap();
789 assert_eq!(tagged_arg_to_json(&arg), encoded);
790 }
791
792 #[test]
793 fn decode_rejects_unknown_tag() {
794 let err = from_json(json!({ "list": [{ "bogus": 1 }] }), &Type::List).unwrap_err();
796 assert!(matches!(err, Error::UnknownTag(_)), "got {err:?}");
797 }
798
799 #[test]
800 fn bare_aggregate_is_rejected() {
801 let err = from_json(json!([1, 2, 3]), &Type::List).unwrap_err();
803 assert!(
804 matches!(err, Error::TargetTypeNotSupported(_)),
805 "got {err:?}"
806 );
807 }
808
809 #[test]
810 fn decode_rejects_malformed_struct() {
811 let err = from_json(
813 json!({ "struct": { "constructor": 0 } }),
814 &Type::Custom("Meta".into()),
815 )
816 .unwrap_err();
817 assert!(matches!(err, Error::MalformedStruct(_)), "got {err:?}");
818 }
819
820 #[test]
821 fn decode_rejects_malformed_map_pair() {
822 let err = from_json(json!({ "map": [[{ "int": 1 }]] }), &Type::Map).unwrap_err();
823 assert!(matches!(err, Error::MalformedMapPair(_)), "got {err:?}");
824 }
825
826 #[test]
827 fn decode_rejects_untagged_nested_value() {
828 let err = from_json(json!({ "list": [5] }), &Type::List).unwrap_err();
830 assert!(matches!(err, Error::MalformedTaggedArg(_)), "got {err:?}");
831 }
832
833 use tx3_tir::model::assets::{AssetClass, CanonicalAssets};
838 use tx3_tir::model::core::Utxo;
839
840 fn sample_utxo_ref() -> UtxoRef {
841 UtxoRef {
842 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
843 .unwrap(),
844 index: 2,
845 }
846 }
847
848 #[test]
849 fn utxo_ref_to_json_format() {
850 let r = sample_utxo_ref();
851 let v = utxo_ref_to_json(&r);
852 assert_eq!(
853 v,
854 json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#2")
855 );
856 }
857
858 #[test]
859 fn arg_to_json_int() {
860 assert_eq!(arg_to_json(&ArgValue::Int(42)), json!(42));
861 }
862
863 #[test]
864 fn arg_to_json_bool() {
865 assert_eq!(arg_to_json(&ArgValue::Bool(true)), json!(true));
866 }
867
868 #[test]
869 fn arg_to_json_string() {
870 assert_eq!(
871 arg_to_json(&ArgValue::String("hello".into())),
872 json!("hello")
873 );
874 }
875
876 #[test]
877 fn arg_to_json_bytes() {
878 assert_eq!(
879 arg_to_json(&ArgValue::Bytes(b"hello".to_vec())),
880 json!("68656c6c6f")
881 );
882 }
883
884 #[test]
885 fn arg_to_json_address() {
886 assert_eq!(
887 arg_to_json(&ArgValue::Address(b"\x01\x02".to_vec())),
888 json!("0102")
889 );
890 }
891
892 #[test]
893 fn arg_to_json_utxo_ref() {
894 let r = sample_utxo_ref();
895 assert_eq!(
896 arg_to_json(&ArgValue::UtxoRef(r.clone())),
897 utxo_ref_to_json(&r)
898 );
899 }
900
901 #[test]
902 fn utxo_to_json_empty_assets() {
903 let utxo = Utxo {
904 r#ref: sample_utxo_ref(),
905 address: b"\xab\xcd".to_vec(),
906 assets: CanonicalAssets::empty(),
907 datum: None,
908 script: None,
909 };
910
911 let v = utxo_to_json(&utxo);
912 assert_eq!(v["assets"], json!({}));
913 assert_eq!(v["address"], json!("abcd"));
914 assert!(v["datum"].is_null());
915 assert!(v["script"].is_null());
916 }
917
918 #[test]
919 fn utxo_to_json_naked_assets() {
920 let utxo = Utxo {
921 r#ref: sample_utxo_ref(),
922 address: b"\x01".to_vec(),
923 assets: CanonicalAssets::from_naked_amount(5_000_000),
924 datum: None,
925 script: None,
926 };
927
928 let v = utxo_to_json(&utxo);
929 assert_eq!(v["assets"]["naked"], json!(5_000_000));
930 }
931
932 #[test]
933 fn utxo_to_json_defined_assets() {
934 let assets = CanonicalAssets::from_class_and_amount(
935 AssetClass::Defined(b"policy1".to_vec(), b"token1".to_vec()),
936 100,
937 );
938
939 let utxo = Utxo {
940 r#ref: sample_utxo_ref(),
941 address: b"\x01".to_vec(),
942 assets,
943 datum: None,
944 script: None,
945 };
946
947 let v = utxo_to_json(&utxo);
948 let key = format!("{}.{}", hex::encode(b"policy1"), hex::encode(b"token1"));
949 assert_eq!(v["assets"][&key], json!(100));
950 }
951}