BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: low
Valid

Fee-on-Transfer Token Insolvency

Root + Impact

Description

The deposit() function mints shares based on the intended transfer amount without verifying how much actually arrived at the vault.

Impact: With fee-on-transfer tokens the vault receives less than expected but mints full shares, leading to insolvency.

// Root cause in the codebase with @> marks to highlight the relevant section
function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
if (block.timestamp >= eventStartDate) {
revert eventStarted();
}
uint256 fee = _getParticipationFee(assets);
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
@ uint256 stakeAsset = assets - fee; // ← Calculate expected amount
stakedAsset[receiver] = stakeAsset;
@ uint256 participantShares = _convertToShares(stakeAsset); // ← Mint based on expected
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset); // ← Transfer
@> _mint(msg.sender, participantShares); // ← Mint shares
// NEVER CHECKS HOW MUCH ACTUALLY ARRIVED!
@> emit deposited(receiver, stakeAsset);
return participantShares;
}

Risk

Likelihood: High

  • With any Fee-on-Transfer Token

Impact: High

  • Vault receives less than expected but mints full shares

  • Winners receives less than expected

Proof of Concept

Setup:

  • Token: Fee-on-transfer with 1% fee per transfer

User1 deposits 100 ETH:

  • Participation fee: 1.5 ETH (goes to fee address)

  • Intended for vault: 98.5 ETH

  • Transfer fee (1%): 0.985 ETH (burned/lost)

  • Actually arrives: 97.515 ETH

  • Shares minted: Based on 98.5 ETH

User2 deposits 100 ETH:

  • Same process, loses 0.985 ETH to transfer fee

Total:

  • Users deposited: 200 ETH

  • Vault received: 195.03 ETH (lost 2x 0.985 ETH to transfer fees)

  • Shares minted: Based on 197 ETH

  • Insolvency: 1.97 ETH shortfall

Compounding losses:

  1. Deposit: Lost ~1% on each deposit (vault got less)

  2. Withdraw: Lost ~1% on each withdrawal (winner got less)

  3. Total: ~2% loss per user's full cycle!

Recommended Mitigation

Measure actual balance change instead of assuming transfer amount

function deposit(uint256 assets, address receiver) public override returns (uint256) {
require(receiver != address(0));
if (block.timestamp >= eventStartDate) {
revert eventStarted();
}
uint256 fee = _getParticipationFee(assets);
if (minimumAmount + fee > assets) {
revert lowFeeAndAmount();
}
uint256 stakeAsset = assets - fee;
// Transfer fee first
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
// FIX: Measure balance BEFORE and AFTER transfer
uint256 balanceBefore = IERC20(asset()).balanceOf(address(this));
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
uint256 balanceAfter = IERC20(asset()).balanceOf(address(this));
// Calculate act amount received
uint256 actualReceived = balanceAfter - balanceBefore;
// Store the actual amount
stakedAsset[receiver] = actualReceived;
// Mint shares based on actual amount received
uint256 participantShares = _convertToShares(actualReceived);
_mint(msg.sender, participantShares);
emit deposited(receiver, actualReceived); // Emit actual amount
return participantShares;
}

Alternative: Explicitly Disallow Fee-on-Transfer Tokens

function deposit(uint256 assets, address receiver) public override returns (uint256) {
// ... existing checks ...
uint256 stakeAsset = assets - fee;
IERC20(asset()).safeTransferFrom(msg.sender, participationFeeAddress, fee);
// Check for fee-on-transfer
uint256 balanceBefore = IERC20(asset()).balanceOf(address(this));
IERC20(asset()).safeTransferFrom(msg.sender, address(this), stakeAsset);
uint256 balanceAfter = IERC20(asset()).balanceOf(address(this));
require(
balanceAfter - balanceBefore == stakeAsset,
"Fee-on-transfer tokens not supported"
);
// Rest of function...
}
Updates

Appeal created

bube Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Fee on transfer tokens

Support

FAQs

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

Give us feedback!