DoubPresaleVesting: owner rescue ERC20 (excess DOUB + stranded tokens), indexer visibility (Finding 7 from #131) #137
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#137
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
Split from #131 (Finding 7).
Problem
contracts/src/vesting/DoubPresaleVesting.solexposes claim-only egress fortokencapped by allocations — norescueERC20-style sweep. Outcomes:#128on other contracts cited in review).Audit text noted no unprivileged extraction path — this ticket covers privileged recovery ergonomics without weakening beneficiary entitlement math.
Recommended fix
Add owner-only
rescueERC20(IERC20 t, address to, uint256 amount)(Ownable2Stepowner):t == token: only allow transfer ofbalanceOf(this) − unclaimable_reservewhereunclaimable_reserve ≥ max(0, totalAllocated − totalClaimedSoFarAcrossBeneficiaries)(implement safely: use total claimed aggregator or recomputed conservative bound consistent with Solidity accounting).t != token: permit full stranded sweep toto(with nonzeroto).emite.g.RescueERC20(token, to, amount, reason)for indexer/ops.Align with
FeeRouter/ BuyRouter rescue precedent (#117) andGitLab #123ingress wording in vesting docs.Acceptance criteria
#55).Verification checklist
docs/frontend/presale-vesting.md,docs/operations/final-signoff-and-value-movement.md,invariants-and-business-logic.md— note rescue + operational policy.yieldomega-guardrailsSKILL — optional#92/#55corridor mention.RescueERC20(INV-INDEXER-112row) if emitting.Repo touch list
contracts/src/vesting/DoubPresaleVesting.solDoubPresaleVesting*.t.solcases or extend existing vesting suitementioned in issue #131
mentioned in commit
64c26564c2@brouie please verify GitLab #137 (DoubPresaleVesting
rescueERC20) onmainat64c2656.Implemented
onlyOwnerrescueERC20(IERC20 t, address to, uint256 amount)(nonReentrant). Vestingtoken: only abovereserveDoubForOutstandingClaimsWad()(=totalAllocated - Σ claimedOfover beneficiaries). Other ERC20: up to full balance.amount == type(uint256).max= max allowed for that branch.RescueERC20(address indexed token, address indexed to, uint256 amount, uint8 kind)(kind0 = vesting token excess, 1 = foreign).idx_doub_vesting_rescue_erc20, decoder +reorg.rsrollback, integration_stage2 row.invariants-and-business-logic.md(§137, INV-INDEXER-112, test map),final-signoff-and-value-movement.md,presale-vesting.md,docs/indexer/design.md,skills/README.md,yieldomega-guardrailsSKILL.Automated verification run
forge test --match-contract DoubPresaleVestingTest(31 tests)cargo test roundtrip_doub_vesting_rescue_erc20Checklist for you
cd contracts && forge test --match-contract DoubPresaleVestingTestintegration_stage2with Postgres if applicable;cargo test roundtrip_doub_vesting_rescue_erc20(allocation - claimed)headroom; foreign token sweep works; events visible in explorer.Leaving issue open per process.
@PlasticDigits — verification PASS on
64c2656. walked your checklist:forge test --match-contract DoubPresaleVestingTestgreen: 32/32 PASS. rescue suite covers excess-only sweep, max sentinel, reserve-untouchable revert, post-claim liability shrinkage, foreign-token full balance, non-owner revert, zero-to/zero-amount reverts, plustest_fuzz_rescueERC20_vesting_excess_bounded(256 runs) andtest_excess_doub_above_total_allocated_is_unrecoverable(the yesterday-finding regression baseline).cargo test roundtrip_doub_vesting_rescue_erc20green. decoder roundtrip clean forRescueERC20(token, to, amount, kind).rescueERC20(DoubPresaleVesting.sol L225-251). vesting token (kind=0) computesreserveDoubForOutstandingClaimsWad = totalAllocated - Σ claimedOf(L159-169) and only allowsbal - reserveto leave. foreign token (kind=1) full balance up to bal.amount == type(uint256).maxsends max allowed slice. errors:RescueExceedsExcess,RescueExceedsBalance,RescueUndercollateralized,RescueZeroRecipient,RescueZeroAmount.onlyOwner+nonReentrant. cannot drain below outstanding claims headroom — proven by the dedicated reverse test plus the post-claim liability-shrinks test.20260504200000_doub_vesting_rescue_erc20applies, decoder L278+L1252-1262 handles the event, persist branch inserts intoidx_doub_vesting_rescue_erc20,reorg.rsincludes table in rollback,integration_stage2covers persistence.invariants-and-business-logic.md,final-signoff-and-value-movement.md,presale-vesting.mdall link cleanly. INV-INDEXER-112 row added.ready for close on your side.
mentioned in issue #146
mentioned in issue #149
mentioned in issue #150
mentioned in merge request !374
mentioned in commit
3d20ecf7f0mentioned in commit
f2aab5371dmentioned in issue #207