When users upgrade their tier in a DAO, the minted counts for tiers are not properly updated, leading to inaccurate tracking of the number of tokens minted per tier. This can result in tiers appearing full when they are not, or exceeding the maximum allowed tokens for a tier.
function testUpdateDaoDoesNotUpdateIndexMintedMapping() public {
TierConfig[] memory tierConfig = new TierConfig[]();
tierConfig[0] = TierConfig({price: 6400, amount: 7, minted: 0, power: 64});
tierConfig[1] = TierConfig({price: 3200, amount: 7, minted: 0, power: 32});
tierConfig[2] = TierConfig({price: 1600, amount: 7, minted: 0, power: 16});
tierConfig[3] = TierConfig({price: 800, amount: 7, minted: 0, power: 8});
tierConfig[4] = TierConfig({price: 400, amount: 7, minted: 0, power: 4});
tierConfig[5] = TierConfig({price: 200, amount: 7, minted: 0, power: 2});
tierConfig[6] = TierConfig({price: 100, amount: 7, minted: 0, power: 1});
MockUsdc mockUsdc = new MockUsdc();
DAOInputConfig memory daoConfig = DAOInputConfig({
ensname: "testdao.eth",
daoType: DAOType.SPONSORED,
currency: address(mockUsdc),
maxMembers: 1270,
noOfTiers: 7
});
currencyManager.addCurrency(address(mockUsdc));
address dao = membershipFactory.createNewDAOMembership(daoConfig, tierConfig);
deal(address(mockUsdc), alice, 100e18);
deal(address(mockUsdc), bob, 100e18);
vm.prank(alice);
mockUsdc.approve(address(membershipFactory), type(uint256).max);
vm.prank(bob);
mockUsdc.approve(address(membershipFactory), type(uint256).max);
vm.startPrank(alice);
membershipFactory.joinDAO(dao, 1);
membershipFactory.joinDAO(dao, 1);
membershipFactory.joinDAO(dao, 1);
membershipFactory.joinDAO(dao, 1);
membershipFactory.joinDAO(dao, 1);
membershipFactory.joinDAO(dao, 1);
membershipFactory.joinDAO(dao, 0);
membershipFactory.joinDAO(dao, 0);
membershipFactory.joinDAO(dao, 0);
vm.stopPrank();
vm.startPrank(bob);
membershipFactory.joinDAO(dao, 1);
membershipFactory.joinDAO(dao, 0);
membershipFactory.joinDAO(dao, 0);
membershipFactory.joinDAO(dao, 0);
membershipFactory.joinDAO(dao, 0);
vm.expectRevert("Tier full.");
membershipFactory.joinDAO(dao, 0);
vm.stopPrank();
TierConfig memory tier1 = membershipFactory.tiers(dao)[1];
TierConfig memory tier0 = membershipFactory.tiers(dao)[0];
assertEq(tier1.minted, 7);
assertEq(MembershipERC1155(dao).balanceOf(alice, 1) + MembershipERC1155(dao).balanceOf(bob, 1), tier1.minted);
assertEq(tier0.minted, 7);
assertEq(MembershipERC1155(dao).balanceOf(alice, 0) + MembershipERC1155(dao).balanceOf(bob, 0), tier0.minted);
vm.startPrank(alice);
membershipFactory.upgradeTier(dao, 1);
membershipFactory.upgradeTier(dao, 1);
membershipFactory.upgradeTier(dao, 1);
vm.stopPrank();
assertEq(tier1.minted, 7);
vm.prank(alice);
vm.expectRevert("Tier full.");
membershipFactory.joinDAO(dao, 1);
vm.stopPrank();
assertEq(MembershipERC1155(dao).balanceOf(alice, 1) + MembershipERC1155(dao).balanceOf(bob, 1), 1);
assertEq(tier0.minted, 7);
assertEq(MembershipERC1155(dao).balanceOf(alice, 0) + MembershipERC1155(dao).balanceOf(bob, 0), 10);
}
pragma solidity 0.8.22;
import "../lib/forge-std/src/Test.sol";
import "../contracts/dao/MembershipFactory.sol";
import "../contracts/dao/tokens/MembershipERC1155.sol";
import "../contracts/dao/CurrencyManager.sol";
import "../contracts/OWPIdentity.sol";
import {DAOType, DAOConfig, TierConfig, DAOInputConfig} from "../contracts/dao/libraries/MembershipDAOStructs.sol";
import {MockERC20} from "../lib/forge-std/src/mocks/MockERC20.sol";
contract MembershipDeploymentTest is Test {
MembershipFactory public membershipFactory;
MembershipERC1155 public membershipImplementation;
CurrencyManager public currencyManager;
OWPIdentity public owpIdentity;
address owpWalletAddress;
address owpBackendAdmin;
address rewarder;
string baseURI;
string profileURI;
address alice;
address bob;
address attacker;
function setUp() public {
owpWalletAddress = makeAddr("OWP_PLATFORM_TREASURY_ADDRESS");
owpBackendAdmin = makeAddr("OWP_PROPOSAL_EXECUTOR");
rewarder = makeAddr("Rewarder");
baseURI = "https://example.com/baseURI/";
profileURI = "https://example.com/profileURI/";
alice = makeAddr("Alice");
bob = makeAddr("bob");
attacker = makeAddr("attacker");
membershipImplementation = new MembershipERC1155();
currencyManager = new CurrencyManager();
membershipFactory = new MembershipFactory(
address(currencyManager), owpWalletAddress, baseURI, address(membershipImplementation)
);
owpIdentity = new OWPIdentity(owpWalletAddress, owpBackendAdmin, profileURI);
membershipFactory.grantRole(0x3124d52df17d64a7d650cea4d44356c1b56f1552895b7db0931fbeaabb38822a, owpBackendAdmin);
}
}
contract MockUsdc is MockERC20 {
function mint(address user, uint256 amount) external {
_mint(user, amount);
}
}
This will result in tiers being incorrectly marked as full, preventing users from joining tiers that actually have available slots. Conversely, it can allow over-minting in higher tiers, exceeding the intended supply and disrupting the DAO's tokenomics.
Consider updating minted counts on tier upgrades while also enforcing tier limits.