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 #[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 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}