xanoutas/solidity-security-findings huggingface dataset:
[H-02] Front-running of first deposit allows attacker to steal funds
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Due to the way in which the `SafEth` share price is calculated, an attacker can front-run the first depositor's transaction and steal funds through an inflation attack. `SafEth::stake` calculates the share price by dividing the total asset amount by the total supply of shares, which is 0 at the time of the first deposit and means that the first depositor will receive 1 share for every wei deposited. The exploit is performed by front-running a large initial deposit and inflating the balance of the staked ether derivative contracts by sending a large number of tokens directly. This causes the `preDepositPrice` to become very large which can result in the victim receiving little/no shares due to rounding given there is no check that the amount to mint is not zero. The result is that the attacker profits at the expense of the victim.

## Proof of Concept
The `preDepositPrice` which is used in calculations of the amount of shares to mint in exchange for a given amount of ether is calculated by:
```
if (totalSupply == 0)
    preDepositPrice = 10 ** 18; // initializes with a price of 1
else preDepositPrice = (10 ** 18 * underlyingValue) / totalSupply;
```

Example exploit steps:
1. Attacker calls `SafEth` with 1 wei for 1 share.
2. Attacker deposits 100 ether worth of staked ether derivative tokens to each derivative token contract in the protocol assuming each has a weight of 1/3.
3. Victim deposits 200 ether for 0 shares due to rounding, assuming a `preDepositPrice` of 300e18 given `totalSupply` of 1e18 and `underlyingValue` of 300e18. Thus, the `mintAmount` calculation is `200e18/300e18` which floor rounds to 0.
4. Attacker withdraws 1 share, profiting 200 ether.

## Severity Justification
The first depositor may not receive shares in exchange for their assets if their transaction is front-run and the total asset amount has been manipulated, so this is evaluated to be HIGH.

## Tools Used
Manual review

## Recommended Mitigation Steps
As stated in other similar reports, one solution to this problem is to burn the first 1000 shares thereby increasing the cost to perform this attack by the same factor. Additionally, ensure the number of shares is non-zero to prevent an attacker from stealing all the funds in the case where subsequent deposits are less than `SafEth::underlyingValue`:

```require(mintAmount != 0, "No shares minted");```
First stake inflation attack
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details



## Impact
The first staker can steal the next staked funds.



## Proof of Concept
As the first one to stake, the attacker stakes `minAmount` ETH, for which `minAmount` (or slightly less due to slippage) SafEth tokens are minted.
The attacker immediately unstakes the entire amount except 1 Wei. He now has the total supply of SafEth which is only 1 Wei, and has lost only the small amount of ETH required to cover the slippage.
Now, instead of staking the normal path the attacker simply transfers a large amount `M` of one of the derivative token (e.g. RETH) to the corresponding derivative contract. His 1 Wei SafEth is now worth `M`.

The above attack has the consequence that for the next stake the [`underlyingValue` is `M`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L68-L75) (minus slippage), so [`preDepositPrice = 1e18 * M`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L81), since `totalSupply == 1`. Therefore the amount minted is [`totalStakeValueEth / M`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L98). The division causes a rounding error in `totalStakeValueEth` by up to `M`, the amount of which is shared with the attacker's 1 Wei SafEth. This means that the attacker will effectively receive part of the next staker's deposited funds. In the worst case, if `M > totalStakeValueEth`, the second staker will recieve no SafEth and his entire amount staked will be claimable by the attacker.
That amounts are lost to the attacker also applies to subsequent stakes, but with possibly diminishing returns as the total supply grows.

Note that the attacker may perform this attack by frontrunning the next staker, making sure that `M > totalStakeValueEth` so that he can unstake the entire balance. After this he can simply repeat the attack.

## Recommended Mitigation Steps
Instead of calling `IERC20.balanceOf()` on the underlying token keep an internal accounting of the balance in each derivative, so that only deposits made by `SafEth` are accounted for. This way the balances cannot be externally inflated.
poolPrice in Reth.sol can overflow and revert
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
To determine the value of sqrtPriceX96 that will cause an overflow, we need to analyze the calculation in the function:

(sqrtPriceX96 * (uint(sqrtPriceX96)) * (1e18)) >> (96 * 2)

The maximum value for a uint256 is 2^256 - 1. An overflow occurs when the result of the multiplication exceeds this value. To find the threshold, we can set up the following inequality:

(sqrtPriceX96^2 * 1e18) / (2^(96*2)) <= 2^256 - 1

Simplifying the inequality:

sqrtPriceX96^2 <= (2^256 - 1) * (2^(96*2)) / 1e18

Taking the square root of both sides to find the threshold for sqrtPriceX96:

sqrtPriceX96 <= sqrt((2^256 - 1) * (2^(96*2)) / 1e18)

After calculating the result, we get:

sqrtPriceX96 <= 5192296858534827

So, any value of sqrtPriceX96 greater than approximately 5,192,296,858,534,827 will result in an overflow.


## Tools Used
calculator

## Recommended Mitigation Steps
use safemath or FullMath library from Uniswap
[H-01] RETH oracle manipulation allows attacker to steal funds
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The Asymmetry SafEth protocol aims to help diversify and decentralize liquid staking derivatives, exchanging ether staked in the protocol for staked ether derivative tokens based on some relative weighting. A function exposed by these derivative wrapper contracts, `IDerivative::ethPerDerivative`, takes `uint256 _amount` as argument which is unused in all invocations except for the case when the derivative contract is `Reth`. This is because the rETH deposit pool has a limit above which the balance is capped, currently set to 5000 ether as given by [`RocketDAOProtocolSettingsDeposit::getMaximumDepositPoolSize`](https://etherscan.io/address/0xCc82C913B9f3a207b332d216B101970E39E59DB3#readContract), so any additional rETH is made up by the SafEth protocol through swaps on Uniswap V3.

Due to a bug in `SafEth::stake` which [passes `derivatives[i].balance()`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L73) as `_amount` to `IDerivative::ethPerDerivative`, after a sufficient number of deposits to `Reth`, this derivative contract balance of `rETH` could exceed the remaining room in the pool, causing `ethPerDerivative` to erronously quote the UniswapV3 pool spot price as defined by `Reth::poolPrice` instead of `RocketTokenRETHInterface::getEthValue`. Also note that whilst this function expects an ether balance, it is in fact receiving an `rETH` balance (the same is true for the other derivative tokens; however, as stated above, this value is not used). Given these preconditions, an attacker could use a flash loan to manipulate the spot price of rETH to steal funds.

A highly discounted rETH price would give rise to a small `underlyingValue` which in turn leads to a small `preDepositPrice` given the existing `totalSupply` which is assumed to be comparably large. When calculating `totalStakeValueEth`, assuming the attacker's ether deposit does not exceed the remaining rETH pool room, `Reth::ethPerDerivative` will this time use the correct rETH pricing which, combined with the small `preDepositPrice`, will result in a large number of SafEth tokens being minted. The attacker can then call `SafEth::unstake` with this large `_safeEthAmount`, withdrawing underlying ether from the derivative contracts for a profit and at the expense of other protocol participants.

## Proof of Concept
1. Let us first assume that the rETH pool is close to full, with a remaining room of say 200 ether which corresponds to the current max amount of the protocol (note that the owner has the ability to change this value). Assuming deposits totalling 100 ether are made to the rETH pool external to the protocol, and the derivative tokens have relative weights of 250, 250, 500 (WstETH, SfrxETH, Reth respectively), the first condition f
Burning rETH at the unstake might revert
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#L108


# Vulnerability details



## Impact
Unstaking is blocked.

## Proof of Concept
When unstaking the `withdraw` of each derivative is called. `Reth.withdraw` withdraws by calling [`RocketTokenRETHInterface(rethAddress()).burn(amount)`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L108). But `RocketTokenRETH.burn` [reverts if the ETH balance is insufficient for the exchange of rETH for ETH](https://etherscan.io/token/0xae78736cd615f374d3085123a210448e74fc6393#code#F6#L140):
```solidity
// Get ETH amount
uint256 ethAmount = getEthValue(_rethAmount);
// Get & check ETH balance
uint256 ethBalance = getTotalCollateral();
require(ethBalance >= ethAmount, "Insufficient ETH balance for exchange");
```
[According to Rocket Pool](https://docs.rocketpool.net/guides/staking/overview.html#the-reth-token) "It's possible that if node operators have put all of the staking pool to work on the Beacon chain, then the liquidity pool won't have enough balance to cover your unstaking. In this scenario, you may find other ways to trade your rETH back to ETH (such as a decentralized exchange like Uniswap (opens new window)) - though they will likely come with a small premium."

In this case `Reth.withdraw` will revert, which blocks the unstaking.

## Recommended Mitigation Steps
Similarly to when depositing, check if `RocketTokenRETHInterface(rethAddress()).getTotalCollateral() >= RocketTokenRETHInterface(rethAddress()).getEthValue(amount)` and resort to Uniswap if it's not.
Adding a new derivate will splits new user's underlying shares in the new derivate to older users
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

# Vulnerability details

## Impact
After a derivate is added to the protocol. Older users will take some new user's share in the new derivate.

The addDerivative function add a new derivate to the protocol. The newly added derivate has zero underlying balance.

Users who stake eth to the protocol after addDerivative action will deposits to the new derviate and increase it's underlying balance.

Users who have shares in the protocol before the addDerivative action now has automatically owns share of the newly added derivates. And They can unstake to withdraw assets in the new derivate.

## Proof of Concept

## Tools Used
Manual Review

## Recommended Mitigation Steps
addDerivative should call rebalanceToWeights() to rebalance the derivates.
User Controllable derivative.balance() - First staker can make preDepositPrice too huge, so next users' mintAmount can be affected
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L79-L81
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L98
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L221-L223
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L122-L124
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L93-L95


# Vulnerability details

## Impact
The main issue is that users are able to control `derivative.balance()`. (derivative means all of `Reth`, `SfrxEth` and `WstEth`)
We can easily find that all of `Reth`, `SfrxEth` and `WstEth` has the balance() function which is linked to the normal ERC20 token's `balanceOf` function.
So if user transfer that token (`rethAddress()`, `SFRX_ETH_ADDRESS`, `WST_ETH`) to individual derivative contract manually, he can increase `derivative.balance()`.

There can be several attacking scenarios. Here PoC is just one example of it.
It's about "First staker can make `preDepositPrice` too huge, so next users' `mintAmount` can be affected".

## Proof of Concept

|Step|Activity|`totalSupply`|`underlyingValue`|`Attacker balance`|
|--|--|--|--|--|
|(orginal)||`0`|`0`|`0`|
|Step 1|Attacker stake `1 ETH = (10**18)`|`about 10**18`||`about 10**18`|
|Step 2|Attacker make his `SafEth` balance to `1` by unstaking|`1`||`1`|
|Step 3|Attacker make `derivatives[0].balance()` to `2 * 10**18` by manual tranfer|`1`|`2 * 10**18`|`1`|
|Step 4|UserB stake `1 ETH = (10**18)`||||

#### Step 1.
Let's say that attacker is the first staker.
Attacker stake 1 ETH

#### Step 2.
Let's call the attacker's balance is `b` (it will be around `10**18`).
Attacker unstake `b - 1`.
Then, his balance and `totalSupply` will be 1.

#### Step 3.
By manual tranfer of `rethAddress()`(or `SFRX_ETH_ADDRESS`, `WST_ETH`) token to individual derivative contracts, we can make `derivative.balance()` to over `2 * 10**18`. It costs about `2 ETH`.

#### Step 4.
Let's say that UserB (normal user) want to stake `1 ETH`.

`underlyingValue` (declared in L68) will be about `2 * 10**18`.
So, `preDepositPrice` (in L81) is about `2 * 10**36` (because `totalSupply` is 1).
We can assume that `totalStakeValueEth` is around `10**18` (which UserB deposited).
In L98, `mintAmount = (totalStakeValueEth * 10**18) / preDepositPrice` is around `10**18 * 10**18 / (2 * 10**36)`.
This value is arithmetically zero by Math.floor.

Eventually, UserB stake 1 ETH, but get no `SafEth`.
Let's imagine that there are numbers of users like UserB.
If the Attacker unstake his remained 1 `SafEth`, he can get back all deposited ETH which can cover the manual amount. And he gets huge profit.

## Tools Used
VSCode

## Recom
ethPerDerivative for Reth may not provide the correct underlying value 
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

ttps://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L72-L75
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L211-L216
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L212
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L146-L149


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

Under certain conditions, staking and unstaking may yield vastly different amounts of tokens. This is because staking calculates `underlyingValue` in an incorrect way for Reth when the balance staked is in the order of magnitude of `rocketDAOProtocolSettingsDeposit.getMaximumDepositPoolSize()`.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

The underlyingValue calculation is here. 
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L72-L75
```
     underlyingValue +=
                    (derivatives[i].ethPerDerivative(derivatives[i].balance()) *
                        derivatives[i].balance()) /
                    10 ** 18;
```
The value is computed using `ethPerDerivative` (for Reth is here: 
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L211-L216). Semantically, the underlyingValue calculation should not depend on logic in poolCanDeposit(_amount) called in 
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L212 which depends on logic such as https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L146-L149.

Note that the logic in stake() https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L92-L94 depends on `ethPerDerivative` but the logic in `unstake()` does not 

## Tools Used

Manual

## Recommended Mitigation Steps

Separate the logic used for calculating deposit values and for calculating underlyingValue
Impossible to unstake SafETH If rETH pool doesn't have enough liquidity
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#L107-L114


# Vulnerability details

## Impact

rETH withdraw may revert if not enough ETH are available in the deposit pool making the unstaking impossible.

## Proof of Concept

When staking for rETH, ETH are sent to the deposit pool waiting to be picked for new miniPool validators.

This pool can be empty or not have enough ETH than what's being asked for when calling burn.

In this case the burn function reverts.

rETH code: 

```solidity
// Burn rETH for ETH
    function burn(uint256 _rethAmount) override external {
        // Check rETH amount
        require(_rethAmount > 0, "Invalid token burn amount");
        require(balanceOf(msg.sender) >= _rethAmount, "Insufficient rETH balance");
        // Get ETH amount
        uint256 ethAmount = getEthValue(_rethAmount);
        // Get & check ETH balance
        uint256 ethBalance = getTotalCollateral();
        require(ethBalance >= ethAmount, "Insufficient ETH balance for exchange");
        // Update balance & supply
        _burn(msg.sender, _rethAmount);
        // Withdraw ETH from deposit pool if required
        withdrawDepositCollateral(ethAmount);
        // Transfer ETH to sender
        msg.sender.transfer(ethAmount);
        // Emit tokens burned event
        emit TokensBurned(msg.sender, _rethAmount, ethAmount, block.timestamp);
    }
```

## Tools Used

Manual review

## Recommended Mitigation Steps

Consider adding a way to sell on the market like wsteth and sfrxeth derivatives do.
Assuming ETH/stETH is 1-1 can lead to reverted withdrawals()
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Impact
If stETH is not 1:1 with ETH on Curve and differs more than the maxSlippage (initialized set to 1%) withdrawals will not be possible. 

## Proof of Concept
On [L60](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L60) in WstETH:
```solidity
uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
```

We see that ETH is assumed to be 1:1 with stETH and that minOut is purely based on maxSlippage. If ETH:stETH differs more than maxSlippage both unstake() and rebalanceToWeight() in SafeETH revert since they both call withdraw() on WstEth on L142 and L118.

In the following POC forks mainnet on november 23, on block 16040000 when eth/stETH < 0.98. I show that even if maxSlippage is set to 2% withdrawals revert.

Change the blockNumber to 1604000 in hardhat.config.ts and create a new test file with the following test:

```typescript
import { SafEth } from "../typechain-types";
import { ethers, upgrades } from "hardhat";
import { expect } from "chai";
import {
  getAdminAccount,
  getUserAccounts,
  getUserBalances,
  randomStakes,
  randomUnstakes,
} from "./helpers/integrationHelpers";
import { getLatestContract } from "./helpers/upgradeHelpers";
import { BigNumber } from "ethers";

describe.only("SafEth POC", function () {
  let safEthContractAddress: string;
  let strategyContractAddress: string;
  let startingBalances: BigNumber[];
  let networkFeesPerAccount: BigNumber[];
  let totalStakedPerAccount: BigNumber[];

  before(async () => {
    startingBalances = await getUserBalances();
    networkFeesPerAccount = startingBalances.map(() => BigNumber.from(0));
    totalStakedPerAccount = startingBalances.map(() => BigNumber.from(0));
  });

  it("Should deploy the strategy contract", async function () {
    const safEthFactory = await ethers.getContractFactory("SafEth");
    const strategy = (await upgrades.deployProxy(safEthFactory, [
      "Asymmetry Finance ETH",
      "safETH",
    ])) as SafEth;
    await strategy.deployed();

    strategyContractAddress = strategy.address;

    const owner = await strategy.owner();
    const derivativeCount = await strategy.derivativeCount();

    expect(owner).eq((await getAdminAccount()).address);
    expect(derivativeCount).eq("0");
  });

  it("Should deploy derivative contracts and add them to the strategy contract with equal weights", async function () {
    const supportedDerivatives = ["Reth", "SfrxEth", "WstEth"];
    const strategy = await getLatestContract(strategyContractAddress, "SafEth");

    for (let i = 0; i < supportedDerivatives.length; i++) {
      const derivativeFactory = await ethers.getContractFactory(
        supportedDerivatives[i]
      );
      const derivative = await upgrades.deployProxy(derivativ