Project

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

DAO creator can deprive the protocol of platformFees fees

Description

In cases where a DAO is created with a tier price set to 4 or less, the DAO creator can deprive the protocol of platform fees. The protocol supports USDT, WETH and WBTC. In case of WETH and USDT, this price will be insignificant, but in case of WBTC, this becomes a valid attack factor because of the price of WBTC and its use of 8 decimals.

Vulnerability Details

The DAO creator can create a DAO with the lowest tier like this-

tierConfigs[6] = TierConfig({
amount: 10000000,
price: 4,
power: 10,
minted: 0
});

When the amount is too high and price is too low, people will join in the lower tier and mint a lot of token then upgrade tier
when necessary. The DAO creator can market this tier as an onboarding tier so that new users mint at the lowest tier only. This bennefits both the user and the creator because all the funds sent by the users when joining DAO will be sent to the daoMembershipAddress address and users can join cheaply.
The protocol will not get a single fee because of truncation of digits in solidity in line 144-

uint256 platformFees = (20 * tierPrice) / 100;

When the tierPrice is 4 (satoshi/wei) or less, the platform fees will be 0.

Impact

  • The DAO creator can deprive the protocol of platformFees.

  • All the funds sent by the users when joining DAO will be sent to the daoMembershipAddress address.

Proof of Concept

  • Install foundry in the project by running the following commands:

    1. Initialize git if not initialized:

    git init
    1. Install foundry:

    npm i --save-dev @nomicfoundation/hardhat-foundry --force
    1. add to hardhat.config.ts:

    require("@nomicfoundation/hardhat-foundry");
    1. Create foundry.toml

    npx hardhat init-foundry

Now create a new file in the test folder:

test/LowFunds.t.sol

Add these lines to the file:

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.20;
import {Test, console} from "../lib/forge-std/src/Test.sol";
import {MembershipERC1155} from "../contracts/dao/tokens/MembershipERC1155.sol";
import {MembershipFactory} from "../contracts/dao/MembershipFactory.sol";
import {CurrencyManager} from "../contracts/dao/CurrencyManager.sol";
import {DAOInputConfig, DAOType, TierConfig} from "../contracts/dao/libraries/MembershipDAOStructs.sol";
import {OWPERC20} from "../contracts/shared/testERC20.sol";
import {IMembershipERC1155} from "../contracts/dao/interfaces/IERC1155Mintable.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
contract PoC is Test {
MembershipERC1155 public membershipERC1155;
MembershipFactory public membershipFactory;
CurrencyManager public currencyManager;
OWPERC20 public currency;
address opWallet;
address deployer;
address daoCreator;
address user;
bytes32 public constant OWP_FACTORY_ROLE = keccak256("OWP_FACTORY_ROLE");
bytes32 public constant DAO_CREATOR = keccak256("DAO_CREATOR");
function setUp() public {
opWallet = makeAddr("opWallet");
deployer = makeAddr("deployer");
daoCreator = makeAddr("daoCreator");
user = makeAddr("user");
vm.startPrank(deployer);
// Deploy MembershipERC1155
membershipERC1155 = new MembershipERC1155();
// Deploy CurrencyManager
currencyManager = new CurrencyManager();
currency = new OWPERC20("Currency", "CUR");
// Deploy MembershipFactory
membershipFactory = new MembershipFactory(address(currencyManager), opWallet, "baseUri", address(membershipERC1155));
currencyManager.addCurrency(address(currency));
vm.stopPrank();
}
function testLowFunds() public {
DAOInputConfig memory daoInputConfig = DAOInputConfig({
ensname: "One",
daoType: DAOType.PUBLIC,
currency: address(currency),
maxMembers: 200,
noOfTiers: 3
});
TierConfig[] memory tierConfigs = new TierConfig[]();
tierConfigs[0] = TierConfig({
amount: 40,
price: 64,
power: 40,
minted: 0
});
tierConfigs[1] = TierConfig({
amount: 40,
price: 16,
power: 20,
minted: 0
});
tierConfigs[2] = TierConfig({
amount: 40,
price: 4,
power: 10,
minted: 0
});
// Create DAO
vm.prank(daoCreator);
address proxy = membershipFactory.createNewDAOMembership(daoInputConfig, tierConfigs);
currency.mint(user, 1e20);
vm.startPrank(user);
currency.approve(address(membershipFactory), 1e20);
// Minting token 10 times at tier 2
for(uint i=0; i<10; i++) {
membershipFactory.joinDAO(proxy, 2);
}
vm.stopPrank();
// All funds that was used to mint token at tier 2 went to balanceOfMembership contract
uint256 balanceOfMembership = currency.balanceOf(proxy);
assertEq(balanceOfMembership, 4*10);
}
}

Run the test:

forge test --match-test testLowFunds

Recommendations

  • Add a check in the joinDAO function to make sure that if the platformFees is 0, user at least pay 1 satoshi/wei to the protocol.

function joinDAO(address daoMembershipAddress, uint256 tierIndex) external {
require(daos[daoMembershipAddress].noOfTiers > tierIndex, "Invalid tier.");
require(daos[daoMembershipAddress].tiers[tierIndex].amount > daos[daoMembershipAddress].tiers[tierIndex].minted, "Tier full.");
uint256 tierPrice = daos[daoMembershipAddress].tiers[tierIndex].price;
uint256 platformFees = (20 * tierPrice) / 100;
+ if(platformFees == 0) {
+ platformFees = 1;
+ }
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}
Updates

Lead Judging Commences

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!