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));
uint256 newPrice = currentPrice.div(ud60x18(100e18)).intoUint256();
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);
console.log("Final tradeSize is: ", newMarkPrice.intoUint256() * uint256(secondTradeSize) / 1e18);
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