xanoutas/solidity-security-findings huggingface dataset:
Lack of overflow protection / low precision can allow attacker to steal SALT rewards
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L57-L92
https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L131-L135
https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L102-L107


# Vulnerability details

## Impact

There is a general issue with the casting of `virtualRewardsToAdd` in `StakingRewards:_increaseUserShare` to the lower precision `uint128`, as this makes it likely that an overflow can happen. Using this, an attacker can deposit a large amount of LP while having their `virtualRewards` not increase at all. This then means that they can steal whatever amount is currently cached in `totalRewards[poolID]`.

## Proof of Concept

As I stated in another submitted issue, there is a general problem in which attackers can snipe newly whitelisted pools to get up to 1% of the emissions rewards (2_000e18 SALT by default). In this attack they will deposit 101 wei of both tokens into the pool. Therefore, we will consider this our starting state - in particular lets say stETH was newly added and this snipe has already occurred. This means theres 101 stETH, 101 WETH, `totalShares[poolID]`=202, and `totalRewards[poolID]`=2_000e18.

Let's now assume userA is depositing WETH & stETH into this pool to steal rewards. Ultimately [`Liquidity:_depositLiquidityAndIncreaseShare`](https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/Liquidity.sol#L102-L107) is run which has the following logic:
```solidity
...
(addedAmountA, addedAmountB, addedLiquidity) = pools.addLiquidity( tokenA, tokenB, maxAmountA, maxAmountB, minLiquidityReceived, totalShares[poolID]);
_increaseUserShare( msg.sender, poolID, addedLiquidity, true );
...
```
The `addedLiquidity` is calculated by `Pools:addLiquidity`, which ultimately calls [`Pools:_addLiquidity`](https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L90-L135), which has the following logic for calculating the amount of LP issued to the user:
```solidity
...
if ( addedAmount0 > addedAmount1)
   addedLiquidity = (totalLiquidity * addedAmount0) / reserve0;
else
   addedLiquidity = (totalLiquidity * addedAmount1) / reserve1;
}
...
```
After this, [`StakingRewards:_increaseUserShare`](https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L57-L92) is used to calculate the `virtualRewards` changes:
```
...
uint256 virtualRewardsToAdd = Math.ceilDiv( totalRewards[poolID] * increaseShareAmount, existingTotalShares );

user.virtualRewards += uint128(virtualRewardsToAdd);
totalRewards[poolID] += uint128(virtualRewardsToAdd);
...
```

Now that we've seen all the relevant equations, let's calculate what deposit of WETH and stETH will allow the attacker to overflow the `virtualRewardsToAdd` calculation to get `user.virtualRewards`=0. We have the following set of equations (added WETH and stETH will be the same):
```
> 202 * addedLiq / 101 = x
> type(uint128).max+1 = 2_000
ManagedWallet.sol will receive eth but funds will be locked forever
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/ManagedWallet.sol#L65


# Vulnerability details

## Impact
Funds in ManagedWallet.sol will be stuck forever

## Proof of Concept
ManagedWallet.sol will receive ETH whenever a confirmation wallet needs to confirm or reject the proposal.

However, the issue is there is no way to transfer or utilize the received ETH, causing ETH to be forever stuck, and not used by anyone.

```solidity
//src/ManagedWallet.sol
// The confirmation wallet confirms or rejects wallet proposals by sending a specific amount of ETH to this contract
    receive() external payable
    	{
    	require( msg.sender == confirmationWallet, "Invalid sender" );

		// Confirm if .05 or more ether is sent and otherwise reject.
		// Done this way in case custodial wallets are used as the confirmationWallet - which sometimes won't allow for smart contract calls.
    	if ( msg.value >= .05 ether )
    		activeTimelock = block.timestamp + TIMELOCK_DURATION; // establish the timelock
    	else
			activeTimelock = type(uint256).max; // effectively never
        }
```
(https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/ManagedWallet.sol#L65)

As seen, confirmationWallet has to send ETH in either confirmation or rejection case. However, there are no methods in ManagedWallet.sol for the received ETH to be used or transferred. Funds will be stuck.

## Tools Used
Manual

## Recommended Mitigation Steps
Transfer ETH received to the intended user such as the protocol wallets.






## Assessed type

Other
Vested Salt tokens not sent to the DAO as required 
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/Upkeep.sol#L223-L228


# Vulnerability details

## Impact
Vested `Salt` tokens will not be sent to the `DAO`. 

## Proof of Concept
Whenever the perform upkeep is called a series of functions are called using try-catch blocks to handle errors. The issue here is that in the `step10()` the comment outlines that: 'Send SALT from the DAO vesting wallet to the DAO (linear distribution over 10 years)'.
```solidity
// 10. Send SALT from the DAO vesting wallet to the DAO (linear distribution over 10 years).
  function step10() public onlySameContract VestingWallet(payable(exchangeConfig.daoVestingWallet())).release(address(salt));
		}
}
```
 but a salt transfer is not implemented inside the function as seen above and  Vested Salt tokens are not going to be sent to the DAO reserve as required. But in the `step11()` a salt transfer is made after getting vested tokens
```solidity
function step11() public onlySameContract {
    uint256 releaseableAmount = VestingWallet(payable(exchangeConfig.teamVestingWallet())).releasable(address(salt));
		// teamVestingWallet actually sends the vested SALT to this contract - which will then need to be sent to the active teamWallet
		VestingWallet(payable(exchangeConfig.teamVestingWallet())).release(address(salt));

		salt.safeTransfer( exchangeConfig.managedTeamWallet().mainWallet(), releaseableAmount );
		}
``` 

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement a transfer  mechanism that will handle the transfer of vested tokens to the DAO. 


## Assessed type

Other
Borrower can frontrun liquidator to avoid liquidation of their insolvent position
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

*NOTE*: This is a different issue from the other one I submit regarding liquidation, this one is a different attack path although the same underlying cause.

The borrower can front run his liquidatable position to prevent liquidation. This will lead to the borrower buying time to repay, recollateralize or delay until price returns to levels where the position is collateralised again

## POC Summary

- When a borrower notices his position is insolvent
- he adds collateral with at most `DUST + 1 wei` (101 wei) amount to activate cooldown
- Liquidator calls `liquidateUser(...)` but it reverts because cooldown is now active
- On the Negative side a borrower can use this to avoid being liquidated or can use this to buy time until he can repay the loan
cooldown can range from 15mins to 6hrs and it is worse yet for higher cooldown periods

## Coded POC

Add the following test case to the `CollateralAndLiquidity.t.sol` file and run `COVERAGE="yes" NETWORK="sep" forge test --mt testLiquidatePositionBorrowerFrontrunsWithCooldown -vv --rpc-url https://rpc.ankr.com/eth_sepolia`

```solidity
function testLiquidatePositionBorrowerFrontrunsWithCooldown() public {

		assertEq( collateralAndLiquidity.numberOfUsersWithBorrowedUSDS(), 0 );

		assertEq(wbtc.balanceOf(address(usds)), 0, "USDS contract should start with zero WBTC");
		assertEq(weth.balanceOf(address(usds)), 0, "USDS contract should start with zero WETH");
		assertEq(usds.balanceOf(alice), 0, "Alice should start with zero USDS");

		// Total needs to be worth at least $2500
		uint256 depositedWBTC = ( 1000 ether *10**8) / priceAggregator.getPriceBTC();
		uint256 depositedWETH = ( 1000 ether *10**18) / priceAggregator.getPriceETH();

        emit log_named_uint("depositedWBTC", depositedWBTC );
        emit log_named_uint("depositedWETH", depositedWETH );

		(uint256 reserveWBTC, uint256 reserveWETH) = pools.getPoolReserves(wbtc, weth);
		assertEq( reserveWBTC, 0, "reserveWBTC doesn't start as zero" );
		assertEq( reserveWETH, 0, "reserveWETH doesn't start as zero" );

		vm.startPrank(alice);
		(,, uint256 aliceInitialAddedLiquidityIncrement) = collateralAndLiquidity.depositCollateralAndIncreaseShare( depositedWBTC, depositedWETH, 0, block.timestamp, false );
        emit log_named_uint("aliceInitialAddedLiquidityIncrement", aliceInitialAddedLiquidityIncrement );
		uint256 maxUSDS = collateralAndLiquidity.maxBorrowableUSDS(alice);
        // emit log_named_uint("initial maxUSDS", maxUSDS );
		assertEq( maxUSDS, 0, "Alice doesn't have enough collateral to borrow USDS" );
When a user’s borrow position becomes insolvent within cooldown period, the position cannot be liquidated
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L70-L76
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L64-L71
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/Liquidity.sol#L107


# Vulnerability details

## Impact

*NOTE*: This is a different issue from the other one I submit regarding liquidation, this one is a different vulnerability although the same underlying cause.

When a borrowed position becomes deliquent during a `cooldown` period, the position cannot be liquidated, this will lead to bad debt in the protocol until the cooldown is over. 

## Vulnerability Details

Currently the `cooldown` period is `1 hr` but it can go as high as `6 hr`. When a user adds liquidity by calling `depositCollateralAndIncreaseShare(...)` in the `CollateralAndLiquidity.sol` contract, an internal call is made to 

`_depositLiquidityAndIncreaseShare(...)` as shown below

```solidity
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 );

		...
		}
```

this in turn makes a call to the `StakingRewards._increaseUserShare(...)` function which executes with `useCoolDown` set to `true`.

```solidity
function _depositLiquidityAndIncreaseShare( IERC20 tokenA, IERC20 tokenB, uint256 maxAmountA, uint256 maxAmountB, uint256 minLiquidityReceived, bool useZapping ) internal returns (uint256 addedAmountA, uint256 addedAmountB, uint256 addedLiquidity)
		{
		...

		// Increase the user's liquidity share by the amount of addedLiquidity.
		// Cooldown is specified to prevent reward hunting (ie - quickly depositing and withdrawing large amounts of liquidity to snipe rewards as they arrive)
		// _increaseUserShare confirms the pool as whitelisted as well.
		_increaseUserShare( msg.sender, poolID, addedLiquidity, true );

		...

		emit LiquidityDeposited(msg.sender, address(tokenA), address(tokenB), addedAmountA, addedAmountB, addedLiquidity);
		}
```

When the user makes a borrow immediately after increasing their shares and the collateralisation decreases, all calls to `CollateralAndLiquidity.liquidateUser(...)` will revert.

## Coded POC

Add the following test case to the `CollateralAndLiquidity.t.sol` file and run `COVERAGE="yes" NETWORK="sep" forge test --mt testLiquidatePositionDuringCooldownWillRevert -vv --rpc-url https://rpc.ankr.com/eth_sepolia`

```solidit
Cooldown in _decreaseUserShare can cause Liquidity to fail
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
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L104-L111
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L57-L71
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L70


# Vulnerability details

## Impact
Liquidation fails because of cooldown expiration of an user. 
The cooldown can be kept engaged all the time if user calls depositCollateralAndIncreaseShare just to trigger the cooldown everytime it passes

## Proof of Concept
The liquidation function in CollateralAndLiquidity can fail if the user Share activity was put in cooldown.
### DOS

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L154
```solidity
function liquidateUser( address wallet ) external nonReentrant
.
.

		_decreaseUserShare( wallet, collateralPoolID, userCollateralAmount, true );
.
.
```

We can see in _decreaseUserShare
```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" );

			// Update the cooldown expiration for future transactions
			user.cooldownExpiration = block.timestamp + stakingConfig.modificationCooldown();
			}
.
.
```

There's a require statement that user must not be in a cooldown now, this will cause the Liquidation to fail

### Enable cooldown

In collateralAndLiquidity

depositCollateralAndIncreaseShare -> _depositLiquidityAndIncreaseShare -> _increaseUserShare

```solidity
function _increaseUserShare( address wallet, bytes32 poolID, uint256 increaseShareAmount, 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" );

			// Update the cooldown expiration for future transactions
			user.cooldownExpiration = block.timestamp + stakingConfig.modificationCooldown();
			}
.
.
```
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L57-L71

User can activate the cooldown using the deposit mechanism
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/stable/CollateralAndLiquidity.sol#L70

## Tools Used
Manual analysis

## Recommended Mitigation Steps
Change the bool for useCooldown in liquidateUser to false from true











## Assessed type

Timing
Users cannot be liquidated during cooldown
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


# Vulnerability details

The [`CollateralAndLiquidity.liquidateUser()`](https://github.com/code-423n4/2024-01-salty/blob/main/src/stable/CollateralAndLiquidity.sol#L154) function calls [`StakingRewards._decreaseUserShare()`](https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L97-L140) to wipe out a user's share in the WBTC/WETH pool that they have used as collateral to borrow USDS. Because the `useCooldown` parameter is set to `true`, the call will fail if the user is currently in the cooldown period.

```solidity
// Liquidate a position which has fallen under the minimum collateral ratio.
// A default 5% of the value of the collateral is sent to the caller, with the rest being sent to the Liquidator for later conversion to USDS which is then burned.
function liquidateUser( address wallet ) external nonReentrant
    {
    require( wallet != msg.sender, "Cannot liquidate self" );

    // First, make sure that the user's collateral ratio is below the required level
    require( canUserBeLiquidated(wallet), "User cannot be liquidated" );

    [...]

    // Decrease the user's share of collateral as it has been liquidated and they no longer have it.
    _decreaseUserShare( wallet, collateralPoolID, userCollateralAmount, true );

    [...]
    }
```

```solidity
// Decrease a user's share for the pool and have any pending rewards sent to them.
// Does not require the pool to be valid (in case the pool was recently unwhitelisted).
function _decreaseUserShare( address wallet, bytes32 poolID, uint256 decreaseShareAmount, bool useCooldown ) internal
    {
    [...]

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

    [...]
    }
```

The cooldown period starts when a user deposits or withdraws collateral. The cooldown period is set in the `StakingConfig` contract to be 1 hour by default (https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingConfig.sol#L31) but can be change to a value between 15 minutes and 6 hours by the DAO (https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingConfig.sol#L85-L99).

A user can avoid liquidation by depositing a minimal amount of collateral. However, when the cooldown period ends the user might need to frontrun a possible liquidation. Depending on the value of their collateral they might be financially incentivized to pay an additional gas fee or MEV bribe to ensure that the liquidation fails.

## Impact

The protocol risks b
Wrong calculation of virtual rewards can cause unfair reward distribution
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

There are multiple issues when calculating virtual rewards in [`StakingRewards._increaseUserShare()`](https://github.com/code-423n4/2024-01-salty/blob/main/src/staking/StakingRewards.sol#L57-L92):

Virtual rewards are not added for the first staker in a pool:

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

```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);
    totalRewards[poolID] += uint128(virtualRewardsToAdd);
    }
```

When the first staker stakes, `existingTotalShares` is 0, so no virtual rewards are added and they get the full reward that has been accumulated so far, giving them a larger reward share than following stakers.

According to the comment, this is a measure to avoid a division by zero. However, the relevant calculation to calculate `virtualRewardsToAdd` shouldn't divide by `existingTotalShares` in the first place. By calculating `virtualRewardsToAdd` as `totalRewards[poolID] * increaseShareAmount / existingTotalShares` (rounding up instead of down), `virtualRewardsToAdd` can be higher than `totalRewards[poolID]` if `increaseShareAmount > existingTotalShares` (which especially can be the case when the pool is new and doesn't have many stakers yet.

Later, `virtualRewardsToAdd` is added to `totalRewards[poolID]`. Because `totalRewards[poolID]` is only decreased when a user unstakes, it is possible that in subsequent calculations `virtualRewardsToAdd` becomes larger than `type(uint128).max`, which causes truncation when `virtualRewardsToAdd` is cast to `uint128` and also can lead to the `user.virtualRewards += uint128(virtualRewardsToAdd)` operation overflowing.

Additionally, `totalRewards[poolID]` can become so large that individual users' reward shares can exceed the contract's SALT balance.

## Impact

The fairness/correctness of the reward distribution is impacted:

* First stakers get a disproportionate share of the reward.
* Users can sometimes not claim their rewards because the reward share that was calculated for them exceeds the contract's SALT balance. However, they can just wait until enought SALT has been distributed to the contract and then withdraw all the SALT. This will happen on a first-come-first-served basis, because there will be not enough SALT to honour every user's claim. An attacker could force this situation by staking specific amounts into a pool, effectively stealing rewards from other users.
* Sometimes deposits might fail due to the `uint128` overflow. However, specific amounts need to be staked for that to happen.

## Proof of Concept

The following test cases can be added to https://github.com/