Summary
The hardcoding of platform fees at 20% in the joinDAO
function represents a significant architectural rigidity that could impact the protocol's long-term sustainability and adaptability. This inflexible fee structure prevents the protocol from adjusting to market conditions, competitive pressures, or varying economic models across different types of DAOs. In scenarios where fee adjustments are necessary due to market dynamics or protocol governance decisions, the entire contract would require redeployment, leading to potential operational disruptions and increased administrative overhead. Furthermore, the hardcoded nature of the fee calculation makes the contract less transparent to users who need to inspect the code to understand the fee structure rather than being able to query it through a public variable.
The root cause lies in the direct embedding of the fee percentage within the function logic, rather than implementing it as a configurable parameter managed through protocol governance. The calculation uint256 platformFees = (20 * tierPrice) / 100
demonstrates how the 20% fee is tightly coupled with the business logic, making it impossible to modify without a contract upgrade. This approach also increases the risk of calculation errors or inconsistencies if the fee structure needs to be referenced or validated across different parts of the protocol, as the value is not centrally managed.
https://github.com/Cyfrin/2024-11-one-world/blob/main/contracts/dao/MembershipFactory.sol#L140
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);
}
Recommended Fix
Implement a configurable fee structure using an administrative function and state variable. Use basis points (BPS) for more precise fee calculations:
contract MembershipFactory {
uint256 public platformFeeBps = 2000;
event PlatformFeeUpdated(uint256 oldFee, uint256 newFee);
function setPlatformFee(uint256 newFeeBps) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(newFeeBps <= 10000, "Fee cannot exceed 100%");
uint256 oldFee = platformFeeBps;
platformFeeBps = newFeeBps;
emit PlatformFeeUpdated(oldFee, newFeeBps);
}
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;
require(tierPrice > 0, "Invalid tier price");
uint256 platformFees = (tierPrice * platformFeeBps) / 10000;
uint256 daoAmount = tierPrice - platformFees;
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, daoAmount);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}
}