Project

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

Tier Capacity Bypass in DAO Membership Upgrades

Github

Summary

The upgradeTier function in the MembershipFactory contract contains a critical vulnerability where users can upgrade to higher tiers without capacity validation and state tracking. This allows bypassing tier capacity limits and creates inconsistencies in membership tracking.

Vulnerability Details

The vulnerability exists in the upgradeTier function which is responsible for handling membership upgrades in sponsored DAOs. This function allows members to upgrade their tier status by burning tokens from their current tier and receiving tokens in a higher tier. However, the implementation lacks crucial security checks and state management:

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 function completely lacks capacity validation for the target tier. When a user attempts to upgrade their membership, the function should verify that the higher tier has available capacity before allowing the upgrade. This is a standard check present in the joinDAO function but was omitted here, creating a critical security gap.

Also the state tracking mechanism is fundamentally broken. The function fails to update the minted counter in the DAO's tier configuration after issuing new tokens. This oversight creates a permanent discrepancy between the recorded state and the actual token distribution. The proper implementation would increment the minted counter of the target tier, similar to how joinDAO maintains accurate state:

// In joinDAO - proper state tracking
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;

The implementation demonstrates inconsistent security patterns compared to other membership management functions in the contract. While joinDAO implements robust checks and state updates, these critical security measures are entirely absent in upgradeTier. This inconsistency suggests a possible oversight during the implementation phase rather than a conscious design decision.

Proof of Concept:

function testTierUpgradeBypass() public {
// Setup: Create DAO with two tiers
// Tier 0 (Higher tier): max capacity 5
// Tier 1 (Lower tier): max capacity 7
// Step 1: Fill Tier 0 to capacity
for (uint i = 0; i < 10; i++) {
joinDAO(daoAddress, 0);
}
// Step 2: Join Tier 1
joinDAO(daoAddress, 1);
// Step 3: Upgrade from Tier 1 to Tier 0
// This should fail but succeeds:
upgradeTier(daoAddress, 1);
// Result:
// - Tier 0 now has 8 members despite max of 7
// - State shows only 10 minted
// - Actual tokens > tracked tokens
}

Impact

Members can freely upgrade to higher tiers even when they are at capacity, effectively breaking the scarcity model that gives premium tiers their value and exclusivity. The state tracking failure creates an irreparable mismatch between recorded and actual token distribution, making it impossible to enforce membership limits or accurately calculate governance weights. This compromises not only the DAO's operational integrity but also its economic model, as tier-based benefits and voting power become diluted beyond their intended design.

Tools Used

Manual Review

Recommendations

The vulnerability requires both immediate tactical fixes and strategic improvements to the tier management system. Here's the comprehensive solution approach:

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.");
uint256 targetTierIndex = fromTierIndex - 1;
// Validate target tier capacity
TierConfig storage targetTier = daos[daoMembershipAddress].tiers[targetTierIndex];
require(targetTier.amount > targetTier.minted, "Target tier at capacity");
// Verify token ownership and approve burn
require(
IMembershipERC1155(daoMembershipAddress).balanceOf(_msgSender(), fromTierIndex) >= 2,
"Insufficient tokens for upgrade"
);
// Update state before token operations
targetTier.minted += 1;
// Execute token operations
IMembershipERC1155(daoMembershipAddress).burn(_msgSender(), fromTierIndex, 2);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), targetTierIndex, 1);
// Emit detailed event for tracking
emit TierUpgraded(_msgSender(), daoMembershipAddress, fromTierIndex, targetTierIndex);
}
Updates

Lead Judging Commences

0xbrivan2 Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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