Summary
In MembershipFactory
users could join tiers by purchasing membership via joinDao function that looks like this :
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;
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);
}
After checking and passing tier index and if there are available memberships the function will update daos
mapping with minted amount and transfer funds from sender and mint the nft to the sender with correct tier index and amount.
The users could upgrade their tier via upgradeTier function and within sponsored daos by burning 2 NFT's from their current tier and minting 1 NFT for the next tier and function looks like this:
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);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, fromTierIndex - 1);
}
Vulnerability Details
The issue here is that upgradeTier
does not update daos
mapping when upgrade happen and this will lead that new members could not join when there is available room.
PoC:
Lets say the user want to upgrade from tier 7 to tier 6 and tier 7 reached its capacity (for example lets say capacity is 500).
When upgrading as we mentioned before user need to burn 2 nft, so the capacity should be 498/500 after the upgrade is done. This should leave room for new members to join the current tier. But since it is not updated anywhere the new users would not be able to join (even tho they should) because protocol will think the tier is still full and because this requiremend in joinDAO
would revert :
require(daos[daoMembershipAddress].tiers[tierIndex].amount > daos[daoMembershipAddress].tiers[tierIndex].minted, "Tier full.");
Impact
Due to missing update new members would not be able to join and that will cost protocol of potential profit also.
Tools Used
Manual Review.
Recommendations
Make the following changes to the upgradeTier
function:
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.");
+ daos[daoMembershipAddress].tiers[fromTierIndex].minted -= 2;
+ daos[daoMembershipAddress].tiers[fromTierIndex - 1].minted += 1;
IMembershipERC1155(daoMembershipAddress).burn(_msgSender(), fromTierIndex, 2);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), fromTierIndex - 1, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, fromTierIndex - 1);
}
This will correctly update data in both tiers.