QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: high
Valid

Owner fee will be locked in `UpliftOnlyExample` contract due to incorrect recipient address in `UpliftOnlyExample::onAfterSwap`

Summary

UpliftOnlyExample::onAfterSwap hook incorrectly transfers funds to UpliftOnlyExample contract instead of the owner address, coupled with a lack of withdrawal function in the UpliftOnlyExample contract, permanently locking the fees in the contract.

Vulnerability Details

In UpliftOnlyExample::onAfterSwap. The vault transfers funds to this contract instead of the owner address. This contract doesn't have any function to transfer out the funds, meaning all owner fees are permanently locked in this contract

function onAfterSwap() {
...
if (ownerFee > 0) {
@> _vault.sendTo(feeToken, address(this), ownerFee);
emit SwapHookFeeCharged(address(this), feeToken, ownerFee);
}
...
}

Proof of Code

Add this function to the UpliftExample.t.sol

function testSwapFeeLockedInHookContract() public {
// 1. Set hook fee percentage
uint64 hookFeePercentage = 1e16; // 1%
vm.prank(owner);
upliftOnlyRouter.setHookSwapFeePercentage(hookFeePercentage);
// 2. Log initial balances
console.log("--- Initial Balances ---");
console.log("Hook Contract USDC Balance:", usdc.balanceOf(address(upliftOnlyRouter)));
console.log("Owner USDC Balance:", usdc.balanceOf(owner));
// 3. Perform swap to generate fees
uint256 swapAmount = 100e18;
vm.prank(bob);
router.swapSingleTokenExactIn(address(pool), dai, usdc, swapAmount, 0, MAX_UINT256, false, bytes(""));
// 4. Log final balances to show fees are stuck in hook
console.log("\n--- After Swap Balances ---");
console.log("Hook Contract USDC Balance:", usdc.balanceOf(address(upliftOnlyRouter)));
console.log("Owner USDC Balance:", usdc.balanceOf(owner));
console.log("\n--- Fees are locked in hook contract ---");
}
[PASS] testSwapFeeLockedInHookContract() (gas: 228556)
Logs:
--- Initial Balances ---
Hook Contract USDC Balance: 0
Owner USDC Balance: 0
--- After Swap Balances ---
Hook Contract USDC Balance: 1000000000000000000
Owner USDC Balance: 0

The test log indicates that the swap fees are sent to the UpliftExample contract. There is no function for transferring or donating the tokens, which leaves the funds locked in the contract.

Impact

  • onAfterSwap hook is called each time a user swaps tokens from the pool.

  • Owner cannot access their entitled fees, resulting in direct financial loss

Tools Used

  • Manual

  • Foundry

Recommendations

Modify the onAfterSwap function to send the fees directly to the owner:

function onAfterSwap() {
...
- _vault.sendTo(feeToken, address(this), ownerFee)
+ _vault.sendTo(feeToken, owner(), ownerFee)
...
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_ownerFee_cannot_be_withdrawn

Likelihood: High, every swap. Impact: High, funds are stuck.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.