Closed c4-bot-4 closed 8 months ago
Picodes marked the issue as primary issue
othernet-global (sponsor) disputed
The ratio of wbtc/collateralShares before liquidate is added is the same as the ratio of (wbtc+addedWBTC)/(collateralShares + addCollateralShares). The same applies to weth.
When a user adds liquidity or collateral, it does not dilute the tokens held by any previous user.
Picodes marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L145 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L304 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L227 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L232-L233 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L235 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L202-L203 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L205 https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L89
Vulnerability details
Impact
When the CollateralAndLiquidity#
liquidateUser()
would be called by a liquidator, whether or not a givenwallet
(borrower) can be liquidated would be checked via the CollateralAndLiquidity#canUserBeLiquidated()
like this: https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L145Within the CollateralAndLiquidity#
canUserBeLiquidated()
, the CollateralAndLiquidity#userCollateralValueInUSD()
would be called to calculate theuserCollateralValue
. Then, theuserCollateralValue
would be used to make sure whether or not the user's position is under collateralized like this: https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L304Within the CollateralAndLiquidity#
userCollateralValueInUSD()
, thecollateralPoolID
of thetotalShares
(totalShares[collateralPoolID]
) would be stored into thetotalCollateralShares
. (NOTE:ThiscollateralPoolID
is thepoolID
of the WBTC/ETH pool) Then, theuserWBTC
and theuserWETH
would be calculated by using thetotalCollateralShares
and so on. Finally, theunderlyingTokenValueInUSD()
would be called like this: https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L227 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L232-L233 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L235Within the CollateralAndLiquidity#
underlyingTokenValueInUSD()
, thebtcValue
and theethValue
would be calculated based on the givenamountBTC
andamountETH
. Then, the sum of thebtcValue
and theethValue
(btcValue + ethValue
) would be returned as a collateral value in USD of a givenamountBTC
andamountETH
like this: https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L202-L203 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L205When a user would call the StakingRewards#
_increaseUserShare()
via the CollateralAndLiquidity#depositCollateralAndIncreaseShare()
, a givenpoolID
of thetotalShares
storage would be increased like this: https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L89Within the CollateralAndLiquidity#
userCollateralValueInUSD()
above, thetotalCollateralShares
, which thecollateralPoolID
of thetotalShares
(totalShares[collateralPoolID]
) is stored, would be used for theuserWBTC
calculation and theuserWETH
calculation.This is problematic because the
totalCollateralShares
(totalShares[collateralPoolID]
) can intentionally be surged by a flashloan attack. Once thetotalCollateralShares
would be surged, theuserWBTC
and theuserWETH
would dramatically be decreased. As a result, thebtcValue
and theethValue
would also dramatically be decreased, meaning that the collateral value (btcValue + ethValue
) in USD of all borrowing positions can dramatically be decreased as well. In this case, a lot of borrowing positions can immediately be moved to a "un-healthy" (liquidatable) status and then the malicious actor can liquidate these "un-healthy borrowing positions by repeatedly calling the CollateralAndLiquidity#liquidateUser()
.This allow a malicious actor to gain a liquidation rewards by intentionally manipulating the
totalCollateralShares
and liquidating a victim borrower's debt position by aback-running
attack - right after a borrower would borrow USDS via the CollateralAndLiquidity#borrowUSDS()
.Due to that, the victim borrower would would lose their collateral (WBTC and WETH), which the borrower deposited.
Proof of Concept
Assumption:
Attack scenario: The malicious actor can this attack above in a single block:
0/ Alice would deposit WBTC and WETH into the WBTC/WETH Pool via the CollateralAndLiquidity#
depositCollateralAndIncreaseShare()
.1/ Alice would borrow the amount of USDS via the CollateralAndLiquidity#
borrowUSDS()
.2/ Bob would monitor the TX of the step 1/ in the mempool and he would back-run it with calling the CollateralAndLiquidity#
depositCollateralAndIncreaseShare()
with the large amount of WBTC and WETH and the CollateralAndLiquidity#liquidateUser()
. This TX would cause the following state changes:_increaseUserShare()
would be called inside the CollateralAndLiquidity#depositCollateralAndIncreaseShare()
. Hence, thetotalShares[collateralPoolID]
(totalCollateralShares
) would dramatically be increased.userWBTC
and theuserWETH
of all existing debt positions in the CollateralAndLiquidity#userCollateralValueInUSD()
would dramatically be dramatically decreased.btcValue
and theethValue
of all existing debt positions in the CollateralAndLiquidity#underlyingTokenValueInUSD()
would also dramatically be dramatically decreased. This means that the collateral value in USD (btcValue + ethValue
) of all debt positions, which is returned from the CollateralAndLiquidity#underlyingTokenValueInUSD()
, can dramatically be decreased as well. https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L232-L233 https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L202-L205uint256 userWBTC = (reservesWBTC userCollateralAmount ) / totalCollateralShares;
uint256 userWETH = (reservesWETH userCollateralAmount ) / totalCollateralShares;
3/ Alice's TX (step 1/) would be executed first.
4/ Right after, Bob's TX (step 2/) would be executed as a back-run.
5/ Due to the step 4/, Alice's debt positions can immediately be moved to a un-healthy (liquidatable) status, which their collateral value in USD (
btcValue + ethValue
) is below 110% of the liquidation threshold. And then, her un-healthy (liquidatable) debt position would be liquidated by the CollateralAndLiquidity#liquidateUser()
call, which is included in the Bob's back-run TX.6/ Bob would receive the liquidation rewards from Alice's un-healthy debt positions that he liquidated.
7/ Alice would would lose their collateral (WBTC and WETH), which she deposited when the step 0/.
Tools Used
Recommended Mitigation Steps
Consider adding a grace period for a few blocks after a debt position would be moved to the un-healthy (liquidatable) status - so that this type of a back-running attack can be avoided.
Assessed type
MEV