Skip to main content

tx3_resolver/
interop.rs

1use base64::Engine as _;
2use serde::{Deserialize, Serialize};
3use serde_json::{Number, Value};
4use thiserror::Error;
5
6use tx3_tir::model::core::{Type, UtxoRef};
7pub use tx3_tir::reduce::ArgValue;
8
9#[derive(Debug, Error)]
10pub enum Error {
11    #[error("invalid base64: {0}")]
12    InvalidBase64(#[from] base64::DecodeError),
13
14    #[error("invalid hex: {0}")]
15    InvalidHex(#[from] hex::FromHexError),
16
17    #[error("invalid bech32: {0}")]
18    InvalidBech32(#[from] bech32::DecodeError),
19
20    #[error("value is not a valid number: {0}")]
21    InvalidBytesForNumber(String),
22
23    #[error("value is null")]
24    ValueIsNull,
25
26    #[error("can't infer type for value: {0}")]
27    CantInferTypeForValue(Value),
28
29    #[error("value is not a number: {0}")]
30    ValueIsNotANumber(Value),
31
32    #[error("value can't fit: {0}")]
33    NumberCantFit(Number),
34
35    #[error("value is not a bool: {0}")]
36    ValueIsNotABool(Value),
37
38    #[error("value is not a string")]
39    ValueIsNotAString,
40
41    #[error("value is not bytes: {0}")]
42    ValueIsNotBytes(Value),
43
44    #[error("value is not a utxo ref: {0}")]
45    ValueIsNotUtxoRef(Value),
46
47    #[error("invalid bytes envelope: {0}")]
48    InvalidBytesEnvelope(serde_json::Error),
49
50    #[error("value is not an address: {0}")]
51    ValueIsNotAnAddress(Value),
52
53    #[error("invalid utxo ref: {0}")]
54    InvalidUtxoRef(String),
55
56    #[error("target type not supported: {0:?}")]
57    TargetTypeNotSupported(Type),
58}
59
60#[derive(Debug, Deserialize, Serialize, Clone)]
61#[serde(rename_all = "lowercase")]
62pub enum BytesEncoding {
63    Base64,
64    Hex,
65}
66
67#[derive(Debug, Deserialize, Serialize, Clone)]
68pub struct BytesEnvelope {
69    // Aliases for backward compatibility
70    #[serde(alias = "bytecode", alias = "payload")]
71    pub content: String,
72    #[serde(rename = "contentType", alias = "encoding")]
73    pub content_type: BytesEncoding,
74}
75
76impl BytesEnvelope {
77    pub fn from_hex(hex: &str) -> Result<Self, Error> {
78        Ok(Self {
79            content: hex.to_string(),
80            content_type: BytesEncoding::Hex,
81        })
82    }
83}
84
85impl From<BytesEnvelope> for Vec<u8> {
86    fn from(envelope: BytesEnvelope) -> Self {
87        match envelope.content_type {
88            BytesEncoding::Base64 => base64_to_bytes(&envelope.content).unwrap(),
89            BytesEncoding::Hex => hex_to_bytes(&envelope.content).unwrap(),
90        }
91    }
92}
93
94fn has_hex_prefix(s: &str) -> bool {
95    s.starts_with("0x")
96}
97
98pub fn string_to_bigint(s: String) -> Result<i128, Error> {
99    if has_hex_prefix(&s) {
100        let bytes = hex_to_bytes(&s)?;
101        let bytes = <[u8; 16]>::try_from(bytes)
102            .map_err(|x| Error::InvalidBytesForNumber(hex::encode(x)))?;
103        Ok(i128::from_be_bytes(bytes))
104    } else {
105        let i = i128::from_str_radix(&s, 10)
106            .map_err(|x| Error::InvalidBytesForNumber(x.to_string()))?;
107        Ok(i)
108    }
109}
110
111pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
112    let s = if has_hex_prefix(s) {
113        s.trim_start_matches("0x")
114    } else {
115        s
116    };
117
118    let out = hex::decode(s)?;
119
120    Ok(out)
121}
122
123pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
124    let out = base64::engine::general_purpose::STANDARD.decode(s)?;
125
126    Ok(out)
127}
128
129pub fn bech32_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
130    let (_, data) = bech32::decode(s)?;
131
132    Ok(data)
133}
134
135fn number_to_bigint(x: Number) -> Result<i128, Error> {
136    x.as_i128().ok_or(Error::NumberCantFit(x))
137}
138
139fn value_to_bigint(value: Value) -> Result<i128, Error> {
140    let out = match value {
141        Value::Number(n) => number_to_bigint(n)?,
142        Value::String(s) => string_to_bigint(s)?,
143        Value::Null => return Err(Error::ValueIsNull),
144        x => return Err(Error::ValueIsNotANumber(x)),
145    };
146
147    Ok(out)
148}
149
150fn value_to_bool(value: Value) -> Result<bool, Error> {
151    match value {
152        Value::Bool(b) => Ok(b),
153        Value::Number(n) if n == Number::from(0) => Ok(false),
154        Value::Number(n) if n == Number::from(1) => Ok(true),
155        Value::String(s) if s == "true" => Ok(true),
156        Value::String(s) if s == "false" => Ok(false),
157        x => Err(Error::ValueIsNotABool(x)),
158    }
159}
160
161fn value_to_bytes(value: Value) -> Result<Vec<u8>, Error> {
162    let out = match value {
163        Value::String(s) => hex_to_bytes(&s)?,
164        Value::Object(_) => {
165            let envelope: BytesEnvelope =
166                serde_json::from_value(value).map_err(Error::InvalidBytesEnvelope)?;
167
168            match envelope.content_type {
169                BytesEncoding::Base64 => base64_to_bytes(&envelope.content)?,
170                BytesEncoding::Hex => hex_to_bytes(&envelope.content)?,
171            }
172        }
173        x => return Err(Error::ValueIsNotBytes(x)),
174    };
175
176    Ok(out)
177}
178
179fn value_to_address(value: Value) -> Result<Vec<u8>, Error> {
180    let out = match value {
181        Value::String(s) => match bech32_to_bytes(&s) {
182            Ok(data) => data,
183            Err(_) => hex_to_bytes(&s)?,
184        },
185        x => return Err(Error::ValueIsNotAnAddress(x)),
186    };
187
188    Ok(out)
189}
190
191fn value_to_underfined(value: Value) -> Result<ArgValue, Error> {
192    match value {
193        Value::Bool(b) => Ok(ArgValue::Bool(b)),
194        Value::Number(x) => Ok(ArgValue::Int(number_to_bigint(x)?)),
195        Value::String(s) => Ok(ArgValue::String(s)),
196        x => Err(Error::CantInferTypeForValue(x)),
197    }
198}
199
200fn string_to_utxo_ref(s: &str) -> Result<UtxoRef, Error> {
201    let (txid, index) = s
202        .split_once('#')
203        .ok_or(Error::InvalidUtxoRef(s.to_string()))?;
204
205    let txid = hex::decode(txid).map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
206    let index = index
207        .parse()
208        .map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
209
210    Ok(UtxoRef { txid, index })
211}
212
213fn value_to_utxo_ref(value: Value) -> Result<UtxoRef, Error> {
214    match value {
215        Value::String(s) => string_to_utxo_ref(&s),
216        x => Err(Error::ValueIsNotUtxoRef(x)),
217    }
218}
219
220pub fn from_json(value: Value, target: &Type) -> Result<ArgValue, Error> {
221    match target {
222        Type::Int => {
223            let i = value_to_bigint(value)?;
224            Ok(ArgValue::Int(i))
225        }
226        Type::Bool => {
227            let b = value_to_bool(value)?;
228            Ok(ArgValue::Bool(b))
229        }
230        Type::Bytes => {
231            let b = value_to_bytes(value)?;
232            Ok(ArgValue::Bytes(b))
233        }
234        Type::Address => {
235            let a = value_to_address(value)?;
236            Ok(ArgValue::Address(a))
237        }
238        Type::UtxoRef => {
239            let x = value_to_utxo_ref(value)?;
240            Ok(ArgValue::UtxoRef(x))
241        }
242        Type::Undefined => value_to_underfined(value),
243        x => Err(Error::TargetTypeNotSupported(x.clone())),
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use serde_json::json;
251
252    // TODO: derive PartialEq in upstream tx3-lang
253    fn partial_eq(a: ArgValue, b: ArgValue) -> bool {
254        match a {
255            ArgValue::Int(a) => match b {
256                ArgValue::Int(b) => dbg!(a) == dbg!(b),
257                _ => false,
258            },
259            ArgValue::Bool(a) => match b {
260                ArgValue::Bool(b) => a == b,
261                _ => false,
262            },
263            ArgValue::String(a) => match b {
264                ArgValue::String(b) => a == b,
265                _ => false,
266            },
267            ArgValue::Bytes(a) => match b {
268                ArgValue::Bytes(b) => a == b,
269                _ => false,
270            },
271            ArgValue::Address(a) => match b {
272                ArgValue::Address(b) => a == b,
273                _ => false,
274            },
275            ArgValue::UtxoSet(hash_set) => match b {
276                ArgValue::UtxoSet(b) => hash_set == b,
277                _ => false,
278            },
279            ArgValue::UtxoRef(utxo_ref) => match b {
280                ArgValue::UtxoRef(b) => utxo_ref == b,
281                _ => false,
282            },
283        }
284    }
285
286    fn json_to_value_test(provided: Value, target: Type, expected: ArgValue) {
287        let value = from_json(provided, &target).unwrap();
288        assert!(partial_eq(value, expected));
289    }
290
291    #[test]
292    fn test_round_trip_small_int() {
293        json_to_value_test(json!(123456789), Type::Int, ArgValue::Int(123456789));
294    }
295
296    #[test]
297    fn test_round_trip_negative_int() {
298        json_to_value_test(json!(-123456789), Type::Int, ArgValue::Int(-123456789));
299    }
300
301    #[test]
302    fn test_round_trip_big_int() {
303        json_to_value_test(
304            json!("12345678901234567890"),
305            Type::Int,
306            ArgValue::Int(12345678901234567890),
307        );
308    }
309
310    #[test]
311    fn test_round_trip_int_overflow() {
312        json_to_value_test(
313            json!(i128::MIN.to_string()),
314            Type::Int,
315            ArgValue::Int(i128::MIN),
316        );
317        json_to_value_test(
318            json!(i128::MAX.to_string()),
319            Type::Int,
320            ArgValue::Int(i128::MAX),
321        );
322    }
323
324    #[test]
325    fn test_round_trip_bool() {
326        json_to_value_test(json!(true), Type::Bool, ArgValue::Bool(true));
327        json_to_value_test(json!(false), Type::Bool, ArgValue::Bool(false));
328    }
329
330    #[test]
331    fn test_round_trip_bool_number() {
332        json_to_value_test(json!(1), Type::Bool, ArgValue::Bool(true));
333        json_to_value_test(json!(0), Type::Bool, ArgValue::Bool(false));
334    }
335
336    #[test]
337    fn test_round_trip_bool_string() {
338        json_to_value_test(json!("true"), Type::Bool, ArgValue::Bool(true));
339        json_to_value_test(json!("false"), Type::Bool, ArgValue::Bool(false));
340    }
341
342    #[test]
343    fn test_round_trip_bytes() {
344        json_to_value_test(
345            json!(hex::encode("hello")),
346            Type::Bytes,
347            ArgValue::Bytes(b"hello".to_vec()),
348        );
349
350        json_to_value_test(
351            json!(format!("0x{}", hex::encode("hello"))),
352            Type::Bytes,
353            ArgValue::Bytes(b"hello".to_vec()),
354        );
355    }
356
357    #[test]
358    fn test_round_trip_bytes_base64() {
359        let json = json!(BytesEnvelope {
360            content: "aGVsbG8=".to_string(),
361            content_type: BytesEncoding::Base64,
362        });
363
364        json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
365    }
366
367    #[test]
368    fn test_round_trip_bytes_hex() {
369        let json = json!(BytesEnvelope {
370            content: "68656c6c6f".to_string(),
371            content_type: BytesEncoding::Hex,
372        });
373
374        json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
375    }
376
377    #[test]
378    fn test_round_trip_address() {
379        json_to_value_test(
380            json!(hex::encode("abc123")),
381            Type::Address,
382            ArgValue::Address(b"abc123".to_vec()),
383        );
384    }
385
386    #[test]
387    fn test_round_trip_address_bech32() {
388        let json = json!("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8");
389        let bytes =
390            hex::decode("619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e").unwrap();
391        json_to_value_test(json, Type::Address, ArgValue::Address(bytes));
392    }
393
394    #[test]
395    fn test_round_trip_utxo_ref() {
396        let json = json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#0");
397
398        let utxo_ref = UtxoRef {
399            txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
400                .unwrap(),
401            index: 0,
402        };
403
404        json_to_value_test(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref));
405    }
406}