Project

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

In `MembershipERC1155:claimProfit()`, profits are calculated using claim timestamp rather than funding timestamp, which leads to unfair distribution of profits

Vulnerability Details

Let's start with the flow of

  1. First, funds enter the contract through MembershipERC1155::sendProfit():

contracts/dao/tokens/MembershipERC1155.sol#L189-L200

/// @notice Distributes profits to token holders
/// @param amount The amount of currency to distribute
function sendProfit(uint256 amount) external {
uint256 _totalSupply = totalSupply;
if (_totalSupply > 0) {
totalProfit += (amount * ACCURACY) / _totalSupply;
IERC20(currency).safeTransferFrom(msg.sender, address(this), amount);
emit Profit(amount);
} else {
IERC20(currency).safeTransferFrom(msg.sender, creator, amount); // Redirect profit to creator if no supply
}
}
  1. When users want to claim, they call MembershipERC1155::claimProfit():

contracts/dao/tokens/MembershipERC1155.sol#L142-L150

/// @notice Claim profits accumulated from the profit pool
/// @return profit The amount of profit claimed
function claimProfit() external returns (uint256 profit) {
profit = saveProfit(msg.sender);
require(profit > 0, "No profit available");
savedProfit[msg.sender] = 0;
IERC20(currency).safeTransfer(msg.sender, profit);
emit Claim(msg.sender, profit);
}
  1. The MembershipERC1155::saveProfit() function is called, which itself calls MembershipERC1155::getUnsaved():

/// @notice Calculates unsaved profits for an account
/// @param account The account to query
/// @return profit The unsaved profit amount
function getUnsaved(address account) internal view returns (uint256 profit) {
return ((totalProfit - lastProfit[account]) * shareOf(account)) / ACCURACY;
}

This function calculates shares based on token balances at the timestamp profits were claimed, not when they were funded.

The problem arises when users can update their token balances between the time profits are distributed and claimed, leading to an unfair distribution of profits.

Consider the following scenario:

  • Let a DAO totalSupply be 2, two tokens of the lowest tier.

  • At timestamp t: 2 honest users -Alice and Bob- own 1 token each.

  • At timestamp t+1: The DAO gets funds by calling MembershipERC1155::sendProfit by an amount of 300 ERC20 tokens.

  • By the current MembershipERC1155::claimProfit logic, each user is elligible to 150 token. This should hold at any timestamp t+n for that particular fund allocation to maintain fairness.

  • At timestamp t+2: The innocent Alice calls MembershipERC1155::joinDAO and gets a second token of the same tier. Bob didn't claim his profit yet. Notice that claiming profits is not enforced at the time of funds allocation i.e of calling sendProfit()

  • At timestamp t+3: Alice is elligible to claim 200 tokens now, while Bob is elligible to claim 100 tokens only.

  • Bob effectively lost 50 tokens (33% of his elligible profits) while Alice got 50 tokens for free.

To recap, the two root causes are:

  • Profits are calculated using claim timestamp rather than funding timestamp and,

  • Profit claiming does not necessarily happen at the time of profit funding.

Impact

The system is flawed and unfair. The described scenario could be scaled to any configuration of DAO and users.

The unfair distribution of profits, even in case of honest behavior of all users, causes direct fund losses to user.

Tools Used

Manual review.

Recommendations

  • Consider adding a mapping that records ERC1155 token balances at the time of profit funding, not at the time of claim.

  • Consider enforcing distribution of profits directly at sendProfit which will enforce token balances at the time of calling it to avoid such timing and unfairness issues.

Note: this issue is different than Cyfrin's 7.2.1. Notice that even by fixing the issue in our report, an attacker could still frontrun a sendProfit function to get a higher share of DAO funding.

Updates

Lead Judging Commences

0xbrivan2 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
0xbrivan2 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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