MaltDataLab fetched priceCumulative directly from Uniswap V2 pool to calculate price of Malt token. However, it is noticed that Uniswap V2 pool use Solidity 0.5.16, which does not revert when overflow happen. In addition, it is actually commented in Uniswap code that
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
However, MaltDataLab contracts use Solidity 0.8 and will revert when overflow. It will break the stabilize() function and always revert since stabilize() call to MaltDataLab contract to get state.
Please note that, with Solidity 0.5.16, when result of addition bigger than max(uint256), it will overflow without any errors. For example, max(uint256) + 2 = 1.
So when price0CumulativeLast is overflow, the new value of price0CumulativeLast will smaller than old value. As the result, when MaltDataLab doing a subtraction to calculate current price, it might get revert.
Lines of code
https://github.com/code-423n4/2023-02-malt/blob/700f9b468f9cf8c9c5cffaa1eba1b8dea40503f9/contracts/StabilityPod/StabilizerNode.sol#L161 https://github.com/code-423n4/2023-02-malt/blob/700f9b468f9cf8c9c5cffaa1eba1b8dea40503f9/contracts/DataFeed/MaltDataLab.sol#L326
Vulnerability details
Impact
MaltDataLab fetched
priceCumulative
directly from Uniswap V2 pool to calculate price of Malt token. However, it is noticed that Uniswap V2 pool use Solidity 0.5.16, which does not revert when overflow happen. In addition, it is actually commented in Uniswap code thathttps://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/UniswapV2Pair.sol#L77-L81
However, MaltDataLab contracts use Solidity 0.8 and will revert when overflow. It will break the
stabilize()
function and always revert sincestabilize()
call to MaltDataLab contract to get state.Please note that, with Solidity 0.5.16, when result of addition bigger than
max(uint256)
, it will overflow without any errors. For example,max(uint256) + 2 = 1
.So when
price0CumulativeLast
is overflow, the new value ofprice0CumulativeLast
will smaller than old value. As the result, when MaltDataLab doing a subtraction to calculate current price, it might get revert.Proof of Concept
Function
stabilize()
will call toMaltDataLab.trackPool()
first https://github.com/code-423n4/2023-02-malt/blob/700f9b468f9cf8c9c5cffaa1eba1b8dea40503f9/contracts/StabilityPod/StabilizerNode.sol#L163Function
trackPool()
used a formula that will revert whenpriceCumulative
overflow in Uniswap pool. https://github.com/code-423n4/2023-02-malt/blob/700f9b468f9cf8c9c5cffaa1eba1b8dea40503f9/contracts/DataFeed/MaltDataLab.sol#L323-L329Scenario:
maltPriceCumulativeLast = max(uint256 - 10)
andprice = 10, timeElapsed = 10
. So the newpriceCumulative = max(uint256 - 10) + 10 * 10 = 99 (overflow)
When doing calculation in Malt protocol,
priceCumulative < maltPriceCumulativeLast
, sopriceCumulative - maltPriceCumulativeLast
will revert and failTools Used
Manual Review
Recommended Mitigation Steps
Consider using
unchecked
block to match handle overflow calculation in Uniswap V2.