xanoutas/solidity-security-findings huggingface dataset:
Wrong slippage calculation during `deposit` can cause sandwich attacks in Reth Derivative
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L170-L183


# Vulnerability details

## Vulnerability Details:
Where : [`deposit() - reth derivative`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L170-L183)
When :  [`poolCanDeposit(msg.value)`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L170) starts returning false.

Slippage is calculated using the spot price of the uniswap v3 pool from which swap is being done, which can be easily manipulated for sandwich attacks. 

## Proof of Concept
- Possibility of sandwich attacks, as when user submits  high value transaction in mempool. 
- Attacker keeps track and buys massive amount of RETH from RETH-ETH UNI Pool. 
- As `minOut` is calculated from the same inflated pool, the slippage holds no value under this circumstance. 
```
            uint rethPerEth = (10 ** 36) / poolPrice();

            uint256 minOut = ((((rethPerEth * msg.value) / 10 ** 18) *
                ((10 ** 18 - maxSlippage))) / 10 ** 18);
```
- Reth Derivative contract gets lesser reth in return and [`underlying` in `stake`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L72) function is also inflated, which in turns leads to lesser SafEth being minted to user.
- Attacker immediately sells RETH in the next transaction or directly above the user's transaction and makes profit from the arbitrage. 
 
## Impact
##### These attack has two implications: 
1. User get less SafEth due to sandwich attacks. 
2. Huge losses could occur during [`rebalanceToWeights()`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L138)

## Tools Used
Manual Review

## Recommended Mitigation Steps 
In such scenarios, it is recommended that slippage should not be calculated from the spot price of the pool.
Upgraded Q -> 2 from #763 [1682682097179]
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

Judge has assessed an item in Issue #763 as 2 risk. The relevant finding follows:

 In line 87 there's a check for a pause setting. If staking is currently paused, the operation will fail.
Similar to the edge case described above, line 88 verifies that msg.value != 0. The division in the weighted proportion may round down the amount to 0.
In summary, this means that if any of these conditions during the staking process of the WstEth or SfrxEth derivative isn't met, then the deposit will fail and will block the whole staking and rebalancing processes in the SafEth contract.
Upgraded Q -> 2 from #883 [1682591284215]
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

Judge has assessed an item in Issue #883 as 2 risk. The relevant finding follows:

 This could cause stake() and unstake() to permanently revert for an prolonged period of time, as it is possible for deposit() and withdraw() to revert due to unchecked external conditions:

Reth
The rocket pool DAO can disable deposits.
Withdrawals will fail if rocket pool does not have sufficient ETH.
WstETH
stETH has a daily staking limit, which could cause deposits to fail.
If any of the external conditions above occurs, their respecitve function will be DOSed.

Although an owner can prevent this by setting the affected derivative's weight to 0, this is not a sufficient mitigation as:

This affects the ETH distribution for each derivative, potentially making it unbalanced.
If the DOS is over a short period of time but occurs repeatedly, such as stETH's staking limit, the owner will have to keep adjusting the affected derivative's weight.
Upgraded Q -> 2 from #706 [1682589361421]
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

Judge has assessed an item in Issue #706 as 2 risk. The relevant finding follows:

 [02] USERS CANNOT SET OWN SLIPPAGE WHEN STAKING AND UNSTAKING
Only the owner of the SafEth contract can call the following SafEth.setMaxSlippage function to set maxSlippage that is used in the corresponding derivative contract. This means that users cannot set their own slippage when staking and unstaking.

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L202-L208

    function setMaxSlippage(
        uint _derivativeIndex,
        uint _slippage
    ) external onlyOwner {
        derivatives[_derivativeIndex].setMaxSlippage(_slippage);
        emit SetMaxSlippage(_derivativeIndex, _slippage);
    }
When calling the following SafEth.stake function, derivative.deposit{value: ethAmount}() is called. If the corresponding derivative contract's deposit function is like the Reth.deposit function that swaps the deposited ETH's corresponding WETH amount for the derivative tokens according to maxSlippage, the swapped derivative token amount can be less than expected to the user because she or he cannot specify her or his own maxSlippage against the potential price manipulation during the swap. This can then cause a less-than-expected SafEth token amount to be minted to the user.

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63-L101

    function stake() external payable {
        ...
        uint256 totalStakeValueEth = 0; // total amount of derivatives worth of ETH in system
        for (uint i = 0; i < derivativeCount; i++) {
            uint256 weight = weights[i];
            IDerivative derivative = derivatives[i];
            if (weight == 0) continue;
            uint256 ethAmount = (msg.value * weight) / totalWeight;

            // This is slightly less than ethAmount because slippage
            uint256 depositAmount = derivative.deposit{value: ethAmount}();
            uint derivativeReceivedEthValue = (derivative.ethPerDerivative(
                depositAmount
            ) * depositAmount) / 10 ** 18;
            totalStakeValueEth += derivativeReceivedEthValue;
        }
        // mintAmount represents a percentage of the total assets in the system
        uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice;
        _mint(msg.sender, mintAmount);
        ...
    }
When calling the following SafEth.unstake function, derivatives[i].withdraw(derivativeAmount) is called. If the corresponding derivative contract's withdraw function is like the SfrxEth.withdraw or WstEth.withdraw function that exchanges the withdrawn derivative token amount for ETH according to maxSlippage, the exchanged ETH amount can be less than expected to the user because she or he cannot specify her or his own maxSlippage against the potential price manipulation during the exchange. This can then cause a less-than-expected ETH amount to be transferred to the user. Moreover, when the derivative
Upgraded Q -> 2 from #623 [1682589089611]
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

Judge has assessed an item in Issue #623 as 2 risk. The relevant finding follows:

 [L‑05] Stuck dust in SafEth contract for division
When stake in the contract SafEth some WEIs could be stuck in the contract because the equation uint256 ethAmount = (msg.value * weight) / totalWeight;, in example: ethAmount = (99 * 1) / 100 = 0.99 = 0 => lost 1 wei
Upgraded Q -> 2 from #463 [1682585731493]
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

Judge has assessed an item in Issue #463 as 2 risk. The relevant finding follows:

 [L-08] Swapping in Reth.sol may be sub-optimal
The Reth pool uses the Weth/Reth 0.05% fee pool to swap between weth and reth. I recommend using the balancer pool to swap instead as it has 80M liquidity compared to Uniswap's 5M liquidity, which is better for larger swaps. Balancer pool also has a lower fee percent at 0.04%

https://www.geckoterminal.com/eth/pools/0x1e19cf2d73a72ef1332c882f20534b6519be0276
Upgraded Q -> 2 from #830 [1682357430454]
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

Judge has assessed an item in Issue #830 as 2 risk. The relevant finding follows:

 unstake() in safETH would be blocked if any derivative gets blocked during withdraw The system essentially comes to a halt if any of stETH/frxETH/rETH stops their withdrawal. https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L108

    function unstake(uint256 _safEthAmount) external {
        require(pauseUnstaking == false, "unstaking is paused");
        uint256 safEthTotalSupply = totalSupply();
        uint256 ethAmountBefore = address(this).balance;

        for (uint256 i = 0; i < derivativeCount; i++) {
            // withdraw a percentage of each asset based on the amount of safETH
            uint256 derivativeAmount = (derivatives[i].balance() *
                _safEthAmount) / safEthTotalSupply;
            if (derivativeAmount == 0) continue; // if derivative empty ignore
            derivatives[i].withdraw(derivativeAmount);
        }
See the markdown file with the details of this report here.
Derivative Pool Issue can Lead to Loss User Funds when Unstaking
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L118


# Vulnerability details

### Impact

In all `withdraw()` functions of derivatives, there is no check for sending zero Ether back to the `safEth` contract. It is important to note that the `address(msg.sender).call{value: 0}("")` function returns `true` even when transferring a zero value. 

On the other hand, the `safEth` contract does not perform a zero check when receiving Ether from the derivatives. As a result, when a user calls the `unstake()` function, if there is any issue while withdrawing from an external liquidity staking derivative contract, the user's `safEth` tokens will be burnt without receiving any Ether compensation in return. This situation may lead to a partial or full loss of users' funds.
 
### Proof of Concept

The test attempts to make a regular call to the `stake()` function and `unstake()` function. Afterward, it compares the Ether balance difference, which should be within 1% of the slippage loss.

To emulate potential issues with one of the external liquidity staking derivatives, comment out the line https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L108 in the `withdraw()` function of `Reth.sol` like this:
```solidity
/**
    @notice - Convert derivative into ETH
 */
function withdraw(uint256 amount) external onlyOwner {
    // RocketTokenRETHInterface(rethAddress()).burn(amount);
    // solhint-disable-next-line
    (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
        ""
    );
    require(sent, "Failed to send Ether");
}
```

By commenting out the line responsible for sending Ether back, you can simulate a situation where the derivative fails to return the expected Ether amount. This will help you observe the impact on the `unstake()` function and ensure that the implemented mitigation steps are effective in preventing the burning of `safEth` tokens. 

To test this scenario in Foundry, run the command `forge test -vv --match-test testFailUnstake`.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "ds-test/test.sol";
import "./cheatcodes.sol"; // CheatCodes interface from https://book.getfoundry.sh/cheatcodes/
import "../../contracts/SafEth/SafEth.sol";
import "../../contracts/SafEth/derivatives/Reth.sol";
import "../../contracts/SafEth/derivatives/SfrxEth.sol";
import "../../contracts/SafEth/derivatives/WstEth.sol";
import "../../contracts/interfaces/IDerivative.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

contract SafEthTest is Test {
    uint256 private constant DEFAULT_MAX_SLIPPAGE = 1e16; // 1%
    uint256 private constant DEFAULT_DERIVATIVE_WEIGHT = 1e18;
    uint256 private TEST_DEPOSIT_WEI = 1 ether;
    CheatCodes private cheats;
    address private admin;
    SafEth private safEth;
    Reth private reth;
    SfrxEth private sfrxEth;
    WstEth private wstEth
WstEth.withdraw() improper implementation of slippage check
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol#L56-L67


# Vulnerability details

## Impact
In the current implementation of withdraw(), the _amount  is not controlled by minOut.

Impact: Users can get rekt.


## Proof of Concept

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol#L56-L67


```solidity
function withdraw(uint256 _amount) external onlyOwner {
        IWStETH(WST_ETH).unwrap(_amount);
        uint256 stEthBal = IERC20(STETH_TOKEN).balanceOf(address(this));
        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);
        uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
        // solhint-disable-next-line
        (bool sent, ) = address(msg.sender).call{value: address(this).balance}(
            ""
        );
        require(sent, "Failed to send Ether");
    }
```


## Tools Used

Manual review

## Recommended Mitigation Steps

Consider implementing the minOut check on line 57 as below:

```solidity
require(_amount >= minOut, "Slippage Check");

```

also in SfrxEth.withdraw()
Ether trapped in contract if no derivates exist
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63


# Vulnerability details

# Ether can get stuck in staking contract if there are no derivatives

## Impact

If the `SafEth` contract is deployed and there are no derivatives added to the contract and a user tries to call the [`stake`](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63) function, then this could result in a loss of funds for the user.

## Proof of Concept

The following scenario could occur, which would result in the user having their Ether locked in the contract with no way of recovering their funds:

1. user calls the [`stake`](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63) function and sends 1 ether to stake
2. there are no derivatives set in the contract so [`derivativeCount`](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L84) is 0
3. this means the for-loop will not run and [`totalStakeValueEth`](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L83) will equal 0
4. as a result, the user will be [minted](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L99) 0 `safETH` erc20 tokens
5. the stake function call will successfully go through, resulting in the user sending 1 ether to the contract, but receives 0 tokens

The following test can be written in the `SafEth-Integration.test.ts` file to confirm this:

```
  it("should result in user losing funds, while receiving 0 safEth tokens", async function () {
    // get SafEth contract
    const strategy = await getLatestContract(strategyContractAddress, "SafEth");

    // get Bob's ether balance before staking
    const [bob] = await ethers.getSigners();
    console.log("bob balance before: ", await ethers.provider.getBalance(bob.address));

    // get Bob's safEth token balance before staking
    let bobTokensBefore = await strategy.connect(bob).balanceOf(bob.address);
    console.log("bob tokens before", bobTokensBefore)

    // Bob tries to stake 1 Ether when there are no derivative contracts added
    // get Bob's safEth token balance before staking
    await strategy.connect(bob).stake({value: ethers.utils.parseEther("1")});

    // Bob's Ether balance will have decreased by at least 1 Ether
    console.log("bob balance after: ", await ethers.provider.getBalance(bob.address));

    // Bob's safEth token's balance will remain unchanged, meaning Bob was minted 0 tokens for staking 1 Ether
    let bobTokensAfter = await strategy.connect(bob).balanceOf(bob.address);
    console.log("bob tokens after", bobTokensAfter)

  });
```

## Tools Used

VS Code, Hardhat

## Recommended Mitigation Steps

Consider pausing the staking for the `SafEth` contract in the `initializer` function and only unpause the `SafEth` contract once derivatives have been added.
Zero value used for `sqrtPriceLimitX96`
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L99


# Vulnerability details

## Impact

In `swapExactInputSingleHop` in `Reth.sol` the `sqrtPriceLimitX96` parameter is [set to 0](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L99) which is useful for testing but can lead to price manipulation attacks.

From the [uniswap docs](https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps#swap-input-parameters):

```
In production, this value can be used to set the limit for the price the swap will push the pool to,
which can help protect against price impact or for setting up logic in a variety of price-relevant
mechanisms.
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Consider setting `sqrtPriceLimitX96` to a reasonable value when using the `swapExactInputSingleHop` Uniswap function.
Ether Locked when Attempting to Call `stake()` during Setup
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L48-L56
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63


# Vulnerability details

### Impact

During the period between the deployment of the `SafEth` contract and the addition of derivatives, there is a possibility for users to send Ether to the contract using the `stake() payable` function. In this scenario, the funds will become locked and inaccessible, resulting in a potential loss of user funds.

### Proof of Concept
Testing this scenario in Foundry with command `forge test -vv --match-test testFailEtherLockedOnStake`. The test checks if Ether becomes locked in the SafEth contract when staking before derivatives are added.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import "ds-test/test.sol";
import "./cheatcodes.sol"; // CheatCodes interface from https://book.getfoundry.sh/cheatcodes/
import "../../contracts/SafEth/SafEth.sol";
import "../../contracts/SafEth/derivatives/Reth.sol";
import "../../contracts/SafEth/derivatives/SfrxEth.sol";
import "../../contracts/SafEth/derivatives/WstEth.sol";
import "../../contracts/interfaces/IDerivative.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

contract SafEthTest is Test {
    uint256 private constant DEFAULT_MAX_SLIPPAGE = 1e16; // 1%
    uint256 private constant DEFAULT_DERIVATIVE_WEIGHT = 1e18;
    uint256 private TEST_DEPOSIT_WEI = 1 ether;
    CheatCodes private cheats;
    address private admin;
    SafEth private safEth;
    Reth private reth;
    SfrxEth private sfrxEth;
    WstEth private wstEth;
    mapping(uint256 => IDerivative) private derivatives;

    // Can receive unstaked Ether
    receive() external payable {}

    function setUp() public {
        // Test on mainnet fork
        cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
        cheats.createSelectFork("mainnet");
        // Set proxy owner address
        admin = vm.addr(1337);

        // safEth token deploy
        safEth = SafEth(
            payable(
                new TransparentUpgradeableProxy(
                    address(new SafEth()),
                    admin,
                    abi.encodeWithSelector(
                        SafEth.initialize.selector,
                        "Asymmetry Finance ETH",
                        "safETH"
                    )
                )
            )
        );  

        // Don't deploy any derivatives 
    }

    function testFailEtherLockedOnStake() public {
        // Add Ether to the current contract's balance
        vm.deal(address(this), TEST_DEPOSIT_WEI);
        // Stake Ether in the safEth contract using the intended stake() function
        safEth.stake{value: TEST_DEPOSIT_WEI}();

        // Verify that no safEth tokens were minted in response to the Ether deposit
        assertTrue(safEth.balanceOf(address(this)) != 0, "No safEth minte
deposit function of WstEth not working correctly
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/a8dd9399565ac608860dcadd7b16ff04aee06cb7/contracts/SafEth/derivatives/WstEth.sol#L73-L81


# Vulnerability details

## Impact
The deposit function doesnt seem to work at all, or correctly at all. Its missing some code, etc.
Please see my natspec notes below for more clarity:

Apologies, this is 10mins before contest ends, so I'm just going to copy my NatSpec notes here:

    function deposit() external payable onlyOwner returns (uint256) {
        uint256 wstEthBalancePre = IWStETH(WST_ETH).balanceOf(address(this)); /// @audit checks the token balance in this contract
        // solhint-disable-next-line
        (bool sent, ) = WST_ETH.call{value: msg.value}(""); /// @audit sends ETH to the address represented by this state constant: WST_ETH
        require(sent, "Failed to send Ether"); /// @audit LOW RISK: missing code which converts the sent ETH into wstETH probably via IWStETH interface, and then sends it to this contract
        uint256 wstEthBalancePost = IWStETH(WST_ETH).balanceOf(address(this)); /// @audit LOW RISK: checks the token balance in this contract, expecting a change, but no ERC20 token was deposited into this contract yet...
        uint256 wstEthAmount = wstEthBalancePost - wstEthBalancePre; /// @audit this will always be 0 until above is fixed
        return (wstEthAmount);
    }


## Proof of Concept

## Tools Used
Manual only.

## Recommended Mitigation Steps
Complete the code as per my @audit notes.
withdraw function of WstEth derivative contract probably sends wrong ETH amount back to owner contract/user
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/a8dd9399565ac608860dcadd7b16ff04aee06cb7/contracts/SafEth/derivatives/WstEth.sol#L63


# Vulnerability details

## Impact
Sends the wrong amount of ETH back to owner contract/user. In most cases, probably way too much, it sends the entire contract ETH balance.
All the other calculations in the withdraw function seem correct, from what can be seen, but, as is the case with most/all of the other 2 derivative contracts as well as with the SafEth contract(if I remember correctly), they too suffer from this same problem. They dont send the correct ETH amount due to shared contract balances, as well as race-condition vulnerabilities, but especially because there is no accounting at the user level, except for the safEth token, which is not enough accounting at all whatsoever.

## Proof of Concept

## Tools Used
Manual only.

## Recommended Mitigation Steps
user to balance mappings at least. 
eliminate race-condition vulnerabilities.
But its incorrect to send entire contract balance, unless each user had their own contract instance. Not the case, I checked. 
Therefore the users share the contract balances. And sending entire contract balance to one user, is logically incorrect.