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: 18/37
Findings: 2
Award: $581.11
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: gellej
Also found by: Czar102, TomFrenchBlockchain, WatchPug, csanuragjain, defsec, hubble, p4st13r4, pedroais
https://github.com/code-423n4/2022-02-badger-citadel/blob/main/contracts/TokenSaleUpgradeable.sol#L271-L281 https://github.com/code-423n4/2022-02-badger-citadel/blob/main/contracts/TokenSaleUpgradeable.sol#L287-L297 https://github.com/code-423n4/2022-02-badger-citadel/blob/main/contracts/TokenSaleUpgradeable.sol#L340-L346
Sale would never end and Owner can transfer all user tokens using the sweep function
Observe the functions setSaleDuration, setSaleStart, setTokenInLimit
Any of these functions can be called in between of sale
So if Sale was meant to be started on timestamp 100 and would end at timestamp 500. Owner can simply call setSaleDuration and set large duration say 100000
Since sale duration increased too much (100100), finalize could never be called which also means user cannot claim any amount
On the other hand owner is free to use sweep function and could transfer all the tokenIn tokens which user paid to the contract while buying
Same scenario could also happen using setSaleStart, setTokenInLimit function. Setting large values for these will make sure that sale never ends
Add a check to only allow changes if sale has not started yet in functions setSaleDuration, setSaleStart, setTokenInLimit
require(saleStart > block.timestamp, "Sale has already started");
#0 - GalloDaSballo
2022-02-14T13:42:08Z
As with any admin privilege
I believe medium severity to be appropriat
#1 - 0xleastwood
2022-03-14T10:42:04Z
Duplicate of #62
#2 - 0xleastwood
2022-03-14T10:53:56Z
Duplicate of #50
65.3297 USDC - $65.33
Total 5 Low issue identified
function setTokenInLimit(uint256 _tokenInLimit) external onlyOwner { require(!finalized, "TokenSale: already finalized"); tokenInLimit = _tokenInLimit; emit TokenInLimitUpdated(_tokenInLimit); }
Observe that there is no check to see that new _tokenInLimit > totalTokenIn.
This is very much required as current totalTokenIn could already be equal to tokenInLimit. Decreasing tokenInLimit puts contract in an invalid state where totalTokenIn > tokenInLimit which should not be possible ideally.
Remediation: Change function like below:
function setTokenInLimit(uint256 _tokenInLimit) external onlyOwner { ... require(totalTokenIn <= _tokenInLimit , "Incorrect token limit"); ... }
function setTokenOutPrice(uint256 _tokenOutPrice) external onlyOwner { require(_tokenOutPrice > 0, "TokenSale: the price must not be zero"); tokenOutPrice = _tokenOutPrice; emit TokenOutPriceUpdated(_tokenOutPrice); }
Remediation: Change the function like below:
function setTokenOutPrice(uint256 _tokenOutPrice) external onlyOwner { ... require(saleStart > block.timestamp, "Price Locked"); ... }
function setGuestlist(address _guestlist) external onlyOwner { guestlist = BadgerGuestListAPI(_guestlist); emit GuestlistUpdated(_guestlist); }
Observe that owner can update the Guest list at any point of sale.
Ideally the users who were whitelisted before sale start should be in guestlist and owner should not be able to add new whitelisted user after sale starts
Remediation: Add a check:
require(saleStart > block.timestamp, "Sale has started, Guest Locked");
function setSaleStart(uint64 _saleStart) external onlyOwner { require( _saleStart >= block.timestamp, "TokenSale: start date may not be in the past" ); require(!finalized, "TokenSale: already finalized"); saleStart = _saleStart; emit SaleStartUpdated(_saleStart); }
Observe that owner can change SaleStart date even if sale has already started.
If sale has already started then this function should not be allowed and setSaleDuration should be used instead as this could cause panic in existing sale buyers
Remediation: Add a check:
require(saleStart > block.timestamp, "Sale has already started");