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 = i128::from_str_radix(&s, 10)
163 .map_err(|x| Error::InvalidBytesForNumber(x.to_string()))?;
164 Ok(i)
165 }
166}
167
168pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
169 let s = if has_hex_prefix(s) {
170 s.trim_start_matches("0x")
171 } else {
172 s
173 };
174
175 let out = hex::decode(s)?;
176
177 Ok(out)
178}
179
180pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
181 let out = base64::engine::general_purpose::STANDARD.decode(s)?;
182
183 Ok(out)
184}
185
186pub fn bech32_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
187 let (_, data) = bech32::decode(s)?;
188
189 Ok(data)
190}
191
192fn number_to_bigint(x: Number) -> Result<i128, Error> {
193 x.as_i128().ok_or(Error::NumberCantFit(x))
194}
195
196fn value_to_bigint(value: Value) -> Result<i128, Error> {
197 let out = match value {
198 Value::Number(n) => number_to_bigint(n)?,
199 Value::String(s) => string_to_bigint(s)?,
200 Value::Null => return Err(Error::ValueIsNull),
201 x => return Err(Error::ValueIsNotANumber(x)),
202 };
203
204 Ok(out)
205}
206
207fn value_to_bool(value: Value) -> Result<bool, Error> {
208 match value {
209 Value::Bool(b) => Ok(b),
210 Value::Number(n) if n == Number::from(0) => Ok(false),
211 Value::Number(n) if n == Number::from(1) => Ok(true),
212 Value::String(s) if s == "true" => Ok(true),
213 Value::String(s) if s == "false" => Ok(false),
214 x => Err(Error::ValueIsNotABool(x)),
215 }
216}
217
218fn value_to_bytes(value: Value) -> Result<Vec<u8>, Error> {
219 let out = match value {
220 Value::String(s) => hex_to_bytes(&s)?,
221 Value::Object(_) => {
222 let envelope: BytesEnvelope =
223 serde_json::from_value(value).map_err(Error::InvalidBytesEnvelope)?;
224
225 match envelope.content_type {
226 BytesEncoding::Base64 => base64_to_bytes(&envelope.content)?,
227 BytesEncoding::Hex => hex_to_bytes(&envelope.content)?,
228 }
229 }
230 x => return Err(Error::ValueIsNotBytes(x)),
231 };
232
233 Ok(out)
234}
235
236fn value_to_address(value: Value) -> Result<Vec<u8>, Error> {
237 let out = match value {
238 Value::String(s) => match bech32_to_bytes(&s) {
239 Ok(data) => data,
240 Err(_) => hex_to_bytes(&s)?,
241 },
242 x => return Err(Error::ValueIsNotAnAddress(x)),
243 };
244
245 Ok(out)
246}
247
248fn value_to_underfined(value: Value) -> Result<ArgValue, Error> {
249 match value {
250 Value::Bool(b) => Ok(ArgValue::Bool(b)),
251 Value::Number(x) => Ok(ArgValue::Int(number_to_bigint(x)?)),
252 Value::String(s) => Ok(ArgValue::String(s)),
253 x => Err(Error::CantInferTypeForValue(x)),
254 }
255}
256
257pub fn string_to_utxo_ref(s: &str) -> Result<UtxoRef, Error> {
258 let (txid, index) = s
259 .split_once('#')
260 .ok_or(Error::InvalidUtxoRef(s.to_string()))?;
261
262 let txid = hex::decode(txid).map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
263 let index = index
264 .parse()
265 .map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
266
267 Ok(UtxoRef { txid, index })
268}
269
270fn value_to_utxo_ref(value: Value) -> Result<UtxoRef, Error> {
271 match value {
272 Value::String(s) => string_to_utxo_ref(&s),
273 x => Err(Error::ValueIsNotUtxoRef(x)),
274 }
275}
276
277fn single_key(value: &Value) -> Option<(&str, &Value)> {
279 let obj = value.as_object()?;
280 if obj.len() != 1 {
281 return None;
282 }
283 obj.iter().next().map(|(k, v)| (k.as_str(), v))
284}
285
286fn is_tagged(value: &Value) -> bool {
290 matches!(
291 single_key(value),
292 Some((
293 "int" | "bool" | "string" | "bytes" | "address" | "utxoRef" | "list" | "tuple"
294 | "map" | "struct",
295 _,
296 ))
297 )
298}
299
300pub fn from_json(value: Value, target: &Type) -> Result<ArgValue, Error> {
307 if is_tagged(&value) {
308 decode_tagged(value)
309 } else {
310 coerce_bare(value, target)
311 }
312}
313
314fn coerce_bare(value: Value, target: &Type) -> Result<ArgValue, Error> {
317 match target {
318 Type::Int => Ok(ArgValue::Int(value_to_bigint(value)?)),
319 Type::Bool => Ok(ArgValue::Bool(value_to_bool(value)?)),
320 Type::Bytes => Ok(ArgValue::Bytes(value_to_bytes(value)?)),
321 Type::Address => Ok(ArgValue::Address(value_to_address(value)?)),
322 Type::UtxoRef => Ok(ArgValue::UtxoRef(value_to_utxo_ref(value)?)),
323 Type::Undefined => value_to_underfined(value),
324 x => Err(Error::TargetTypeNotSupported(x.clone())),
325 }
326}
327
328fn decode_tagged(value: Value) -> Result<ArgValue, Error> {
332 let Some((tag, _)) = single_key(&value) else {
333 return Err(Error::MalformedTaggedArg(value));
334 };
335 let tag = tag.to_string();
336 let inner = match value {
338 Value::Object(mut m) => m.remove(&tag).expect("single key present"),
339 _ => unreachable!("single_key guaranteed an object"),
340 };
341
342 match tag.as_str() {
343 "int" => Ok(ArgValue::Int(value_to_bigint(inner)?)),
344 "bool" => Ok(ArgValue::Bool(value_to_bool(inner)?)),
345 "string" => match inner {
346 Value::String(s) => Ok(ArgValue::String(s)),
347 _ => Err(Error::ValueIsNotAString),
348 },
349 "bytes" => Ok(ArgValue::Bytes(value_to_bytes(inner)?)),
350 "address" => Ok(ArgValue::Address(value_to_address(inner)?)),
351 "utxoRef" => Ok(ArgValue::UtxoRef(value_to_utxo_ref(inner)?)),
352 "list" => Ok(ArgValue::List(decode_seq(inner)?)),
353 "tuple" => Ok(ArgValue::Tuple(decode_seq(inner)?)),
354 "map" => Ok(ArgValue::Map(decode_map(inner)?)),
355 "struct" => decode_struct(inner),
356 other => Err(Error::UnknownTag(other.to_string())),
357 }
358}
359
360fn decode_seq(value: Value) -> Result<Vec<ArgValue>, Error> {
362 match value {
363 Value::Array(items) => items.into_iter().map(decode_tagged).collect(),
364 x => Err(Error::MalformedTaggedArg(x)),
365 }
366}
367
368fn decode_map(value: Value) -> Result<Vec<(ArgValue, ArgValue)>, Error> {
370 let arr = match value {
371 Value::Array(a) => a,
372 x => return Err(Error::MalformedTaggedArg(x)),
373 };
374
375 arr.into_iter()
376 .map(|pair| match pair {
377 Value::Array(mut kv) if kv.len() == 2 => {
378 let v = kv.pop().unwrap();
379 let k = kv.pop().unwrap();
380 Ok((decode_tagged(k)?, decode_tagged(v)?))
381 }
382 x => Err(Error::MalformedMapPair(x)),
383 })
384 .collect()
385}
386
387fn decode_struct(value: Value) -> Result<ArgValue, Error> {
389 let mut obj = match value {
390 Value::Object(o) => o,
391 x => return Err(Error::MalformedStruct(x)),
392 };
393
394 let constructor = obj
395 .get("constructor")
396 .and_then(Value::as_u64)
397 .ok_or_else(|| Error::MalformedStruct(Value::Object(obj.clone())))? as usize;
398
399 let fields = obj
400 .remove("fields")
401 .ok_or_else(|| Error::MalformedStruct(Value::Object(obj.clone())))?;
402
403 Ok(ArgValue::Struct {
404 constructor,
405 fields: decode_seq(fields)?,
406 })
407}
408
409pub fn utxo_ref_to_json(r: &UtxoRef) -> Value {
414 Value::String(format!("{}#{}", hex::encode(&r.txid), r.index))
415}
416
417pub fn arg_to_json(arg: &ArgValue) -> Value {
418 match arg {
419 ArgValue::Int(i) => serde_json::json!(i),
420 ArgValue::Bool(b) => Value::Bool(*b),
421 ArgValue::String(s) => Value::String(s.clone()),
422 ArgValue::Bytes(v) => Value::String(hex::encode(v)),
423 ArgValue::Address(v) => Value::String(hex::encode(v)),
424 ArgValue::UtxoRef(r) => utxo_ref_to_json(r),
425 ArgValue::UtxoSet(_) => Value::Null,
426 ArgValue::List(_) | ArgValue::Tuple(_) | ArgValue::Map(_) | ArgValue::Struct { .. } => {
429 tagged_arg_to_json(arg)
430 }
431 }
432}
433
434pub fn tagged_arg_to_json(arg: &ArgValue) -> Value {
437 match arg {
438 ArgValue::Int(i) => serde_json::json!({ "int": i }),
439 ArgValue::Bool(b) => serde_json::json!({ "bool": b }),
440 ArgValue::String(s) => serde_json::json!({ "string": s }),
441 ArgValue::Bytes(v) => serde_json::json!({ "bytes": hex::encode(v) }),
442 ArgValue::Address(v) => serde_json::json!({ "address": hex::encode(v) }),
443 ArgValue::UtxoRef(r) => serde_json::json!({ "utxoRef": format!("{}#{}", hex::encode(&r.txid), r.index) }),
444 ArgValue::UtxoSet(_) => Value::Null,
446 ArgValue::List(xs) => {
447 serde_json::json!({ "list": xs.iter().map(tagged_arg_to_json).collect::<Vec<_>>() })
448 }
449 ArgValue::Tuple(xs) => {
450 serde_json::json!({ "tuple": xs.iter().map(tagged_arg_to_json).collect::<Vec<_>>() })
451 }
452 ArgValue::Map(pairs) => serde_json::json!({
453 "map": pairs
454 .iter()
455 .map(|(k, v)| vec![tagged_arg_to_json(k), tagged_arg_to_json(v)])
456 .collect::<Vec<_>>()
457 }),
458 ArgValue::Struct {
459 constructor,
460 fields,
461 } => serde_json::json!({
462 "struct": {
463 "constructor": constructor,
464 "fields": fields.iter().map(tagged_arg_to_json).collect::<Vec<_>>(),
465 }
466 }),
467 }
468}
469
470pub fn utxo_to_json(utxo: &Utxo) -> Value {
471 let assets: Map<String, Value> = utxo
472 .assets
473 .iter()
474 .map(|(class, amount)| (class.to_string(), serde_json::json!(amount)))
475 .collect();
476
477 serde_json::json!({
478 "ref": utxo_ref_to_json(&utxo.r#ref),
479 "address": hex::encode(&utxo.address),
480 "assets": assets,
481 "datum": utxo.datum,
482 "script": utxo.script,
483 })
484}
485
486fn parse_asset_class(key: &str) -> AssetClass {
487 if key == "naked" {
488 AssetClass::Naked
489 } else if let Some((policy, name)) = key.split_once('.') {
490 let policy_bytes = hex::decode(policy).unwrap_or_default();
491 let name_bytes = hex::decode(name).unwrap_or_default();
492 AssetClass::Defined(policy_bytes, name_bytes)
493 } else {
494 let name_bytes = hex::decode(key).unwrap_or_default();
495 AssetClass::Named(name_bytes)
496 }
497}
498
499fn assets_from_json(value: &Value) -> Result<CanonicalAssets, Error> {
500 let obj = value.as_object().ok_or(Error::ValueIsNotAString)?;
501
502 let mut assets = CanonicalAssets::empty();
503 for (key, amount_val) in obj {
504 let class = parse_asset_class(key);
505 let amount = value_to_bigint(amount_val.clone())?;
506 assets = assets + CanonicalAssets::from_class_and_amount(class, amount);
507 }
508
509 Ok(assets)
510}
511
512pub fn utxo_from_json(value: &Value) -> Result<Utxo, Error> {
513 let ref_str = value["ref"].as_str().ok_or(Error::ValueIsNotAString)?;
514 let utxo_ref = string_to_utxo_ref(ref_str)?;
515
516 let address = hex_to_bytes(value["address"].as_str().ok_or(Error::ValueIsNotAString)?)?;
517
518 let assets = assets_from_json(&value["assets"])?;
519
520 let datum = value
521 .get("datum")
522 .filter(|v| !v.is_null())
523 .map(|v| serde_json::from_value(v.clone()))
524 .transpose()
525 .map_err(|e| Error::InvalidBytesEnvelope(e))?;
526
527 let script = value
528 .get("script")
529 .filter(|v| !v.is_null())
530 .map(|v| serde_json::from_value(v.clone()))
531 .transpose()
532 .map_err(|e| Error::InvalidBytesEnvelope(e))?;
533
534 Ok(Utxo {
535 r#ref: utxo_ref,
536 address,
537 assets,
538 datum,
539 script,
540 })
541}
542
543#[cfg(test)]
544mod tests {
545 use super::*;
546 use serde_json::json;
547
548 fn assert_from_json(provided: Value, target: Type, expected: ArgValue) {
549 let value = from_json(provided, &target).unwrap();
550 assert_eq!(value, expected);
551 }
552
553 #[test]
558 fn from_json_small_int() {
559 assert_from_json(json!(123456789), Type::Int, ArgValue::Int(123456789));
560 }
561
562 #[test]
563 fn from_json_negative_int() {
564 assert_from_json(json!(-123456789), Type::Int, ArgValue::Int(-123456789));
565 }
566
567 #[test]
568 fn from_json_big_int() {
569 assert_from_json(
570 json!("12345678901234567890"),
571 Type::Int,
572 ArgValue::Int(12345678901234567890),
573 );
574 }
575
576 #[test]
577 fn from_json_int_overflow() {
578 assert_from_json(
579 json!(i128::MIN.to_string()),
580 Type::Int,
581 ArgValue::Int(i128::MIN),
582 );
583 assert_from_json(
584 json!(i128::MAX.to_string()),
585 Type::Int,
586 ArgValue::Int(i128::MAX),
587 );
588 }
589
590 #[test]
591 fn from_json_bool() {
592 assert_from_json(json!(true), Type::Bool, ArgValue::Bool(true));
593 assert_from_json(json!(false), Type::Bool, ArgValue::Bool(false));
594 }
595
596 #[test]
597 fn from_json_bool_number() {
598 assert_from_json(json!(1), Type::Bool, ArgValue::Bool(true));
599 assert_from_json(json!(0), Type::Bool, ArgValue::Bool(false));
600 }
601
602 #[test]
603 fn from_json_bool_string() {
604 assert_from_json(json!("true"), Type::Bool, ArgValue::Bool(true));
605 assert_from_json(json!("false"), Type::Bool, ArgValue::Bool(false));
606 }
607
608 #[test]
609 fn from_json_bytes() {
610 assert_from_json(
611 json!(hex::encode("hello")),
612 Type::Bytes,
613 ArgValue::Bytes(b"hello".to_vec()),
614 );
615
616 assert_from_json(
617 json!(format!("0x{}", hex::encode("hello"))),
618 Type::Bytes,
619 ArgValue::Bytes(b"hello".to_vec()),
620 );
621 }
622
623 #[test]
624 fn from_json_bytes_base64() {
625 let json = json!(BytesEnvelope {
626 content: "aGVsbG8=".to_string(),
627 content_type: BytesEncoding::Base64,
628 });
629
630 assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
631 }
632
633 #[test]
634 fn from_json_bytes_hex() {
635 let json = json!(BytesEnvelope {
636 content: "68656c6c6f".to_string(),
637 content_type: BytesEncoding::Hex,
638 });
639
640 assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
641 }
642
643 #[test]
644 fn from_json_address() {
645 assert_from_json(
646 json!(hex::encode("abc123")),
647 Type::Address,
648 ArgValue::Address(b"abc123".to_vec()),
649 );
650 }
651
652 #[test]
653 fn from_json_address_bech32() {
654 let json = json!("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8");
655 let bytes =
656 hex::decode("619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e").unwrap();
657 assert_from_json(json, Type::Address, ArgValue::Address(bytes));
658 }
659
660 #[test]
661 fn from_json_utxo_ref() {
662 let json = json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#0");
663
664 let utxo_ref = UtxoRef {
665 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
666 .unwrap(),
667 index: 0,
668 };
669
670 assert_from_json(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref));
671 }
672
673 #[test]
674 fn from_json_undefined_infers_from_shape() {
675 assert_from_json(json!(true), Type::Undefined, ArgValue::Bool(true));
677 assert_from_json(json!(42), Type::Undefined, ArgValue::Int(42));
678 assert_from_json(json!("hi"), Type::Undefined, ArgValue::String("hi".into()));
679 }
680
681 #[test]
682 fn bytes_envelope_object_is_not_mistaken_for_a_tag() {
683 let envelope = json!({ "content": "aGVsbG8=", "contentType": "base64" });
686 assert_from_json(envelope, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
687 }
688
689 #[test]
690 fn decode_list_of_int() {
691 assert_from_json(
692 json!({ "list": [{ "int": 1 }, { "int": 2 }, { "int": 3 }] }),
693 Type::List,
694 ArgValue::List(vec![ArgValue::Int(1), ArgValue::Int(2), ArgValue::Int(3)]),
695 );
696 }
697
698 #[test]
699 fn decode_tuple_int_bytes() {
700 assert_from_json(
701 json!({ "tuple": [{ "int": 42 }, { "bytes": "cafe" }] }),
702 Type::Tuple,
703 ArgValue::Tuple(vec![ArgValue::Int(42), ArgValue::Bytes(vec![0xca, 0xfe])]),
704 );
705 }
706
707 #[test]
708 fn decode_map_string_keys() {
709 assert_from_json(
710 json!({ "map": [[{ "string": "1" }, { "int": 100 }], [{ "string": "2" }, { "int": 200 }]] }),
711 Type::Map,
712 ArgValue::Map(vec![
713 (ArgValue::String("1".into()), ArgValue::Int(100)),
714 (ArgValue::String("2".into()), ArgValue::Int(200)),
715 ]),
716 );
717 }
718
719 #[test]
720 fn decode_struct_record() {
721 assert_from_json(
723 json!({ "struct": { "constructor": 0, "fields": [{ "bytes": "aabb" }, { "bytes": "0011" }] } }),
724 Type::Custom("AssetClass".into()),
725 ArgValue::Struct {
726 constructor: 0,
727 fields: vec![
728 ArgValue::Bytes(vec![0xaa, 0xbb]),
729 ArgValue::Bytes(vec![0x00, 0x11]),
730 ],
731 },
732 );
733 }
734
735 #[test]
736 fn decode_nested_struct_05_invoke() {
737 assert_from_json(
739 json!({
740 "struct": {
741 "constructor": 0,
742 "fields": [
743 { "list": [{ "int": 1 }, { "int": 2 }, { "int": 3 }] },
744 { "int": 7 }
745 ]
746 }
747 }),
748 Type::Custom("Meta".into()),
749 ArgValue::Struct {
750 constructor: 0,
751 fields: vec![
752 ArgValue::List(vec![ArgValue::Int(1), ArgValue::Int(2), ArgValue::Int(3)]),
753 ArgValue::Int(7),
754 ],
755 },
756 );
757 }
758
759 #[test]
760 fn decode_accepts_tagged_scalar_leniently() {
761 assert_from_json(json!({ "int": 5 }), Type::Int, ArgValue::Int(5));
762 assert_from_json(json!({ "bytes": "cafe" }), Type::Bytes, ArgValue::Bytes(vec![0xca, 0xfe]));
763 }
764
765 #[test]
766 fn decode_round_trips_through_encoded_json() {
767 let encoded = json!({
768 "struct": {
769 "constructor": 0,
770 "fields": [{ "list": [{ "int": 1 }, { "int": 2 }] }, { "int": 7 }]
771 }
772 });
773 let arg = from_json(encoded.clone(), &Type::Custom("Meta".into())).unwrap();
774 assert_eq!(tagged_arg_to_json(&arg), encoded);
775 }
776
777 #[test]
778 fn decode_rejects_unknown_tag() {
779 let err = from_json(json!({ "list": [{ "bogus": 1 }] }), &Type::List).unwrap_err();
781 assert!(matches!(err, Error::UnknownTag(_)), "got {err:?}");
782 }
783
784 #[test]
785 fn bare_aggregate_is_rejected() {
786 let err = from_json(json!([1, 2, 3]), &Type::List).unwrap_err();
788 assert!(
789 matches!(err, Error::TargetTypeNotSupported(_)),
790 "got {err:?}"
791 );
792 }
793
794 #[test]
795 fn decode_rejects_malformed_struct() {
796 let err = from_json(
798 json!({ "struct": { "constructor": 0 } }),
799 &Type::Custom("Meta".into()),
800 )
801 .unwrap_err();
802 assert!(matches!(err, Error::MalformedStruct(_)), "got {err:?}");
803 }
804
805 #[test]
806 fn decode_rejects_malformed_map_pair() {
807 let err = from_json(json!({ "map": [[{ "int": 1 }]] }), &Type::Map).unwrap_err();
808 assert!(matches!(err, Error::MalformedMapPair(_)), "got {err:?}");
809 }
810
811 #[test]
812 fn decode_rejects_untagged_nested_value() {
813 let err = from_json(json!({ "list": [5] }), &Type::List).unwrap_err();
815 assert!(matches!(err, Error::MalformedTaggedArg(_)), "got {err:?}");
816 }
817
818 use tx3_tir::model::assets::{AssetClass, CanonicalAssets};
823 use tx3_tir::model::core::Utxo;
824
825 fn sample_utxo_ref() -> UtxoRef {
826 UtxoRef {
827 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
828 .unwrap(),
829 index: 2,
830 }
831 }
832
833 #[test]
834 fn utxo_ref_to_json_format() {
835 let r = sample_utxo_ref();
836 let v = utxo_ref_to_json(&r);
837 assert_eq!(
838 v,
839 json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#2")
840 );
841 }
842
843 #[test]
844 fn arg_to_json_int() {
845 assert_eq!(arg_to_json(&ArgValue::Int(42)), json!(42));
846 }
847
848 #[test]
849 fn arg_to_json_bool() {
850 assert_eq!(arg_to_json(&ArgValue::Bool(true)), json!(true));
851 }
852
853 #[test]
854 fn arg_to_json_string() {
855 assert_eq!(
856 arg_to_json(&ArgValue::String("hello".into())),
857 json!("hello")
858 );
859 }
860
861 #[test]
862 fn arg_to_json_bytes() {
863 assert_eq!(
864 arg_to_json(&ArgValue::Bytes(b"hello".to_vec())),
865 json!("68656c6c6f")
866 );
867 }
868
869 #[test]
870 fn arg_to_json_address() {
871 assert_eq!(
872 arg_to_json(&ArgValue::Address(b"\x01\x02".to_vec())),
873 json!("0102")
874 );
875 }
876
877 #[test]
878 fn arg_to_json_utxo_ref() {
879 let r = sample_utxo_ref();
880 assert_eq!(
881 arg_to_json(&ArgValue::UtxoRef(r.clone())),
882 utxo_ref_to_json(&r)
883 );
884 }
885
886 #[test]
887 fn utxo_to_json_empty_assets() {
888 let utxo = Utxo {
889 r#ref: sample_utxo_ref(),
890 address: b"\xab\xcd".to_vec(),
891 assets: CanonicalAssets::empty(),
892 datum: None,
893 script: None,
894 };
895
896 let v = utxo_to_json(&utxo);
897 assert_eq!(v["assets"], json!({}));
898 assert_eq!(v["address"], json!("abcd"));
899 assert!(v["datum"].is_null());
900 assert!(v["script"].is_null());
901 }
902
903 #[test]
904 fn utxo_to_json_naked_assets() {
905 let utxo = Utxo {
906 r#ref: sample_utxo_ref(),
907 address: b"\x01".to_vec(),
908 assets: CanonicalAssets::from_naked_amount(5_000_000),
909 datum: None,
910 script: None,
911 };
912
913 let v = utxo_to_json(&utxo);
914 assert_eq!(v["assets"]["naked"], json!(5_000_000));
915 }
916
917 #[test]
918 fn utxo_to_json_defined_assets() {
919 let assets = CanonicalAssets::from_class_and_amount(
920 AssetClass::Defined(b"policy1".to_vec(), b"token1".to_vec()),
921 100,
922 );
923
924 let utxo = Utxo {
925 r#ref: sample_utxo_ref(),
926 address: b"\x01".to_vec(),
927 assets,
928 datum: None,
929 script: None,
930 };
931
932 let v = utxo_to_json(&utxo);
933 let key = format!("{}.{}", hex::encode(b"policy1"), hex::encode(b"token1"));
934 assert_eq!(v["assets"][&key], json!(100));
935 }
936}