Skip to content

feat(won): auto-unwrap on BSC→ETH + permissionless deposit#45

Merged
on-unknown-fish merged 9 commits into
mainfrom
feat/won-autounwrap-open-deposit
Jun 22, 2026
Merged

feat(won): auto-unwrap on BSC→ETH + permissionless deposit#45
on-unknown-fish merged 9 commits into
mainfrom
feat/won-autounwrap-open-deposit

Conversation

@chiro-hiro

Copy link
Copy Markdown
Contributor

Summary

Two product changes to WrappedON (the only custom contract), shipped together in one new (redeployed) wON since the contract is non-upgradeable:

  1. Auto-unwrap on BSC→ETH — when a CCIP arrival is fully covered by the wON wrap-reserve (ON.balanceOf(wON) >= amount), mint delivers native ON to the receiver (all-or-nothing) and mints 0 wON, leaving ccipMintHeadroomUsed untouched; otherwise mints wON as before. New event CCIPAutoUnwrapped.
  2. Permissionless deposit — anyone can wrap ON→wON; LIQUIDITY_MANAGER_ROLE removed entirely (reverses SECURITY M3/[Report M3] Public uncapped deposit() can expand wON supply beyond intended seeded liquidity #25 by product decision).

Design + plan: docs/superpowers/specs/2026-06-22-… and docs/superpowers/plans/2026-06-23-….

Invariants & safety

  • Safety invariant lockedON_BSC + reserveON_ETH >= totalSupply(wON) holds on the auto-unwrap branch (BSC lock +N, reserve −N net out).
  • Auto-unwrap is placed inside mint (keeps the single-custom-contract rule; stock CCIP pools unchanged). Verified the stock BurnMintTokenPool.releaseOrMint calls mint and never reads the receiver balance, so delivering ON is transparent to the pool.
  • Accepted residual risks recorded in SECURITY.md: permissionless-deposit supply posture; a compromised MINTER_ROLE pool can now also drain the reserve (same trust assumption as mint).

Tests

  • 150 mock-suite tests pass (incl. 4 stateful invariants; fork tests self-skip without RPC). New: auto-unwrap unit tests (covered / exact-reserve / one-wei-short / insufficient), invariant totalAutoUnwrapped accounting, pool-level + fork auto-unwrap coverage.
  • forge build clean; forge fmt --check clean.

Deployment note (operator, post-merge)

wON is non-upgradeable, so this requires a fresh ETH-side redeploy + BSC ETH-lane re-wire (BSC pool unchanged; re-wire via script 05 only — not make deploy-bsc, which re-runs the path-4-blocked script 04). Steps in RUNBOOK.md §4.4. The current on-chain wON is clean (no holders/reserve), so no migration.

Design spec for two product changes shipping in one wON redeploy:
auto-unwrap native ON on covered CCIP arrivals (all-or-nothing, inside
mint), and permissionless deposit (remove LIQUIDITY_MANAGER_ROLE). Covers
invariants, security record (M3/#25 reversal + reserve-drain blast radius),
clean redeploy + BSC re-wire, scripts, tests, and docs.
…deposit

Task-by-task TDD plan: remove LIQUIDITY_MANAGER_ROLE / permissionless
deposit, auto-unwrap native ON in mint, invariant + pool/fork coverage,
docs + M3 reversal, and the clean redeploy + BSC re-wire runbook.
Add test_BscToEth_AutoUnwrapWhenReserveCovers to PoolRoundtrip.t.sol and
test_Fork_ETH_BscToEth_AutoUnwrap to Fork_ETH.t.sol. Both tests prove that
a BSC→ETH CCIP arrival delivers native ON (not wON) when the wrap reserve
covers the amount, totalSupply is unchanged, and ccipMintHeadroomUsed stays
at zero.

Also promotes onEth from a setUp local to a state variable in PoolRoundtrip
so tests can deal/approve against it.
Update all five docs to reflect two shipped changes on this branch:
1. WrappedON.deposit is now permissionless; LIQUIDITY_MANAGER_ROLE removed.
2. WrappedON.mint auto-unwraps (delivers native ON when reserve covers
   a BSC→ETH arrival, mints 0 wON, cap counter untouched).

SECURITY.md WON-19 status → REVERSED (2026-06-23) with two residual-risk
notes: permissionless deposit bounds and auto-unwrap reserve-drain under
compromised MINTER_ROLE pool.
…olish)

- SECURITY.md: update three stale §4.4 cross-refs to §4.5 (rate-limit /
  stuck-transfer section; §4.4 is now Redeploy since this branch added it)
- SECURITY.md WON-19: drop script/06_TransferOwnership.s.sol from Location
  (LIQUIDITY_MANAGER_ROLE removed; only src/WrappedON.sol is relevant)
- Fork_ETH.t.sol: add ccipMintHeadroomUsed == 0 assertion to AutoUnwrap test
- README.md: add "(all-or-nothing)" qualifier to auto-unwrap description
- CLAUDE.md: note mint auto-unwrap behaviour in wON bullet
- WrappedON.t.sol: add test_MintMintsWonWhenReserveOneWeiShort boundary test

@on-unknown-fish on-unknown-fish left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the wON auto-unwrap + permissionless deposit change.

Contract (src/WrappedON.sol): The auto-unwrap branch in mint is correct — placed after the amount == 0 guard, under nonReentrant, delivers native ON all-or-nothing and returns early without incrementing ccipMintHeadroomUsed. The safety invariant lockedON_BSC + reserveON_ETH >= totalSupply(wON) holds mechanically (BSC lock += amount, reserve -= amount net out), and saturating-decrement burns already tolerate the never-incremented counter, so no underflow on later bridge-out. Permissionless deposit is a clean modifier removal with the role fully excised from the contract, scripts 06/08, and all tests.

Tests: Thorough — unit coverage incl. exact-reserve and one-wei-short boundaries, pool-level and fork tests exercising the real BurnMintTokenPool.releaseOrMint (which has no post-mint balance-delta assertion, so a 0-wON mint is tolerated), and the stateful invariant handler correctly extended with totalAutoUnwrapped to keep the tight reserve invariant. The handler's maxAmount = max(reserve, headroom) bounding is sound — any amount > reserve necessarily has headroom >= amount, so the cap-check path never spuriously reverts.

Docs: Consistent across all six files; SECURITY.md WON-19 marked REVERSED with honest residual-risk notes (permissionless-deposit bounds + compromised-pool reserve drain under the trusted-MINTER model).

CI green (Build & test, Slither). No merge conflicts. LGTM.

@on-unknown-fish on-unknown-fish merged commit a9ccb0d into main Jun 22, 2026
2 checks passed
@on-unknown-fish on-unknown-fish deleted the feat/won-autounwrap-open-deposit branch June 22, 2026 18:14
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