You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Move the bucket to Rain Floats, and reject negative values #115
The orchestrator in S01-Issuer/st0x.deploy meters mint caps with this library. That metering is moving from raw token amounts to values, and the value arrives as a Rain Float, so the bucket needs to work in Floats rather than in fixed point.
What changes
LeakyBucket currently holds uint256 checkpoint, uint256 capacity, uint256 leakRate, with the level packed as (level << 64) | timestamp and LEAKY_BUCKET_LEVEL_MAX = type(uint256).max >> 64. Level, capacity and leak rate become Rain Floats.
The arithmetic has a direct equivalent: add, sub, mul and the comparisons all exist in LibDecimalFloat, and division by zero reverts rather than producing a NaN.
Prevent negatives
A uint256 level could not be negative by construction. A Rain Float carries a signed int224 coefficient, so the migration introduces the possibility. The library itself should reject negative values rather than leaving each consumer to guard its own inputs — a consumer computing a fill from an external price has no other backstop if that price arrives negative.
Zero is already rejected and stays rejected.
Two slots is acceptable
The packed (level << 64) | timestamp word does not survive: a Float needs all 32 bytes, so the checkpoint becomes two slots and costs an extra cold SLOAD/SSTORE per operation. The consumer has confirmed that is acceptable — its mints are large enough that the difference is immaterial.
Things to be careful about
Comparisons must go through LibDecimalFloat. A Float's exponent occupies the high 32 bits as a two's-complement int32, so a negative exponent reads as an enormous unsigned value. A raw bytes32 or uint256 comparison on a level against a capacity orders them incorrectly while looking entirely plausible on review.
Accumulation can round where integer arithmetic was exact. A bucket applies level - drain + fill repeatedly, and packLossy shrinks the coefficient when it exceeds int224. A systematic rounding bias in one direction is a slow drift in the limit, so the rounding direction should be chosen so the error is always conservative — the bucket fills at least as fast as the true value.
Rounding is decided. SPEC.md item 15: the bucket rounds so that it fills at least as fast as the true value.
So where Float accumulation across level - drain + fill has to round, it rounds against the caller: the level is never understated and the drain is never overstated. A systematic bias the other way is a rate limit that drifts loose over thousands of operations with nothing catching it.
Closing as complete. Floats landed in #116 and negative values are rejected by #123. The one remaining item was directed rounding, and that is ruled not a problem: rounding direction does not accumulate in the bucket. That supersedes the earlier rounding note on this issue.
Activity
thedavidmeister commentedon Sep 24, 2026
Rounding is decided. SPEC.md item 15: the bucket rounds so that it fills at least as fast as the true value.
So where Float accumulation across
level - drain + fillhas to round, it rounds against the caller: the level is never understated and the drain is never overstated. A systematic bias the other way is a rate limit that drifts loose over thousands of operations with nothing catching it.audit: record the scan after the negative level and timestamp guards
test: bound the negative timestamps without a truncating cast
audit: record the scan after the lint fix on #123
thedavidmeister commentedon Oct 4, 2026
Closing as complete. Floats landed in #116 and negative values are rejected by #123. The one remaining item was directed rounding, and that is ruled not a problem: rounding direction does not accumulate in the bucket. That supersedes the earlier rounding note on this issue.