Skip to content

docs(fee): document effective base-fee floor vs minBaseFee - #372

Open
kutluhaneth46 wants to merge 3 commits into
circlefin:mainfrom
kutluhaneth46:cursor/docs-fee-effective-floor-88c1
Open

docs(fee): document effective base-fee floor vs minBaseFee#372
kutluhaneth46 wants to merge 3 commits into
circlefin:mainfrom
kutluhaneth46:cursor/docs-fee-effective-floor-88c1

Conversation

@kutluhaneth46

Copy link
Copy Markdown
Contributor

Summary

  • Documents the effective empty-block floor max(minBaseFee, 10000/kRate) in ADR-0004 (decrease-path integer truncation).
  • Notes that assets/testnet/config.json fee params are historical vs live testnet.
  • Adds a unit test pinning early-testnet-like kRate=1250 resting at 7 wei.

Deliberately does not add a ProtocolConfig require(minBaseFee * kRate >= 10000) — that would break regenerating historical testnet genesis (kRate: 25, minBaseFee: 1). Happy to follow up if maintainers want that guard only on updateFeeParams.

Addresses #367.

Test plan

Pin the empty-block truncation floor (kRate=1250 → 7 wei) with a unit
test and record the max(minBaseFee, 10000/kRate) interaction in ADR-0004.
Leaves ProtocolConfig requires alone so historical testnet genesis still
regenerates. Addresses circlefin#367.
Tighten ADR-0004 wording and the unit test per review on circlefin#367: the
truncation threshold is 10000/kRate, but the fee rests one wei below it.
Pin the early-testnet (8→7, 7→7) boundary vectors explicitly.
@kutluhaneth46

Copy link
Copy Markdown
Contributor Author

Thanks @osr21 for the formula correction on #367 — you're right that `10000/kRate` is the truncation threshold, not the resting value.

Updated ADR-0004 to `ceil(10000/kRate) - 1` (1250 → 7, 200 → 49) and reshaped the unit test to assert the `8 → 7` / `7 → 7` boundary property with the early-testnet parameter triple pinned, rather than only a long empty-block run.

Fix cargo fmt --check failure on the assert_eq! line length.
@osr21

osr21 commented Sep 9, 2026

Copy link
Copy Markdown

Reviewed #372 since that's where the correction landed. The formula and the test are right — I replayed them rather than eyeballing. One inconsistency in the ADR prose is worth fixing before merge, because it will read as a contradiction to anyone who checks the committed config.

Verified

I re-derived arc_calc_next_block_base_fee standalone and validated the port before using it: brute-forcing kRate ∈ [1,10000] × iem ∈ {2500,5000,7500,10000} against the committed test_calc_next_block_base_fee table returns a unique match at (200, 7500), i.e. ARC_BASE_FEE_K_RATE / ARC_BASE_FEE_INVERSE_ELASTICITY_MULTIPLIER. Replaying your test at k_rate=1250, iem=5000, gas_limit=30_000_000:

gas_target = 15,000,000   denominator = 120,000,000   =>  decrease = floor(base_fee / 8)

8 -> 7   PASS      7 -> 7   PASS      6 -> 6   PASS
1 gwei  -> settles at 7 after 145 empty blocks   PASS   (loop budget 2,000)

All four assertions hold, and the 2,000-iteration budget has ~13x headroom over the 145 steps actually needed. k_rate: u64 in the signature, so the bare let k_rate = 1250; infers cleanly. Caveat: this is arithmetic verification, not a compile — I can't run cargo here, so your unchecked cargo test box still needs a real run.

The ADR formula block is correct, including the threshold-vs-resting distinction and kRate = 200 ⇒ 49.

The ADR example contradicts the committed config

early testnet sat at 7 wei for millions of blocks against a declared minBaseFee of 1

The minBaseFee = 1 comes from assets/testnet/config.json — but that same file declares kRate: 25, which predicts a resting value of 399, not 7. The two halves of the sentence come from different parameter generations:

source kRate predicted resting
assets/testnet/config.json (and assets/devnet/config.json) 25 399
assets/mainnet/config.json + live testnet feeParams() 200 49
early testnet, implied by the observed 7 wei 1250 7

A reader who follows the ADR to the committed file will compute 399, get a mismatch, and conclude the paragraph is wrong. Naming the value fixes it — something like "early testnet ran kRate = 1250 (resting value 7), which matches neither the committed genesis in assets/testnet/config.json (kRate = 25 ⇒ 399) nor the live parameter set (kRate = 200 ⇒ 49)". That also makes your "historical genesis values" note land harder, since it shows there have been at least three generations rather than two.

Why 1250 is a principled choice for the test

Worth saying in the test comment, because it currently reads as an arbitrary constant: kRate = 1250, iem = 5000 is exactly the EIP-1559 equivalence point. The crate's existing test_calc_next_block_base_fee already asserts that arc_calc_next_block_base_fee matches eip1559_next_base_fee when called with ARC_BASE_FEE_FIXED_POINT_SCALE / DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR (10000/8 = 1250) and ARC_BASE_FEE_FIXED_POINT_SCALE / DEFAULT_ELASTICITY_MULTIPLIER (10000/2 = 5000). So early testnet was running EIP-1559-equivalent fee dynamics, and your test parameters sit on a line the crate already covers — not a made-up config.

One boundary worth adding

kRate = 10000 is the maximum ProtocolConfig permits, and it's the degenerate end of this behaviour: denominator == gas_target, so decrease == base_fee and a single empty block takes the computed fee to 0, after which the output clamp lifts it to minBaseFee. It's the only case where the decrease path's "never reaches zero" intent fails outright, and resting = ceil(10000/10000) - 1 = 0 keeps your formula consistent through it. Three lines in the same test.

The domain caveat you wrote ("at production-scale gas limits") is doing real work, incidentally — the closed form loses a wei at degenerate gas targets, e.g. kRate = 3 rests at 3332 rather than 3333 when gas_limit is tiny. Good that it's scoped.

Nit

The ADR hunk adds a stray blank line inside the pseudocode block, just above # 4. Apply chainspec absolute bounds — unrelated to the change, same artifact you cleaned up in #373.

Agreed on leaving the ProtocolConfig require alone, for the reason you gave.


Disclosure: I'm an external community contributor, not affiliated with Circle, with no write access to this repository. Advisory review only, not an approval — the numbers above come from a standalone replay validated against the crate's own test vectors, and should be confirmed with a real cargo test run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants