1use crate::models::SignerError;
4use serde::{Deserialize, Serialize};
5use soroban_rs::xdr::{
6 AccountId, ContractExecutable, 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(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(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: {}",
360 e
361 ))
362 })?;
363
364 let create_args_v2 = CreateContractArgsV2 {
365 contract_id_preimage: preimage,
366 executable: ContractExecutable::Wasm(wasm_hash),
367 constructor_args: constructor_args_vec,
368 };
369
370 return Ok(HostFunction::CreateContractV2(create_args_v2));
371 }
372 }
373
374 let create_args = CreateContractArgs {
376 contract_id_preimage: preimage,
377 executable: ContractExecutable::Wasm(wasm_hash),
378 };
379
380 Ok(HostFunction::CreateContract(create_args))
381}
382
383impl TryFrom<HostFunctionSpec> for HostFunction {
384 type Error = SignerError;
385
386 fn try_from(spec: HostFunctionSpec) -> Result<Self, Self::Error> {
387 match spec {
388 HostFunctionSpec::InvokeContract {
389 contract_address,
390 function_name,
391 args,
392 } => convert_invoke_contract(contract_address, function_name, args),
393
394 HostFunctionSpec::UploadWasm { wasm } => convert_upload_wasm(wasm),
395
396 HostFunctionSpec::CreateContract {
397 source,
398 wasm_hash,
399 salt,
400 constructor_args,
401 } => convert_create_contract(source, wasm_hash, salt, constructor_args),
402 }
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use serde_json::json;
410
411 const TEST_PK: &str = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
412 const TEST_CONTRACT: &str = "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA";
413
414 mod wasm_source_to_bytes_tests {
415 use super::*;
416
417 #[test]
418 fn test_hex_conversion() {
419 let wasm = WasmSource::Hex {
420 hex: "deadbeef".to_string(),
421 };
422 let result = wasm_source_to_bytes(wasm).unwrap();
423 assert_eq!(result, vec![0xde, 0xad, 0xbe, 0xef]);
424 }
425
426 #[test]
427 fn test_base64_conversion() {
428 let wasm = WasmSource::Base64 {
429 base64: "3q2+7w==".to_string(), };
431 let result = wasm_source_to_bytes(wasm).unwrap();
432 assert_eq!(result, vec![0xde, 0xad, 0xbe, 0xef]);
433 }
434
435 #[test]
436 fn test_invalid_hex() {
437 let wasm = WasmSource::Hex {
438 hex: "invalid_hex".to_string(),
439 };
440 let result = wasm_source_to_bytes(wasm);
441 assert!(result.is_err());
442 assert!(result.unwrap_err().to_string().contains("Invalid hex"));
443 }
444
445 #[test]
446 fn test_invalid_base64() {
447 let wasm = WasmSource::Base64 {
448 base64: "!!!invalid!!!".to_string(),
449 };
450 let result = wasm_source_to_bytes(wasm);
451 assert!(result.is_err());
452 assert!(result.unwrap_err().to_string().contains("Invalid base64"));
453 }
454 }
455
456 mod parse_salt_bytes_tests {
457 use super::*;
458
459 #[test]
460 fn test_valid_32_byte_hex() {
461 let salt = Some(
462 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(),
463 );
464 let result = parse_salt_bytes(salt).unwrap();
465 assert_eq!(result.len(), 32);
466 assert_eq!(result[0], 0x01);
467 assert_eq!(result[1], 0x23);
468 }
469
470 #[test]
471 fn test_none_returns_zeros() {
472 let result = parse_salt_bytes(None).unwrap();
473 assert_eq!(result, [0u8; 32]);
474 }
475
476 #[test]
477 fn test_invalid_hex() {
478 let salt = Some("gg".to_string()); let result = parse_salt_bytes(salt);
480 assert!(result.is_err());
481 assert!(result.unwrap_err().to_string().contains("Invalid salt hex"));
482 }
483
484 #[test]
485 fn test_wrong_length() {
486 let salt = Some("abcd".to_string()); let result = parse_salt_bytes(salt);
488 assert!(result.is_err());
489 assert!(result
490 .unwrap_err()
491 .to_string()
492 .contains("Salt must be 32 bytes"));
493 }
494 }
495
496 mod parse_wasm_hash_tests {
497 use super::*;
498
499 #[test]
500 fn test_valid_32_byte_hex() {
501 let hash_hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
502 let result = parse_wasm_hash(hash_hex).unwrap();
503 assert_eq!(result.0[0], 0x01);
504 assert_eq!(result.0[31], 0xef);
505 }
506
507 #[test]
508 fn test_invalid_hex() {
509 let result = parse_wasm_hash("invalid_hex");
510 assert!(result.is_err());
511 assert!(result.unwrap_err().to_string().contains("Invalid hex"));
512 }
513
514 #[test]
515 fn test_wrong_length() {
516 let result = parse_wasm_hash("abcd");
517 assert!(result.is_err());
518 assert!(result
519 .unwrap_err()
520 .to_string()
521 .contains("Hash must be 32 bytes"));
522 }
523 }
524
525 mod build_contract_preimage_tests {
526 use super::*;
527
528 #[test]
529 fn test_with_address_source() {
530 let source = ContractSource::Address {
531 address: TEST_PK.to_string(),
532 };
533 let result = build_contract_preimage(source, None).unwrap();
534
535 if let ContractIdPreimage::Address(preimage) = result {
536 assert!(matches!(preimage.address, ScAddress::Account(_)));
537 assert_eq!(preimage.salt.0, [0u8; 32]);
538 } else {
539 panic!("Expected Address preimage");
540 }
541 }
542
543 #[test]
544 fn test_with_contract_source() {
545 let source = ContractSource::Contract {
546 contract: TEST_CONTRACT.to_string(),
547 };
548 let result = build_contract_preimage(source, None).unwrap();
549
550 if let ContractIdPreimage::Address(preimage) = result {
551 assert!(matches!(preimage.address, ScAddress::Contract(_)));
552 assert_eq!(preimage.salt.0, [0u8; 32]);
553 } else {
554 panic!("Expected Address preimage");
555 }
556 }
557
558 #[test]
559 fn test_with_custom_salt() {
560 let source = ContractSource::Address {
561 address: TEST_PK.to_string(),
562 };
563 let salt = Some(
564 "0000000000000000000000000000000000000000000000000000000000000042".to_string(),
565 );
566 let result = build_contract_preimage(source, salt).unwrap();
567
568 if let ContractIdPreimage::Address(preimage) = result {
569 assert_eq!(preimage.salt.0[31], 0x42);
570 } else {
571 panic!("Expected Address preimage");
572 }
573 }
574
575 #[test]
576 fn test_invalid_address() {
577 let source = ContractSource::Address {
578 address: "INVALID".to_string(),
579 };
580 let result = build_contract_preimage(source, None);
581 assert!(result.is_err());
582 }
583 }
584
585 mod convert_invoke_contract_tests {
586 use super::*;
587
588 #[test]
589 fn test_valid_contract_address() {
590 let result =
591 convert_invoke_contract(TEST_CONTRACT.to_string(), "hello".to_string(), vec![]);
592 assert!(result.is_ok());
593
594 if let HostFunction::InvokeContract(args) = result.unwrap() {
595 assert!(matches!(args.contract_address, ScAddress::Contract(_)));
596 assert_eq!(args.function_name.to_utf8_string_lossy(), "hello");
597 assert_eq!(args.args.len(), 0);
598 } else {
599 panic!("Expected InvokeContract");
600 }
601 }
602
603 #[test]
604 fn test_function_name_conversion() {
605 let result = convert_invoke_contract(
606 TEST_CONTRACT.to_string(),
607 "transfer_tokens".to_string(),
608 vec![],
609 );
610 assert!(result.is_ok());
611
612 if let HostFunction::InvokeContract(args) = result.unwrap() {
613 assert_eq!(args.function_name.to_utf8_string_lossy(), "transfer_tokens");
614 } else {
615 panic!("Expected InvokeContract");
616 }
617 }
618
619 #[test]
620 fn test_various_arg_types() {
621 let args = vec![
622 json!({"u64": 1000}),
623 json!({"string": "hello"}),
624 json!({"address": TEST_PK}),
625 ];
626 let result =
627 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), args);
628 assert!(result.is_ok());
629
630 if let HostFunction::InvokeContract(invoke_args) = result.unwrap() {
631 assert_eq!(invoke_args.args.len(), 3);
632 } else {
633 panic!("Expected InvokeContract");
634 }
635 }
636
637 #[test]
638 fn test_invalid_contract_address() {
639 let result =
640 convert_invoke_contract("INVALID".to_string(), "hello".to_string(), vec![]);
641 assert!(result.is_err());
642 }
643 }
644
645 mod convert_upload_wasm_tests {
646 use super::*;
647
648 #[test]
649 fn test_hex_source() {
650 let wasm = WasmSource::Hex {
651 hex: "deadbeef".to_string(),
652 };
653 let result = convert_upload_wasm(wasm);
654 assert!(result.is_ok());
655
656 if let HostFunction::UploadContractWasm(bytes) = result.unwrap() {
657 assert_eq!(bytes.to_vec(), vec![0xde, 0xad, 0xbe, 0xef]);
658 } else {
659 panic!("Expected UploadContractWasm");
660 }
661 }
662
663 #[test]
664 fn test_base64_source() {
665 let wasm = WasmSource::Base64 {
666 base64: "3q2+7w==".to_string(),
667 };
668 let result = convert_upload_wasm(wasm);
669 assert!(result.is_ok());
670 }
671
672 #[test]
673 fn test_invalid_wasm() {
674 let wasm = WasmSource::Hex {
675 hex: "invalid".to_string(),
676 };
677 let result = convert_upload_wasm(wasm);
678 assert!(result.is_err());
679 }
680 }
681
682 mod convert_create_contract_tests {
683 use super::*;
684
685 #[test]
686 fn test_v1_no_constructor_args() {
687 let source = ContractSource::Address {
688 address: TEST_PK.to_string(),
689 };
690 let wasm_hash =
691 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
692 let result = convert_create_contract(source, wasm_hash, None, None);
693
694 assert!(result.is_ok());
695 assert!(matches!(result.unwrap(), HostFunction::CreateContract(_)));
696 }
697
698 #[test]
699 fn test_v2_with_constructor_args() {
700 let source = ContractSource::Address {
701 address: TEST_PK.to_string(),
702 };
703 let wasm_hash =
704 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
705 let args = Some(vec![json!({"string": "hello"}), json!({"u64": 42})]);
706 let result = convert_create_contract(source, wasm_hash, None, args);
707
708 assert!(result.is_ok());
709 if let HostFunction::CreateContractV2(args) = result.unwrap() {
710 assert_eq!(args.constructor_args.len(), 2);
711 } else {
712 panic!("Expected CreateContractV2");
713 }
714 }
715
716 #[test]
717 fn test_empty_constructor_args_uses_v1() {
718 let source = ContractSource::Address {
719 address: TEST_PK.to_string(),
720 };
721 let wasm_hash =
722 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
723 let args = Some(vec![]);
724 let result = convert_create_contract(source, wasm_hash, None, args);
725
726 assert!(result.is_ok());
727 assert!(matches!(result.unwrap(), HostFunction::CreateContract(_)));
728 }
729
730 #[test]
731 fn test_salt_handling() {
732 let source = ContractSource::Address {
733 address: TEST_PK.to_string(),
734 };
735 let wasm_hash =
736 "0000000000000000000000000000000000000000000000000000000000000001".to_string();
737 let salt = Some(
738 "0000000000000000000000000000000000000000000000000000000000000042".to_string(),
739 );
740 let result = convert_create_contract(source, wasm_hash, salt, None);
741
742 assert!(result.is_ok());
743 }
744 }
745
746 #[test]
748 fn test_invoke_contract() {
749 let spec = HostFunctionSpec::InvokeContract {
750 contract_address: TEST_CONTRACT.to_string(),
751 function_name: "hello".to_string(),
752 args: vec![json!({"string": "world"})],
753 };
754
755 let result = HostFunction::try_from(spec);
756 assert!(result.is_ok());
757 assert!(matches!(result.unwrap(), HostFunction::InvokeContract(_)));
758 }
759
760 #[test]
761 fn test_upload_wasm() {
762 let spec = HostFunctionSpec::UploadWasm {
763 wasm: WasmSource::Hex {
764 hex: "deadbeef".to_string(),
765 },
766 };
767
768 let result = HostFunction::try_from(spec);
769 assert!(result.is_ok());
770 assert!(matches!(
771 result.unwrap(),
772 HostFunction::UploadContractWasm(_)
773 ));
774 }
775
776 #[test]
777 fn test_create_contract_v1() {
778 let spec = HostFunctionSpec::CreateContract {
779 source: ContractSource::Address {
780 address: TEST_PK.to_string(),
781 },
782 wasm_hash: "0000000000000000000000000000000000000000000000000000000000000001"
783 .to_string(),
784 salt: None,
785 constructor_args: None,
786 };
787
788 let result = HostFunction::try_from(spec);
789 assert!(result.is_ok());
790 assert!(matches!(result.unwrap(), HostFunction::CreateContract(_)));
791 }
792
793 #[test]
794 fn test_create_contract_v2() {
795 let spec = HostFunctionSpec::CreateContract {
796 source: ContractSource::Address {
797 address: TEST_PK.to_string(),
798 },
799 wasm_hash: "0000000000000000000000000000000000000000000000000000000000000001"
800 .to_string(),
801 salt: None,
802 constructor_args: Some(vec![json!({"string": "init"})]),
803 };
804
805 let result = HostFunction::try_from(spec);
806 assert!(result.is_ok());
807 assert!(matches!(result.unwrap(), HostFunction::CreateContractV2(_)));
808 }
809
810 #[test]
811 fn test_host_function_spec_serde() {
812 let spec = HostFunctionSpec::InvokeContract {
813 contract_address: TEST_CONTRACT.to_string(),
814 function_name: "test".to_string(),
815 args: vec![json!({"u64": 42})],
816 };
817 let json = serde_json::to_string(&spec).unwrap();
818 assert!(json.contains("invoke_contract"));
819 assert!(json.contains(TEST_CONTRACT));
820
821 let deserialized: HostFunctionSpec = serde_json::from_str(&json).unwrap();
822 assert_eq!(spec, deserialized);
823 }
824
825 #[test]
826 fn test_u64_string_to_number_conversion() {
827 let args = vec![
829 json!({"u64": "1000"}),
830 json!({"i64": "-500"}),
831 json!({"timepoint": "123456"}),
832 json!({"duration": "7890"}),
833 ];
834
835 let result = convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), args);
836 assert!(
837 result.is_ok(),
838 "Should successfully convert string u64/i64 to numbers"
839 );
840
841 let u128_arg = vec![json!({"u128": {"hi": "100", "lo": "200"}})];
843 let result =
844 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), u128_arg);
845 assert!(result.is_ok(), "Should successfully convert u128 parts");
846
847 let i128_arg = vec![json!({"i128": {"hi": "-100", "lo": "200"}})];
849 let result =
850 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), i128_arg);
851 assert!(result.is_ok(), "Should successfully convert i128 parts");
852
853 let u256_arg =
855 vec![json!({"u256": {"hi_hi": "1", "hi_lo": "2", "lo_hi": "3", "lo_lo": "4"}})];
856 let result =
857 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), u256_arg);
858 assert!(result.is_ok(), "Should successfully convert u256 parts");
859
860 let i256_arg =
862 vec![json!({"i256": {"hi_hi": "-1", "hi_lo": "2", "lo_hi": "3", "lo_lo": "4"}})];
863 let result =
864 convert_invoke_contract(TEST_CONTRACT.to_string(), "test".to_string(), i256_arg);
865 assert!(result.is_ok(), "Should successfully convert i256 parts");
866 }
867
868 #[test]
869 fn test_host_function_spec_json_format() {
870 let invoke = HostFunctionSpec::InvokeContract {
872 contract_address: TEST_CONTRACT.to_string(),
873 function_name: "test".to_string(),
874 args: vec![json!({"u64": 42})],
875 };
876 let invoke_json = serde_json::to_value(&invoke).unwrap();
877 assert_eq!(invoke_json["type"], "invoke_contract");
878 assert_eq!(invoke_json["contract_address"], TEST_CONTRACT);
879 assert_eq!(invoke_json["function_name"], "test");
880
881 let upload = HostFunctionSpec::UploadWasm {
883 wasm: WasmSource::Hex {
884 hex: "deadbeef".to_string(),
885 },
886 };
887 let upload_json = serde_json::to_value(&upload).unwrap();
888 assert_eq!(upload_json["type"], "upload_wasm");
889 assert!(upload_json["wasm"].is_object());
890
891 let create = HostFunctionSpec::CreateContract {
893 source: ContractSource::Address {
894 address: TEST_PK.to_string(),
895 },
896 wasm_hash: "0000000000000000000000000000000000000000000000000000000000000001"
897 .to_string(),
898 salt: None,
899 constructor_args: None,
900 };
901 let create_json = serde_json::to_value(&create).unwrap();
902 assert_eq!(create_json["type"], "create_contract");
903 assert_eq!(create_json["source"]["from"], "address");
904 assert_eq!(create_json["source"]["address"], TEST_PK);
905 }
906}