xanoutas/solidity-security-findings huggingface dataset:
DoS: Liquidation of an Undercollateralized position might fail due to Cooldown period of Wallet
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L154


# Vulnerability details

## Impact
The identified vulnerability allows a malicious borrower to engage in a Denial-of-Service (DOS) attack by disrupting (reverting) the liquidation process for an undercollateralized borrowed position. This manipulation maximizes the likelihood of the targeted position becoming insolvent, potentially leading to financial losses and compromising the integrity of the lending protocol.

The DoS will be at least 15 minutes and can be much longer.

## Proof of Concept
Liquidation of a position can be initiated if the ratio of collateral / borrowed USDS falls below the minimum value (default 110%). Any user can initiate it by calling [CollateralAndLiquidity.liquidateUser()](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L140).

```solidity
function liquidateUser(address wallet) external nonReentrant {
  .
  .
  .
  // Decrease the user's share of collateral as it has been liquidated and they no longer have it.
  _decreaseUserShare(wallet, collateralPoolID, userCollateralAmount, true);
  .
  .
  .
}
```

This function calls an internal function [\_decreaseUserShare()](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L154), as shown above, to decrease the user shares of collateral. This function has multiple parameters, one of which is `bool useCooldown`. If `useCooldown=true` then the wallet's cooldown period will be checked during liquidation.

The duration of cooldown period can range from 15 minutes to 6 hours which is set by the DAO, as described in [StakingConfig](https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingConfig.sol#L28-L31).

The cooldown period is unique for each wallet and is set to `block.timestamp + stakingConfig.modificationCooldown()` whenever a user's shares are [increased](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L70) or [decreased](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L110).

So if `block.timestamp < user.cooldownExpiration`, the transaction will revert because the cooldown period of the wallet to be liquidated haven't expired.

The holder of a position can always extend the `cooldownExpiration` time by adding a relatively small amount of collateral.

1. Alice have borrowed 10,000 USDS and has a collateral / borrowed ratio < 110%
   1. This position can be liquidated
2. Alice deposits some collateral worth 1 USDS
   1. Now `cooldownExpiration = block.timestamp + 1 hour`
3. A user calls `liquidateUser()` to liquidate this position
   1. Since `block.timestamp < cooldownExpiration`, the position cannot be liquid
A user can prevent being liquidated by adding few wei of collateral just before liquidator call
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L107


# Vulnerability details

## Vulnerability details

Alice can front-run a liquidator call `liquidate(alice)` by calling `depositCollateralAndIncreaseShare` with a value > DUST and DoS/prevent its liquidation, [as liquidate](https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L154) is limited [by `cooldownExpiration` in `_decreaseUserShare`](https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L107)

Why? 

1) every time `_increaseUserShare` or `_decreaseUserShare` is called with `useCooldown = true`, a cooldown is set to `block.timestamp + modificationCooldown`. This cooldown will prevent any calls to these functions (if `useCooldown = true`) until the time has passed
2) Because `depositCollateralAndIncreaseShare` (which calls `_depositLiquidityAndIncreaseShare`, itself calling `_increaseUserShare` with `useCooldown == true`) increase the cooldown
3) And `liquidate(alice)` also calls `_decreaseUserShare(alice,...)` with `useCooldown = true` in the same block, the cooldown will not be expired, causing the call to revert

## Impact
A malicious user can keep its position unhealthy by preventing liquidations

## Proof of Concept

```solidity
	function test_auditPreventLiquidationByAddingCollateral() public {
		
		// Bob deposits collateral so alice can be liquidated
        vm.startPrank(bob);
		collateralAndLiquidity.depositCollateralAndIncreaseShare(wbtc.balanceOf(bob), weth.balanceOf(bob), 0, block.timestamp, false );
		vm.stopPrank();


        // Deposit and borrow from Alice's account to create basis for liquidation
        _depositCollateralAndBorrowMax(alice);

        // Cause a drastic price drop to allow for liquidation
        _crashCollateralPrice();

        vm.expectRevert("Must wait for the cooldown to expire");
        collateralAndLiquidity.liquidateUser(alice);

        // Warp past the lockout period, so liquidation is now allowed
        vm.warp(block.timestamp + 1 hours); // warp to just after the lockout period

		//alice add few wai of liquidity, which do not recover her health factor
		deal(address(wbtc), alice, 101);
		deal(address(weth), alice, 101);
		vm.prank(alice);
		collateralAndLiquidity.depositCollateralAndIncreaseShare(101, 101, 0, block.timestamp, false );

        vm.expectRevert("Must wait for the cooldown to expire");
        collateralAndLiquidity.liquidateUser(alice);
    }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
When the user position is unhealthy, user should be forced to at least add collateral up to a healthy state


## Assessed type

Context
lack of scaling decimal places of erc20 leads to break the protocol invariant 
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/pools/Pools.sol#L140-L165


# Vulnerability details

## Impact
Protocol didn't scale the erc20 tokens .This will cause protocol will not work as intended.

## Proof of Concept
Erc20 tokens have different decimals .Protocol gonna use erc20 which has always not 1e18 .Let's assume user add tokenA and token B as liquidity .Token A has 1e18 decimal places and Token B has 1e6 decimal places.When user add liquidity , there will be lossing funds cause of rounding error due to different decimal places.

   uint256 proportionalB = ( maxAmount0 * reserve1 ) / reserve0;

		// proportionalB too large for the specified maxAmountB?
		if ( proportionalB > maxAmount1 )
			{
			// Use maxAmountB and a proportional amount for tokenA instead
		---->>	addedAmount0 = ( maxAmount1 * reserve0 ) / reserve1;
			addedAmount1 = maxAmount1;
			}
		else
			{
			addedAmount0 = maxAmount0;
			addedAmount1 = proportionalB;
			}

		// Update the reserves
		reserves.reserve0 += uint128(addedAmount0);
		reserves.reserve1 += uint128(addedAmount1);

## Tools Used
manual view
## Recommended Mitigation Steps
scale all erc20 tokens to 1e18


## Assessed type

Invalid Validation
Users can unstake and then cancel their stake immediately to game the rewards in StakingRewards.sol
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/Staking.sol#L84-L97


# Vulnerability details

## Proof Of Concept

In Staking.sol, users can call `unstake()` to withdraw their xSALT. When `unstake()` is called, `_decreaseUserShare()` is called in StakingRewards.sol and the user receives some salt 

```
                // Determine the share of the rewards for the amountToDecrease (will include previously added virtual rewards)
>                uint256 rewardsForAmount = ( totalRewards[poolID] * decreaseShareAmount ) / totalShares[poolID];


                // For the amountToDecrease determine the proportion of virtualRewards (proportional to all virtualRewards for the user)
                // Round virtualRewards down in favor of the protocol
 >               uint256 virtualRewardsToRemove = (user.virtualRewards * decreaseShareAmount) / user.userShare;


                // Update totals
                totalRewards[poolID] -= rewardsForAmount;
                totalShares[poolID] -= decreaseShareAmount;


                // Update the user's share and virtual rewards
                user.userShare -= uint128(decreaseShareAmount);
                user.virtualRewards -= uint128(virtualRewardsToRemove);


                uint256 claimableRewards = 0;


                // Some of the rewardsForAmount are actually virtualRewards and can't be claimed.
                // In the event that virtualRewards are greater than actual rewards - claimableRewards will stay zero.
                if ( virtualRewardsToRemove < rewardsForAmount )
                        claimableRewards = rewardsForAmount - virtualRewardsToRemove;


                // Send the claimable rewards
                if ( claimableRewards != 0 )
>                        salt.safeTransfer( wallet, claimableRewards );
```

Once `_decreaseUserShare()` executes, the user waits for the unstake duration to recover their salt.

In the Staking contract, there is another function to cancel their unstaked position via `cancelUnstake()` This function will increase the user share based on what has been unstaked previously through `_increaseUserShare()`. `_increaseUserShare()` refills the `totalRewards` for the pool as well as the `virtualRewards` of the user.

```
        function cancelUnstake( uint256 unstakeID ) external nonReentrant
                {
                Unstake storage u = _unstakesByID[unstakeID];


                require( u.status == UnstakeState.PENDING, "Only PENDING unstakes can be cancelled" );
                require( block.timestamp < u.completionTime, "Unstakes that have already completed cannot be cancelled" );
                require( msg.sender == u.wallet, "Sender is not the original staker" );


                // Update the user's share of the rewards for staked SALT
>               _increaseUserShare( msg.sender, PoolUtils.STAKED_SALT, u.unstakedXSALT, false );


                u.status = UnstakeS
MALICIOUS BORROWER CAN AVOID LIQUIDATION BY UPDATING THE `user.cooldownExpiration` TIMESTAMP THUS INCURRING LOSS OF FUNDS ON THE PROTOCOL
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L154
https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L70-L76
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L107
https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L146-L147
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L64-L71
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L104-L111


# Vulnerability details

## Impact

The `CollateralAndLiquidity.liquidateUser` function is used to liquidate a position which has fallen under the minimum collateral ratio. During the `liquidateUser` function execution the `StakingRewards._decreaseUserShare` function is called to decrease the user's share of collateral as it has been liquidated and they no longer have it, as shown below:

		_decreaseUserShare( wallet, collateralPoolID, userCollateralAmount, true );

As it is evident from the above code snippet the `useCooldown bool is set to true`. Hence the `StakingRewards._decreaseUserShare` function will perform the following check to ensure that the `cool down period has expired`.

		if ( useCooldown ) //@audit-info - check if the cooldown is mentioned
		if ( msg.sender != address(exchangeConfig.dao()) ) // DAO doesn't use the cooldown
			{
			require( block.timestamp >= user.cooldownExpiration, "Must wait for the cooldown to expire" );
			//@audit-info - ensure hte cool down period has passed
			// Update the cooldown expiration for future transactions
			user.cooldownExpiration = block.timestamp + stakingConfig.modificationCooldown();
			}

if the `block.timestamp < user.cooldownExpiration` the transaction will revert and the liquidation won't be executed. 

A malicious borrower can exploit the `user.cooldownExpiration` to revert the liquidation attempts and thus keep his position alive until he can get his collateralization ratio above the `minimumCollateralRatioPercent` or can wait till his collateralization ratio is below < 100 thus incurring loss on the protocol. The malicious borrower can manipulate the `user.cooldownExpiration` to prevent the liquidation as explained below:

1. A malicious borrower calls the `CollateralAndLiquidity.depositCollateralAndIncreaseShare` and deposits collateral (WBTC/WETH) worth of USD 10,000 and borrows USDS 5000. (consider the initialCollateralRatioPercent = 200).
2. This will update the `user.cooldownExpiration` since the `StakingRewards._increaseUserShare` is called. The `user.cooldownExpiration = block.timestamp + stakingConfig.modificationCooldown()` is executed and the `user.cooldownExpiration` is set to one hour from the transaction execution time (block.timestamp).
3. The malicious borrower does not attempt to deposit or withdraw any collateral after this initial transaction and hence after one hour his `user.cooldownExpiration` will b
Incorrect increase of `totalRewards` in `_increaseUserShare` function
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L78-L85


# Vulnerability details


## Issue Description

The `totalRewards` represent the total pending SALT rewards for each poolID. When the `StakingRewards.addSALTRewards` function is called during the global upkeep call, it updates the `totalRewards` value for each pool according to the SALT reward received.

The issue is that the `totalRewards` is incorrectly increased in the `StakingRewards._increaseUserShare` function when a user tries to increase their shares:

```solidity
if (existingTotalShares != 0) // prevent / 0
    {
        // Round up in favor of the protocol.
        uint256 virtualRewardsToAdd = Math.ceilDiv(
            totalRewards[poolID] * increaseShareAmount,
            existingTotalShares
        );

        user.virtualRewards += uint128(virtualRewardsToAdd);
        //@audit should not increase totalRewards
        totalRewards[poolID] += uint128(virtualRewardsToAdd);
    }
```

This is incorrect because virtual rewards `virtualRewardsToAdd` are only used to deduct a portion of the user rewards that they are not allowed to claim as they increase their shares at the given `totalRewards/totalShares` ratio.

By increasing the `totalRewards` value by `virtualRewardsToAdd`, the function is adding virtual rewards that don't exist in the contract yet in the form of SALT tokens. This will make the reward calculation incorrect and compromise the whole shares/rewards system. 

Users who stake afterward will be promised rewards that don't exist and may not be able to claim them later due to insufficient SALT funds in the contract.

## Impact

The incorrect update of `totalRewards` in the `StakingRewards._increaseUserShare` function will lead to bad shares/rewards accounting and result in some users being unable to claim rewards due to insufficient SALT funds in the contract.

## Proof of Concept

Let's take a simple scenario of a user Bob who will deposit 1000 shares in the pool `poolID == 10`.

* Before Bob's call, we had the following states:

Bob shares: `user.userShare = 0`

Bob virtual rewards: `user.virtualRewards = 0`

Pool total shares: `totalShares[poolID] = 8000`

Pool total rewards: `totalRewards[poolID] = 10000`

* When Bob calls the `depositLiquidityAndIncreaseShare` function, it will internally invoke `StakingRewards._increaseUserShare` function to add the 1000 shares.

* After `StakingRewards._increaseUserShare` is called, we get to these states using the formula above:

Bob shares: `user.userShare = 1000`

Bob virtual rewards: `user.virtualRewards = 1250`

Pool total shares: `totalShares[poolID] = 9000`

Pool total rewards: `totalRewards[poolID] = 11250`

* We see here that the `totalRewards` was increased from `10000` to `11250`, but we know that no SALT rewards were added to the contract to reflect that change. These rewards are virtual, having no existence, yet they are recorded as normal rewards.

After Bob's
malicious user can exploit the staking reward system
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L78-L86


# Vulnerability details

## Impact
malicious user increase share right before adding staking salts and decrease share after that and take the rewards .It will be unfair for long term staker .

## Proof of Concept
 Malicious User deposit wbtc/weth liquidity as collateral to increase share
   
     function depositCollateralAndIncreaseShare( uint256 maxAmountWBTC, uint256 maxAmountWETH, uint256 minLiquidityReceived, uint256 deadline, bool useZapping ) external nonReentrant ensureNotExpired(deadline)  returns (uint256 addedAmountWBTC, uint256 addedAmountWETH, uint256 addedLiquidity)
		{
		// Have the user deposit the specified WBTC/WETH liquidity and increase their collateral share
		(addedAmountWBTC, addedAmountWETH, addedLiquidity) = _depositLiquidityAndIncreaseShare( wbtc, weth, maxAmountWBTC, maxAmountWETH, minLiquidityReceived, useZapping );

		emit CollateralDeposited(msg.sender, addedAmountWBTC, addedAmountWETH, addedLiquidity);
		}

user will get rewards based on reward/shares ratio

       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);
		}

Protocol send rewards to staking rewards contract from reward emitter by calling performUpkeep .Protocol reward system is not based on time .This leads to be exploited .

POC
1.Protocol send rewards to staking rewards contract 
2.malicious user see this and they want to increase share before rewards are not reached into staking rewards contract
3.malicious user front run and call deposit collateral & increase share
4.After rewards are now in staking rewards contract ,malicious call withdrawCollateral & claim
5.Malicious only have to wait 1 hr to bypass the check of preventing instant withdraw .

## Tools Used
manual view
## Recommended Mitigation Steps
make sure user have to wait for long enough to prevent from those attacks


## Assessed type

MEV
Incorrect logic for USDS repayments can result in improper liquidation of protocol owned liquidity
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/Liquidizer.sol#L101-L126
https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L115-L135


# Vulnerability details

## Impact

When users repay their borrowed USDS, those tokens are sent to the USDS token address rather than the Liquidizer contract. However, the Liquidizer contract expects this USDS to be part of its balance, and since its not, it has logic to withdraw POL in order to swap into USDS. This results in a direct loss of assets for the DAO due to loss of POL, due to the burning of an improper amount of USDS.

## Proof of Concept

When a user is repaying their USDS 'loan', they will call [`CollateralAndLiquidity:repayUSDS`](https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L115-L135), which is defined as follows:
```solidity
function repayUSDS( uint256 amountRepaid ) external nonReentrant
  {
  require( userShareForPool( msg.sender, collateralPoolID ) > 0, "User does not have any collateral" );
  require( amountRepaid <= usdsBorrowedByUsers[msg.sender], "Cannot repay more than the borrowed amount" );
  require( amountRepaid > 0, "Cannot repay zero amount" );

  // Decrease the borrowed amount for the user
  usdsBorrowedByUsers[msg.sender] -= amountRepaid;

  // Have the user send the USDS to the USDS contract so that it can later be burned (on USDS.performUpkeep)
@>  usds.safeTransferFrom(msg.sender, address(usds), amountRepaid);

  // Have USDS remember that the USDS should be burned
@>  liquidizer.incrementBurnableUSDS( amountRepaid );

  ...
  }
```

Most importantly here, the USDS transferred from the user is send to the USDS token address, while `incrementBurnableUSDS` is called on the liquidizer contract, which increments the `usdsThatShouldBeBurned` state variable. This function call also occurs in the [`CollateralAndLiquidity:liquidateUser`](https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L140-L188) function where it records the total USDS borrow balance which needs to be acquired through swapping the taken user collateral.

Let's now see how this variable is used in the Liquidizer contract. It has the [`Liquidizer:performUpkeep`](https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/Liquidizer.sol#L132-L152) function which is tasked with swapping the taken collateral (e.g. WETH, WBTC) into USDS, then burning the correct amount of USDS. As part of this call it calls the internal function `_possiblyBurnUSDS` which handles the logic for burning USDS, which is defined as follows:
```solidity
function _possiblyBurnUSDS() internal
   {
	...
   uint256 usdsBalance = usds.balanceOf(address(this));
@>   if ( usdsBalance >= usdsThatShouldBeBurned )
      {
      // Burn only up to usdsThatShouldBeBurned.
      // Leftover USDS will be kept in this contract in case it needs to be burned later.
      _burnUSDS( usdsThatShould