openzeppelin_relayer/domain/relayer/stellar/
utils.rs1use crate::constants::STELLAR_LEDGER_TIME_SECONDS;
7use crate::models::RelayerError;
8use crate::services::provider::StellarProviderTrait;
9
10pub const DEFAULT_SOROBAN_MAX_FEE_SLIPPAGE_BPS: u64 = 500;
13
14pub fn apply_max_fee_slippage_with_bps(fee_in_token: u64, slippage_bps: u64) -> i128 {
28 let fee_with_slippage = (fee_in_token as u128) * (10000 + slippage_bps as u128) / 10000;
29 fee_with_slippage as i128
30}
31
32pub fn apply_max_fee_slippage(fee_in_token: u64) -> i128 {
34 apply_max_fee_slippage_with_bps(fee_in_token, DEFAULT_SOROBAN_MAX_FEE_SLIPPAGE_BPS)
35}
36
37pub async fn get_expiration_ledger<P>(
42 provider: &P,
43 validity_seconds: u64,
44) -> Result<u32, RelayerError>
45where
46 P: StellarProviderTrait + Send + Sync,
47{
48 let current_ledger = provider
49 .get_latest_ledger()
50 .await
51 .map_err(|e| RelayerError::Internal(format!("Failed to get latest ledger: {e}")))?;
52
53 let mut ledgers_to_add = validity_seconds.div_ceil(STELLAR_LEDGER_TIME_SECONDS);
54 if ledgers_to_add == 0 {
55 ledgers_to_add = 1;
56 }
57 Ok(current_ledger
58 .sequence
59 .saturating_add(ledgers_to_add as u32))
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use std::future::ready;
66
67 use crate::services::provider::MockStellarProviderTrait;
68
69 #[test]
74 fn test_apply_max_fee_slippage_basic() {
75 let result = apply_max_fee_slippage(10000);
77 assert_eq!(result, 10500);
78 }
79
80 #[test]
81 fn test_apply_max_fee_slippage_zero() {
82 let result = apply_max_fee_slippage(0);
83 assert_eq!(result, 0);
84 }
85
86 #[test]
87 fn test_apply_max_fee_slippage_large_value() {
88 let large_fee: u64 = 1_000_000_000_000;
89 let result = apply_max_fee_slippage(large_fee);
90 assert_eq!(result, 1_050_000_000_000i128);
91 }
92
93 #[test]
94 fn test_apply_max_fee_slippage_small_value() {
95 let result = apply_max_fee_slippage(100);
96 assert_eq!(result, 105);
97 }
98
99 #[test]
104 fn test_apply_max_fee_slippage_with_bps_zero_slippage() {
105 let result = apply_max_fee_slippage_with_bps(10000, 0);
107 assert_eq!(result, 10000);
108 }
109
110 #[test]
111 fn test_apply_max_fee_slippage_with_bps_one_percent() {
112 let result = apply_max_fee_slippage_with_bps(10000, 100);
114 assert_eq!(result, 10100);
115 }
116
117 #[test]
118 fn test_apply_max_fee_slippage_with_bps_ten_percent() {
119 let result = apply_max_fee_slippage_with_bps(10000, 1000);
121 assert_eq!(result, 11000);
122 }
123
124 #[test]
125 fn test_apply_max_fee_slippage_with_bps_hundred_percent() {
126 let result = apply_max_fee_slippage_with_bps(10000, 10000);
128 assert_eq!(result, 20000);
129 }
130
131 #[test]
132 fn test_apply_max_fee_slippage_with_bps_zero_fee() {
133 let result = apply_max_fee_slippage_with_bps(0, 500);
134 assert_eq!(result, 0);
135 }
136
137 #[test]
138 fn test_apply_max_fee_slippage_with_bps_large_fee() {
139 let large_fee: u64 = 1_000_000_000_000;
140 let result = apply_max_fee_slippage_with_bps(large_fee, 500);
142 assert_eq!(result, 1_050_000_000_000i128);
143 }
144
145 #[test]
146 fn test_apply_max_fee_slippage_with_bps_small_fee_rounds_down() {
147 let result = apply_max_fee_slippage_with_bps(1, 1);
149 assert_eq!(result, 1);
151 }
152
153 #[tokio::test]
158 async fn test_get_expiration_ledger_success() {
159 let mut provider = MockStellarProviderTrait::new();
160 provider.expect_get_latest_ledger().returning(|| {
161 Box::pin(ready(Ok(
162 soroban_rs::stellar_rpc_client::GetLatestLedgerResponse {
163 id: "test".to_string(),
164 protocol_version: 20,
165 sequence: 1000,
166 },
167 )))
168 });
169
170 let result = get_expiration_ledger(&provider, 300).await;
171 assert!(result.is_ok());
172 let expiration = result.unwrap();
173 assert_eq!(expiration, 1060); }
175
176 #[tokio::test]
177 async fn test_get_expiration_ledger_zero_seconds() {
178 let mut provider = MockStellarProviderTrait::new();
179 provider.expect_get_latest_ledger().returning(|| {
180 Box::pin(ready(Ok(
181 soroban_rs::stellar_rpc_client::GetLatestLedgerResponse {
182 id: "test".to_string(),
183 protocol_version: 20,
184 sequence: 1000,
185 },
186 )))
187 });
188
189 let result = get_expiration_ledger(&provider, 0).await;
190 assert!(result.is_ok());
191 let expiration = result.unwrap();
192 assert_eq!(expiration, 1001); }
194
195 #[tokio::test]
196 async fn test_get_expiration_ledger_provider_error() {
197 use crate::services::provider::ProviderError;
198
199 let mut provider = MockStellarProviderTrait::new();
200 provider.expect_get_latest_ledger().returning(|| {
201 Box::pin(ready(Err(ProviderError::Other(
202 "network error".to_string(),
203 ))))
204 });
205
206 let result = get_expiration_ledger(&provider, 300).await;
207 assert!(result.is_err());
208 match result.unwrap_err() {
209 RelayerError::Internal(msg) => {
210 assert!(msg.contains("Failed to get latest ledger"));
211 }
212 _ => panic!("Expected Internal error"),
213 }
214 }
215
216 #[tokio::test]
217 async fn test_get_expiration_ledger_non_divisible_seconds() {
218 let mut provider = MockStellarProviderTrait::new();
220 provider.expect_get_latest_ledger().returning(|| {
221 Box::pin(ready(Ok(
222 soroban_rs::stellar_rpc_client::GetLatestLedgerResponse {
223 id: "test".to_string(),
224 protocol_version: 20,
225 sequence: 1000,
226 },
227 )))
228 });
229
230 let result = get_expiration_ledger(&provider, 7).await;
231 assert!(result.is_ok());
232 let expiration = result.unwrap();
233 assert_eq!(expiration, 1002); }
235
236 #[tokio::test]
237 async fn test_get_expiration_ledger_one_second() {
238 let mut provider = MockStellarProviderTrait::new();
239 provider.expect_get_latest_ledger().returning(|| {
240 Box::pin(ready(Ok(
241 soroban_rs::stellar_rpc_client::GetLatestLedgerResponse {
242 id: "test".to_string(),
243 protocol_version: 20,
244 sequence: 500,
245 },
246 )))
247 });
248
249 let result = get_expiration_ledger(&provider, 1).await;
250 assert!(result.is_ok());
251 let expiration = result.unwrap();
252 assert_eq!(expiration, 501); }
254
255 #[tokio::test]
256 async fn test_get_expiration_ledger_sequence_near_max() {
257 let mut provider = MockStellarProviderTrait::new();
259 provider.expect_get_latest_ledger().returning(|| {
260 Box::pin(ready(Ok(
261 soroban_rs::stellar_rpc_client::GetLatestLedgerResponse {
262 id: "test".to_string(),
263 protocol_version: 20,
264 sequence: u32::MAX - 1,
265 },
266 )))
267 });
268
269 let result = get_expiration_ledger(&provider, 300).await;
270 assert!(result.is_ok());
271 let expiration = result.unwrap();
272 assert_eq!(expiration, u32::MAX);
274 }
275
276 #[tokio::test]
277 async fn test_get_expiration_ledger_exact_ledger_time() {
278 let mut provider = MockStellarProviderTrait::new();
280 provider.expect_get_latest_ledger().returning(|| {
281 Box::pin(ready(Ok(
282 soroban_rs::stellar_rpc_client::GetLatestLedgerResponse {
283 id: "test".to_string(),
284 protocol_version: 20,
285 sequence: 2000,
286 },
287 )))
288 });
289
290 let result = get_expiration_ledger(&provider, STELLAR_LEDGER_TIME_SECONDS).await;
291 assert!(result.is_ok());
292 let expiration = result.unwrap();
293 assert_eq!(expiration, 2001); }
295}