xanoutas/solidity-security-findings huggingface dataset:
MAX_SLASHING_PERCENT_WAD can be exceeded if a NativeVault's balance is unstable
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/SlasherLib.sol#L79-L92


# Vulnerability details

## Summary

MAX_SLASHING_PERCENT_WAD can be exceeded if a NativeNode balance goes down due to withdrawn rewards from staking on the beacon chain. It is possible that the Veto committee won't be able to avoid it. This would also happen every time a slashing request is done with the maximum allowed percentage and the balance drops.

## Vulnerability Details
fetchEarmarkedStakes in SlasherLib.sol is called when requesting a slash, and we have to wait 2 days after that until the finalizeSlashing() in Core.sol could be called.

```solidity
    function requestSlashing(
        CoreLib.Storage storage self,
        IDSS dss,
        SlashRequest memory slashingMetadata,
        uint256 nonce
    ) external returns (QueuedSlashing memory queuedSlashing) {
        validateRequestSlashingParams(self, slashingMetadata, dss);
@>>     uint256[] memory earmarkedStakes = fetchEarmarkedStakes(slashingMetadata);

        queuedSlashing = QueuedSlashing({
            dss: dss,
            timestamp: uint96(block.timestamp),
            operator: slashingMetadata.operator,
            vaults: slashingMetadata.vaults,
@>>         earmarkedStakes: earmarkedStakes,
            nonce: nonce
        });
       ............. more code
```

```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
            );
        }
    }
```

But if in the next 2 days the total assets of the NativeVault have gone down due to awards withdrawn or validators opting out, the slash request that has been submitted by the DSS could end up overriding the MAX_SLASHING_PERCENT_WAD and slashing a higher percentage of the total assets, as the finalizeSlashing in SlasherLib.sol doesn't currently validate the balance and just slashes the amount from the slashing request:
```solidity

    function finalizeSlashing(CoreLib.Storage storage self, QueuedSlashing memory queuedSlashing) external {
        bytes32 slashRoot = calculateRoot(queuedSlashing);
        if (!self.slashingRequests[slashRoot]) revert InvalidSlashingParams();
        if (queuedSlashing.timestamp + Constants.SLASHING_VETO_WINDOW > block.timestamp) {
            revert MinSlashingDelayNotPassed();
        }
        delete self.slashingRequests[slashRoot];

        for (uint256 i = 0; i < queuedSlashing.vaults.length; i++) {
            IKarakBaseVault(queuedSlashing.vaults[i]).slashAsse
Front-running Vulnerability in NativeVault Snapshot Process
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Intro

The NativeVault contract's snapshot process is vulnerable to a front-running attack that could be exploited by a malicious node owner. This vulnerability stems from the ability of any node owner to initiate a snapshot and the subsequent ability for anyone to submit validator proofs for that snapshot. This will lead to a myriad of issues like denial of service and griefing attacks.

## Details

## Attack Vector

A malicious node owner can affect pending snapshots of other users through the following mechanism:

1. Monitor the mempool for `startSnapshot` transactions from other node owners.
2. Front-run these transactions with their own `startSnapshot` call.
3. Quickly follow up with `validateSnapshotProofs` calls using manipulated proofs.

## Impact

1. **Snapshot Overwriting**: 
- When a malicious actor front-runs a legitimate `startSnapshot` call, it overwrites the `currentSnapshotTimestamp` and `currentSnapshot` in the contract's state.
- This effectively cancels the pending snapshot of the legitimate user, as the contract now considers the malicious actor's snapshot as the current one.

2. **Proof Invalidation Dos**: 
- The `validateSnapshotProofs` function checks against the `currentSnapshotTimestamp`:

```solidity
if (validatorDetails.lastBalanceUpdateTimestamp >= node.currentSnapshotTimestamp) {
    revert ValidatorAlreadyProved();
}
```

- By setting a new `currentSnapshotTimestamp`, the attacker invalidates any proofs that legitimate users were preparing to submit for their own snapshots.

3. **Balance Update Manipulation**:
- The attacker can submit manipulated proofs that misrepresent the balance changes of validators.
- This affects the `snapshot.balanceDeltaWei` calculation, which directly impacts the balance updates for all users.

4. **Griefing through Delayed Balance Updates**: 

- Legitimate users whose snapshots are overwritten will have to wait and attempt to start a new snapshot later.

- This delay could prevent timely recognition of balance changes, potentially leading to:
  - Delayed reward distributions
  - Inaccurate representation of user stakes
  - Misalignment with actual validator performances on the beacon chain

5. **Incorrect Balance Calculations**:
- If the attacker successfully submits manipulated proofs, it could lead to:
  - Overstating of the attacker's balance increases
  - Understating of the attacker's balance decreases
  - Potential understating of other users' balance increases
  - Possible overstating of other users' balance decreases

6. **Economic Losses**:
- Users might miss out on timely withdrawal opportunities due to understated balances.

## Code Sections

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

```solidity
function startSnapshot(bool revertIfNoBalanceChange)
    external
    nonReentrant
    nodeExists(msg.sender)
    whenFunct
Operators can finalize their vault staking to DSS even after unregistering from them due to missing registered check for operator on 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


# Vulnerability details

## Impact
- The `finalizeUpdateVaultStakeInDSS` function allows the operators to still register to the DSS even after unregistering from them after placing the vault stake update request.
- This allows them to prevent from getting slashed from the operators and perform their tasks.

## Proof of Concept
```solidity
    function finalizeUpdateVaultStakeInDSS(Operator.QueuedStakeUpdate memory queuedStake)
        external
        nonReentrant
        whenFunctionNotPaused(Constants.PAUSE_CORE_FINALIZE_STAKE_UPDATE)
    {
        _self().validateAndUpdateVaultStakeInDSS(queuedStake);
        emit FinishedStakeUpdate(queuedStake);
    }
```

- Operator registers them to a DSS.
- Then deploys their vault and creates a request to register on DSS.
- Then unregisters from DSS, the operator will be able to perform this task as they have no vault staked to that DSS (only request is placed, but to the mapping corresponding to that DSS no vaults are added in the array).
- Then call the `finalizeUpdateVaultStakeInDSS` to finally update their stake to the DSS.
- Thus, operator have successfully put their stake to DSS, but are actually not registered.

## Tools Used
Manual Review

## Recommended Mitigation Steps
In the `finalizeUpdateVaultStakeInDSS`, add the following function check:
```solidity
isOperatorRegisteredToDSS(queuedStake.operator, queuedStake.updateRequest.dss)
```


## Assessed type

Context
no  checks to find whether any  vault is pending for finailsed staking in a DAA while unregistering the DSS
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/Operator.sol#L181-L203


# Vulnerability details

## Impact
There are no  checks to find whether any  vault is pending for finailsed staking in a DAA while unregistering the DSS…. There are no checks in finalisedstake to find where the vault is staked in the dss.

## Proof of Concept
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/entities/Operator.sol#L181-L203
```solidity    
    function unregisterOperatorFromDSS(
        ....
    ) external {
        State storage operatorState = self.operatorState[operator];
        // Checks if all operator delegations are zero
        address[] memory vaults = getVaultsStakedToDSS(operatorState, dss);
        if (vaults.length != 0) revert AllVaultsNotUnstakedFromDSS();
        if (!isOperatorRegisteredToDSS(self, operator, dss)) revert OperatorNotValidatingForDSS();


        self.operatorState[operator].dssMap.remove(address(dss));
        HookLib.callHookIfInterfaceImplemented({
            ......
        });
    }
```   
Even though vaults.length = 0 there can be vault pending for finalised stake in that DSS which is going to be unregistered unregistered.
Here while unregisterig a dss from an operator there are no checks done to ensure that any vault is pending for finalised staking.

So when the ` validateAndUpdateVaultStakeInDSS` is called , a new vault is added to the unregistered DSS mentioned in the `queuedStakeUpdate`

## Tools Used
Manual.
## Recommended Mitigation Steps
Check pendingStakeUpdates for the vault in that DSS.


## Assessed type

Context
NativeVault.sol :: validateExpiredSnapshot() will always revert, making it impossible for users from initiating a new snapshot.
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
**`validateExpiredSnapshot()`** is intended to be callable by anyone if the node owner delays starting a new snapshot beyond a specified period, specifically 7 days. However, the function is rendered ineffective because it checks if **`node.currentSnapshotTimestamp != 0`**, which always causes the transaction to revert. This issue prevents the initiation of a new snapshot, rendering the function useless.

## Proof of Concept
**`validateExpiredSnapshot()`** is implemented as follows:
```Solidity
function validateExpiredSnapshot(address nodeOwner)
        external
        nonReentrant
        nodeExists(nodeOwner)
        whenFunctionNotPaused(Constants.PAUSE_NATIVEVAULT_VALIDATE_EXPIRED_SNAPSHOT)
    {
        NativeVaultLib.NativeNode storage node = _state().ownerToNode[nodeOwner];
        // Only check if the last snapshot has expired or not
        if (node.lastSnapshotTimestamp + Constants.SNAPSHOT_EXPIRY > block.timestamp) {
            revert SnapshotNotExpired();
        }

        _startSnapshot(node, false, nodeOwner);
    }
```
As you can see, the function checks if the snapshot has expired and, if so, calls **`_startSnapshot()`** to initiate a new snapshot.
```Solidity
function _startSnapshot(NativeVaultLib.NativeNode storage node, bool revertIfNoBalanceChange, address nodeOwner)
        internal
    {
        if (node.currentSnapshotTimestamp != 0) revert PendingIncompleteSnapshot();

///...
}
```
As you can see, for a new snapshot to be created, the previous one must be completed; otherwise, the transaction will revert. This makes the snapshot expiry time ineffective, as users cannot invoke **`validateExpiredSnapshot()`**. The only way to initiate a new snapshot is to wait for the current one to finish and then use **`startSnapshot()`**.

The following POC demonstrates the issue. To run it, copy the code into **`nativeVault.t.sol`**.
```Solidity
function test_validateExpiredSnapshotAlwaysRervert(bytes32 parentRoot) public {
        vm.assume(parentRoot != bytes32(0));
        test_validateWithdrawalCredentials();

        address node = nativeVault.createNode();
        assertEq(nativeVault.getNodeOwner(node), address(this));

        vm.store(Constants.BEACON_ROOTS_ADDRESS, timestamp_idx(block.timestamp), bytes32(uint256(block.timestamp)));
        vm.store(Constants.BEACON_ROOTS_ADDRESS, root_idx(block.timestamp), parentRoot);

        //do a snapshot
        nativeVault.startSnapshot(false);

        assertEq(nativeVault.currentSnapshotTimestamp(address(this)), block.timestamp);
        assertEq(nativeVault.currentSnapshot(address(this)).parentBeaconBlockRoot, parentRoot);

        //Increment time to Constants.SNAPSHOT_EXPIRY
        vm.warp(block.timestamp + 7 days);
        vm.expectRevert(PendingIncompleteSnapshot.selector);
        nativeVault.validateExpi
Slashing may sometimes fail for stETH vaults due to its 1-2 wei corner problem
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Slashing stETH vaults may sometimes fail and always revert due to its innate 1-2 wei corner issue.

## Proof of Concept

1. Context 

The protocol plans on working with all ERC20 tokens, and that includes stETH. stETH however, has intrinsic rounding down problems. It's a known issue, and the stETH balance of an account could be lower of [1-2 wei](https://github.com/lidofinance/lido-dao/issues/442) because of rounding down. As such the amount received during transfer can be short 1 or 2 wei. This isn't much, but in context of slashing, in which an amount is transferred from the vault is also expected to be the amount transferred to address(0), this can mean unexpected failure of the `handleSlashing` function, potentially dossing/delaying slashing of malicious operators.

2. Bug Location

In SlashingHandler.sol, we see the `handleSlashing` function. The function transfers the amount of tokens to slash from the vault to the handler. After, the function attempts to transfer the "same" amount address(0).

```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);
    }
```
3. Final effect
As explained that the amount received during transfer can be 1 or 2 wei shorter, the function's attempt to send the same amount fails and slashing is not completed.

4. Runnable POC

The gist link below contains modifications of some of the provided tests, including a mock token that simulates stETH losing 1 wei upon transfers, and instructions on how to run them.

https://gist.github.com/ZanyBonzy/033ee1ca8aae4969a676d8ce83a68c9d

The expected should look like this with an error that reads ERC20InsufficientBalance
<img width="1249" alt="img" src="https://gist.github.com/user-attachments/assets/a4ced83d-5ab6-4a1b-a3f1-2b552c22bc14">

## Tools Used
Manual Review

## Recommended Mitigation Steps

Recommend querying token balances before and after every transfer, and transferring the difference between them instead.


## Assessed type

Token-Transfer
Enabled by default rebasing rewards from USDB/WETH will be lost upon deployment to blast
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Vault.sol#L101
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Vault.sol#L86
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Vault.sol#L118


# Vulnerability details

### Impact

Loss of enabled-by-default yield for USDB and WETH on blast when the tokens are used in vaults.

### Proof of Concept

From the provided information in the readme, all possible ERC20s are to be used. Also, the chains to deploy on includes blast

> ERC20 used by the protocol	Any (all possible ERC20s)
> Chains the protocol will be deployed on	Ethereum,Arbitrum,Base,BSC,Optimism,OtherBlast, Mantle, K2(karaks l2 based on op stack)

USDB and WETH on blash are two ERC20 tokens, and they have their yield modes set as automatic by default. 

From [Blast Docs](https://docs.blast.io/building/guides/weth-yield)

> Similar to ETH, WETH and USDB on Blast is also rebasing and follows the same yield mode configurations.

> However, unlike ETH where contracts have Disabled yield by default, WETH and USDB accounts have Automatic yield by default for both EOAs and smart contracts.

Vault.sol uses internal accounting to track all the tokens deposited by the users. Taking the `deposit` function as an example:
Users will deposit their USDB/WETH into the subsequent vaults, get minted shares in return. With the period while their tokens are locked in the vault, the vault will accrue these rebasing rewards which have been established to be automatic. The same can occur in the other `deposit` function and the `mint` function as they both get tokens into the protocol.

```solidity
    function deposit(uint256 assets, address to)
        public
        override(ERC4626, IVault)
        whenFunctionNotPaused(Constants.PAUSE_VAULT_DEPOSIT)
        nonReentrant
        returns (uint256 shares)
    {
        if (assets == 0) revert ZeroAmount();
        return super.deposit(assets, to);
    }
```

So in using USDB/WETH as vault assets, the accrued yield due to automatic rebasing are lost as they cannot be claimed. Also, due to the lack of configuration for blast, there's no way to change the yield mode of the tokens.

### Tools Used
Manual code review

### Recommended Mitigation Steps

Add the ability to set native RebasingERC20 token to CLAIMABLE and implement a way to claim the yields. 


## Assessed type

Context
SlashingHandler::handleSlashing will never work if the token is a fee-on-transfer or rebase token
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
While a proper way to handle fee-on-transfer tokens slashing in the `SlashingHandler`'s `handleSlashing` function would be checking the `token.balanceOf(address(this))` value after the `transferFrom` call completes, the current implementation of the `handleSlashing` function doesn't have this feature and thus will always revert and be DoS'ed to slash fee-on-transfer tokens.

**Please note that this comment was already made by Slither, but it doesn't stress enough how disasterous and impactful not implementing the proper handling for real `token.balanceOf(address(this))` is.`

The problem is that the `SlashingHandler` will attempt to transfer `amount` of `token` to the `address(0)`.

But the `amount` is actually the initial argument that the `handleSlashing` function receives, and it is the argument used to indicate how much of a `token` to transfer from the `msg.sender`.

But the real amount of `token` received may be less than the initial `amount`, if the `token` is a rebalance or a fee-on-transfer token.

And during the `SafeTransferLib.safeTransfer(address(token), address(0), amount);` call this function will always revert.

## References
```solidity
    /// @notice slashes the specified amount of the token
    /// The vault needs to give approval to the slashing handler prior to slashing
    /// Pulls the assets form the vault and send them to address(0)
    /// @param token address of the token to be slashed
    /// @param amount quantity of assets to be slashed
    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);
    }
```

- https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/SlashingHandler.sol#L47C1-L59C6

## Tools Used
Manual review.

## Recommended Mitigation Steps
To make the `SlashingHandler` contract compatible with fee-on-transfer and rebase tokens, the following fix should be implemented:
```diff
    /// @notice slashes the specified amount of the token
    /// The vault needs to give approval to the slashing handler prior to slashing
    /// Pulls the assets form the vault and send them to address(0)
    /// @param token address of the token to be slashed
    /// @param amount quantity of assets to be slashed
    function handleSlashing(IERC20 token, uint256 amount) external {
        if (amount == 0) revert ZeroAmount();
        if (!_config().supportedAssets[token]) revert UnsupportedAsset();

        SafeTransferLib.safeTransferFrom(address(token), msg.sender,