API Reference

Utilities API Reference

This page documents the public API of openzeppelin_utils for OpenZeppelin Contracts for Sui v1.x.

use openzeppelin_utils::rate_limiter;

Embeddable, multi-strategy rate-limiting primitive. RateLimiter is a store + drop value, not a Sui object: it lives as a field inside an object the integrator owns, and its scope is that parent. There is no registry, no shared state, and no library-owned object.

The same consume and inspect API serves all three strategies. The variant is chosen at construction and can only be swapped by building a fresh RateLimiter and overwriting the field. All functions that observe or advance time take &Clock (the shared Sui Clock singleton at 0x6).

Types

Functions

Errors

Types

enum RateLimiter has drop, store

struct

#

One embeddable limiter with three strategies: Bucket, FixedWindow, and Cooldown. Every variant stores an available counter that starts at initial_available, is decremented by successful consumes, and is reset back toward capacity by refill (Bucket), window rollover (FixedWindow), or cooldown release (Cooldown).

store lets it live as a field inside an integrator's key object, and drop lets the parent object be destroyed and lets a fresh limiter overwrite an old one during reconfigure-by-reconstruction. There is no key/UID (it is never a top-level object) and no copy (a duplicable limiter would multiply configured capacity by N).

The variant fields are not directly accessible from outside the module; read them through the getters below.

Functions

new_bucket(capacity: u64, refill_amount: u64, refill_interval_ms: u64, last_refill_ms: u64, initial_available: u64, clock: &Clock) -> RateLimiter

public

#

Creates a token bucket with an explicit initial balance and refill anchor. available accrues refill_amount every refill_interval_ms, capped at capacity.

last_refill_ms anchors the refill schedule; for a greenfield limiter pass clock.timestamp_ms(). Pass an earlier value to preserve the refill phase across a reconstruction, but only when the rate (refill_amount / refill_interval_ms) is unchanged. Carrying an old anchor into a new rate pre-credits elapsed time at the new rate and can briefly exceed it. initial_available is the starting balance and must be <= capacity; 0 forces a wait for the first refill.

Aborts with EZeroCapacity if capacity is 0.

Aborts with EZeroRefillAmount if refill_amount is 0.

Aborts with EZeroRefillInterval if refill_interval_ms is 0.

Aborts with EInitialAboveCapacity if initial_available > capacity.

Aborts with EBucketAnchorInFuture if last_refill_ms > clock.timestamp_ms().

new_fixed_window(capacity: u64, window_ms: u64, window_start_ms: u64, initial_available: u64, clock: &Clock) -> RateLimiter

public

#

Creates a fixed window limiter anchored at window_start_ms. Windows are [window_start_ms + k·window_ms, window_start_ms + (k+1)·window_ms) for k >= 0, and available resets to capacity when time crosses into a later window. The first window always has length exactly window_ms (anchor-based, not wall-clock-aligned).

initial_available is the starting available units for the current window and must be <= capacity. For a greenfield limiter pass clock.timestamp_ms() as window_start_ms.

Aborts with EZeroCapacity if capacity is 0.

Aborts with EZeroWindow if window_ms is 0.

Aborts with EInitialAboveCapacity if initial_available > capacity.

Aborts with EWindowAnchorInFuture if window_start_ms > clock.timestamp_ms().

A caller can spend the full quota at the end of one window and again at the start of the next, yielding a 2 * capacity burst across the boundary. Pick FixedWindow when the per-window quota is the contract and that boundary burst is acceptable; otherwise use a Bucket.

new_cooldown(capacity: u64, cooldown_ms: u64, cooldown_end_ms: u64, initial_available: u64, clock: &Clock) -> RateLimiter

public

#

Creates a cooldown limiter. Up to capacity units may be consumed (in any combination of per-call amounts) before the limiter gates. Once available reaches 0, cooldown_end_ms is set to now + cooldown_ms; no further consume succeeds until now >= cooldown_end_ms, at which point available resets to capacity.

cooldown_end_ms <= now means no gate is armed. The valid seed combinations are: initial_available > 0 with cooldown_end_ms <= now (granted - up to initial_available units before the first arm); initial_available == 0 with cooldown_end_ms > now (gated - reconstructing a limiter mid-throttle, or arming a delay in front of an action); and initial_available == 0 with cooldown_end_ms <= now (released - projects to fully available on the next read or consume).

Aborts with EZeroCapacity if capacity is 0.

Aborts with EZeroCooldown if cooldown_ms is 0.

Aborts with EInitialAboveCapacity if initial_available > capacity.

Aborts with ECooldownArmedWithTokens if initial_available > 0 and cooldown_end_ms > now (self-contradictory).

try_consume(self: &mut RateLimiter, amount: u64, clock: &Clock) -> bool

public

#

Projects state forward (accrual / window rollover / gate release), then consumes amount if the projected headroom allows. All-or-nothing: on success the projected state is committed and amount deducted; on failure the persisted state is left untouched. Returns true if the consume succeeded, false if the limiter refused or if amount is 0.

Aborts with ECooldownDeadlineOverflow if consuming arms a Cooldown gate and now + cooldown_ms would overflow u64.

consume_or_abort(self: &mut RateLimiter, amount: u64, clock: &Clock)

public

#

Applies accrual, then consumes amount or aborts. The ergonomic wrapper for the common "rate-limit then act" path.

Aborts with EInvalidAmount if amount is 0.

Aborts with ERateLimited if the limiter cannot satisfy the request.

Aborts with ECooldownDeadlineOverflow if consuming arms a Cooldown gate and now + cooldown_ms would overflow u64.

available(self: &RateLimiter, clock: &Clock) -> u64

public

#

Read-only view of the units consumable right now, after projecting accrual / window reset / gate release on read (not committed). Variant-agnostic; never aborts.

For Bucket, this is the tokens consumable now. For FixedWindow, the remaining headroom after any rollover. For Cooldown, capacity if the gate has elapsed, otherwise the stored available.

try_consume(self.available(clock), clock) returns false when available() returns 0 (a zero-unit consume is rejected). Guard with if (n > 0) { self.try_consume(n, clock); }.

capacity(self: &RateLimiter) -> u64

public

#

Returns the capacity of the limiter, regardless of variant. Variant-agnostic; never aborts.

is_bucket(self: &RateLimiter) -> bool

public

#

Returns true only if the limiter is a Bucket. Variant-agnostic; never aborts. Use a predicate to branch before calling a variant-typed getter, which would otherwise abort EWrongVariant on a mismatch.

is_fixed_window(self: &RateLimiter) -> bool

public

#

Returns true only if the limiter is a FixedWindow. Variant-agnostic; never aborts.

is_cooldown(self: &RateLimiter) -> bool

public

#

Returns true only if the limiter is a Cooldown. Variant-agnostic; never aborts.

refill_amount(self: &RateLimiter) -> u64

public

#

Returns the tokens credited per refill interval.

Aborts with EWrongVariant if the limiter is not a Bucket.

refill_interval_ms(self: &RateLimiter) -> u64

public

#

Returns the length of one refill interval, in milliseconds.

Aborts with EWrongVariant if the limiter is not a Bucket.

last_refill_ms(self: &RateLimiter, clock: &Clock) -> u64

public

#

Returns the projected refill anchor at now, so it pairs coherently with available(clock): read both, reconstruct, and the new limiter preserves refill phase.

Aborts with EWrongVariant if the limiter is not a Bucket.

window_ms(self: &RateLimiter) -> u64

public

#

Returns the length of one window, in milliseconds.

Aborts with EWrongVariant if the limiter is not a FixedWindow.

window_start_ms(self: &RateLimiter, clock: &Clock) -> u64

public

#

Returns the projected window anchor at now, so it pairs coherently with available(clock) for snapshotting.

Aborts with EWrongVariant if the limiter is not a FixedWindow.

cooldown_ms(self: &RateLimiter) -> u64

public

#

Returns the wait between exhausting a batch and the next reset, in milliseconds.

Aborts with EWrongVariant if the limiter is not a Cooldown.

cooldown_end_ms(self: &RateLimiter) -> u64

public

#

Returns the stored gate deadline as-is (a deadline does not evolve with time). Only meaningful when available(clock) == 0.

Aborts with EWrongVariant if the limiter is not a Cooldown.

Errors

ERateLimited (code 0)

error

#

Raised by consume_or_abort when the limiter cannot satisfy the request.

EZeroCapacity (code 1)

error

#

Raised by any constructor called with capacity == 0.

EWrongVariant (code 2)

error

#

Raised when a variant-typed getter is called on a limiter of a different variant. Guard with is_bucket / is_fixed_window / is_cooldown when the variant is not known statically.

EInvalidAmount (code 3)

error

#

Raised by consume_or_abort when amount == 0. try_consume returns false instead of aborting.

EZeroRefillAmount (code 4)

error

#

Raised by new_bucket when refill_amount == 0.

EZeroRefillInterval (code 5)

error

#

Raised by new_bucket when refill_interval_ms == 0.

EZeroWindow (code 6)

error

#

Raised by new_fixed_window when window_ms == 0.

EZeroCooldown (code 7)

error

#

Raised by new_cooldown when cooldown_ms == 0.

EInitialAboveCapacity (code 8)

error

#

Raised by any constructor when initial_available > capacity.

EWindowAnchorInFuture (code 9)

error

#

Raised by new_fixed_window when window_start_ms is later than the current time.

ECooldownArmedWithTokens (code 10)

error

#

Raised by new_cooldown when initial_available > 0 and cooldown_end_ms > now, which is a self-contradictory seed (units available while a gate is armed).

EBucketAnchorInFuture (code 11)

error

#

Raised by new_bucket when last_refill_ms is later than the current time.

ECooldownDeadlineOverflow (code 12)

error

#

Raised by try_consume / consume_or_abort when a consume drains a Cooldown batch and arming its gate would overflow u64 (now + cooldown_ms). Any realistic cooldown_ms (seconds to years) stays well clear of this bound.

On this page