Closed c4-bot-10 closed 6 months ago
Picodes marked the issue as duplicate of #419
Picodes marked the issue as not a duplicate
othernet-global (sponsor) disputed
As mentioned in the Technical Overview, fixed arbitrage paths were used to keep gas costs as low as possible. By design the contracts do not try to extract all arbitrage opportunities:
Picodes changed the severity to QA (Quality Assurance)
This report would be a better fit for the analysis as it's not strictly speaking a security issue but a design suggestion
Picodes marked the issue as grade-c
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/arbitrage/ArbitrageSearch.sol#L101-L136
Vulnerability details
Impact
Lesser Arbitrage profits and faliure to detect arb opportunities
Proof of Concept
The following pools are existing in correct ratio- 1 wbtc : 40000 dai (1 wbtc = 40000 \$) 1 wbtc : 16 weth ( 1 weth = 40000 DAI / 16 = 2500$) -> (Point 1)
Let's consider a pool which needs to be balanced with respect to other pools in the salty contracts i .e weth - dai pool 1 weth : 1 dai as you can see there are huge arbitrage profits to be made here. Alice sees the opportunity and calls depositSwapWithdraw with 34 dai. Lets look at the poc and look at its logs to get a better understanding of what happens during the swap procedure. You can run the poc by adding it to src/pools/tests/Pools.t.sol
So the logs of poc are (ignoring the empty salt pools) - Logs: arbProfit: 0.000000000000000000 reserve_Wbtc_wbtc_weth_pool: 1.00000000 reserve_Weth_wbtc_weth_pool: 16.000000000000000000 reserve_Weth_weth_dai_pool: 0.028571428571428572 reserve_Dai_weth_dai_pool: 35.000000000000000000 reserve_Wbtc_wbtc_dai_pool: 1.00000000 reserve_Dai_wbtc_dai_pool: 40000.000000000000000000
So now according to DAI - WETH pool , ~0.029 weth = 35 dai, so 1 weth ~= 1207\$ which is not in sync with other pools by a 1293\$ (2500 - 1207). Even with this large arbitrage opportunity the atomic arbitrage fails to trigger leading to arbProfit =0, which in itself is a unexpected behaviour according to salty docs (close focus on point 2 & 3 in the doc).
So now let's see why the atomic arb failed to trigger in the swap call's _adjustReservesForSwapAndAttemptArbitrage first the reserves are adjusted to what shown in the logs above, but in the _attemptArbitrage call the valueInEth = 2.775e16 for 34 dai (according to the new adjustedReserves). which is double of what it should be hence following the
swap: swapTokenIn->WETH arb: WETH->DAI->WBTC->WETH
route will make the _bisectionSearch's return value to be 0 cause it will be calculated using a path that is unprofitable. Here in this path relatively large amt of weth will return a small amount of DAI, due to reserves not being in sync.
Making the amountOut variable < bestArbAmountIn.
Tools Used
Manual Review
Recommended Mitigation Steps
There should be at least two arbitrage path returned by the _arbitragePath. Two paths provide better gurantee that atomic arbitrage will be triggered For e.g-
In the coded POC if you can remove the comments from depositSwapWithdraw(dai,wbtc,...) and comment out depositSwapWithdraw(dai,weth,...) i.e make the last 6 lines like this
and run it again this time you will see the pools are almost balanced and the DAO made a profit of whopping 0.65 weth.
This time the code works as expected because in the depositSwapWithdraw(dai,wbtc,...) call the path followed is WETH -> WBTC -> DAI -> WETH instead of WETH -> DAI -> WBTC -> WETH
Assessed type
Error