Project

One World
NFTDeFi
15,000 USDC
View results
Submission Details
Severity: medium
Invalid

Lack of Fee-On-Transfer Token Support in joinDAO Function Causes Financial Discrepancies and Losses

Summary

The lack of support for fee-on-transfer tokens in the joinDAO function presents a critical vulnerability that could lead to accounting discrepancies and fund losses for DAOs using such tokens as their membership currency. When fee-on-transfer tokens are used, the actual amount received by the platform and DAO will be less than the calculated amounts due to the token's internal fee mechanism. This creates a mismatch between expected and actual received amounts, potentially leading to significant financial losses over time. The severity is particularly high as popular tokens like SafeMoon or similar tokens with transfer taxes could be whitelisted through the currency manager, making this vulnerability immediately exploitable.

The issue lies in the function's assumption that the amount specified in transferFrom is equal to the amount received by the recipient. The contract calculates platform fees and DAO amounts based on the pre-fee tierPrice, but fee-on-transfer tokens deduct their fees from each transfer, resulting in recipients receiving less than the calculated amounts. This architectural oversight means that both the platform (owpWallet) and the DAO receive less than their intended share, breaking the economic model of the membership system. Furthermore, since the minting of NFT membership is tied to these transfers, users effectively receive full membership benefits while paying reduced actual amounts.

https://github.com/Cyfrin/2024-11-one-world/blob/main/contracts/dao/MembershipFactory.sol#L140

function joinDAO(address daoMembershipAddress, uint256 tierIndex) external {
require(daos[daoMembershipAddress].noOfTiers > tierIndex, "Invalid tier.");
require(daos[daoMembershipAddress].tiers[tierIndex].amount > daos[daoMembershipAddress].tiers[tierIndex].minted, "Tier full.");
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
uint256 platformFees = (20 * tierPrice) / 100;
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
// No accounting for potential transfer fees
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}

Recommended Fix

Implement balance checks before and after transfers to ensure correct amounts are received, and adjust the transfer amounts to account for fees:

function joinDAO(address daoMembershipAddress, uint256 tierIndex) external {
require(daos[daoMembershipAddress].noOfTiers > tierIndex, "Invalid tier.");
require(daos[daoMembershipAddress].tiers[tierIndex].amount > daos[daoMembershipAddress].tiers[tierIndex].minted, "Tier full.");
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
require(tierPrice > 0, "Invalid tier price");
uint256 platformFees = (tierPrice * platformFeeBps) / 10000;
uint256 daoAmount = tierPrice - platformFees;
IERC20 token = IERC20(daos[daoMembershipAddress].currency);
// Record initial balances
uint256 initialPlatformBalance = token.balanceOf(owpWallet);
uint256 initialDAOBalance = token.balanceOf(daoMembershipAddress);
// Perform transfers
token.transferFrom(_msgSender(), owpWallet, platformFees);
token.transferFrom(_msgSender(), daoMembershipAddress, daoAmount);
// Verify received amounts
uint256 platformReceived = token.balanceOf(owpWallet) - initialPlatformBalance;
uint256 daoReceived = token.balanceOf(daoMembershipAddress) - initialDAOBalance;
require(platformReceived >= (platformFees * 95) / 100, "Platform fee transfer failed"); // Allow 5% slippage
require(daoReceived >= (daoAmount * 95) / 100, "DAO payment transfer failed"); // Allow 5% slippage
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
emit TokensReceived(platformReceived, daoReceived); // New event for transparency
}
event TokensReceived(uint256 platformReceived, uint256 daoReceived);
Updates

Lead Judging Commences

0xbrivan2 Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
0xbrivan2 Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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