xanoutas/solidity-security-findings huggingface dataset:
Incorrect call to `incrementBurnableUSDS` in `repayUSDS` will lead to loss of DAO POL
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L124-L128


# Vulnerability details

## Issue Description

The `repayUSDS` function transfers the repaid USDS directly to the `USDS` contract (so that it can later be burned during the `performUpkeep` call), but the function also records (remembers) the USDS amount in the liquidizer contract by calling `incrementBurnableUSDS`:

```solidity
function repayUSDS(uint256 amountRepaid) external nonReentrant {
    ...

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

    //@audit USDS was not transferred to the liquidizer => shouldn't increment liquidizer.usdsThatShouldBeBurned
    // Have the liquidizer remember that the USDS should be burned
    liquidizer.incrementBurnableUSDS(amountRepaid);

    ...
}
```

The call to `liquidizer.incrementBurnableUSDS` will cause the incorrect increment of `usdsThatShouldBeBurned`. This variable is supposed to indicate the amount of USDS that should be sent to the `USDS` contract to get burned.

But in our case here, the repaid amount was already transferred to the `USDS` contract, so by incrementing `usdsThatShouldBeBurned`, we basically accounted twice for the same amount. As a result, we will always have `usdsThatShouldBeBurned` greater than the actual liquidizer balance `usds.balanceOf(liquidizer)`.

This issue will cause an incorrect behavior of `_possiblyBurnUSDS()` function, as the liquidizer contract state wrongly indicates that a loss has occurred (bad debt) `usdsThatShouldBeBurned > usds.balanceOf(liquidizer)`, which leads to draining more POL from the DAO than necessary (this is due to the fact that `usdsThatShouldBeBurned` doesn't really reflect the correct amount of USDS that should be burned).

```solidity
function _possiblyBurnUSDS() internal {
    // Check if there is USDS to burn
    if (usdsThatShouldBeBurned == 0) return;

    uint256 usdsBalance = usds.balanceOf(address(this));
    //@audit usdsThatShouldBeBurned will always be greater than usdsBalance
    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(usdsThatShouldBeBurned);
        usdsThatShouldBeBurned = 0;
    } else {
        //@audit will always drain more funds from POL
        // The entire usdsBalance will be burned - but there will still be an outstanding balance to burn later
        _burnUSDS(usdsBalance);
        usdsThatShouldBeBurned -= usdsBalance;

        // As there is a shortfall in the amount of USDS that can be burned, liquidate some Protocol Owned Liquidity and
        // send the underlying tokens here to be swapped to USDS
        dao.withdrawPOL(salt, usds, PERCENT_POL_TO_WITHDR
`Pools._adjustReservesForSwap` calcluates amountOut incorrectly.
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`Pools._adjustReservesForSwap` is used to calculate `amountOut` given `amountIn`, but the current implementation isn't correct which will cause the pool imbalance.

## Proof of Concept
[Pools._adjustReservesForSwap](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/pools/Pools.sol#L235-L276) is supposed to implemente the algorithm as described in comments: `amountOut=r1*amountIn/(r0+amountIn)`.
But the implementation is not correct.
```solidity
235     function _adjustReservesForSwap( IERC20 tokenIn, IERC20 tokenOut, uint256 amountIn ) internal returns (uint256 amountOut)
236         {
237         (bytes32 poolID, bool flipped) = PoolUtils._poolIDAndFlipped(tokenIn, tokenOut);
238
239         PoolReserves storage reserves = _poolReserves[poolID];
240         uint256 reserve0 = reserves.reserve0;
241         uint256 reserve1 = reserves.reserve1;
242
243         require((reserve0 >= PoolUtils.DUST) && (reserve1 >= PoolUtils.DUST), "Insufficient reserves before swap");
244
245                 // Constant Product AMM Math
246                 // k=r0*r1                                                                                                                                      �~@� product of reserves is constant k
247                 // k=(r0+amountIn)*(r1-amountOut)                                                       �~@� add some token0 to r0 and swap it for some token1 which is removed from r1
248                 // r1-amountOut=k/(r0+amountIn)                                                         �~@� divide by (r0+amountIn) and flip
249                 // amountOut=r1-k/(r0+amountIn)                                                         �~@� multiply by -1 and isolate amountOut
250                 // amountOut(r0+amountIn)=r1(r0+amountIn)-k             �~@� multiply by (r0+amountIn)
251                 // amountOut(r0+amountIn)=r1*r0+r1*amountIn-k   �~@� multiply r1 by (r0+amountIn)
252                 // amountOut(r0+amountIn)=k+r1*amountIn-k                       �~@� r0*r1=k (from above)
253                 // amountOut(r0+amountIn)=r1*amountIn                                   �~@� cancel k
254                 // amountOut=r1*amountIn/(r0+amountIn)                          �~@� isolate amountOut
255
256                 // See if the reserves are flipped in regards to the argument token order
257         if (flipped)
258                 {
259                         reserve1 += amountIn;
260                         amountOut = reserve0 * amountIn / reserve1;
261                         reserve0 -= amountOut;
262                 }
263         else
264                 {
265                         reserve0 += amountIn;
266                         amountOut = reserve1 * amountIn / reserve0;
267
Frontrunning Liquidation can help a bad debtor keep his collateral for longer periods than desired
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L140-L188
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L97-L111


# Vulnerability details


## Impact
Liquidations can be delayed as well adding more collateral can be delayed which can lead to de-pegging usds

## Proof of Concept

The function liquidateUser calls \_decreaseUserShare as can be seen from below snippet.

```solidity
	function liquidateUser( address wallet ) external nonReentrant
		{
        ...
		// 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 ); ///@audit ^ calls _decreaseUserShare
        ....
        }
```

\_decreaseUserShare function in turn checks for liquidity cooldown but it can be manipulated easily by adding some amount of collateral just above the DUST i.e 100. So a malicious debtor can postpone his liquidation for an hour. This can be harmful for usds in a flash crash situation where each hour he gets delayed the more loss the project takes. Leading to de - pegging of the stable coin.

```solidity
	    function _decreaseUserShare( address wallet, bytes32 poolID, uint256 decreaseShareAmount, bool useCooldown ) internal
		{
        ...
		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" ); ///<-@audit cooldown check 
			...
			}
        ...
        }
```

Conversly someone might not be able to add enough collateral to his loan in time.

Here's a coded poc-

```solidity
		function setupSaltWethPool() public {
            vm.startPrank(DEPLOYER);
            salt.approve(address(collateralAndLiquidity), 1000 ether);
            weth.approve(address(collateralAndLiquidity), 1 ether);
            (,,uint addedLiq)=collateralAndLiquidity.depositLiquidityAndIncreaseShare(salt,weth,1000 ether, 1 ether, 1001 ether,block.timestamp,false);
            assertEq(addedLiq, 1001 ether);
            vm.stopPrank();
        }

        function setupSaltWbtcPool() public {
            vm.startPrank(DEPLOYER);
            salt.approve(address(collateralAndLiquidity), 16000 ether);
            wbtc.approve(address(collateralAndLiquidity), 1e8);
            collateralAndLiquidity.depositLiquidityAndIncreaseShare(salt,wbtc,16000 ether, 1e8, 0,block.timestamp,false);
            vm.stopPrank();
        }

        function testBadDebt6() pub
user can claim same rewards multiple times and drain protocol of rewards and funds
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
loss of funds for protocol and can claim other user's rewards

## Proof of Concept
 the state variable `_userShareInfo[msg.sender]` is not update after the transfer of rewards

```javascript

  function claimAllRewards( bytes32[] calldata poolIDs ) external nonReentrant returns (uint256 claimableRewards)
    	{
		mapping(bytes32=>UserShareInfo) storage userInfo = _userShareInfo[msg.sender];

		claimableRewards = 0;
		for( uint256 i = 0; i < poolIDs.length; i++ )
			{
			bytes32 poolID = poolIDs[i];

			uint256 pendingRewards = userRewardForPool( msg.sender, poolID );

			// Increase the virtualRewards balance for the user to account for them receiving the rewards without withdrawing
			userInfo[poolID].virtualRewards += uint128(pendingRewards);

			claimableRewards += pendingRewards;
			}

		if ( claimableRewards > 0 )
			{
			// Send the actual rewards
			salt.safeTransfer( msg.sender, claimableRewards );

			emit RewardsClaimed(msg.sender, claimableRewards);
			}
    	}

```
## Tools Used

manual

## Recommended Mitigation Steps
update user state variables for rewards that are claimed to zero


## Assessed type

Error
Blacklisted user may prevent ` claimAllRewards()`
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If one user becomes blacklisted or otherwise cannot be transferred funds in any of the rewards tokens or the deposit token then they will not be able to call get token reward because transaction will always revert.

## Proof of Concept
 if a user has been blacklisted (in certain ERC20 tokens) or if a token is paused or there is an attack and the token is stuck.

## Tools Used
manual

## Recommended Mitigation Steps
have a `withdraw` function that will allow a user to claim their own reward
not inside a for loop. claim for separate pool id's


## Assessed type

DoS
Adding liquidity with zapping while providing only one of the tokens can cause extreme slippage that results in liquidity being drained from Salty
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
1. When adding liquidity to a Salty pool using the [_depositLiquidityAndIncreaseShare](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/Liquidity.sol#L83C11-L83C44) the LP can choose to use zapping - swapping between TokenA and TokenB to match the added liquidity ratio to the current reserves ratio. The zapping swap causes some price slippage in the pool which is accounted for in the zapping amount calculation in [_dualZapInLiquidity](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/Liquidity.sol#L62). This is done by finding a swap amount that will match the added liquidity ratio with the **post-swap** pool reserves ratio. 

2. The code comment for the [_dualZapInLiquidity](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/Liquidity.sol#L62) function reads: *"Deposit an arbitrary amount of one or both tokens into the pool and receive liquidity corresponding the the value of both of them. As the ratio of tokens added to the pool has to be the same as the existing ratio of reserves, some of the excess token will be swapped to the other."* indicating that the function supports any combination of token amounts, including supplying only one of them.

2. When adding one sided liquidity, especially in large amounts compared to existing liquidity, this slippage can be extreme and affect the pool price in hundreds of percents.

3. Because of the atomic automatic arbitrage (AAA) employed for the ZAP swaps, the resulting slippage affects all pools that participate in the arbitrage (in the POC example: wbtc/weth, wbtc/dai and dai/weth are all affected by a liquidity addition to weth/dai).

4. The resulting extreme slippage can then be exploited by arbitrageurs who buy the underpriced token on Salty to sell on external exchanges, profiting on the expense of Salty's liquidity providers. (AAA will not prevent this, as shown in the POC, because it doesn't affect the trade on which it is applied).

5. The root cause of this is the lack of an option to specify acceptable slippage limits when adding liquidity using zapping.

## Proof of Concept
The following POC shows how adding liquidity to the Weth/Dai pool using ZAP (after an initial amount of liquidity had been added at a reasonable price ratio) can cause significant slippage on the weth/dai, weth/wbtc and wbtc/dai pools.

To run: add the following functions to '2024-01-salty\src\staking\tests\Liquidity.t.sol' and run ``COVERAGE="yes" NETWORK="sep" forge test -vv --rpc-url YOUR_RPC_URL --match-test "testInitialLidquidity"``

```solidity
function testZapSlippage() public {
    //deposit initial liquidity to the weth/dai pool with resonable ratio
    addLiquidityForUser(alice,weth, dai,
LPs can be exploited by frontrunning liquidity additions to a new pool with an initial deposit that creates a distorted initial price
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

# LPs can be exploited by frontrunning liquidity additions to a new pool with an initial deposit that creates a distorted initial price

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


## Impact
### High Level
Salty pools can only be initialized after they are activated (there is no option to kickstart liquidity atomically with the pool creation/activation as is the case with most Dexes). An exploiter can frontrun other LPs and add initial liquidity with a distorted price for a low cost (by adding very small amounts at the desired ratio). The following LP transactions will add liquidity roughly at the ratio the exploiter set (with larger amounts) and the exploiter can then backrun them with a trade that extracts most of the added liquidity due to the abnormal price. 

### In Detail
1. Salty pools can only have liquidity added to them is a separate transaction after they are created or activated. This applies both to the initial pools (SALT/WBTC,
SALT/WETH,SALT/USDS,WBTC/USDS,WBTC/DAI,WBTC/WETH,WETH/USDS,WETH/DAI,USDS/DAI) and to Dao whitelisted pools.
2. For the initial pools (whitelisted by the Deployer), liquidity can only be added after [BootstrapBallot.finalizeBallot](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/launch/BootstrapBallot.sol#L69) is called. This is because the [addLiquidity](https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/pools/Pools.sol#L143) function requires the exchange to be live, which happens after the bootstrapBallot if finalized.
3. For Dao whitelisted pools liquidity can only be added  after the whitelisting ballot is accepted (through a finalyzeBallot call that can be made by anyone).
4. The above opens the door to an exploit that involves setting the first liquidity in a pool as described bellow:

#### Exploit Scenario: (demonstrated in the POC below)
1. Exploiter monitors mempool for the ballot finalize tx, and once called, mints a very small amount of USDS (roughly 10100111wei needed for the exploit) and looks for the first LP transaction on one of the pools, for example USDS/DAI. 
2. An LP sends a transaction to add liquidity to the pool, sending roughly equal max amounts of USDS/DAI (assuming the peg is roughly in tact), for the sake of example 10000*10^18 of each.
3. Exploiter frontruns the LPs TX with their own tx that adds USDS/Dai liquidity at a ratio of 101wei (to pass the minimum DUST requirement) to 10^21 Dai. Since the addLiquidity function accepts the first deposit ratio as is, the initial USDS/Dai price is set as 1:10^16.
4. The LPs tx is executed, and since the addLiquidity logic is to take as much as possible from maxTokenA and maxTokenB to match the current ratio, the l
DoS: Blacklisted user may prevent `  withdraw `
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
withdraw will revert , funds will be locked in the contract. 

## Proof of Concept
usdc or usdt has a blacklist function that can cause blacklisted tokens not to
Transfer. Token can still get blacklisted after they are deposited. 

## Tools Used

manual

## Recommended Mitigation Steps
check if user is not blacklisted .Consider allowing `withdraw() ` to be called after a set period of time after the pool end if most users have withdrawn or some similar criteria.




## Assessed type

DoS
User can escape liquidation for a time period he decides
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L140
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L104-L111


# Vulnerability details

## Summary

Users can escape liquidation by front-running the liquidation transaction and adding very little collateral.

## Vulnerability Details

When trying to liquidate a user we decrease his shares and in that `CollateralAndLiquidity::_decreaseUserShare` function we have a cooldown argument that is set to `true` in the `liquidateUser` function. This means that a user has to wait a default of 1 hour before being able to do other actions in that contract. This can be exploited by the user:
- A user sees a liquidation transaction in the mempool that will remove his collateral
- He front-runns the liquidation transaction by adding collateral that has very little value (just above the Dust amount which is 100 wei)
- Now he is in the cooldown period of 1 hour
- The liquidation transaction goes through but it reverts because the user isn't allowed to perform any actions in his cooldown period.
- The user can keep doing this as long as he wants

## Proof of Concept
Add this test in `2024-01-salty\src\stable\tests\CollateralAndLiquidity.t.sol`
Run `COVERAGE="yes" NETWORK="sep" forge test --match-test testEscapingLiquidation -vvv --rpc-url (your rpc url)`

    function testEscapingLiquidation() public {
        // BTC price is $40,000
        // ETH price is $2,000
        vm.startPrank(DEPLOYER);
        forcedPriceFeed.setBTCPrice(40000 ether);
        forcedPriceFeed.setETHPrice(2000 ether);
        vm.stopPrank();

        // The first user that deposited (not important it is just to keep enough liquidity after liquidation)
        vm.prank(charlie);
        collateralAndLiquidity.depositCollateralAndIncreaseShare(1 * 10 ** 8, 20 ether, 0, block.timestamp, false);

        // Alice deposits 1WBTC and 20ETH because the ratio is 1:20
        // And then she borrows the max amount and is 200% overcollateralized
        // Alice deposited a total of $80k collateral and borrowed $40k
        vm.startPrank(alice);
        collateralAndLiquidity.depositCollateralAndIncreaseShare(1 * 10 ** 8, 20 ether, 0, block.timestamp, false);

        uint256 maxUSDS = collateralAndLiquidity.maxBorrowableUSDS(alice);
        collateralAndLiquidity.borrowUSDS(maxUSDS);

        vm.stopPrank();

        vm.warp(block.timestamp + 60 * 60 + 1);

        // Can be used to test liquidation by reducing BTC and ETH price.
        // Original collateral ratio is 200% with a minimum collateral ratio of 110%.
        // So dropping the prices by 46% should allow positions to be liquidated and still
        // ensure that the collateral is above water and able to be liquidated successfully.
        // Now 1WBTC is worth $21,600 1WETH = $1,080
        // Alice's collateral is now worth $43,200 and she has $40k borrowed
        // This leaves Alice 1