Project

One World
NFTDeFi
15,000 USDC
View results
Submission Details
Severity: medium
Invalid

Profit Distribution Manipulation Through Tier Weight Exploitation

Summary

Bug exists in CurrencyManager.sol where there's a race condition in the currency whitelisting process. The addCurrency function doesn't properly synchronize the state between checking if a currency is whitelisted and adding it.

The MembershipERC1155 contract implements a profit-sharing mechanism that can be exploited through tier manipulation. The contract calculates shares based on tier weights (64, 32, 16, 8, 4, 2, 1) but fails to properly track profit claims across tier transfers and upgrades.

Vulnerability Details

CurrencyManager allows whitelisting of non-existent or non-ERC20 addresses, Malicious addresses could be whitelisted as currencies.

In CurrencyManager.sol: https://github.com/Cyfrin/2024-11-one-world/blob/1e872c7ab393c380010a507398d4b4caca1ae32b/contracts/dao/CurrencyManager.sol#L33-L43

function addCurrency(address currency) external override onlyRole(ADMIN_ROLE) {
if (currency == address(0))
revert CurrencyManagerError("Cannot be null address");
// @FOUND - Missing validation for contract existence
// @FOUND - No validation if currency is actually an ERC20 token
if (_whitelistedCurrencies.contains(currency))
revert CurrencyManagerError("Already whitelisted");
_whitelistedCurrencies.add(currency);
emit CurrencyWhitelisted(currency);
}

The profit distribution mechanism in MembershipERC1155 can be manipulated through the interaction between sendProfit and token transfers, allowing malicious users to claim disproportionate profits by exploiting the share calculation system.

MembershipFactory lacks proper validation of DAO addresses, Invalid DAOs could be created and joined.

  • Platform fees could be manipulated through precision loss

  • DAO membership limits could be bypassed

In MembershipFactory.sol: https://github.com/Cyfrin/2024-11-one-world/blob/1e872c7ab393c380010a507398d4b4caca1ae32b/contracts/dao/MembershipFactory.sol#L140-L147

function joinDAO(address daoMembershipAddress, uint256 tierIndex) external {
// @FOUND - No validation if daoMembershipAddress is actually a MembershipERC1155
require(daos[daoMembershipAddress].noOfTiers > tierIndex, "Invalid tier.");
// @FOUND - Missing check for maximum member limit
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
uint256 platformFees = (20 * tierPrice) / 100;
// @FOUND - Fee calculation vulnerable to precision loss
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);

The profit distribution system uses a weighted share calculation based on tier levels, but the shareOf function and profit tracking mechanism can be exploited through strategic token transfers and profit claims.

In MembershipERC1155.sol: https://github.com/Cyfrin/2024-11-one-world/blob/1e872c7ab393c380010a507398d4b4caca1ae32b/contracts/dao/tokens/MembershipERC1155.sol#L191-L200

https://github.com/Cyfrin/2024-11-one-world/blob/1e872c7ab393c380010a507398d4b4caca1ae32b/contracts/dao/tokens/MembershipERC1155.sol#L169-L177

function sendProfit(uint256 amount) external {
uint256 _totalSupply = totalSupply;
if (_totalSupply > 0) {
// @FOUND - Precision loss in profit calculation
totalProfit += (amount * ACCURACY) / _totalSupply;
IERC20(currency).safeTransferFrom(msg.sender, address(this), amount);
emit Profit(amount);
} else {
// @FOUND - Profit redirected to creator without validation
IERC20(currency).safeTransferFrom(msg.sender, creator, amount);
}
}
function shareOf(address account) public view returns (uint256) {
// @FOUND - Weighted calculation can be manipulated through tier transfers
return (balanceOf(account, 0) * 64) +
(balanceOf(account, 1) * 32) +
(balanceOf(account, 2) * 16) +
(balanceOf(account, 3) * 8) +
(balanceOf(account, 4) * 4) +
(balanceOf(account, 5) * 2) +
balanceOf(account, 6);
}

Core Issue The vulnerability exists in the interaction between tier weights and profit distribution in MembershipERC1155.sol: https://github.com/Cyfrin/2024-11-one-world/blob/1e872c7ab393c380010a507398d4b4caca1ae32b/contracts/dao/tokens/MembershipERC1155.sol#L169-L177

// @FOUND - Core vulnerability in share calculation
function shareOf(address account) public view returns (uint256) {
return (balanceOf(account, 0) * 64) + // Tier 0: Highest weight
(balanceOf(account, 1) * 32) + // Tier 1: Second highest
(balanceOf(account, 2) * 16); // And so on...
}

Exploitation Path

  • Initial State

// User holds 1 token in Tier 0
// Share weight = 64
// Total profit pool = 1000 tokens
  • Manipulation

// Step 1: Claim profit with Tier 0 weight
// Profit claim = (64/totalSupply) * 1000
// Step 2: Upgrade to Tier 1
// New share weight = 32
// Step 3: Claim again
// Additional profit = (32/newTotalSupply) * remainingPool

Mathematical Proof

Initial Share = (userShares * ACCURACY) / totalSupply
= (64 * 1e30) / totalSupply
After Upgrade = (32 * 1e30) / newTotalSupply

This mathematical progression shows how users can extract more value than intended through strategic tier management.

The vulnerability becomes more severe when combined with the MembershipFactory's tier upgrade mechanism, allowing systematic exploitation of the profit distribution system.

In another way

// In MembershipERC1155.sol
function shareOf(address account) public view returns (uint256) {
return (balanceOf(account, 0) * 64) + // Tier 0 weight
(balanceOf(account, 1) * 32) + // Tier 1 weight
// ... other tiers
}

Mathematical Demonstration

Initial State (Tier 0):
- Share Weight = 64
- Profit Share = (64 * profit) / totalSupply
After Upgrade (Tier 1):
- Share Weight = 32
- Additional Share = (32 * remainingProfit) / newTotalSupply
Scenario A: Normal Usage
- User claims once: 1000 tokens * (64/totalSupply) = X
Scenario B: Exploit
Step 1: Claim with Tier 0 = 1000 * (64/totalSupply) = X
Step 2: Upgrade and claim = remaining * (32/newSupply) = Y
Total extracted = X + Y > intended share

Impact

  1. Loss of user funds through invalid currency transfers

  2. Creation of malicious DAOs

  3. Revenue loss for the platform through fee manipulation

Recommendations

Add profit claim cooldown period and implement snapshot mechanism for profit distribution.

function sendProfit(uint256 amount) external {
uint256 _totalSupply = totalSupply;
if (_totalSupply > 0) {
+ uint256 profitPerShare = (amount * ACCURACY) / _totalSupply;
+ require(profitPerShare > 0, "Profit too small");
- totalProfit += (amount * ACCURACY) / _totalSupply;
+ totalProfit += profitPerShare;
IERC20(currency).safeTransferFrom(msg.sender, address(this), amount);
emit Profit(amount);
} else {
+ require(creator != address(0), "Invalid creator");
IERC20(currency).safeTransferFrom(msg.sender, creator, amount);
}
}
Updates

Lead Judging Commences

0xbrivan2 Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.