Skip to content

feat(zones): park forced-exit fees in a separate claimable pool - #8440

Open
adityapk00 wants to merge 10 commits into
exithatchfrom
claude/forced-exit-fee-pool-ffbf02
Open

adityapk00 wants to merge 10 commits into
exithatchfrom
claude/forced-exit-fee-pool-ffbf02

Conversation

@adityapk00

@adityapk00 adityapk00 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #8401 for TIP-1012 (#7593, review thread).

A failed admin payout now parks the fee in pendingForcedExitFees[token][admin] instead of user refunds, and claimForcedExitFees(token) pays it to the caller without a Role.Account check, so an admin outside the allowlist can still claim after AccessPolicy abdication. Fees stay with the admin credited at admission and are paid only to that address, so a TIP-403 block on the admin still holds. claimRefund and claimForcedExitFees share one payout helper; claimRefund's behaviour is unchanged. The checker and verify-portal-backing count parked fees as a separate liability.

The checker's persisted TokenState gains a pending_forced_exit_fees field, so its schema version moves from 1 to 2. Existing checker databases fail to open with a schema mismatch and need a resync (delete <datadir>/checker).

forced-withdraw now warns when the account pays its own fee, since that publishes the account as the L1 fee payer.

The first commit is a pure rename (compensation → fee).

🤖 Generated with Claude Code

adityapk00 and others added 2 commits October 19, 2026 09:51
Pure rename, matching TIP-1012: `FORCED_EXIT_COMPENSATION` → `FORCED_EXIT_FEE`,
`ForcedExitCompensationPending` → `ForcedExitFeePending`, and
`ForcedExitCompensationRejected` → `ForcedExitFeeRejected`, plus the helpers,
tests, metric name, and docs that use them. The T14 `ZonePortal` runtime is
regenerated with `forge inspect ZonePortal deployedBytecode`; the local forge
reproduces the previous runtime byte-for-byte, so only the renamed selectors,
topics, and metadata hash change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed admin payout in `requestForcedExit` now credits
`pendingForcedExitFees[token][admin]` (new slot 31) instead of the user
`refunds` registry. `claimForcedExitFees(token)` pays the caller only, with the
same TIP-1028 precheck and `CallbackRejected` revert as `claimRefund`, and
emits `ForcedExitFeesClaimed`. It has no `Role.Account` check, so an admin
outside the allowlist can still claim after AccessPolicy abdication, and fees
stay with the admin credited at admission.

The checker and `verify-portal-backing` count parked fees as their own
liability. The checker's persisted `TokenState` gains a field, so its schema
version moves to 2 and existing checker databases must be resynced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Changelog not found.

A changelog entry is required before merging. We've generated a suggested changelog based on your changes:

Preview
---
tempo-chainspec: patch
---

Updated the predeployed contract bytecode in the development genesis configuration.

Add changelog to commit this to your branch.

adityapk00 and others added 2 commits October 19, 2026 10:04
exithatch has not shipped, so its checker changes land under one schema
version rather than bumping per PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

📊 Tempo Precompiles Coverage

📦 Download full HTML report

@adityapk00

Copy link
Copy Markdown
Contributor Author

cyclops audit

@tempoxyz-bot

tempoxyz-bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

✅ Cyclops Security Review: safe to merge

Last reviewed: bd2a431 (latest) · no open findings

# Severity Finding Introduced Status
1 ⚪ Informational New token fee-liability field breaks existing checker databases on upgrade d7d386c ⚪ Invalid, per @​adityapk00

Comment thread crates/zones/checker/src/accounting/mod.rs
adityapk00 and others added 6 commits October 19, 2026 12:14
`pending_forced_exit_fees` changes the persisted `TokenState`, which ships on
main at schema 1 with five fields. Bumping to 2 makes a database written by a
main build fail the version check instead of a later token-row decode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`claimRefund` and `claimForcedExitFees` now pay through one `_payClaim`
helper; each keeps its own access check and event. Regenerates the T14
`ZonePortal` runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refund, forced-exit fee, and inbox liability scans now sum through one `sum_amounts` helper, and `outstanding_refunds` becomes `outstanding` so the fee path no longer reports a refund history error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`setAdminReceives` replaces the repeated admin receive-policy toggles, and `assertFeeParked` takes its own balance snapshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Self-paying publishes the account as the L1 fee payer, which TIP-1012 says
clients should warn about.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adityapk00

Copy link
Copy Markdown
Contributor Author

cyclops audit

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. It changes how the ZonePortal holds and pays out funds and adds a new storage slot, and the files are under CODEOWNERS, so a human look would still be worthwhile.

What was reviewed:

  • The requestForcedExit fee parking, the new claimForcedExitFees, and the shared _payClaim helper. The helper zeroes the balance before the transfer and reverts with CallbackRejected if the transfer fails. claimRefund still runs _requireAllowed first and emits the same event as before.
  • Storage slot 31 for pendingForcedExitFees is appended after slot 30, so no existing slot moves.
  • In the checker, the new ForcedExitFee liability is included in liability() with checked addition, and zero-amount fee claims are ignored. A RefundClaimed event can no longer clear a parked fee (there is a test asserting Underflow). SCHEMA_VERSION goes from 1 to 2 for the new TokenState field.
Extended reasoning...

The diff is 22 files (about 900 lines added, 630 removed). It covers the ZonePortal Solidity contract, its storage layout, regenerated bindings and genesis bytecode, the zone checker's accounting and persistence, xtask tooling, and the spec and docs. The security-sensitive part is custody: who can claim parked forced-exit fees. claimForcedExitFees deliberately skips the Role.Account allowlist check, and pays only the admin who was credited when the forced exit was admitted. The paths are owned by CODEOWNERS (crates/zones/contracts, crates/contracts, crates/chainspec, xtask). An earlier inline bot thread on the checker accounting was resolved only by the PR author. Given the size, the change to fund handling and the code ownership, this goes to a human rather than being auto-approved.

This branch has not been deployed

No deployments
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