Project

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

MembershipERC1155 profit calculations can leave dust

Summary

Calculations in MembershipERC1155::sendProfitare imprecise and can leave some dust on contract

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

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

Due to how profit split works, dust will accumulate and as there is no other withdrawal possibility this will be stuck in contract.

Proof of concept

Use following test contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { Test, console } from "lib/forge-std/src/Test.sol";
import {MockUSDC} from "./mocks/MockUSDC.sol";
import {MembershipFactory} from "../contracts/dao/MembershipFactory.sol";
import {CurrencyManager} from "../contracts/dao/CurrencyManager.sol";
import {MembershipERC1155} from "../contracts/dao/tokens/MembershipERC1155.sol";
import {DAOInputConfig, DAOType, DAOConfig, TierConfig} from "../contracts/dao/libraries/MembershipDAOStructs.sol";
contract MembershipProfit is Test{
address factoryOwner = makeAddr("factoryOwner");
address daoCreator = makeAddr("daoCreator");
address owpWallet = makeAddr("owpWallet");
address daoUser1 = makeAddr("daoUser1");
address daoUser2 = makeAddr("daoUser2");
address donator = makeAddr("donator");
MockUSDC usdc;
CurrencyManager currencyManager;
MembershipFactory membershipFactory;
MembershipERC1155 membershipToken;
DAOInputConfig daoConfig;
TierConfig[] tiers;
function setUp() public {
membershipToken = new MembershipERC1155();
vm.startPrank(factoryOwner);
usdc = new MockUSDC();
currencyManager = new CurrencyManager();
currencyManager.addCurrency(address(usdc));
membershipFactory = new MembershipFactory(address(currencyManager), owpWallet, "", address(membershipToken));
vm.stopPrank();
usdc.mint(daoUser1, 100e6);
usdc.mint(daoUser2, 100e6);
usdc.mint(donator, 1000e6);
daoConfig = DAOInputConfig({
ensname: "DaoName",
daoType: DAOType.SPONSORED,
currency: address(usdc),
maxMembers: 100,
noOfTiers: 7
});
TierConfig memory tier0 = TierConfig({
amount: 3,
price: 3e6,
power: 3,
minted: 0
});
tiers.push(tier0);
TierConfig memory tier1 = TierConfig({
amount: 3,
price: 2e6,
power: 2,
minted: 0
});
tiers.push(tier1);
TierConfig memory tier2 = TierConfig({
amount: 4,
price: 1e6,
power: 1,
minted: 0
});
tiers.push(tier2);
TierConfig memory tier3 = TierConfig({
amount: 4,
price: 1e6,
power: 1,
minted: 0
});
tiers.push(tier3);
TierConfig memory tier4 = TierConfig({
amount: 4,
price: 1e6,
power: 1,
minted: 0
});
tiers.push(tier4);
TierConfig memory tier5 = TierConfig({
amount: 4,
price: 0,
power: 1,
minted: 0
});
tiers.push(tier5);
TierConfig memory tier6 = TierConfig({
amount: 4,
price: 0,
power: 1,
minted: 0
});
tiers.push(tier6);
}
function test_dust() public {
vm.startPrank(daoCreator);
address daoAddress = membershipFactory.createNewDAOMembership(daoConfig, tiers);
vm.stopPrank();
vm.startPrank(daoUser1);
usdc.approve(address(membershipFactory), type(uint256).max);
membershipFactory.joinDAO(daoAddress, 6);
vm.stopPrank();
vm.startPrank(daoUser2);
usdc.approve(address(membershipFactory), type(uint256).max);
membershipFactory.joinDAO(daoAddress, 5);
vm.stopPrank();
vm.startPrank(donator);
usdc.approve(daoAddress, type(uint256).max);
MembershipERC1155(daoAddress).sendProfit(100e6);
vm.stopPrank();
vm.prank(daoUser1);
MembershipERC1155(daoAddress).claimProfit();
console.log("User1 balance after claim: %s", usdc.balanceOf(daoUser1));
vm.prank(daoUser2);
MembershipERC1155(daoAddress).claimProfit();
console.log("User2 balance after claim: %s", usdc.balanceOf(daoUser2));
console.log("Daoaddress balance after all users claim: %s", usdc.balanceOf(daoAddress));
}
}

And this mock:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import { console } from "lib/forge-std/src/console.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockUSDC is ERC20 {
constructor() ERC20("USDC", "USDC") {
_mint(msg.sender, 1_000_000e6);
}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}

run command forge test --mt test_dust -vvv

Result:

Logs:
User1 balance after claim: 133333333
User2 balance after claim: 166666666
Daoaddress balance after all users claim: 1

Impact

Low

Tools Used

Manual review

Recommendations

Implement other withdrawal possibility

Updates

Lead Judging Commences

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.