Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

tokenbucket.gno

2.58 Kb · 122 lines
  1package tokenbucket
  2
  3import (
  4	"errors"
  5
  6	u256 "gno.land/p/onbloc/math/uint256"
  7)
  8
  9var (
 10	ErrZeroCapacity         = errors.New("tokenbucket: capacity must not be zero")
 11	ErrZeroRefillRate       = errors.New("tokenbucket: refill rate must not be zero")
 12	ErrRateLimitExceeded    = errors.New("tokenbucket: rate limit exceeded")
 13	ErrTokenBucketUnderflow = errors.New("tokenbucket: token bucket underflow")
 14)
 15
 16type TokenBucket struct {
 17	Capacity   *u256.Uint
 18	Available  *u256.Uint
 19	RefillRate *u256.Uint
 20	LastRefill *u256.Uint
 21}
 22
 23func New(capacity *u256.Uint, refillRate *u256.Uint, now *u256.Uint) (*TokenBucket, error) {
 24	if capacity.IsZero() {
 25		return nil, ErrZeroCapacity
 26	}
 27
 28	if refillRate.IsZero() {
 29		return nil, ErrZeroRefillRate
 30	}
 31
 32	return &TokenBucket{
 33		Capacity:   capacity.Clone(),
 34		Available:  capacity.Clone(),
 35		RefillRate: refillRate.Clone(),
 36		LastRefill: now.Clone(),
 37	}, nil
 38}
 39
 40func (tb *TokenBucket) Refill(now *u256.Uint) {
 41	if now.Lte(tb.LastRefill) {
 42		return
 43	}
 44
 45	if tb.Available.Gte(tb.Capacity) {
 46		tb.LastRefill.Set(now)
 47		return
 48	}
 49
 50	elapsed, underflow := u256.Zero().SubOverflow(now, tb.LastRefill)
 51	if underflow {
 52		panic(ErrTokenBucketUnderflow)
 53	}
 54
 55	toRefill := saturatingMul(tb.RefillRate, elapsed)
 56	tb.Available = min(tb.Capacity, saturatingAdd(tb.Available, toRefill))
 57	tb.LastRefill.Set(now)
 58}
 59
 60func (tb *TokenBucket) RateLimit(amount *u256.Uint, now *u256.Uint) error {
 61	if amount.IsZero() {
 62		return nil
 63	}
 64
 65	tb.Refill(now)
 66
 67	if tb.Available.Lt(amount) {
 68		return ErrRateLimitExceeded
 69	}
 70
 71	tb.Available = u256.Zero().Sub(tb.Available, amount)
 72
 73	return nil
 74}
 75
 76func (tb *TokenBucket) Update(capacity *u256.Uint, refillRate *u256.Uint, reset bool) error {
 77	if capacity.IsZero() {
 78		return ErrZeroCapacity
 79	}
 80
 81	if refillRate.IsZero() {
 82		return ErrZeroRefillRate
 83	}
 84
 85	tb.Capacity = capacity.Clone()
 86	tb.RefillRate = refillRate.Clone()
 87
 88	// Mirror union's token_bucket update (token_bucket.rs L84-88): only reset
 89	// available on first init or explicit reset. Do NOT clamp available down when
 90	// capacity shrinks — union lets a now-excess available drain naturally.
 91	if tb.LastRefill.IsZero() || reset {
 92		tb.Available = tb.Capacity.Clone()
 93	}
 94
 95	return nil
 96}
 97
 98func saturatingAdd(x *u256.Uint, y *u256.Uint) *u256.Uint {
 99	z, overflow := u256.Zero().AddOverflow(x, y)
100	if overflow {
101		return u256.Zero().SetAllOne()
102	}
103
104	return z
105}
106
107func saturatingMul(x *u256.Uint, y *u256.Uint) *u256.Uint {
108	z, overflow := u256.Zero().MulOverflow(x, y)
109	if overflow {
110		return u256.Zero().SetAllOne()
111	}
112
113	return z
114}
115
116func min(x *u256.Uint, y *u256.Uint) *u256.Uint {
117	if x.Lte(y) {
118		return x.Clone()
119	}
120
121	return y.Clone()
122}