Summary
There are two ways users could become members to the desired tier:
Via purchasing tier via joinDAO for desired tier
By upgrading from lower tier to higher via upgradeTier
The joinDAO
already have protective methods to not allow tier capacity to be exceeded:
require(daos[daoMembershipAddress].noOfTiers > tierIndex, "Invalid tier.");
require(daos[daoMembershipAddress].tiers[tierIndex].amount > daos[daoMembershipAddress].tiers[tierIndex].minted, "Tier full.");
Vulnerability Details
The issue arise in upgradeTier
that 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);
}
The upgradeTier
does not check if the desired tier reached its capacity or not.
This missing check will lead to tier capacity limit to be exceeded.
PoC:
Lets say member of tier 5 wants to upgrade to tier 4. Tier 4 is already reached its capacity (capacity will be 100 for the sake of example) before the upgrade happened.
Now after passing the checks the function burn 2 tokens from the tier 5 and mint 1 token for tier 4.
But because it never checks if the tier 4 reached its capacity it will mint token anyway.
So now the total would be 101/100 which will exceed tier limit.
Impact
Tier limit capacity will be exceeded
Tools Used
Manual Review
Recommendations
Change the design of upgradeTier
to correctly check and update availability of tiers 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.");
+ require(daos[daoMembershipAddress].tiers[fromTierIndex - 1].amount > daos[daoMembershipAddress].tiers[fromTierIndex - 1].minted, "Tier full.");
+ 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);
}