New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QA report #66
Comments
Ultimately the rounding issue is a problem with integer division, I don't think the finding as dramatic as it sounds |
Duplicate of #50 |
After further thought, I actually think this is more of an issue of state handling. Users can mis-use this function to receive slightly lower amounts than expected due to the imprecise nature of arithmetic in Solidity. I'll mark this as |
Since this issue was downgraded to a QA level, and the warden did not submit a separate QA report, we've renamed this one to "QA report" for consistency. The original title, for the record, was "Loss of excess funds due to loss of precision." |
Lines of code
https://github.com/code-423n4/2022-02-badger-citadel/blob/84596551d62f243d13fcb2d486346dde08002f7b/contracts/TokenSaleUpgradeable.sol#L216-L224
Vulnerability details
Impact
The contract comments nor the submission text detail what happens when the user submits more
tokenIn
tokens than are required to recieve NtokenOut
tokens, but not enough for N+1 tokens. The contract as written takes any excess contributions for itself.Impact
Due to loss of precision, the number of
tokenOut
tokens receieved is rounded down and the user loses any excess contributions, and also has to deal with any tax implications, due to this undocumented and surprising behavior. If amount of funds lost is directly proportional to the ratio of thetokenOutPrice
totokenOut.decimals()
.Proof of Concept
If
tokenOutPrice
is greater than10**tokenOut.decimals()
, loss of precision due totokenOutAmount_
not being a floating type, will mean that the caller will lose out on fractions oftokenOut
tokens. Thebuy()
function does not return any excesstokenIn
tokens, and does not keep track of fractions of tokens partially paid for, and instead keeps them for itself.buy()
callsgetAmountOut()
:https://github.com/code-423n4/2022-02-badger-citadel/blob/84596551d62f243d13fcb2d486346dde08002f7b/contracts/TokenSaleUpgradeable.sol#L175-L181
And
getAmount()
doesn't keep track of partialtokenOutAmount_
shttps://github.com/code-423n4/2022-02-badger-citadel/blob/84596551d62f243d13fcb2d486346dde08002f7b/contracts/TokenSaleUpgradeable.sol#L216-L224
Based on this conversion comment
/// eg. 1 WBTC (8 decimals) = 40,000 CTDL ==> price = 10^8 / 40,000
https://github.com/code-423n4/2022-02-badger-citadel/blob/84596551d62f243d13fcb2d486346dde08002f7b/contracts/TokenSaleUpgradeable.sol#L32
If
tokenOut
were something like a BTC with only 2 decimals andtokenIn
were USD, the comment would read:/// eg. 40,000 USD (2 decimals) = 1 BTC ==> price = (40,000 * 10^2) / 1
which is 4,000,000. With this
tokenOutPrice
, if someone submitted $79k USDC, they'd getint((79000 * 10**2)/(4000000)
which is only 1 BTC, when they spent almost 2 BTC's worth of USDC.Tools Used
Code inspection
Recommended Mitigation Steps
The code should follow the "principle of least surprise" and
require()
thattokenOutPrice
be less than or equal to10**tokenOut.decimals()
, or keep track of partial amounts, or refund excess contributions.The text was updated successfully, but these errors were encountered: