Summary:
The function setAuctionConfig() which is inside SpiceAuction contract is vulnerable to setting a untrusted recipient address.
Vulnerability Details:
If the recipient is not trusted(DAOExecutor made a mistake). Then inside of bid()
function, the tokens could be sent to the not trusted address.
Impact:
The sender approves SpiceAuction contract's address to spend tokens on the sender behalf. The sender's bid amount will be sent to the not trusted address and his funds will be lost.
The only thing that i have changed for the purpose of the test is the recipient address inside _getAuctionConfig()
:
function _getAuctionConfig() internal view returns (ISpiceAuction.SpiceAuctionConfig memory config) {
config.duration = 7 days;
config.waitPeriod = 2 weeks;
config.minimumDistributedAuctionToken = 1 ether;
config.starter = alice;
config.startCooldown = 1 hours;
config.isTempleGoldAuctionToken = true;
ISpiceAuction.ActivationMode mode = ISpiceAuction.ActivationMode.AUCTION_TOKEN_BALANCE;
config.activationMode = mode;
config.recipient = address(0x5);
}
function _setAuctionConfig() private returns (ISpiceAuction.SpiceAuctionConfig memory config) {
config = _getAuctionConfig();
vm.startPrank(daoExecutor);
spice.setAuctionConfig(config);
vm.stopPrank();
}
function _startAuction(bool _setConfig, bool _sendAuctionTokens)
internal
returns (ISpiceAuction.SpiceAuctionConfig memory config)
{
if (_setConfig) {
config = _setAuctionConfig();
} else {
uint256 epochId = spice.currentEpoch();
config = spice.getAuctionConfig(epochId + 1);
}
if (_sendAuctionTokens) {
address auctionToken = config.isTempleGoldAuctionToken ? address(templeGold) : spice.spiceToken();
dealAdditional(IERC20(auctionToken), address(spice), 100 ether);
}
if (config.starter != address(0)) vm.startPrank(config.starter);
vm.warp(block.timestamp + config.waitPeriod);
spice.startAuction();
}
function test_bidSpiceAuctionWithUntrustedRecipient() public {
deal(daiToken, alice, 100 ether);
uint256 aliceBidAmount = 20 ether;
ISpiceAuction.SpiceAuctionConfig memory _config = _startAuction(true, true);
uint256 epoch = spice.currentEpoch();
IAuctionBase.EpochInfo memory epochInfo = spice.getEpochInfo(epoch);
vm.warp(epochInfo.startTime);
IERC20(daiToken).approve(address(spice), type(uint256).max);
vm.expectEmit(address(spice));
emit Deposit(alice, epoch, aliceBidAmount);
spice.bid(aliceBidAmount);
assertEq(IERC20(daiToken).balanceOf(alice), 100 ether - aliceBidAmount);
assertEq(IERC20(daiToken).balanceOf(_config.recipient), aliceBidAmount);
assertEq(IERC20(daiToken).balanceOf(address(0x5)), aliceBidAmount);
epochInfo = spice.getEpochInfo(epoch);
assertEq(epochInfo.totalBidTokenAmount, aliceBidAmount);
assertEq(epochInfo.totalAuctionTokenAmount, 100 ether);
assertEq(spice.depositors(alice, epoch), aliceBidAmount);
assertEq(spice.getClaimableForEpoch(alice, epoch), 100 ether);
}

Tools Used:
Manual Review
Recommendations:
Consider adding a whitelist mapping so that it takes 1 extra step to set the recipient. This way it will lower the chance of making a mistake.