xanoutas/solidity-security-findings huggingface dataset:
Fund stealing via Donation Attack on `SafEth.stake()`
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

By simple manipulations, the first depositor can infinitely drain all next depositors' funds by causing a rounding error in `SafEth.stake()` share-issuing logic and forcing it to mint zero SafEth shares to all next stakers.

A similar vulnerability was highlighted in OpenZeppelin ERC4626 audit (HIGH-01): https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/audits/2022-10-ERC4626.pdf

To attack SafEth a hacker needs to become the first investor with any amount of staked ETH, then withdraw the most of the stake so there is only 1 wei of SafEth shares left, and then inflate the underlying asset value by manually donating a large amount of underlying assets to relevant derivative pools. Any staker after the hacker's attack will receive zero shares for their stake. The attacker can redeem all underlying funds at any time by burning the only 1 wei share in the contract.

## Proof of Concept


Attack scenario:

**Step 1.** A hacker backruns `SafEth.initialize()` function and becomes the first investor in the contract by staking `minAmount` ETH via `SafEth.stake()`.

The contract is initialized with `minAmount = 0.5 ETH`:
```
minAmount = 5 * 10 ** 17; // initializing with .5 ETH as minimum
```
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L54

Right after initialization the `SafEth.totalSupply` is zero. Thus, the contract mints `0.5` SafEth shares to the hacker:
```
if (totalSupply == 0)
    preDepositPrice = 10 ** 18; // initializes with a price of 1
```
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L79-L80


**Step 2.** The hacker unstakes most of the funds via `SafEth.unstake()` so there is only 1 single wei share on the balance left. This can be done by calling:
```
safEth.unstake(safEth.balanceOf(hacker) - 1)
```

Now the `SafEth.totalSupply` is 1 wei.


**Step 3.** The hacker inflates the underlying asset value.

For example, the hacker buys 300.0 WstEth from the market and directly donates it to the project's WstEth derivative pool by calling
```
IERC20(WST_ETH).transfer(address(derivativeContract), 300e18)
```

The WstEth derivative contract's balance is calculated as follows:
```
function balance() public view returns (uint256) {
    return IERC20(WST_ETH).balanceOf(address(this));
}
```
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L93-L96

After the hacker's direct transfer it returns a value greater or equal to `300e18`.

Note that the hacker can redeem the full sum (300.0 ETH) at any time since they own 100% of SafEth shares (even though it's just a 1 wei).

**Step 4.** . Any new depositor that stakes funds aft
An user who stakes soon after first derivative is added will enjoy the shares from other users who stake in all the derivatives.
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
A user who starts stake soon after the first derivative is added will enjoy the shares of others who are staking in all other derivatives.

## Proof of Concept
Owner will call the function `addDerivative()` to add the derivative. this function will take `derivative address` and `weight` as arguments.
When looking at the `one derivative address` with `one weight` value, owner would plan to call this function sequentially. Each derivative would be added in different transactions.

In the above scenario, soon after adding the first derivative, a user can front run and call the stake function with maximum deposit amount value (200 ETH). or the user may call the stake function two times with high gas fee.

The minted safETH would be 200 units (maximum). 

Now, all the derivatives are added. user A and B are staking 3 ETH each in each of the derivatives.

Their share would be 1 ETH. lets assume with uniform weightage criteria.

When the first user wants to unstake, they might get their shares from all the derivative though they have staked in one derivative only.

      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);
        }
        _burn(msg.sender, _safEthAmount);

Now, user A and B wants to unstake, they would get their shares from single derivative only. Because, rest of the shares from other derivatives are already withdrawn by the first user.

## Tools Used

Manual code review.

## Recommended Mitigation Steps

Add all the derivatives in single transaction

    function addDerivative(
        address _contractAddress, --------------> change to array of address
        uint256 _weight ------------------------> change to array weight.
    ) external onlyOwner {
        derivatives[derivativeCount] = IDerivative(_contractAddress);
        weights[derivativeCount] = _weight;
        derivativeCount++;


        uint256 localTotalWeight = 0;
        for (uint256 i = 0; i < derivativeCount; i++)
            localTotalWeight += weights[i];
        totalWeight = localTotalWeight;
        emit DerivativeAdded(_contractAddress, _weight, derivativeCount);
    }
Price oracle manipulation in Reth that results in fund being stolen
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

https://github.com/NaryaAI/2023-03-c4-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L240


# Vulnerability details

## Impact
The price of Reth in ETH can be manipulated by flashloan. An attacker can exploit this fact to steal funds from other stakers.

## Proof of Concept
https://github.com/NaryaAI/2023-03-c4-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L240

```
    /**
        @notice - Price of derivative in liquidity pool
     */
    function poolPrice() private view returns (uint256) {
        address rocketTokenRETHAddress = RocketStorageInterface(
            ROCKET_STORAGE_ADDRESS
        ).getAddress(
                keccak256(
                    abi.encodePacked("contract.address", "rocketTokenRETH")
                )
            );
        IUniswapV3Factory factory = IUniswapV3Factory(UNI_V3_FACTORY);
        IUniswapV3Pool pool = IUniswapV3Pool(
            factory.getPool(rocketTokenRETHAddress, W_ETH_ADDRESS, 500)
        );
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0(); // (1)
        return (sqrtPriceX96 * (uint(sqrtPriceX96)) * (1e18)) >> (96 * 2);
    }
```
At (1), the function fetches the `slot0.sqrtPriceX96` of the pool which shows the current price without any historical price information.

This value can be manipulated by an attacker through flashloan. For example, an attacker can
(1) Use flashloan to get a large amount of WETH from any pool.
(2) Add liquidity to the rETH-WTH pool at a large tick range (e.g., 29000 ~ 29010)
(3) Swap all the WETH through the rETH-WTH pool
Then the current price of rETH in the pool will be dramatically increased, which will affect the stake() function (if totalSupply is 0, the attacker can gain many more safETH than being supposed to).

## Tools Used

## Recommended Mitigation Steps
Use TWAP instead of the current price (see https://docs.uniswap.org/contracts/v3/reference/core/libraries/Oracle).
Wrong arithmetic in unstake function
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In the unstake function, the ethAmountToWithdraw variable will always be equal to zero or a negative number, but never a positive number. This is because the ethAmountBefore variable is set to the balance of the contract before any funds are withdrawn from the derivatives, while the ethAmountAfter variable is set to the balance of the contract after the funds have been withdrawn.

Since the function transfers funds to the user, the balance of the contract after the transfer will always be lower than the balance before the transfer, resulting in a negative or zero ethAmountToWithdraw value. Therefore, the equation ethAmountToWithdraw = ethAmountAfter - ethAmountBefore will always result in a zero or negative value.


## Tools Used
manual inspection


## Recommended Mitigation Steps

uint256 ethAmountToWithdraw = ethAmountBefore - ethAmountAfter;
rETH/WETH swap pool price manipulation for incorrect underlying assets estimation
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-L96


# Vulnerability details

## Impact
This vulnerability lets attackers receive an additional amount of `safETH` tokens and swap them for `ETH` with profit. Finally the cost of `safETH` will decrease because of underlying assets loosing. It is possible because of different branches in estimation of `rETH` asset for the current balance and deposited amount. 

## Proof of Concept
Prerequisites: the `SafEth` contract has `rETH` balance more than 5000 ETH (this is the current max value that can be deposited in the Rocket pool) and `weight` != 0.
The attackers transaction will be consist of:
#1. Take a flashloan with approximately 5000 rETH. It is enough  to move the price in the swap pool.
#2. Make a swap in the Uniswap v3 rETH/WETH pool to influence on the tokens rate.
#3. Call `stake` function in the `SafEth` contract in a loop with amount less than 200 to satisfy the contract conditions:
#3.1 The line [#72](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L72-L75) computes `underlyingValue` with the current assets balances. Let’s look at the    `derivatives[i].ethPerDerivative(derivatives[i].balance())` call for the `safETH` asset.
#3.2 It routes us to the line #211 of the `Reth` contract. There are two variants for asset estimation depending on `_amount`.
#3.3 The `poolCanDeposit` checks that the `_amount` satisfy the max and min possible meanings for depositing setting in  the [`rocketDAOProtocolSettingsDeposit`](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L147-L149). The current max value is 5000 ETH. Of course it is not correct to send asset balance in the function, which waits for the amount of ETH, but this is not in the scope of the PoC. 
#3.4 So we will go to another branch with `poolPrice` function [(line #215)](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/Reth.sol#L215), which asks the current rate of the rETH/WETH pair in the Uniswap v3 pool and receives the manipulated rate.
#3.5 We received a really low estimation for the `safETH` asset in the `underlyingValue` variable. In the line [#81](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L81) the `preDepositPrice` is calculated - the ratio of `underlyingValue` and `totalSupply`.
#3.6 The next step is staking of ETH in the derivatives. The corresponding `ethAmount` will be calculated depending on `weight` in the line [#88](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L88)
#3.7  There is the `derivative.deposit` call in the line [#91](https://github.com/code-423n
The user might receive unfair amount of ETH upon unstaking 
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability Details
* Let's assume for simplicity, that Asymmetry currently has 2 underlying derivatives: Lido and RocketPool. And the weights are:
  * stETH: `weights[0] = 50e18,`
  * rETH: `weights[1] = 50e18.`
  
* As of now, the exchange rate is the following:
  * `1stETH = 	0.988232 ETH`
  * `1wstETH = 1.12 ETH`
  * `1 rETH = 1.069457 ETH`
  
</br>

* Consider the following scenario: 
  * Bob stakes 10eth by invoking the `stake()` in [SafEth.sol](https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol)
    * $$ethAmount[0] = 5ETH => depositAmount[0] = 4.48wstETH => derivativeReceivedEthValue[0] = \frac{1116781458397758464 stETH * 4.48 wstETH}{1e18}$$
    * $$ethAmount[1] = 5ETH => depositAmount[1] = 4.67526978644rETH => derivativeReceivedEthValue[1] = \frac{1064454100689835271ETH * 4.67526978644rETH}{1e18}$$
    * $$totalStakedValueEth = \frac{1116781458397758464 stETH * 4.48 wstETH}{1e18} + \frac{1064454100689835271ETH * 4.67526978644rETH}{1e18}$$

  * As you can see above, those amounts are not the same, they deviate from deposited 5eth, therefore the shares are not perfectly equal: 
    * $$mintedSharesFor(\frac{1116781458397758464 stETH * 4.48 wstETH}{1e18}) \: != \: mintedSharesFor(\frac{1064454100689835271ETH * 4.67526978644rETH}{1e18})$$
    * $$mintAmount[0] = \frac{5.0031809e+18}{1e18}$$
    * $$mintAmount[1] = \frac{4.9766101e+18}{1e18}$$
    


  * Everything seems to be okay, until the weights change. 
    * Let's say now, Asymmetry add another derivative and adjust the weights:
      * stETH: `weights[0] = 1e18,`
      * rETH: `weights[1] = 90e18.`
      * sfrxETH: `weights[2] = 9e18`
  * The balances slightly start to change(i.e. more eth comes into RocketPool and after some time it rebalances)
  * Now, the user who gains unfair amount of shares, decides to unstake his mintAmount[0] + mintAmount[1] and according to the weights. But the RocketPool upon burning rETH provides more eth on X amount of _safEth. As a result the user gains more eth, simply because he received _safEth shares on stETH, but now 90% of his shares will be burnt by the RocketPool provided more eth then it should.



## Impact
*  Users might lose their underlying eth.

</br>


## Tools Used
* Manual Review

</br>

## Recommended Mitigation Steps
- Short term: N/A
- Long term: It should not be possible to receive eth on _safETH shares received from the not the current derivative that tries to withdraw. 
  - (i.e burning/exchanging _safETH shares received from other derivative could cause some loses or extra funds in a user's pocket)
First Depositor can significantly inflate the price of safETH to the detriment of 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#L63-L81


# Vulnerability details

## Impact
The stake function uses the totalsupply() as the denominator in the division if the totalsupply() is not ZERO.
It is anticipated that totalsupply() value is either a significant value or defaulted to 10**18.

```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;
```
To set up the scenario it is important to first take note that there is no set unstake values.
If the first depositor can stake a value eg. 0.5 ETH and then get safETH tokens eg. 1000 tokens.
They can then unstake a value of 999 tokens, leaving the total supply as 1, this bypasses the check for a totalsupply of 0 when staking and instead of the denominator being at least 10**18, it is in fact only 1.
To show the impact, I have taken a value of 1 multiplied by 10 ** 18 and divided by the 2 values in chisel to demonstrate the difference.
```Text
uint256 goodDenominator;
goodDenominator = 10 ** 18;
uint256 badDenominator;
badDenominator = 1;
uint256 anticipatedLowerPrice = 1* 10 ** 18 /goodDenominator;
uint256 badHighPrice = 1 * 10 ** 18 /badDenominator;
anticipatedLowerPrice
Type: uint
├ Hex: 0x1
└ Decimal: 1
badHighPrice
Type: uint
├ Hex: 0xde0b6b3a7640000
└ Decimal: 1000000000000000000
```

Thus as you can see in the example, the value would be 1000000000000000000 which is much higher than the anticipated value of 1.

## Proof of Concept
I have coded a PoC which I will include below, that shows the effect such an attack could have.
The hardhat test can be placed in it's own file and run separately.
It will give four outputs:
1 The values of tokens stakers get if the attack is successful.
2 The values if the same action of unstaking balance-1 which leaves 1 token, should the deposit not be the first.
3 The values of tokens stakers get if everything works as anticipated and there is a first deposit of 0.5 ETH.
4 The values of tokens stakers get if everything works as anticipated and there is no first deposit of 0.5 ETH.


For scenario 1 listed above the output is below:
```Text
    First Depositor Exploit
SafeEth totalsupply
BigNumber { value: "0" }
SafeEth totalsupply after 0.5 stake
BigNumber { value: "500050473544899939" }
SafeEth totalsupply after unstake of balance - 1
BigNumber { value: "1" }
Minted tokens for Second depositor staking 200 ETH
BigNumber { value: "66677603838999772926" }
Minted tokens for Third depositor staking 1 ETH
BigNumber { value: "333367045345000893" }
Minted tokens for Fourth depositor staking 0.5 ETH
BigNumber { value: "166683390128838133" }
Final TotalSupply
BigNumber { value: "67177654274473611953" }
      ✓ Should Stake at smallest amount 0.5 and then unstake ba
Stake function can revert due to division by 0
Audit: 2023-03-asymmetry-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In the stake function, if derivatives[i].balance() is lower than 1e18 (less than 1 derivative in terms of ETH), then the expression (derivatives[i].ethPerDerivative(derivatives[i].balance()) * derivatives[i].balance()) / 10 ** 18 can result in a value less than 1. Since Solidity performs integer division and rounds down, this expression will result in "underlyingValue" being 0. Consequently, preDepositPrice will be 0. This will cause the equation for mintAmount to be divided by 0 which will result in transaction revert.



## Tools Used
Manual inspection

## Recommended Mitigation Steps
Put require(preDepositPrice != 0, "preDepositPrice is zero"); before
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L98
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

## 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.
In wstEth `deposit()` function we are wrongly assuming steth price is 1:1 with eth.
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
In following line in wstEth:
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L60
we are wrongly assuming that 1sthEth is 1:1 in price with the eth but that is not the case, even currently it is on uniswap as
```
1 stETH = 1.00185 ETH 
```
```solidity
        uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
```
That is not alot but in future it can vary. 
So to catter the deposit issue use the mechanism using the `ethPerDerivative()` function as used in the sfrxEth.sol like following:

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

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

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


## Tools Used
Manual Review
## Recommended Mitigation Steps
Calculate it using the `ethPerDerivative()` function like in SfrxEth.sol