The UpliftOnlyExample contract retains a portion of the swap fees (ownerFee) in its own address (address(this)) without providing a mechanism for withdrawal. This issue leads to the accumulation of tokens in the contract, rendering them inaccessible and potentially blocking significant funds over time.
The vulnerability arises from the onAfterSwap function, where the contract calculates and transfers the ownerFee to address(this):
However, no public or internal method exists to allow the owner (or any authorized role) to withdraw these accumulated tokens. As a result, tokens remain stuck in the contract indefinitely, with no means to access or utilize them.
This issue is critical because it undermines the contract’s ability to manage or monetize these fees, effectively locking funds in the contract.
Tokens sent to address(this) via _vault.sendTo(feeToken, address(this), ownerFee); remain locked because there is no exit mechanism. While end-users are unaffected (since the protocol’s core operations function normally), the owner forfeits any revenue from accrued swap fees, potentially losing significant value over time.
Key points:
Blocked Funds: Fees accumulate in the contract and cannot be withdrawn or repurposed, leading to inefficiency and lost revenue.
Obscured Monetization: Because the owner cannot retrieve these tokens, the protocol’s fee-collection model effectively fails.
Transparency Concerns: The design lacks clarity on the final destination of fees, raising doubts among auditors and users.
Inequitable Fee Split: adminFee is properly routed to quantAMMAdmin, but ownerFee is trapped in address(this) with no withdrawal method.
Reduced Sustainability: Although user liquidity remains intact, the inability to extract owner fees undermines the protocol’s economic model and trust.
Add the following test to pkg/pool-hooks/test/foundry/UpliftExample.t.sol:
This result confirms that the tokens (1e18 USDC) remain in the contract with no way to withdraw them.
No withdrawFees(...): The contract does not define any method that sends these accumulated tokens out from address(this).
Foundry: Used to develop and run the test testSwapHookFeeStuckInContract to confirm the vulnerability.
Manual Code Review: Verified that the contract indeed lacks any withdrawal function for the accumulated fees.
Implement a Withdrawal Method
Add a function that lets an authorized role (e.g., onlyOwner) withdraw any ERC20 tokens that accumulate in the contract, for example:
Send Fees Directly to the Owner
Replace _vault.sendTo(feeToken, address(this), ownerFee); with _vault.sendTo(feeToken, owner(), ownerFee); or another appropriate address. This prevents the contract from holding tokens at all.
Document Any Intentional Behavior
If the project deliberately intends for fees to remain locked (e.g., a burn mechanism or permanent donation), ensure that the contract’s documentation clearly states this behavior to avoid confusion.
Review Security of New Withdrawals
If a withdrawal function is added, confirm that permissions are properly restricted (onlyOwner) and that the function does not introduce reentrancy or other security flaws.
These recommendations will ensure the protocol can properly manage and access collected fees, mitigating the risk of funds being permanently locked.
Likelihood: High, every swap. Impact: High, funds are stuck.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.