msc-smart-contract-auditing/vulnerable-functions-base huggingface dataset:
Vulnerability: If another module adds a module, the safe will be bricked
Severity: high

Description:
If a module is added by another module, it will bypass the `enableNewModule()` function that increments `enabledModuleCount`. This will throw off the module validation in `checkTransaction()` and `checkAfterExecution()` and could cause the safe to become permanently bricked.\\nIn order to ensure that signers cannot add new modules to the safe (thus giving them unlimited future governing power), the guard portion of the gate checks that the hash of the modules before the transaction is the same as the hash after.\\nBefore:\\n```\\n(address[] memory modules,) = safe.getModulesPaginated(SENTINEL_OWNERS, enabledModuleCount);\\n_existingModulesHash = keccak256(abi.encode(modules));\\n```\\n\\nAfter:\\n```\\n(address[] memory modules,) = safe.getModulesPaginated(SENTINEL_OWNERS, enabledModuleCount + 1);\\nif (keccak256(abi.encode(modules)) != _existingModulesHash) {\\n    revert SignersCannotChangeModules();\\n}\\n```\\n\\nYou'll note that the "before" check uses `enabledModuleCount` and the "after" check uses `enabledModuleCount + 1`. The reason for this is that we want to be able to catch whether the user added a new module, which requires us taking a larger pagination to make sure we can view the additional module.\\nHowever, if we were to start with a number of modules larger than `enabledModuleCount`, the result would be that the "before" check would clip off the final modules, and the "after" check would include them, thus leading to different hashes.\\nThis situation can only arise if a module is added that bypasses the `enableModule()` function. But this exact situation can happen if one of the other modules on the safe adds a module to the safe.\\nIn this case, the modules on the safe will increase but `enabledModuleCount` will not. This will lead to the "before" and "after" checks returning different arrays each time, and therefore disallowing transactions.\\nThe only possible ways to fix this problem will be to have the other module remove the additional one they added. But, depending on the specific circumstances, this option may not be possible. For example, the module that performed the adding may not have the ability to remove modules.

Impact:
The safe can be permanently bricked, with the guard functions disallowing any transactions. All funds in the safe will remain permanently stuck.

Recommended mitigation:
The module guarding logic needs to be rethought. Given the large number of unbounded risks it opens up, I would recommend not allowing other modules on any safes that use this functionality.
Vulnerability: Owners can be swapped even though they still wear their signer hats
Severity: medium

Description:
`HatsSignerGateBase` does not check for a change of owners post-flight. This allows a group of actors to collude and replace opposing signers with cooperating signers, even though the replaced signers still wear their signer hats.\\nThe `HatsSignerGateBase` performs various checks to prevent a multisig transaction to tamper with certain variables. Something that is currently not checked for in `checkAfterExecution` is a change of owners. A colluding group of malicious signers could abuse this to perform swaps of safe owners by using a delegate call to a corresponding malicious contract. This would bypass the requirement of only being able to replace an owner if he does not wear his signer hat anymore as used in _swapSigner:\\n```\\nfor (uint256 i; i < _ownerCount - 1;) {\\n    ownerToCheck = _owners[i];\\n\\n    if (!isValidSigner(ownerToCheck)) {\\n        // prep the swap\\n        data = abi.encodeWithSignature(\\n            "swapOwner(address,address,address)",\\n            // rest of code\\n```\\n

Impact:
bypass restrictions and perform action that should be disallowed.

Recommended mitigation:
Issue Owners can be swapped even though they still wear their signer hats\\nPerform a pre- and post-flight comparison on the safe owners, analogous to what is currently done with the modules.
Vulnerability: The Hats contract needs to override the ERC1155.balanceOfBatch function
Severity: medium

Description:
The Hats contract does not override the ERC1155.balanceOfBatch function\\nThe Hats contract overrides the ERC1155.balanceOf function to return a balance of 0 when the hat is inactive or the wearer is ineligible.\\n```\\n    function balanceOf(address _wearer, uint256 _hatId)\\n        public\\n        view\\n        override(ERC1155, IHats)\\n        returns (uint256 balance)\\n    {\\n        Hat storage hat = _hats[_hatId];\\n\\n        balance = 0;\\n\\n        if (_isActive(hat, _hatId) && _isEligible(_wearer, hat, _hatId)) {\\n            balance = super.balanceOf(_wearer, _hatId);\\n        }\\n    }\\n```\\n\\nBut the Hats contract does not override the ERC1155.balanceOfBatch function, which causes balanceOfBatch to return the actual balance no matter what the circumstances.\\n```\\n    function balanceOfBatch(address[] calldata owners, uint256[] calldata ids)\\n        public\\n        view\\n        virtual\\n        returns (uint256[] memory balances)\\n    {\\n        require(owners.length == ids.length, "LENGTH_MISMATCH");\\n\\n        balances = new uint256[](owners.length);\\n\\n        // Unchecked because the only math done is incrementing\\n        // the array index counter which cannot possibly overflow.\\n        unchecked {\\n            for (uint256 i = 0; i < owners.length; ++i) {\\n                balances[i] = _balanceOf[owners[i]][ids[i]];\\n            }\\n        }\\n    }\\n```\\n

Impact:
This will make balanceOfBatch return a different result than balanceOf, which may cause errors when integrating with other projects

Recommended mitigation:
Consider overriding the ERC1155.balanceOfBatch function in Hats contract to return 0 when the hat is inactive or the wearer is ineligible.
Vulnerability: [Medium][Outdated State] `_removeSigner` incorrectly updates `signerCount` and safe `threshold`
Severity: medium

Description:
`_removeSigner` can be called whenever a signer is no longer valid to remove an invalid signer. However, under certain situations, `removeSigner` incorrectly reduces the number of `signerCount` and sets the `threshold` incorrectly.\\n`_removeSigner` uses the code snippet below to decide if the number of `signerCount` should be reduced:\\n```\\n        if (validSignerCount == currentSignerCount) {\\n            newSignerCount = currentSignerCount;\\n        } else {\\n            newSignerCount = currentSignerCount - 1;\\n        }\\n```\\n\\nIf first clause is supposed to be activated when `validSignerCount` and `currentSignerCount` are still in sync, and we want to remove an invalid signer. The second clause is for when we need to identify a previously active signer which is inactive now and want to remove it. However, it does not take into account if a previously in-active signer became active. In the scenario described below, the `signerCount` would be updated incorrectly:\\n(1) Lets imagine there are 5 signers where 0, 1 and 2 are active while 3 and 4 are inactive, the current `signerCount = 3` (2) In case number 3 regains its hat, it will become active again (3) If we want to delete signer 4 from the owners' list, the `_removeSigner` function will go through the signers and find 4 valid signers, since there were previously 3 signers, `validSignerCount == currentSignerCount` would be false. (4) In this case, while the number of `validSingerCount` increased, the `_removeSigner` reduces one.

Impact:
This can make the `signerCount` and safe `threshold` to update incorrectly which can cause further problems, such as incorrect number of signatures needed.

Recommended mitigation:
Check if the number of `validSignerCount` decreased instead of checking equality:\\n```\\n@line 387 HatsSignerGateBase\\n- if (validSignerCount == currentSignerCount) {\\n+ if (validSignerCount >= currentSignerCount) {\\n```\\n
Vulnerability: targetThreshold can be set below minThreshold, violating important invariant
Severity: medium

Description:
There are protections in place to ensure that `minThreshold` is not set above `targetThreshold`, because the result is that the max threshold on the safe would be less than the minimum required. However, this check is not performed when `targetThreshold` is set, which results in the same situation.\\nWhen the `minThreshold` is set on `HatsSignerGateBase.sol`, it performs an important check that `minThreshold` <= targetThreshold:\\n```\\nfunction _setMinThreshold(uint256 _minThreshold) internal {\\n    if (_minThreshold > maxSigners || _minThreshold > targetThreshold) {\\n        revert InvalidMinThreshold();\\n    }\\n\\n    minThreshold = _minThreshold;\\n}\\n```\\n\\nHowever, when `targetThreshold` is set, there is no equivalent check that it remains above minThreshold:\\n```\\nfunction _setTargetThreshold(uint256 _targetThreshold) internal {\\n    if (_targetThreshold > maxSigners) {\\n        revert InvalidTargetThreshold();\\n    }\\n\\n    targetThreshold = _targetThreshold;\\n}\\n```\\n\\nThis is a major problem, because if it is set lower than `minThreshold`, `reconcileSignerCount()` will set the safe's threshold to be this value, which is lower than the minimum, and will cause all tranasctions to fail.

Impact:
Settings that are intended to be guarded are not, which can lead to parameters being set in such a way that all transactions fail.

Recommended mitigation:
Perform a check in `_setTargetThreshold()` that it is greater than or equal to minThreshold:\\n```\\nfunction _setTargetThreshold(uint256 _targetThreshold) internal {\\n// Add the line below\\n   if (_targetThreshold < minThreshold) {\\n// Add the line below\\n     revert InvalidTargetThreshold();\\n// Add the line below\\n   }\\n    if (_targetThreshold > maxSigners) {\\n        revert InvalidTargetThreshold();\\n    }\\n\\n    targetThreshold = _targetThreshold;\\n}\\n```\\n
Vulnerability: Hats can be overwritten
Severity: medium

Description:
Child hats can be created under a non-existent admin. Creating the admin allows overwriting the properties of the child-hats, which goes against the immutability of hats.\\n```\\n    function _createHat(\\n        uint256 _id,\\n        string calldata _details,\\n        uint32 _maxSupply,\\n        address _eligibility,\\n        address _toggle,\\n        bool _mutable,\\n        string calldata _imageURI\\n    ) internal returns (Hat memory hat) {\\n        hat.details = _details;\\n        hat.maxSupply = _maxSupply;\\n        hat.eligibility = _eligibility;\\n        hat.toggle = _toggle;\\n        hat.imageURI = _imageURI;\\n        hat.config = _mutable ? uint96(3 << 94) : uint96(1 << 95);\\n        _hats[_id] = hat;\\n\\n\\n        emit HatCreated(_id, _details, _maxSupply, _eligibility, _toggle, _mutable, _imageURI);\\n    }\\n```\\n\\nNow, the next eligible hat for this admin is 1.1.1, which is a hat that was already created and minted. This can allow the admin to change the properties of the child, even if the child hat was previously immutable. This contradicts the immutability of hats, and can be used to rug users in multiple ways, and is thus classified as high severity. This attack can be carried out by any hat wearer on their child tree, mutating their properties.

Impact:

Recommended mitigation:
Check if admin exists, before minting by checking any of its properties against default values\\n```\\nrequire(_hats[admin].maxSupply > 0, "Admin not created")\\n```\\n
Vulnerability: Safe can be bricked because threshold is updated with validSignerCount instead of newThreshold
Severity: high

Description:
The safe's threshold is supposed to be set with the lower value of the `validSignerCount` and the `targetThreshold` (intended to serve as the maximum). However, the wrong value is used in the call to the safe's function, which in some circumstances can lead to the safe being permanently bricked.\\nIn `reconcileSignerCount()`, the valid signer count is calculated. We then create a value called `newThreshold`, and set it to the minimum of the valid signer count and the target threshold. This is intended to be the value that we update the safe's threshold with.\\n```\\nif (validSignerCount <= target && validSignerCount != currentThreshold) {\\n    newThreshold = validSignerCount;\\n} else if (validSignerCount > target && currentThreshold < target) {\\n    newThreshold = target;\\n}\\n```\\n\\nHowever, there is a typo in the contract call, which accidentally uses `validSignerCount` instead of `newThreshold`.\\nThe result is that, if there are more valid signers than the `targetThreshold` that was set, the threshold will be set higher than intended, and the threshold check in `checkAfterExecution()` will fail for being above the max, causing all safe transactions to revert.\\nThis is a major problem because it cannot necessarily be fixed. In the event that it is a gate with a single hat signer, and the eligibility module for the hat doesn't have a way to turn off eligibility, there will be no way to reduce the number of signers. If this number is greater than `maxSigners`, there is no way to increase `targetThreshold` sufficiently to stop the reverting.\\nThe result is that the safe is permanently bricked, and will not be able to perform any transactions.

Impact:
All transactions will revert until `validSignerCount` can be reduced back below `targetThreshold`, which re

Recommended mitigation:
Change the value in the function call from `validSignerCount` to `newThreshold`.\\n```\\nif (newThreshold > 0) {\\n// Remove the line below\\n    bytes memory data = abi.encodeWithSignature("changeThreshold(uint256)", validSignerCount);\\n// Add the line below\\n    bytes memory data = abi.encodeWithSignature("changeThreshold(uint256)", newThreshold);\\n\\n    bool success = safe.execTransactionFromModule(\\n        address(safe), // to\\n        0, // value\\n        data, // data\\n        Enum.Operation.Call // operation\\n    );\\n\\n    if (!success) {\\n        revert FailedExecChangeThreshold();\\n    }\\n}\\n```\\n
Vulnerability: Owners can be swapped even though they still wear their signer hats
Severity: medium

Description:
`HatsSignerGateBase` does not check for a change of owners post-flight. This allows a group of actors to collude and replace opposing signers with cooperating signers, even though the replaced signers still wear their signer hats.\\nThe `HatsSignerGateBase` performs various checks to prevent a multisig transaction to tamper with certain variables. Something that is currently not checked for in `checkAfterExecution` is a change of owners. A colluding group of malicious signers could abuse this to perform swaps of safe owners by using a delegate call to a corresponding malicious contract. This would bypass the requirement of only being able to replace an owner if he does not wear his signer hat anymore as used in _swapSigner:\\n```\\nfor (uint256 i; i < _ownerCount - 1;) {\\n    ownerToCheck = _owners[i];\\n\\n    if (!isValidSigner(ownerToCheck)) {\\n        // prep the swap\\n        data = abi.encodeWithSignature(\\n            "swapOwner(address,address,address)",\\n            // rest of code\\n```\\n

Impact:
bypass restrictions and perform action that should be disallowed.

Recommended mitigation:
Perform a pre- and post-flight comparison on the safe owners, analogous to what is currently done with the modules.
Vulnerability: Safe can be bricked because threshold is updated with validSignerCount instead of newThreshold
Severity: high

Description:
The safe's threshold is supposed to be set with the lower value of the `validSignerCount` and the `targetThreshold` (intended to serve as the maximum). However, the wrong value is used in the call to the safe's function, which in some circumstances can lead to the safe being permanently bricked.\\nIn `reconcileSignerCount()`, the valid signer count is calculated. We then create a value called `newThreshold`, and set it to the minimum of the valid signer count and the target threshold. This is intended to be the value that we update the safe's threshold with.\\n```\\nif (validSignerCount <= target && validSignerCount != currentThreshold) {\\n    newThreshold = validSignerCount;\\n} else if (validSignerCount > target && currentThreshold < target) {\\n    newThreshold = target;\\n}\\n```\\n\\nHowever, there is a typo in the contract call, which accidentally uses `validSignerCount` instead of `newThreshold`.\\nThe result is that, if there are more valid signers than the `targetThreshold` that was set, the threshold will be set higher than intended, and the threshold check in `checkAfterExecution()` will fail for being above the max, causing all safe transactions to revert.\\nThis is a major problem because it cannot necessarily be fixed. In the event that it is a gate with a single hat signer, and the eligibility module for the hat doesn't have a way to turn off eligibility, there will be no way to reduce the number of signers. If this number is greater than `maxSigners`, there is no way to increase `targetThreshold` sufficiently to stop the reverting.\\nThe result is that the safe is permanently bricked, and will not be able to perform any transactions.

Impact:
All transactions will revert until `validSignerCount` can be reduced back below `targetThreshold`, which re

Recommended mitigation:
Issue Safe can be bricked because threshold is updated with validSignerCount instead of newThreshold\\nChange the value in the function call from `validSignerCount` to `newThreshold`.\\n```\\nif (newThreshold > 0) {\\n// Remove the line below\\n    bytes memory data = abi.encodeWithSignature("changeThreshold(uint256)", validSignerCount);\\n// Add the line below\\n    bytes memory data = abi.encodeWithSignature("changeThreshold(uint256)", newThreshold);\\n\\n    bool success = safe.execTransactionFromModule(\\n        address(safe), // to\\n        0, // value\\n        data, // data\\n        Enum.Operation.Call // operation\\n    );\\n\\n    if (!success) {\\n        revert FailedExecChangeThreshold();\\n    }\\n}\\n```\\n
Vulnerability: Changing hat toggle address can lead to unexpected changes in status
Severity: medium

Description:
Changing the toggle address should not change the current status unless intended to. However, in the event that a contract's toggle status hasn't been synced to local state, this change can accidentally toggle the hat back on when it isn't intended.\\nWhen an admin for a hat calls `changeHatToggle()`, the `toggle` address is updated to a new address they entered:\\n```\\nfunction changeHatToggle(uint256 _hatId, address _newToggle) external {\\n    if (_newToggle == address(0)) revert ZeroAddress();\\n\\n    _checkAdmin(_hatId);\\n    Hat storage hat = _hats[_hatId];\\n\\n    if (!_isMutable(hat)) {\\n        revert Immutable();\\n    }\\n\\n    hat.toggle = _newToggle;\\n\\n    emit HatToggleChanged(_hatId, _newToggle);\\n}\\n```\\n\\nToggle addresses can be either EOAs (who must call `setHatStatus()` to change the local config) or contracts (who must implement the `getHatStatus()` function and return the value).\\nThe challenge comes if a hat has a toggle address that is a contract. The contract changes its toggle value to `false` but is never checked (which would push the update to the local state). The admin thus expects that the hat is turned off.\\nThen, the toggle is changed to an EOA. One would expect that, until a change is made, the hat would remain in the same state, but in this case, the hat defaults back to its local storage state, which has not yet been updated and is therefore set to `true`.\\nEven in the event that the admin knows this and tries to immediately toggle the status back to `false`, it is possible for a malicious user to sandwich their transaction between the change to the EOA and the transaction to toggle the hat off, making use of a hat that should be off. This could have dramatic consequences when hats are used for purposes such as multisig signing.

Impact:
Hats may unexpectedly be toggled from `off` to `on` during toggle address transfer, reactivating hats that are intended to be turned `off`.

Recommended mitigation:
The `changeHatToggle()` function needs to call `checkHatToggle()` before changing over to the new toggle address, to ensure that the latest status is synced up.
Vulnerability: Precision differences when calculating userCollateralRatioMantissa causes major issues for some token pairs
Severity: high

Description:
When calculating userCollateralRatioMantissa in borrow and liquidate. It divides the raw debt value (in loan token precision) by the raw collateral balance (in collateral precision). This skew is fine for a majority of tokens but will cause issues with specific token pairs, including being unable to liquidate a subset of positions no matter what.\\nWhen calculating userCollateralRatioMantissa, both debt value and collateral values are left in the native precision. As a result of this certain token pairs will be completely broken because of this. Other pairs will only be partially broken and can enter state in which it's impossible to liquidate positions.\\nImagine a token pair like USDC and SHIB. USDC has a token precision of 6 and SHIB has 18. If the user has a collateral balance of 100,001 SHIB (100,001e18) and a loan borrow of 1 USDC (1e6) then their userCollateralRatioMantissa will actually calculate as zero:\\n```\\n1e6 * 1e18 / 100,001e18 = 0\\n```\\n\\nThere are two issues with this. First is that a majority of these tokens simply won't work. The other issue is that because userCollateralRatioMantissa returns 0 there are states in which some debt is impossible to liquidate breaking a key invariant of the protocol.\\nAny token with very high or very low precision will suffer from this.

Impact:
Some token pairs will always be/will become broken

Recommended mitigation:
userCollateralRatioMantissa should be calculated using debt and collateral values normalized to 18 decimal points
Vulnerability: Fee share calculation is incorrect
Severity: medium

Description:
Fees are given to the feeRecipient by minting them shares. The current share calculation is incorrect and always mints too many shares the fee recipient, giving them more fees than they should get.\\nThe current equation is incorrect and will give too many shares, which is demonstrated in the example below.\\nExample:\\n```\\n_supplied = 100\\n_totalSupply = 100\\n\\n_interest = 10\\nfee = 2\\n```\\n\\nCalculate the fee with the current equation:\\n```\\n_accuredFeeShares = fee * _totalSupply / supplied = 2 * 100 / 100 = 2\\n```\\n\\nThis yields 2 shares. Next calculate the value of the new shares:\\n```\\n2 * 110 / 102 = 2.156\\n```\\n\\nThe value of these shares yields a larger than expected fee. Using a revised equation gives the correct amount of fees:\\n```\\n_accuredFeeShares = (_totalSupply * fee) / (_supplied + _interest - fee) = 2 * 100 / (100 + 10 - 2) = 1.852\\n\\n1.852 * 110 / 101.852 = 2\\n```\\n\\nThis new equation yields the proper fee of 2.

Impact:
Fee recipient is given more fees than intended, which results in less interest for LPs

Recommended mitigation:
Issue Fee share calculation is incorrect\\nUse the modified equation shown above:\\n```\\n    uint fee = _interest * _feeMantissa / 1e18;\\n    // 13. Calculate the accrued fee shares\\n-   _accruedFeeShares = fee * _totalSupply / _supplied; // if supplied is 0, we will have returned at step 7\\n+   _accruedFeeShares = fee * (_totalSupply * fee) / (_supplied + _interest - fee); // if supplied is 0, we will have returned at step 7\\n    // 14. Update the total supply\\n    _currentTotalSupply += _accruedFeeShares;\\n```\\n