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
61#[derive(Debug, Deserialize, Serialize, Clone)]
62#[serde(rename_all = "lowercase")]
63pub enum BytesEncoding {
64 Base64,
65 Hex,
66}
67
68#[derive(Debug, Deserialize, Serialize, Clone)]
69pub struct BytesEnvelope {
70 #[serde(alias = "bytecode", alias = "payload")]
72 pub content: String,
73 #[serde(rename = "contentType", alias = "encoding")]
74 pub content_type: BytesEncoding,
75}
76
77#[derive(Debug, Deserialize, Serialize, Clone)]
80pub struct TirEnvelope {
81 #[serde(alias = "bytecode", alias = "payload")]
83 pub content: String,
84 pub encoding: BytesEncoding,
85 pub version: String,
86}
87
88impl BytesEnvelope {
89 pub fn from_hex(hex: &str) -> Result<Self, Error> {
90 Ok(Self {
91 content: hex.to_string(),
92 content_type: BytesEncoding::Hex,
93 })
94 }
95}
96
97impl From<BytesEnvelope> for Vec<u8> {
98 fn from(envelope: BytesEnvelope) -> Self {
99 match envelope.content_type {
100 BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(),
101 BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(),
102 }
103 }
104}
105
106impl From<TirEnvelope> for Vec<u8> {
107 fn from(envelope: TirEnvelope) -> Self {
108 match envelope.encoding {
109 BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(),
110 BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(),
111 }
112 }
113}
114
115impl TryFrom<TirEnvelope> for tx3_tir::encoding::AnyTir {
116 type Error = crate::Error;
117
118 fn try_from(envelope: TirEnvelope) -> Result<Self, Self::Error> {
119 let version = tx3_tir::encoding::TirVersion::try_from(envelope.version.as_str())?;
120 let bytes: Vec<u8> = envelope.into();
121 let tir = tx3_tir::encoding::from_bytes(&bytes, version)?;
122 Ok(tir)
123 }
124}
125
126impl From<tx3_tir::encoding::AnyTir> for TirEnvelope {
127 fn from(tir: tx3_tir::encoding::AnyTir) -> Self {
128 let (bytes, version) = match tir {
129 tx3_tir::encoding::AnyTir::V1Beta0(tx) => tx3_tir::encoding::to_bytes(&tx),
130 };
131 Self {
132 content: hex::encode(bytes),
133 encoding: BytesEncoding::Hex,
134 version: version.to_string(),
135 }
136 }
137}
138
139fn has_hex_prefix(s: &str) -> bool {
140 s.starts_with("0x")
141}
142
143pub fn string_to_bigint(s: String) -> Result<i128, Error> {
144 if has_hex_prefix(&s) {
145 let bytes = hex_to_bytes(&s)?;
146 let bytes = <[u8; 16]>::try_from(bytes)
147 .map_err(|x| Error::InvalidBytesForNumber(hex::encode(x)))?;
148 Ok(i128::from_be_bytes(bytes))
149 } else {
150 let i = i128::from_str_radix(&s, 10)
151 .map_err(|x| Error::InvalidBytesForNumber(x.to_string()))?;
152 Ok(i)
153 }
154}
155
156pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
157 let s = if has_hex_prefix(s) {
158 s.trim_start_matches("0x")
159 } else {
160 s
161 };
162
163 let out = hex::decode(s)?;
164
165 Ok(out)
166}
167
168pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
169 let out = base64::engine::general_purpose::STANDARD.decode(s)?;
170
171 Ok(out)
172}
173
174pub fn bech32_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
175 let (_, data) = bech32::decode(s)?;
176
177 Ok(data)
178}
179
180fn number_to_bigint(x: Number) -> Result<i128, Error> {
181 x.as_i128().ok_or(Error::NumberCantFit(x))
182}
183
184fn value_to_bigint(value: Value) -> Result<i128, Error> {
185 let out = match value {
186 Value::Number(n) => number_to_bigint(n)?,
187 Value::String(s) => string_to_bigint(s)?,
188 Value::Null => return Err(Error::ValueIsNull),
189 x => return Err(Error::ValueIsNotANumber(x)),
190 };
191
192 Ok(out)
193}
194
195fn value_to_bool(value: Value) -> Result<bool, Error> {
196 match value {
197 Value::Bool(b) => Ok(b),
198 Value::Number(n) if n == Number::from(0) => Ok(false),
199 Value::Number(n) if n == Number::from(1) => Ok(true),
200 Value::String(s) if s == "true" => Ok(true),
201 Value::String(s) if s == "false" => Ok(false),
202 x => Err(Error::ValueIsNotABool(x)),
203 }
204}
205
206fn value_to_bytes(value: Value) -> Result<Vec<u8>, Error> {
207 let out = match value {
208 Value::String(s) => hex_to_bytes(&s)?,
209 Value::Object(_) => {
210 let envelope: BytesEnvelope =
211 serde_json::from_value(value).map_err(Error::InvalidBytesEnvelope)?;
212
213 match envelope.content_type {
214 BytesEncoding::Base64 => base64_to_bytes(&envelope.content)?,
215 BytesEncoding::Hex => hex_to_bytes(&envelope.content)?,
216 }
217 }
218 x => return Err(Error::ValueIsNotBytes(x)),
219 };
220
221 Ok(out)
222}
223
224fn value_to_address(value: Value) -> Result<Vec<u8>, Error> {
225 let out = match value {
226 Value::String(s) => match bech32_to_bytes(&s) {
227 Ok(data) => data,
228 Err(_) => hex_to_bytes(&s)?,
229 },
230 x => return Err(Error::ValueIsNotAnAddress(x)),
231 };
232
233 Ok(out)
234}
235
236fn value_to_underfined(value: Value) -> Result<ArgValue, Error> {
237 match value {
238 Value::Bool(b) => Ok(ArgValue::Bool(b)),
239 Value::Number(x) => Ok(ArgValue::Int(number_to_bigint(x)?)),
240 Value::String(s) => Ok(ArgValue::String(s)),
241 x => Err(Error::CantInferTypeForValue(x)),
242 }
243}
244
245pub fn string_to_utxo_ref(s: &str) -> Result<UtxoRef, Error> {
246 let (txid, index) = s
247 .split_once('#')
248 .ok_or(Error::InvalidUtxoRef(s.to_string()))?;
249
250 let txid = hex::decode(txid).map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
251 let index = index
252 .parse()
253 .map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
254
255 Ok(UtxoRef { txid, index })
256}
257
258fn value_to_utxo_ref(value: Value) -> Result<UtxoRef, Error> {
259 match value {
260 Value::String(s) => string_to_utxo_ref(&s),
261 x => Err(Error::ValueIsNotUtxoRef(x)),
262 }
263}
264
265pub fn from_json(value: Value, target: &Type) -> Result<ArgValue, Error> {
266 match target {
267 Type::Int => {
268 let i = value_to_bigint(value)?;
269 Ok(ArgValue::Int(i))
270 }
271 Type::Bool => {
272 let b = value_to_bool(value)?;
273 Ok(ArgValue::Bool(b))
274 }
275 Type::Bytes => {
276 let b = value_to_bytes(value)?;
277 Ok(ArgValue::Bytes(b))
278 }
279 Type::Address => {
280 let a = value_to_address(value)?;
281 Ok(ArgValue::Address(a))
282 }
283 Type::UtxoRef => {
284 let x = value_to_utxo_ref(value)?;
285 Ok(ArgValue::UtxoRef(x))
286 }
287 Type::Undefined => value_to_underfined(value),
288 x => Err(Error::TargetTypeNotSupported(x.clone())),
289 }
290}
291
292pub fn utxo_ref_to_json(r: &UtxoRef) -> Value {
297 Value::String(format!("{}#{}", hex::encode(&r.txid), r.index))
298}
299
300pub fn arg_to_json(arg: &ArgValue) -> Value {
301 match arg {
302 ArgValue::Int(i) => serde_json::json!(i),
303 ArgValue::Bool(b) => Value::Bool(*b),
304 ArgValue::String(s) => Value::String(s.clone()),
305 ArgValue::Bytes(v) => Value::String(hex::encode(v)),
306 ArgValue::Address(v) => Value::String(hex::encode(v)),
307 ArgValue::UtxoRef(r) => utxo_ref_to_json(r),
308 ArgValue::UtxoSet(_) => Value::Null,
309 }
310}
311
312pub fn utxo_to_json(utxo: &Utxo) -> Value {
313 let assets: Map<String, Value> = utxo
314 .assets
315 .iter()
316 .map(|(class, amount)| (class.to_string(), serde_json::json!(amount)))
317 .collect();
318
319 serde_json::json!({
320 "ref": utxo_ref_to_json(&utxo.r#ref),
321 "address": hex::encode(&utxo.address),
322 "assets": assets,
323 "datum": utxo.datum,
324 "script": utxo.script,
325 })
326}
327
328fn parse_asset_class(key: &str) -> AssetClass {
329 if key == "naked" {
330 AssetClass::Naked
331 } else if let Some((policy, name)) = key.split_once('.') {
332 let policy_bytes = hex::decode(policy).unwrap_or_default();
333 let name_bytes = hex::decode(name).unwrap_or_default();
334 AssetClass::Defined(policy_bytes, name_bytes)
335 } else {
336 let name_bytes = hex::decode(key).unwrap_or_default();
337 AssetClass::Named(name_bytes)
338 }
339}
340
341fn assets_from_json(value: &Value) -> Result<CanonicalAssets, Error> {
342 let obj = value.as_object().ok_or(Error::ValueIsNotAString)?;
343
344 let mut assets = CanonicalAssets::empty();
345 for (key, amount_val) in obj {
346 let class = parse_asset_class(key);
347 let amount = value_to_bigint(amount_val.clone())?;
348 assets = assets + CanonicalAssets::from_class_and_amount(class, amount);
349 }
350
351 Ok(assets)
352}
353
354pub fn utxo_from_json(value: &Value) -> Result<Utxo, Error> {
355 let ref_str = value["ref"].as_str().ok_or(Error::ValueIsNotAString)?;
356 let utxo_ref = string_to_utxo_ref(ref_str)?;
357
358 let address = hex_to_bytes(value["address"].as_str().ok_or(Error::ValueIsNotAString)?)?;
359
360 let assets = assets_from_json(&value["assets"])?;
361
362 let datum = value
363 .get("datum")
364 .filter(|v| !v.is_null())
365 .map(|v| serde_json::from_value(v.clone()))
366 .transpose()
367 .map_err(|e| Error::InvalidBytesEnvelope(e))?;
368
369 let script = value
370 .get("script")
371 .filter(|v| !v.is_null())
372 .map(|v| serde_json::from_value(v.clone()))
373 .transpose()
374 .map_err(|e| Error::InvalidBytesEnvelope(e))?;
375
376 Ok(Utxo {
377 r#ref: utxo_ref,
378 address,
379 assets,
380 datum,
381 script,
382 })
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use serde_json::json;
389
390 fn partial_eq(a: ArgValue, b: ArgValue) -> bool {
392 match a {
393 ArgValue::Int(a) => match b {
394 ArgValue::Int(b) => dbg!(a) == dbg!(b),
395 _ => false,
396 },
397 ArgValue::Bool(a) => match b {
398 ArgValue::Bool(b) => a == b,
399 _ => false,
400 },
401 ArgValue::String(a) => match b {
402 ArgValue::String(b) => a == b,
403 _ => false,
404 },
405 ArgValue::Bytes(a) => match b {
406 ArgValue::Bytes(b) => a == b,
407 _ => false,
408 },
409 ArgValue::Address(a) => match b {
410 ArgValue::Address(b) => a == b,
411 _ => false,
412 },
413 ArgValue::UtxoSet(hash_set) => match b {
414 ArgValue::UtxoSet(b) => hash_set == b,
415 _ => false,
416 },
417 ArgValue::UtxoRef(utxo_ref) => match b {
418 ArgValue::UtxoRef(b) => utxo_ref == b,
419 _ => false,
420 },
421 }
422 }
423
424 fn assert_from_json(provided: Value, target: Type, expected: ArgValue) {
425 let value = from_json(provided, &target).unwrap();
426 assert!(partial_eq(value, expected));
427 }
428
429 #[test]
434 fn from_json_small_int() {
435 assert_from_json(json!(123456789), Type::Int, ArgValue::Int(123456789));
436 }
437
438 #[test]
439 fn from_json_negative_int() {
440 assert_from_json(json!(-123456789), Type::Int, ArgValue::Int(-123456789));
441 }
442
443 #[test]
444 fn from_json_big_int() {
445 assert_from_json(
446 json!("12345678901234567890"),
447 Type::Int,
448 ArgValue::Int(12345678901234567890),
449 );
450 }
451
452 #[test]
453 fn from_json_int_overflow() {
454 assert_from_json(
455 json!(i128::MIN.to_string()),
456 Type::Int,
457 ArgValue::Int(i128::MIN),
458 );
459 assert_from_json(
460 json!(i128::MAX.to_string()),
461 Type::Int,
462 ArgValue::Int(i128::MAX),
463 );
464 }
465
466 #[test]
467 fn from_json_bool() {
468 assert_from_json(json!(true), Type::Bool, ArgValue::Bool(true));
469 assert_from_json(json!(false), Type::Bool, ArgValue::Bool(false));
470 }
471
472 #[test]
473 fn from_json_bool_number() {
474 assert_from_json(json!(1), Type::Bool, ArgValue::Bool(true));
475 assert_from_json(json!(0), Type::Bool, ArgValue::Bool(false));
476 }
477
478 #[test]
479 fn from_json_bool_string() {
480 assert_from_json(json!("true"), Type::Bool, ArgValue::Bool(true));
481 assert_from_json(json!("false"), Type::Bool, ArgValue::Bool(false));
482 }
483
484 #[test]
485 fn from_json_bytes() {
486 assert_from_json(
487 json!(hex::encode("hello")),
488 Type::Bytes,
489 ArgValue::Bytes(b"hello".to_vec()),
490 );
491
492 assert_from_json(
493 json!(format!("0x{}", hex::encode("hello"))),
494 Type::Bytes,
495 ArgValue::Bytes(b"hello".to_vec()),
496 );
497 }
498
499 #[test]
500 fn from_json_bytes_base64() {
501 let json = json!(BytesEnvelope {
502 content: "aGVsbG8=".to_string(),
503 content_type: BytesEncoding::Base64,
504 });
505
506 assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
507 }
508
509 #[test]
510 fn from_json_bytes_hex() {
511 let json = json!(BytesEnvelope {
512 content: "68656c6c6f".to_string(),
513 content_type: BytesEncoding::Hex,
514 });
515
516 assert_from_json(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
517 }
518
519 #[test]
520 fn from_json_address() {
521 assert_from_json(
522 json!(hex::encode("abc123")),
523 Type::Address,
524 ArgValue::Address(b"abc123".to_vec()),
525 );
526 }
527
528 #[test]
529 fn from_json_address_bech32() {
530 let json = json!("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8");
531 let bytes =
532 hex::decode("619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e").unwrap();
533 assert_from_json(json, Type::Address, ArgValue::Address(bytes));
534 }
535
536 #[test]
537 fn from_json_utxo_ref() {
538 let json = json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#0");
539
540 let utxo_ref = UtxoRef {
541 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
542 .unwrap(),
543 index: 0,
544 };
545
546 assert_from_json(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref));
547 }
548
549 use tx3_tir::model::assets::{AssetClass, CanonicalAssets};
554 use tx3_tir::model::core::Utxo;
555
556 fn sample_utxo_ref() -> UtxoRef {
557 UtxoRef {
558 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
559 .unwrap(),
560 index: 2,
561 }
562 }
563
564 #[test]
565 fn utxo_ref_to_json_format() {
566 let r = sample_utxo_ref();
567 let v = utxo_ref_to_json(&r);
568 assert_eq!(
569 v,
570 json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#2")
571 );
572 }
573
574 #[test]
575 fn arg_to_json_int() {
576 assert_eq!(arg_to_json(&ArgValue::Int(42)), json!(42));
577 }
578
579 #[test]
580 fn arg_to_json_bool() {
581 assert_eq!(arg_to_json(&ArgValue::Bool(true)), json!(true));
582 }
583
584 #[test]
585 fn arg_to_json_string() {
586 assert_eq!(
587 arg_to_json(&ArgValue::String("hello".into())),
588 json!("hello")
589 );
590 }
591
592 #[test]
593 fn arg_to_json_bytes() {
594 assert_eq!(
595 arg_to_json(&ArgValue::Bytes(b"hello".to_vec())),
596 json!("68656c6c6f")
597 );
598 }
599
600 #[test]
601 fn arg_to_json_address() {
602 assert_eq!(
603 arg_to_json(&ArgValue::Address(b"\x01\x02".to_vec())),
604 json!("0102")
605 );
606 }
607
608 #[test]
609 fn arg_to_json_utxo_ref() {
610 let r = sample_utxo_ref();
611 assert_eq!(
612 arg_to_json(&ArgValue::UtxoRef(r.clone())),
613 utxo_ref_to_json(&r)
614 );
615 }
616
617 #[test]
618 fn utxo_to_json_empty_assets() {
619 let utxo = Utxo {
620 r#ref: sample_utxo_ref(),
621 address: b"\xab\xcd".to_vec(),
622 assets: CanonicalAssets::empty(),
623 datum: None,
624 script: None,
625 };
626
627 let v = utxo_to_json(&utxo);
628 assert_eq!(v["assets"], json!({}));
629 assert_eq!(v["address"], json!("abcd"));
630 assert!(v["datum"].is_null());
631 assert!(v["script"].is_null());
632 }
633
634 #[test]
635 fn utxo_to_json_naked_assets() {
636 let utxo = Utxo {
637 r#ref: sample_utxo_ref(),
638 address: b"\x01".to_vec(),
639 assets: CanonicalAssets::from_naked_amount(5_000_000),
640 datum: None,
641 script: None,
642 };
643
644 let v = utxo_to_json(&utxo);
645 assert_eq!(v["assets"]["naked"], json!(5_000_000));
646 }
647
648 #[test]
649 fn utxo_to_json_defined_assets() {
650 let assets = CanonicalAssets::from_class_and_amount(
651 AssetClass::Defined(b"policy1".to_vec(), b"token1".to_vec()),
652 100,
653 );
654
655 let utxo = Utxo {
656 r#ref: sample_utxo_ref(),
657 address: b"\x01".to_vec(),
658 assets,
659 datum: None,
660 script: None,
661 };
662
663 let v = utxo_to_json(&utxo);
664 let key = format!("{}.{}", hex::encode(b"policy1"), hex::encode(b"token1"));
665 assert_eq!(v["assets"][&key], json!(100));
666 }
667}