Summary
The MembershipFactory
contract does not check if a user already has a membership in a specific tier before allowing them to join the DAO again.
Vulnerability Details
The current implementation of the joinDAO
function in the MembershipFactory
contract does not include any checks to verify if the user already has a membership in the specified tier. This allows users to join the DAO multiple times in the same tier, which goes against the intended membership model.
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);
}
https://github.com/Cyfrin/2024-11-one-world/blob/1e872c7ab393c380010a507398d4b4caca1ae32b/contracts/dao/MembershipFactory.sol#L137C5-L150C6
Impact
The DAO's membership model is designed to have one membership per user per tier. Allowing users to join multiple times in the same tier breaks this assumption and can lead to one member posing as multiple members making a proposal.
Tools Used
Manual review
Recommendations
The joinDAO
function in the MembershipFactory
contract should be modified to include a check for existing membership before allowing a user to join a specific tier. This can be done by maintaining a mapping of users to their owned memberships and checking against it before minting a new membership:
mapping(address => mapping(address => bool)) public userMemberships;
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.");
require(!userMemberships[_msgSender()][daoMembershipAddress], "User already has membership in this tier.");
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
userMemberships[_msgSender()][daoMembershipAddress] = true;
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}