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