feat(L2): add system address refunding to FeeDisburser#358
Conversation
Before bridging collected fees to L1, FeeDisburser now tops up configured L2 system addresses to their target balances, mirroring the BalanceTracker pattern on L1. Remaining balance after refunds is bridged to L1_WALLET as before. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
✅ Heimdall Review Status
|
Adds tests covering the new setSystemAddresses function, disburseFees with system address refunds, and reentrancy guard. Also fixes setSystemAddresses access control to use _assertOnlyProxyAdmin() instead of _assertOnlyProxyAdminOwner() so the L2 alias (raw EIP-1967 admin slot) can call it directly via deposit tx without requiring a ProxyAdmin contract. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Inline _processSystemAddressRefunds into disburseFees and _validateSystemAddressConfig into setSystemAddresses — both were private, single-caller wrappers. Add _makeSingleConfig test helper to collapse repeated 4-line array setup. Drop no-op vm.deal(addr, 0) calls. FeeDisburser now matches BalanceTracker's pattern on L1. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
- Fix ProcessedFunds NatSpec: clarify balanceSent is amount attempted, not received - Remove EmptySystemAddresses error; allow empty arrays to clear config - Change reentrancy guard from 0/1/0 to 1/2/1 to keep slot warm after first call - Fix setSystemAddresses NatSpec: "ProxyAdmin owner" → "ProxyAdmin" - Remove virtual from _refillBalanceIfNeeded (matches BalanceTracker pattern) - Add RevertingReceiver helper and tests for clearing config, multiple system addresses ordering, and reverting recipients Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
These files are not part of this PR; the fmt changes are noise. Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
jackchuma
left a comment
There was a problem hiding this comment.
The patch leaves required generated snapshots stale, which will fail repository checks, and the new setter is not reachable through the standard proxy admin owner flow. These should be fixed before the change is considered correct.
- Replace setSystemAddresses with initialize(reinitializer(2)) so config is set atomically via upgradeAndCall, resolving P2 access control issue - Regenerate ABI, storage layout, and semver-lock snapshots (P1) - Simplify ReentrantReceiver to be stateless (use msg.sender in receive) - Replace RevertingReceiver helper with vm.mockCallRevert - Remove redundant vm.deal(vault, 0) lines in refund tests (fresh state is 0) - Delete test_disburseFees_success_allFundsConsumedByRefund (covered by the more general partial refund test) Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
Approved review 4667004766 from jackchuma is now dismissed due to new commit. Re-request for approval.
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
Co-authored-by: Codex <codex-noreply@coinbase.com>
| } | ||
|
|
||
| lastDisbursementTime = block.timestamp; | ||
| uint256 disbursementTime = block.timestamp; |
There was a problem hiding this comment.
nit: block.timestamp is fixed for the transaction, so disbursementTime is only an alias used to assign storage and emit the event. Assign lastDisbursementTime = block.timestamp and emit block.timestamp directly to remove the temporary variable
There was a problem hiding this comment.
nit: feeBalance is only used for the immediately following zero-balance check. Nothing can change this contract’s balance between the assignment and condition, so if (address(this).balance == 0) removes a local without changing behavior
Co-authored-by: Codex <codex-noreply@coinbase.com>
Closes CHAIN-4644
What changed?
`FeeDisburser` now tops up a configurable set of L2 system addresses before bridging the remaining fees to L1. Previously, the full collected balance was always bridged.
Specifically:
Mirrors the existing `BalanceTracker` pattern on L1 (`src/L1/BalanceTracker.sol`).
Why?
L2 system addresses currently must be funded out-of-band, independently of the fee collection cycle. Routing a first-priority top-up through `FeeDisburser` (same contract that already holds all collected fees) eliminates that operational dependency and keeps the L1 and L2 funding patterns consistent.
How to test?
forge test --match-path test/L2/FeeDisburser.t.sol -vvvTests cover: