Project

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

Profit sharing mechanism not considering the time period of a member's investment thus putting early membership purchaser at a disadvantage.

Summary

Profit Calculation is done based on share amount but not on how long it has been invested. So Early membership buyers are at disadvantage.

Root Cause

MembershipERC1155.sol, Line 169-177

Vulnerability Details

Profit calculation is done in MembershipERC1155:getUnsaved() function where shareOf(account) provide the share of the account

/// @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;
}

Here ShareOf() function calculate share of an account with tier weight, time weight is not included.

/// @notice Calculates the share of total profits for an account
/// @param account The account to query
/// @return The weighted share of the account
function shareOf(address account) public view returns (uint256) {
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);
}

POC

Lets say, Alice buys total 112 shares (some membership tokens) after the creation of a DAO. And the DAO makes profit available after certain time period. As the time period is not documented by OWP, lets assume it can be 1 day to few years.

Bob buys the same amount of 112 share(membership tokens) just a day or hour before when the profit is available not necessarily by front running.

Both of them would get the same profit which is not desirable.

Lets take another simplistic scenario, A DAO which can take at most 365 members. If everyday a new member join the DAO by purchasing same share of membership tokens, and the profit is available after 365 days. They would all get the same profit. Then who would be at disadvantage and who would gain more.

it would like
(M1<M2<M3...............................<M365)

Add this foundrty test file in the test folder. Should install forge-std dependencies.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "forge-std/Test.sol";
import "../contracts/dao/CurrencyManager.sol";
import "../contracts/dao/MembershipFactory.sol";
import "../contracts/dao/libraries/MembershipDAOStructs.sol";
import "../contracts/dao/tokens/MembershipERC1155.sol";
import { OWPERC20 } from "../contracts/shared/testERC20.sol"; // Update paths as needed
contract MembershipFactoryTest is Test {
CurrencyManager public currencyManager;
MembershipERC1155 public membershipImplementation;
MembershipFactory public membershipFactory;
address public daoMembershipAddress;
OWPERC20 public testERC20;
address public owner = makeAddr("owner");
address public DAOCreator = makeAddr("DAOCreator");
address public profitProvider = makeAddr("profit provider");
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
DAOInputConfig public daoConfig;
TierConfig[] public tierConfig;
//Setup function for testing
function setUp() public {
vm.startBroadcast(owner);
// Deploy CurrencyManager
currencyManager = new CurrencyManager();
// Deploy MembershipERC1155
membershipImplementation = new MembershipERC1155();
testERC20 = new OWPERC20("OWP", "OWP");
currencyManager.addCurrency(address(testERC20));
testERC20.mint(alice, 10000000000000000000);
testERC20.mint(bob, 10000000000000000000);
testERC20.mint(profitProvider, 10000000000000000000);
// Deploy MembershipFactory
membershipFactory = new MembershipFactory(
address(currencyManager),
address(owner),
"https://baseuri.com/",
address(membershipImplementation)
);
vm.stopBroadcast();
}
function testProfitSharingMechanism() public {
vm.startBroadcast(owner);
// Set DAO and Tier Input configurations
daoConfig = DAOInputConfig({
ensname: "testdao.eth",
daoType: DAOType.PUBLIC,
currency: address(testERC20),
maxMembers: 100,
noOfTiers: 3
});
tierConfig.push(
TierConfig({ price: 300, amount: 10, minted: 0, power: 12 })
);
tierConfig.push(
TierConfig({ price: 200, amount: 10, minted: 0, power: 6 })
);
tierConfig.push(
TierConfig({ price: 100, amount: 10, minted: 0, power: 3 })
);
//creating new DAO
daoMembershipAddress = membershipFactory.createNewDAOMembership(
daoConfig,
tierConfig
);
vm.stopBroadcast();
//alice joining DAO
vm.startBroadcast(alice);
testERC20.approve(address(membershipFactory), 1000000000);
membershipFactory.joinDAO(daoMembershipAddress, 0);
membershipFactory.joinDAO(daoMembershipAddress, 1);
membershipFactory.joinDAO(daoMembershipAddress, 2);
vm.stopBroadcast();
//bob joining DAO
vm.startBroadcast(bob);
testERC20.approve(address(membershipFactory), 1000000000);
membershipFactory.joinDAO(daoMembershipAddress, 0);
membershipFactory.joinDAO(daoMembershipAddress, 1);
membershipFactory.joinDAO(daoMembershipAddress, 2);
vm.stopBroadcast();
//Making profit available
vm.startBroadcast(profitProvider);
testERC20.approve(daoMembershipAddress, 5000);
MembershipERC1155(daoMembershipAddress).sendProfit(5000);
vm.stopBroadcast();
//share of Alice and Bob
console.log(
"Share of Alice: ",
MembershipERC1155(daoMembershipAddress).shareOf(alice)
);
console.log(
"Share of Bob: ",
MembershipERC1155(daoMembershipAddress).shareOf(bob)
);
//alice is claiming profit.
vm.startBroadcast(alice);
console.log("Alice Claimed Profit.");
console.log(MembershipERC1155(daoMembershipAddress).claimProfit());
vm.stopBroadcast();
//bob is claim profit later
vm.startBroadcast(bob);
console.log("Bob Claimed Profit.");
console.log(MembershipERC1155(daoMembershipAddress).claimProfit());
vm.stopBroadcast();
}
}

Impact

Likelihood: High (Certain)

Impact is also Medium. Concept of investment is always time bound which is defied in this profit sharing mechanism discouraging early investment (funding)

Tools Used

Manual Review.

Recommendations

There's two ways to solve this issue.

1) Include the time weight while calculating profit. or,

2) Must impose or restrict certain time limit to join in the DAO (which not beneficial from the economic perspective)

Updates

Lead Judging Commences

0xbrivan2 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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