Summary
The upgradeTier function in MembershipFactory allows users to bypass platform fees by joining at lowest tier and upgrading to higher tiers without paying additional fees, causing significant protocol revenue loss.
Vulnerability Details
In MembershipFactory.sol, the upgradeTier mechanism fails to collect platform fees during tier upgrades in sponsored DAOs:
function joinDAO(address daoMembershipAddress, uint256 tierIndex) external {
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
uint256 platformFees = (20 * tierPrice) / 100;
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
}
function upgradeTier(address daoMembershipAddress, uint256 fromTierIndex) external {
require(daos[daoMembershipAddress].daoType == DAOType.SPONSORED, "Upgrade not allowed.");
require(daos[daoMembershipAddress].noOfTiers >= fromTierIndex + 1, "No higher tier available.");
IMembershipERC1155(daoMembershipAddress).burn(_msgSender(), fromTierIndex, 2);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), fromTierIndex - 1, 1);
}
Attack path:
Join at tier 6 (lowest) paying minimal platform fees
Upgrade to higher tiers without additional fee payment
Gain higher tier benefits and profit share weights (64x, 32x, 16x, etc.)
Avoid substantial platform fees meant for higher tiers
Impact
Direct protocol revenue loss from bypassed platform fees
Example loss calculation:
Tier 6 entry: 27.5 USDT (5.5 USDT fee)
Should pay for Tier 5: 275 USDT (55 USDT fee)
Lost fees per upgrade: ~50 USDT
Creates unfair advantage over users paying full fees
More severe in sponsored DAOs due to mandatory 7-tier structure
Tools Used
Recommendations
Add fee collection to upgradeTier:
function upgradeTier(address daoMembershipAddress, uint256 fromTierIndex) external {
require(daos[daoMembershipAddress].daoType == DAOType.SPONSORED, "Upgrade not allowed.");
uint256 toTierIndex = fromTierIndex - 1;
require(daos[daoMembershipAddress].noOfTiers >= fromTierIndex + 1, "No higher tier available.");
uint256 priceDiff = daos[daoMembershipAddress].tiers[toTierIndex].price - daos[daoMembershipAddress].tiers[fromTierIndex].price;
uint256 platformFees = (20 * priceDiff) / 100;
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, priceDiff - platformFees);
IMembershipERC1155(daoMembershipAddress).burn(_msgSender(), fromTierIndex, 2);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), toTierIndex, 1);
}