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

The funding rate is susceptible to a wrong calculation due to a possible precision loss

Summary

The calculation of the funding rate is subject to precision loss making the PerpMarket::getFundingRate function to return an incorrect result.

Vulnarability Details

When calculating the current funding rate, the PerpMarket::getFundingRate function calls the PerpMarket::getCurrentFundingVelocity function:

File: src/perpetuals/leaves/PerpMarket.sol
function getCurrentFundingRate(Data storage self) internal view returns (SD59x18) {
return sd59x18(self.lastFundingRate).add(
getCurrentFundingVelocity(self).mul(getProportionalElapsedSinceLastFunding(self).intoSD59x18())
);
}
function getCurrentFundingVelocity(Data storage self) internal view returns (SD59x18) {
SD59x18 maxFundingVelocity = sd59x18(uint256(self.configuration.maxFundingVelocity).toInt256());
SD59x18 skewScale = sd59x18(uint256(self.configuration.skewScale).toInt256());
SD59x18 skew = sd59x18(self.skew);
if (skewScale.isZero()) {
return SD59x18_ZERO;
}
SD59x18 proportionalSkew = skew.div(skewScale);
SD59x18 proportionalSkewBounded = Math.min(Math.max(unary(SD_UNIT), proportionalSkew), SD_UNIT);
return proportionalSkewBounded.mul(maxFundingVelocity);
}

In the PerpMarket::getCurrentFundingVelocity function, the funding velocity value returned depends on proportionalSkewBounded which is itself subject to conditions as we can see. In the case where proportionalSkewBounded equals proportionalSkew, there will be a loss of precision due to the division of skew by skewScale before multiplying the result obtained by maxFundingVelocity. The result is an inaccurate value for the current funding velocity, and therefore for the current funding rate.

Proof Of Concept

Below's my optimized version of the PerpMarket::getCurrentFundingVelocity function:

File: src/perpetuals/leaves/PerpMarket.sol
function getCurrentFundingVelocity(Data storage self) internal view returns (SD59x18) {
SD59x18 maxFundingVelocity = sd59x18(uint256(self.configuration.maxFundingVelocity).toInt256());
SD59x18 skewScale = sd59x18(uint256(self.configuration.skewScale).toInt256());
SD59x18 skew = sd59x18(self.skew);
if (skewScale.isZero()) {
return SD59x18_ZERO;
}
return proportionalSkewBounded == unary(SD_UNIT) || proportionalSkewBounded == SD_UNIT
? proportionalSkewBounded.mul(maxFundingVelocity)
: skew.mul(maxFundingVelocity).div(skewScale);
}

Modify the GetFundingVelocity_Integration_Test::testFuzz_GivenTheresAMarketCreated test function as follows:

File: /test/integration/perpetuals/perp-market-branch/getFundingVelocity/getFundingVelocity.t.sol
/// *** existing code ***
import { console } from "forge-std/console.sol";
/// *** existing code ***
function testFuzz_GivenTheresAMarketCreated(
uint128 marketId,
uint256 marginValueUsd,
bool isLong,
uint256 timeElapsed
)
external
{
/// *** existing code ***
++ console.log("expectedFundingVelocity", expectedFundingVelocity);
++ console.log("fundingVelocity", fundingVelocity.intoInt256());
// it should return the funding velocity
++ assertLte(fundingVelocity.intoInt256(), expectedFundingVelocity, "invalid funding velocity");
}

Run forge test --mc GetFundingVelocity_Integration_Test -vv.

Impact

The inaccuracy of the funding rate expands to the funding fee per unit and therefore on the unrealized PNL, which affects the liquidation and fulfillment of orders. The incorrect value will affect all future values as it is stored in the PerpMarket::updateFunding function, compounding the error and increasing the impact over time.

Tools Used

Manual review.

Recommendations

Change the PerpMarket::getCurrentFundingVelocity function with an optimized one as shown in the POC.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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