Skip to content

refactor: deprecate deposit & V0 paths in MembershipManager (strict deletion-only)#427

Merged
0xpanicError merged 2 commits into
pankaj/feat/security-upgradesfrom
seongyun/refactor/deprecate-membership-deposits-clean
May 26, 2026
Merged

refactor: deprecate deposit & V0 paths in MembershipManager (strict deletion-only)#427
0xpanicError merged 2 commits into
pankaj/feat/security-upgradesfrom
seongyun/refactor/deprecate-membership-deposits-clean

Conversation

@seongyun-ko
Copy link
Copy Markdown
Contributor

@seongyun-ko seongyun-ko commented May 25, 2026

Summary

Stacks on top of #420 (pankaj/feat/security-upgrades). After the one-shot V0 → V1 batch migration on mainnet (279 NFTs / ~186 ETH; verified by full 10k-id sweep: tokenDeposits[id].amounts > 0 count = 0), the V0 storage + V0→V1 migration code + the entire deposit/mint/top-up surface in MembershipManager is unreachable from any live NFT. We're also closing the contract to new deposits, so this PR removes that dead code.

Audit-scope guarantee — strict deletion-only

File Insertions Deletions
src/MembershipManager.sol 0 306
src/interfaces/IMembershipManager.sol 0 14
src/MembershipNFT.sol 0 0
src/interfaces/IMembershipNFT.sol 0 0
src/archive/MembershipManagerV0.sol 0 0

Every line of source code that remains is byte-identical to its pre-trim form. No reorganization, no new comments, no new logic, no behavior change in remaining code. MembershipNFT is untouched entirely.

Size impact (MembershipManager only)

Bytes Margin
Before 26,412 over the 24,576 EIP-170 cap
After 17,184 +7,392

Functions removed from MembershipManager

External (entry points):

  • wrapEthForEap, wrapEth (×2), topUpDepositWithEth
  • migrateFromV0ToV1
  • addNewTier, updateTier, setPoints, updatePointsParams, setDepositAmountParams, setTopUpCooltimePeriod, setFeeAmounts, setFanBoostThresholdEthAmount

Internal (had no remaining callers after the externals above were removed):

  • _distributeStakingRewardsV0, _claimStakingRewards, _migrateFromV0ToV1
  • _incrementTokenDeposit, _decrementTokenDeposit, _incrementTierDeposit, _decrementTierDeposit
  • _mintMembershipNFT, _deposit, _topUpDeposit, _wrapEth
  • _incrementTokenVaultShareV1, _setPoints, _feeAmountSanityCheck

Call-site removals in KEEP functions (single-line deletions only — diff is purely lines preceded by -):

  • rebase(): _distributeStakingRewardsV0(...)
  • claim(): _claimStakingRewards, _migrateFromV0ToV1
  • unwrapForEEthAndBurn(): same + the // Claim ... before burn comment
  • requestWithdrawAndBurn(): same + the comment

Functions intentionally preserved

  • MembershipManager.canTopUp(...) — kept because MembershipNFT.canTopUp() forwards to it. Removing it would force a deletion in MembershipNFT, expanding audit scope. Pure view, no security implication.

Storage layout

Preserved unchanged. The V0 mappings (tokenDeposits) and array (tierDeposits) remain in the layout for a safe in-place UUPS upgrade — auto-generated getters return zeroed values.

Verification

On-chain sweep

Full 10,000 ever-minted ID sweep against mainnet:

  • tokenDeposits[id].amounts > 0: 0
  • version == 1 (alive V1 NFTs): 1,761
  • version == 0 (burned slot or never minted): 8,239
  • Unexpected versions: 0

Fork test

test/MembershipDeprecationUpgradeFork.t.sol forks mainnet, deploys the trimmed implementation, UUPS-upgrades the live MM proxy (owner() == UPGRADE_TIMELOCK), and exercises real on-chain fan-holder flows:

Test Token Owner Result
unwrapForEEthAndBurn_migratedV0 6251 (was V0) 0x8a28…A079 ✅ burned, ~1.117 eETH received
unwrapForEEthAndBurn_bornV1 7500 0x277f…7d44 ✅ burned, ~0.137 eETH received
requestWithdrawAndBurn_bornV1 8500 0xba32…315B ✅ burned, returned WithdrawRequestNFT id
requestWithdraw_partial_bornV1 9900 0x8B3f…13B7 ✅ NFT alive, valueOf decreased correctly
rebase_postTrim_allAccrualBranches ETHERFI_ADMIN ✅ 0 / +1 ETH / −1 ETH all work

Run:

forge test --match-contract MembershipDeprecationUpgradeForkTest \
  --fork-url $MAINNET_RPC_URL -vv

Migration artifacts

script/operations/v0-migration/ ships the one-shot migrator that cleared the 279 V0 NFTs + the JSON ID list + test/V0MigrationFork.t.sol for re-verification at audit time.

Test churn (also deletion-only)

  • test/Blacklist.t.sol: 22 lines deleted (3 blacklist tests exercising the removed deposit functions). No additions.
  • All other test files untouched.

Test plan

  • forge build clean
  • forge test --match-contract MembershipDeprecationUpgradeForkTest --fork-url $MAINNET_RPC_URL — all 5 pass
  • Full on-chain V0 sweep confirms zero holdouts
  • OZ storage-layout check before merge
  • Build the on-chain upgrade tx (UUPS upgradeTo on the MM proxy; authorized by UPGRADE_TIMELOCK)

cc @0xpanicError for review


Note

High Risk
Large deletion on a live UUPS proxy handling membership NFT balances and rebase; wrong ordering (trim before migration) or storage-layout mistakes could brick exits or rewards, though fork tests and preserved slots mitigate that.

Overview
This PR removes dead V0 and deposit surfaces from MembershipManager and IMembershipManager after the planned one-shot migration of the last 279 V0 membership NFTs (~186 ETH backing). It also adds operational artifacts (batch migrator, Foundry script, JSON ID lists, README) and mainnet fork tests to run and verify that migration and post-trim behavior.

Contract trim: Deleted entry points include wrapEth / wrapEthForEap, topUpDepositWithEth, migrateFromV0ToV1, tier/points/deposit/fee admin setters, and all V0 reward/migration/deposit internals. rebase no longer calls V0 distribution; claim, unwrapForEEthAndBurn, and requestWithdrawAndBurn no longer auto-claim V0 staking rewards or migrate before burn. V0 storage slots (tokenDeposits, tierDeposits) stay in the layout for a safe UUPS upgrade; canTopUp remains as a view for MembershipNFT. MembershipNFT is unchanged.

Tests: test/Blacklist.t.sol drops blacklist tests for removed deposit APIs. New fork tests upgrade to the trimmed impl and check unwrap, partial/full withdraw+burn, and rebase (0 / ± accrual); a separate fork test batch-migrates from v0_ids_flat.json then checks rebase and real-owner exits.

Reviewed by Cursor Bugbot for commit 2694253. Bugbot is set up for automated code reviews on this repo. Configure here.

@seongyun-ko seongyun-ko requested a review from 0xpanicError May 25, 2026 15:37
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6f2c5ec. Configure here.

Comment thread src/MembershipManager.sol
…eletion-only)

After the one-shot V0 → V1 batch migration on mainnet (279 NFTs / ~186 ETH;
verified by full 10k-id sweep, all `tokenDeposits[id].amounts == 0`), the V0
storage + V0->V1 migration code + entire deposit/mint/top-up surface in
MembershipManager became unreachable from any live NFT. We're closing the
contract to new deposits anyway, so this PR removes that code.

Audit-scope guarantee
=====================
This is a strict deletion-only PR for source code:

  src/MembershipManager.sol             306 deletions,  0 insertions
  src/interfaces/IMembershipManager.sol  14 deletions,  0 insertions
  src/MembershipNFT.sol                   0 deletions,  0 insertions
  src/interfaces/IMembershipNFT.sol       0 deletions,  0 insertions
  src/archive/MembershipManagerV0.sol     0 deletions,  0 insertions

Every line of source code that remains is byte-identical to its
pre-trim form. No reorganization, no new comments, no new logic, no
behavior change in remaining code. The MembershipNFT contract is
untouched entirely.

Size impact (MembershipManager only):
  before: 26,412 bytes (over EIP-170 limit)
  after:  17,184 bytes (margin +7,392)

Functions removed from MembershipManager
========================================
External:
  - wrapEthForEap
  - wrapEth (uint256, uint256, address)
  - wrapEth (uint256, uint256)
  - topUpDepositWithEth
  - migrateFromV0ToV1
  - addNewTier, updateTier, setPoints, updatePointsParams
  - setDepositAmountParams, setTopUpCooltimePeriod, setFeeAmounts,
    setFanBoostThresholdEthAmount

Internal (had no remaining callers after the externals above were removed):
  - _distributeStakingRewardsV0
  - _claimStakingRewards
  - _migrateFromV0ToV1
  - _incrementTokenDeposit, _decrementTokenDeposit,
    _incrementTierDeposit, _decrementTierDeposit
  - _mintMembershipNFT, _deposit, _topUpDeposit, _wrapEth
  - _incrementTokenVaultShareV1
  - _setPoints, _feeAmountSanityCheck

Call-site removals in KEEP functions (single-line deletions only):
  - rebase(): `_distributeStakingRewardsV0(...)`
  - claim(): `_claimStakingRewards`, `_migrateFromV0ToV1`
  - unwrapForEEthAndBurn(): same + the `// Claim ... before burn` comment
  - requestWithdrawAndBurn(): same + the comment

Functions intentionally preserved
=================================
  - canTopUp(...): kept because MembershipNFT.canTopUp() forwards to it.
    Removing it would force a deletion in MembershipNFT, expanding scope.

Verification
============
  - Full 10k-id mainnet sweep: 0 NFTs with `tokenDeposits.amounts > 0`,
    1,761 alive V1 NFTs, 0 unexpected versions.
  - test/MembershipDeprecationUpgradeFork.t.sol upgrades the live proxies
    to the trimmed implementation and asserts real on-chain owners can:
      * unwrapForEEthAndBurn on a migrated-V0 NFT (id 6251) → eETH received
      * unwrapForEEthAndBurn on a born-V1 NFT (id 7500)
      * requestWithdrawAndBurn on a born-V1 NFT (id 8500)
      * partial requestWithdraw on a born-V1 NFT (id 9900)
      * rebase(0 / +1 ETH / -1 ETH) — including the slashing branch
  - test/V0MigrationFork.t.sol documents the batch-migration helper
    (script/operations/v0-migration/) used to clear V0 holdouts.

Storage layout is preserved unchanged — the V0 mappings (tokenDeposits)
and array (tierDeposits) stay in the layout for a safe in-place UUPS
upgrade.

Test churn (also deletion-only):
  test/Blacklist.t.sol: 22 deletions (3 deprecated blacklist tests).
@seongyun-ko seongyun-ko force-pushed the seongyun/refactor/deprecate-membership-deposits-clean branch from 6f2c5ec to 6b13418 Compare May 25, 2026 15:57
@seongyun-ko seongyun-ko changed the title refactor: deprecate deposit & V0 paths in MembershipManager / MembershipNFT refactor: deprecate deposit & V0 paths in MembershipManager (strict deletion-only) May 25, 2026
@0xpanicError 0xpanicError merged commit 23da7fa into pankaj/feat/security-upgrades May 26, 2026
1 check passed
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