Closed c4-submissions closed 1 year ago
minhquanym marked the issue as sufficient quality report
This is actually impossible because scaleFactor
can never be below 1e27
.
You can prove this with solc's SMT solver by running:
solc test/TestRoundingError.sol --model-checker-engine bmc --model-checker-show-proved-safe
which outputs:
Info: BMC: Assertion violation check is safe!
--> test/TestRoundingError.sol:20:5:
|
20 | assert(scaledAmount2 == scaledAmount);
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0;
uint constant RAY = 1e27;
uint constant HALF_RAY = 0.5e27;
contract TestRoundingError {
function check_RoundingError(uint scaledAmount, uint scaleFactor) external pure {
require(scaleFactor >= RAY && scaleFactor <= type(uint112).max);
// Inline rayMul and rayDiv functions to help SMT solver parse the code
// normalizedAmount = rayMul(scaledAmount, scaleFactor)
require(scaledAmount <= (type(uint256).max - HALF_RAY) / scaleFactor);
uint normalizedAmount = (scaledAmount * scaleFactor + HALF_RAY) / RAY;
// scaledAmount2 = rayDiv(normalizedAmount, scaleFactor)
uint halfScaleFactor = scaleFactor / 2;
require(normalizedAmount <= (type(uint256).max - halfScaleFactor) / RAY);
uint scaledAmount2 = (normalizedAmount * RAY + halfScaleFactor) / scaleFactor;
assert(scaledAmount2 == scaledAmount);
}
}
d1ll0n (sponsor) disputed
MarioPoneder marked the issue as unsatisfactory: Insufficient proof
Agree with the sponsor.
However, I want to point out the opportunity to submit a runnable PoC during post-judging QA to undisputably prove the issue.
Lines of code
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketToken.sol#L18
Vulnerability details
Impact
Under the hood WildcatMarketToken uses "scaled token amounts", while outside it operates with "normalized token amounts". Thus
balanceOf()
converts internal scaledAmount to normalized. Transfer on the contrary converts normalized amount to scaled.Problem is that both these functions round up while converting.
Issue arises when in function
balanceOf()
value rounds up, such thatscaledAmount < balanceOf() * scaleFactor
. In this case transfer reverts if you try to send full balance. I suppose to look by example: 1) Suppose user's scaled (internal) balance is 5, scaleFactor = 0.5e27 2) He callsbalanceOf()
and gets5 * 0.5 = 3
because function rounds up. 3) He callstransfer(3)
, function converts it to scaled balance3 / 0.5 = 6
. But his internal balance is 5 - therefore revert.This inconsistency introduces serious problem for third party protocols that use WildcatMarketToken. Because to transfer full balance of contract is pretty normal behaviour, and in this case it reverts.
Proof of Concept
balanceOf
usesrayMul()
that rounds up:_transfer()
usesrayDiv()
which rounds up too:Insert this file to
/test
:Tools Used
Manual Review
Recommended Mitigation Steps
Round down in
balanceOf
. Something like:Assessed type
ERC20