| type | reference |
|---|
Beyond which config fields exist, a pool's runtime behavior differs by version, and an adopted older pool
behaves differently from the template's v2.0 default. Every row below is backed by a passing fixture that
exercises the real deployed bytecode of each version (a Sepolia fork), not a mock. Keyed off the pool's
typeAndVersion.
| Behavior | v1.5.0 | v1.5.1 | v1.6.0 (Siloed only) | v1.6.1 | v2.0 |
|---|---|---|---|---|---|
| Mixed decimals | Not supported (mints the raw source amount 1:1) | Supported | Supported | Supported | Supported |
| Inbound rate limit metered on | raw source amount | un-rescaled source amount | un-rescaled source amount | rescaled local amount | rescaled local amount |
| Rate-limiter config validation (enabled) | rate >= capacity or rate == 0 reverts |
same as v1.5.0 | same as v1.5.0 | rate > capacity reverts (rate == 0 allowed) |
same as v1.6.1 |
Pause via capacity=1, rate=1 |
reverts (1 >= 1) |
reverts | reverts | valid | valid |
| Fast-finality rate limiter | none | none | none | none | separate limiter; falls back to the standard bucket when disabled |
| Dedicated pause function | none | none | none | none | none (pause is the rate-limit throttle) |
A v2 pool has no dedicated pause or Pausable mechanism, so the reversible pause in both v1 and v2 is a
rate-limit throttle. The only hard stop is removing the chain, which tears the lane down rather than
pausing it (and carries a stale-config re-add gotcha), so it is the wrong tool for a temporary pause.
- On v1.6.1+ and v2.0, an enabled limiter with
capacity=0, rate=0is a true zero-throughput block (every transfer revertsTokenMaxCapacityExceeded), cleaner thancapacity=1, rate=1, which leaks one unit and refills. - On v1.5.x and Siloed v1.6.0 this is impossible: the validation rejects
rate == 0when enabled, so v1.5.x can only near-pause withcapacity > rate > 0(for example2/1), which still leaks.
Footgun: isEnabled=true with capacity=0, rate=0 PAUSES (blocks everything), while isEnabled=false
with the same numbers REMOVES the limit (unlimited). Identical numbers, opposite behavior; only the flag
differs. The UpdateRateLimiters script can set the enabled-0/0 pause via the explicit
OUTBOUND_RATE_LIMIT_ENABLED=true / INBOUND_RATE_LIMIT_ENABLED=true override; a 0/0 bucket declared in
the lanes{} config resolves to disabled (remove, not pause). See
operations: rate limits.
A v2.0 pool has a separate fast-finality rate limiter, but the fast-finality consume FALLS BACK to the standard bucket when the fast-finality bucket is disabled. So the "pause standard leaves fast-finality flowing" bypass bites ONLY when a separate fast-finality bucket is enabled with headroom; on a default pool (fast-finality disabled) the standard pause already covers fast-finality transfers. A full pause of a pool whose fast-finality bucket is enabled must throttle both bucket sets.
Every claim above is proven by a passing fixture against the real deployed bytecode of each version:
test/reference/RateLimiterVersionBehavior.t.solbacks the validation boundary, thecapacity=1, rate=1pause difference, the enabled-0/0pause and its disabled-0/0footgun, and the fast-finality fallback.test/reference/DecimalsMeteringVersionBehavior.t.solbacks the inbound-metering decimals difference across all four versions.
These deltas belong in the pause, rate-limit, and adoption flows as callouts. For the version catalog and support range, see pool versions.