1use crate::error::{MovementError, MovementResult};
33use serde::{Deserialize, Serialize};
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct SimulationResult {
41 success: bool,
43 vm_status: String,
45 gas_used: u64,
47 max_gas_amount: u64,
49 gas_unit_price: u64,
51 changes: Vec<StateChange>,
53 events: Vec<SimulatedEvent>,
55 hash: String,
57 vm_error: Option<VmError>,
59 raw: serde_json::Value,
61}
62
63impl SimulationResult {
64 pub fn from_response(response: Vec<serde_json::Value>) -> MovementResult<Self> {
70 let data = response
71 .into_iter()
72 .next()
73 .ok_or_else(|| MovementError::Api {
74 status_code: 200,
75 message: "Empty simulation response".into(),
76 error_code: None,
77 vm_error_code: None,
78 })?;
79
80 Self::from_json(data)
81 }
82
83 pub fn from_json(data: serde_json::Value) -> MovementResult<Self> {
89 let success = data
90 .get("success")
91 .and_then(serde_json::Value::as_bool)
92 .unwrap_or(false);
93
94 let vm_status = data
95 .get("vm_status")
96 .and_then(serde_json::Value::as_str)
97 .unwrap_or("Unknown")
98 .to_string();
99
100 let gas_used = data
101 .get("gas_used")
102 .and_then(serde_json::Value::as_str)
103 .and_then(|s| s.parse().ok())
104 .unwrap_or(0);
105
106 let max_gas_amount = data
107 .get("max_gas_amount")
108 .and_then(serde_json::Value::as_str)
109 .and_then(|s| s.parse().ok())
110 .unwrap_or(0);
111
112 let gas_unit_price = data
113 .get("gas_unit_price")
114 .and_then(serde_json::Value::as_str)
115 .and_then(|s| s.parse().ok())
116 .unwrap_or(0);
117
118 let hash = data
119 .get("hash")
120 .and_then(serde_json::Value::as_str)
121 .unwrap_or("")
122 .to_string();
123
124 let changes = data
126 .get("changes")
127 .and_then(serde_json::Value::as_array)
128 .map(|arr| arr.iter().map(StateChange::from_json).collect())
129 .unwrap_or_default();
130
131 let events = data
133 .get("events")
134 .and_then(|v| v.as_array())
135 .map(|arr| arr.iter().map(SimulatedEvent::from_json).collect())
136 .unwrap_or_default();
137
138 let vm_error = if success {
140 None
141 } else {
142 Some(VmError::from_status(&vm_status))
143 };
144
145 Ok(Self {
146 success,
147 vm_status,
148 gas_used,
149 max_gas_amount,
150 gas_unit_price,
151 changes,
152 events,
153 hash,
154 vm_error,
155 raw: data,
156 })
157 }
158
159 pub fn success(&self) -> bool {
161 self.success
162 }
163
164 pub fn failed(&self) -> bool {
166 !self.success
167 }
168
169 pub fn vm_status(&self) -> &str {
171 &self.vm_status
172 }
173
174 pub fn gas_used(&self) -> u64 {
176 self.gas_used
177 }
178
179 pub fn max_gas_amount(&self) -> u64 {
181 self.max_gas_amount
182 }
183
184 pub fn gas_unit_price(&self) -> u64 {
186 self.gas_unit_price
187 }
188
189 pub fn gas_cost(&self) -> u64 {
191 self.gas_used.saturating_mul(self.gas_unit_price)
192 }
193
194 pub fn safe_gas_estimate(&self) -> u64 {
198 self.gas_used.saturating_mul(120) / 100
199 }
200
201 pub fn changes(&self) -> &[StateChange] {
203 &self.changes
204 }
205
206 pub fn events(&self) -> &[SimulatedEvent] {
208 &self.events
209 }
210
211 pub fn hash(&self) -> &str {
213 &self.hash
214 }
215
216 pub fn vm_error(&self) -> Option<&VmError> {
218 self.vm_error.as_ref()
219 }
220
221 pub fn raw(&self) -> &serde_json::Value {
223 &self.raw
224 }
225
226 pub fn is_insufficient_balance(&self) -> bool {
228 self.vm_error
229 .as_ref()
230 .is_some_and(VmError::is_insufficient_balance)
231 }
232
233 pub fn is_sequence_number_error(&self) -> bool {
235 self.vm_error
236 .as_ref()
237 .is_some_and(VmError::is_sequence_number_error)
238 }
239
240 pub fn is_out_of_gas(&self) -> bool {
242 self.vm_error.as_ref().is_some_and(VmError::is_out_of_gas)
243 }
244
245 pub fn error_message(&self) -> Option<String> {
247 if self.success {
248 return None;
249 }
250
251 self.vm_error
252 .as_ref()
253 .map(VmError::user_message)
254 .or_else(|| Some(self.vm_status.clone()))
255 }
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct StateChange {
261 pub change_type: String,
263 pub address: String,
265 pub resource_type: Option<String>,
267 pub module: Option<String>,
269 pub data: Option<serde_json::Value>,
271}
272
273impl StateChange {
274 fn from_json(json: &serde_json::Value) -> Self {
275 Self {
276 change_type: json
277 .get("type")
278 .and_then(serde_json::Value::as_str)
279 .unwrap_or("unknown")
280 .to_string(),
281 address: json
282 .get("address")
283 .and_then(serde_json::Value::as_str)
284 .unwrap_or("")
285 .to_string(),
286 resource_type: json
287 .get("data")
288 .and_then(|d| d.get("type"))
289 .and_then(serde_json::Value::as_str)
290 .map(ToString::to_string),
291 module: json
292 .get("module")
293 .and_then(serde_json::Value::as_str)
294 .map(ToString::to_string),
295 data: json.get("data").cloned(),
296 }
297 }
298
299 pub fn is_write(&self) -> bool {
301 self.change_type == "write_resource"
302 }
303
304 pub fn is_delete(&self) -> bool {
306 self.change_type == "delete_resource"
307 }
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct SimulatedEvent {
313 pub event_type: String,
315 pub sequence_number: u64,
317 pub data: serde_json::Value,
319}
320
321impl SimulatedEvent {
322 fn from_json(json: &serde_json::Value) -> Self {
323 Self {
324 event_type: json
325 .get("type")
326 .and_then(|v| v.as_str())
327 .unwrap_or("")
328 .to_string(),
329 sequence_number: json
330 .get("sequence_number")
331 .and_then(|v| v.as_str())
332 .and_then(|s| s.parse().ok())
333 .unwrap_or(0),
334 data: json.get("data").cloned().unwrap_or(serde_json::Value::Null),
335 }
336 }
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct VmError {
342 pub category: VmErrorCategory,
344 pub status: String,
346 pub abort_code: Option<u64>,
348 pub location: Option<String>,
350}
351
352impl VmError {
353 fn from_status(status: &str) -> Self {
354 let category = VmErrorCategory::from_status(status);
355
356 let abort_code = if status.contains("ABORTED") {
358 status
360 .split('(')
361 .nth(1)
362 .and_then(|s| s.trim_end_matches(')').parse().ok())
363 } else {
364 None
365 };
366
367 let location = if status.contains("::") {
369 status
370 .split_whitespace()
371 .find(|s| s.contains("::"))
372 .map(|s| s.trim_end_matches(':').to_string())
373 } else {
374 None
375 };
376
377 Self {
378 category,
379 status: status.to_string(),
380 abort_code,
381 location,
382 }
383 }
384
385 pub fn is_insufficient_balance(&self) -> bool {
387 matches!(self.category, VmErrorCategory::InsufficientBalance)
388 || self.status.contains("INSUFFICIENT")
389 || self.status.contains("NOT_ENOUGH")
390 }
391
392 pub fn is_sequence_number_error(&self) -> bool {
394 matches!(self.category, VmErrorCategory::SequenceNumber)
395 }
396
397 pub fn is_out_of_gas(&self) -> bool {
399 matches!(self.category, VmErrorCategory::OutOfGas)
400 }
401
402 pub fn user_message(&self) -> String {
404 match self.category {
405 VmErrorCategory::InsufficientBalance => {
406 "Insufficient balance to complete this transaction".to_string()
407 }
408 VmErrorCategory::SequenceNumber => {
409 "Transaction sequence number mismatch - the account's sequence number may have changed".to_string()
410 }
411 VmErrorCategory::OutOfGas => {
412 "Transaction ran out of gas - try increasing max_gas_amount".to_string()
413 }
414 VmErrorCategory::MoveAbort => {
415 if let Some(code) = self.abort_code {
416 format!("Transaction aborted with code {code}")
417 } else {
418 "Transaction was aborted by the Move VM".to_string()
419 }
420 }
421 VmErrorCategory::ResourceNotFound => {
422 "Required resource not found on chain".to_string()
423 }
424 VmErrorCategory::ModuleNotFound => {
425 "Required module not found on chain".to_string()
426 }
427 VmErrorCategory::FunctionNotFound => {
428 "Function not found in the specified module".to_string()
429 }
430 VmErrorCategory::TypeMismatch => {
431 "Type argument mismatch in function call".to_string()
432 }
433 VmErrorCategory::Unknown => self.status.clone(),
434 }
435 }
436}
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
440pub enum VmErrorCategory {
441 InsufficientBalance,
443 SequenceNumber,
445 OutOfGas,
447 MoveAbort,
449 ResourceNotFound,
451 ModuleNotFound,
453 FunctionNotFound,
455 TypeMismatch,
457 Unknown,
459}
460
461impl VmErrorCategory {
462 fn from_status(status: &str) -> Self {
463 let status_upper = status.to_uppercase();
464
465 if status_upper.contains("INSUFFICIENT") || status_upper.contains("NOT_ENOUGH") {
466 Self::InsufficientBalance
467 } else if status_upper.contains("SEQUENCE_NUMBER")
468 || status_upper.contains("SEQUENCE NUMBER")
469 {
470 Self::SequenceNumber
471 } else if status_upper.contains("OUT_OF_GAS") || status_upper.contains("OUT OF GAS") {
472 Self::OutOfGas
473 } else if status_upper.contains("ABORT") {
474 Self::MoveAbort
475 } else if status_upper.contains("RESOURCE") && status_upper.contains("NOT") {
476 Self::ResourceNotFound
477 } else if status_upper.contains("MODULE") && status_upper.contains("NOT") {
478 Self::ModuleNotFound
479 } else if status_upper.contains("FUNCTION") && status_upper.contains("NOT") {
480 Self::FunctionNotFound
481 } else if status_upper.contains("TYPE")
482 && (status_upper.contains("MISMATCH") || status_upper.contains("ERROR"))
483 {
484 Self::TypeMismatch
485 } else {
486 Self::Unknown
487 }
488 }
489}
490
491#[derive(Debug, Clone, Default)]
493pub struct SimulationOptions {
494 pub estimate_gas_only: bool,
496 pub sequence_number_override: Option<u64>,
498 pub gas_unit_price_override: Option<u64>,
500 pub max_gas_amount_override: Option<u64>,
502}
503
504impl SimulationOptions {
505 #[must_use]
507 pub fn new() -> Self {
508 Self::default()
509 }
510
511 #[must_use]
513 pub fn estimate_gas_only(mut self) -> Self {
514 self.estimate_gas_only = true;
515 self
516 }
517
518 #[must_use]
520 pub fn with_sequence_number(mut self, seq: u64) -> Self {
521 self.sequence_number_override = Some(seq);
522 self
523 }
524
525 #[must_use]
527 pub fn with_gas_unit_price(mut self, price: u64) -> Self {
528 self.gas_unit_price_override = Some(price);
529 self
530 }
531
532 #[must_use]
534 pub fn with_max_gas_amount(mut self, amount: u64) -> Self {
535 self.max_gas_amount_override = Some(amount);
536 self
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn test_parse_success_result() {
546 let json = serde_json::json!({
547 "success": true,
548 "vm_status": "Executed successfully",
549 "gas_used": "100",
550 "max_gas_amount": "200000",
551 "gas_unit_price": "100",
552 "hash": "0x123",
553 "changes": [],
554 "events": []
555 });
556
557 let result = SimulationResult::from_json(json).unwrap();
558 assert!(result.success());
559 assert_eq!(result.gas_used(), 100);
560 assert_eq!(result.gas_cost(), 10000);
561 }
562
563 #[test]
564 fn test_parse_failed_result() {
565 let json = serde_json::json!({
566 "success": false,
567 "vm_status": "Move abort in 0x1::coin: EINSUFFICIENT_BALANCE(0x10001)",
568 "gas_used": "50",
569 "max_gas_amount": "200000",
570 "gas_unit_price": "100",
571 "hash": "0x456",
572 "changes": [],
573 "events": []
574 });
575
576 let result = SimulationResult::from_json(json).unwrap();
577 assert!(result.failed());
578 assert!(result.is_insufficient_balance());
579 assert!(result.vm_error().is_some());
580 }
581
582 #[test]
583 fn test_error_categories() {
584 assert_eq!(
585 VmErrorCategory::from_status("INSUFFICIENT_BALANCE"),
586 VmErrorCategory::InsufficientBalance
587 );
588 assert_eq!(
589 VmErrorCategory::from_status("SEQUENCE_NUMBER_TOO_OLD"),
590 VmErrorCategory::SequenceNumber
591 );
592 assert_eq!(
593 VmErrorCategory::from_status("OUT_OF_GAS"),
594 VmErrorCategory::OutOfGas
595 );
596 assert_eq!(
597 VmErrorCategory::from_status("Move abort"),
598 VmErrorCategory::MoveAbort
599 );
600 }
601
602 #[test]
603 fn test_safe_gas_estimate() {
604 let json = serde_json::json!({
605 "success": true,
606 "vm_status": "Executed successfully",
607 "gas_used": "1000",
608 "max_gas_amount": "200000",
609 "gas_unit_price": "100",
610 "hash": "0x123",
611 "changes": [],
612 "events": []
613 });
614
615 let result = SimulationResult::from_json(json).unwrap();
616 assert_eq!(result.gas_used(), 1000);
617 assert_eq!(result.safe_gas_estimate(), 1200); }
619
620 #[test]
621 fn test_parse_events() {
622 let json = serde_json::json!({
623 "success": true,
624 "vm_status": "Executed successfully",
625 "gas_used": "100",
626 "max_gas_amount": "200000",
627 "gas_unit_price": "100",
628 "hash": "0x123",
629 "changes": [],
630 "events": [
631 {
632 "type": "0x1::coin::DepositEvent",
633 "sequence_number": "5",
634 "data": {"amount": "1000"}
635 }
636 ]
637 });
638
639 let result = SimulationResult::from_json(json).unwrap();
640 assert_eq!(result.events().len(), 1);
641 assert_eq!(result.events()[0].event_type, "0x1::coin::DepositEvent");
642 assert_eq!(result.events()[0].sequence_number, 5);
643 }
644
645 #[test]
646 fn test_parse_changes() {
647 let json = serde_json::json!({
648 "success": true,
649 "vm_status": "Executed successfully",
650 "gas_used": "100",
651 "max_gas_amount": "200000",
652 "gas_unit_price": "100",
653 "hash": "0x123",
654 "changes": [
655 {
656 "type": "write_resource",
657 "address": "0x1",
658 "data": {"type": "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"}
659 }
660 ],
661 "events": []
662 });
663
664 let result = SimulationResult::from_json(json).unwrap();
665 assert_eq!(result.changes().len(), 1);
666 assert!(result.changes()[0].is_write());
667 }
668
669 #[test]
670 fn test_simulation_options_default() {
671 let opts = SimulationOptions::default();
672 assert!(!opts.estimate_gas_only);
673 assert!(opts.sequence_number_override.is_none());
674 assert!(opts.gas_unit_price_override.is_none());
675 assert!(opts.max_gas_amount_override.is_none());
676 }
677
678 #[test]
679 fn test_simulation_options_builder() {
680 let opts = SimulationOptions::new()
681 .estimate_gas_only()
682 .with_sequence_number(5)
683 .with_gas_unit_price(200)
684 .with_max_gas_amount(500_000);
685
686 assert!(opts.estimate_gas_only);
687 assert_eq!(opts.sequence_number_override, Some(5));
688 assert_eq!(opts.gas_unit_price_override, Some(200));
689 assert_eq!(opts.max_gas_amount_override, Some(500_000));
690 }
691
692 #[test]
693 fn test_vm_error_category_resource_not_found() {
694 assert_eq!(
695 VmErrorCategory::from_status("RESOURCE_NOT_FOUND"),
696 VmErrorCategory::ResourceNotFound
697 );
698 }
699
700 #[test]
701 fn test_vm_error_category_module_not_found() {
702 assert_eq!(
703 VmErrorCategory::from_status("MODULE_NOT_PUBLISHED"),
704 VmErrorCategory::ModuleNotFound
705 );
706 }
707
708 #[test]
709 fn test_vm_error_category_function_not_found() {
710 assert_eq!(
711 VmErrorCategory::from_status("FUNCTION_NOT_FOUND"),
712 VmErrorCategory::FunctionNotFound
713 );
714 }
715
716 #[test]
717 fn test_vm_error_category_type_mismatch() {
718 assert_eq!(
719 VmErrorCategory::from_status("TYPE_MISMATCH"),
720 VmErrorCategory::TypeMismatch
721 );
722 assert_eq!(
723 VmErrorCategory::from_status("TYPE_ERROR"),
724 VmErrorCategory::TypeMismatch
725 );
726 }
727
728 #[test]
729 fn test_vm_error_category_unknown() {
730 assert_eq!(
731 VmErrorCategory::from_status("SOME_RANDOM_ERROR"),
732 VmErrorCategory::Unknown
733 );
734 }
735
736 #[test]
737 fn test_simulation_result_accessors() {
738 let json = serde_json::json!({
739 "success": true,
740 "vm_status": "Executed successfully",
741 "gas_used": "1500",
742 "max_gas_amount": "200000",
743 "gas_unit_price": "100",
744 "hash": "0xabc123",
745 "changes": [],
746 "events": []
747 });
748
749 let result = SimulationResult::from_json(json).unwrap();
750 assert!(result.success());
751 assert!(!result.failed());
752 assert_eq!(result.vm_status(), "Executed successfully");
753 assert_eq!(result.gas_used(), 1500);
754 assert_eq!(result.max_gas_amount(), 200_000);
755 assert_eq!(result.gas_unit_price(), 100);
756 assert_eq!(result.gas_cost(), 150_000); assert_eq!(result.hash(), "0xabc123");
758 assert!(result.events().is_empty());
759 assert!(result.changes().is_empty());
760 }
761
762 #[test]
763 fn test_simulation_result_from_response() {
764 let response = vec![serde_json::json!({
765 "success": true,
766 "vm_status": "Executed successfully",
767 "gas_used": "100",
768 "max_gas_amount": "200000",
769 "gas_unit_price": "100",
770 "hash": "0x123",
771 "changes": [],
772 "events": []
773 })];
774
775 let result = SimulationResult::from_response(response).unwrap();
776 assert!(result.success());
777 }
778
779 #[test]
780 fn test_simulation_result_from_empty_response() {
781 let response: Vec<serde_json::Value> = vec![];
782 let result = SimulationResult::from_response(response);
783 assert!(result.is_err());
784 }
785
786 #[test]
787 fn test_state_change_delete() {
788 let json = serde_json::json!({
789 "success": true,
790 "vm_status": "Executed successfully",
791 "gas_used": "100",
792 "max_gas_amount": "200000",
793 "gas_unit_price": "100",
794 "hash": "0x123",
795 "changes": [
796 {
797 "type": "delete_resource",
798 "address": "0x1",
799 "data": {}
800 }
801 ],
802 "events": []
803 });
804
805 let result = SimulationResult::from_json(json).unwrap();
806 assert_eq!(result.changes().len(), 1);
807 assert!(result.changes()[0].is_delete());
808 assert!(!result.changes()[0].is_write());
809 }
810
811 #[test]
812 fn test_simulation_result_with_vm_error() {
813 let json = serde_json::json!({
814 "success": false,
815 "vm_status": "INSUFFICIENT_BALANCE",
816 "gas_used": "0",
817 "max_gas_amount": "200000",
818 "gas_unit_price": "100",
819 "hash": "0x123",
820 "changes": [],
821 "events": []
822 });
823
824 let result = SimulationResult::from_json(json).unwrap();
825 assert!(result.failed());
826 assert!(result.is_insufficient_balance());
827 assert!(!result.is_out_of_gas());
828 assert!(!result.is_sequence_number_error());
829 }
830
831 #[test]
832 fn test_simulation_result_out_of_gas() {
833 let json = serde_json::json!({
834 "success": false,
835 "vm_status": "OUT_OF_GAS",
836 "gas_used": "200000",
837 "max_gas_amount": "200000",
838 "gas_unit_price": "100",
839 "hash": "0x123",
840 "changes": [],
841 "events": []
842 });
843
844 let result = SimulationResult::from_json(json).unwrap();
845 assert!(result.is_out_of_gas());
846 }
847
848 #[test]
849 fn test_simulation_result_sequence_error() {
850 let json = serde_json::json!({
851 "success": false,
852 "vm_status": "SEQUENCE_NUMBER_TOO_OLD",
853 "gas_used": "0",
854 "max_gas_amount": "200000",
855 "gas_unit_price": "100",
856 "hash": "0x123",
857 "changes": [],
858 "events": []
859 });
860
861 let result = SimulationResult::from_json(json).unwrap();
862 assert!(result.is_sequence_number_error());
863 }
864
865 #[test]
866 fn test_simulated_event_parsing() {
867 let json = serde_json::json!({
868 "success": true,
869 "vm_status": "Executed successfully",
870 "gas_used": "100",
871 "max_gas_amount": "200000",
872 "gas_unit_price": "100",
873 "hash": "0x123",
874 "changes": [],
875 "events": [
876 {
877 "type": "0x1::coin::WithdrawEvent",
878 "sequence_number": "10",
879 "data": {"amount": "500"}
880 },
881 {
882 "type": "0x1::coin::DepositEvent",
883 "sequence_number": "20",
884 "data": {"amount": "500"}
885 }
886 ]
887 });
888
889 let result = SimulationResult::from_json(json).unwrap();
890 assert_eq!(result.events().len(), 2);
891 assert_eq!(result.events()[0].event_type, "0x1::coin::WithdrawEvent");
892 assert_eq!(result.events()[0].sequence_number, 10);
893 assert_eq!(result.events()[1].event_type, "0x1::coin::DepositEvent");
894 assert_eq!(result.events()[1].sequence_number, 20);
895 }
896
897 #[test]
898 fn test_vm_error_user_messages() {
899 let insufficient = VmError::from_status("INSUFFICIENT_BALANCE");
901 assert!(insufficient.user_message().contains("Insufficient"));
902
903 let seq_error = VmError::from_status("SEQUENCE_NUMBER_TOO_OLD");
904 assert!(seq_error.user_message().contains("sequence number"));
905
906 let out_of_gas = VmError::from_status("OUT_OF_GAS");
907 assert!(out_of_gas.user_message().contains("gas"));
908
909 let resource_not_found = VmError::from_status("RESOURCE_NOT_FOUND");
910 assert!(resource_not_found.user_message().contains("resource"));
911
912 let module_not_found = VmError::from_status("MODULE_NOT_PUBLISHED");
913 assert!(module_not_found.user_message().contains("module"));
914
915 let function_not_found = VmError::from_status("FUNCTION_NOT_FOUND");
916 assert!(function_not_found.user_message().contains("Function"));
917
918 let type_mismatch = VmError::from_status("TYPE_MISMATCH");
919 assert!(type_mismatch.user_message().contains("Type"));
920
921 let unknown = VmError::from_status("UNKNOWN_ERROR_XYZ");
922 assert_eq!(unknown.user_message(), "UNKNOWN_ERROR_XYZ");
923 }
924
925 #[test]
926 fn test_vm_error_move_abort_with_code() {
927 let abort = VmError::from_status("ABORTED in 0x1::coin: SOME_ERROR(65537)");
929 assert_eq!(abort.category, VmErrorCategory::MoveAbort);
930 assert_eq!(abort.abort_code, Some(65537));
931 assert!(abort.location.is_some());
932 assert!(abort.user_message().contains("65537"));
933 }
934
935 #[test]
936 fn test_vm_error_move_abort_without_code() {
937 let abort = VmError::from_status("Move abort");
938 assert_eq!(abort.category, VmErrorCategory::MoveAbort);
939 assert!(abort.abort_code.is_none());
940 assert!(abort.user_message().contains("aborted"));
941 }
942
943 #[test]
944 fn test_simulation_result_error_message_success() {
945 let json = serde_json::json!({
946 "success": true,
947 "vm_status": "Executed successfully",
948 "gas_used": "100",
949 "max_gas_amount": "200000",
950 "gas_unit_price": "100",
951 "hash": "0x123",
952 "changes": [],
953 "events": []
954 });
955
956 let result = SimulationResult::from_json(json).unwrap();
957 assert!(result.error_message().is_none());
958 }
959
960 #[test]
961 fn test_simulation_result_error_message_failure() {
962 let json = serde_json::json!({
963 "success": false,
964 "vm_status": "INSUFFICIENT_BALANCE",
965 "gas_used": "0",
966 "max_gas_amount": "200000",
967 "gas_unit_price": "100",
968 "hash": "0x123",
969 "changes": [],
970 "events": []
971 });
972
973 let result = SimulationResult::from_json(json).unwrap();
974 let error_msg = result.error_message().unwrap();
975 assert!(error_msg.contains("Insufficient"));
976 }
977
978 #[test]
979 fn test_simulation_result_raw_accessor() {
980 let json = serde_json::json!({
981 "success": true,
982 "vm_status": "Executed successfully",
983 "gas_used": "100",
984 "max_gas_amount": "200000",
985 "gas_unit_price": "100",
986 "hash": "0x123",
987 "changes": [],
988 "events": [],
989 "extra_field": "extra_value"
990 });
991
992 let result = SimulationResult::from_json(json).unwrap();
993 let raw = result.raw();
994 assert_eq!(
995 raw.get("extra_field").unwrap().as_str(),
996 Some("extra_value")
997 );
998 }
999
1000 #[test]
1001 fn test_state_change_with_module() {
1002 let json = serde_json::json!({
1003 "type": "write_module",
1004 "address": "0x1",
1005 "module": "my_module"
1006 });
1007
1008 let change = StateChange::from_json(&json);
1009 assert_eq!(change.change_type, "write_module");
1010 assert_eq!(change.module, Some("my_module".to_string()));
1011 }
1012
1013 #[test]
1014 fn test_simulated_event_with_null_data() {
1015 let json = serde_json::json!({
1016 "type": "0x1::event::SomeEvent",
1017 "sequence_number": "5"
1018 });
1020
1021 let event = SimulatedEvent::from_json(&json);
1022 assert_eq!(event.event_type, "0x1::event::SomeEvent");
1023 assert_eq!(event.sequence_number, 5);
1024 assert!(event.data.is_null());
1025 }
1026
1027 #[test]
1028 fn test_vm_error_not_enough_variant() {
1029 let error = VmError::from_status("NOT_ENOUGH_GAS");
1030 assert!(error.is_insufficient_balance() || error.status.contains("NOT_ENOUGH"));
1031 }
1032
1033 #[test]
1034 fn test_vm_error_category_sequence_number_variant() {
1035 assert_eq!(
1037 VmErrorCategory::from_status("SEQUENCE NUMBER INVALID"),
1038 VmErrorCategory::SequenceNumber
1039 );
1040 }
1041
1042 #[test]
1043 fn test_vm_error_category_out_of_gas_with_space() {
1044 assert_eq!(
1045 VmErrorCategory::from_status("OUT OF GAS"),
1046 VmErrorCategory::OutOfGas
1047 );
1048 }
1049
1050 #[test]
1051 fn test_simulation_result_missing_fields() {
1052 let json = serde_json::json!({});
1054
1055 let result = SimulationResult::from_json(json).unwrap();
1056 assert!(!result.success());
1057 assert_eq!(result.gas_used(), 0);
1058 assert_eq!(result.vm_status(), "Unknown");
1059 }
1060
1061 #[test]
1062 fn test_simulation_result_vm_error_accessor() {
1063 let json = serde_json::json!({
1064 "success": false,
1065 "vm_status": "ABORT",
1066 "gas_used": "0",
1067 "max_gas_amount": "200000",
1068 "gas_unit_price": "100",
1069 "hash": "0x123",
1070 "changes": [],
1071 "events": []
1072 });
1073
1074 let result = SimulationResult::from_json(json).unwrap();
1075 assert!(result.vm_error().is_some());
1076 let vm_error = result.vm_error().unwrap();
1077 assert_eq!(vm_error.category, VmErrorCategory::MoveAbort);
1078 }
1079
1080 #[test]
1081 fn test_simulation_options_new() {
1082 let opts = SimulationOptions::new();
1083 assert!(!opts.estimate_gas_only);
1084 }
1085}