tx3_resolver/
interop.rs

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