xanoutas/solidity-security-findings huggingface dataset:
WETH compatibility issue on Blast chain
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/ab18e1f6c03e118158369527baa2487b2b4616b1/src/SlashingHandler.sol#L52-L59


# Vulnerability details

## Impact

The slashing mechanism in the Vault contract may fail or behave inconsistently when handling certain ERC20 tokens on chains with non-standard token implementations, such as Blast.

## Proof of Concept
According to [ReadMe](https://github.com/code-423n4/2024-07-karak?tab=readme-ov-file#scoping-q--a), 

#### ERC20 token behaviors in scope

| Question                                                                                                                                                   | Answer |
| ---------------------------------------------------------------------------------------------- | ------ |
| ERC20 used by the protocol  | Any (all possible ERC20s    |

WETH is a type of ERC-20.

Also, it's explicitly stated that the protocol will be deployed on the Blast chain too. However, if we look at the `handleSlashing` function of [`SlashingHandler.sol` contract,](https://github.com/code-423n4/2024-07-karak/blob/ab18e1f6c03e118158369527baa2487b2b4616b1/src/SlashingHandler.sol#L52-L59)

```solidity
function handleSlashing(IERC20 token, uint256 amount) external {
        if (amount == 0) revert ZeroAmount();
        if (!_config().supportedAssets[token]) revert UnsupportedAsset();

 @>     SafeTransferLib.safeTransferFrom(address(token), msg.sender, address(this), amount);
        // Below is where custom logic for each asset lives
        SafeTransferLib.safeTransfer(address(token), address(0), amount);
    }
```
The token transfer is done using the safeTransferFrom method. This works fine on most chains (Ethereum, Optimism, Polygon, BSC) which use the standard WETH9 contract that handles the case src == msg.sender:

WETH9.sol
```solidity
if (src != msg.sender && allowance[src][msg.sender] != uint(- 1)) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }
```
The problem is that the WETH implementation on Blast uses [a different contract](https://blastscan.io/address/0x4300000000000000000000000000000000000004#code), and does not have this `src == msg.sender handling`.

Also, the issue is presented in [Wrapped Arbitrum](https://arbiscan.io/address/0x8b194beae1d3e0788a1a35173978001acdfba668#code) and  [Wrapped Fantom](https://ftmscan.com/token/0x21be370d5312f44cb42ce377bc9b8a0cef1a4c83#code).



Setup a foundry environment, get an Arbitrum RPC endpoint url and run the following command: `forge test --match-test testTransferFromIssue --fork-url {YOUR_RPC}`. This will revert with a ERC20: transfer amount exceeds allowance error, because of the issue described above.

```solidity
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";

contract PairTest is Test {
    using stdStorage for StdStorage;
    address alice = address
Operators can finalize vault stake updates even when unregistered from DSS, disrupting DSS slashing ruquests
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/Core.sol#L146
https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/entities/Operator.sol#L126
https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/entities/Operator.sol#L1
https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/Core.sol#L228


# Vulnerability details

## Impact
Anyone can finalize an operator's stake vault update request, even if the operator is unregistered from a given DSS, which later prevents the DSS from slashing these updates. An operator can initiate a `Core::requestUpdateVaultStakeInDSS()` call, then unregister from the DSS and still call `Core::finalizeUpdateVaultStakeInDSS()` successfully updating their stake records, signaling that there are staked funds towards the DSS for a particular vault belonging to the operator. From this setup, the assumption is probably that the hook calls made in `Operator::validateAndUpdateVaultStakeInDSS()` will validate the operator status, however, the call to the hook is sent with `ignoreFailure: true`, meaning that even if `HookLib::callHookIfInterfaceImplemented()` fails, a revert won't happen, allowing the update on the `operatorState.vaultStakedInDssMap` data to propagate through. Because of this, any slashing requests made by the DSS towards this status update will revert, as `Core::requestSlashing()` requires the slashing request's operator to be registered to the DSS. 

This issue was reported in a previous [audit](https://github.com/code-423n4/2024-07-karak/blob/main/audits/core/Renascence%20-%20Karak%20Core%20Audit%20Report.pdf) of Karak, made by Renascence. Both the auditing team and the protocol have confirmed that it should be fixed, however, it appears to be still valid.

## Proof of Concept
https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/Core.sol#L146
https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/entities/Operator.sol#L126
https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/entities/Operator.sol#L128
https://github.com/code-423n4/2024-07-karak/blob/a9658bccc884eb72538875c3940c6b507e06d4ca/src/Core.sol#L228

The following test case can be added in `operatorDSS.t.sol` and run with `forge test --mt test_DSS_cannot_slash_unregistered_operator_stake`:

<details>

<summary>PoC</summary>


```solidity
function test_DSS_cannot_slash_unregistered_operator_stake() public {
        deposit_into_vaults();
        address requestedVault = address(vaults[0]);
        address requestedVault1 = address(vaults[1]);
        Operator.StakeUpdateRequest memory stakeUpdate =
            Operator.StakeUpdateRequest({vault: requestedVault, dss: dss, toStake: true});
        Operator.StakeUpdateRequest memory stakeUpdate1 =
            O
Missing access control in `Core::finalizeUpdateVaultStakeInDSS` allows unauthorized stake updates
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L143-L153


# Vulnerability details

## Impact

`Core::finalizeUpdateVaultStakeInDSS` doesn't check if the operator is registered with the DSS, which allows any address to call this function and finalize stake updates.

Operator could:

1. Call `Core::requestUpdateVaultStakeInDSS`
2. Unregister from the DSS by using `Core::unregisterOperatorFromDSS()`
3. Finally call `Core::finalizeUpdateStakeInDSS()`

As suggested by Renascence's audit report on the Core contract, this vulnerability could be used to:

1. Finalize stake updates for operators who are no longer registered with a DSS.
2. Bypass the intended access control, potentially leading to unauthorized stake manipulations.
3. Cause inconsistencies between the Core contract's state and the DSS's expectations, e.g. disallow DSS to slash the misbehaving operator.

## Recommended Mitigation Steps

Add the `checkIfOperatorIsRegInRegDSS` function call to the `Core::finalizeUpdateVaultStakeInDSS` function, to ensure only the registered operator can call the function:

```diff
function finalizeUpdateVaultStakeInDSS(Operator.QueuedStakeUpdate memory queuedStake)
    external
    nonReentrant
    whenFunctionNotPaused(Constants.PAUSE_CORE_FINALIZE_STAKE_UPDATE)
{
+   _self().checkIfOperatorIsRegInRegDSS(queuedStake.operator, queuedStake.updateRequest.dss);
    _self().validateAndUpdateVaultStakeInDSS(queuedStake);
    emit FinishedStakeUpdate(queuedStake);
}
```






## Assessed type

Access Control
Users that deposits after a DSS slash requested can lose funds unjustly
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Vault.sol#L78-L87


# Vulnerability details

## Impact
Considering a case when a DSS uses a flashBot to request slashing from a Vault, and the transaction did go through before an innocent user(Alice) deposit calls go through, it means Alice's funds would be slashed as well even when the slashed amount does not include Alice funds(since it's a new deposit).

The reason this cannot be considered a user error is the fact that a slash request on the vault might be justifiable based on the operator's activity, what cannot be confirmed from the request is the amount currently in the vault as at the time of operator malicious activities, DSS might front-run a huge deposit with a valid slash request(one that is likely to be justified by veto committee). 

## Proof of Concept
Under normal circumstances, when a DSS requests the slashing of a vault, it goes through a waiting period of 2 days to give the veto committee the allowance to council slashing if need be, if not, the slashing is finalized and money is sent from the vault to the slashHandler for burning purpose.

Now, let us consider the case of a Vault_A that has 1,000_USDC as ```totalAsset``` and 1,000 as total shares/totalSuppy

If vault_A gets a slash request of 100%, this means when not canceled, 1,000_USDC(total assets) of the vault is moved to the slashHandler.

More so, the slashing process is protected against users that might want to game the system and run before slashing is finalized by queuing withdrawal for 7days 

Also, note that the amount to be slashed has been calculated in ```fetchEarmarkedStakes()```
``` solidity
   function fetchEarmarkedStakes(SlashRequest memory slashingMetadata)
        internal
        view
        returns (uint256[] memory earmarkedStakes)
    {
        earmarkedStakes = new uint256[](slashingMetadata.vaults.length);
        for (uint256 i = 0; i < slashingMetadata.vaults.length; ++i) {
            earmarkedStakes[i] = Math.mulDiv(
                slashingMetadata.slashPercentagesWad[i],
                IKarakBaseVault(slashingMetadata.vaults[i]).totalAssets(),
                Constants.MAX_SLASHING_PERCENT_WAD
            );
        }
    }
```
So, in this case, 100% of the asset which amounts to 1_000_USDC has been marked to be slashed.

If the slashing had been finalised before Alice's deposit, she would have minted ```(1000 *  1000+1) / 1 = 1,001,000 shares!``` and if any previous depositor(that had their funds slashed tries to withdraw, it will amount to zero asset ```(1000 * 1000+1) / (1,001,000 + 1000) = 0 asset! and reverted```

But in this case, since slash request have been placed directly before Alice deposit, 1,000 shares is minted to Alice. so vault has;
```
totalAsset = 2,000
totalSupply = 2,000
```
When slashing is finalized, the predetermined amount of 1,000 USDC is removed from the vault to leave the vault with 1,000_USDC and 2,000
DOSing of NativeVault deployment.
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L42
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/CoreLib.sol#L85
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/CoreLib.sol#L72


# Vulnerability details

## Impact
When deploying a NativeVault, asset address in the configuration struct(Operator input) can be zero according to NativeVault ```initialization``` function
```solidity
 /// @notice Initializes the vault
    /// @param _owner The owner of the NativeVault, which should be Core in most cases.
    /// @param _operator The operator of the vault (usually the caller of the deployVault function on Core which triggers this).
@>  /// @param _depositToken Disregarded for NativeVault. Can be address(0).
    /// @param _name The name of the vault
    /// @param _symbol The symbol of the vault
    /// @param _extraData Serialized bytes consisting {address slashStore, address nodeImplementation}
    function initialize(
        address _owner,
        address _operator,
        address _depositToken,
        string memory _name,
        string memory _symbol,
        bytes memory _extraData
    ) external initializer {
```

Also, protocol admin set allowlist(tokens that can be used as vault asset), which include setting up the slash Handler address, in the settings, zero address cannot be set up
```solidity
 function allowlistAssets(Storage storage self, address[] memory assets, address[] memory slashingHandlers)
        external
    {
        if (assets.length != slashingHandlers.length) revert LengthsDontMatch();
        for (uint256 i = 0; i < assets.length; i++) {
    @>        if (slashingHandlers[i] == address(0) || assets[i] == address(0)) revert ZeroAddress();
            self.assetSlashingHandlers[assets[i]] = slashingHandlers[i];
        }
    }
```
From the above snippet, it shows that no slashHandler is set for ```address(0)```.  This is still making sense, since when deploying a NativeVault that accept address(0) as asset address the slash store is encoded as ```extraData```
```solidity
  function initialize(
        address _owner,
        address _operator,
        address _depositToken,
        string memory _name,
        string memory _symbol,
        bytes memory _extraData
    ) external initializer {
        _initializeOwner(_owner);
        __Pauser_init();

        if (_depositToken != Constants.DEAD_BEEF) revert DepositTokenNotAccepted();

 @>       (address manager, address slashStore, address nodeImplementation) =
            abi.decode(_extraData, (address, address, address));
```

The cause of DOS here lies in the validation check when deploying vault. When a NativeVault is being deployed and asset address is zero, the function reverts


## Proof of Concept
```solidity
    function validateVaultConfigs(Storage storage self, VaultLib.Con
User can request update of the vault stake and then unregister before finalization
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L189


# Vulnerability details

## Impact

In the current implementation of the `Core`, the operator has to be registered to a chosen DSS to be able to stake the vault to it. However, current functionality is mostly 2step where first the operation has to be requested and then finalized after some cooldown. The problem is that when user requests staking to DSS, the function checks if it's registered or not but when it finalizes, it does not check the registration status so the user can effectively front-run the finalization transaction and unregister immediately as the `unregister()` function will show there are no vaults of the operator that are staked to this particular DSS.
 

## Proof of Concept

1. Let's say the user calls `requestUpdateVaultStakeInDSS()`, the function checks the registration status of the operator:

https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L138
```
 self.checkIfOperatorIsRegInRegDSS(operator, vaultStakeUpdateRequest.dss);
```

2. Then some time should pass before the request can be finalized called `MIN_STAKE_DELAY`:

https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L93-95
```
  if (queuedStakeUpdate.startTimestamp + Constants.MIN_STAKE_UPDATE_DELAY > block.timestamp) {
            revert OperatorStakeUpdateDelayNotPassed();
        }
```

3. Additionally, when user unregisters from DSS, the function `unregisterOperatorFromDSS` checks whether there are not any vaults staked to this particular DSS:

https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L190
```
 if (vaults.length != 0) revert AllVaultsNotUnstakedFromDSS();
```

4. The vault is only added to the vaults staked to DSS structure only when request is finalized:


https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L103-109
```
  function updateVaultStakeInDSS(State storage operatorState, address vault, IDSS dss, bool toStake) internal {
        if (toStake) {
            operatorState.vaultStakedInDssMap[dss].add(vault);
        } else {
            operatorState.vaultStakedInDssMap[dss].remove(vault);
        }
    }

```

5. So the issue lies in not checking whether operator is registered to DSS when finalizing request:

https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L89-101
```
  function validateQueuedStakeUpdate(State storage operatorState, QueuedStakeUpdate memory queuedStakeUpdate)
        internal
        view
    {
        if (queuedStakeUpdate.startTimestamp + Constants.MIN_STAKE_UPDATE_DELAY > block.timestamp) {
            revert OperatorStakeUpdateDelayNotPassed();
        }
        if (
            calculateRoot(queuedStakeUpdate) != operatorState.pendingStakeUpdates[queuedStakeUpdate.updateRequest.vault]
        ) {
            revert InvalidQueuedStakeUpdateInput();
        }
    }
```

The function only checks
MIN_STAKE_UPDATE_DELAY for staking a vault may disincentivize users to stake
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/interfaces/Constants.sol#L15


# Vulnerability details

## Impact

In the current implementation of the `Core` functionality, there is `MIN_STAKE_UPDATE_DELAY` parameter that is used to verify whether 9 days has passed since the vault stake update has been requested. In the case of unstaking it's done to make sure that the user is not able to front-run slashing. However, for the initial stake such huge amount of wait time seems extremely unreasonable and may disincentivize some users.

## Proof of Concept

There is the following `MIN_STAKE_UPDATE_DELAY` parameter that is equal to 9 days:

https://github.com/code-423n4/2024-07-karak/blob/main/src/interfaces/Constants.sol#L15
```
uint256 public constant MIN_STAKE_UPDATE_DELAY = SLASHING_WINDOW + SLASHING_VETO_WINDOW;
```

For staking reasons it's not reasonable to have such a huge amount of time because there are no any risks around front-running. However, there can be a case when DSS does not validate an operator right away in `registerOperatorToDSS()` but does this in `validateAndUpdateVaultStakeInDSS()` after some time. The problem is that in the first function it does not ignore failure meaning DSS has an ability to block registration of operator but in the second function it cannot do so and it's just formal. Therefore, there is no any reason for such delay and it can significantly impact user experience and prevent from using it as it's needed to wait for 9 days to stake and 9 days to unstake a single vault.


## Tools Used

Manual review.

## Recommended Mitigation Steps

Reduce update delay for staking operation as there are not any risks of front-running.


## Assessed type

Other
finalizeUpdateVaultStakeInDSS() can be called after an operator has unregistered from the DSS
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L146-L153


# Vulnerability details

## Vulnerability Details

`Core::requestUpdateVaultStakeInDSS()` allows only operators that are registered to a certain DSS to stake/unstake vaults to it. However this can be bypassed because `Core::finalizeUpdateVaultStakeInDSS()` doesn't check if the operator is still registered to the certain DSS, thus an operator can unregister from the DSS between the requested update and the finalization. Let's see how this is a problem:

1. An operator is registered to a certain DSS and calls `Core::requestUpdateVaultStakeInDSS` to stake a vault.
2. Now there is a 9 days delay until someone can execute the finalization of the update.
3. The operator calls `Core::unregisterOperatorFromDSS()`. Since the state is not updated from the requested vault update and the operator still doesn't have any vaults staked to the DSS, the transaction executes successfully and now the operator is no longer staked to the DSS.
4. The rest of `MIN_STAKE_UPDATE_DELAY` passes and now either the operator or a random person calls `Core::finalizeUpdateVaultStakeInDSS()` to stake the vault to the DSS. Since a check is missing here to make sure that the operator is still registered to the DSS, the transaction executes, the state is updated and now this vault is staked to the DSS.
5. Now the above-mentioned vault cannot be unstaked, because in the `Core::requestUpdateVaultStakeInDSS` there is a check if the operator is registered to the DSS, but he is not. The DSS cannot request slashing because there is a check to see if the operator is registered to the DSS, this means that  the operator can do whatever he wants and still be left unpunished by the DSS. `Core::operatorState[operator].vaultStakedInDssMap[dss]` will contain the inaccessible vault.

## Impact

Does an incorrect state update to the `vaultStakedInDssMap` mapping which could lead to a lot of problems including unfair distribution of rewards, inability of slashing.

## Tools Used

Manual Review

## Recommended Mitigation Steps

In `Operator::validateAndUpdateVaultStakeInDSS()` check if the operator is still registered to the DSS.

```diff
function validateAndUpdateVaultStakeInDSS(CoreLib.Storage storage self, QueuedStakeUpdate memory queuedStakeUpdate)
        external
    {
        State storage operatorState = self.operatorState[queuedStakeUpdate.operator];
+       checkIfOperatorIsRegInRegDSS(self, queuedStakeUpdate.operator, queuedStakeUpdate.updateRequest.dss);
        validateQueuedStakeUpdate(operatorState, queuedStakeUpdate);
        updateVaultStakeInDSS(
            operatorState,
            queuedStakeUpdate.updateRequest.vault,
            queuedStakeUpdate.updateRequest.dss,
            queuedStakeUpdate.updateRequest.toStake
        );
        delete operatorState.pendingStakeUpdates[queuedStakeUpdate.updateRequest.vault];
        IDSS dss = queuedStakeUpdate.updateRequest.dss;
        Hook
If slashingHandler for ETH is changed in the Core.sol it will be impossible to slash any of the already deployed NativeVaults.
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L308


# Vulnerability details

## Bug description
When assets are allowlisted in the Core, it's assigned a slashingHandler.

[CoreLib.sol#L73](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/CoreLib.sol#L73)
```solidity
self.assetSlashingHandlers[assets[i]] = slashingHandlers[i];
```

When NativeVault is being initialized, the slashStore is stored in storage.

[NativeVault.sol#L75](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L75)
```solidity
self.slashStore = slashStore;
```
When slashing is being finalized, `Vault.slashAssets()` is called with slashingHandler as one of the parameters. More specifically, the parameter is retrieved from the `assetSlashingHandlers` mapping of the Core contract.

[SlasherLib.sol#L135-L138](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/SlasherLib.sol#L135-L138)
```solidity
IKarakBaseVault(queuedSlashing.vaults[i]).slashAssets(
    queuedSlashing.earmarkedStakes[i],
    self.assetSlashingHandlers[
        IKarakBaseVault(queuedSlashing.vaults[i]).asset()
    ]
);
```

In the `NativeVault::slashAssets()` exists a check that ensures that `slashStore` of the NativeVault is equal to the `slashingHandler` of the underlying asset.

[NativeVault.sol#L308](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L308)
```solidity
if (slashingHandler != self.slashStore) revert NotSlashStore();
```

If after a vault was deployed with the correct slashStore, the slashingHandler is changed for ETH as an underlying asset, the slashing won't work for such vaults. 

## Impact
DoS of slashing.


## Recommended Mitigation
Allow the manager to change the slashStore of the NativeVault.


## Assessed type

Other