Skip to content

fix: forward AuctionManager bid revenue + guard WRNFT finalize ordering#440

Merged
0xpanicError merged 2 commits into
pankaj/feat/security-upgradesfrom
seongyun/fix/auction-revenue-wrnft-init-guard
May 27, 2026
Merged

fix: forward AuctionManager bid revenue + guard WRNFT finalize ordering#440
0xpanicError merged 2 commits into
pankaj/feat/security-upgradesfrom
seongyun/fix/auction-revenue-wrnft-init-guard

Conversation

@seongyun-ko
Copy link
Copy Markdown
Contributor

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

Follow-ups to #385 from the multi-lens audit. Two H-rated findings; minimal-surface fixes.

H-5 · AuctionManager bid revenue trap

Pre-#385, every consumed bid forwarded bid.amount to membershipManagerContractAddress via processAuctionFeeTransfer. That function was deleted in this branch and updateSelectedBidInformation now only flips bid.isActive=false, stranding the consumed-bid ETH in the contract. The new treasury immutable was added but never read.

Fix: rewire updateSelectedBidInformation to forward bid.amounttreasury immediately on consumption. Drop the unused membershipManagerContractAddress immutable (and its constructor param). Emit BidRevenueForwarded for observability.

Bid-cancellation refund is unchanged — _cancelBid still refunds the bidder. The on-chain invariant address(this).balance ≥ Σ(bid.amount for active bids) is preserved.

Stranded today (not handled by this PR): ~0.097 ETH in accumulatedRevenue below the 0.2 ETH auto-forward threshold. That can be left in the contract per the audit conversation and handled later if desired.

H-3 · WithdrawRequestNFT init-order trap

finalizeRequests had no guard that initializeShareRateFreezeUpgrade() had pushed the sentinel checkpoint. If a finalizeRequests(N) slipped in between the proxy upgrade and the init call, it would push (N, rateNow) as the trace's first entry. Every legacy tokenId ≤ N would then lowerLookup to rateNow instead of the 0 sentinel that signals "fall back to live LP rate", silently mispaying every pre-upgrade holder.

Fix: add NotInitialized + a _finalizationRates.length() == 0 precondition to finalizeRequests. The upgrade-ordering trap becomes impossible — finalizeRequests reverts cleanly until the sentinel is in place.

Test changes

  • TestSetup.sol pushes the sentinel as part of standard setup (matches post-upgrade reality). Done in both the fresh-deploy path and the fork-upgrade path.
  • WithdrawRequestNFTIntrusive gains clearFinalizationRatesForTest() and a matching test-helper _clearFinalizationRatesForTest() so the four tests that explicitly exercise the pre-init transition can reset the trace.
  • All new AuctionManager(...) call-sites updated for the dropped constructor param.

Test results

  • forge test --match-contract WithdrawRequestNFTTest → 79/80 pass (1 fails due to missing MAINNET_RPC_URL, pre-existing).
  • forge test --match-contract AuctionManagerTest → 21/21 pass.
  • Full suite: 973 pass / 104 fail; every failure is a pre-existing missing-env (MAINNET_RPC_URL / OP_RPC_URL).

Deployment runbook addition

The upgrade timelock batch must call WithdrawRequestNFT.initializeShareRateFreezeUpgrade() after the new impl is in place. The new guard makes finalizeRequests revert cleanly until this happens, so the failure mode is loud rather than silent.


Note

High Risk
Changes treasury ETH routing on bid consumption and gates withdrawal finalization on upgrade ordering—both affect protocol funds and pre-upgrade claim semantics if deployment steps are wrong.

Overview
This PR closes two audit follow-ups: consumed auction bid ETH no longer sits in AuctionManager, and WithdrawRequestNFT.finalizeRequests cannot run before the share-rate-freeze sentinel exists.

AuctionManager: When a bid is consumed via updateSelectedBidInformation, the contract now sends bid.amount to the treasury immutable (with BidRevenueForwarded), replacing the removed membership-manager fee path. The unused membershipManagerContractAddress immutable and constructor argument are dropped; all new AuctionManager(...) sites are updated.

WithdrawRequestNFT: finalizeRequests reverts with NotInitialized while _finalizationRates is empty, so a finalize between proxy upgrade and initializeShareRateFreezeUpgrade() cannot seed a real rate as the first checkpoint and break legacy live-rate fallback.

Tests / deploy hygiene: TestSetup and fork setup call initializeShareRateFreezeUpgrade() by default; intrusive test helpers clear the checkpoint trace so pre-init behavior stays testable.

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

…lize ordering

Two follow-up fixes from the multi-lens audit of PR #385.

H-5 (AuctionManager): pre-PR, every consumed bid forwarded `bid.amount` to
`membershipManagerContractAddress` via `processAuctionFeeTransfer`. That fn
was deleted in this branch and `updateSelectedBidInformation` now only flips
`bid.isActive=false`, stranding the consumed-bid ETH in the contract. The
`treasury` immutable was added but never read.

This change rewires `updateSelectedBidInformation` to forward `bid.amount`
to `treasury` immediately on consumption, matching the previously-removed
revenue path. The `membershipManagerContractAddress` immutable and its
constructor param are dropped (unused). Bid-cancellation refund is
unaffected (`_cancelBid` still refunds the bidder).

The currently-stranded ~0.097 ETH (`accumulatedRevenue` below the 0.2 ETH
auto-forward threshold) is left to be handled separately; the per-call
forward stops new revenue from getting trapped going forward.

H-3 (WithdrawRequestNFT): `finalizeRequests` lacked any guard that the
share-rate-freeze sentinel had been pushed. If `initializeShareRateFreezeUpgrade`
is skipped between proxy upgrade and the first oracle report, the first
`finalizeRequests(N)` pushes `(N, rateNow)` as the trace's first entry.
Every legacy tokenId then resolves to `rateNow` via `lowerLookup` instead
of the `0` sentinel that signals "fall back to live LP rate", silently
mispaying every pre-upgrade holder.

This change adds `NotInitialized` + a `length() == 0` precondition to
`finalizeRequests`, making the upgrade-ordering trap impossible.

Tests:
- `TestSetup` pushes the sentinel as part of standard setup (matches
  post-upgrade reality on mainnet).
- `WithdrawRequestNFTIntrusive` gains `clearFinalizationRatesForTest()`
  so the four tests that explicitly exercise the pre-init transition can
  reset the trace to length 0.
- All AuctionManager constructor call-sites updated for the dropped param.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@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 b0732d2. Configure here.

(bool sent, ) = treasury.call{value: amount}("");
if (!sent) revert EtherTransferFailed();
emit BidRevenueForwarded(_bidId, treasury, amount);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

reEnterAuction creates insolvency after bid ETH forwarded

Medium Severity

reEnterAuction re-activates a bid without restoring the ETH that updateSelectedBidInformation already forwarded to treasury. If called after consumption, bid.isActive becomes true again while the contract no longer holds bid.amount, breaking the invariant address(this).balance ≥ Σ(active bid amounts). A subsequent _cancelBid would attempt a refund the contract cannot cover.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b0732d2. Configure here.

…nto seongyun/fix/auction-revenue-wrnft-init-guard
@0xpanicError 0xpanicError merged commit 51cf2bf into pankaj/feat/security-upgrades May 27, 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