DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: medium
Invalid

Incorrect calculation in funding fee

Summary

Ffunding fee being calculated with the wrong sign, potentially leading to funds flowing in the wrong direction.

Vulnerability Details

function getPendingFundingFeePerUnit(
Data storage self,
SD59x18 fundingRate,
UD60x18 markPriceX18
)
internal
view
returns (SD59x18)
{
SD59x18 avgFundingRate = unary(sd59x18(self.lastFundingRate).add(fundingRate)).div(sd59x18Convert(2));
return avgFundingRate.mul(getProportionalElapsedSinceLastFunding(self).intoSD59x18()).mul(
markPriceX18.intoSD59x18()
);
}
SD59x18 avgFundingRate = unary(sd59x18(self.lastFundingRate).add(fundingRate)).div(sd59x18Convert(2));

The unary function typically returns the negative of its input. So this line is effectively calculating -(lastFundingRate + fundingRate) / 2, which is the negative of the average, not the average itself.

  • This will result in the funding fee being calculated with the wrong sign, potentially leading to funds flowing in the wrong direction (e.g., longs paying shorts when they should be receiving, or vice versa).

  • POC Below - forgive the formatting.

  • // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;

    import "@prb/math/contracts/SD59x18.sol";
    import "@prb/math/contracts/UD60x18.sol";

    contract PerpMarket {
    using SD59x18 for SD59x18.SD59x18;
    using UD60x18 for UD60x18.UD60x18;

    int256 public lastFundingRate;
    uint256 public lastFundingTime;
    function getPendingFundingFeePerUnit(
    SD59x18.SD59x18 fundingRate,
    UD60x18.UD60x18 markPriceX18
    ) public view returns (SD59x18.SD59x18) {
    SD59x18.SD59x18 avgFundingRate = SD59x18.unary(
    SD59x18.add(SD59x18.wrap(lastFundingRate), fundingRate)
    ).div(SD59x18.wrap(2e18));
    return avgFundingRate.mul(
    getProportionalElapsedSinceLastFunding().intoSD59x18()
    ).mul(markPriceX18.intoSD59x18());
    }
    function getProportionalElapsedSinceLastFunding() public view returns (UD60x18.UD60x18) {
    return UD60x18.wrap((block.timestamp - lastFundingTime) * 1e18).div(UD60x18.wrap(3600e18));
    }
    // Function to set lastFundingRate and lastFundingTime for testing
    function setLastFunding(int256 _lastFundingRate, uint256 _lastFundingTime) public {
    lastFundingRate = _lastFundingRate;
    lastFundingTime = _lastFundingTime;
    }
  • // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;

    import "forge-std/Test.sol";
    import "../src/PerpMarket.sol";
    import "@prb/math/contracts/SD59x18.sol";
    import "@prb/math/contracts/UD60x18.sol";

    contract PerpMarketTest is Test {
    using SD59x18 for SD59x18.SD59x18;
    using UD60x18 for UD60x18.UD60x18;

    PerpMarket public perpMarket;
    function setUp() public {
    perpMarket = new PerpMarket();
    }
    function testIncorrectFundingFeeCalculation() public {
    // Set up initial conditions
    perpMarket.setLastFunding(1e18, block.timestamp - 1800); // 0.1% funding rate, 30 minutes ago
    SD59x18.SD59x18 currentFundingRate = SD59x18.wrap(2e18); // 0.2% current funding rate
    UD60x18.UD60x18 markPrice = UD60x18.wrap(1000e18); // $1000 mark price
    // Calculate pending funding fee
    SD59x18.SD59x18 pendingFee = perpMarket.getPendingFundingFeePerUnit(currentFundingRate, markPrice);
    // The correct calculation should be:
    // avgFundingRate = (0.1% + 0.2%) / 2 = 0.15%
    // proportionalElapsed = 1800 / 3600 = 0.5
    // pendingFee = 0.15% * 0.5 * $1000 = $0.75
    // However, due to the unary function, we get a negative value
    assertTrue(pendingFee.lt(SD59x18.wrap(0)), "Pending fee should be negative due to unary function");
    // Calculate the expected correct value
    SD59x18.SD59x18 correctAvgFundingRate = SD59x18.wrap(1e18).add(currentFundingRate).div(SD59x18.wrap(2e18));
    SD59x18.SD59x18 correctPendingFee = correctAvgFundingRate.mul(
    UD60x18.wrap(0.5e18).intoSD59x18()
    ).mul(markPrice.intoSD59x18());
    // Compare the absolute values
    assertTrue(
    pendingFee.abs().eq(correctPendingFee),
    "Absolute value of incorrect fee should equal correct fee"
    );
    // Log the values for manual verification
    emit log_named_decimal_int("Incorrect Pending Fee", pendingFee.unwrap(), 18);
    emit log_named_decimal_int("Correct Pending Fee", correctPendingFee.unwrap(), 18);
    }

    }

  • Run forge test -vv in the terminal

Impact

The getPendingFundingFeePerUnit function returns a negative value when it should be positive. The absolute value of the incorrect fee equals the correct fee, showing that the only error is the sign flip caused by the unary function. This will result in the funding fee being calculated with the wrong sign, potentially leading to funds flowing in the wrong direction

Tools Used

Manual Review

Recommendations

SD59x18 avgFundingRate = sd59x18(self.lastFundingRate).add(fundingRate).div(sd59x18Convert(2));

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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