Summary
The minimum number of distributed tokens in an auction is checked to be different from zero while setting an auction config independently of the chosen activation mode, incorrectly enforcing it for non-token balance activation modes.
Vulnerability Details
While setting the configuration for an auction, the SpiceAuction contract checks that the minimumDistributedAuctionToken
variable must be greater than zero.
93: if (_config.duration < MINIMUM_AUCTION_PERIOD
94: || _config.duration > MAXIMUM_AUCTION_DURATION
95: || _config.waitPeriod > MAXIMUM_AUCTION_WAIT_PERIOD) { revert CommonEventsAndErrors.InvalidParam(); }
96:
97: if (_config.waitPeriod == 0
98: || _config.minimumDistributedAuctionToken == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
99: if (_config.recipient == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
Spice auctions have two different activation modes, AUCTION_TOKEN_BALANCE
and USER_FIRST_BID
.
36: enum ActivationMode {
37:
38: AUCTION_TOKEN_BALANCE,
39:
40: USER_FIRST_BID
41: }
In AUCTION_TOKEN_BALANCE
mode, starting an auction depends on the amount of auction tokens reaching the desired minimum.
161: if (config.activationMode == ActivationMode.AUCTION_TOKEN_BALANCE) {
162: if (config.minimumDistributedAuctionToken == 0) { revert MissingAuctionTokenConfig(); }
163: }
164: if (epochAuctionTokenAmount < config.minimumDistributedAuctionToken) { revert NotEnoughAuctionTokens(); }
This is not a requirement for other activation modes. However, as detailed above, the check is always enforced in setAuctionConfig()
, incorrectly disallowing setting a zero minimumDistributedAuctionToken
value for modes different than AUCTION_TOKEN_BALANCE
.
Impact
When setting the activation mode to USER_FIRST_BID
the configurator would need to be forced to set a positive amount for minimumDistributedAuctionToken
.
Tools Used
None.
Recommendations
First, in startAuction()
, enforce the check on minimumDistributedAuctionToken
only if activationMode == AUCTION_TOKEN_BALANCE
.
if (_config.duration < MINIMUM_AUCTION_PERIOD
|| _config.duration > MAXIMUM_AUCTION_DURATION
|| _config.waitPeriod > MAXIMUM_AUCTION_WAIT_PERIOD) { revert CommonEventsAndErrors.InvalidParam(); }
/// @dev startCooldown can be zero
- if (_config.waitPeriod == 0
- || _config.minimumDistributedAuctionToken == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
+ if (_config.waitPeriod == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
+ if (_config.activationMode == ActivationMode.AUCTION_TOKEN_BALANCE && _config.minimumDistributedAuctionToken == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
if (_config.recipient == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
Then, in startAuction()
simply remove the check since this is already covered while setting the config.
(,address auctionToken) = _getBidAndAuctionTokens(config);
uint256 totalAuctionTokenAllocation = _totalAuctionTokenAllocation[auctionToken];
uint256 balance = IERC20(auctionToken).balanceOf(address(this));
uint256 epochAuctionTokenAmount = balance - (totalAuctionTokenAllocation - _claimedAuctionTokens[auctionToken]);
- if (config.activationMode == ActivationMode.AUCTION_TOKEN_BALANCE) {
- if (config.minimumDistributedAuctionToken == 0) { revert MissingAuctionTokenConfig(); }
- }
if (epochAuctionTokenAmount < config.minimumDistributedAuctionToken) { revert NotEnoughAuctionTokens(); }