DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Extract a disproportionate share of positive funding fees

Summary

A vulnerability allows an attacker to extract a disproportionate share of positive funding fees accumulated in the PerpetualVault without having contributed to their generation, creating an unfair profit at the expense of legitimate users.

Severity: Medium

The vulnerability has a significant financial impact on users, but requires specific timing and conditions to be exploited.

Vulnerability Details

The vulnerability lies in the implementation of the funding fee distribution mechanism in the PerpetualVault.sol contract. When funding fees are claimed, they are simply added to the total vault value, thus increasing the value of all shares proportionally.

An attacker can exploit this behavior by:

  1. Observing off-chain the accumulation of positive funding fees

  2. Depositing funds just before the scheduled claiming of funding fees

  3. Instantly receiving shares that entitle them to a portion of the funding fees

  4. Waiting for the lock period to end to withdraw their funds with profit

Below is a complete Proof of Concept that demonstrates this vulnerability:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
contract FundingFeeExploitTest is Test {
// Address configuration
address public owner = address(0x1);
address public regularUser = address(0x2);
address public attacker = address(0x3);
address public keeper = address(0x4);
address public treasury = address(0x5);
// Parameters
uint256 public constant depositAmount = 10000e6; // 10,000 USDC
uint256 public constant fundingFeesAmount = 1000e6; // 1,000 USDC of funding fees
uint256 public lockTime = 7 days;
// Simulated variables
uint256 public totalShares;
uint256 public initialVaultValue;
uint256 public attackerShares;
uint256 public vaultValue;
function setUp() public {
// Initial setup
initialVaultValue = 50000e6; // 50,000 USDC
vaultValue = initialVaultValue;
totalShares = 50000; // 1 USDC = 1 share initially
// Advance time to simulate an existing position
vm.warp(block.timestamp + 3 days);
}
function testFundingFeeExploit() public {
console.log("Initial vault value:", initialVaultValue / 1e6, "USDC");
console.log("Accumulated funding fees:", fundingFeesAmount / 1e6, "USDC");
// 1. Simulate attacker's deposit
vm.startPrank(attacker);
// Calculate attacker's share (10,000 USDC / 50,000 USDC = 0.2 of shares)
attackerShares = (depositAmount * totalShares) / vaultValue;
totalShares += attackerShares;
vaultValue += depositAmount;
console.log("Attacker shares:", attackerShares);
vm.stopPrank();
// 2. Calculate the proportion of attacker's shares
uint256 attackerShareRatio = (attackerShares * 10000) / totalShares;
console.log("Attacker share ratio:", attackerShareRatio / 100, "%");
// 3. Simulate an action that claims funding fees
// Funding fees are added to the vault value
vaultValue += fundingFeesAmount;
console.log("New vault value after claiming fees:", vaultValue / 1e6, "USDC");
console.log("Value increase:", fundingFeesAmount / 1e6, "USDC");
// 4. Advance time past the lock time
vm.warp(block.timestamp + lockTime + 1);
// 5. Simulate attacker's withdrawal
vm.startPrank(attacker);
// Calculate amount attacker can withdraw
uint256 withdrawAmount = (attackerShares * vaultValue) / totalShares;
totalShares -= attackerShares;
vaultValue -= withdrawAmount;
console.log("Amount withdrawn by attacker:", withdrawAmount / 1e6, "USDC");
vm.stopPrank();
// 6. Calculate attacker's profit
uint256 attackerProfit = withdrawAmount - depositAmount;
console.log("Attacker profit:", attackerProfit / 1e6, "USDC");
// 7. Calculate expected profit based on actual contribution
// Attacker should not receive funding fees as they just deposited
uint256 fairProfit = 0; // Expected profit is 0
// 8. Compare actual profit with fair profit
console.log("Funding fees extracted by attacker:", attackerProfit / 1e6, "USDC");
console.log("Fair share of funding fees:", fairProfit / 1e6, "USDC");
// Assertion: attacker extracted disproportionate value
assertGt(attackerProfit, fairProfit, "Attacker should not profit from funding fees");
// 9. Calculate percentage of total funding fees extracted by attacker
uint256 extractionPercentage = (attackerProfit * 10000) / fundingFeesAmount;
console.log("Percentage of funding fees extracted:", extractionPercentage / 100, "%");
// 10. Extracted percentage should approximately match share ratio
assertApproxEqRel(extractionPercentage, attackerShareRatio, 0.1e18, "Extraction should be proportional to shares");
}
}

The PoC demonstrates that an attacker acquiring 16.67% of the shares can extract exactly 16.6% of the funding fees, despite not having contributed to their generation.

Impact

This vulnerability has a significant financial impact:

  • Legitimate users who have maintained their positions for a long time have a portion of their rightful revenue stolen

  • Attackers can extract risk-free profits, creating a negative-sum game for long-term users

  • At scale, this exploitation could create deposit/withdrawal cycles synchronized with funding fee claims, destabilizing the protocol

Numerical proof: An attacker depositing 10,000 USDC (16.67% of shares) extracts exactly 166 USDC (16.6%) from the 1,000 USDC of funding fees, despite not having contributed to generating them.

Tools Used

  • Foundry for writing and executing the proof of concept test

  • Manual code analysis

Recommendations

To address this vulnerability, I propose:

  1. Time-based distribution: Implement a mechanism where funding fees are distributed proportionally to time spent in the vault and deposit amount.

  2. Vesting periods: Establish a system where new deposits do not immediately receive previously accumulated funding fees. New deposits should only be entitled to funding fees accumulated after their entry.

  3. Regular snapshots: Create regular snapshots of held shares and distribute funding fees based on these historical snapshots rather than current shares.

  4. Reserve account for funding fees: Maintain a separate account for claimed funding fees and distribute them gradually to shareholders according to a formula that takes into account the duration of holding.

Updates

Lead Judging Commences

n0kto Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

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