Skip to content

fix(security): timelock storage slot, drip mutex, inflation pool deregister#116

Merged
drewstone merged 1 commit into
mainfrom
fix/post-launch-hardening-followups
May 4, 2026
Merged

fix(security): timelock storage slot, drip mutex, inflation pool deregister#116
drewstone merged 1 commit into
mainfrom
fix/post-launch-hardening-followups

Conversation

@drewstone
Copy link
Copy Markdown
Contributor

Summary

Follows up #115 with the code-level remaining items from the pre-mainnet audit. Three operational items (populate base-mainnet.json multisigs, deploy/pin Timelock+Governor, choose Hyperlane ISM / LayerZero DVN config) need your input and aren't in this PR.

Changes

TangleTimelock — fix _setMinDelay slot + restore onlySelf

The previous override of updateDelay had two bugs:

  1. Authorization regression — relaxed parent's onlySelf to onlyRole(DEFAULT_ADMIN_ROLE). Compromised admin could change the delay outside a queued proposal.
  2. Wrong storage slotsstore(0x33, newDelay). OZ 5.x TimelockControllerUpgradeable uses ERC-7201 namespaced storage at 0x9a37c2aa…fb3600; _minDelay is at that location + 1. Writing to slot 51 was a silent no-op for getMinDelay() and could corrupt unrelated proxy storage.

Now:

  • onlySelf reinstated (callable only via a queued, scheduled proposal).
  • _setMinDelay writes to TIMELOCK_CONTROLLER_STORAGE_LOCATION + 1.
  • Bounds [1 day, 30 days] still enforced.

StreamingPaymentManager — drip mutex

dripAndGetChunk and dripOperatorStreams are now nonReentrant. Closes the same-tx double-drip race where the transfer-to-distributor hook could re-enter and process additional chunks before the first frame finished updating lastDripTime.

InflationPool — deregister inactive operators

trackedOperators previously grew monotonically; every distribution iterated stale entries. New paths:

  • deregisterOperator(address)
  • deregisterOperators(address[])
  • deregisterCustomer(address)
  • deregisterDeveloper(address)

Each clears the isTracked* flag, swap-and-pops the address out of the tracked list, and clears *RegistrationEpoch. Pending rewards remain claimable. Re-registration is permitted.

Tests

Two new files under test/security/:

  • TimelockSetMinDelayTest.t.sol (3 tests): non-self caller reverts, getMinDelay() reflects new value, bounds enforced.
  • InflationPoolDeregisterTest.t.sol (3 tests): removes from tracked list, non-admin reverts, re-registration allowed.

Full suite: 1478/1478 passing (1472 baseline + 6 new).

Test plan

  • forge build clean
  • forge test -- 1478/1478 passing on fast profile
  • CI green
  • Reviewer walks the new test files
  • OZ pin: confirm we are still on @openzeppelin-contracts-upgradeable 5.1.0. The TIMELOCK_CONTROLLER_STORAGE_LOCATION constant must be re-verified before bumping.

Still operational, not in this PR

  • Populate deploy/config/base-mainnet.json with the production multisig addresses.
  • Deploy TangleTimelock + TangleGovernor and either pin their addresses or wire them through FullDeploy.
  • Pin Hyperlane ISM (e.g. AggregationIsm[MultisigIsm + TrustedRelayer]) and LayerZero DVN/ULN.

…gister

Code-level follow-ups from the pre-mainnet audit (#115).

TangleTimelock
- updateDelay restored to onlySelf (parent's policy); the previous override
  silently degraded it to onlyRole(DEFAULT_ADMIN_ROLE).
- _setMinDelay now writes to the correct OZ ERC-7201 namespaced slot for
  TimelockControllerStorage._minDelay (TIMELOCK_CONTROLLER_STORAGE_LOCATION
  + 1) instead of slot 0x33. The old write was a no-op for getMinDelay()
  and could silently corrupt slot 51 of the proxy.
- Bounds (MIN_DELAY=1d, MAX_DELAY=30d) still enforced.

StreamingPaymentManager
- dripAndGetChunk and dripOperatorStreams now nonReentrant. Closes the
  same-tx double-drip race where the transfer-to-distributor hook could
  re-enter and process additional chunks before the first frame finished.

InflationPool
- New deregisterOperator / deregisterOperators / deregisterCustomer /
  deregisterDeveloper paths. Inactive operators no longer accumulate in
  trackedOperators, capping distribution-loop gas growth over time. The
  swap-and-pop helper clears membership flags and registration epochs;
  pending rewards remain claimable. Re-registration is permitted.

Tests
- test/security/TimelockSetMinDelayTest.t.sol: 3 tests (caller auth,
  persistence through getMinDelay, bounds enforcement).
- test/security/InflationPoolDeregisterTest.t.sol: 3 tests (removal,
  non-admin revert, re-registration).
- Full suite: 1478/1478 passing.
@tangletools
Copy link
Copy Markdown
Contributor

tangletools commented May 4, 2026

🔍 Reviewing 7cea7895

Pass Status ETA
🔬 Kimi agent 🟡 Running (2 min) ~5-15 min

Agent review running. Reads the actual code. This comment updates in place.

tangletools · #116 · model: kimi-for-coding · started 2026-05-04T17:02:48Z

@drewstone drewstone merged commit 2d0d606 into main May 4, 2026
1 check failed
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