Platform: Code4rena
Start Date: 12/12/2022
Pot Size: $36,500 USDC
Total HM: 8
Participants: 103
Period: 7 days
Judge: berndartmueller
Id: 193
League: ETH
Rank: 48/103
Findings: 1
Award: $59.72
๐ Selected for report: 1
๐ Solo Findings: 0
๐ Selected for report: Zarf
Also found by: 0xDave, Apocalypto, CRYP70, Franfran, Jeiwan, UNCHAIN, adriro, bytehat, chaduke, hansfriese, hihen, kiki_dev, koxuan, minhtrng, rajatbeladiya, unforgiven, wait, yixxas
59.7202 USDC - $59.72
In order to guarantee the contract does not become insolvent, incoming assets should be rounded up, while outgoing assets should be rounded down.
The function buyQuote()
calculates the amount of base tokens required to buy a given amount of fractional tokens. However, this function rounds down the required amount, which is in favor of the buyer (i.e. he/she has to provide less base tokens for the amount of receiving fractional tokens.
Depending on the amount of current token reserves and the amount of fractional tokens the user wishes to buy, it might be possible to receive free fractional tokens.
Assume the following reserve state:
1e7
)1e25
)The user wishes to buy 0,9 fractional tokens (=9e17
). Then, the function buyQuote()
will calculate the amount of base tokens as follows:
(9e17 * 1000 * 1e7) / ((1e25 - 9e17) * 997) = 0,903
As division in Solidity will round down, the amount results in 0
amount of base tokens required (WBTC) to buy 0,9 fractional tokens.
Using the example above, 0,9 fractional tokens is a really small amount (0,1 BTC / 1e7 = +- $0,00017
). Moreover, if the user keeps repeating this attack, the fractional token reserve becomes smaller, which will result in a buyQuote amount of >1, after which the tokens will not be free anymore.
Additionally, as the contract incorporates a fee of 30bps, it will likely not be insolvent. The downside would be the LP holder, which will receive a fee of less than 30bps. Hence, the impact is rated as medium.
Manual Review
For incoming assets, itโs recommended to round up the required amount. We could use solmateโs FixedPointMathLib
library to calculate the quote and round up. This way the required amount will always at least be 1 wei:
function buyQuote(uint256 outputAmount) public view returns (uint256) { return mulDivUp(outputAmount * 1000, baseTokenReserves(), (fractionalTokenReserves() - outputAmount) * 997); }
#0 - c4-judge
2022-12-23T13:49:30Z
berndartmueller marked the issue as primary issue
#1 - c4-sponsor
2023-01-05T13:30:28Z
outdoteth marked the issue as sponsor confirmed
#2 - outdoteth
2023-01-06T17:23:54Z
Fixed in: https://github.com/outdoteth/caviar/pull/4
Uses muldivup from solmate to round up the calculation in buyQuote.
#3 - c4-judge
2023-01-10T09:42:56Z
berndartmueller marked the issue as selected for report