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