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 #[serde(alias = "bytecode", alias = "payload")]
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
93fn has_hex_prefix(s: &str) -> bool {
94 s.starts_with("0x")
95}
96
97pub fn string_to_bigint(s: String) -> Result<i128, Error> {
98 if has_hex_prefix(&s) {
99 let bytes = hex_to_bytes(&s)?;
100 let bytes = <[u8; 16]>::try_from(bytes)
101 .map_err(|x| Error::InvalidBytesForNumber(hex::encode(x)))?;
102 Ok(i128::from_be_bytes(bytes))
103 } else {
104 let i = i128::from_str_radix(&s, 10)
105 .map_err(|x| Error::InvalidBytesForNumber(x.to_string()))?;
106 Ok(i)
107 }
108}
109
110pub fn hex_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
111 let s = if has_hex_prefix(s) {
112 s.trim_start_matches("0x")
113 } else {
114 s
115 };
116
117 let out = hex::decode(s)?;
118
119 Ok(out)
120}
121
122pub fn base64_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
123 let out = base64::engine::general_purpose::STANDARD.decode(s)?;
124
125 Ok(out)
126}
127
128pub fn bech32_to_bytes(s: &str) -> Result<Vec<u8>, Error> {
129 let (_, data) = bech32::decode(s)?;
130
131 Ok(data)
132}
133
134fn number_to_bigint(x: Number) -> Result<i128, Error> {
135 x.as_i128().ok_or(Error::NumberCantFit(x))
136}
137
138fn value_to_bigint(value: Value) -> Result<i128, Error> {
139 let out = match value {
140 Value::Number(n) => number_to_bigint(n)?,
141 Value::String(s) => string_to_bigint(s)?,
142 Value::Null => return Err(Error::ValueIsNull),
143 x => return Err(Error::ValueIsNotANumber(x)),
144 };
145
146 Ok(out)
147}
148
149fn value_to_bool(value: Value) -> Result<bool, Error> {
150 match value {
151 Value::Bool(b) => Ok(b),
152 Value::Number(n) if n == Number::from(0) => Ok(false),
153 Value::Number(n) if n == Number::from(1) => Ok(true),
154 Value::String(s) if s == "true" => Ok(true),
155 Value::String(s) if s == "false" => Ok(false),
156 x => Err(Error::ValueIsNotABool(x)),
157 }
158}
159
160fn value_to_bytes(value: Value) -> Result<Vec<u8>, Error> {
161 let out = match value {
162 Value::String(s) => hex_to_bytes(&s)?,
163 Value::Object(_) => {
164 let envelope: BytesEnvelope =
165 serde_json::from_value(value).map_err(Error::InvalidBytesEnvelope)?;
166
167 match envelope.encoding {
168 BytesEncoding::Base64 => base64_to_bytes(&envelope.content)?,
169 BytesEncoding::Hex => hex_to_bytes(&envelope.content)?,
170 }
171 }
172 x => return Err(Error::ValueIsNotBytes(x)),
173 };
174
175 Ok(out)
176}
177
178fn value_to_address(value: Value) -> Result<Vec<u8>, Error> {
179 let out = match value {
180 Value::String(s) => match bech32_to_bytes(&s) {
181 Ok(data) => data,
182 Err(_) => hex_to_bytes(&s)?,
183 },
184 x => return Err(Error::ValueIsNotAnAddress(x)),
185 };
186
187 Ok(out)
188}
189
190fn value_to_underfined(value: Value) -> Result<ArgValue, Error> {
191 match value {
192 Value::Bool(b) => Ok(ArgValue::Bool(b)),
193 Value::Number(x) => Ok(ArgValue::Int(number_to_bigint(x)?)),
194 Value::String(s) => Ok(ArgValue::String(s)),
195 x => Err(Error::CantInferTypeForValue(x)),
196 }
197}
198
199fn string_to_utxo_ref(s: &str) -> Result<UtxoRef, Error> {
200 let (txid, index) = s
201 .split_once('#')
202 .ok_or(Error::InvalidUtxoRef(s.to_string()))?;
203
204 let txid = hex::decode(txid).map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
205 let index = index
206 .parse()
207 .map_err(|_| Error::InvalidUtxoRef(s.to_string()))?;
208
209 Ok(UtxoRef { txid, index })
210}
211
212fn value_to_utxo_ref(value: Value) -> Result<UtxoRef, Error> {
213 match value {
214 Value::String(s) => string_to_utxo_ref(&s),
215 x => Err(Error::ValueIsNotUtxoRef(x)),
216 }
217}
218
219pub fn from_json(value: Value, target: &Type) -> Result<ArgValue, Error> {
220 match target {
221 Type::Int => {
222 let i = value_to_bigint(value)?;
223 Ok(ArgValue::Int(i))
224 }
225 Type::Bool => {
226 let b = value_to_bool(value)?;
227 Ok(ArgValue::Bool(b))
228 }
229 Type::Bytes => {
230 let b = value_to_bytes(value)?;
231 Ok(ArgValue::Bytes(b))
232 }
233 Type::Address => {
234 let a = value_to_address(value)?;
235 Ok(ArgValue::Address(a))
236 }
237 Type::UtxoRef => {
238 let x = value_to_utxo_ref(value)?;
239 Ok(ArgValue::UtxoRef(x))
240 }
241 Type::Undefined => value_to_underfined(value),
242 x => Err(Error::TargetTypeNotSupported(x.clone())),
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use serde_json::json;
250
251 fn partial_eq(a: ArgValue, b: ArgValue) -> bool {
253 match a {
254 ArgValue::Int(a) => match b {
255 ArgValue::Int(b) => dbg!(a) == dbg!(b),
256 _ => false,
257 },
258 ArgValue::Bool(a) => match b {
259 ArgValue::Bool(b) => a == b,
260 _ => false,
261 },
262 ArgValue::String(a) => match b {
263 ArgValue::String(b) => a == b,
264 _ => false,
265 },
266 ArgValue::Bytes(a) => match b {
267 ArgValue::Bytes(b) => a == b,
268 _ => false,
269 },
270 ArgValue::Address(a) => match b {
271 ArgValue::Address(b) => a == b,
272 _ => false,
273 },
274 ArgValue::UtxoSet(hash_set) => match b {
275 ArgValue::UtxoSet(b) => hash_set == b,
276 _ => false,
277 },
278 ArgValue::UtxoRef(utxo_ref) => match b {
279 ArgValue::UtxoRef(b) => utxo_ref == b,
280 _ => false,
281 },
282 }
283 }
284
285 fn json_to_value_test(provided: Value, target: Type, expected: ArgValue) {
286 let value = from_json(provided, &target).unwrap();
287 assert!(partial_eq(value, expected));
288 }
289
290 #[test]
291 fn test_round_trip_small_int() {
292 json_to_value_test(json!(123456789), Type::Int, ArgValue::Int(123456789));
293 }
294
295 #[test]
296 fn test_round_trip_negative_int() {
297 json_to_value_test(json!(-123456789), Type::Int, ArgValue::Int(-123456789));
298 }
299
300 #[test]
301 fn test_round_trip_big_int() {
302 json_to_value_test(
303 json!("12345678901234567890"),
304 Type::Int,
305 ArgValue::Int(12345678901234567890),
306 );
307 }
308
309 #[test]
310 fn test_round_trip_int_overflow() {
311 json_to_value_test(
312 json!(i128::MIN.to_string()),
313 Type::Int,
314 ArgValue::Int(i128::MIN),
315 );
316 json_to_value_test(
317 json!(i128::MAX.to_string()),
318 Type::Int,
319 ArgValue::Int(i128::MAX),
320 );
321 }
322
323 #[test]
324 fn test_round_trip_bool() {
325 json_to_value_test(json!(true), Type::Bool, ArgValue::Bool(true));
326 json_to_value_test(json!(false), Type::Bool, ArgValue::Bool(false));
327 }
328
329 #[test]
330 fn test_round_trip_bool_number() {
331 json_to_value_test(json!(1), Type::Bool, ArgValue::Bool(true));
332 json_to_value_test(json!(0), Type::Bool, ArgValue::Bool(false));
333 }
334
335 #[test]
336 fn test_round_trip_bool_string() {
337 json_to_value_test(json!("true"), Type::Bool, ArgValue::Bool(true));
338 json_to_value_test(json!("false"), Type::Bool, ArgValue::Bool(false));
339 }
340
341 #[test]
342 fn test_round_trip_bytes() {
343 json_to_value_test(
344 json!(hex::encode("hello")),
345 Type::Bytes,
346 ArgValue::Bytes(b"hello".to_vec()),
347 );
348
349 json_to_value_test(
350 json!(format!("0x{}", hex::encode("hello"))),
351 Type::Bytes,
352 ArgValue::Bytes(b"hello".to_vec()),
353 );
354 }
355
356 #[test]
357 fn test_round_trip_bytes_base64() {
358 let json = json!(BytesEnvelope {
359 content: "aGVsbG8=".to_string(),
360 encoding: BytesEncoding::Base64,
361 });
362
363 json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
364 }
365
366 #[test]
367 fn test_round_trip_bytes_hex() {
368 let json = json!(BytesEnvelope {
369 content: "68656c6c6f".to_string(),
370 encoding: BytesEncoding::Hex,
371 });
372
373 json_to_value_test(json, Type::Bytes, ArgValue::Bytes(b"hello".to_vec()));
374 }
375
376 #[test]
377 fn test_round_trip_address() {
378 json_to_value_test(
379 json!(hex::encode("abc123")),
380 Type::Address,
381 ArgValue::Address(b"abc123".to_vec()),
382 );
383 }
384
385 #[test]
386 fn test_round_trip_address_bech32() {
387 let json = json!("addr1vx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzers66hrl8");
388 let bytes =
389 hex::decode("619493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e").unwrap();
390 json_to_value_test(json, Type::Address, ArgValue::Address(bytes));
391 }
392
393 #[test]
394 fn test_round_trip_utxo_ref() {
395 let json = json!("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef#0");
396
397 let utxo_ref = UtxoRef {
398 txid: hex::decode("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
399 .unwrap(),
400 index: 0,
401 };
402
403 json_to_value_test(json, Type::UtxoRef, ArgValue::UtxoRef(utxo_ref));
404 }
405}