Tiny collateral share increase prevents liquidation #435
Labels
3 (High Risk)
Assets can be stolen/lost/compromised directly
bug
Something isn't working
duplicate-312
edited-by-warden
satisfactory
satisfies C4 submission criteria; eligible for awards
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L154
Vulnerability details
Title
Tiny collateral share increase prevents liquidation
Links to affected code
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L154
Vulnerability details
Impact
To maintain the invariant of USDS overcollateralization, it is imperative to permit the liquidation of positions that fall below the required collateral threshold.
When a user becomes undercollateralized due to market fluctuations, they can prevent their liquidation by using the modification cooldown, that serves as a temporary shield against liquidation, providing a window during which no liquidation actions can be taken.
A user can continually prevent liquidation by adding a minimal amount of collateral at the conclusion of each cooldown period, effectively granting an indefinite reprieve from liquidation.
Transaction ordering could allow for a liquidation transaction to precede the collateral increase, but the presence of a cap on liquidation rewards introduces an economic factor, incentivizing liquidators to moderate their gas expenditures to ensure that the liquidation remains financially viable.
By default, the reward cap is only $500 USD. To order share increase before any liquidations, the gas price merely needs to push the liquidation transaction cost above that.
With a constant cost in execution, the strategy becomes more optimal the larger the share (the larger the amount of USDS undercollateralized), which are the holding you need liquidated as they have the greatest impact on overall collateralization.
To push the liquidation transaction gas cost above $500 USD with the increase share gas is ~$667 USD, proportionally a trivial sum for a seriously large holding.
Proof of Concept
Steps
Invariant: USDS is overcollateralized
The Salty.io documentation
Tiny increase of collateral is allowed
In the flow for
CollateralAndLiquidity::depositCollateralAndIncreaseShare()
, minimum values are only required byPools::addLiquidity()
andStakedRewards::increaseUserShare()
.The amount of both tokens must be greater than dust, in Pools::addLiquidity()
The share must be greater than zero, in StakingRewards::_increaseUserShare()
Modification Cooldown period
Default of 1 hour, with potential range of 15 minutes to 6 hours, in adjustment of 15 minutes, in StakingConfig
Change in collateral share sets cooldown
When a user increase their share in
CollateralAndLiquidity
, the cooldown is updated inStakingRewards
CollateralAndLiquidity::depositCollateralAndIncreaseShare()
Liquidity::_depositLiquidityAndIncreaseShare()
CollateralAndLiquidity::_depositLiquidityAndIncreaseShare()
StakingRewards::_increaseUserShare()
Modification Cooldown prevents liquidation
To liquidate a user, their share must be decreased, but when decreasing the user's share,
true
is given for using the cooldown.When
block.timestamp
is within the modified cooldown window the transaction reverts.CollateralAndLiquidity::liquidateUser()
Reward cap
Liquidation rewards are capped, initially at $500 USD.
Liquidation rewards are DAO configurable parameter in StableConfig
Reward capping being performed in CollateralAndLiquidity::liquidateUser()
Gas Usage
A successful
CollateralAndLiquidity::depositCollateralAndIncreaseShare()
uses ~134% more gas than a successfulCollateralAndLiquidity::liquidateUser()
.Gas Report
Add the test case to src/stable/tests/CollateralAndLiquidity.t.sol
To include the gas report add this to
foundry.toml
When running the test add
--gas-report
to the command to generate the gas usage table.Test Case
Test Case
Demonstrates the increase of collateral share by a tiny amount prevents liquidation.Add the test case to src/stable/tests/CollateralAndLiquidity.t.sol
Tools Used
Manual review, Foundry test
Recommended Mitigation Steps
Allow liquidation irrespective of the user's cooldown status.
In CollateralAndLiquidity ::liquidateUser()
// Withdraw the liquidated collateral from the liquidity pool. // The liquidity is owned by this contract so when it is withdrawn it will be reclaimed by this contract. (uint256 reclaimedWBTC, uint256 reclaimedWETH) = pools.removeLiquidity(wbtc, weth, userCollateralAmount, 0, 0, totalShares[collateralPoolID] ); // Decrease the user's share of collateral as it has been liquidated and they no longer have it. - _decreaseUserShare( wallet, collateralPoolID, userCollateralAmount, true ); _decreaseUserShare( wallet, collateralPoolID, userCollateralAmount, false ); // The caller receives a default 5% of the value of the liquidated collateral. uint256 rewardPercent = stableConfig.rewardPercentForCallingLiquidation();
Assessed type
Other
The text was updated successfully, but these errors were encountered: