Summary
Detailed analysis of fee losses across all tiers reveals that higher tiers with more decimal places result in larger losses for the protocol.
Vulnerability Details
Current calculation:
uint256 platformFees = (20 * tierPrice) / 100;
Detailed analysis of all tiers (based on the picture with the tier cost):
Expected fee: 1581.25 * 0.20 = 316.25
Actual fee: (20 * 1581.25) / 100 = 316
Loss per transaction: 0.25
Expected fee: 790.63 * 0.20 = 158.126
Actual fee: (20 * 790.63) / 100 = 158
Loss per transaction: 0.126
Expected fee: 425.16 * 0.20 = 85.032
Actual fee: (20 * 425.16) / 100 = 85
Loss per transaction: 0.032
Expected fee: 209.21 * 0.20 = 41.842
Actual fee: (20 * 209.21) / 100 = 41
Loss per transaction: 0.842 (Fourth largest loss!)
Expected fee: 104.6 * 0.20 = 20.92
Actual fee: (20 * 104.6) / 100 = 20
Loss per transaction: 0.92 (Third largest loss!)
Expected fee: 69.76 * 0.20 = 13.952
Actual fee: (20 * 69.76) / 100 = 13
Loss per transaction: 0.952 (Second largest loss!)
Expected fee: 34.88 * 0.20 = 6.976
Actual fee: (20 * 34.88) / 100 = 6
Loss per transaction: 0.976 (Largest loss!)
Impact
The most significant losses occur in lower tiers (4-7) where the rounding down has a larger relative impact:
Losses by tier contribution:
Tier 7: ~54.6% of total loss (491.904/900.864)
Tier 6: ~26.63% of total loss (239.904/900.864)
Tier 5: ~12.87% of total loss (115.92/900.864)
Tier 4: ~5.90% of total loss (53.046/900.864)
Calculating total losses at maximum capacity per tier:
Loss per tx: 0.976
Total loss at capacity: 0.976 * 504 = 491.904 tokens
Loss per tx: 0.952
Total loss at capacity: 0.952 * 252 = 239.904 tokens
Loss per tx: 0.92
Total loss at capacity: 0.92 * 126 = 115.92 tokens
Loss per tx: 0.842
Total loss at capacity: 0.842 * 63 = 53.046 tokens
Total maximum potential loss across all affected tiers: 900.864 tokens
(53.046 + 115.92 + 239.904 + 491.904)
Recommended Mitigation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
+ import "@openzeppelin/contracts/utils/math/Math.sol";
contract MembershipFactory is AccessControl, NativeMetaTransaction{
+ using Math for uint256;
+
+ uint256 public constant PLATFORM_FEE_PERCENTAGE = 20;
+ uint256 public constant PERCENTAGE_BASIS = 100;
function joinDAO(address daoMembershipAddress, uint256 tierIndex) external {
- uint256 platformFees = (20 * tierPrice) / 100;
+ uint256 platformFees = Math.mulDiv(
+ tierPrice,
+ PLATFORM_FEE_PERCENTAGE,
+ PERCENTAGE_BASIS,
+ Math.Rounding.Up
+ );
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
}
}