Consider the situation where a created DAO has 2 tiers, tier 1 and tier 2. User Bob has paid and is a member of tier 2 in the DAO. Another user with the EXTERNAL_CALLER
role calls MembershipFactory::updateDAOMembership
and passes a new TierConfig
that only includes 1 tier, tier 1. This DAO now has a minted count of 0 users, even though Bob had minted a tier 2 membership from the last implementation.
pragma solidity ^0.8.22;
import {Test, console} from "forge-std/Test.sol";
import "../contracts/OWPIdentity.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "../lib/forge-std/src/mocks/MockERC20.sol";
import {MembershipFactory} from "../contracts/dao/MembershipFactory.sol";
import "../contracts/dao/CurrencyManager.sol";
import {MembershipERC1155} from "../contracts/dao/tokens/MembershipERC1155.sol";
import "../contracts/dao/libraries/MembershipDAOStructs.sol";
contract IdTest is Test {
OWPIdentity public identity;
MembershipFactory public factory;
CurrencyManager public currencyManager;
MockERC20 public weth;
MembershipERC1155 public membership;
address public admin;
address public minter;
address public alice;
address public bob;
address public owpWallet;
function setUp() public {
admin = makeAddr("admin");
minter = makeAddr("minter");
alice = makeAddr("alice");
bob = makeAddr("bob");
owpWallet = makeAddr("owpWallet");
identity = new OWPIdentity(admin, minter, "https://example.com/");
vm.startPrank(admin);
weth = new MockERC20();
weth.initialize("WrappedEth", "weth", 18);
vm.stopPrank();
vm.startPrank(admin);
currencyManager = new CurrencyManager();
currencyManager.addCurrency(address(weth));
vm.stopPrank();
vm.startPrank(admin);
membership = new MembershipERC1155();
vm.stopPrank();
vm.startPrank(admin);
factory =
new MembershipFactory(address(currencyManager), owpWallet, "https://example.com/", address(membership));
vm.stopPrank();
}
function test_badMintCount() public {
TierConfig[] memory tiersInput = new TierConfig[]();
tiersInput[0] = TierConfig({amount: 10, price: 10, power: 2, minted: 0});
tiersInput[1] = TierConfig({amount: 20, price: 5, power: 1, minted: 0});
DAOInputConfig memory daoInputConfig = DAOInputConfig("daoPublic1", DAOType.PUBLIC, address(weth), 30, 2);
vm.startPrank(alice);
address aliceDao = factory.createNewDAOMembership(daoInputConfig, tiersInput);
vm.stopPrank();
vm.startPrank(bob);
address daoCheck = factory.userCreatedDAOs(alice, "daoPublic1");
assertEq(daoCheck, aliceDao);
deal(address(weth), bob, 100);
IERC20(weth).approve(address(factory), 100);
factory.joinDAO(daoCheck, 1);
uint256 nftBalance = MembershipERC1155(daoCheck).balanceOf(bob, 1);
assertEq(nftBalance, 1);
vm.stopPrank();
TierConfig[] memory tiersReturned = new TierConfig[]();
tiersReturned = factory.tiers(address(daoCheck));
uint256 totalMinted;
console.log("tiersReturned length BEFORE: ", tiersReturned.length);
for (uint256 i = 0; i < tiersReturned.length; i++) {
totalMinted += tiersReturned[i].minted;
}
assertEq(tiersReturned.length, 2);
assertEq(totalMinted, 1);
TierConfig[] memory tiersInputNew = new TierConfig[]();
tiersInputNew[0] = TierConfig({amount: 5, price: 25, power: 5, minted: 0});
vm.startPrank(admin);
factory.updateDAOMembership("daoPublic1", tiersInputNew);
vm.stopPrank();
TierConfig[] memory tiersReturnedSecond = new TierConfig[]();
tiersReturnedSecond = factory.tiers(address(daoCheck));
uint256 totalMintedSecond;
for (uint256 i = 0; i < tiersReturnedSecond.length; i++) {
totalMintedSecond += tiersReturnedSecond[i].minted;
}
assertEq(tiersReturnedSecond.length, 1);
assertEq(totalMintedSecond, 0);
}
}
If being a member of a removed tier, should still keep you as a member of the DAO, then the minted count that is calculated is incorrect.
If being a member of a removed tier, should remove your membership from the DAO, then that is not fair, as that user paid a price for that membership slot that they are not getting back.
The fix is non-trivial as business choices must be made as to whether tiers of DAOs should be able to be 'removed'. An option could be to only allow additional tiers to be added. An option could be to not allow the updating of DAO TierConfigs
once created.