The DAO tracks the number of tokens minted per tier using a counter in the tier configuration. When users join a DAO, this counter is incremented as shown in the joinDAO
function:
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);
}
However, when users upgrade their tier by burning 2 tokens from a lower tier to mint 1 token in a higher tier, the minted
count in the original tier is not decremented:
This creates a discrepancy between the actual number of tokens and the tracked count. For example:
So the inflated count causes tiers to reach their limit prematurely.
pragma solidity ^0.8.22;
import "forge-std/Test.sol";
import "../contracts/dao/MembershipFactory.sol";
import "../contracts/dao/CurrencyManager.sol";
import "../contracts/dao/tokens/MembershipERC1155.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
contract MembershipFactoryDAOTest is Test {
MembershipFactory factory;
MembershipERC1155 implementation;
CurrencyManager currencyManager;
TestERC20 currency;
address alice = address(0x1);
address bob = address(0x2);
address owpWallet = address(0x3);
function setUp() public {
currency = new TestERC20("Test", "TST");
currencyManager = new CurrencyManager();
implementation = new MembershipERC1155();
factory = new MembershipFactory(
address(currencyManager),
owpWallet,
"ipfs://",
address(implementation)
);
currencyManager.addCurrency(address(currency));
vm.deal(alice, 100 ether);
vm.deal(bob, 100 ether);
currency.mint(alice, 1000e18);
currency.mint(bob, 1000e18);
}
function testTierSupplyMismatch() public {
TierConfig[] memory tiers = new TierConfig[]();
for(uint i = 0; i < 7; i++) {
tiers[i] = TierConfig({
amount: 1000,
price: 100e18,
power: 2**(6-i),
minted: 0
});
}
DAOInputConfig memory daoConfig = DAOInputConfig({
ensname: "test.eth",
daoType: DAOType.SPONSORED,
currency: address(currency),
maxMembers: 19825,
noOfTiers: 7
});
vm.prank(alice);
address daoAddress = factory.createNewDAOMembership(daoConfig, tiers);
MembershipERC1155 dao = MembershipERC1155(daoAddress);
TierConfig[] memory initialTiers = factory.tiers(daoAddress);
console.log("Initial tier 6 minted:", initialTiers[6].minted);
vm.startPrank(alice);
currency.approve(address(factory), 1000e18);
for(uint i = 0; i < 4; i++) {
factory.joinDAO(daoAddress, 6);
}
TierConfig[] memory tiersAfterJoin = factory.tiers(daoAddress);
console.log("Tier 6 minted after joins:", tiersAfterJoin[6].minted);
console.log("Alice tier 6 balance:", dao.balanceOf(alice, 6));
uint256 initialTotalSupply = dao.totalSupply();
console.log("Initial total supply:", initialTotalSupply);
factory.upgradeTier(daoAddress, 6);
TierConfig[] memory tiersAfterUpgrade = factory.tiers(daoAddress);
console.log("Tier 6 minted after first upgrade:", tiersAfterUpgrade[6].minted);
console.log("Tier 5 minted after first upgrade:", tiersAfterUpgrade[5].minted);
console.log("Alice tier 6 balance:", dao.balanceOf(alice, 6));
console.log("Alice tier 5 balance:", dao.balanceOf(alice, 5));
factory.upgradeTier(daoAddress, 6);
TierConfig[] memory finalTiers = factory.tiers(daoAddress);
console.log("Final tier 6 minted:", finalTiers[6].minted);
console.log("Final tier 5 minted:", finalTiers[5].minted);
console.log("Final tier 6 balance:", dao.balanceOf(alice, 6));
console.log("Final tier 5 balance:", dao.balanceOf(alice, 5));
vm.stopPrank();
uint256 totalMembers = 0;
for(uint i = 0; i < 7; i++) {
totalMembers += finalTiers[i].minted;
}
console.log("Total members according to minted:", totalMembers);
uint256 actualMembers = 0;
for(uint i = 0; i < 7; i++) {
actualMembers += dao.balanceOf(alice, i);
}
console.log("Actual members from balances:", actualMembers);
assertEq(totalMembers, actualMembers, "Minted count mismatch with actual tokens");
}
}
contract TestERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}