msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: `QVSimpleStrategy` never updates `allocator.voiceCredits`.
Severity: high

Description:
```\\n    function _allocate(bytes memory _data, address _sender) internal virtual override {\\n        …\\n\\n        // check that the recipient has voice credits left to allocate\\n        if (!_hasVoiceCreditsLeft(voiceCreditsToAllocate, allocator.voiceCredits)) revert INVALID();\\n\\n        _qv_allocate(allocator, recipient, recipientId, voiceCreditsToAllocate, _sender);\\n    }\\n```\\n\\n```\\n    function _hasVoiceCreditsLeft(uint256 _voiceCreditsToAllocate, uint256 _allocatedVoiceCredits)\\n        internal\\n        view\\n        override\\n        returns (bool)\\n    {\\n        return _voiceCreditsToAllocate + _allocatedVoiceCredits <= maxVoiceCreditsPerAllocator;\\n    }\\n```\\n\\nThe problem is that `allocator.voiceCredits` is always zero. Both `QVSimpleStrategy` and `QVBaseStrategy` don't update `allocator.voiceCredits`. Thus, allocators can cast more votes than `maxVoiceCreditsPerAllocator`.

Impact:
Every allocator has an unlimited number of votes.

Recommended mitigation:
Updates `allocator.voiceCredits` in `QVSimpleStrategy._allocate`.\\n```\\n    function _allocate(bytes memory _data, address _sender) internal virtual override {\\n        …\\n\\n        // check that the recipient has voice credits left to allocate\\n        if (!_hasVoiceCreditsLeft(voiceCreditsToAllocate, allocator.voiceCredits)) revert INVALID();\\n// Add the line below\\n       allocator.voiceCredits // Add the line below\\n= voiceCreditsToAllocate;\\n        _qv_allocate(allocator, recipient, recipientId, voiceCreditsToAllocate, _sender);\\n    }\\n```\\n
Vulnerability: `Registry.sol` generate clone `Anchor.sol` never work. Profile owner cannot use their `Anchor` wallet
Severity: high

Description:
Add this test to `Registry.t.sol` test file to reproduce the issue.\\n```\\n    function test_Audit_createProfile() public {\\n        // create profile\\n        bytes32 newProfileId = registry().createProfile(nonce, name, metadata, profile1_owner(), profile1_members());\\n        Registry.Profile memory profile = registry().getProfileById(newProfileId);\\n        Anchor _anchor = Anchor(payable(profile.anchor));\\n\\n        console.log("registry address: %s", address(registry()));\\n        console.log("anchor address: %s", profile.anchor);\\n        console.log("anchor.registry: %s", address(_anchor.registry()));\\n\\n        emit log_named_bytes32("profile.id", profile.id);\\n        emit log_named_bytes32("anchor.profile.id", _anchor.profileId());\\n\\n        Anchor _anchor_proxy = Anchor(payable(address( _anchor.registry())));\\n        assertEq(address(registry()),address(_anchor.registry()) ,"wrong anchor registry");\\n    }\\n```\\n\\nWhat happen with `Anchor.sol` is it expect `msg.sender` is `Registry` contract. But in reality `msg.sender` is a proxy contract generated by Solady during `CREATE3` operation.\\n```\\n    constructor(bytes32 _profileId) {\\n        registry = Registry(msg.sender);//@audit H Registry address here is not Registry. msg.sender is a proxy contract. Create3 deploy 2 contract. one is proxy. other is actual bytecode.\\n        profileId = _profileId;\\n    }\\n```\\n\\nThis can be seen with Solady comment for proxy contract. `msg.sender` above is middleman proxy contract. Not `Registry` contract. Solady generate 2 contract during CREATE3 operation. One is proxy contract. Second is actual bytecode.

Impact:
`Anchor.execute()` function will not work because `registry` address point to empty proxy contract and not actual `Registry` so all call will revert.\\n```\\nFile: allo-v2\\contracts\\core\\Anchor.sol\\n    function execute(address _target, uint256 _value, bytes memory _data) external returns (bytes memory) {\\n        // Check if the caller is the owner of the profile and revert if not\\n        if (!registry.isOwnerOfProfile(profileId, msg.sender)) revert UNAUTHORIZED();\\n```\\n\\nProfile owner cannot use their wallet `Anchor`. All funds send to this `Anchor` contract will be lost forever.

Recommended mitigation:
Move `msg.sender` into constructor parameter\\n```\\nFile: allo-v2\\contracts\\core\\Registry.sol\\n            bytes memory creationCode = abi.encodePacked(type(Anchor).creationCode, abi.encode(_profileId, address(this))); //@audit fix creation code\\n\\n            // Use CREATE3 to deploy the anchor contract\\n            anchor = CREATE3.deploy(salt, creationCode, 0); \\nFile: allo-v2\\contracts\\core\\Anchor.sol\\n    constructor(bytes32 _profileId, address _registry) {\\n        registry = Registry(_registry);\\n        profileId = _profileId;\\n    }\\n```\\n
Vulnerability: `fundPool` does not work with fee-on-transfer token
Severity: medium

Description:
In `_fundPool`, the parameter for `increasePoolAmount` is directly the amount used in the `transferFrom` call.\\n```\\n        _transferAmountFrom(_token, TransferData({from: msg.sender, to: address(_strategy), amount: amountAfterFee}));\\n        _strategy.increasePoolAmount(amountAfterFee);\\n```\\n\\nWhen `_token` is a fee-on-transfer token, the actual amount transferred to `_strategy` will be less than `amountAfterFee`. Therefore, the current approach could lead to a recorded balance that is greater than the actual balance.

Impact:
`fundPool` does not work with fee-on-transfer token

Recommended mitigation:
Use the change in `_token` balance as the parameter for `increasePoolAmount`.
Vulnerability: Exponential Inflation of Voice Credits in Quadratic Voting Strategy
Severity: medium

Description:
In the given code snippet, we observe a potential issue in the way voice credits are being accumulated for each recipient. The specific lines of code in question are:\\n```\\nfunction _qv_allocate(\\n        // rest of code\\n    ) internal onlyActiveAllocation {\\n        // rest of code\\n        uint256 creditsCastToRecipient = _allocator.voiceCreditsCastToRecipient[_recipientId];\\n        // rest of code\\n        // get the total credits and calculate the vote result\\n        uint256 totalCredits = _voiceCreditsToAllocate + creditsCastToRecipient;\\n        // rest of code\\n        //E update allocator mapping voice for this recipient\\n        _allocator.voiceCreditsCastToRecipient[_recipientId] += totalCredits; //E @question should be only _voiceCreditsToAllocate\\n        // rest of code\\n    }\\n```\\n\\nWe can see that at the end :\\n```\\n_allocator.voiceCreditsCastToRecipient[_recipientId] = _allocator.voiceCreditsCastToRecipient[_recipientId] + _voiceCreditsToAllocate +  _allocator.voiceCreditsCastToRecipient[_recipientId];\\n```\\n\\nHere, totalCredits accumulates both the newly allocated voice credits (_voiceCreditsToAllocate) and the credits previously cast to this recipient (creditsCastToRecipient). Later on, this totalCredits is added again to `voiceCreditsCastToRecipient[_recipientId]`, thereby including the previously cast credits once more\\nProof of Concept (POC):\\nLet's consider a scenario where a user allocates credits in three separate transactions:\\nTransaction 1: Allocates 5 credits\\ncreditsCastToRecipient initially is 0\\ntotalCredits = 5 (5 + 0)\\nNew voiceCreditsCastToRecipient[_recipientId] = 5\\nTransaction 2: Allocates another 5 credits\\ncreditsCastToRecipient now is 5 (from previous transaction)\\ntotalCredits = 10 (5 + 5)\\nNew voiceCreditsCastToRecipient[_recipientId] = 15 (10 + 5)\\nTransaction 3: Allocates another 5 credits\\ncreditsCastToRecipient now is 15\\ntotalCredits = 20 (5 + 15)\\nNew voiceCreditsCastToRecipient[_recipientId] = 35 (20 + 15)\\nFrom the above, we can see that the voice credits cast to the recipient are exponentially growing with each transaction instead of linearly increasing by 5 each time

Impact:
Exponential increase in the voice credits attributed to a recipient, significantly skewing the results of the voting strategy( if one recipient receive 15 votes in one vote and another one receive 5 votes 3 times, the second one will have 20 votes and the first one 15) Over time, this could allow for manipulation and loss of trust in the voting mechanism and the percentage of amount received by recipients as long as allocations are used to calculate the match amount they will receive from the pool amount.

Recommended mitigation:
Code should be modified to only add the new voice credits to the recipient's tally. The modified line of code should look like:\\n```\\n_allocator.voiceCreditsCastToRecipient[_recipientId] += _voiceCreditsToAllocate;\\n```\\n
Vulnerability: RFPSimpleStrategy milestones can be set multiple times
Severity: medium

Description:
The `setMilestones` function in `RFPSimpleStrategy` contract checks if `MILESTONES_ALREADY_SET` or not by `upcomingMilestone` index.\\n```\\nif (upcomingMilestone != 0) revert MILESTONES_ALREADY_SET();\\n```\\n\\nBut `upcomingMilestone` increases only after distribution, and until this time will always be equal to 0.

Impact:
It can accidentally break the pool state or be used with malicious intentions.\\nTwo managers accidentally set the same milestones. Milestones are duplicated and can't be reset, the pool needs to be recreated.\\nThe manager, in cahoots with the recipient, sets milestones one by one, thereby bypassing `totalAmountPercentage` check and increasing the payout amount.

Recommended mitigation:
Fix condition if milestones should only be set once.\\n```\\nif (milestones.length > 0) revert MILESTONES_ALREADY_SET();\\n```\\n\\nOr allow milestones to be reset while they are not in use.\\n```\\nif (milestones.length > 0) {\\n    if (milestones[0].milestoneStatus != Status.None) revert MILESTONES_ALREADY_IN_USE();\\n    delete milestones;\\n}\\n```\\n
Vulnerability: Allo#_fundPool
Severity: medium

Description:
Let's see the code of the `_fundPool` function:\\n```\\nfunction _fundPool(uint256 _amount, uint256 _poolId, IStrategy _strategy) internal {\\n        uint256 feeAmount;\\n        uint256 amountAfterFee = _amount;\\n\\n        Pool storage pool = pools[_poolId];\\n        address _token = pool.token;\\n\\n        if (percentFee > 0) {\\n            feeAmount = (_amount * percentFee) / getFeeDenominator();\\n            amountAfterFee -= feeAmount;\\n\\n            _transferAmountFrom(_token, TransferData({from: msg.sender, to: treasury, amount: feeAmount}));\\n        }\\n\\n        _transferAmountFrom(_token, TransferData({from: msg.sender, to: address(_strategy), amount: amountAfterFee}));\\n        _strategy.increasePoolAmount(amountAfterFee);\\n\\n        emit PoolFunded(_poolId, amountAfterFee, feeAmount);\\n    }\\n```\\n\\nThe `feeAmount` is calculated as follows:\\n```\\nfeeAmount = (_amount * percentFee) / getFeeDenominator();\\n```\\n\\nwhere `getFeeDenominator` returns `1e18` and `percentFee` is represented like that: `1e18` = 100%, 1e17 = 10%, 1e16 = 1%, 1e15 = 0.1% (from the comments when declaring the variable).\\nLet's say the pool uses a token like GeminiUSD which is a token with 300M+ market cap, so it's widely used, and `percentFee` == 1e15 (0.1%)\\nA user could circumvent the fee by depositing a relatively small amount. In our example, he can deposit 9 GeminiUSD. In that case, the calculation will be: `feeAmount = (_amount * percentFee) / getFeeDenominator() = (9e2 * 1e15) / 1e18 = 9e17/1e18 = 9/10 = 0;`\\nSo the user ends up paying no fee. There is nothing stopping the user from funding his pool by invoking the `fundPool` with such a small amount as many times as he needs to fund the pool with whatever amount he chooses, circumventing the fee.\\nEspecially with the low gas fees on L2s on which the protocol will be deployed, this will be a viable method to fund a pool without paying any fee to the protocol.

Impact:
The protocol doesn't collect fees from pools with low decimal tokens.

Recommended mitigation:
Add a `minFundAmount` variable and check for it when funding a pool.
Vulnerability: `_distribute()` function in RFPSimpleStrategy contract has wrong requirement causing DOS
Severity: medium

Description:
The function _distribute():\\n```\\n    function _distribute(address[] memory, bytes memory, address _sender)\\n        internal\\n        virtual\\n        override\\n        onlyInactivePool\\n        onlyPoolManager(_sender)\\n    {\\n        // rest of code\\n\\n        IAllo.Pool memory pool = allo.getPool(poolId);\\n        Milestone storage milestone = milestones[upcomingMilestone];\\n        Recipient memory recipient = _recipients[acceptedRecipientId];\\n\\n        if (recipient.proposalBid > poolAmount) revert NOT_ENOUGH_FUNDS();\\n\\n        uint256 amount = (recipient.proposalBid * milestone.amountPercentage) / 1e18;\\n\\n        poolAmount -= amount;//<@@ NOTICE the poolAmount get decrease over time\\n\\n        _transferAmount(pool.token, recipient.recipientAddress, amount);\\n\\n        // rest of code\\n    }\\n```\\n\\nLet's suppose this scenario:\\nPool manager funding the contract with 100 token, making `poolAmount` variable equal to 100\\nPool manager set 5 equal milestones with 20% each\\nSelected recipient's proposal bid is 100, making `recipients[acceptedRecipientId].proposalBid` variable equal to 100\\nAfter milestone 1 done, pool manager pays recipient using `distribute()`. Value of variables after: `poolAmount = 80 ,recipients[acceptedRecipientId].proposalBid = 100`\\nAfter milestone 2 done, pool manager will get DOS trying to pay recipient using `distribute()` because of this line:\\n```\\nif (recipient.proposalBid > poolAmount) revert NOT_ENOUGH_FUNDS();\\n```\\n

Impact:
This behaviour will cause DOS when distributing the 2nd milestone or higher

Recommended mitigation:
```\\n-        if (recipient.proposalBid > poolAmount) revert NOT_ENOUGH_FUNDS();\\n+        if ((recipient.proposalBid * milestone.amountPercentage) / 1e18 > poolAmount) revert NOT_ENOUGH_FUNDS();\\n```\\n
Vulnerability: CREATE3 is not available in the zkSync Era.
Severity: medium

Description:
The zkSync Era docs explain how it differs from Ethereum.\\nPOC:\\n```\\n// SPDX-License-Identifier: Unlicensed\\npragma solidity ^0.8.0;\\n\\nimport "./MiniContract.sol";\\nimport "./CREATE3.sol";\\n\\ncontract DeployTest {\\n    address public deployedAddress;\\n    event Deployed(address);\\n    \\n    function generateContract() public returns(address, address) {\\n        bytes32 salt = keccak256("SALT");\\n\\n        address preCalculatedAddress = CREATE3.getDeployed(salt);\\n\\n        // check if the contract has already been deployed by checking code size of address\\n        bytes memory creationCode = abi.encodePacked(type(MiniContract).creationCode, abi.encode(777));\\n\\n        // Use CREATE3 to deploy the anchor contract\\n        address deployed = CREATE3.deploy(salt, creationCode, 0);\\n        return (preCalculatedAddress, deployed);\\n    }\\n}\\n```\\n\\nAlso, the logic to compute the address of Create2 is different from Ethereum, as shown below, so the CREATE3 library cannot be used as it is.\\nThis cause registry returns an incorrect `preCalculatedAddress`, causing the anchor to be registered to an address that is not the actual deployed address.\\n```\\naddress ⇒ keccak256( \\n    keccak256("zksyncCreate2") ⇒ 0x2020dba91b30cc0006188af794c2fb30dd8520db7e2c088b7fc7c103c00ca494, \\n    sender, \\n    salt, \\n    keccak256(bytecode), \\n    keccak256(constructorInput)\\n ) \\n```\\n

Impact:
`generateAnchor` doesn't work, so user can't do anything related to anchor.

Recommended mitigation:
This can be solved by implementing CREATE2 directly instead of CREATE3 and using `type(Anchor).creationCode`. Also, the compute address logic needs to be modified for zkSync.
Vulnerability: UUPSUpgradeable vulnerability in OpenZeppelin Contracts
Severity: medium

Description:
Openzeppelin has found the critical severity bug in UUPSUpgradeable. The kyber-swap contracts has used both openzeppelin contracts as well as openzeppelin upgrabable contracts with version v4.3.1. This is confirmed from package.json.\\n```\\nFile: ks-elastic-sc/package.json\\n\\n    "@openzeppelin/contracts": "4.3.1",\\n    "@openzeppelin/test-helpers": "0.5.6",\\n    "@openzeppelin/contracts-upgradeable": "4.3.1",\\n```\\n\\nThe `UUPSUpgradeable` vulnerability has been found in openzeppelin version as follows,\\n@openzeppelin/contracts : Affected versions >= 4.1.0 < 4.3.2 @openzeppelin/contracts-upgradeable : >= 4.1.0 < 4.3.2\\nHowever, openzeppelin has fixed this issue in versions 4.3.2\\nOpenzeppelin bug acceptance and fix: check here\\nThe following contracts has been affected due to this vulnerability\\nPoolOracle.sol\\nTokenPositionDescriptor.sol\\nBoth of these contracts are UUPSUpgradeable and the issue must be fixed.

Impact:
Upgradeable contracts using UUPSUpgradeable may be vulnerable to an attack affecting uninitialized implementation contracts.

Recommended mitigation:
Update the openzeppelin library to latest version.\\nCheck this openzeppelin security advisory to initialize the UUPS implementation contracts.\\nCheck this openzeppelin UUPS documentation.
Vulnerability: MultiAccount `depositAndAllocateForAccount` function doesn't scale the allocated amount correctly, failing to allocate enough funds
Severity: medium

Description:
Internal accounting (allocatedBalances) are tracked as fixed numbers with 18 decimals, while collateral tokens can have different amount of decimals. This is correctly accounted for in AccountFacet.depositAndAllocate:\\n```\\n    AccountFacetImpl.deposit(msg.sender, amount);\\n    uint256 amountWith18Decimals = (amount * 1e18) /\\n        (10 ** IERC20Metadata(GlobalAppStorage.layout().collateral).decimals());\\n    AccountFacetImpl.allocate(amountWith18Decimals);\\n```\\n\\nBut it is treated incorrectly in MultiAccount.depositAndAllocateForAccount:\\n```\\n    ISymmio(symmioAddress).depositFor(account, amount);\\n    bytes memory _callData = abi.encodeWithSignature(\\n        "allocate(uint256)",\\n        amount\\n    );\\n    innerCall(account, _callData);\\n```\\n\\nThis leads to incorrect allocated amounts.

Impact:
Similar to 222 from previous audit contest, the user expects to have full amount deposited and allocated, but ends up with only dust amount allocated, which can lead to unexpected liquidations (for example, user is at the edge of liquidation, calls depositAndAllocate to improve account health, but is liquidated instead). For consistency reasons, since this is almost identical to 222, it should also be high.

Recommended mitigation:
Scale amount correctly before allocating it:\\n```\\n    ISymmio(symmioAddress).depositFor(account, amount);\\n+   uint256 amountWith18Decimals = (amount * 1e18) /\\n+       (10 ** IERC20Metadata(collateral).decimals());\\n    bytes memory _callData = abi.encodeWithSignature(\\n        "allocate(uint256)",\\n-       amount\\n+       amountWith18Decimals\\n    );\\n    innerCall(account, _callData);\\n```\\n
Vulnerability: Incorrect approach to tracking the PnL of a DV
Severity: high

Description:
Let $DV_A$ be a certain destination vault.\\nAssume that at $T0$, the current debt value (currentDvDebtValue) of $DV_A$ is 95 WETH, and the last debt value (updatedDebtBasis) is 100 WETH. Since the current debt value has become smaller than the last debt value, the vault is making a loss of 5 WETH since the last rebalancing, so $DV_A$ is sitting at a loss, and users can only burn a limited amount of DestinationVault_A's shares.\\nAssume that at $T1$, there is some slight rebalancing performed on $DV_A$, and a few additional LP tokens are deposited to it. Thus, its current debt value increased to 98 WETH. At the same time, the `destInfo.debtBasis` and `destInfo.ownedShares` will be updated to the current value.\\nImmediately after the rebalancing, $DV_A$ will not be considered sitting in a loss since the `currentDvDebtValue` and `updatedDebtBasis` should be equal now. As a result, users could now burn all the $DV_A$ shares of the LMPVault during withdrawal.\\n$DV_A$ suddenly becomes not sitting at a loss even though the fact is that it is still sitting at a loss of 5 WETH. The loss has been written off.\\n```\\nFile: LMPDebt.sol\\n        // Neither of these numbers include rewards from the DV\\n        if (currentDvDebtValue < updatedDebtBasis) {\\n            // We are currently sitting at a loss. Limit the value we can pull from\\n            // the destination vault\\n            currentDvDebtValue = currentDvDebtValue.mulDiv(userShares, totalVaultShares, Math.Rounding.Down);\\n            currentDvShares = currentDvShares.mulDiv(userShares, totalVaultShares, Math.Rounding.Down);\\n        }\\n```\\n

Impact:
A DV might be incorrectly marked as not sitting in a loss, thus allowing users to burn all the DV shares, locking in all the loss of the DV and the vault shareholders.

Recommended mitigation:
Consider a more sophisticated approach to track a DV's Profit and Loss (PnL).\\nIn our example, $DV_A$ should only be considered not making a loss if the price of the LP tokens starts to appreciate and cover the loss of 5 WETH.
Vulnerability: ETH deposited by the user may be stolen.
Severity: high

Description:
In the `deposit` function, if the user pays with ETH, it will first call `_processEthIn` to wrap it and then call `pullToken` to transfer.\\n```\\n    /// @inheritdoc ILMPVaultRouterBase\\n    function deposit(\\n        ILMPVault vault,\\n        address to,\\n        uint256 amount,\\n        uint256 minSharesOut\\n    ) public payable virtual override returns (uint256 sharesOut) {\\n        // handle possible eth\\n        _processEthIn(vault);\\n\\n        IERC20 vaultAsset = IERC20(vault.asset());\\n        pullToken(vaultAsset, amount, address(this));\\n\\n        return _deposit(vault, to, amount, minSharesOut);\\n    }\\n```\\n\\n`_processEthIn` will wrap ETH into WETH, and these WETH belong to the contract itself.\\n```\\n    function _processEthIn(ILMPVault vault) internal {\\n        // if any eth sent, wrap it first\\n        if (msg.value > 0) {\\n            // if asset is not weth, revert\\n            if (address(vault.asset()) != address(weth9)) {\\n                revert InvalidAsset();\\n            }\\n\\n            // wrap eth\\n            weth9.deposit{ value: msg.value }();\\n        }\\n    }\\n```\\n\\nHowever, `pullToken` transfers from `msg.sender` and does not use the WETH obtained in `_processEthIn`.\\n```\\n    function pullToken(IERC20 token, uint256 amount, address recipient) public payable {\\n        token.safeTransferFrom(msg.sender, recipient, amount);\\n    }\\n```\\n\\nIf the user deposits 10 ETH and approves 10 WETH to the contract, when the deposit amount is 10, all of the user's 20 WETH will be transferred into the contract.\\nHowever, due to the `amount` being 10, only 10 WETH will be deposited into the vault, and the remaining 10 WETH can be stolen by the attacker using `sweepToken`.\\n```\\n    function sweepToken(IERC20 token, uint256 amountMinimum, address recipient) public payable {\\n        uint256 balanceToken = token.balanceOf(address(this));\\n        if (balanceToken < amountMinimum) revert InsufficientToken();\\n\\n        if (balanceToken > 0) {\\n            token.safeTransfer(recipient, balanceToken);\\n        }\\n    }\\n```\\n\\nBoth `mint` and `deposit` in `LMPVaultRouterBase` have this problem.

Impact:
ETH deposited by the user may be stolen.

Recommended mitigation:
Perform operations based on the size of `msg.value` and amount:\\nmsg.value == amount: transfer WETH from contract not `msg.sender`\\nmsg.value > amount: transfer WETH from contract not `msg.sender` and refund to `msg.sender`\\nmsg.value < amount: transfer WETH from contract and transfer remaining from `msg.sender`