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