TempleGold

TempleDAO
Foundry
25,000 USDC
View results
Submission Details
Severity: high
Invalid

Fee-on-Transfer Token Handling Vulnerability in SpiceAuction.sol

File Location: protocol/contracts/templegold/SpiceAuction.sol#L183-L203

Vulnerability Details

Without measuring the balance before and after the transfer, there's no way to ensure that enough tokens were transferred, in the cases where the token has a fee-on-transfer mechanic. If there are latent funds in the contract, subsequent transfers will succeed.

Impact

  • Accounting Errors

  • Exploitation by Malicious Users

  • Financial Loss

  • Trust Platform Disrupted

Tools Used

  • inspection manual

  • solidity

Recommendations

Measuring Balance Before and After Transfer:

  • Before making a transfer, measure the token balance at the recipient's address.

  • After making the transfer, measure the token balance again at the recipient's address.

  • Calculate the number of tokens actually received based on the difference between the balance after and before the transfer.

Validation of Number of Tokens Received:

  • Make sure the number of tokens received matches the expected amount. If they do not match, cancel the transaction to prevent accounting errors.

update Contract Code:

  • Implement balance measurement logic and validate the number of tokens received in the 'bid' function.

Code snippet L183-L203:

function bid(uint256 amount) external virtual override {
/// @dev Cache, gas savings
uint256 epochId = _currentEpochId;
EpochInfo storage info = epochs[epochId];
if(!info.isActive()) { revert CannotDeposit(); }
if (amount == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
SpiceAuctionConfig storage config = auctionConfigs[epochId];
(address bidToken,) = _getBidAndAuctionTokens(config);
address _recipient = config.recipient;
uint256 _bidTokenAmountBefore = IERC20(bidToken).balanceOf(_recipient);
IERC20(bidToken).safeTransferFrom(msg.sender, _recipient, amount);
uint256 _bidTokenAmountAfter = IERC20(bidToken).balanceOf(_recipient);
// fee on transfer tokens
if (amount != _bidTokenAmountAfter - _bidTokenAmountBefore) { revert CommonEventsAndErrors.InvalidParam(); }
depositors[msg.sender][epochId] += amount;
info.totalBidTokenAmount += amount;
emit Deposit(msg.sender, epochId, amount);
}

Fixed code:

function bid(uint256 amount) external virtual override {
/// @dev Cache, gas savings
uint256 epochId = _currentEpochId;
EpochInfo storage info = epochs[epochId];
if (!info.isActive()) { revert CannotDeposit(); }
if (amount == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
SpiceAuctionConfig storage config = auctionConfigs[epochId];
(address bidToken,) = _getBidAndAuctionTokens(config);
address _recipient = config.recipient;
// Measure the balance before the transfer
uint256 _bidTokenAmountBefore = IERC20(bidToken).balanceOf(_recipient);
// Perform the transfer
IERC20(bidToken).safeTransferFrom(msg.sender, _recipient, amount);
// Measure the balance after the transfer
uint256 _bidTokenAmountAfter = IERC20(bidToken).balanceOf(_recipient);
// Calculate the actual amount received
uint256 receivedAmount = _bidTokenAmountAfter - _bidTokenAmountBefore;
// Check if the received amount matches the expected amount
if (receivedAmount != amount) { revert CommonEventsAndErrors.InvalidParam(); }
depositors[msg.sender][epochId] += receivedAmount;
info.totalBidTokenAmount += receivedAmount;
emit Deposit(msg.sender, epochId, receivedAmount);
}

explanation:

Balance measurement before and after transfer:

  • ‘uint256 _bidTokenAmountBefore = IERC20(bidToken).balanceOf(_recipient);’

  • ‘uint256 _bidTokenAmountAfter = IERC20(bidToken).balanceOf(_recipient);’

  • This allows us to know the number of tokens received taking into account transfer fees.

Calculation of the number of tokens received:

  • ‘uint256 receivedAmount = _bidTokenAmountAfter - _bidTokenAmountBefore;’

  • Ensure that we get the number of tokens actually received.

Validate the number of tokens received:

  • ‘if (receivedAmount != amount) { revert CommonEventsAndErrors.InvalidParam(); }’

  • Avoids the possibility of receiving an inappropriate number of tokens.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.