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

Static `skewScale` will Cause a Market to become Unstable.

Summary

A static skewScale will no longer be correct after a price change in the token.

Vulnerability Details

The protocol will lean towards being unstable as an asset/token price changes. The purpose of the skew is to maintain the balance of the protocol. However, as token/asset's price increases the skew will have a smaller impact and as the token/asset price decreases the skew will have a greater impact.

PerpMarket.sol::getMarkPrice uses the skewScale to determine the skew's influence on the tokens/asset getMarkPrice.
For convenience the code is shown below and here:

function getMarkPrice(
Data storage self,
SD59x18 skewDelta,
UD60x18 indexPriceX18
)
internal
view
returns (UD60x18 markPrice)
{
SD59x18 skewScale = sd59x18(self.configuration.skewScale.toInt256());
SD59x18 skew = sd59x18(self.skew);
SD59x18 priceImpactBeforeDelta = skew.div(skewScale);
SD59x18 newSkew = skew.add(skewDelta);
SD59x18 priceImpactAfterDelta = newSkew.div(skewScale);
SD59x18 cachedIndexPriceX18 = indexPriceX18.intoSD59x18();
UD60x18 priceBeforeDelta =
cachedIndexPriceX18.add(cachedIndexPriceX18.mul(priceImpactBeforeDelta)).intoUD60x18();
UD60x18 priceAfterDelta =
cachedIndexPriceX18.add(cachedIndexPriceX18.mul(priceImpactAfterDelta)).intoUD60x18();
markPrice = priceBeforeDelta.add(priceAfterDelta).div(ud60x18Convert(2));
}

PoC

Paste the following code into the existing test getMarkPrice.t.sol with the added added import for console.log

import { console } from "forge-std/Test.sol";
function test_skewScaleShouldNotBeConstant(uint128 marketId) external {
MarketConfig memory fuzzMarketConfig = getFuzzMarketConfig(marketId);
UD60x18 indexPriceX18 = ud60x18(fuzzMarketConfig.mockUsdPrice);
int256 firstTradeSize = 1000e18;
UD60x18 markPrice =
perpsEngine.getMarkPrice(fuzzMarketConfig.marketId, indexPriceX18.intoUint256(), firstTradeSize);
UD60x18 currentPrice = getPrice(MockPriceFeed(fuzzMarketConfig.priceAdapter));
// we are decreasing the price by 100 times
uint256 newPrice = currentPrice.div(ud60x18(100e18)).intoUint256();
// we are increasing the trade size by 100 times
// Thus the USD value of the secondTradeSize == firstTradeSize
int256 secondTradeSize = 100_000e18;
skip(6000 seconds);
UD60x18 newMarkPrice = perpsEngine.getMarkPrice(fuzzMarketConfig.marketId, newPrice, secondTradeSize);
console.log("initial markPrice is: ", markPrice.intoUint256());
console.log("initial tradeSize USD is: ", markPrice.intoUint256() * uint256(firstTradeSize) / 1e18); // dividing
// by 1e18 because that is the precision of tradeSize
console.log("Final tradeSize is: ", newMarkPrice.intoUint256() * uint256(secondTradeSize) / 1e18);
//The firstTrade's total USD will be smaller than the secondTrade's total USD value due to the skewScale not
// taking into account a token's price change potential
assertLt(
(markPrice.intoUint256() * uint256(firstTradeSize) / 1e18),
(newMarkPrice.intoUint256() * uint256(secondTradeSize) / 1e18)
);
}

Also, it can be noted that the above test can be reversed with similar results.

Impact

Protocol can become unstable.

Tools Used

Foundry testing and manual review

Recommendations

skewScale could be updated periodically or use a different skewing mechanism like the protocol's net open interest in USD

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

Appeal created

izuman Submitter
12 months ago
izuman Submitter
12 months ago
izuman Submitter
12 months ago
inallhonesty Lead Judge
12 months ago
inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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