When a DAO tier configuration is updated, if it is to remove some tiers, it only preserves the minted
amount of the kept tiers, the other tiers are simply deleted without checking for if there is minted tokens of those tiers.
pragma solidity ^0.8.22;
import {Test} from "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../contracts/dao/MembershipFactory.sol";
import "../contracts/dao/CurrencyManager.sol";
import "../contracts/dao/tokens/MembershipERC1155.sol";
import {DAOType, DAOConfig, DAOInputConfig, TierConfig} from "../contracts/dao/libraries/MembershipDAOStructs.sol";
contract Audit is Test {
address admin = makeAddr("Admin");
address owpWallet = makeAddr("owpWallet");
ERC20 WETH = new MockERC20("Wrapped ETH", "WETH", 18);
ERC20 WBTC = new MockERC20("Wrapped BTC", "WBTC", 8);
ERC20 USDC = new MockERC20("USDC", "USDC", 6);
MembershipFactory membershipFactory;
CurrencyManager currencyManager;
function setUp() public {
vm.startPrank(admin);
currencyManager = new CurrencyManager();
currencyManager.addCurrency(address(WETH));
currencyManager.addCurrency(address(WBTC));
currencyManager.addCurrency(address(USDC));
MembershipERC1155 membershipERC1155Implementation = new MembershipERC1155();
membershipFactory = new MembershipFactory(
address(currencyManager),
owpWallet,
"https://baseuri.com/",
address(membershipERC1155Implementation)
);
vm.stopPrank();
}
function testAudit_tierAccounting() public {
address creator = makeAddr("Creator");
DAOInputConfig memory daoInputConfig = DAOInputConfig({
ensname: "PUBLIC DAO",
daoType: DAOType.PUBLIC,
currency: address(USDC),
maxMembers: 127,
noOfTiers: 7
});
vm.startPrank(creator);
address daoMemebershipAddr = membershipFactory.createNewDAOMembership(
daoInputConfig,
createTierConfigs(
daoInputConfig.noOfTiers,
ERC20(daoInputConfig.currency).decimals()
)
);
vm.stopPrank();
address alice = makeAddr("Alice");
deal(address(USDC), alice, 1e6);
vm.startPrank(alice);
USDC.approve(address(membershipFactory), 16e6);
membershipFactory.joinDAO(daoMemebershipAddr, 6);
vm.stopPrank();
vm.startPrank(admin);
membershipFactory.updateDAOMembership(
"PUBLIC DAO",
createTierConfigs(
daoInputConfig.noOfTiers - 1,
ERC20(daoInputConfig.currency).decimals()
)
);
vm.stopPrank();
vm.startPrank(admin);
TierConfig[] memory tiers = createTierConfigs(
daoInputConfig.noOfTiers,
ERC20(daoInputConfig.currency).decimals()
);
tiers[6].minted = 1;
membershipFactory.updateDAOMembership(
"PUBLIC DAO",
createTierConfigs(
daoInputConfig.noOfTiers,
ERC20(daoInputConfig.currency).decimals()
)
);
vm.stopPrank();
assertEq(membershipFactory.tiers(daoMemebershipAddr)[6].minted, 0);
assertEq(MembershipERC1155(daoMemebershipAddr).balanceOf(alice, 6), 1);
}
function createTierConfigs(uint noOfTiers, uint8 decimals) private returns (TierConfig[] memory tiers) {
tiers = new TierConfig[](noOfTiers);
uint price = 1 * 10 ** decimals;
uint power = 1;
for (int i = int(noOfTiers) - 1; i >= 0; --i) {
uint index = uint(i);
tiers[index] = TierConfig({
amount: 2 ** index,
price: price,
power: power,
minted: 0
});
price *= 2;
power *= 2;
}
}
}
contract MockERC20 is ERC20 {
uint8 _decimals;
constructor(
string memory name_,
string memory symbol_,
uint8 decimals_
) ERC20(name_, symbol_) {
_decimals = decimals_;
}
function decimals() public view override returns (uint8) {
return _decimals;
}
}
When updating DAO tier configuration, if there are minted tokens in deleted tiers, burn the tokens and refund to the users, or the transaction can simply revert.