USDS borrower might be liquidated due to unable increasing collateral
Proof of Concept
Anyone can deposit WBTC and WETH into Salty Protocol as WBTC/WETH liquidity.
WBTC/WETH liquidity provider is allowed to borrow USDS with WBTC/WETH liquidity as collateral.
The borrower could be liquidated if their collateral ratio is under the minimum threshold.
If the prices of WBTC and WETH drop a lot, A borrower have to increase their collateral ratio by depositing WBTC/WETH to avoid to be liquidated.
However, a borrower might not be able to increase their collateral on time due to cooldown restriction, resulting in liquidation.
Suppose StakingConfig.modificationCooldown is 1 hour, StableConfig.initialCollateralRatioPercent is 200 and StableConfig.minimumCollateralRatioPercent is 110:
Alice deposits 20000 USD worth of WBTC/WETH
Alice borrows 10000 USDS, the collateral ratio of alice is 200%
Alice's collateral is now valued at 12000 USD due to the significant drop in the prices of WBTC/WETH
Alice deposits an additional 2000 USD worth of WBTC/WETH to mitigate the risk of liquidation.
30 minutes later, the value of alice's collateral drops to 12000 USD again
Alice tries to deposit more collteral to avoid liquidation. However it is blocked due to 1 hour cooldown limitation.
The prices of WBTC/WETH keep dropping, alice's collateral is valued less than 11000 USD by the time the cooldown is finished.
Alice could be liquidated due to insufficient collateral ratio.
Copy below codes to CollateralAndLiquidity.t.sol and run COVERAGE="yes" NETWORK="sep" forge test -vv --rpc-url RPC_URL --match-test testDepositCollateralTwiceInOneHour
function testDepositCollateralTwiceInOneHour() public
{
uint wbtcBalance = wbtc.balanceOf(alice);
uint wethBalance = weth.balanceOf(alice);
vm.startPrank(alice);
collateralAndLiquidity.depositCollateralAndIncreaseShare(wbtcBalance/2, wethBalance/2, 0, block.timestamp, true );
vm.warp(block.timestamp + 30 minutes);
vm.expectRevert("Must wait for the cooldown to expire");
collateralAndLiquidity.depositCollateralAndIncreaseShare(wbtcBalance/2, wethBalance/2, 0, block.timestamp, true );
vm.stopPrank();
}
Tools Used
Manual review
Recommended Mitigation Steps
Remove cooldownExpiration checking from _increaseUserShare():
function _increaseUserShare( address wallet, bytes32 poolID, uint256 increaseShareAmount, bool useCooldown ) internal
{
require( poolsConfig.isWhitelisted( poolID ), "Invalid pool" );
require( increaseShareAmount != 0, "Cannot increase zero share" );
UserShareInfo storage user = _userShareInfo[wallet][poolID];
if ( useCooldown )
if ( msg.sender != address(exchangeConfig.dao()) ) // DAO doesn't use the cooldown
{
- require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" );
// Update the cooldown expiration for future transactions
user.cooldownExpiration = block.timestamp + stakingConfig.modificationCooldown();
}
uint256 existingTotalShares = totalShares[poolID];
// Determine the amount of virtualRewards to add based on the current ratio of rewards/shares.
// The ratio of virtualRewards/increaseShareAmount is the same as totalRewards/totalShares for the pool.
// The virtual rewards will be deducted later when calculating the user's owed rewards.
if ( existingTotalShares != 0 ) // prevent / 0
{
// Round up in favor of the protocol.
uint256 virtualRewardsToAdd = Math.ceilDiv( totalRewards[poolID] * increaseShareAmount, existingTotalShares );
user.virtualRewards += uint128(virtualRewardsToAdd);
totalRewards[poolID] += uint128(virtualRewardsToAdd);
}
// Update the deposit balances
user.userShare += uint128(increaseShareAmount);
totalShares[poolID] = existingTotalShares + increaseShareAmount;
emit UserShareIncreased(wallet, poolID, increaseShareAmount);
}
Lines of code
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L67
Vulnerability details
Impact
USDS
borrower might be liquidated due to unable increasing collateralProof of Concept
Anyone can deposit WBTC and WETH into Salty Protocol as WBTC/WETH liquidity. WBTC/WETH liquidity provider is allowed to borrow USDS with WBTC/WETH liquidity as collateral. The borrower could be liquidated if their collateral ratio is under the minimum threshold.
If the prices of WBTC and WETH drop a lot, A borrower have to increase their collateral ratio by depositing WBTC/WETH to avoid to be liquidated. However, a borrower might not be able to increase their collateral on time due to cooldown restriction, resulting in liquidation.
Suppose
StakingConfig.modificationCooldown
is 1 hour,StableConfig.initialCollateralRatioPercent
is 200 andStableConfig.minimumCollateralRatioPercent
is 110:20000
USD worth of WBTC/WETH10000
USDS, the collateral ratio of alice is 200%12000
USD due to the significant drop in the prices of WBTC/WETH2000
USD worth of WBTC/WETH to mitigate the risk of liquidation.30 minutes
later, the value of alice's collateral drops to 12000 USD again1 hour
cooldown limitation.11000
USD by the time the cooldown is finished.Copy below codes to CollateralAndLiquidity.t.sol and run
COVERAGE="yes" NETWORK="sep" forge test -vv --rpc-url RPC_URL --match-test testDepositCollateralTwiceInOneHour
Tools Used
Manual review
Recommended Mitigation Steps
Remove
cooldownExpiration
checking from_increaseUserShare()
:Assessed type
Timing