PR-C: implement locked review rulings (S-01, S-03, S-05, S-09, S-11, S-12, S-13) - #14
Merged
Conversation
…S-12, S-13) S-01: honest bucket-index init; settlement floors index at 1 wei — the 0==RAY lazy sentinel that resurrected wiped buckets is gone (root cause). S-03: sMax never snaps down; decay is the sole descent, floored at the tracked leader; permissionless refreshSMax(postId) closes lazy-accrual deviations; tracker widened to TRACKED_POSTS=10. I.4 re-documented as true-about-mechanism; S-04 global coupling ratified as intended. S-05: false rMax<RAY comments corrected at both bucket sites. S-09: 9e17 (10%/day) is correct; docstring fixed; Deploy.s.sol pins it. S-11: StakeLot.entryEpoch removed; getUserLotInfo now a 4-tuple. S-12: _rescalePositions + PositionsRescaled removed (dead code). S-13: unreachable ZeroAddress branch removed from acceptGovernance. Reviewer PoCs for the fixed findings converted to regression form; S04SpecTestPoC Q2 documents the changed (intended) exited-whale arc. New test/PrCRegressions.t.sol: 7 targeted regressions. Suite 332/0. Pre-genesis: storage layout and ABI changes are free; fresh Fuji redeploy required (sequenced with Track B mock deploy).
…ntryEpoch removal; pre-genesis, fresh redeploy planned)
…th — nonReentrant + own-token, same class as stake/withdraw; _projectBucket divide-before-multiply — intentional truncation-order mirror of _settleBucket for V.8) + prune fingerprints of removed code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR-C: implement the locked security-review rulings
Closes out the remaining findings from @ibnu76's review of
58971c05, per therulings locked in SESSION-STATE. Builds on merged main (
aa7d477= PR-A #12 +PR-D #13). Pre-genesis, so storage-layout and ABI changes are free.
Rulings implemented
S-01 (Low) —
bucketIndexRay == 0sentinel collision → honest init (Option 1).The pooled tail bucket used
0as a lazy "means RAY" sentinel. Sustained lossesdecay the index toward zero; the moment a settlement wrote exactly
0, the nextread resurrected every wiped bucket at face value — insolvency (~1400 days at
deployed rates, ~250 at the 5e18 policy cap;
S01DeployParamshas the sweep).Now:
_bucketAddinitializes the index to RAY explicitly on first entry (safe:a floor-guaranteed nonzero index means
0 ⇔ no member has ever entered, zero scaled shares),_bucketIndexreturns storage verbatim, and_settleBucketfloors the index at 1 wei — a fully wiped bucket is dust-dead, exits stay live
via the full-exit branch, and no value can be conjured back.
_projectBucketwas rewritten to project through the index with identical operations and the
same floor, so view == materialized to the wei even in the wipe regime
(a V.8 fidelity improvement over the old live-based formula).
S-03 (Low) — sMax tracker, layered per the ruling.
(i) Never snap down. The old code snapped
sMaxto the tracked leader theinstant the leader shrank — a 1-wei dust post could drag
sMaxto dust andinflate every post's participation factor to the clamp. Decay (10%/epoch,
30-epoch catch-up cap) is now the sole descent, floored at the tracked
leader. (ii) Permissionless
refreshSMax(postId). The irreducible poke forlazy-accrual dormant posts: settles the post if an epoch has passed, then feeds
its true stored total to the tracker. It can only make
sMaxmore honest, soit is open to anyone; the ops worker will poke the largest posts each epoch
(companion app-repo patch). Emits
SMaxRefreshed. (iii) Tracker widened3 → 10 (
TRACKED_POSTS);getTopPosts()still returns the top three pairsfor ABI stability.
S-04 (closed, ratified) + I.4 re-documented.
ECONOMIC_INVARIANTS.mdI.4is rewritten to be true-about-this-mechanism: sMax ≥ every tracked total after
update; deviations from untracked dormant posts are closeable by anyone via the
poke; participation clamps at 1.0 bound the pre-poke worst case at the rMax
ceiling. The cross-post coupling through global sMax is ratified as intended
design in one sentence (code comment at the participation site + I.4).
S-05 (Info) — false
rMax < RAYcomments. Both bucket-math sites now statethe true bound: rMax scales with elapsed epochs, rBase can exceed RAY under
dormancy, and the
gRay >= RAYwipe branch is live (correct total-lossfloor), not dead as the old comment claimed.
S-09 (Info) — decay-rate docstring. 9e17 (10%/day) was always the deployed
constant and is correct as the backstop; the docstring claiming 0.5%/day was
the bug. Docstring fixed;
Deploy.s.solnow sets the value explicitly on thedev path and
require-pins it on every path, so drift fails the deploy loudly.S-11 (Info) —
StakeLot.entryEpochremoved. Stored, never read by anysettlement path. Struct field, write site, and the view tuple slot are gone:
getUserLotInforeturns(amount, weightedPosition, sideTotal, positionWeight). Interface updated; all in-tree destructurings mechanicallymigrated (36 sites).
S-12 (Info) —
_rescalePositionsremoved. Positions are recomputed asmidpoints (< total) after every mutation and settlement, so the rescale body
was unreachable outside the neutral branch, where it was a no-op. Function,
both call sites, and the
PositionsRescaledevent are gone; the_applyEpochclamp remains as the safety net.
S-13 (Info) — dead
ZeroAddressbranch inacceptGovernanceremoved.When pending is
address(0)no caller can equal it, soNotPendingGovernancealways fired first. Behavior identical; regression test pins the revert.
Behavior change to be aware of (intended)
Under never-snap-down, an exited whale's sMax peak no longer vanishes
instantly — it decays (bounded, ≤ ~90 epochs to burn 4 orders of magnitude,
faster with per-epoch pokes), transiently suppressing smaller posts' rates.
That trade is the point: instant snap-down was the manipulation lever.
S04SpecTestPoC::test_Q2now documents the full arc: suppression exists →pokes burn the peak down → rate recovers to the never-whaled rate within 2%.
Tests
solvency asserts for S-01; drag/snap-down asserts inverted for S-03 with the
poke closing the documented pre-poke gap; S-11 rewritten as a 4-tuple
queue-position regression). Untouched PoCs remain as evidence.
test/PrCRegressions.t.sol: 7 targeted regressions (never-snap-down;exact decay curve + leader floor; permissionless poke honesty; 10-slot
tracker; wiped-bucket solvency across two settlements incl. member exit;
fresh-bucket re-init; S-13 revert).
the working sandbox, once on a pristine reconstruction of merged main with
only the shipped editors applied.
src/StakeEngine.solsha256:4c6f8dc99a10a0dd4ec2d47ace930a123a3f13fb48d3eeedeea62add777f17a9.Toolchain note (worth reading — cost us a phantom-bug hunt)
solc 0.8.33 under this profile caches in-frame
block.timestampafter thefirst read, so chains of
vm.warp(block.timestamp + X)inside one test framecollapse to a single warp (the warp applies to the env; the test's re-read is
stale — engine/handler frames always read fresh, so contracts and PR-D's
invariant handler are unaffected). New tests use
vm.getBlockTimestamp()forwarp arithmetic. Existing PoCs are left as authored: they are empirically
anchored on this exact toolchain, and their
_fresh()absolute-warp patternre-anchors each scenario correctly.
Follow-ups
Track B mock deploy;
app/deployments/fuji.jsonaddresses rotate and the apprepo needs the 4-tuple
getUserLotInfoABI.refreshSMaxeach epochfor the largest posts.
complains, re-run with
--update(expected shrink only, no new findings).