msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: Removing an owner does not work in PersonalAccountRegistry
Severity: high

Description:
An owner of a personal account can be added/removed by other owners. When removing the owner, only `removedAtBlockNumber` value is updated. `accounts[account].owners[owner].added` remains true:\\n```\\naccounts[account].owners[owner].removedAtBlockNumber = block.number;\\n\\nemit AccountOwnerRemoved(\\n account,\\n owner\\n);\\n```\\n\\nBut when the account is checked whether this account is the owner, only `accounts[account].owners[owner].added` is actually checked:\\n```\\nfunction \\_verifySender(\\n address account\\n)\\n private\\n returns (address)\\n{\\n address sender = \\_getContextSender();\\n\\n if (!accounts[account].owners[sender].added) {\\n require(\\n accounts[account].salt == 0\\n );\\n\\n bytes32 salt = keccak256(\\n abi.encodePacked(sender)\\n );\\n\\n require(\\n account == \\_computeAccountAddress(salt)\\n );\\n\\n accounts[account].salt = salt;\\n accounts[account].owners[sender].added = true;\\n\\n emit AccountOwnerAdded(\\n account,\\n sender\\n );\\n }\\n\\n return sender;\\n}\\n```\\n\\nSo the owner will never be removed, because `accounts[account].owners[owner].added` will always be `true.

Impact:

Recommended mitigation:
Properly check if the account is still the owner in the `_verifySender` function.
Vulnerability: The withdrawal mechanism is overcomplicated
Severity: medium

Description:
To withdraw the funds, anyone who has the account in `PaymentRegistry` should call the `withdrawDeposit` function and go through the withdrawal process. After the lockdown period (30 days), the user will withdraw all the funds from the account.\\n```\\nfunction withdrawDeposit(\\n address token\\n)\\n external\\n{\\n address owner = \\_getContextAccount();\\n uint256 lockedUntil = deposits[owner].withdrawalLockedUntil[token];\\n\\n /\\* solhint-disable not-rely-on-time \\*/\\n\\n if (lockedUntil != 0 && lockedUntil <= now) {\\n deposits[owner].withdrawalLockedUntil[token] = 0;\\n\\n address depositAccount = deposits[owner].account;\\n uint256 depositValue;\\n\\n if (token == address(0)) {\\n depositValue = depositAccount.balance;\\n } else {\\n depositValue = ERC20Token(token).balanceOf(depositAccount);\\n }\\n\\n \\_transferFromDeposit(\\n depositAccount,\\n owner,\\n token,\\n depositValue\\n );\\n\\n emit DepositWithdrawn(\\n depositAccount,\\n owner,\\n token,\\n depositValue\\n );\\n } else {\\n \\_deployDepositAccount(owner);\\n\\n lockedUntil = now.add(depositWithdrawalLockPeriod);\\n\\n deposits[owner].withdrawalLockedUntil[token] = lockedUntil;\\n\\n emit DepositWithdrawalRequested(\\n deposits[owner].account,\\n owner,\\n token,\\n lockedUntil\\n );\\n }\\n /\\* solhint-enable not-rely-on-time \\*/\\n}\\n```\\n\\nDuring that period, everyone who has a channel with the user is forced to commit their channels or lose money from that channel. When doing so, every user will reset the initial lockdown period and the withdrawer should start the process again.\\n```\\nif (deposits[sender].withdrawalLockedUntil[token] > 0) {\\n deposits[sender].withdrawalLockedUntil[token] = 0;\\n```\\n\\nThere is no way for the withdrawer to close the channel by himself. If the withdrawer has N channels, it's theoretically possible to wait for up to N*(30 days) period and make N+2 transactions.

Impact:

Recommended mitigation:
There may be some minor recommendations on how to improve that without major changes:\\nWhen committing a payment channel, do not reset the lockdown period to zero. Two better option would be either not change it at all or extend to `now + depositWithdrawalLockPeriod`
Vulnerability: The lockdown period shouldn't be extended when called multiple times
Severity: low

Description:
In order to withdraw a deposit from the `PaymentRegistry`, the account owner should call the `withdrawDeposit` function and wait for `depositWithdrawalLockPeriod` (30 days) before actually transferring all the tokens from the account.\\nThe issue is that if the withdrawer accidentally calls it for the second time before these 30 days pass, the waiting period gets extended for 30 days again.\\n```\\nif (lockedUntil != 0 && lockedUntil <= now) {\\n deposits[owner].withdrawalLockedUntil[token] = 0;\\n\\n address depositAccount = deposits[owner].account;\\n uint256 depositValue;\\n\\n if (token == address(0)) {\\n depositValue = depositAccount.balance;\\n } else {\\n depositValue = ERC20Token(token).balanceOf(depositAccount);\\n }\\n\\n \\_transferFromDeposit(\\n depositAccount,\\n owner,\\n token,\\n depositValue\\n );\\n\\n emit DepositWithdrawn(\\n depositAccount,\\n owner,\\n token,\\n depositValue\\n );\\n} else {\\n \\_deployDepositAccount(owner);\\n\\n lockedUntil = now.add(depositWithdrawalLockPeriod);\\n```\\n

Impact:

Recommended mitigation:
Resolution\\nComment from the client: The issue has been solved\\nOnly extend the waiting period when a withdrawal is requested for the first time.
Vulnerability: Gateway can call any contract  Acknowledged
Severity: low

Description:
Resolution\\nComment from the client: That's right Gateway can call any contract, we want to keep it open for any external contract.\\nThe `Gateway` contract is used as a gateway for meta transactions and batched transactions. It can currently call any contract, while is only intended to call specific contracts in the system that implemented `GatewayRecipient` interface:\\n```\\n for (uint256 i = 0; i < data.length; i++) {\\n require(\\n to[i] != address(0)\\n );\\n\\n // solhint-disable-next-line avoid-low-level-calls\\n (succeeded,) = to[i].call(abi.encodePacked(data[i], account, sender));\\n\\n require(\\n succeeded\\n );\\n }\\n}\\n```\\n\\nThere are currently no restrictions for `to` value.

Impact:

Recommended mitigation:
Make sure, only intended contracts can be called by the `Gateway` : `PersonalAccountRegistry`, `PaymentRegistry`, `ENSController`.
Vulnerability: Remove unused code
Severity: low

Description:
```\\n return \\_deployAccount(\\n salt,\\n 0\\n );\\n}\\n\\nfunction \\_deployAccount(\\n bytes32 salt,\\n uint256 value\\n)\\n internal\\n returns (address)\\n{\\n return address(new Account{salt: salt, value: value}());\\n}\\n```\\n

Impact:

Recommended mitigation:
It is recommended to remove this value as there are no use cases for it at the moment, however if it is planned to be used in the future, it should be well documented in the code to prevent confusion.
Vulnerability: Every node gets a full validator's bounty
Severity: high

Description:
Resolution\\nThis issue is addressed in Bug/skale 3273 formula fix 435 and SKALE-3273 Fix BountyV2 populating error 438.\\nThe main change is related to how bounties are calculated for each validator. Below are a few notes on these pull requests:\\n`nodesByValidator` mapping is no longer used in the codebase and the non-zero values are deleted when `calculateBounty()` is called for a specific validator. The mapping is kept in the code for compatible storage layout in upgradable proxies.\\nSome functions such as `populate()` was developed for the transition to the upgraded contracts (rewrite `_effectiveDelegatedSum` values based on the new calculation formula). This function is not part of this review and will be removed in the future updates.\\nUnlike the old architecture, `nodesByValidator[validatorId]` is no longer used within the system to calculate `_effectiveDelegatedSum` and bounties. This is replaced by using overall staked amount and duration.\\nIf a validator does not claim their bounty during a month, it is considered as a misbehave and her bounty goes to the bounty pool for the next month.\\nTo get the bounty, every node calls the `getBounty` function of the `SkaleManager` contract. This function can be called once per month. The size of the bounty is defined in the `BountyV2` contract in the `_calculateMaximumBountyAmount` function:\\n```\\nreturn epochPoolSize\\n    .add(\\_bountyWasPaidInCurrentEpoch)\\n    .mul(\\n        delegationController.getAndUpdateEffectiveDelegatedToValidator(\\n            nodes.getValidatorId(nodeIndex),\\n            currentMonth\\n        )\\n    )\\n    .div(effectiveDelegatedSum);\\n```\\n\\nThe problem is that this amount actually represents the amount that should be paid to the validator of that node. But each node will get this amount. Additionally, the amount of validator's bounty should also correspond to the number of active nodes, while this formula only uses the amount of delegated funds.

Impact:

Recommended mitigation:
Every node should get only their parts of the bounty.
Vulnerability: A node exit prevents some other nodes from exiting for some period  Pending
Severity: medium

Description:
When a node wants to exit, the `nodeExit` function should be called as many times, as there are schains in the node. Each time one schain is getting removed from the node. During every call, all the active schains are getting frozen for 12 hours.\\n```\\nfunction freezeSchains(uint nodeIndex) external allow("SkaleManager") {\\n    SchainsInternal schainsInternal = SchainsInternal(contractManager.getContract("SchainsInternal"));\\n    bytes32[] memory schains = schainsInternal.getActiveSchains(nodeIndex);\\n    for (uint i = 0; i < schains.length; i++) {\\n        Rotation memory rotation = rotations[schains[i]];\\n        if (rotation.nodeIndex == nodeIndex && now < rotation.freezeUntil) {\\n            continue;\\n        }\\n        string memory schainName = schainsInternal.getSchainName(schains[i]);\\n        string memory revertMessage = "Node cannot rotate on Schain ";\\n        revertMessage = revertMessage.strConcat(schainName);\\n        revertMessage = revertMessage.strConcat(", occupied by Node ");\\n        revertMessage = revertMessage.strConcat(rotation.nodeIndex.uint2str());\\n        string memory dkgRevert = "DKG process did not finish on schain ";\\n        ISkaleDKG skaleDKG = ISkaleDKG(contractManager.getContract("SkaleDKG"));\\n        require(\\n            skaleDKG.isLastDKGSuccessful(keccak256(abi.encodePacked(schainName))),\\n            dkgRevert.strConcat(schainName));\\n        require(rotation.freezeUntil < now, revertMessage);\\n        \\_startRotation(schains[i], nodeIndex);\\n    }\\n}\\n```\\n\\nBecause of that, no other node that is running one of these schains can exit during that period. In the worst-case scenario, one malicious node has 128 Schains and calls `nodeExit` every 12 hours. That means that some nodes will not be able to exit for 64 days.

Impact:

Recommended mitigation:
Make node exiting process less synchronous.
Vulnerability: Removing a node require multiple transactions and may be very expensive  Pending
Severity: medium

Description:
When removing a node from the network, the owner should redistribute all the schains that are currently on that node to the other nodes. To do so, the validator should call the `nodeExit` function of the `SkaleManager` contract. In this function, only one schain is going to be removed from the node. So the node would have to call the `nodeExit` function as many times as there are schains in the node. Every call iterates over every potential node that can be used as a replacement (like in https://github.com/ConsenSys/skale-network-audit-2020-10/issues/3).\\nIn addition to that, the first call will iterate over all schains in the node, make 4 SSTORE operations and external calls for each schain:\\n```\\nfunction \\_startRotation(bytes32 schainIndex, uint nodeIndex) private {\\n    ConstantsHolder constants = ConstantsHolder(contractManager.getContract("ConstantsHolder"));\\n    rotations[schainIndex].nodeIndex = nodeIndex;\\n    rotations[schainIndex].newNodeIndex = nodeIndex;\\n    rotations[schainIndex].freezeUntil = now.add(constants.rotationDelay());\\n    waitForNewNode[schainIndex] = true;\\n}\\n```\\n\\nThis may hit the block gas limit even easier than issue 5.4.\\nIf the first transaction does not hit the block's gas limit, the maximum price of deleting a node would be BLOCK_GAS_COST * 128. At the moment, it's around $50,000.

Impact:

Recommended mitigation:
Optimize the process of deleting a node, so it can't hit the gas limit in one transaction, and the overall price should be cheaper.
Vulnerability: Adding a new schain may potentially hit the gas limit  Pending
Severity: medium

Description:
When adding a new schain, a group of random 16 nodes is randomly selected to run that schain. In order to do so, the `_generateGroup` function iterates over all the nodes that can be used for that purpose:\\n```\\nfunction \\_generateGroup(bytes32 schainId, uint numberOfNodes) private returns (uint[] memory nodesInGroup) {\\n    Nodes nodes = Nodes(contractManager.getContract("Nodes"));\\n    uint8 space = schains[schainId].partOfNode;\\n    nodesInGroup = new uint[](numberOfNodes);\\n\\n    uint[] memory possibleNodes = isEnoughNodes(schainId);\\n    require(possibleNodes.length >= nodesInGroup.length, "Not enough nodes to create Schain");\\n    uint ignoringTail = 0;\\n    uint random = uint(keccak256(abi.encodePacked(uint(blockhash(block.number.sub(1))), schainId)));\\n    for (uint i = 0; i < nodesInGroup.length; ++i) {\\n        uint index = random % (possibleNodes.length.sub(ignoringTail));\\n        uint node = possibleNodes[index];\\n        nodesInGroup[i] = node;\\n        \\_swap(possibleNodes, index, possibleNodes.length.sub(ignoringTail).sub(1));\\n        ++ignoringTail;\\n\\n        \\_exceptionsForGroups[schainId][node] = true;\\n        addSchainForNode(node, schainId);\\n        require(nodes.removeSpaceFromNode(node, space), "Could not remove space from Node");\\n    }\\n```\\n\\nIf the total number of nodes exceeds around a few thousands, adding a schain may hit the block gas limit.

Impact:

Recommended mitigation:
Avoid iterating over all nodes when selecting a random node for a schain.
Vulnerability: Re-entrancy attacks with ERC-777
Severity: low

Description:
Some tokens may allow users to perform re-entrancy while calling the `transferFrom` function. For example, it would be possible for an attacker to “borrow” a large amount of ERC-777 tokens from the lending pool by re-entering the `deposit` function from within `transferFrom`.\\n```\\nfunction deposit(\\n  address asset,\\n  uint256 amount,\\n  address onBehalfOf,\\n  uint16 referralCode\\n) external override {\\n  \\_whenNotPaused();\\n  ReserveLogic.ReserveData storage reserve = \\_reserves[asset];\\n\\n  ValidationLogic.validateDeposit(reserve, amount);\\n\\n  address aToken = reserve.aTokenAddress;\\n\\n  reserve.updateState();\\n  reserve.updateInterestRates(asset, aToken, amount, 0);\\n\\n  bool isFirstDeposit = IAToken(aToken).balanceOf(onBehalfOf) == 0;\\n  if (isFirstDeposit) {\\n    \\_usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true);\\n  }\\n\\n  IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex);\\n\\n  //transfer to the aToken contract\\n  IERC20(asset).safeTransferFrom(msg.sender, aToken, amount);\\n\\n  emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode);\\n}\\n```\\n\\nBecause the `safeTransferFrom` call is happening at the end of the `deposit` function, the `deposit` will be fully processed before the tokens are actually transferred.\\nSo at the beginning of the transfer, the attacker can re-enter the call to withdraw their deposit. The withdrawal will succeed even though the attacker's tokens have not yet been transferred to the lending pool. Essentially, the attacker is granted a flash-loan but without paying fees.\\nAdditionally, after these calls, interest rates will be skewed because interest rate update relies on the actual current balance.\\nRemediation\\nDo not whitelist ERC-777 or other re-entrable tokens to prevent this kind of attack.

Impact:

Recommended mitigation:
Resolution\\nThe issue was partially mitigated in `deposit` function by minting AToken before the transfer of the `deposit` token.
Vulnerability: Attacker can abuse swapLiquidity function to drain users' funds
Severity: medium

Description:
The `swapLiquidity` function allows liquidity providers to atomically swap their collateral. The function takes a receiverAddressargument that normally points to an `ISwapAdapter` implementation trusted by the user.\\n```\\nvars.fromReserveAToken.burn(\\n msg.sender,\\n receiverAddress,\\n amountToSwap,\\n fromReserve.liquidityIndex\\n);\\n// Notifies the receiver to proceed, sending as param the underlying already transferred\\nISwapAdapter(receiverAddress).executeOperation(\\n fromAsset,\\n toAsset,\\n amountToSwap,\\n address(this),\\n params\\n);\\n\\nvars.amountToReceive = IERC20(toAsset).balanceOf(receiverAddress);\\nif (vars.amountToReceive != 0) {\\n IERC20(toAsset).transferFrom(\\n receiverAddress,\\n address(vars.toReserveAToken),\\n vars.amountToReceive\\n );\\n\\n if (vars.toReserveAToken.balanceOf(msg.sender) == 0) {\\n \\_usersConfig[msg.sender].setUsingAsCollateral(toReserve.id, true);\\n }\\n\\n vars.toReserveAToken.mint(msg.sender, vars.amountToReceive, toReserve.liquidityIndex);\\n```\\n\\nHowever, since an attacker can pass any address as the `receiverAddress`, they can arbitrarily transfer funds from other contracts that have given allowances to the `LendingPool` contract (for example, another ISwapAdapter).\\nThe `amountToSwap` is defined by the caller and can be very small. The attacker gets the difference between `IERC20(toAsset).balanceOf(receiverAddress)` value of `toAsset` and the `amountToSwap` of `fromToken`.\\nRemediation\\nEnsure that no funds can be stolen from contracts that have granted allowances to the `LendingPool` contract.

Impact:

Recommended mitigation:
Resolution\\nSolved by removing `swapLiquidity` functionality.
Vulnerability: VotingMachine - verifyNonce should only allow the next nonce
Severity: high

Description:
When a relayer calls `submitVoteByRelayer` they also need to provide a nonce. This nonce is cryptographicly checked against the provided signature. It is also checked again to be higher than the previous nonce saved for that voter.\\n```\\n/// @notice Verifies the nonce of a voter on a proposal\\n/// @param \\_proposalId The id of the proposal\\n/// @param \\_voter The address of the voter\\n/// @param \\_relayerNonce The nonce submitted by the relayer\\nfunction verifyNonce(uint256 \\_proposalId, address \\_voter, uint256 \\_relayerNonce) public view {\\n    Proposal storage \\_proposal = proposals[\\_proposalId];\\n    require(\\_proposal.voters[\\_voter].nonce < \\_relayerNonce, "INVALID\\_NONCE");\\n}\\n```\\n\\nWhen the vote is saved, the previous nonce is incremented.\\n```\\nvoter.nonce = voter.nonce.add(1);\\n```\\n\\nThis leaves the opportunity to use the same signature to vote multiple times, as long as the provided nonce is higher than the incremented nonce.

Impact:

Recommended mitigation:
The check should be more restrictive and make sure the consecutive nonce was provided.\\n```\\nrequire(\\_proposal.voters[\\_voter].nonce + 1 == \\_relayerNonce, "INVALID\\_NONCE");\\n```\\n
Vulnerability: VoteMachine - Cancelling vote does not increase the nonce
Severity: low

Description:
A vote can be cancelled by calling `cancelVoteByRelayer` with the proposal ID, nonce, voter's address, signature and a hash of the sent params.\\nThe parameters are hashed and checked against the signature correctly.\\nThe nonce is part of these parameters and it is checked to be valid.\\n```\\nrequire(\\_proposal.voters[\\_voter].nonce < \\_relayerNonce, "INVALID\\_NONCE");\\n```\\n\\nOnce the vote is cancelled, the data is cleared but the nonce is not increased.\\n```\\nif (\\_cachedVoter.balance > 0) {\\n    \\_proposal.votes[\\_cachedVoter.vote] = \\_proposal.votes[\\_cachedVoter.vote].sub(\\_cachedVoter.balance.mul(\\_cachedVoter.weight));\\n    \\_proposal.totalVotes = \\_proposal.totalVotes.sub(1);\\n    voter.weight = 0;\\n    voter.balance = 0;\\n    voter.vote = 0;\\n    voter.asset = address(0);\\n    emit VoteCancelled(\\n        \\_proposalId,\\n        \\_voter,\\n        \\_cachedVoter.vote,\\n        \\_cachedVoter.asset,\\n        \\_cachedVoter.weight,\\n        \\_cachedVoter.balance,\\n        uint256(\\_proposal.proposalStatus)\\n    );\\n}\\n```\\n\\nThis means that in the future, the same signature can be used as long as the nonce is still higher than the current one.

Impact:

Recommended mitigation:
Considering the recommendation from issue https://github.com/ConsenSys/aave-governance-dao-audit-2020-01/issues/4 is implemented, the nonce should also increase when the vote is cancelled. Otherwise the same signature can be replayed again.
Vulnerability: Possible lock ups with SafeMath multiplication  Acknowledged
Severity: low

Description:
In some cases using SafeMath can lead to a situation where a contract is locked up due to an unavoidable overflow.\\nIt is theoretically possible that both the `internalSubmitVote()` and `internalCancelVote()` functions could become unusable by voters with a high enough balance, if the asset weighting is set extremely high.\\nThis line in `internalSubmitVote()` could overflow if the voter's balance and the asset weight were sufficiently high:\\n```\\nuint256 \\_votingPower = \\_voterAssetBalance.mul(\\_assetWeight);\\n```\\n\\nA similar situation occurs in internalCancelVote():\\n```\\n\\_proposal.votes[\\_cachedVoter.vote] = \\_proposal.votes[\\_cachedVoter.vote].sub(\\_cachedVoter.balance.mul(\\_cachedVoter.weight));\\n\\_proposal.totalVotes = \\_proposal.totalVotes.sub(1);\\n```\\n

Impact:

Recommended mitigation:
This could be protected against by setting a maximum value for asset weights. In practice it is very unlikely to occur in this situation, but it could be introduced at some point in the future.
Vulnerability: Reentrancy vulnerability in MetaSwap.swap()
Severity: high

Description:
`MetaSwap.swap()` should have a reentrancy guard.\\nThe adapters use this general process:\\nCollect the from token (or ether) from the user.\\nExecute the trade.\\nTransfer the contract's balance of tokens (from and to) and ether to the user.\\nIf an attacker is able to reenter `swap()` before step 3, they can execute their own trade using the same tokens and get all the tokens for themselves.\\nThis is partially mitigated by the check against `amountTo` in `CommonAdapter`, but note that the `amountTo` typically allows for slippage, so it may still leave room for an attacker to siphon off some amount while still returning the required minimum to the user.\\n```\\n// Transfer remaining balance of tokenTo to sender\\nif (address(tokenTo) != Constants.ETH) {\\n    uint256 balance = tokenTo.balanceOf(address(this));\\n    require(balance >= amountTo, "INSUFFICIENT\\_AMOUNT");\\n    \\_transfer(tokenTo, balance, recipient);\\n} else {\\n```\\n\\nAs an example of how this could be exploited, 0x supports an “EIP1271Wallet” signature type, which invokes an external contract to check whether a trade is allowed. A malicious maker might front run the swap to reduce their inventory. This way, the taker is sending more of the taker asset than necessary to `MetaSwap`. The excess can be stolen by the maker during the EIP1271 call.

Impact:

Recommended mitigation:
Use a simple reentrancy guard, such as OpenZeppelin's `ReentrancyGuard` to prevent reentrancy in `MetaSwap.swap()`. It might seem more obvious to put this check in `Spender.swap()`, but the `Spender` contract intentionally does not use any storage to avoid interference between different adapters.