xanoutas/solidity-security-findings huggingface dataset:
Precision loss in stake function affects share calculation
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-L101


# Vulnerability details

The `stake` function in `SafEth` performs divisions before multiplications, which incur in unnecesary precission loss errors, along with other divisions that could be avoided.

## Impact

Stakers receive less `SafEth` shares than expected if calculated in a way to  minimize precision errors.

## Proof of Concept

There are several blocks on the `stake` function that could be improved to reduce precision loss.

[Link to stake function](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63-L101)

On the `for` block, the `underlyingValue` is divided by `10 ** 18` for each derivative. This number can be divided just once, to minimize precision loss from each division.

```solidity
for (uint i = 0; i < derivativeCount; i++)
    underlyingValue +=
        (derivatives[i].ethPerDerivative(derivatives[i].balance()) *
            derivatives[i].balance()) /
        10 ** 18; // @audit
```

Later the `underlyingValue` is used to calculate the `preDepositPrice`, and with that the `mintAmount`:

```solidity
uint256 totalSupply = totalSupply();
uint256 preDepositPrice; // Price of safETH in regards to ETH
if (totalSupply == 0)
    preDepositPrice = 10 ** 18; // initializes with a price of 1
else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;

// ...

uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice;
```

There's a division before multiplication covered on the `mintAmount` calculation, that can be avoided as demonstrated here to reduce precision loss:

```soldity
uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice;
uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / ((10 ** 18 * underlyingValue) / totalSupply);
uint256 mintAmount = totalStakeValueEth * totalSupply / underlyingValue;
```

Same thing happens with `totalStakeValueEth` which covers another division before multiplication. And also dividing multiple times instead of just once:

```solidity
uint256 totalStakeValueEth = 0; // total amount of derivatives worth of ETH in system
for (uint i = 0; i < derivativeCount; i++) {
    // ...

    uint derivativeReceivedEthValue = (derivative.ethPerDerivative(
        depositAmount
    ) * depositAmount) / 10 ** 18; // @audit
    totalStakeValueEth += derivativeReceivedEthValue;
}

uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice;
```

## Tools Used

Manual review

## Recommended Mitigation Steps

Refactor the `stake` function to reduce precision loss by avoiding divisions before multiplications, and minimize errors by dividing only once when possible.

Here's a suggested refactoring, moving divisions to the end of the calculation, and simplifying factors that cancel each other like `10 ** 18 / 10 ** 18`.

```diff
    function stake() external payable {
        require(pauseStaking == false, "staking is paused")
Slippage is not controllable by user
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details




## Impact
User may be unable to unstake if any of the derivative tokens experiences volatility.
User may lose more funds than he would be willing to due to slippage when staking.

## Proof of Concept
[`maxSlippage` is set exclusively by the owner of `SafEth`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L202-L208).
This slippage might be higher than a prospective staker would tolerate, preventing him from staking.
If the price of a derivative token is decreasing it may incur a greater slippage than `maxSlippage`. This makes unstaking impossible and the stakeholder will be unable to stop loss his assets.

## Recommended Mitigation Steps
Let the staker/unstaker specify his own max slippage, preferably for each derivative individually.
_amount parameter in function ethPerDerivative() not used in any of the 3 derivative contracts, but must be used, no exceptions
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/b3861c62e22b4c8df2a19d25cad8d98eaaf23a9e/contracts/SafEth/derivatives/Reth.sol#L211-L216
https://github.com/code-423n4/2023-03-asymmetry/blob/a8dd9399565ac608860dcadd7b16ff04aee06cb7/contracts/SafEth/derivatives/SfrxEth.sol#L111-L117
https://github.com/code-423n4/2023-03-asymmetry/blob/a8dd9399565ac608860dcadd7b16ff04aee06cb7/contracts/SafEth/derivatives/WstEth.sol#L86-L88


# Vulnerability details

## Impact
Not sure, somewhere between medium and high. But impacts the entire protocol in terms of derivatives, users' deposits/withdraws, etc.

## Proof of Concept

## Tools Used
Manual only.

## Recommended Mitigation Steps

For Reth.sol contract:
https://github.com/code-423n4/2023-03-asymmetry/blob/b3861c62e22b4c8df2a19d25cad8d98eaaf23a9e/contracts/SafEth/derivatives/Reth.sol#L211-L216

FIXED CODE:
getEthValue(10 ** 18) means only 1 rETH token passed as parameter value. Incorrect. Should be getEthValue(_amount * 10 ** 18)

For SfrxEth contract:
https://github.com/code-423n4/2023-03-asymmetry/blob/a8dd9399565ac608860dcadd7b16ff04aee06cb7/contracts/SafEth/derivatives/SfrxEth.sol#L111-L117

FIXED CODE:
        uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets(
            _amount * 10 ** 18
        );

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

FIXED CODE:
        function ethPerDerivative(uint256 _amount) public view returns (uint256) {
        return IWStETH(WST_ETH).getStETHByWstETH(_amount * 10 ** 18);
    }
Reentrancy attack vulnerability in derivative contract's deposit function
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

There is a vulnerability in the stake() function of the SafEth.sol contract (lines 70-83). It appears to be vulnerable to reentrancy attacks. This vulnerability arises because the deposit() function can be invoked multiple times within a single transaction. As a result, an attacker can repeatedly call the stake() function to deposit and withdraw safEth tokens from the derivative contract within a single transaction. This recursive calling can enable the attacker to gain safEth tokens that are not backed by actual ETH, leading to a distorted valuation. The attacker can then redeem these safEth tokens for ETH. It is crucial to add reentrancy guards to the deposit() and stake() functions to prevent these types of attacks.
Funds are lost if staked before derivatives are added
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details



## Impact
Funds are lost if staked before derivatives are added.

## Proof of Concept
`SafEth` is not [initialized](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L48-L56) with derivatives and `pauseStaking` is by default `false`.
Calling [`SafEth.stake()`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L63-L101) before derivatives are added is therefore possible. But then the paid ether is not deposited in any derivatives and no shares are minted, so the ether is just lost to the contract.

```solidity
function stake() external payable {
    require(pauseStaking == false, "staking is paused"); // @audit pauseStaking == false by default 
    require(msg.value >= minAmount, "amount too low");
    require(msg.value <= maxAmount, "amount too high");

    uint256 underlyingValue = 0;

    // Getting underlying value in terms of ETH for each derivative
    for (uint i = 0; i < derivativeCount; i++)
        underlyingValue +=
            (derivatives[i].ethPerDerivative(derivatives[i].balance()) *
                derivatives[i].balance()) /
            10 ** 18;

    uint256 totalSupply = totalSupply();
    uint256 preDepositPrice; // Price of safETH in regards to ETH
    if (totalSupply == 0) // @audit this will be the case before derivatives are added as shares will not be minted
        preDepositPrice = 10 ** 18; // initializes with a price of 1
    else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;

    uint256 totalStakeValueEth = 0; // total amount of derivatives worth of ETH in system
    for (uint i = 0; i < derivativeCount; i++) { // @audit before derivatives are added derivativeCount == 0 so loop is skipped
        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; // @audit totalStakeValueEth remains 0
    _mint(msg.sender, mintAmount); // @audit so mintAmount == 0 and no shares are minted
    emit Staked(msg.sender, msg.value, mintAmount);
}
```

## Recommended Mitigatio
Lost remainder from sum of fractions
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L84-L96
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L147-L153


# Vulnerability details



## Impact
Not all of `msg.value` is `stake()`-d. Not all of rebalanced amount is redeposited.

## Proof of Concept
In `SafEth.stake()` the ether to be staked is distributed to each derivative by its weight:
```solidity
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;
}
```
However, `ethAmount` is rounded down for each derivative in the division. Thus, the sum of all `ethAmount` may be less than `msg.value`, the remainder of which is just left in the contract and not deposited in any derivative.

The same issue obtains in `SafEth.rebalanceToWeights()` at L147-L153:
```solidity
for (uint i = 0; i < derivativeCount; i++) {
    if (weights[i] == 0 || ethAmountToRebalance == 0) continue;
    uint256 ethAmount = (ethAmountToRebalance * weights[i]) /
        totalWeight;
    // Price will change due to slippage
    derivatives[i].deposit{value: ethAmount}();
}
```

## Recommended Mitigation Steps
Deposit the entire remainder of `msg.value` for the last derivative instead of calculating its weighted share.
Incorrect minOut calculation in WstEth.withdraw()
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#L60


# Vulnerability details

## Impact
Final value of minOut will be lower than expected. It will lead to higher slippage value. User might loose  funds.

## Proof of Concept
1.The minOut is expected to be 1% less than (stEthBal * ethPerDerivative)
2.But ethPerDerivative is not included in the calcucation resulting in lower minOut value than expected

## Tools Used
Manual code review

## Recommended Mitigation Steps
Modify the calculation to -
`uint256 minOut = (((ethPerDerivative(_amount) * stEthBal) / 10 ** 18) * (10 ** 18 - maxSlippage)) / 10 ** 18;`

The `_amount` param in `ethPerDerivative(_amount)` is irrelevant in this case.
Trades of derivative tokens are performed with sub-optimal `minAmount` due to precission loss in deposit and withdraw functions
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L171-L185
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/SfrxEth.sol#L74-L82


# Vulnerability details

The `minOut` value is used as the minimum amount of tokens expected to get when a trade is made. Setting a lower value means that it can accept less tokens than it should do. This is due to a precision loss error.

## Impact

Trades can happen with a lower output amount than expected, receiving less derivative Ether for the protocol, and the users.

## Proof of Concept

The `deposit` function in the `Reth` derivative contract has a precision loss when calculating `minOut`, as it performs divisions before multiplications:

```solidity
// File: contracts/SafEth/derivatives/Reth.sol

uint rethPerEth = (10 ** 36) / poolPrice();

uint256 minOut = ((((rethPerEth * msg.value) / 10 ** 18) *
    ((10 ** 18 - maxSlippage))) / 10 ** 18);

IWETH(W_ETH_ADDRESS).deposit{value: msg.value}();
uint256 amountSwapped = swapExactInputSingleHop(
    W_ETH_ADDRESS,
    rethAddress(),
    500,
    msg.value,
    minOut
);

return amountSwapped;
```

[Link to code](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L171-L185)

The `withdraw` function in the `SfrxEth` derivative contract has a precision loss when calculating `minOut`, as it also performs divisions before multiplications:

```solidity
// File: contracts/SafEth/derivatives/SfrxEth.sol

function withdraw(uint256 _amount) external onlyOwner {
    // ...

    uint256 minOut = (((ethPerDerivative(_amount) * _amount) / 10 ** 18) *
        (10 ** 18 - maxSlippage)) / 10 ** 18;

    IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).exchange(
        1,
        0,
        frxEthBalance,
        minOut
    );

    // ...
}

function ethPerDerivative(uint256 _amount) public view returns (uint256) {
    uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets(
        10 ** 18
    );
    return ((10 ** 18 * frxAmount) /
        IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle());
}
```

[Link to code](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/SfrxEth.sol#L74-L82)

## Tools Used

Manual review

## Recommended Mitigation Steps

Avoid divisions before multiplications to minimize precision loss errors.

Suggested modifications after reorganizing the calculations and canceling common factors:

```diff
// File: contracts/SafEth/derivatives/Reth.sol

- uint rethPerEth = (10 ** 36) / poolPrice();

- uint256 minOut = ((((rethPerEth * msg.value) / 10 ** 18) *
-     ((10 ** 18 - maxSlippage))) / 10 ** 18);
+ uint256 minOut = msg.value * (10 ** 18 - maxSlippage) / poolPrice();
    

IWETH(W_ETH_ADDRESS).deposit{value: msg.value}();
uint256 amountSwapped = swapExactInputSingleHop(
    W_ETH_ADDRESS,
    rethAddress(),
    500,
    msg.value,
    minOut
);

return amountSwapped;
```

```d
Temporary locking of funds due to owner controlled maxSlippage
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#L174 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L75 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L60


# Vulnerability details

## Impact
In the `deposit()` function of `Reth.sol` we are using the `maxSlippage` hardcoded by the operator but instead of this we would have prompted the user to set slippage manually. Similarly in `withdraw()` function of `SfrxEth.sol` we are using hardcoded slippage setted by the operator. Also in the `withdraw()` function of `WstETH.sol` we are using the pre-definded value of slippage.

The underlying yield tokens price may go down abruptly.
If Luna/UST things happen again, users' funds may get locked.

Moreover, the withdrawal of the lidoVault takes a swap from the curve pool. 1 WstEth worth `0.995 ETH` at the time of writing.

Given that users' funds would be stuck temporarily while the operator adjust the slippage and it would create panic, investment loss for the users and Reputation loss of the protocol

solidity

        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; //20*2 => 40/100 = 0.4
            if (derivativeAmount == 0) continue; // if derivative empty ignore
            derivatives[i].withdraw(derivativeAmount);
        }

Moreover as 1 of the protocol will suffer this (https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L174

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

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

 and withdraw is in the loop (as shown the code above) so we would have to give high slippage to the other two protocols, which would cause unnecessary fee being dedected from user's investment


## Proof of Concept
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L174
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L75
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L60


## Tools Used

Manual Review

## Recommended Mitigation Steps

Make another function in which user will be prompted to add slippage by himself as shown below

solidity
function unstake(uint256 _safEth
potential stake() DoS if sole safETH holder (ie: first depositor) unstakes totalSupply - 1
Audit: 2023-03-asymmetry-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L68-L81
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L98


# Vulnerability details

## Impact
Potential inability to stake (ie: DoS) if sole safETH user (ie: this would also make them the sole safETH holder) unstakes `totalSupply - 1`.

## Proof of Concept
The goal of this POC is to prove that this line can revert https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L98
```
        uint256 mintAmount = (totalStakeValueEth * 10 ** 18) / preDepositPrice;
```
This can occur if the attacker can cause `preDepositPrice = 0`.

A user who is the first staker will be the sole holder of 100% of `totalSupply` of safETH.
They can then unstake (and therefore burn) `totalSupply - 1` leaving a total of 1 wei of safETH in circulation.

In earlier lines in `stake()` https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L77-L81, we see 
```
        uint256 totalSupply = totalSupply();
        uint256 preDepositPrice; // Price of safETH in regards to ETH
        if (totalSupply == 0)
            preDepositPrice = 10 ** 18; // initializes with a price of 1
        else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;
```
With `totalSupply = 1`, we see that the above code block will execute the `else` code path, and that if `underlyingValue = 0`, then `preDepositPrice = 0`.

`underlyingValue` is set in earlier lines: https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L68-L75
```
        uint256 underlyingValue = 0;

        // Getting underlying value in terms of ETH for each derivative
        for (uint i = 0; i < derivativeCount; i++)
            underlyingValue +=
                (derivatives[i].ethPerDerivative(derivatives[i].balance()) *
                    derivatives[i].balance()) /
                10 ** 18;
```

For a simple case, assume there is 1 derivative with 100% weight. Let's use rETH for this example since the derivative can get its `ethPerDerivative` price from an AMM. In this case:
- Assume the `ethPerDerivative()` value has been manipulated in the underlying AMM pool such that 1 derivative ETH is worth less than 1 ETH. eg: 1 rETH = 9.99...9e17 ETH
- In this case, also assume that since there is 1 wei of safETH circulating, there should be 1 wei of ETH staked through the protocol, and therefore `derivatives[i].balance() = 1 wei`.

This case will result in `underlyingValue += (9.99...9e17 * 1) / 10 ** 18 = 0`.

We can see that it is therefore possible to cause a divide by 0 revert and malfunction of the `stake()` function.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Assuming the deployment process will s