openzeppelin_relayer/domain/transaction/stellar/
utils.rs1use crate::models::OperationSpec;
3use crate::models::RelayerError;
4
5pub fn needs_simulation(operations: &[OperationSpec]) -> bool {
7 operations.iter().any(|op| {
8 matches!(
9 op,
10 OperationSpec::InvokeContract { .. }
11 | OperationSpec::CreateContract { .. }
12 | OperationSpec::UploadWasm { .. }
13 )
14 })
15}
16
17pub fn next_sequence_u64(seq_num: i64) -> Result<u64, RelayerError> {
18 let next_i64 = seq_num
19 .checked_add(1)
20 .ok_or_else(|| RelayerError::ProviderError("sequence overflow".into()))?;
21 u64::try_from(next_i64)
22 .map_err(|_| RelayerError::ProviderError("sequence overflows u64".into()))
23}
24
25pub fn i64_from_u64(value: u64) -> Result<i64, RelayerError> {
26 i64::try_from(value).map_err(|_| RelayerError::ProviderError("u64→i64 overflow".into()))
27}
28
29#[cfg(test)]
30mod tests {
31 use super::*;
32 use crate::models::AssetSpec;
33 use crate::models::{AuthSpec, ContractSource, WasmSource};
34
35 const TEST_PK: &str = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
36
37 fn payment_op(destination: &str) -> OperationSpec {
38 OperationSpec::Payment {
39 destination: destination.to_string(),
40 amount: 100,
41 asset: AssetSpec::Native,
42 }
43 }
44
45 #[test]
46 fn returns_false_for_only_payment_ops() {
47 let ops = vec![payment_op(TEST_PK)];
48 assert!(!needs_simulation(&ops));
49 }
50
51 #[test]
52 fn returns_true_for_invoke_contract_ops() {
53 let ops = vec![OperationSpec::InvokeContract {
54 contract_address: "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA"
55 .to_string(),
56 function_name: "transfer".to_string(),
57 args: vec![],
58 auth: None,
59 }];
60 assert!(needs_simulation(&ops));
61 }
62
63 #[test]
64 fn returns_true_for_upload_wasm_ops() {
65 let ops = vec![OperationSpec::UploadWasm {
66 wasm: WasmSource::Hex {
67 hex: "deadbeef".to_string(),
68 },
69 auth: None,
70 }];
71 assert!(needs_simulation(&ops));
72 }
73
74 #[test]
75 fn returns_true_for_create_contract_ops() {
76 let ops = vec![OperationSpec::CreateContract {
77 source: ContractSource::Address {
78 address: TEST_PK.to_string(),
79 },
80 wasm_hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
81 .to_string(),
82 salt: None,
83 constructor_args: None,
84 auth: None,
85 }];
86 assert!(needs_simulation(&ops));
87 }
88
89 #[test]
90 fn returns_true_for_single_invoke_host_function() {
91 let ops = vec![OperationSpec::InvokeContract {
92 contract_address: "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA"
93 .to_string(),
94 function_name: "transfer".to_string(),
95 args: vec![],
96 auth: Some(AuthSpec::SourceAccount),
97 }];
98 assert!(needs_simulation(&ops));
99 }
100
101 #[test]
102 fn returns_false_for_multiple_payment_ops() {
103 let ops = vec![payment_op(TEST_PK), payment_op(TEST_PK)];
104 assert!(!needs_simulation(&ops));
105 }
106
107 mod next_sequence_u64_tests {
108 use super::*;
109
110 #[test]
111 fn test_increment() {
112 assert_eq!(next_sequence_u64(0).unwrap(), 1);
113
114 assert_eq!(next_sequence_u64(12345).unwrap(), 12346);
115 }
116
117 #[test]
118 fn test_error_path_overflow_i64_max() {
119 let result = next_sequence_u64(i64::MAX);
120 assert!(result.is_err());
121 match result.unwrap_err() {
122 RelayerError::ProviderError(msg) => assert_eq!(msg, "sequence overflow"),
123 _ => panic!("Unexpected error type"),
124 }
125 }
126 }
127
128 mod i64_from_u64_tests {
129 use super::*;
130
131 #[test]
132 fn test_happy_path_conversion() {
133 assert_eq!(i64_from_u64(0).unwrap(), 0);
134 assert_eq!(i64_from_u64(12345).unwrap(), 12345);
135 assert_eq!(i64_from_u64(i64::MAX as u64).unwrap(), i64::MAX);
136 }
137
138 #[test]
139 fn test_error_path_overflow_u64_max() {
140 let result = i64_from_u64(u64::MAX);
141 assert!(result.is_err());
142 match result.unwrap_err() {
143 RelayerError::ProviderError(msg) => assert_eq!(msg, "u64→i64 overflow"),
144 _ => panic!("Unexpected error type"),
145 }
146 }
147
148 #[test]
149 fn test_edge_case_just_above_i64_max() {
150 let value = (i64::MAX as u64) + 1;
152 let result = i64_from_u64(value);
153 assert!(result.is_err());
154 match result.unwrap_err() {
155 RelayerError::ProviderError(msg) => assert_eq!(msg, "u64→i64 overflow"),
156 _ => panic!("Unexpected error type"),
157 }
158 }
159 }
160}