1use serde::Deserialize;
4use std::collections::HashMap;
5use super::anf_interpreter::ANFProgram;
6
7#[derive(Debug, Clone)]
13pub struct TransactionData {
14 pub txid: String,
15 pub version: u32,
16 pub inputs: Vec<TxInput>,
17 pub outputs: Vec<TxOutput>,
18 pub locktime: u32,
19 pub raw: Option<String>,
20}
21
22pub type Transaction = TransactionData;
24
25#[derive(Debug, Clone)]
27pub struct TxInput {
28 pub txid: String,
29 pub output_index: u32,
30 pub script: String,
31 pub sequence: u32,
32}
33
34#[derive(Debug, Clone)]
36pub struct TxOutput {
37 pub satoshis: i64,
38 pub script: String,
39}
40
41#[derive(Debug, Clone)]
43pub struct Utxo {
44 pub txid: String,
45 pub output_index: u32,
46 pub satoshis: i64,
47 pub script: String,
48}
49
50#[derive(Debug, Clone)]
56pub struct DeployOptions {
57 pub satoshis: i64,
58 pub change_address: Option<String>,
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct CallOptions {
64 pub satoshis: Option<i64>,
66 pub change_address: Option<String>,
67 pub new_state: Option<HashMap<String, SdkValue>>,
69 pub outputs: Option<Vec<OutputSpec>>,
72 pub additional_contract_inputs: Option<Vec<Utxo>>,
76 pub additional_contract_input_args: Option<Vec<Vec<SdkValue>>>,
80 pub change_pub_key: Option<String>,
83 pub terminal_outputs: Option<Vec<TerminalOutput>>,
89}
90
91#[derive(Debug, Clone)]
93pub struct TerminalOutput {
94 pub script_hex: String,
95 pub satoshis: i64,
96}
97
98#[derive(Debug, Clone)]
100pub struct OutputSpec {
101 pub satoshis: i64,
102 pub state: HashMap<String, SdkValue>,
103}
104
105#[derive(Debug, Clone, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct RunarArtifact {
113 pub version: String,
114 pub contract_name: String,
115 pub abi: Abi,
116 pub script: String,
117 #[serde(default)]
118 pub state_fields: Option<Vec<StateField>>,
119 #[serde(default)]
120 pub constructor_slots: Option<Vec<ConstructorSlot>>,
121 #[serde(default, rename = "codeSeparatorIndex")]
122 pub code_separator_index: Option<usize>,
123 #[serde(default, rename = "codeSeparatorIndices")]
124 pub code_separator_indices: Option<Vec<usize>>,
125 #[serde(default)]
126 pub anf: Option<ANFProgram>,
127}
128
129#[derive(Debug, Clone, Deserialize)]
131pub struct Abi {
132 pub constructor: AbiConstructor,
133 pub methods: Vec<AbiMethod>,
134}
135
136#[derive(Debug, Clone, Deserialize)]
138pub struct AbiConstructor {
139 pub params: Vec<AbiParam>,
140}
141
142#[derive(Debug, Clone, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct AbiMethod {
146 pub name: String,
147 pub params: Vec<AbiParam>,
148 pub is_public: bool,
149 #[serde(default)]
150 pub is_terminal: Option<bool>,
151}
152
153#[derive(Debug, Clone, Deserialize)]
155pub struct AbiParam {
156 pub name: String,
157 #[serde(rename = "type")]
158 pub param_type: String,
159}
160
161#[derive(Debug, Clone, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct StateField {
165 pub name: String,
166 #[serde(rename = "type")]
167 pub field_type: String,
168 pub index: usize,
169 #[serde(default)]
174 pub initial_value: Option<serde_json::Value>,
175}
176
177#[derive(Debug, Clone, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct ConstructorSlot {
181 pub param_index: usize,
182 pub byte_offset: usize,
183}
184
185#[derive(Debug, Clone, PartialEq)]
191pub enum SdkValue {
192 Int(i64),
194 BigInt(num_bigint::BigInt),
196 Bool(bool),
198 Bytes(String),
200 Auto,
204}
205
206impl SdkValue {
207 pub fn as_int(&self) -> i64 {
210 match self {
211 SdkValue::Int(n) => *n,
212 SdkValue::BigInt(n) => {
213 use num_bigint::ToBigInt;
214 let min = i64::MIN.to_bigint().unwrap();
215 let max = i64::MAX.to_bigint().unwrap();
216 if *n >= min && *n <= max {
217 n.to_string().parse::<i64>().unwrap()
219 } else {
220 panic!("SdkValue::as_int: BigInt value {} exceeds i64 range", n)
221 }
222 }
223 _ => panic!("SdkValue::as_int called on non-numeric variant"),
224 }
225 }
226
227 pub fn as_bool(&self) -> bool {
229 match self {
230 SdkValue::Bool(b) => *b,
231 _ => panic!("SdkValue::as_bool called on non-Bool variant"),
232 }
233 }
234
235 pub fn as_bytes(&self) -> &str {
237 match self {
238 SdkValue::Bytes(s) => s,
239 _ => panic!("SdkValue::as_bytes called on non-Bytes variant"),
240 }
241 }
242}
243
244#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
257 fn utxo_creation_and_field_access() {
258 let utxo = Utxo {
259 txid: "aa".repeat(32),
260 output_index: 0,
261 satoshis: 100_000,
262 script: "76a914".to_string(),
263 };
264 assert_eq!(utxo.txid, "aa".repeat(32));
265 assert_eq!(utxo.output_index, 0);
266 assert_eq!(utxo.satoshis, 100_000);
267 assert_eq!(utxo.script, "76a914");
268 }
269
270 #[test]
271 fn utxo_clone() {
272 let utxo = Utxo {
273 txid: "bb".repeat(32),
274 output_index: 1,
275 satoshis: 50_000,
276 script: "51".to_string(),
277 };
278 let cloned = utxo.clone();
279 assert_eq!(cloned.txid, utxo.txid);
280 assert_eq!(cloned.output_index, utxo.output_index);
281 assert_eq!(cloned.satoshis, utxo.satoshis);
282 assert_eq!(cloned.script, utxo.script);
283 }
284
285 #[test]
290 fn transaction_data_construction_defaults() {
291 let tx = TransactionData {
292 txid: "cc".repeat(32),
293 version: 1,
294 inputs: vec![],
295 outputs: vec![],
296 locktime: 0,
297 raw: None,
298 };
299 assert_eq!(tx.txid, "cc".repeat(32));
300 assert_eq!(tx.version, 1);
301 assert!(tx.inputs.is_empty());
302 assert!(tx.outputs.is_empty());
303 assert_eq!(tx.locktime, 0);
304 assert!(tx.raw.is_none());
305 }
306
307 #[test]
308 fn transaction_data_with_inputs_and_outputs() {
309 let tx = TransactionData {
310 txid: "dd".repeat(32),
311 version: 1,
312 inputs: vec![TxInput {
313 txid: "ee".repeat(32),
314 output_index: 0,
315 script: "00".to_string(),
316 sequence: 0xffffffff,
317 }],
318 outputs: vec![TxOutput {
319 satoshis: 75_000,
320 script: "76a914".to_string(),
321 }],
322 locktime: 500_000,
323 raw: Some("0100000001...".to_string()),
324 };
325 assert_eq!(tx.inputs.len(), 1);
326 assert_eq!(tx.inputs[0].sequence, 0xffffffff);
327 assert_eq!(tx.outputs.len(), 1);
328 assert_eq!(tx.outputs[0].satoshis, 75_000);
329 assert_eq!(tx.locktime, 500_000);
330 assert!(tx.raw.is_some());
331 }
332
333 #[test]
338 fn runar_artifact_deserialize_minimal() {
339 let json = r#"{
340 "version": "0.1.0",
341 "contractName": "TestContract",
342 "abi": {
343 "constructor": { "params": [] },
344 "methods": []
345 },
346 "script": "5151"
347 }"#;
348 let artifact: RunarArtifact = serde_json::from_str(json).unwrap();
349 assert_eq!(artifact.version, "0.1.0");
350 assert_eq!(artifact.contract_name, "TestContract");
351 assert_eq!(artifact.script, "5151");
352 assert!(artifact.abi.constructor.params.is_empty());
353 assert!(artifact.abi.methods.is_empty());
354 assert!(artifact.state_fields.is_none());
355 assert!(artifact.constructor_slots.is_none());
356 assert!(artifact.code_separator_index.is_none());
357 }
358
359 #[test]
360 fn runar_artifact_deserialize_with_methods() {
361 let json = r#"{
362 "version": "0.1.0",
363 "contractName": "Counter",
364 "abi": {
365 "constructor": {
366 "params": [{ "name": "count", "type": "bigint" }]
367 },
368 "methods": [{
369 "name": "increment",
370 "params": [],
371 "isPublic": true
372 }]
373 },
374 "script": "00ab"
375 }"#;
376 let artifact: RunarArtifact = serde_json::from_str(json).unwrap();
377 assert_eq!(artifact.abi.constructor.params.len(), 1);
378 assert_eq!(artifact.abi.constructor.params[0].name, "count");
379 assert_eq!(artifact.abi.constructor.params[0].param_type, "bigint");
380 assert_eq!(artifact.abi.methods.len(), 1);
381 assert_eq!(artifact.abi.methods[0].name, "increment");
382 assert!(artifact.abi.methods[0].is_public);
383 }
384
385 #[test]
386 fn runar_artifact_deserialize_with_state_fields_and_slots() {
387 let json = r#"{
388 "version": "0.1.0",
389 "contractName": "Stateful",
390 "abi": {
391 "constructor": { "params": [{ "name": "x", "type": "bigint" }] },
392 "methods": [{ "name": "update", "params": [], "isPublic": true }]
393 },
394 "script": "aabb",
395 "stateFields": [{ "name": "x", "type": "bigint", "index": 0 }],
396 "constructorSlots": [{ "paramIndex": 0, "byteOffset": 5 }],
397 "codeSeparatorIndex": 10,
398 "codeSeparatorIndices": [10, 20]
399 }"#;
400 let artifact: RunarArtifact = serde_json::from_str(json).unwrap();
401 let sf = artifact.state_fields.as_ref().unwrap();
402 assert_eq!(sf.len(), 1);
403 assert_eq!(sf[0].name, "x");
404 assert_eq!(sf[0].index, 0);
405 let cs = artifact.constructor_slots.as_ref().unwrap();
406 assert_eq!(cs.len(), 1);
407 assert_eq!(cs[0].param_index, 0);
408 assert_eq!(cs[0].byte_offset, 5);
409 assert_eq!(artifact.code_separator_index, Some(10));
410 assert_eq!(artifact.code_separator_indices, Some(vec![10, 20]));
411 }
412
413 #[test]
418 fn sdk_value_int() {
419 let v = SdkValue::Int(42);
420 assert_eq!(v.as_int(), 42);
421 }
422
423 #[test]
424 fn sdk_value_bool() {
425 let v = SdkValue::Bool(true);
426 assert!(v.as_bool());
427 }
428
429 #[test]
430 fn sdk_value_bytes() {
431 let v = SdkValue::Bytes("deadbeef".to_string());
432 assert_eq!(v.as_bytes(), "deadbeef");
433 }
434
435 #[test]
436 fn sdk_value_auto() {
437 let v = SdkValue::Auto;
438 assert_eq!(v, SdkValue::Auto);
439 }
440
441 #[test]
442 #[should_panic(expected = "non-numeric")]
443 fn sdk_value_as_int_panics_on_bool() {
444 SdkValue::Bool(true).as_int();
445 }
446
447 #[test]
448 #[should_panic(expected = "non-Bool")]
449 fn sdk_value_as_bool_panics_on_int() {
450 SdkValue::Int(1).as_bool();
451 }
452
453 #[test]
454 #[should_panic(expected = "non-Bytes")]
455 fn sdk_value_as_bytes_panics_on_int() {
456 SdkValue::Int(1).as_bytes();
457 }
458
459 #[test]
460 fn sdk_value_equality() {
461 assert_eq!(SdkValue::Int(5), SdkValue::Int(5));
462 assert_ne!(SdkValue::Int(5), SdkValue::Int(6));
463 assert_ne!(SdkValue::Int(1), SdkValue::Bool(true));
464 }
465
466 #[test]
471 fn deploy_options_construction() {
472 let opts = DeployOptions {
473 satoshis: 1000,
474 change_address: Some("maddr".to_string()),
475 };
476 assert_eq!(opts.satoshis, 1000);
477 assert_eq!(opts.change_address.as_deref(), Some("maddr"));
478 }
479
480 #[test]
481 fn call_options_default() {
482 let opts = CallOptions::default();
483 assert!(opts.satoshis.is_none());
484 assert!(opts.change_address.is_none());
485 assert!(opts.new_state.is_none());
486 assert!(opts.outputs.is_none());
487 assert!(opts.terminal_outputs.is_none());
488 }
489}
490
491#[derive(Debug, Clone)]
502pub struct PreparedCall {
503 pub sighash: String,
505 pub preimage: String,
507 pub op_push_tx_sig: String,
509 pub tx_hex: String,
511 pub sig_indices: Vec<usize>,
513
514 pub(crate) method_name: String,
516 pub(crate) resolved_args: Vec<SdkValue>,
517 pub(crate) method_selector_hex: String,
518 pub(crate) is_stateful: bool,
519 pub(crate) is_terminal: bool,
520 pub(crate) needs_op_push_tx: bool,
521 pub(crate) method_needs_change: bool,
522 pub(crate) change_pkh_hex: String,
523 pub(crate) change_amount: i64,
524 pub(crate) method_needs_new_amount: bool,
525 pub(crate) new_amount: i64,
526 pub(crate) preimage_index: Option<usize>,
527 pub(crate) contract_utxo: Utxo,
528 pub(crate) new_locking_script: String,
529 pub(crate) new_satoshis: i64,
530 pub(crate) has_multi_output: bool,
531 pub(crate) contract_outputs: Vec<ContractOutputEntry>,
532 pub(crate) code_sep_idx: i64,
533}
534
535#[derive(Debug, Clone)]
537pub struct ContractOutputEntry {
538 pub script: String,
539 pub satoshis: i64,
540}