docs: add tokenomics vulnerabilities 39 and 40 - #351
Merged
Conversation
39. DefaultTargetDispenserL2.migrate() sweeps the balance to the new dispenser and then closes the old one permanently - owner is zeroed and _locked is left at 2, which the contract's own closing comment records as deliberate. Both are terminal: owner-gated paths and reentrancy-guarded paths alike become unreachable. A staking claim's token leg and message leg settle independently, so a token leg dispatched before migration can land afterwards on an address with no code path able to move it, with no forwarding function and no rescue. The amount is unrecoverable rather than stuck. migrate() does require a pause first, which is the intended settle-then-migrate procedure, but pausing stops new dispatches rather than a transfer already in flight. Nobody can cause it deliberately - the trigger is the migration's own timing - so it is recorded as an operational property. 40. ArbitrumDepositProcessorL1 enforces a fee floor of exactly two wei, so a bid of 2 passes. A reserve-covered claim advances L1 bookkeeping, including consuming the withheld amount, and enqueues the retryable at that boundary value; if the bid is not executable the message can expire while the L1 side already treats the claim as settled. The bid is the claimant's own input and a low bid mainly harms their own message. What is not theirs to control is the ordering - state advances before delivery is known executable - which is a property of the state machine rather than of any caller's input. Raising the constant is not a durable answer, since any fixed floor can be under-priced later, so the mitigation is on advancing state.
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.
Two new entries, from internal review.
39 — A retired L2 target dispenser cannot forward or release a late arrival
DefaultTargetDispenserL2.migrate()sweeps the balance to the new dispenser and then closes the old one permanently:Both statements are terminal, and the closing comment records the intent. With
ownerzeroed every owner-gated path is unreachable; with_lockedleft at2every guarded path is too.A staking claim's token leg and message leg settle independently. A token leg dispatched before migration can land afterwards on an address with no code path able to move it — no forwarding function, no rescue. The amount is unrecoverable, not merely stuck.
migrate()does require a pause first, which is the intended settle-then-migrate procedure — but a pause stops new dispatches, not a transfer already in flight across the bridge. Nobody can cause this deliberately; the trigger is the migration's own timing, so it is recorded as an operational property rather than an attack.Mitigation: keep a forwarding path on the retired dispenser — leave
ownerset to the new dispenser, or add arescue(address token)callable by it.40 — L1 claim state advances before an Arbitrum retryable is known redeemable
A bid of
2passes. A reserve-covered claim then advances L1 bookkeeping — including consuming the withheld amount — and enqueues the retryable at that boundary value. If the bid is not executable, the message can expire while the L1 side already treats the claim as settled.The bid is the claimant's own input, and a low one mainly harms their own message. What is not theirs to control is the ordering: state advances before delivery is known to be executable, which is a property of the state machine rather than of any caller's input.
Raising the constant is not a durable answer — any fixed floor can be under-priced later — so the mitigation is on advancing state, not on the bid.
Table of contents verified against headings.