1use crate::models::SignerError;
4use serde::{Deserialize, Serialize};
5use soroban_rs::xdr::{
6 AccountId, ContractExecutable, ContractId, ContractIdPreimage, ContractIdPreimageFromAddress,
7 CreateContractArgs, CreateContractArgsV2, Hash, HostFunction, InvokeContractArgs,
8 PublicKey as XdrPublicKey, ScAddress, ScSymbol, ScVal, Uint256, VecM,
9};
10use std::convert::TryFrom;
11use utoipa::ToSchema;
12
13fn fix_u64_format(value: &mut serde_json::Value) {
27 match value {
28 serde_json::Value::Object(map) => {
29 if map.len() == 1 {
31 if let Some(serde_json::Value::String(s)) = map.get("u64") {
32 if let Ok(num) = s.parse::<u64>() {
33 map.insert("u64".to_string(), serde_json::json!(num));
34 }
35 } else if let Some(serde_json::Value::String(s)) = map.get("i64") {
36 if let Ok(num) = s.parse::<i64>() {
37 map.insert("i64".to_string(), serde_json::json!(num));
38 }
39 } else if let Some(serde_json::Value::String(s)) = map.get("timepoint") {
40 if let Ok(num) = s.parse::<u64>() {
41 map.insert("timepoint".to_string(), serde_json::json!(num));
42 }
43 } else if let Some(serde_json::Value::String(s)) = map.get("duration") {
44 if let Ok(num) = s.parse::<u64>() {
45 map.insert("duration".to_string(), serde_json::json!(num));
46 }
47 }
48 }
49
50 if map.contains_key("hi") && map.contains_key("lo") && map.len() == 2 {
52 if let Some(serde_json::Value::String(s)) = map.get("hi") {
53 if let Ok(num) = s.parse::<u64>() {
54 map.insert("hi".to_string(), serde_json::json!(num));
55 }
56 }
57 if let Some(serde_json::Value::String(s)) = map.get("lo") {
58 if let Ok(num) = s.parse::<u64>() {
59 map.insert("lo".to_string(), serde_json::json!(num));
60 }
61 }
62 }
63
64 if map.contains_key("u128") {
66 if let Some(serde_json::Value::Object(inner)) = map.get_mut("u128") {
67 if let Some(serde_json::Value::String(s)) = inner.get("hi") {
69 if let Ok(num) = s.parse::<u64>() {
70 inner.insert("hi".to_string(), serde_json::json!(num));
71 }
72 }
73 if let Some(serde_json::Value::String(s)) = inner.get("lo") {
74 if let Ok(num) = s.parse::<u64>() {
75 inner.insert("lo".to_string(), serde_json::json!(num));
76 }
77 }
78 }
79 }
80
81 if map.contains_key("i128") {
83 if let Some(serde_json::Value::Object(inner)) = map.get_mut("i128") {
84 if let Some(serde_json::Value::String(s)) = inner.get("hi") {
86 if let Ok(num) = s.parse::<i64>() {
87 inner.insert("hi".to_string(), serde_json::json!(num));
88 }
89 }
90 if let Some(serde_json::Value::String(s)) = inner.get("lo") {
91 if let Ok(num) = s.parse::<u64>() {
92 inner.insert("lo".to_string(), serde_json::json!(num));
93 }
94 }
95 }
96 }
97
98 if map.contains_key("u256") {
100 if let Some(serde_json::Value::Object(inner)) = map.get_mut("u256") {
101 for key in ["hi_hi", "hi_lo", "lo_hi", "lo_lo"] {
103 if let Some(serde_json::Value::String(s)) = inner.get(key) {
104 if let Ok(num) = s.parse::<u64>() {
105 inner.insert(key.to_string(), serde_json::json!(num));
106 }
107 }
108 }
109 }
110 }
111
112 if map.contains_key("i256") {
114 if let Some(serde_json::Value::Object(inner)) = map.get_mut("i256") {
115 if let Some(serde_json::Value::String(s)) = inner.get("hi_hi") {
117 if let Ok(num) = s.parse::<i64>() {
118 inner.insert("hi_hi".to_string(), serde_json::json!(num));
119 }
120 }
121 for key in ["hi_lo", "lo_hi", "lo_lo"] {
122 if let Some(serde_json::Value::String(s)) = inner.get(key) {
123 if let Ok(num) = s.parse::<u64>() {
124 inner.insert(key.to_string(), serde_json::json!(num));
125 }
126 }
127 }
128 }
129 }
130
131 if map.contains_key("hi_hi")
133 && map.contains_key("hi_lo")
134 && map.contains_key("lo_hi")
135 && map.contains_key("lo_lo")
136 && map.len() == 4
137 {
138 for key in ["hi_hi", "hi_lo", "lo_hi", "lo_lo"] {
139 if let Some(serde_json::Value::String(s)) = map.get(key) {
140 if let Ok(num) = s.parse::<u64>() {
141 map.insert(key.to_string(), serde_json::json!(num));
142 }
143 }
144 }
145 }
146
147 for (_, v) in map.iter_mut() {
149 fix_u64_format(v);
150 }
151 }
152 serde_json::Value::Array(arr) => {
153 for v in arr.iter_mut() {
155 fix_u64_format(v);
156 }
157 }
158 _ => {}
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
164#[serde(untagged)]
165pub enum WasmSource {
166 Hex { hex: String },
167 Base64 { base64: String },
168}
169
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
172#[serde(tag = "from", rename_all = "snake_case")]
173pub enum ContractSource {
174 Address { address: String }, Contract { contract: String }, }
177
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
180#[serde(tag = "type", rename_all = "snake_case")]
181pub enum HostFunctionSpec {
182 InvokeContract {
184 contract_address: String,
185 function_name: String,
186 args: Vec<serde_json::Value>,
187 },
188
189 UploadWasm {
191 wasm: WasmSource,
192 },
193
194 CreateContract {
196 source: ContractSource,
197 wasm_hash: String, #[serde(skip_serializing_if = "Option::is_none")]
199 salt: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
201 constructor_args: Option<Vec<serde_json::Value>>,
202 },
203}
204
205fn wasm_source_to_bytes(wasm: WasmSource) -> Result<Vec<u8>, SignerError> {
209 match wasm {
210 WasmSource::Hex { hex } => hex::decode(&hex)
211 .map_err(|e| SignerError::ConversionError(format!("Invalid hex in wasm: {e}"))),
212 WasmSource::Base64 { base64 } => {
213 use base64::{engine::general_purpose, Engine as _};
214 general_purpose::STANDARD
215 .decode(&base64)
216 .map_err(|e| SignerError::ConversionError(format!("Invalid base64 in wasm: {e}")))
217 }
218 }
219}
220
221fn parse_salt_bytes(salt: Option<String>) -> Result<[u8; 32], SignerError> {
223 if let Some(salt_hex) = salt {
224 let bytes = hex::decode(&salt_hex)
225 .map_err(|e| SignerError::ConversionError(format!("Invalid salt hex: {e}")))?;
226 if bytes.len() != 32 {
227 return Err(SignerError::ConversionError("Salt must be 32 bytes".into()));
228 }
229 let mut array = [0u8; 32];
230 array.copy_from_slice(&bytes);
231 Ok(array)
232 } else {
233 Ok([0u8; 32]) }
235}
236
237fn parse_wasm_hash(wasm_hash: &str) -> Result<Hash, SignerError> {
239 let hash_bytes = hex::decode(wasm_hash)
240 .map_err(|e| SignerError::ConversionError(format!("Invalid hex in wasm_hash: {e}")))?;
241 if hash_bytes.len() != 32 {
242 return Err(SignerError::ConversionError(format!(
243 "Hash must be 32 bytes, got {}",
244 hash_bytes.len()
245 )));
246 }
247 let mut hash_array = [0u8; 32];
248 hash_array.copy_from_slice(&hash_bytes);
249 Ok(Hash(hash_array))
250}
251
252fn build_contract_preimage(
254 source: ContractSource,
255 salt: Option<String>,
256) -> Result<ContractIdPreimage, SignerError> {
257 let salt_bytes = parse_salt_bytes(salt)?;
258
259 match source {
260 ContractSource::Address { address } => {
261 let public_key =
262 stellar_strkey::ed25519::PublicKey::from_string(&address).map_err(|e| {
263 SignerError::ConversionError(format!("Invalid account address: {e}"))
264 })?;
265 let account_id = AccountId(XdrPublicKey::PublicKeyTypeEd25519(Uint256(public_key.0)));
266
267 Ok(ContractIdPreimage::Address(ContractIdPreimageFromAddress {
268 address: ScAddress::Account(account_id),
269 salt: Uint256(salt_bytes),
270 }))
271 }
272 ContractSource::Contract { contract } => {
273 let contract_id = stellar_strkey::Contract::from_string(&contract).map_err(|e| {
274 SignerError::ConversionError(format!("Invalid contract address: {e}"))
275 })?;
276
277 Ok(ContractIdPreimage::Address(ContractIdPreimageFromAddress {
278 address: ScAddress::Contract(ContractId(Hash(contract_id.0))),
279 salt: Uint256(salt_bytes),
280 }))
281 }
282 }
283}
284
285fn convert_invoke_contract(
287 contract_address: String,
288 function_name: String,
289 args: Vec<serde_json::Value>,
290) -> Result<HostFunction, SignerError> {
291 let contract = stellar_strkey::Contract::from_string(&contract_address)
293 .map_err(|e| SignerError::ConversionError(format!("Invalid contract address: {e}")))?;
294 let contract_addr = ScAddress::Contract(ContractId(Hash(contract.0)));
295
296 let function_symbol = ScSymbol::try_from(function_name.as_bytes().to_vec())
298 .map_err(|e| SignerError::ConversionError(format!("Invalid function name: {e}")))?;
299
300 let scval_args: Vec<ScVal> = args
304 .iter()
305 .map(|json| {
306 let mut modified_json = json.clone();
307 fix_u64_format(&mut modified_json);
308 serde_json::from_value(modified_json)
309 })
310 .collect::<Result<Vec<_>, _>>()
311 .map_err(|e| SignerError::ConversionError(format!("Failed to deserialize ScVal: {e}")))?;
312 let args_vec = VecM::try_from(scval_args)
313 .map_err(|e| SignerError::ConversionError(format!("Failed to convert arguments: {e}")))?;
314
315 Ok(HostFunction::InvokeContract(InvokeContractArgs {
316 contract_address: contract_addr,
317 function_name: function_symbol,
318 args: args_vec,
319 }))
320}
321
322fn convert_upload_wasm(wasm: WasmSource) -> Result<HostFunction, SignerError> {
324 let bytes = wasm_source_to_bytes(wasm)?;
325 Ok(HostFunction::UploadContractWasm(bytes.try_into().map_err(
326 |e| SignerError::ConversionError(format!("Failed to convert wasm bytes: {e:?}")),
327 )?))
328}
329
330fn convert_create_contract(
332 source: ContractSource,
333 wasm_hash: String,
334 salt: Option<String>,
335 constructor_args: Option<Vec<serde_json::Value>>,
336) -> Result<HostFunction, SignerError> {
337 let preimage = build_contract_preimage(source, salt)?;
338 let wasm_hash = parse_wasm_hash(&wasm_hash)?;
339
340 if let Some(args) = constructor_args {
342 if !args.is_empty() {
343 let scval_args: Vec<ScVal> = args
347 .iter()
348 .map(|json| {
349 let mut modified_json = json.clone();
350 fix_u64_format(&mut modified_json);
351 serde_json::from_value(modified_json)
352 })
353 .collect::<Result<Vec<_>, _>>()
354 .map_err(|e| {
355 SignerError::ConversionError(format!("Failed to deserialize ScVal: {e}"))
356 })?;
357 let constructor_args_vec = VecM::try_from(scval_args).map_err(|e| {
358 SignerError::ConversionError(format!(
359 "Failed to convert constructor arguments: {e}"
360 ))
361 })?;
362
363 let create_args_v2 = CreateContractArgsV2 {
364 contract_id_preimage: preimage,
365 executable: ContractExecutable::Wasm(wasm_hash),
366 constructor_args: constructor_args_vec,
367 };
368
369 return Ok(HostFunction::CreateContractV2(create_args_v2));
370 }
371 }
372
373 let create_args = CreateContractArgs {
375 contract_id_preimage: preimage,
376 executable: ContractExecutable::Wasm(wasm_hash),
377 };
378
379 Ok(HostFunction::CreateContract(create_args))
380}
381
382impl TryFrom<HostFunctionSpec> for HostFunction {
383 type Error = SignerError;
384
385 fn try_from(spec: HostFunctionSpec) -> Result<Self, Self::Error> {
386 match spec {
387 HostFunctionSpec::InvokeContract {
388 contract_address,
389 function_name,
390 args,
391 } => convert_invoke_contract(contract_address, function_name, args),
392
393 HostFunctionSpec::UploadWasm { wasm } => convert_upload_wasm(wasm),
394
395 HostFunctionSpec::CreateContract {
396 source,
397 wasm_hash,
398 salt,
399 constructor_args,
400 } => convert_create_contract(source, wasm_hash, salt, constructor_args),
401 }
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use serde_json::json;
409
410 const TEST_PK: &str = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
411 const TEST_CONTRACT: &str = "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA";
412
413 mod wasm_source_to_bytes_tests {
414 use super::*;
415
416 #[test]
417 fn test_hex_conversion() {
418 let wasm = WasmSource::Hex {
419 hex: "deadbeef".to_string(),
420 };
421 let result = wasm_source_to_bytes(wasm).unwrap();
422 assert_eq!(result, vec![0xde, 0xad, 0xbe, 0xef]);
423 }
424
425 #[test]
426 fn test_base64_conversion() {
427 let wasm = WasmSource::Base64 {
428 base64: "3q2+7w==".to_string(), };
430 let result = wasm_source_to_bytes(wasm).unwrap();
431 assert_eq!(result, vec![0xde, 0xad, 0xbe, 0xef]);
432 }
433
434 #[test]
435 fn test_invalid_hex() {
436 let wasm = WasmSource::Hex {
437 hex: "invalid_hex".to_string(),
438 };
439 let result = wasm_source_to_bytes(wasm);
440 assert!(result.is_err());
441 assert!(result.unwrap_err().to_string().contains("Invalid hex"));
442 }
443
444 #[test]
445 fn test_invalid_base64() {
446 let wasm = WasmSource::Base64 {
447 base64: "!!!invalid!!!".to_string(),
448 };
449 let result = wasm_source_to_bytes(wasm);
450 assert!(result.is_err());
451 assert!(result.unwrap_err().to_string().contains("Invalid base64"));
452 }
453 }
454
455 mod parse_salt_bytes_tests {
456 use super::*;
457
458 #[test]
459 fn test_valid_32_byte_hex() {
460 let salt = Some(
461 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(),
462 );
463 let result = parse_salt_bytes(salt).unwrap();
464 assert_eq!(result.len(), 32);
465 assert_eq!(result[0], 0x01);
466 assert_eq!(result[1], 0x23);
467 }
468
469 #[test]
470 fn test_none_returns_zeros() {
471 let result = parse_salt_bytes(None).unwrap();
472 assert_eq!(result, [0u8; 32]);
473 }
474
475 #[test]
476 fn test_invalid_hex() {
477 let salt = Some("gg".to_string()); let result = parse_salt_bytes(salt);
479 assert!(result.is_err());
480 assert!(result.unwrap_err().to_string().contains("Invalid salt hex"));
481 }
482
483 #[test]
484 fn test_wrong_length() {
485 let salt = Some("abcd".to_string()); let result = parse_salt_bytes(salt);
487 assert!(result.is_err());
488 assert!(result
489 .unwrap_err()
490 .to_string()
491 .contains("Salt must be 32 bytes"));
492 }
493 }
494
495 mod parse_wasm_hash_tests {
496 use super::*;
497
498 #[test]
499 fn test_valid_32_byte_hex() {
500 let hash_hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
501 let result = parse_wasm_hash(hash_hex).unwrap();
502 assert_eq!(result.0[0], 0x01);
503 assert_eq!(result.0[31], 0xef);
504 }
505
506 #[test]
507 fn test_invalid_hex() {
508 let result = parse_wasm_hash("invalid_hex");
509 assert!(result.is_err());
510 assert!(result.unwrap_err().to_string().contains("Invalid hex"));
511 }
512
513 #[test]
514 fn test_wrong_length() {
515 let result = parse_wasm_hash("abcd");
516 assert!(result.is_err());
517 assert!(result
518 .unwrap_err()
519 .to_string()
520 .contains("Hash must be 32 bytes"));
521 }
522 }
523
524 mod build_contract_preimage_tests {
525 use super::*;
526
527 #[test]
528 fn test_with_address_source() {
529 let source = ContractSource::Address {
530 address: TEST_PK.to_string(),
531 };
532 let result = build_contract_preimage(source, None).unwrap();
533
534 if let ContractIdPreimage::Address(preimage) = result {
535 assert!(matches!(preimage.address, ScAddress::Account(_)));
536 assert_eq!(preimage.salt.0, [0u8; 32]);
537 } else {
538 panic!("Expected Address preimage");
539 }
540 }
541
542 #[test]
543 fn test_with_contract_source() {
544 let source = ContractSource::Contract {
545 contract: TEST_CONTRACT.to_string(),
546 };
547 let result = build_contract_preimage(source, None).unwrap();
548
549 if let ContractIdPreimage::Address(preimage) = result {
550 assert!(matches!(preimage.address, ScAddress::Contract(_)));
551 assert_eq!(preimage.salt.0, [0u8; 32]);
552 } else {
553 panic!("Expected Address preimage");
554 }
555 }
556
557 #[test]
558 fn test_with_custom_salt() {
559 let source = ContractSource::Address {
560 address: TEST_PK.to_string(),
561 };
562 let salt = Some(
563 "0000000000000000000000000000000000000000000000000000000000000042".to_string(),
564 );
565 let result = build_contract_preimage(source, salt).unwrap();
566
567 if let ContractIdPreimage::Address(preimage) = result {
568 assert_eq!(preimage.salt.0[31], 0x42);
569 } else {
570 panic!("Expected Address preimage");
571 }
572 }
573
574 #[test]
575 fn test_invalid_address() {
576 let source = ContractSource::Address {
577 address: "INVALID".to_string(),
578 };
579 let result = build_contract_preimage(source, None);
580 assert!(result.is_err());
581 }
582 }
583
584 mod convert_invoke_contract_tests {
585 use super::*;
586
587 #[test]
588 fn test_valid_contract_address() {
589 let result =
590 convert_invoke_contract(TEST_CONTRACT.to_string(), "hello".to_string(), vec![]);
591 assert!(result.is_ok());
592
593 if let HostFunction::InvokeContract(args) = result.unwrap() {
594 assert!(matches!(args.contract_address, ScAddress::Contract(_)));
595 assert_eq!(args.function_name.to_utf8_string_lossy(), "hello");
596 assert_eq!(args.args.len(), 0);
597 } else {
598 panic!("Expected InvokeContract");
599 }
600 }
601
602 #[test]
603 fn test_function_name_conversion() {
604 let result = convert_invoke_contract(
605 TEST_CONTRACT.to_string(),
606 "transfer_tokens".to_string(),
607 vec![],
608 );
609 assert!(result.is_ok());
610
611 if let HostFunction::InvokeContract(args) = result.unwrap() {
612 assert_eq!(args.function_name.to_utf8_string_lossy(), "transfer_tokens");
613 } else {
614 panic!("Expected InvokeContract");
615 }
616 }
617
618 #[test]
619 fn test_various_arg_types() {
620 let args = vec![
621 json!({"u64": 1000}),
622 json!({"string": "hello"}),
623 json!({"address": TEST_PK}),
624 ];
625 let result =
626 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), args);
627 assert!(result.is_ok());
628
629 if let HostFunction::InvokeContract(invoke_args) = result.unwrap() {
630 assert_eq!(invoke_args.args.len(), 3);
631 } else {
632 panic!("Expected InvokeContract");
633 }
634 }
635
636 #[test]
637 fn test_invalid_contract_address() {
638 let result =
639 convert_invoke_contract("INVALID".to_string(), "hello".to_string(), vec![]);
640 assert!(result.is_err());
641 }
642 }
643
644 mod convert_upload_wasm_tests {
645 use super::*;
646
647 #[test]
648 fn test_hex_source() {
649 let wasm = WasmSource::Hex {
650 hex: "deadbeef".to_string(),
651 };
652 let result = convert_upload_wasm(wasm);
653 assert!(result.is_ok());
654
655 if let HostFunction::UploadContractWasm(bytes) = result.unwrap() {
656 assert_eq!(bytes.to_vec(), vec![0xde, 0xad, 0xbe, 0xef]);
657 } else {
658 panic!("Expected UploadContractWasm");
659 }
660 }
661
662 #[test]
663 fn test_base64_source() {
664 let wasm = WasmSource::Base64 {
665 base64: "3q2+7w==".to_string(),
666 };
667 let result = convert_upload_wasm(wasm);
668 assert!(result.is_ok());
669 }
670
671 #[test]
672 fn test_invalid_wasm() {
673 let wasm = WasmSource::Hex {
674 hex: "invalid".to_string(),
675 };
676 let result = convert_upload_wasm(wasm);
677 assert!(result.is_err());
678 }
679 }
680
681 mod convert_create_contract_tests {
682 use super::*;
683
684 #[test]
685 fn test_v1_no_constructor_args() {
686 let source = ContractSource::Address {
687 address: TEST_PK.to_string(),
688 };
689 let wasm_hash =
690 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
691 let result = convert_create_contract(source, wasm_hash, None, None);
692
693 assert!(result.is_ok());
694 assert!(matches!(result.unwrap(), HostFunction::CreateContract(_)));
695 }
696
697 #[test]
698 fn test_v2_with_constructor_args() {
699 let source = ContractSource::Address {
700 address: TEST_PK.to_string(),
701 };
702 let wasm_hash =
703 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
704 let args = Some(vec![json!({"string": "hello"}), json!({"u64": 42})]);
705 let result = convert_create_contract(source, wasm_hash, None, args);
706
707 assert!(result.is_ok());
708 if let HostFunction::CreateContractV2(args) = result.unwrap() {
709 assert_eq!(args.constructor_args.len(), 2);
710 } else {
711 panic!("Expected CreateContractV2");
712 }
713 }
714
715 #[test]
716 fn test_empty_constructor_args_uses_v1() {
717 let source = ContractSource::Address {
718 address: TEST_PK.to_string(),
719 };
720 let wasm_hash =
721 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
722 let args = Some(vec![]);
723 let result = convert_create_contract(source, wasm_hash, None, args);
724
725 assert!(result.is_ok());
726 assert!(matches!(result.unwrap(), HostFunction::CreateContract(_)));
727 }
728
729 #[test]
730 fn test_salt_handling() {
731 let source = ContractSource::Address {
732 address: TEST_PK.to_string(),
733 };
734 let wasm_hash =
735 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
736 let salt = Some(
737 "0000000000000000000000000000000000000000000000000000000000000042".to_string(),
738 );
739 let result = convert_create_contract(source, wasm_hash, salt, None);
740
741 assert!(result.is_ok());
742 }
743 }
744
745 #[test]
747 fn test_invoke_contract() {
748 let spec = HostFunctionSpec::InvokeContract {
749 contract_address: TEST_CONTRACT.to_string(),
750 function_name: "hello".to_string(),
751 args: vec![json!({"string": "world"})],
752 };
753
754 let result = HostFunction::try_from(spec);
755 assert!(result.is_ok());
756 assert!(matches!(result.unwrap(), HostFunction::InvokeContract(_)));
757 }
758
759 #[test]
760 fn test_upload_wasm() {
761 let spec = HostFunctionSpec::UploadWasm {
762 wasm: WasmSource::Hex {
763 hex: "deadbeef".to_string(),
764 },
765 };
766
767 let result = HostFunction::try_from(spec);
768 assert!(result.is_ok());
769 assert!(matches!(
770 result.unwrap(),
771 HostFunction::UploadContractWasm(_)
772 ));
773 }
774
775 #[test]
776 fn test_create_contract_v1() {
777 let spec = HostFunctionSpec::CreateContract {
778 source: ContractSource::Address {
779 address: TEST_PK.to_string(),
780 },
781 wasm_hash: "0000000000000000000000000000000000000000000000000000000000000001"
782 .to_string(),
783 salt: None,
784 constructor_args: None,
785 };
786
787 let result = HostFunction::try_from(spec);
788 assert!(result.is_ok());
789 assert!(matches!(result.unwrap(), HostFunction::CreateContract(_)));
790 }
791
792 #[test]
793 fn test_create_contract_v2() {
794 let spec = HostFunctionSpec::CreateContract {
795 source: ContractSource::Address {
796 address: TEST_PK.to_string(),
797 },
798 wasm_hash: "0000000000000000000000000000000000000000000000000000000000000001"
799 .to_string(),
800 salt: None,
801 constructor_args: Some(vec![json!({"string": "init"})]),
802 };
803
804 let result = HostFunction::try_from(spec);
805 assert!(result.is_ok());
806 assert!(matches!(result.unwrap(), HostFunction::CreateContractV2(_)));
807 }
808
809 #[test]
810 fn test_host_function_spec_serde() {
811 let spec = HostFunctionSpec::InvokeContract {
812 contract_address: TEST_CONTRACT.to_string(),
813 function_name: "test".to_string(),
814 args: vec![json!({"u64": 42})],
815 };
816 let json = serde_json::to_string(&spec).unwrap();
817 assert!(json.contains("invoke_contract"));
818 assert!(json.contains(TEST_CONTRACT));
819
820 let deserialized: HostFunctionSpec = serde_json::from_str(&json).unwrap();
821 assert_eq!(spec, deserialized);
822 }
823
824 #[test]
825 fn test_u64_string_to_number_conversion() {
826 let args = vec![
828 json!({"u64": "1000"}),
829 json!({"i64": "-500"}),
830 json!({"timepoint": "123456"}),
831 json!({"duration": "7890"}),
832 ];
833
834 let result = convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), args);
835 assert!(
836 result.is_ok(),
837 "Should successfully convert string u64/i64 to numbers"
838 );
839
840 let u128_arg = vec![json!({"u128": {"hi": "100", "lo": "200"}})];
842 let result =
843 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), u128_arg);
844 assert!(result.is_ok(), "Should successfully convert u128 parts");
845
846 let i128_arg = vec![json!({"i128": {"hi": "-100", "lo": "200"}})];
848 let result =
849 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), i128_arg);
850 assert!(result.is_ok(), "Should successfully convert i128 parts");
851
852 let u256_arg =
854 vec![json!({"u256": {"hi_hi": "1", "hi_lo": "2", "lo_hi": "3", "lo_lo": "4"}})];
855 let result =
856 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), u256_arg);
857 assert!(result.is_ok(), "Should successfully convert u256 parts");
858
859 let i256_arg =
861 vec![json!({"i256": {"hi_hi": "-1", "hi_lo": "2", "lo_hi": "3", "lo_lo": "4"}})];
862 let result =
863 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), i256_arg);
864 assert!(result.is_ok(), "Should successfully convert i256 parts");
865 }
866
867 #[test]
868 fn test_host_function_spec_json_format() {
869 let invoke = HostFunctionSpec::InvokeContract {
871 contract_address: TEST_CONTRACT.to_string(),
872 function_name: "test".to_string(),
873 args: vec![json!({"u64": 42})],
874 };
875 let invoke_json = serde_json::to_value(&invoke).unwrap();
876 assert_eq!(invoke_json["type"], "invoke_contract");
877 assert_eq!(invoke_json["contract_address"], TEST_CONTRACT);
878 assert_eq!(invoke_json["function_name"], "test");
879
880 let upload = HostFunctionSpec::UploadWasm {
882 wasm: WasmSource::Hex {
883 hex: "deadbeef".to_string(),
884 },
885 };
886 let upload_json = serde_json::to_value(&upload).unwrap();
887 assert_eq!(upload_json["type"], "upload_wasm");
888 assert!(upload_json["wasm"].is_object());
889
890 let create = HostFunctionSpec::CreateContract {
892 source: ContractSource::Address {
893 address: TEST_PK.to_string(),
894 },
895 wasm_hash: "0000000000000000000000000000000000000000000000000000000000000001"
896 .to_string(),
897 salt: None,
898 constructor_args: None,
899 };
900 let create_json = serde_json::to_value(&create).unwrap();
901 assert_eq!(create_json["type"], "create_contract");
902 assert_eq!(create_json["source"]["from"], "address");
903 assert_eq!(create_json["source"]["address"], TEST_PK);
904 }
905}