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: 117/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
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/Market.sol#L150
When a user calls the buy() function, he can be front-run such that he has to pay way more than he had hoped for, based on the tokenCount
at the time of placing the order. There is no slippage or maxPriceLimit
parameter inside buy()
to protect him from this.
Suppose:
shareData[_id].tokenCount
is 10.buy(1, 1)
hoping to pay an amount of 11 * LINEAR_INCREASE
as per the calculation inside Market::getBuyPrice() and LinearBondingCurve::getPriceAndFee().buy(1, 20)
.shareData[_id].tokenCount
is now 30.31 * LINEAR_INCREASE
, much more than he had planned for.Manual inspection.
Add a maxPriceLimit
parameter inside buy()
which causes the function to revert if price
exceeds it.
Other
#0 - c4-pre-sort
2023-11-18T10:44:38Z
minhquanym marked the issue as duplicate of #12
#1 - c4-judge
2023-11-28T23:36:29Z
MarioPoneder marked the issue as satisfactory
🌟 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
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/Market.sol#L174
When a user calls the sell() function, she can be front-run such that she receives way less than she had hoped for, based on the tokenCount
at the time of placing the order. There is no slippage or minPriceExpected
parameter inside sell()
to protect her from this.
Suppose:
shareData[_id].tokenCount
is 30.sell(1, 1)
hoping to receive an amount of 30 * LINEAR_INCREASE
as per the calculation inside Market::getSellPrice() and LinearBondingCurve::getPriceAndFee().sell(1, 20)
.shareData[_id].tokenCount
is now 10.10 * LINEAR_INCREASE
, much less than she had planned for. She would have rather preferred to wait for tokenCount to go up before selling her tokens.Manual inspection.
Add a minPriceExpected
parameter inside sell()
which causes the function to revert if price
received is less than it.
Other
#0 - c4-pre-sort
2023-11-18T10:44:29Z
minhquanym marked the issue as duplicate of #12
#1 - c4-judge
2023-11-28T23:36:19Z
MarioPoneder marked the issue as satisfactory
🌟 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
https://github.com/code-423n4/2023-11-canto/blob/main/1155tech-contracts/src/Market.sol#L150
An attacker can watch the mempool to wait for a user's buy() call and sandwich it to make immediate profits. Since buy
& sell
prices depend on the number of tokens, and also since there is no holding-period required before selling, attacker can make immediate profits.
Paste the following inside 2023-11-canto/1155tech-contracts/src/test/Market.t.sol
and run with forge test --mt test_t0x1c_Sandwich -vv
. Alice is the attacker in this example:
function test_t0x1c_Sandwich() public { testCreateNewShare(); token.approve(address(market), type(uint256).max); vm.prank(alice); token.approve(address(market), type(uint256).max); deal(address(token), alice, 10e18); // give some funds to Alice uint256 aliceInitialBalance = token.balanceOf(alice); // simulating some pre-existing orders market.buy(1, 2); market.buy(1, 3); //==============================// //======= SANDWICH ATTACK ======// //==============================// vm.prank(alice); market.buy(1, 10); // Front-run market.buy(1, 1); // BUY order from naive user vm.prank(alice); market.sell(1, 10); // Back-run // Immediate Profit assertGt(token.balanceOf(alice), aliceInitialBalance, "no profit"); }
Foundry.
Protocol can considering adding a delay
between buy & sell from the same address so that it's not possible within the same block. Also, slippage protection for users will help keep such situations at bay to some extent.
Timing
#0 - c4-pre-sort
2023-11-18T09:49:52Z
minhquanym marked the issue as duplicate of #12
#1 - c4-judge
2023-11-28T23:17:47Z
MarioPoneder marked the issue as satisfactory