msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: RocketMinipoolDelegate - Redundant refund() call on forced finalization
Severity: medium

Description:
The `RocketMinipoolDelegate.refund` function will force finalization if a user previously distributed the pool. However, `_finalise` already calls `_refund()` if there is a node refund balance to transfer, making the additional call to `_refund()` in `refund()` obsolete.\\n```\\nfunction refund() override external onlyMinipoolOwnerOrWithdrawalAddress(msg.sender) onlyInitialised {\\n    // Check refund balance\\n    require(nodeRefundBalance > 0, "No amount of the node deposit is available for refund");\\n    // If this minipool was distributed by a user, force finalisation on the node operator\\n    if (!finalised && userDistributed) {\\n        \\_finalise();\\n    }\\n    // Refund node\\n    \\_refund();\\n}\\n```\\n\\n```\\nfunction \\_finalise() private {\\n    // Get contracts\\n    RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager"));\\n    // Can only finalise the pool once\\n    require(!finalised, "Minipool has already been finalised");\\n    // Set finalised flag\\n    finalised = true;\\n    // If slash is required then perform it\\n    if (nodeSlashBalance > 0) {\\n        \\_slash();\\n    }\\n    // Refund node operator if required\\n    if (nodeRefundBalance > 0) {\\n        \\_refund();\\n    }\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nFixed in https://github.com/rocket-pool/rocketpool/tree/77d7cca65b7c0557cfda078a4fc45f9ac0cc6cc6 by refactoring `refund()` to avoid a double invocation of `_refund()` in the `_finalise()` codepath.\\nFixed per the recommendation. Thanks.\\nWe recommend refactoring the if condition to contain `_refund()` in the else branch.
Vulnerability: Sparse documentation and accounting complexity  Acknowledged
Severity: medium

Description:
Throughout the project, inline documentation is either sparse or missing altogether. Furthermore, few technical documents about the system's design rationale are available. The recent releases' increased complexity makes it significantly harder to trace the flow of funds through the system as components change semantics, are split into separate contracts, etc.\\nIt is essential that documentation not only outlines what is being done but also why and what a function's role in the system's “bigger picture” is. Many comments in the code base fail to fulfill this requirement and are thus redundant, e.g.\\n```\\n// Sanity check that refund balance is zero\\nrequire(nodeRefundBalance == 0, "Refund balance not zero");\\n```\\n\\n```\\n// Remove from vacant set\\nrocketMinipoolManager.removeVacantMinipool();\\n```\\n\\n```\\nif (ownerCalling) {\\n    // Finalise the minipool if the owner is calling\\n    \\_finalise();\\n```\\n\\nThe increased complexity and lack of documentation can increase the likelihood of developer error. Furthermore, the time spent maintaining the code and introducing new developers to the code base will drastically increase. This effect can be especially problematic in the system's accounting of funds as the various stages of a Minipool imply different flows of funds and interactions with external dependencies. Documentation should explain the rationale behind specific hardcoded values, such as the magic `8 ether` boundary for withdrawal detection. An example of a lack of documentation and distribution across components is the calculation and influence of `ethMatched` as it plays a role in:\\nthe minipool bond reducer,\\nthe node deposit contract,\\nthe node manager, and\\nthe node staking contract.

Impact:

Recommended mitigation:
As the Rocketpool system grows in complexity, we highly recommend significantly increasing the number of inline comments and general technical documentation and exploring ways to centralize the system's accounting further to provide a clear picture of which funds move where and at what point in time. Where the flow of funds is obscured because multiple components or multi-step processes are involved, we recommend adding extensive inline documentation to give context.
Vulnerability: RocketNodeDistributor - Missing extcodesize check in dynamic proxy  Won't Fix
Severity: medium

Description:
`RocketNodeDistributor` dynamically retrieves the currently set delegate from the centralized `RocketStorage` contract. The target contract (delegate) is resolved inside the fallback function. It may return `address(0)`. `rocketStorage.getAddress()` does not enforce that the requested settings key exists, which may lead to `RocketNodeDistributor` delegate-calling into `address(0)`, which returns no error. This might stay undetected when calling `RocketNodeDistributorDelegate.distribute()` as the method does not return a value, which is consistent with calling a target address with no code.\\n```\\nfallback() external payable {\\n    address \\_target = rocketStorage.getAddress(distributorStorageKey);\\n    assembly {\\n        calldatacopy(0x0, 0x0, calldatasize())\\n        let result := delegatecall(gas(), \\_target, 0x0, calldatasize(), 0x0, 0)\\n        returndatacopy(0x0, 0x0, returndatasize())\\n        switch result case 0 {revert(0, returndatasize())} default {return (0, returndatasize())}\\n    }\\n}\\n```\\n\\n```\\nfunction getAddress(bytes32 \\_key) override external view returns (address r) {\\n    return addressStorage[\\_key];\\n}\\n```\\n

Impact:

Recommended mitigation:
Before delegate-calling into the target contract, check if it exists.\\n```\\nassembly {\\n    codeSize := extcodesize(\\_target)\\n}\\nrequire(codeSize > 0);\\n```\\n
Vulnerability: RocketDAOProtocolSettingsRewards - settings key collission  Acknowledged
Severity: medium

Description:
A malicious user may craft a DAO protocol proposal to set a rewards claimer for a specific contract, thus overwriting another contract's settings. This issue arises due to lax requirements when choosing safe settings keys.\\n```\\nfunction setSettingRewardsClaimer(string memory \\_contractName, uint256 \\_perc) override public onlyDAOProtocolProposal {\\n    // Get the total perc set, can't be more than 100\\n    uint256 percTotal = getRewardsClaimersPercTotal();\\n    // If this group already exists, it will update the perc\\n    uint256 percTotalUpdate = percTotal.add(\\_perc).sub(getRewardsClaimerPerc(\\_contractName));\\n    // Can't be more than a total claim amount of 100%\\n    require(percTotalUpdate <= 1 ether, "Claimers cannot total more than 100%");\\n    // Update the total\\n    setUint(keccak256(abi.encodePacked(settingNameSpace,"rewards.claims", "group.totalPerc")), percTotalUpdate);\\n    // Update/Add the claimer amount\\n    setUint(keccak256(abi.encodePacked(settingNameSpace, "rewards.claims", "group.amount", \\_contractName)), \\_perc);\\n    // Set the time it was updated at\\n    setUint(keccak256(abi.encodePacked(settingNameSpace, "rewards.claims", "group.amount.updated.time", \\_contractName)), block.timestamp);\\n}\\n```\\n\\nThe method updates the rewards claimer for a specific contract by writing to the following two setting keys:\\n`settingNameSpace.rewards.claimsgroup.amount<_contractName>`\\n`settingNameSpace.rewards.claimsgroup.amount.updated.time<_contractName>`\\nDue to the way the settings hierarchy was chosen in this case, a malicious proposal might define a `<_contractName> = .updated.time<targetContract>` that overwrites the settings of a different contract with an invalid value.\\nNote that the issue of delimiter consistency is also discussed in issue 5.12.\\nThe severity rating is based on the fact that this should be detectable by DAO members. However, following a defense-in-depth approach means that such collisions should be avoided wherever possible.

Impact:

Recommended mitigation:
We recommend enforcing a unique prefix and delimiter when concatenating user-provided input to setting keys. In this specific case, the settings could be renamed as follows:\\n`settingNameSpace.rewards.claimsgroup.amount.value<_contractName>`\\n`settingNameSpace.rewards.claimsgroup.amount.updated.time<_contractName>`
Vulnerability: RocketDAOProtocolSettingsRewards - missing setting delimiters  Acknowledged
Severity: medium

Description:
Settings in the Rocket Pool system are hierarchical, and namespaces are prefixed using dot delimiters.\\nCalling `abi.encodePacked(<string>, <string>)` on strings performs a simple concatenation. According to the settings' naming scheme, it is suggested that the following example writes to a key named: `<settingNameSpace>.rewards.claims.group.amount.<_contractName>`. However, due to missing delimiters, the actual key written to is: `<settingNameSpace>.rewards.claimsgroup.amount<_contractName>`.\\nNote that there is no delimiter between `claims|group` and `amount|<_contractName>`.\\n```\\nfunction setSettingRewardsClaimer(string memory \\_contractName, uint256 \\_perc) override public onlyDAOProtocolProposal {\\n    // Get the total perc set, can't be more than 100\\n    uint256 percTotal = getRewardsClaimersPercTotal();\\n    // If this group already exists, it will update the perc\\n    uint256 percTotalUpdate = percTotal.add(\\_perc).sub(getRewardsClaimerPerc(\\_contractName));\\n    // Can't be more than a total claim amount of 100%\\n    require(percTotalUpdate <= 1 ether, "Claimers cannot total more than 100%");\\n    // Update the total\\n    setUint(keccak256(abi.encodePacked(settingNameSpace,"rewards.claims", "group.totalPerc")), percTotalUpdate);\\n    // Update/Add the claimer amount\\n    setUint(keccak256(abi.encodePacked(settingNameSpace, "rewards.claims", "group.amount", \\_contractName)), \\_perc);\\n    // Set the time it was updated at\\n    setUint(keccak256(abi.encodePacked(settingNameSpace, "rewards.claims", "group.amount.updated.time", \\_contractName)), block.timestamp);\\n}\\n```\\n

Impact:

Recommended mitigation:
We recommend adding the missing intermediate delimiters. The system should enforce delimiters after the last setting key before user input is concatenated to reduce the risk of accidental namespace collisions.
Vulnerability: Redundant double casts  Acknowledged
Severity: low

Description:
`_rocketStorageAddress` is already of contract type `RocketStorageInterface`.\\n```\\n/// @dev Set the main Rocket Storage address\\nconstructor(RocketStorageInterface \\_rocketStorageAddress) {\\n    // Update the contract address\\n    rocketStorage = RocketStorageInterface(\\_rocketStorageAddress);\\n}\\n```\\n\\n`_tokenAddress` is already of contract type `ERC20Burnable`.\\n```\\nfunction burnToken(ERC20Burnable \\_tokenAddress, uint256 \\_amount) override external onlyLatestNetworkContract {\\n    // Get contract key\\n    bytes32 contractKey = keccak256(abi.encodePacked(getContractName(msg.sender), \\_tokenAddress));\\n    // Update balances\\n    tokenBalances[contractKey] = tokenBalances[contractKey].sub(\\_amount);\\n    // Get the token ERC20 instance\\n    ERC20Burnable tokenContract = ERC20Burnable(\\_tokenAddress);\\n```\\n\\n`_rocketTokenRPLFixedSupplyAddress` is already of contract type `IERC20`.\\n```\\nconstructor(RocketStorageInterface \\_rocketStorageAddress, IERC20 \\_rocketTokenRPLFixedSupplyAddress) RocketBase(\\_rocketStorageAddress) ERC20("Rocket Pool Protocol", "RPL") {\\n    // Version\\n    version = 1;\\n    // Set the mainnet RPL fixed supply token address\\n    rplFixedSupplyContract = IERC20(\\_rocketTokenRPLFixedSupplyAddress);\\n```\\n

Impact:

Recommended mitigation:
We recommend removing the unnecessary double casts and copies of local variables.
Vulnerability: RocketMinipoolDelegate - Missing event in prepareVacancy
Severity: low

Description:
The function `prepareVacancy` updates multiple contract state variables and should therefore emit an event.\\n```\\n/// @dev Sets the bond value and vacancy flag on this minipool\\n/// @param \\_bondAmount The bond amount selected by the node operator\\n/// @param \\_currentBalance The current balance of the validator on the beaconchain (will be checked by oDAO and scrubbed if not correct)\\nfunction prepareVacancy(uint256 \\_bondAmount, uint256 \\_currentBalance) override external onlyLatestContract("rocketMinipoolManager", msg.sender) onlyInitialised {\\n    // Check status\\n    require(status == MinipoolStatus.Initialised, "Must be in initialised status");\\n    // Sanity check that refund balance is zero\\n    require(nodeRefundBalance == 0, "Refund balance not zero");\\n    // Check balance\\n    RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool"));\\n    uint256 launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance();\\n    require(\\_currentBalance >= launchAmount, "Balance is too low");\\n    // Store bond amount\\n    nodeDepositBalance = \\_bondAmount;\\n    // Calculate user amount from launch amount\\n    userDepositBalance = launchAmount.sub(nodeDepositBalance);\\n    // Flag as vacant\\n    vacant = true;\\n    preMigrationBalance = \\_currentBalance;\\n    // Refund the node whatever rewards they have accrued prior to becoming a RP validator\\n    nodeRefundBalance = \\_currentBalance.sub(launchAmount);\\n    // Set status to preLaunch\\n    setStatus(MinipoolStatus.Prelaunch);\\n}\\n```\\n

Impact:

Recommended mitigation:
Emit the missing event.
Vulnerability: RocketMinipool - Inconsistent access control modifier declaration onlyMinipoolOwner  Acknowledged
Severity: low

Description:
The access control modifier `onlyMinipoolOwner` should be renamed to `onlyMinipoolOwnerOrWithdrawalAddress` to be consistent with the actual check permitting the owner or the withdrawal address to interact with the function. This would also be consistent with other declarations in the codebase.\\nExample\\nThe `onlyMinipoolOwner` modifier in `RocketMinipoolBase` is the same as `onlyMinipoolOwnerOrWithdrawalAddress` in other modules.\\n```\\n/// @dev Only allow access from the owning node address\\nmodifier onlyMinipoolOwner() {\\n    // Only the node operator can upgrade\\n    address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(nodeAddress);\\n    require(msg.sender == nodeAddress || msg.sender == withdrawalAddress, "Only the node operator can access this method");\\n    \\_;\\n}\\n```\\n\\n```\\n// Only allow access from the owning node address\\nmodifier onlyMinipoolOwner() {\\n    // Only the node operator can upgrade\\n    address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(nodeAddress);\\n    require(msg.sender == nodeAddress || msg.sender == withdrawalAddress, "Only the node operator can access this method");\\n    \\_;\\n}\\n```\\n\\nOther declarations:\\n```\\n/// @dev Only allow access from the owning node address\\nmodifier onlyMinipoolOwner(address \\_nodeAddress) {\\n    require(\\_nodeAddress == nodeAddress, "Invalid minipool owner");\\n    \\_;\\n}\\n\\n/// @dev Only allow access from the owning node address or their withdrawal address\\nmodifier onlyMinipoolOwnerOrWithdrawalAddress(address \\_nodeAddress) {\\n    require(\\_nodeAddress == nodeAddress || \\_nodeAddress == rocketStorage.getNodeWithdrawalAddress(nodeAddress), "Invalid minipool owner");\\n    \\_;\\n}\\n```\\n\\n```\\n// Only allow access from the owning node address\\nmodifier onlyMinipoolOwner(address \\_nodeAddress) {\\n    require(\\_nodeAddress == nodeAddress, "Invalid minipool owner");\\n    \\_;\\n}\\n\\n// Only allow access from the owning node address or their withdrawal address\\nmodifier onlyMinipoolOwnerOrWithdrawalAddress(address \\_nodeAddress) {\\n    require(\\_nodeAddress == nodeAddress || \\_nodeAddress == rocketStorage.getNodeWithdrawalAddress(nodeAddress), "Invalid minipool owner");\\n    \\_;\\n}\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nAcknowledged by the client. Not addressed within rocket-pool/[email protected]77d7cca\\nAgreed. This would change a lot of contracts just for a minor improvement in readbility.\\nWe recommend renaming `RocketMinipoolBase.onlyMinipoolOwner` to `RocketMinipoolBase.onlyMinipoolOwnerOrWithdrawalAddress`.
Vulnerability: RocketDAO*Settings - settingNameSpace should be immutable  Acknowledged
Severity: low

Description:
The `settingNameSpace` in the abstract contract `RocketDAONodeTrustedSettings` is only set on contract deployment. Hence, the fields should be declared immutable to make clear that the settings namespace cannot change after construction.\\n`RocketDAONodeTrustedSettings`\\n```\\n// The namespace for a particular group of settings\\nbytes32 settingNameSpace;\\n```\\n\\n```\\n// Construct\\nconstructor(RocketStorageInterface \\_rocketStorageAddress, string memory \\_settingNameSpace) RocketBase(\\_rocketStorageAddress) {\\n    // Apply the setting namespace\\n    settingNameSpace = keccak256(abi.encodePacked("dao.trustednodes.setting.", \\_settingNameSpace));\\n}\\n```\\n\\n`RocketDAOProtocolSettings`\\n```\\n// The namespace for a particular group of settings\\nbytes32 settingNameSpace;\\n```\\n\\n```\\n// Construct\\nconstructor(RocketStorageInterface \\_rocketStorageAddress, string memory \\_settingNameSpace) RocketBase(\\_rocketStorageAddress) {\\n    // Apply the setting namespace\\n    settingNameSpace = keccak256(abi.encodePacked("dao.protocol.setting.", \\_settingNameSpace));\\n}\\n```\\n\\n```\\nconstructor(RocketStorageInterface \\_rocketStorageAddress) RocketDAOProtocolSettings(\\_rocketStorageAddress, "auction") {\\n    // Set version\\n    version = 1;\\n```\\n

Impact:

Recommended mitigation:
We recommend using the `immutable` annotation in Solidity (see Immutable).
Vulnerability: didTransferShares function has no access control modifier
Severity: high

Description:
The staked tokens (shares) in Forta are meant to be transferable. Similarly, the rewards allocation for these shares for delegated staking is meant to be transferable as well. This allocation for the shares' owner is tracked in the `StakeAllocator`. To enable this, the Forta staking contract `FortaStaking` implements a `_beforeTokenTransfer()` function that calls `_allocator.didTransferShares()` when it is appropriate to transfer the underlying allocation.\\n```\\nfunction \\_beforeTokenTransfer(\\n    address operator,\\n    address from,\\n    address to,\\n    uint256[] memory ids,\\n    uint256[] memory amounts,\\n    bytes memory data\\n) internal virtual override {\\n    for (uint256 i = 0; i < ids.length; i++) {\\n        if (FortaStakingUtils.isActive(ids[i])) {\\n            uint8 subjectType = FortaStakingUtils.subjectTypeOfShares(ids[i]);\\n            if (subjectType == DELEGATOR\\_NODE\\_RUNNER\\_SUBJECT && to != address(0) && from != address(0)) {\\n                \\_allocator.didTransferShares(ids[i], subjectType, from, to, amounts[i]);\\n            }\\n```\\n\\nDue to this, the `StakeAllocator.didTransferShares()` has an `external` visibility so it can be called from the `FortaStaking` contract to perform transfers. However, there is no access control modifier to allow only the staking contract to call this. Therefore, anyone can call this function with whatever parameters they want.\\n```\\nfunction didTransferShares(\\n    uint256 sharesId,\\n    uint8 subjectType,\\n    address from,\\n    address to,\\n    uint256 sharesAmount\\n) external {\\n    \\_rewardsDistributor.didTransferShares(sharesId, subjectType, from, to, sharesAmount);\\n}\\n```\\n\\nSince the allocation isn't represented as a token standard and is tracked directly in the `StakeAllocator` and `RewardsDistributor`, it lacks many standard checks that would prevent abuse of the function. For example, this function does not have a check for allowance or `msg.sender==from`, so any user could call `didTransferShares()` with `to` being their address and `from` being any address they want `to` transfer allocation `from`, and the call would succeed.

Impact:

Recommended mitigation:
Apply access control modifiers as appropriate for this contract, for example `onlyRole()`.
Vulnerability: Storage gap variables slightly off from the intended size
Severity: medium

Description:
The Forta staking system is using upgradeable proxies for its deployment strategy. To avoid storage collisions between contract versions during upgrades, uint256[] private `__gap` array variables are introduced that create a storage buffer. Together with contract state variables, the storage slots should sum up to 50. For example, the `__gap` variable is present in the `BaseComponentUpgradeable` component, which is the base of most Forta contracts, and there is a helpful comment in `AgentRegistryCore` that describes how its relevant `__gap` variable size was calculated:\\n```\\nuint256[50] private \\_\\_gap;\\n```\\n\\n```\\nuint256[41] private \\_\\_gap; // 50 - 1 (frontRunningDelay) - 3 (\\_stakeThreshold) - 5 StakeSubjectUpgradeable\\n```\\n\\nHowever, there are a few places where the `__gap` size was not computed correctly to get the storage slots up to 50. Some of these are:\\n```\\nuint256[49] private \\_\\_gap;\\n```\\n\\n```\\nuint256[47] private \\_\\_gap;\\n```\\n\\n```\\nuint256[44] private \\_\\_gap;\\n```\\n\\nWhile these still provide large storage buffers, it is best if the `__gap` variables are calculated to hold the same buffer within contracts of similar types as per the initial intentions to avoid confusion.\\nDuring conversations with the Forta Foundation team, it appears that some contracts like `ScannerRegistry` and `AgentRegistry` should instead add up to 45 with their `__gap` variable due to the `StakeSubject` contracts they inherit from adding 5 from themselves. This is something to note and be careful with as well for future upgrades.

Impact:

Recommended mitigation:
Provide appropriate sizes for the `__gap` variables to have a consistent storage layout approach that would help avoid storage issues with future versions of the system.