Project

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

Potential Re-Entrancy Vulnerability in joinDAO

Summary

he joinDAO function is vulnerable to a re-entrancy attack due to external calls made before state updates. Specifically, the function performs an ERC20 transferFrom call, which an attacker could exploit to recursively re-enter joinDAO before the membership count is updated. This allows an attacker to mint multiple memberships within a single tier, bypassing intended tier limits.

Vulnerability Details

The joinDAO function is vulnerable to re-entrancy because it performs an external call to transfer ERC20 tokens (using IERC20.transferFrom) before finalizing the membership count update. Specifically, this call can trigger an attacker-controlled contract that can recursively call joinDAO, resulting in multiple membership mints within a tier that should have been limited.

Impact

A re-entrancy vulnerability in the joinDAO function could allow an attacker to repeatedly call the function before state updates are finalized, potentially minting multiple memberships in a tier beyond its intended limit. If exploited, this would allow malicious users to bypass the maximum membership restrictions on each tier, impacting the DAO’s resource allocation and user experience.

If an attacker successfully exploits this vulnerability, they could:

Mint multiple memberships in a single tier, exceeding the intended limit.

  • Potentially fill up high-demand tiers with multiple memberships under a single malicious address.

  • Cause financial or reputational damage to the DAO by breaking membership limits.

Code

The vulnerable sequence in the joinDAO function is as follows:

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;
// Increment minted count for the selected tier before transferring funds
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
// Transfer platform fees and tier price
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
// Mint the membership NFT for the user
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}

If the IERC20.transferFrom calls allow for re-entrancy (for example, if an attacker uses a custom ERC20 token with a transferFrom function that makes a callback), the attacker can re-enter joinDAO before the minted count (daos[daoMembershipAddress].tiers[tierIndex].minted) is incremented.

Tools Used

Manual Review

Recommendations

To prevent re-entrancy attacks, consider the following mitigations:

Check-Effects-Interactions Pattern: Update the state before making any external calls. For joinDAO, ensure the minted count is incremented before any token transfers are initiated.

Re-Entrancy Guard: Add a re-entrancy guard modifier, such as OpenZeppelin’s nonReentrant, to prevent re-entrant calls within joinDAO.

Use Pull Payment Pattern: Instead of transferring funds within joinDAO, implement a “pull payment” mechanism where funds are claimed by recipients after the transaction completes, reducing the potential for re-entrancy.

Here’s an example of how the joinDAO function would look after applying the Check-Effects-Interactions Pattern:

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;
// Increment minted count for the selected tier (state update before external call)
daos[daoMembershipAddress].tiers[tierIndex].minted += 1;
// Transfer platform fees and tier price (external calls after state update)
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), owpWallet, platformFees);
IERC20(daos[daoMembershipAddress].currency).transferFrom(_msgSender(), daoMembershipAddress, tierPrice - platformFees);
// Mint the membership NFT for the user
IMembershipERC1155(daoMembershipAddress).mint(_msgSender(), tierIndex, 1);
emit UserJoinedDAO(_msgSender(), daoMembershipAddress, tierIndex);
}
Updates

Lead Judging Commences

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

Support

FAQs

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