The following Exchange.getMarkPrice function uses pool.baseAssetPrice()'s returned baseAssetPrice, which is spotPrice returned by perpMarket.assetPrice(), to calculate and return the markPrice. When such spotPrice is 0, this function would return a 0 markPrice.
function baseAssetPrice() public view override returns (uint256 spotPrice, bool isInvalid) {
(spotPrice, isInvalid) = perpMarket.assetPrice();
}
As shown by the code below, calling the KangarooVault.transferPerpMargin and KangarooVault._openPosition functions would revert if baseAssetPrice returned by LIQUIDITY_POOL.baseAssetPrice() is 0 no matter what the returned isInvalid is. This means that the price returned by perpMarket.assetPrice() should not be trusted and used whenever such price is 0.
However, some functions that call the Exchange.getMarkPrice function do not additionally check if the Exchange.getMarkPrice function's returned markPrice is 0, which can lead to unexpected consequences. For example, the following KangarooVault.removeCollateral function executes (uint256 markPrice,) = LIQUIDITY_POOL.getMarkPrice(). When markPrice is 0, which is caused by a 0 spotPrice returned by perpMarket.assetPrice(), such price should be considered as invalid and should not be used; yet, in this case, such 0 markPrice can cause minColl to also be 0, which then makes require(positionData.totalCollateral >= minColl + collateralToRemove) much more likely to be passed. In this situation, calling the KangarooVault.removeCollateral function can remove the specified collateralToRemove collateral from the Power Perp position but this actually should not be allowed because such 0 spotPrice and 0 markPrice should be considered as invalid and should not be used.
function getMarkPrice() public view override returns (uint256, bool) {
return exchange.getMarkPrice();
}
Proof of Concept
The following steps can occur for the described scenario.
The KangarooVault.removeCollateral function is called with collateralToRemove being 100e18.
markPrice returned by LIQUIDITY_POOL.getMarkPrice() is 0 because a 0 spotPrice is returned by perpMarket.assetPrice().
Due to the 0 markPrice, minColl is 0, and positionData.totalCollateral >= minColl + collateralToRemove can be true even if positionData.totalCollateral is also 100e18 at this moment.
Calling the KangarooVault.removeCollateral function does not revert, and 100e18 collateral is remove from the Power Perp position.
However, a 0 spotPrice returned by perpMarket.assetPrice() and a 0 markPrice returned by LIQUIDITY_POOL.getMarkPrice() should be considered as invalid and should not be used. In this case, removing 100e18 collateral from the Power Perp position should not be allowed or succeed but it does.
Tools Used
VSCode
Recommended Mitigation Steps
Functions, such as the KangarooVault.removeCollateral function, that call the Exchange.getMarkPrice function can be updated to additionally check if the Exchange.getMarkPrice function's returned markPrice is 0. If it is 0, calling these functions should revert.
Lines of code
https://github.com/code-423n4/2023-03-polynomial/blob/main/src/Exchange.sol#L186-L202 https://github.com/code-423n4/2023-03-polynomial/blob/main/src/LiquidityPool.sol#L399-L401 https://github.com/code-423n4/2023-03-polynomial/blob/main/src/KangarooVault.sol#L401-L420 https://github.com/code-423n4/2023-03-polynomial/blob/main/src/KangarooVault.sol#L556-L611 https://github.com/code-423n4/2023-03-polynomial/blob/main/src/KangarooVault.sol#L436-L447 https://github.com/code-423n4/2023-03-polynomial/blob/main/src/LiquidityPool.sol#L404-L406
Vulnerability details
Impact
The following
Exchange.getMarkPrice
function usespool.baseAssetPrice()
's returnedbaseAssetPrice
, which isspotPrice
returned byperpMarket.assetPrice()
, to calculate and return themarkPrice
. When suchspotPrice
is 0, this function would return a 0markPrice
.https://github.com/code-423n4/2023-03-polynomial/blob/main/src/Exchange.sol#L186-L202
https://github.com/code-423n4/2023-03-polynomial/blob/main/src/LiquidityPool.sol#L399-L401
As shown by the code below, calling the
KangarooVault.transferPerpMargin
andKangarooVault._openPosition
functions would revert ifbaseAssetPrice
returned byLIQUIDITY_POOL.baseAssetPrice()
is 0 no matter what the returnedisInvalid
is. This means that the price returned byperpMarket.assetPrice()
should not be trusted and used whenever such price is 0.https://github.com/code-423n4/2023-03-polynomial/blob/main/src/KangarooVault.sol#L401-L420
https://github.com/code-423n4/2023-03-polynomial/blob/main/src/KangarooVault.sol#L556-L611
However, some functions that call the
Exchange.getMarkPrice
function do not additionally check if theExchange.getMarkPrice
function's returnedmarkPrice
is 0, which can lead to unexpected consequences. For example, the followingKangarooVault.removeCollateral
function executes(uint256 markPrice,) = LIQUIDITY_POOL.getMarkPrice()
. WhenmarkPrice
is 0, which is caused by a 0spotPrice
returned byperpMarket.assetPrice()
, such price should be considered as invalid and should not be used; yet, in this case, such 0markPrice
can causeminColl
to also be 0, which then makesrequire(positionData.totalCollateral >= minColl + collateralToRemove)
much more likely to be passed. In this situation, calling theKangarooVault.removeCollateral
function can remove the specifiedcollateralToRemove
collateral from the Power Perp position but this actually should not be allowed because such 0spotPrice
and 0markPrice
should be considered as invalid and should not be used.https://github.com/code-423n4/2023-03-polynomial/blob/main/src/KangarooVault.sol#L436-L447
https://github.com/code-423n4/2023-03-polynomial/blob/main/src/LiquidityPool.sol#L404-L406
Proof of Concept
The following steps can occur for the described scenario.
KangarooVault.removeCollateral
function is called withcollateralToRemove
being 100e18.markPrice
returned byLIQUIDITY_POOL.getMarkPrice()
is 0 because a 0spotPrice
is returned byperpMarket.assetPrice()
.markPrice
,minColl
is 0, andpositionData.totalCollateral >= minColl + collateralToRemove
can be true even ifpositionData.totalCollateral
is also 100e18 at this moment.KangarooVault.removeCollateral
function does not revert, and 100e18 collateral is remove from the Power Perp position.spotPrice
returned byperpMarket.assetPrice()
and a 0markPrice
returned byLIQUIDITY_POOL.getMarkPrice()
should be considered as invalid and should not be used. In this case, removing 100e18 collateral from the Power Perp position should not be allowed or succeed but it does.Tools Used
VSCode
Recommended Mitigation Steps
Functions, such as the
KangarooVault.removeCollateral
function, that call theExchange.getMarkPrice
function can be updated to additionally check if theExchange.getMarkPrice
function's returnedmarkPrice
is 0. If it is 0, calling these functions should revert.