Project

One World
NFTDeFi
15,000 USDC
View results
Submission Details
Severity: low
Valid

Vulnerability in `MembershipFactory::updateDAOMembership` Allows Arbitrary Minted Values for New Tiers

Summary

The MembershipFactory::updateDAOMembership does not validate the minted parameter in the tierConfigs struct for new tiers. This allows arbitrary values to be set for the minted parameter, leading to discrepancies between the recorded minted value and the actual number of tokens minted. This can undermine the integrity of the DAO's membership system, potentially causing issues with governance, distribution of rewards, and overall trust in the protocol.

Vulnerability Details

MembershipFactory.sol#L100-L134

function updateDAOMembership(string calldata ensName, TierConfig[] memory tierConfigs)
external
onlyRole(EXTERNAL_CALLER)
returns (address)
{
address daoAddress = getENSAddress[ensName];
require(tierConfigs.length <= TIER_MAX, "Invalid tier count.");
require(tierConfigs.length > 0, "Invalid tier count.");
require(daoAddress != address(0), "DAO does not exist.");
DAOConfig storage dao = daos[daoAddress];
if (dao.daoType == DAOType.SPONSORED) {
require(tierConfigs.length == TIER_MAX, "Invalid tier count.");
}
uint256 maxMembers = 0;
// Preserve minted values and adjust the length of dao.tiers
@> for (uint256 i = 0; i < tierConfigs.length; i++) {
@> if (i < dao.tiers.length) {
@> tierConfigs[i].minted = dao.tiers[i].minted;
}
}
// Reset and update the tiers array
delete dao.tiers;
for (uint256 i = 0; i < tierConfigs.length; i++) {
dao.tiers.push(tierConfigs[i]);
maxMembers += tierConfigs[i].amount;
}
// updating the ceiling limit acc to new data
if (maxMembers > dao.maxMembers) {
dao.maxMembers = maxMembers;
}
dao.noOfTiers = tierConfigs.length;
return daoAddress;
}

When using updateDAOMembership to update the tier configuration, there are potential flaw where tierConfigs minted amount can be set with any arbitrary number if the config is in new tierConfigs.
The issue is within the marked line, whereas the tierConfigs.length index is checked againts dao.tiers.length or the current state variables.
But the function fails to check whether the new tier config on tierConfigs minted amount is equal to zero or not.

This would lead to the minted number is not the same as the actual number, causing discrepancy.

example scenario:

  1. alice create DAO with 3 tier

  2. owner updates the DAO to 4 tier

  3. using minted value for tier 4 to 50 instead of 0

add this code to test folder:

ERC20Mock.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC20Mock is ERC20 {
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
function burn(address from, uint256 amount) public {
_burn(from, amount);
}
}

Base.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import {MembershipFactory} from "../contracts/dao/MembershipFactory.sol";
import {CurrencyManager} from "../contracts/dao/CurrencyManager.sol";
import {DAOType, DAOConfig, DAOInputConfig, TierConfig} from "../contracts/dao/libraries/MembershipDAOStructs.sol";
import {MembershipERC1155} from "../contracts/dao/tokens/MembershipERC1155.sol";
import {Test} from "lib/forge-std/src/Test.sol";
import {ERC20Mock} from "./ERC20Mock.sol";
import {console2} from "lib/forge-std/src/console2.sol";
contract Base is Test {
string baseURI = "0x";
address owpWallet = makeAddr("owpWallet");
address owner = makeAddr("owner");
address alice = makeAddr("alice");
address bob = makeAddr("bob");
address charlie = makeAddr("charlie");
MembershipERC1155 membershiImplementation;
MembershipFactory membershipFactory;
CurrencyManager currencyManager;
ERC20Mock currency;
event UserJoinedDAO(address indexed user, address indexed membershipNftAddress, uint256 tierIndex);
function setUp() public {
vm.startPrank(owner);
membershiImplementation = new MembershipERC1155();
currencyManager = new CurrencyManager();
currency = new ERC20Mock("Currency", "CUR", 100e18);
membershipFactory =
new MembershipFactory(address(currencyManager), owpWallet, baseURI, address(membershiImplementation));
currencyManager.addCurrency(address(currency));
currency.mint(alice, 100e18);
currency.mint(bob, 100e18);
currency.mint(charlie, 100e18);
vm.stopPrank();
}
function test_POC_updateDAOMembership_noCheckOnTierConfigParams() public {
vm.startPrank(alice);
DAOInputConfig memory daoConfig = DAOInputConfig("DAO", DAOType.PUBLIC, address(currency), 100, 3);
TierConfig[] memory tiersConfig = new TierConfig[]();
// amount 10, price 100, power 30, minted 0
tiersConfig[0] = TierConfig(10, 100, 30, 0);
// amount 10, price 100, power 20, minted 0
tiersConfig[1] = TierConfig(10, 100, 20, 0);
// amount 10, price 100, power 10, minted 0
tiersConfig[2] = TierConfig(10, 100, 10, 0);
address daoAddress = membershipFactory.createNewDAOMembership(daoConfig, tiersConfig);
vm.stopPrank();
vm.startPrank(owner);
// update tier 4 to have params minted 50
TierConfig[] memory newTiersConfig = new TierConfig[]();
newTiersConfig[0] = TierConfig(10, 100, 30, 0);
newTiersConfig[1] = TierConfig(10, 100, 20, 0);
newTiersConfig[2] = TierConfig(10, 100, 10, 0);
newTiersConfig[3] = TierConfig(10, 100, 5, 50);
membershipFactory.updateDAOMembership("DAO", newTiersConfig);
vm.stopPrank();
// check if the update is successful
TierConfig[] memory tiers = membershipFactory.tiers(daoAddress);
assertEq(tiers[3].minted, 50);
}
}

run the command forge test --mt test_POC_updateDAOMembership_noCheckOnTierConfigParams and the test would pass indicating the minted amount for tier 4 equal to 50.

Impact

This would cause discrepancies between the recorded minted value and the actual number of tokens minted. Such discrepancies can undermine the integrity of the DAO's membership system, potentially leading to issues with governance, distribution of rewards, and overall trust in the protocol.

Tools Used

foundry

Recommendations

we can enforce the minted amount to zero for new tiers:

diff --git a/contracts/dao/MembershipFactory.sol b/contracts/dao/MembershipFactory.sol
index 02205e3..e89ade4 100644
--- a/contracts/dao/MembershipFactory.sol
+++ b/contracts/dao/MembershipFactory.sol
function updateDAOMembership(string calldata ensName, TierConfig[] memory tierConfigs)
external onlyRole(EXTERNAL_CALLER) returns (address) {
address daoAddress = getENSAddress[ensName];
require(tierConfigs.length <= TIER_MAX, "Invalid tier count.");
require(tierConfigs.length > 0, "Invalid tier count.");
require(daoAddress != address(0), "DAO does not exist.");
DAOConfig storage dao = daos[daoAddress];
if(dao.daoType == DAOType.SPONSORED){
require(tierConfigs.length == TIER_MAX, "Invalid tier count.");
}
@@ -113,19 +139,23 @@ contract MembershipFactory is AccessControl, NativeMetaTransaction {
// Preserve minted values and adjust the length of dao.tiers
for (uint256 i = 0; i < tierConfigs.length; i++) {
if (i < dao.tiers.length) {
tierConfigs[i].minted = dao.tiers[i].minted;
+ } else {
+ tierConfigs[i].minted = 0;
}
}
Updates

Lead Judging Commences

0xbrivan2 Lead Judge
7 months ago
0xbrivan2 Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

0xbrivan2 Lead Judge
6 months ago
0xbrivan2 Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

minted value is not asserted to be zero when adding new tiers

Support

FAQs

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