Project

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

MembershipFactory:upgradeTier() function call creates unintended Denial of Service due to lack of update for TierConfig[].minted variable.

Summary

When a member of a DAO tries to upgrade tier, two membership token is burned and one is minted for the upgraded tier, But the TierConfig[].minted variable is not tracking this, which in turn creates Denail of service for low tiers if it is once filled, later burned through upgrading.

Root Cause

The vulnerability is located in the MembershipFactory.sol contract from line 155 to 161.

Vulnerability Details

Theres no updating of variable TierCongi[].minted

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);
}

And This is checked in the MembershipFactory:joinDAO() function

require(daos[daoMembershipAddress].tiers[tierIndex].amount > daos[daoMembershipAddress].tiers[tierIndex].minted, "Tier full.");

POC

A Dao have 7 tiers, [0,1,2,3,4,5,6], Tier[6] can have at most two tokens (Tier[6].amount=2). A user have two tokens of Tier[6]. If he upgrade tier then he would have one token of Tier[5] and zero token of Tier[6]. Because two tokens of Tier[6] have been burned and one token is minted of Tier[5].

Now if he tries to get Tier[6] token, then he can not get it because Tier[6].minted variable is not updating.And it is still 2, but expected to be zero.

This can become Denail of service if once a lower tier bcomes filled. Later, if it becomes availabe or even empty through upgradation no one can still buy this membership token.

Add this t.sol file in test folder and run forge test

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "forge-std/Test.sol";
import "../contracts/dao/CurrencyManager.sol";
import "../contracts/dao/MembershipFactory.sol";
import "../contracts/dao/libraries/MembershipDAOStructs.sol";
import "../contracts/dao/tokens/MembershipERC1155.sol";
import { OWPERC20 } from "../contracts/shared/testERC20.sol";
contract MembershipFactoryTest is Test {
CurrencyManager public currencyManager;
MembershipERC1155 public membershipImplementation;
MembershipFactory public membershipFactory;
address public daoMembershipAddress;
OWPERC20 public testERC20;
address public owner = makeAddr("owner");
address public DAOCreator = makeAddr("DAOCreator");
address public member1 = makeAddr("member1");
DAOConfig public daoconfig;
DAOInputConfig public daoConfig;
TierConfig[] public tierConfig;
//Setup function for testing
function setUp() public {
vm.startBroadcast(owner);
// Deploy CurrencyManager
currencyManager = new CurrencyManager();
// Deploy MembershipERC1155
membershipImplementation = new MembershipERC1155();
testERC20 = new OWPERC20("OWP", "OWP");
currencyManager.addCurrency(address(testERC20));
testERC20.mint(member1, 10000000000000000000);
// Deploy MembershipFactory
membershipFactory = new MembershipFactory(
address(currencyManager),
address(owner),
"https://baseuri.com/",
address(membershipImplementation)
);
vm.stopBroadcast();
}
function testUpgadingTier() public {
vm.startBroadcast(owner);
// Set DAO and Tier Input configurations
daoConfig = DAOInputConfig({
ensname: "testdao.eth",
daoType: DAOType.SPONSORED,
currency: address(testERC20),
maxMembers: 100,
noOfTiers: 7
});
tierConfig.push(
TierConfig({ price: 300, amount: 3, minted: 0, power: 12 })
);
tierConfig.push(TierConfig({ price: 200, amount: 3, minted: 0, power: 6 }));
tierConfig.push(TierConfig({ price: 100, amount: 3, minted: 0, power: 3 }));
tierConfig.push(TierConfig({ price: 100, amount: 3, minted: 0, power: 3 }));
tierConfig.push(TierConfig({ price: 100, amount: 3, minted: 0, power: 3 }));
tierConfig.push(TierConfig({ price: 100, amount: 3, minted: 0, power: 3 }));
tierConfig.push(TierConfig({ price: 100, amount: 2, minted: 0, power: 3 }));
//creating new DAO
daoMembershipAddress = membershipFactory.createNewDAOMembership(
daoConfig,
tierConfig
);
vm.stopBroadcast();
//Member1 joining DAO
vm.startBroadcast(member1);
testERC20.approve(address(membershipFactory), 1000000000);
membershipFactory.joinDAO(daoMembershipAddress, 5);
membershipFactory.joinDAO(daoMembershipAddress, 6);
membershipFactory.joinDAO(daoMembershipAddress, 6);
membershipFactory.upgradeTier(daoMembershipAddress, 6);
TierConfig[] memory tier = membershipFactory.tiers(daoMembershipAddress);
//Tier 0, Tier 1 , Tier 2
//Tier[2].minted should be zero which is not the case.
console.log(tier[6].minted);
console.log(tier[5].minted);
vm.expectRevert("Tier full.");
//This is the exampleo of DOS
membershipFactory.joinDAO(daoMembershipAddress, 6);
vm.stopBroadcast();
}
}

Impact

Likelihood is High, and Impact is Medium as lower tiers can become unusable when, they really have zero members or zero tokens. Thus the DAO as a whole become mostly unusable.

Tools Used

Manual Review, foundry

Mitigation

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
);
+++++++ ++daos[daoMembershipAddress].tiers[fromTierIndex].minted -= 2;
IMembershipERC1155(daoMembershipAddress).mint(
_msgSender(),
fromTierIndex - 1,
1
);
++++++++ daos[daoMembershipAddress].tiers[fromTierIndex - 1].minted += 1;
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, fromTierIndex - 1);
}
Updates

Lead Judging Commences

0xbrivan2 Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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

Give us feedback!