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 MockERC20 is ERC20 {
constructor() ERC20("Mock Token", "MTK") {
_mint(msg.sender, 1000000 * 10**18);
}
}
contract MembershipFactoryTest is Test, ERC1155Holder {
MembershipFactory public factory;
CurrencyManager public currencyManager;
MembershipERC1155 public membershipImpl;
MockERC20 public mockToken;
address public admin = address(this);
address public owpWallet = address(0x123);
event MembershipDAONFTCreated(string indexed ensName, address nftAddress, DAOConfig daoData);
function setUp() public {
mockToken = new MockERC20();
currencyManager = new CurrencyManager();
currencyManager.addCurrency(address(mockToken));
membershipImpl = new MembershipERC1155();
factory = new MembershipFactory(
address(currencyManager),
owpWallet,
"https://api.oneworldproject.io/metadata/",
address(membershipImpl)
);
}
function test_LostTokensAfterTierUpdate_andProfitIsLost() public {
TierConfig[] memory tiers = new TierConfig[]();
for(uint i = 0; i < 7; i++) {
tiers[i] = TierConfig({
amount: 100,
price: 1 ether / (2 ** i),
power: 64 / (2 ** i),
minted: 0
});
}
DAOInputConfig memory daoConfig = DAOInputConfig({
ensname: "test.dao",
daoType: DAOType.PUBLIC,
currency: address(mockToken),
maxMembers: 700,
noOfTiers: 7
});
address daoAddress = factory.createNewDAOMembership(daoConfig, tiers);
MembershipERC1155 membershipToken = MembershipERC1155(daoAddress);
address user = makeAddr("user");
deal(address(mockToken), user, 100 ether);
vm.startPrank(user);
mockToken.approve(address(factory), 1 ether);
factory.joinDAO(daoAddress, 6);
vm.stopPrank();
console.log("User balance of tier 6 token:", membershipToken.balanceOf(user, 6));
console.log("Initial tier 6 power:", membershipToken.shareOf(user));
TierConfig[] memory newTiers = new TierConfig[]();
for(uint i = 0; i < 3; i++) {
newTiers[i] = TierConfig({
amount: 100,
price: 1 ether / (2 ** i),
power: 64 / (2 ** i),
minted: 0
});
}
bytes32 EXTERNAL_CALLER = keccak256("EXTERNAL_CALLER");
factory.grantRole(EXTERNAL_CALLER, address(this));
factory.updateDAOMembership("test.dao", newTiers);
console.log("User balance of tier 6 token after update:", membershipToken.balanceOf(user, 6));
console.log("User power after tier removed:", membershipToken.shareOf(user));
address profitSender = makeAddr("profitSender");
deal(address(mockToken), profitSender, 10 ether);
vm.startPrank(profitSender);
mockToken.approve(daoAddress, 10 ether);
membershipToken.sendProfit(10 ether);
vm.stopPrank();
console.log("Profit share for non-existent tier: %e", membershipToken.profitOf(user));
}
function test_TierIsLostAfterUpdate() public {
TierConfig[] memory tiers = new TierConfig[]();
for(uint i = 0; i < 7; i++) {
tiers[i] = TierConfig({
amount: 100,
price: 1 ether / (2 ** i),
power: 64 / (2 ** i),
minted: 0
});
}
DAOInputConfig memory daoConfig = DAOInputConfig({
ensname: "test.dao",
daoType: DAOType.PUBLIC,
currency: address(mockToken),
maxMembers: 700,
noOfTiers: 7
});
address daoAddress = factory.createNewDAOMembership(daoConfig, tiers);
address user = makeAddr("user");
deal(address(mockToken), user, 100 ether);
vm.startPrank(user);
mockToken.approve(address(factory), 2 ether);
factory.joinDAO(daoAddress, 0);
factory.joinDAO(daoAddress, 6);
vm.stopPrank();
TierConfig[] memory currentTiers = factory.tiers(daoAddress);
assertEq(currentTiers[0].minted, 1, "Tier 0 should have 1 token minted");
assertEq(currentTiers[6].minted, 1, "Tier 6 should have 1 token minted");
TierConfig[] memory newTiers = new TierConfig[]();
for(uint i = 0; i < 3; i++) {
newTiers[i] = TierConfig({
amount: 100,
price: 1 ether / (2 ** i),
power: 64 / (2 ** i),
minted: 0
});
}
bytes32 EXTERNAL_CALLER = keccak256("EXTERNAL_CALLER");
factory.grantRole(EXTERNAL_CALLER, address(this));
factory.updateDAOMembership("test.dao", newTiers);
TierConfig[] memory updatedTiers = factory.tiers(daoAddress);
console.log("Number of tiers after update:", updatedTiers.length);
console.log("Tier 0 minted tokens:", updatedTiers[0].minted);
vm.startPrank(user);
mockToken.approve(address(factory), 1 ether);
vm.expectRevert("Invalid tier.");
factory.joinDAO(daoAddress, 6);
vm.stopPrank();
}
}
The likelihood is low, but the impact is high. Thus, Medium severity.
Before iterating the tierConfig and deleting current tiers, add the following check: