feat(won): auto-unwrap on BSC→ETH + permissionless deposit#45
Conversation
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
left a comment
There was a problem hiding this comment.
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.
Summary
Two product changes to
WrappedON(the only custom contract), shipped together in one new (redeployed) wON since the contract is non-upgradeable:ON.balanceOf(wON) >= amount),mintdelivers native ON to the receiver (all-or-nothing) and mints 0 wON, leavingccipMintHeadroomUseduntouched; otherwise mints wON as before. New eventCCIPAutoUnwrapped.deposit— anyone can wrap ON→wON;LIQUIDITY_MANAGER_ROLEremoved 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-…anddocs/superpowers/plans/2026-06-23-….Invariants & safety
lockedON_BSC + reserveON_ETH >= totalSupply(wON)holds on the auto-unwrap branch (BSC lock +N, reserve −N net out).mint(keeps the single-custom-contract rule; stock CCIP pools unchanged). Verified the stockBurnMintTokenPool.releaseOrMintcallsmintand never reads the receiver balance, so delivering ON is transparent to the pool.SECURITY.md: permissionless-deposit supply posture; a compromisedMINTER_ROLEpool can now also drain the reserve (same trust assumption asmint).Tests
totalAutoUnwrappedaccounting, pool-level + fork auto-unwrap coverage.forge buildclean;forge fmt --checkclean.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 inRUNBOOK.md §4.4. The current on-chain wON is clean (no holders/reserve), so no migration.