Skip to main content

ccxt_core/
rate_limiter.rs

1//! Rate Limiter Module
2//!
3//! This module provides rate limiting functionality to prevent exceeding exchange API limits.
4//! It implements token bucket algorithm with configurable capacity and refill rate.
5//!
6//! # Features
7//!
8//! - **Token Bucket Algorithm**: Classic rate limiting strategy
9//! - **Async-Friendly**: Built on tokio for async/await support
10//! - **Thread-Safe**: Uses Arc<Mutex<>> for concurrent access
11//! - **Configurable**: Flexible capacity and refill rate settings
12//!
13//! # Example
14//!
15//! ```rust
16//! use ccxt_core::rate_limiter::{RateLimiter, RateLimiterConfig};
17//! use std::time::Duration;
18//!
19//! # async fn example() {
20//! // Create a rate limiter: 10 requests per second
21//! let config = RateLimiterConfig::new(10, Duration::from_secs(1));
22//! let limiter = RateLimiter::new(config);
23//!
24//! // Wait for permission to make a request
25//! limiter.wait().await;
26//! // Make your API request here
27//! # }
28//! ```
29
30use crate::error::{ConfigValidationError, ValidationResult};
31use std::sync::Arc;
32use std::time::{Duration, Instant};
33use tokio::sync::Mutex;
34use tokio::time::sleep;
35
36/// Rate limiter configuration
37#[derive(Debug, Clone)]
38pub struct RateLimiterConfig {
39    /// Maximum number of tokens (requests) in the bucket
40    pub capacity: u32,
41    /// Time window for refilling tokens
42    pub refill_period: Duration,
43    /// Number of tokens to refill per period (defaults to capacity)
44    pub refill_amount: u32,
45    /// Cost per request in tokens (defaults to 1)
46    pub cost_per_request: u32,
47}
48
49impl RateLimiterConfig {
50    /// Create a new rate limiter configuration
51    ///
52    /// # Arguments
53    ///
54    /// * `capacity` - Maximum number of requests allowed in the time window
55    /// * `refill_period` - Time window for the rate limit
56    ///
57    /// # Example
58    ///
59    /// ```rust
60    /// use ccxt_core::rate_limiter::RateLimiterConfig;
61    /// use std::time::Duration;
62    ///
63    /// // 100 requests per minute
64    /// let config = RateLimiterConfig::new(100, Duration::from_secs(60));
65    /// ```
66    pub fn new(capacity: u32, refill_period: Duration) -> Self {
67        Self {
68            capacity,
69            refill_period,
70            refill_amount: capacity,
71            cost_per_request: 1,
72        }
73    }
74
75    /// Set custom refill amount (different from capacity)
76    pub fn with_refill_amount(mut self, amount: u32) -> Self {
77        self.refill_amount = amount;
78        self
79    }
80
81    /// Set custom cost per request
82    pub fn with_cost_per_request(mut self, cost: u32) -> Self {
83        self.cost_per_request = cost;
84        self
85    }
86}
87
88impl Default for RateLimiterConfig {
89    fn default() -> Self {
90        // Default: 10 requests per second
91        Self::new(10, Duration::from_secs(1))
92    }
93}
94
95impl RateLimiterConfig {
96    /// Validates the rate limiter configuration parameters.
97    ///
98    /// # Returns
99    ///
100    /// Returns `Ok(ValidationResult)` if the configuration is valid.
101    /// The `ValidationResult` may contain warnings for suboptimal but valid configurations.
102    ///
103    /// Returns `Err(ConfigValidationError)` if the configuration is invalid.
104    ///
105    /// # Validation Rules
106    ///
107    /// - `capacity` must be > 0 (zero capacity is invalid)
108    /// - `refill_period` < 100ms generates a warning (may cause high CPU usage)
109    ///
110    /// # Example
111    ///
112    /// ```rust
113    /// use ccxt_core::rate_limiter::RateLimiterConfig;
114    /// use std::time::Duration;
115    ///
116    /// let config = RateLimiterConfig::new(10, Duration::from_secs(1));
117    /// let result = config.validate();
118    /// assert!(result.is_ok());
119    ///
120    /// let invalid_config = RateLimiterConfig::new(0, Duration::from_secs(1));
121    /// let result = invalid_config.validate();
122    /// assert!(result.is_err());
123    /// ```
124    pub fn validate(&self) -> Result<ValidationResult, ConfigValidationError> {
125        let mut warnings = Vec::new();
126
127        // Validate capacity > 0
128        if self.capacity == 0 {
129            return Err(ConfigValidationError::invalid(
130                "capacity",
131                "capacity cannot be zero",
132            ));
133        }
134
135        // Warn if refill_period < 100ms
136        if self.refill_period < Duration::from_millis(100) {
137            warnings.push(format!(
138                "refill_period {:?} is very short, may cause high CPU usage",
139                self.refill_period
140            ));
141        }
142
143        Ok(ValidationResult::with_warnings(warnings))
144    }
145}
146
147/// Internal state of the rate limiter
148#[derive(Debug)]
149struct RateLimiterState {
150    /// Current number of available tokens
151    tokens: u32,
152    /// Last time tokens were refilled
153    last_refill: Instant,
154    /// Accumulated nanoseconds for fractional token tracking (precision optimization)
155    /// This field tracks the remainder when elapsed time doesn't evenly divide into refill periods,
156    /// preventing floating-point precision drift over long-running applications.
157    remainder_nanos: u64,
158    /// Configuration
159    config: RateLimiterConfig,
160}
161
162impl RateLimiterState {
163    fn new(config: RateLimiterConfig) -> Self {
164        Self {
165            tokens: config.capacity,
166            last_refill: Instant::now(),
167            remainder_nanos: 0,
168            config,
169        }
170    }
171
172    /// Refill tokens based on elapsed time using integer arithmetic
173    ///
174    /// This implementation uses nanosecond-based integer arithmetic instead of
175    /// floating-point seconds to avoid precision drift over long-running applications.
176    /// The formula used is: `elapsed_nanos / period_nanos * refill_amount`
177    ///
178    /// The `remainder_nanos` field tracks fractional periods that haven't yet
179    /// accumulated to a full refill period, ensuring no time is lost between refills.
180    ///
181    /// Uses u128 for all intermediate calculations to prevent overflow for 10+ years of uptime.
182    fn refill(&mut self) {
183        let now = Instant::now();
184        // Use u128 for all time calculations (prevents overflow for 10+ years)
185        let elapsed_nanos = now.duration_since(self.last_refill).as_nanos(); // u128
186        let period_nanos = self.config.refill_period.as_nanos(); // u128
187
188        // Avoid division by zero (should not happen with valid config)
189        if period_nanos == 0 {
190            return;
191        }
192
193        // Add elapsed time to accumulated remainder using saturating_add to prevent overflow
194        // Even though u128 is very large, using saturating_add ensures safety in extreme edge cases
195        let total_nanos = u128::from(self.remainder_nanos).saturating_add(elapsed_nanos);
196
197        // Calculate complete periods using integer division
198        let complete_periods = total_nanos / period_nanos;
199
200        if complete_periods > 0 {
201            // Calculate tokens to add using u128 arithmetic (prevents overflow)
202            // Truncation is safe here because we explicitly cap at u32::MAX
203            #[allow(clippy::cast_possible_truncation)]
204            let tokens_to_add = (complete_periods * u128::from(self.config.refill_amount))
205                .min(u128::from(u32::MAX)) as u32;
206
207            // Add tokens up to capacity
208            self.tokens = self
209                .tokens
210                .saturating_add(tokens_to_add)
211                .min(self.config.capacity);
212
213            // Store remainder for next refill (preserves fractional periods)
214            // Truncation is safe: remainder is always < period_nanos, which fits in u64
215            // (period_nanos comes from Duration::as_nanos() which is typically < u64::MAX)
216            #[allow(clippy::cast_possible_truncation)]
217            let remainder = (total_nanos % period_nanos) as u64;
218            self.remainder_nanos = remainder;
219            self.last_refill = now;
220        }
221    }
222
223    /// Try to consume tokens
224    fn try_consume(&mut self, cost: u32) -> bool {
225        self.refill();
226
227        if self.tokens >= cost {
228            self.tokens -= cost;
229            true
230        } else {
231            false
232        }
233    }
234
235    /// Calculate wait time until enough tokens are available
236    fn wait_time(&self, cost: u32) -> Duration {
237        if self.tokens >= cost {
238            return Duration::ZERO;
239        }
240
241        let tokens_needed = cost - self.tokens;
242        let refill_rate =
243            f64::from(self.config.refill_amount) / self.config.refill_period.as_secs_f64();
244        let wait_seconds = f64::from(tokens_needed) / refill_rate;
245
246        Duration::from_secs_f64(wait_seconds)
247    }
248}
249
250/// Rate limiter using token bucket algorithm
251///
252/// This structure is thread-safe and can be shared across multiple tasks.
253#[derive(Debug, Clone)]
254pub struct RateLimiter {
255    state: Arc<Mutex<RateLimiterState>>,
256}
257
258impl Default for RateLimiter {
259    fn default() -> Self {
260        Self::new(RateLimiterConfig::default())
261    }
262}
263
264impl RateLimiter {
265    /// Create a new rate limiter with the given configuration
266    ///
267    /// # Example
268    ///
269    /// ```rust
270    /// use ccxt_core::rate_limiter::{RateLimiter, RateLimiterConfig};
271    /// use std::time::Duration;
272    ///
273    /// let config = RateLimiterConfig::new(50, Duration::from_secs(1));
274    /// let limiter = RateLimiter::new(config);
275    /// ```
276    pub fn new(config: RateLimiterConfig) -> Self {
277        Self {
278            state: Arc::new(Mutex::new(RateLimiterState::new(config))),
279        }
280    }
281
282    /// Wait until a request can be made (async)
283    ///
284    /// This method will block until enough tokens are available.
285    ///
286    /// # Example
287    ///
288    /// ```rust
289    /// use ccxt_core::rate_limiter::RateLimiter;
290    ///
291    /// # async fn example() {
292    /// let limiter = RateLimiter::default();
293    /// limiter.wait().await;
294    /// // Make API request here
295    /// # }
296    /// ```
297    pub async fn wait(&self) {
298        self.wait_with_cost(1).await;
299    }
300
301    /// Wait until a request with custom cost can be made
302    ///
303    /// # Arguments
304    ///
305    /// * `cost` - Number of tokens to consume for this request
306    pub async fn wait_with_cost(&self, cost: u32) {
307        loop {
308            let wait_duration = {
309                let mut state = self.state.lock().await;
310                if state.try_consume(cost) {
311                    return;
312                }
313                state.wait_time(cost)
314            };
315
316            if wait_duration > Duration::ZERO {
317                sleep(wait_duration).await;
318            } else {
319                // Small delay to prevent busy waiting
320                sleep(Duration::from_millis(10)).await;
321            }
322        }
323    }
324
325    /// Acquire permission to make a request (wait if necessary)
326    ///
327    /// This is an alias for `wait()` that matches the naming convention
328    /// used in other rate limiting libraries.
329    pub async fn acquire(&self, cost: u32) {
330        self.wait_with_cost(cost).await;
331    }
332
333    /// Try to make a request without waiting
334    ///
335    /// Returns `true` if the request can proceed, `false` if rate limited.
336    ///
337    /// # Example
338    ///
339    /// ```rust
340    /// use ccxt_core::rate_limiter::RateLimiter;
341    ///
342    /// # async fn example() {
343    /// let limiter = RateLimiter::default();
344    /// if limiter.try_acquire().await {
345    ///     // Make API request
346    /// } else {
347    ///     // Rate limited, handle accordingly
348    /// }
349    /// # }
350    /// ```
351    pub async fn try_acquire(&self) -> bool {
352        self.try_acquire_with_cost(1).await
353    }
354
355    /// Try to make a request with custom cost without waiting
356    pub async fn try_acquire_with_cost(&self, cost: u32) -> bool {
357        let mut state = self.state.lock().await;
358        state.try_consume(cost)
359    }
360
361    /// Get current number of available tokens
362    pub async fn available_tokens(&self) -> u32 {
363        let mut state = self.state.lock().await;
364        state.refill();
365        state.tokens
366    }
367
368    /// Reset the rate limiter to full capacity
369    pub async fn reset(&self) {
370        let mut state = self.state.lock().await;
371        state.tokens = state.config.capacity;
372        state.last_refill = Instant::now();
373        state.remainder_nanos = 0;
374    }
375}
376
377/// Multi-tier rate limiter for exchanges with multiple rate limit tiers
378///
379/// Some exchanges have different rate limits for different endpoint types
380/// (e.g., public vs private, order placement vs market data)
381#[derive(Debug, Clone)]
382pub struct MultiTierRateLimiter {
383    limiters: Arc<Mutex<std::collections::HashMap<String, RateLimiter>>>,
384}
385
386impl MultiTierRateLimiter {
387    /// Create a new multi-tier rate limiter
388    pub fn new() -> Self {
389        Self {
390            limiters: Arc::new(Mutex::new(std::collections::HashMap::new())),
391        }
392    }
393
394    /// Add a rate limiter for a specific tier
395    ///
396    /// # Arguments
397    ///
398    /// * `tier` - Name of the tier (e.g., "public", "private", "orders")
399    /// * `limiter` - Rate limiter configuration for this tier
400    pub async fn add_tier(&self, tier: String, limiter: RateLimiter) {
401        let mut limiters = self.limiters.lock().await;
402        limiters.insert(tier, limiter);
403    }
404
405    /// Wait for permission on a specific tier
406    pub async fn wait(&self, tier: &str) {
407        let limiter = {
408            let limiters = self.limiters.lock().await;
409            limiters.get(tier).cloned()
410        };
411
412        if let Some(limiter) = limiter {
413            limiter.wait().await;
414        }
415    }
416
417    /// Try to acquire permission on a specific tier without waiting
418    pub async fn try_acquire(&self, tier: &str) -> bool {
419        let limiter = {
420            let limiters = self.limiters.lock().await;
421            limiters.get(tier).cloned()
422        };
423
424        if let Some(limiter) = limiter {
425            limiter.try_acquire().await
426        } else {
427            true // No limiter for this tier, allow by default
428        }
429    }
430}
431
432impl Default for MultiTierRateLimiter {
433    fn default() -> Self {
434        Self::new()
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441
442    #[test]
443    fn test_rate_limiter_config() {
444        let config = RateLimiterConfig::new(100, Duration::from_secs(60));
445        assert_eq!(config.capacity, 100);
446        assert_eq!(config.refill_period, Duration::from_secs(60));
447        assert_eq!(config.refill_amount, 100);
448        assert_eq!(config.cost_per_request, 1);
449    }
450
451    #[test]
452    fn test_rate_limiter_config_custom() {
453        let config = RateLimiterConfig::new(100, Duration::from_secs(60))
454            .with_refill_amount(50)
455            .with_cost_per_request(2);
456
457        assert_eq!(config.refill_amount, 50);
458        assert_eq!(config.cost_per_request, 2);
459    }
460
461    #[tokio::test]
462    async fn test_rate_limiter_basic() {
463        let config = RateLimiterConfig::new(5, Duration::from_secs(1));
464        let limiter = RateLimiter::new(config);
465
466        // Should be able to make 5 requests immediately
467        for _ in 0..5 {
468            assert!(limiter.try_acquire().await);
469        }
470
471        // 6th request should fail
472        assert!(!limiter.try_acquire().await);
473    }
474
475    #[tokio::test]
476    async fn test_rate_limiter_refill() {
477        let config = RateLimiterConfig::new(2, Duration::from_millis(100));
478        let limiter = RateLimiter::new(config);
479
480        // Use all tokens
481        assert!(limiter.try_acquire().await);
482        assert!(limiter.try_acquire().await);
483        assert!(!limiter.try_acquire().await);
484
485        // Wait for refill
486        sleep(Duration::from_millis(150)).await;
487
488        // Should have tokens again
489        assert!(limiter.try_acquire().await);
490    }
491
492    #[tokio::test]
493    async fn test_rate_limiter_wait() {
494        let config = RateLimiterConfig::new(2, Duration::from_millis(100));
495        let limiter = RateLimiter::new(config);
496
497        // Use all tokens
498        limiter.wait().await;
499        limiter.wait().await;
500
501        let start = Instant::now();
502        limiter.wait().await; // This should wait
503        let elapsed = start.elapsed();
504
505        // Should have waited at least 80ms (with some tolerance)
506        assert!(elapsed >= Duration::from_millis(80));
507    }
508
509    #[tokio::test]
510    async fn test_rate_limiter_custom_cost() {
511        let config = RateLimiterConfig::new(10, Duration::from_secs(1));
512        let limiter = RateLimiter::new(config);
513
514        // One request with cost 5
515        assert!(limiter.try_acquire_with_cost(5).await);
516        assert_eq!(limiter.available_tokens().await, 5);
517
518        // Another request with cost 3
519        assert!(limiter.try_acquire_with_cost(3).await);
520        assert_eq!(limiter.available_tokens().await, 2);
521
522        // Request with cost 3 should fail (only 2 tokens left)
523        assert!(!limiter.try_acquire_with_cost(3).await);
524    }
525
526    #[tokio::test]
527    async fn test_rate_limiter_reset() {
528        let config = RateLimiterConfig::new(5, Duration::from_secs(1));
529        let limiter = RateLimiter::new(config);
530
531        // Use all tokens
532        for _ in 0..5 {
533            limiter.wait().await;
534        }
535
536        assert_eq!(limiter.available_tokens().await, 0);
537
538        // Reset
539        limiter.reset().await;
540
541        assert_eq!(limiter.available_tokens().await, 5);
542    }
543
544    #[tokio::test]
545    async fn test_multi_tier_rate_limiter() {
546        let multi = MultiTierRateLimiter::new();
547
548        // Add tiers
549        let public_config = RateLimiterConfig::new(10, Duration::from_secs(1));
550        let private_config = RateLimiterConfig::new(5, Duration::from_secs(1));
551
552        multi
553            .add_tier("public".to_string(), RateLimiter::new(public_config))
554            .await;
555        multi
556            .add_tier("private".to_string(), RateLimiter::new(private_config))
557            .await;
558
559        // Test public tier
560        for _ in 0..10 {
561            assert!(multi.try_acquire("public").await);
562        }
563        assert!(!multi.try_acquire("public").await);
564
565        // Test private tier
566        for _ in 0..5 {
567            assert!(multi.try_acquire("private").await);
568        }
569        assert!(!multi.try_acquire("private").await);
570
571        // Unknown tier should allow by default
572        assert!(multi.try_acquire("unknown").await);
573    }
574
575    #[tokio::test]
576    async fn test_concurrent_access() {
577        let config = RateLimiterConfig::new(10, Duration::from_secs(1));
578        let limiter = RateLimiter::new(config);
579
580        let mut handles = vec![];
581
582        // Spawn 10 concurrent tasks
583        for _ in 0..10 {
584            let limiter_clone = limiter.clone();
585            let handle = tokio::spawn(async move {
586                limiter_clone.wait().await;
587            });
588            handles.push(handle);
589        }
590
591        // Wait for all tasks
592        for handle in handles {
593            handle.await.unwrap();
594        }
595
596        // All tokens should be consumed
597        assert_eq!(limiter.available_tokens().await, 0);
598    }
599
600    #[test]
601    fn test_rate_limiter_config_validate_default() {
602        let config = RateLimiterConfig::default();
603        let result = config.validate();
604        assert!(result.is_ok());
605        assert!(result.unwrap().warnings.is_empty());
606    }
607
608    #[test]
609    fn test_rate_limiter_config_validate_zero_capacity() {
610        let config = RateLimiterConfig::new(0, Duration::from_secs(1));
611        let result = config.validate();
612        assert!(result.is_err());
613        let err = result.unwrap_err();
614        assert_eq!(err.field_name(), "capacity");
615        assert!(matches!(
616            err,
617            crate::error::ConfigValidationError::ValueInvalid { .. }
618        ));
619    }
620
621    #[test]
622    fn test_rate_limiter_config_validate_short_refill_period_warning() {
623        let config = RateLimiterConfig::new(10, Duration::from_millis(50));
624        let result = config.validate();
625        assert!(result.is_ok());
626        let validation_result = result.unwrap();
627        assert!(!validation_result.warnings.is_empty());
628        assert!(validation_result.warnings[0].contains("refill_period"));
629        assert!(validation_result.warnings[0].contains("very short"));
630    }
631
632    #[test]
633    fn test_rate_limiter_config_validate_refill_period_boundary() {
634        // refill_period = 100ms should not generate warning
635        let config = RateLimiterConfig::new(10, Duration::from_millis(100));
636        let result = config.validate();
637        assert!(result.is_ok());
638        assert!(result.unwrap().warnings.is_empty());
639
640        // refill_period = 99ms should generate warning
641        let config = RateLimiterConfig::new(10, Duration::from_millis(99));
642        let result = config.validate();
643        assert!(result.is_ok());
644        assert!(!result.unwrap().warnings.is_empty());
645    }
646
647    #[test]
648    fn test_rate_limiter_config_validate_valid_config() {
649        let config = RateLimiterConfig::new(100, Duration::from_secs(60));
650        let result = config.validate();
651        assert!(result.is_ok());
652        assert!(result.unwrap().warnings.is_empty());
653    }
654
655    /// Test that the integer-based refill calculation maintains precision
656    /// over many iterations without drift.
657    #[test]
658    fn test_rate_limiter_integer_precision() {
659        // Create a state with a specific configuration
660        let config = RateLimiterConfig::new(100, Duration::from_millis(100)).with_refill_amount(10);
661        let mut state = RateLimiterState::new(config);
662
663        // Consume all tokens
664        state.tokens = 0;
665
666        // Simulate many small time increments that don't complete a full period
667        // This tests the remainder_nanos accumulation
668        let period_nanos = 100_000_000u64; // 100ms in nanos
669        let small_increment = 33_333_333u64; // ~33.3ms in nanos
670
671        // After 3 increments of ~33.3ms, we should have ~100ms total
672        // which equals 1 complete period = 10 tokens
673        state.remainder_nanos = small_increment;
674        state.remainder_nanos += small_increment;
675        state.remainder_nanos += small_increment;
676
677        // Calculate complete periods
678        let complete_periods = state.remainder_nanos / period_nanos;
679        assert_eq!(complete_periods, 0); // 99.9ms < 100ms, no complete period yet
680
681        // Add one more nanosecond to push over the threshold
682        state.remainder_nanos += 1;
683        let complete_periods = state.remainder_nanos / period_nanos;
684        assert_eq!(complete_periods, 1); // Now we have 1 complete period
685
686        // Verify remainder is preserved correctly
687        let expected_remainder = (small_increment * 3 + 1) % period_nanos;
688        assert_eq!(state.remainder_nanos % period_nanos, expected_remainder);
689    }
690
691    /// Test that remainder_nanos is properly reset when reset() is called
692    #[tokio::test]
693    async fn test_rate_limiter_reset_clears_remainder() {
694        let config = RateLimiterConfig::new(5, Duration::from_secs(1));
695        let limiter = RateLimiter::new(config);
696
697        // Use all tokens
698        for _ in 0..5 {
699            limiter.wait().await;
700        }
701
702        // Wait a bit to accumulate some remainder
703        sleep(Duration::from_millis(50)).await;
704
705        // Reset should clear everything including remainder
706        limiter.reset().await;
707
708        // Verify tokens are back to capacity
709        assert_eq!(limiter.available_tokens().await, 5);
710    }
711
712    /// Test that the refill calculation handles zero period_nanos gracefully
713    #[test]
714    fn test_rate_limiter_refill_zero_period_protection() {
715        // This tests the edge case protection in refill()
716        // A zero period should not cause division by zero
717        let config = RateLimiterConfig {
718            capacity: 10,
719            refill_period: Duration::ZERO, // Edge case: zero duration
720            refill_amount: 5,
721            cost_per_request: 1,
722        };
723        let mut state = RateLimiterState::new(config);
724        state.tokens = 0;
725
726        // This should not panic due to division by zero
727        state.refill();
728
729        // Tokens should remain unchanged (no refill with zero period)
730        assert_eq!(state.tokens, 0);
731    }
732
733    /// Test that integer arithmetic doesn't overflow with large values
734    #[test]
735    fn test_rate_limiter_refill_overflow_protection() {
736        let config =
737            RateLimiterConfig::new(u32::MAX, Duration::from_nanos(1)).with_refill_amount(u32::MAX);
738        let mut state = RateLimiterState::new(config);
739        state.tokens = 0;
740
741        // Simulate a large elapsed time
742        state.remainder_nanos = u64::MAX / 2;
743
744        // This should not panic due to overflow
745        // The saturating_mul and min operations should protect against overflow
746        let period_nanos = 1u64;
747        let complete_periods = state.remainder_nanos / period_nanos;
748        let tokens_to_add = complete_periods
749            .saturating_mul(u64::from(state.config.refill_amount))
750            .min(u64::from(u32::MAX)) as u32;
751
752        // Should be capped at u32::MAX
753        assert_eq!(tokens_to_add, u32::MAX);
754    }
755}