Platform: Code4rena
Start Date: 04/02/2022
Pot Size: $30,000 USDC
Total HM: 3
Participants: 37
Period: 3 days
Judge: leastwood
Id: 84
League: ETH
Rank: 11/37
Findings: 3
Award: $1,218.58
🌟 Selected for report: 0
🚀 Solo Findings: 0
https://github.com/code-423n4/2022-02-badger-citadel/blob/84596551d62f243d13fcb2d486346dde08002f7b/contracts/TokenSaleUpgradeable.sol#L254 https://github.com/code-423n4/2022-02-badger-citadel/blob/84596551d62f243d13fcb2d486346dde08002f7b/contracts/TokenSaleUpgradeable.sol#L192
In order for users to claim their promised tokenOut tokens, the contract owner must call the finalize()
function. If the owner never calls the finalize()
function, no user can call the claim()
function to get their tokens. The owner can call the sweep()
function to take all the tokens that users have sent to the contract and run away without calling finalize()
. Even if the contract owner is not planning to rug users, if the owner is suddenly unresponsive, the same end result occurs where users cannot get their tokens.
I do not see any assumptions about whether the contract owner is trusted, but the contest description says "Specific care should be put in Rug Vectors". If the contract owner is not trusted, this could be a high risk issue. If the owner can be trusted to do the right thing and provide enough tokenOut tokens, then this might be a non-critical issue.
The finalize()
function has the onlyOwner
modifier. This provides the owner with the power to prevent users from claiming their tokenOut tokens, because the claim()
function requires finalized to be true.
Removing the onlyOwner
modifier from the finalize()
function so any user can start the claiming process when the sale is over. If the onlyOwner
modifier remains on this function, the owner can hold the tokenOut tokens hostage.
#0 - GalloDaSballo
2022-02-10T01:54:51Z
Agree with the finding, we should escrow the funds and make them release only if the owner calls finalize and sends the tokenOut
#1 - 0xleastwood
2022-03-14T12:09:29Z
Duplicate of #50
#2 - 0xleastwood
2022-03-16T12:27:46Z
Duplicate of #61
77.6075 USDC - $77.61
The getAmountOut()
function could result in a "tokenOutAmount_" return value of zero because solidity only supports integers and not floating point numbers. This is an unexpected edge case because the contract has require(_tokenInAmount > 0)
, which look like it should result in a non-zero token out amount. The result of this edge case is that the user would lose their input tokens without gaining any tokens or DAO commitments in return. The end result is a user losing tokens.
Line 221 is where the math calculating "tokenOutAmount_" could round down to zero
tokenOutAmount_ = (_tokenInAmount * 10**tokenOut.decimals()) / tokenOutPrice;
For example, if the _tokenInAmount value is 1, tokenOut.decimals() is 8, and tokenOutPrice is 10**10, then the result is:
tokenOutAmount_ = (1 * 10**8) / 10**10 = 10**8 / 10**10 = 10**-2 = 0
Any time that 10tokenOut.decimals() < tokenOutPrice would result in a tokenOutAmount_ value of zero and the user would receive no tokens despite losing their input tokens. If there is a large order of magnitude difference between the decimals of tokenOutPrice and tokenOut.decimals(), say 1010, a user could lose up to 10**10 of their tokens without getting any value from this contract.
One simple solution for this edge case is to add a require statement to the getAmountOut()
function to confirm that user will receive non-zero output tokens.
require(tokenOutAmount_ > 0);
#0 - GalloDaSballo
2022-02-10T02:05:31Z
Disagree with the severity as we're dealing with dust amounts, depositors will "risk" loosing up to
tokenOutPrice - 1
of their tokenIn as due to solidity using integer division, anything below will get rounded down to 0.
That's the maximum loosable value, and it's not even extractable by an end user (only by the owner), which (because this is a token sale contract) has no liquid market in which to utilize the excess value
#1 - 0xleastwood
2022-03-14T11:15:59Z
Duplicate of #50
#2 - 0xleastwood
2022-03-21T08:32:00Z
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 low
to reflect this.
#3 - CloudEllie
2022-03-25T03:25:34Z
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 "tokenOutAmount_ could be zero."