xanoutas/solidity-security-findings huggingface dataset:
Vault staking request to a DSS cannot be canceled, putting user funds at risk in the case of a malicios DSS
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If an operator no longer wants to stake a vault after submitting a vault staking request, it is not able to cancel that request. Considering the scenario of a malicios/compromised DSS, an operator staking a vault will not be able to cancel their staking request. Furthermore, by design anyone can finalize a staking request. This would put operators at risk of unfair slashing of their funds. This risk is somewhat mitigated by the veto committee which can cancel slashing requests. At the very least the vault funds will be unsuable for the unstaking + staking to new DSS duration of 18 days.

## Proof of Concept
There is no function to cancel a staking request and the `finalizeUpdateVaultStakeInDSS()` function is callable by anyone:

```js
    /// @notice Allows anyone to finish the queued request for an operator to update assets delegated to a DSS
    /// @dev Only operator can finish their queued request valid only after a
    /// minimum delay of `Constants.MIN_STAKE_UPDATE_DELAY` after starting the request
    function finalizeUpdateVaultStakeInDSS(Operator.QueuedStakeUpdate memory queuedStake)
        external
        nonReentrant
        whenFunctionNotPaused(Constants.PAUSE_CORE_FINALIZE_STAKE_UPDATE)
    {
        _self().validateAndUpdateVaultStakeInDSS(queuedStake);
        emit FinishedStakeUpdate(queuedStake);
    }
```

This means a malicios DSS can finalize vault staking requests even when this is not desireable by the operator.

Consider the following scenario:

An operator wants to stake its vault to a DSS, so it submits a vault staking request via `requestUpdateVaultStakeInDSS()`. This would set a timer of `MIN_STAKE_UPDATE_DELAY = 9 days` after which the request can be finalized.

During this time of 9 days, the DSS turns out to be malicios and is unfairly slashing user funds.

The operator cannot remove his request and the malicios DSS finilizes the request by calling `finalizeUpdateVaultStakeInDSS()` and then slashes the user funds.

The veto commitee does not react on time and the operator loses his funds. Note that even if the veto committee reacts on time, the user funds will be unusable for the duration of unstaking the vault(9) and staking it to a new DSS(another 9 days), since there can be only one stake/unstake request per vault at a time.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Allow operators to cancel vault staking requests


## Assessed type

Other
Wrong input parameter passed to DSS hook function - msg.sender instead of operator
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/main/src/interfaces/IDSS.sol#L15
https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L146
https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L126


# Vulnerability details

## Impact
The protocol defines an interface with optional hook functions for DSSes to implement. These functions take in the Operator as an input parameter. By design anyone can finalize a vault staking request by calling `finalizeUpdateVaultStakeInDSS()`. However, in the function flow, it calls `validateAndUpdateVaultStakeInDSS()` which passes in `msg.sender`, instead of the operator as the parameter to the low level call to the DSS.

This is not in line with the specified DSS interface by the protocol. The severity impact depends on the implementation of the DSS, however issues are certain to happen, as this is a stake update function and the DSS expects the operator as input.

## Proof of Concept
The protocol specifies the following DSS interface:

https://github.com/code-423n4/2024-07-karak/blob/main/src/interfaces/IDSS.sol#L15
```js
interface IDSS is IERC165 {
    // HOOKS

    function registrationHook(address operator, bytes memory extraData) external;
    function unregistrationHook(address operator, bytes memory extraData) external;

    function requestUpdateStakeHook(address operator, Operator.StakeUpdateRequest memory newStake) external;
    function cancelUpdateStakeHook(address operator, address vault) external;
    function finishUpdateStakeHook(address operator) external;
    function requestSlashingHook(address operator, uint256[] memory slashingPercentagesWad) external;
    function cancelSlashingHook(address operator) external;
    function finishSlashingHook(address operator) external;
}
```

As we can see, all hooks expect the operator as input. However, by design anyone can finalize a vault stake update request and the `finalizeUpdateVaultStakeInDSS()` function is callable by anyone:

https://github.com/code-423n4/2024-07-karak/blob/main/src/Core.sol#L146
```js
    /// @notice Allows anyone to finish the queued request for an operator to update assets delegated to a DSS
    /// @dev Only operator can finish their queued request valid only after a
    /// minimum delay of `Constants.MIN_STAKE_UPDATE_DELAY` after starting the request
    function finalizeUpdateVaultStakeInDSS(Operator.QueuedStakeUpdate memory queuedStake)
        external
        nonReentrant
        whenFunctionNotPaused(Constants.PAUSE_CORE_FINALIZE_STAKE_UPDATE)
    {
        _self().validateAndUpdateVaultStakeInDSS(queuedStake);
        emit FinishedStakeUpdate(queuedStake);
    }
```

This function in turn calls `validateAndUpdateVaultStakeInDSS()`, which makes the low level call to `finishUpdateStakeHook()` with `msg.sender` as parameter, and not the operator:

https://github.com/code-423n4/2024-07-karak/blob/main/src/entities/Operator.sol#L126
```js
    function validateAndUp
Operator can set custom manager, slashStore and nodeImplementation when deploying NativeVault
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L46
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L161
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L299


# Vulnerability details

## **Vulnerability Details:**

The [deployVaults](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L161) function in the Core contract allows the operator to specify the extraData parameter in the vaultConfigs struct. The extraData parameter is then used in the vault's [initialize](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L46) function, particularly when deploying a NativeVault. It is used to set the manager, slashStore, and nodeImplementation.

```solidity
    function initialize(
        ...
        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));

        if (manager == address(0) || slashStore == address(0) || nodeImplementation == address(0)) revert ZeroAddress();
        _grantRoles(manager, Constants.MANAGER_ROLE);
	...
    }
```

- The manager role is used for the [changeNodeImplementation](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L83) function, which allows modifications to the NativeNode implementation and upgrades to all NativeNodes. Additionally, it controls the [pauseNode](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L334) and [unpauseNode](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L340) functions, enabling the operator to pause or resume NativeNode activities.
- The slashStore address is designated to receive slashed assets.
- The nodeImplementation address specifies the NativeNode implementation.

## **Impact:**

- When a slashing event occurs from the core contract, the slashed assets are sent to the slashingHandler contract. If the slashingHandler address passed down by the core does not match the slashStore address set by the operator during vault initialization, the transaction will revert. This allows an operator to prevent their vault from being slashed by setting a different address.
- Additionally, the operator can pause or unpause certain functionalities, blocking withdrawals, and change the node implementation, which could lead to unauthorized malicious upgrades.

## **Tools Used:**

- Manual analysis

## **Recommendation:**

Preset the manager,
Violation of Invariant Allowing DSSs to Slash Unregistered Operators
Audit: 2024-07-karak-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L130
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L220
https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L248


# Vulnerability details

## **Vulnerability Details:**

Operators select DSSs to allocate their funds to, and for each DSS, they can choose a list of vaults to stake. The selected vaults are fully staked to the chosen DSS. 

To unstake, the operator must adhere to a MIN_STAKE_UPDATE_DELAY of 9 days, which prevents front-running a slashing event. An operator can fully unregister from a DSS once all the vaults are unstaked. The DSS has the ability to slash any malicious behaviour that occurred before the withdrawal initiation for up to 7 days.

The problem is, the current implementation breaks one of the main invariants (confirmed by sponsors).

- [**Only DSSs an operator is registered with can slash said operator**](https://code4rena.com/audits/2024-07-karak-restaking#top:~:text=Only%20DSSs%20an%20operator%20is%20registered%20with%20can%20slash%20said%20operator)

Consider the following scenario:

- Operator calls the [requestUpdateVaultStakeInDSS](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L130) function to unstake Vault 1 from DSS, initiating the 9-day `MIN_STAKE_UPDATE_DELAY`.
- After 8 days, the DSS calls the [requestSlashing](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L220) function on Operator’s Vault 1, starting the 2-day `SLASHING_VETO_WINDOW`.
- After 1 day, the operator calls the [finalizeUnstaking](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L248C14-L248C30) function to finalize unstaking Vault 1 from DSS and the [unregisterOperatorFromDSS](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L113C14-L113C39) function to unregister from DSS.
- After another day, the DSS (or anyone) calls the [finalizeSlashing](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/Core.sol#L248C14-L248C30) function to finalize the slash on the operator, who is no longer registered with the DSS.

## **Proof Of Concept:**

```solidity
    function test_slash_unregistered_operator() public {
        // register operator to dss, deploy vaults, stake vaults to dss
        stake_vaults_to_dss();
        // check if operator is registered to dss
        assertEq(true, core.isOperatorRegisteredToDSS(operator, dss));
        // unstake vaults from dss
        address[] memory operatorVaults = core.fetchVaultsStakedInDSS(operator, dss);
        Operator.StakeUpdateRequest memory stakeUpdate =
            Operator.StakeUpdateRequest({vault: operatorVaults[0], dss:
nodeOwners that deposited assets after slashing will account for the slashed assets
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

`nodeOwners` that are not present during slashing will incur the loss of the slashed assets.

## Bug Description
When a `nodeOwner` deposits into the vault, a share is minted to the nodeOwner signifying a deposit which is equivalent of the ETH on the beacon (validateWithdrawalCredentials)

But an oversight allows a `nodeOwner` to have his node slashed even if he wasn't in the vault when the slashing happens, this is because the actual slashing ( ETH out of nodes ) happens not in the same time as the [slashing](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L299) it self.

Consider this scenario :

1. A vault operator have his vault slashed 10 `ETH` due to malicious behavior with a `totalAsstes()` and `totalSupplly()` of `500e18` before slashing.
2. bob, not knowing of the slashing deploys and deposit in to a node, lets say `100e18` ( or 101, but this will not change the calculations) shares minted
3. now bob, calls the [starSnapshort](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L112) to update the node states and the [_transferToSlahsStore](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L456) is called to transfer the slashed assets out of the node.
4. but due to the vault being slashed ( totalAssets - 10), he will now bear the responsibility and have his node slashed even though  the node wasnt even active when the vault gets slashed

this is due to the calculations used in order to transfer the slashed assets

```solidity
 uint256 o = _decimalsOffset();
        if (o == 0) {
            return FixedPointMathLib.fullMulDiv(shares, totalAssets() + 1, _inc(totalSupply()));
        }
```

remember `totalAssets()` is now minus 10 ( slashed)

using the above calculations 
```solidity
100e18 * 590e18 + 1 / 600e18 + 1 = 99319467554076539101
```

meaning the node has been slashed  approx `680532445923460899` since [totalRestakedETH](https://github.com/code-423n4/2024-07-karak/blob/f5e52fdcb4c20c4318d532a9f08f7876e9afb321/src/NativeVault.sol#L503) == 100e18 ( assets)
```solidity
503         self.ownerToNode[_of].totalRestakedETH += assets;
```

## Tools Used

Manual review

Remix

## Recommended Mitigation Steps



The issue can be mitigated by promptly removing the slashed assets immediately after the slashing event. This ensures that node owners that are not present at the time will not even be aware that slashing occurred 


## Assessed type

Math
Upgraded Q -> 2 from #22 [1723479041527]
Audit: 2024-07-karak-findings
Severity: MEDIUM

Judge has assessed an item in Issue #22 as 2 risk. The relevant finding follows:

 [01] Protocol can fail to support fee-on-transfer tokens
Description
According to https://github.com/code-423n4/2024-07-karak?tab=readme-ov-file#erc20-token-behaviors-in-scope, fee-on-transfer tokens are in scope and should be supported. Calling the following slashAssets function further calls the handleSlashing function below that executes SafeTransferLib.safeTransferFrom(address(token), msg.sender, address(this), amount) and then SafeTransferLib.safeTransfer(address(token), address(0), amount). When token is a fee-on-transfer token, after executing SafeTransferLib.safeTransferFrom(address(token), msg.sender, address(this), amount), the SlashingHandler contract’s balance of such token would be less than amount because such token’s transfer fee is deducted, which then causes the execution of SafeTransferLib.safeTransfer(address(token), address(0), amount) to revert. As a result, the DSS fails to slash the operator’s vault when the vault’s underlying token is a fee-on-transfer token, and the protocol fails to support such fee-on-transfer token.

https://github.com/code-423n4/2024-07-karak/blob/53eb78ebda718d752023db4faff4ab1567327db4/src/Vault.sol#L193-L205

    function slashAssets(uint256 totalAssetsToSlash, address slashingHandler)
        external
        onlyCore
        returns (uint256 transferAmount)
    {
        transferAmount = Math.min(totalAssets(), totalAssetsToSlash);

        // Approve to the handler and then call the handler which will draw the funds
        SafeTransferLib.safeApproveWithRetry(asset(), slashingHandler, transferAmount);
        ISlashingHandler(slashingHandler).handleSlashing(IERC20(asset()), transferAmount);
        ...
    }
https://github.com/code-423n4/2024-07-karak/blob/53eb78ebda718d752023db4faff4ab1567327db4/src/SlashingHandler.sol#L52-L59

    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);
    }
Recommended Mitigation
The handleSlashing function can be updated to transfer the actual token amount received by the SlashingHandler contract through the SafeTransferLib.safeTransferFrom function call when calling the SafeTransferLib.safeTransfer function.
Insufficient Funds for Slashing Due to Time Delay Could Render Slashing Mechanism Ineffective
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-karak/blob/ab18e1f6c03e118158369527baa2487b2b4616b1/src/entities/SlasherLib.sol#L94-L124
https://github.com/code-423n4/2024-07-karak/blob/ab18e1f6c03e118158369527baa2487b2b4616b1/src/entities/SlasherLib.sol#L126-L151


# Vulnerability details

## Impact
It can lead to failed slashing attempts and differences between expected and actual slashed amount. 

## Proof of Concept
The problem stems from the time delay between requesting and finalizing a slashing operation.

In the [`requestSlashing` function:](https://github.com/code-423n4/2024-07-karak/blob/ab18e1f6c03e118158369527baa2487b2b4616b1/src/entities/SlasherLib.sol#L94-L124)

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

The `earmarkedStakes` are calculated based on the current total assets of the vault.

In the [`finalizeSlashing` function:](https://github.com/code-423n4/2024-07-karak/blob/ab18e1f6c03e118158369527baa2487b2b4616b1/src/entities/SlasherLib.sol#L126-L151)

```solidity
function finalizeSlashing(CoreLib.Storage storage self, QueuedSlashing memory queuedSlashing) external {
    // ...
    if (queuedSlashing.timestamp + Constants.SLASHING_VETO_WINDOW > block.timestamp) {
        revert MinSlashingDelayNotPassed();
    }
    // ...
    for (uint256 i = 0; i < queuedSlashing.vaults.length; i++) {
        IKarakBaseVault(queuedSlashing.vaults[i]).slashAssets(
            queuedSlashing.earmarkedStakes[i],
            self.assetSlashingHandlers[IKarakBaseVault(queuedSlashing.vaults[i]).asset()]
        );
    }
    // ...
}
```

The slashing is only executed after the `SLASHING_VETO_WINDOW` has passed. During this time window, the total assets in the vault could decrease, potentially leading to insufficient funds for slashing when `finalizeSlashing` is called.


## Tools Used
Manual review

## Recommended Mitigation Steps
Consider implementing a locking mechanism that prevents withdrawals or transfers of the earmarked assets during the veto window. Alternatively, recalculate the slashable amount at the time of finalization based on the current total assets, ensuring that the slashed amount is always proportional to the available assets. Then, implement checks in the `finalizeSlashing` function to handle cases where the available assets are less than the originally earmarked amount, possibly by slashing a percentage of the current assets rather than a fixed amount.


## Assessed type

ETH-Transfer
Protocol could be tricked by some tokens stemming from the fact that rebasing/FOT tokens are not handled correctly
Audit: 2024-07-karak-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Firstly, note that this has been stated in the readMe: https://github.com/code-423n4/2024-07-karak?tab=readme-ov-file#erc20-token-behaviors-in-scope


```
### ERC20 token behaviors in scope

| Question                                                                                                                                                   | Answer |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| [Balance changes outside of transfers](https://github.com/d-xo/weird-erc20?tab=readme-ov-file#balance-modifications-outside-of-transfers-rebasingairdrops) | Yes    |

```

The protocol aims to work with all sorts of ERC20 tokens including tokens that charge a fee on transfer including `PAXG`. Deposits and withdrawals of these tokens when being used as staking tokens in `vault.sol` are not properly handled which can cause a number of accounting issues.
The same effect, although to a less degree can be observed with tokens like stETH which has the [1 wei corner case issue](https://docs.lido.fi/guides/lido-tokens-integration-guide/#1-2-wei-corner-case) in which the token amount transferred can be a bit less than the amount entered in. This also includes other rebasing tokens, and tokens with variable balances or airdrops as the direction of rebasing can lead to the same effect as FOT tokens, which token amount being less than internal accounting suggests, same as extra tokens from positive rebasing and airdrops being lost due it also not matching internal accounting and no other way to claim these tokens.

This means that we should take into account rebasing tokens. Also, the `AMPL` token, in particular, has a `_gonsPerFragment` variable that is used to change the balances of other users. The goal of that is to keep a stable price by manipulating the supply.

This creates a huge issue for this particular protocol. 

Rebasing tokens can significantly impact the functionality of this Vault contract. The contract is designed as an ERC4626 vault, which assumes a stable relationship between shares and underlying assets. However, rebasing tokens can disrupt this relationship, causing several issues as mentioned above and even more in the POC.


## Proof of Concept
Take a look at Deposit Function:  https://github.com/code-423n4/2024-07-karak/blob/ab18e1f6c03e118158369527baa2487b2b4616b1/src/Vault.sol#L78-L88
```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);
}
```
This deposit function calls the Solady ERC4626 implementation's `deposit` function.