Farseen0/scar-pairs huggingface dataset:
HIGH severity: TRST-More than one hat of the same hatId can be assigned to a user. **Description:**
Hats are minted internally using `_mintHat()`.

As documentation states, it is unsafe if **_wearer** already has the **hatId**. However, this could 
easily be the case when called from `mintHat()`. 

The function validates **_wearer** doesn't currently wear the hat, but its...

Vulnerable code:
/// @notice Internal call to mint a Hat token to a wearer
        /// @dev Unsafe if called when `_wearer` has a non-zero balance of `_hatId`
        /// @param _wearer The wearer of the Hat and the recipient of the  newly minted token
        /// @param _hatId The id of the Hat to mint
        function _mintHat(address _wearer, uint256 _hatId) internal {
            unchecked {
        // should not overflow since `mintHat` enforces max balance of 1
            _balanceOf[_wearer][_hatId] = 1;
        // increment Hat supply counter
        // should not overflow given AllHatsWorn check in `mintHat` ++_hats[_hatId].supply;
        }
        emit TransferSingle(msg.sender, address(0), _wearer, _hatId, 1);
        }

function mintHat(uint256 _hatId, address _wearer) public returns (bool) {
        Hat memory hat = _hats[_hatId];
            if (hat.maxSupply == 0) revert HatDoesNotExist(_hatId);
        // only the wearer of a hat's admin Hat can mint it
             _checkAdmin(_hatId);
            if (hat.supply >= hat.maxSupply) {
                 revert AllHatsWorn(_hatId);
                     }
        if (isWearerOfHat(_wearer, _hatId)) {
                revert AlreadyWearingHat(_wearer, _hatId);
                      }
        _mintHat(_wearer, _hatId);
             return true;
                  }
HIGH severity: TRST-TXs can be executed by less than the minimum required signatures. **Description:**
In HatsSignerGateBase, `checkTransaction()` is the function called by the Gnosis safe to 
approve the transaction. Several checks are in place.

The first check is that the number of owners registered on the safe is at least **minThreshold**. 
The second check is that the number...

Vulnerable code:
uint256 safeOwnerCount = safe.getOwners().length;
             if (safeOwnerCount < minThreshold) {
                 revert BelowMinThreshold(minThreshold, safeOwnerCount);
        }

uint256 validSigCount = countValidSignatures(txHash, signatures, signatures.length / 65);
        // revert if there aren't enough valid signatures
             if (validSigCount < safe.getThreshold()) {
              revert InvalidSigners();
                  }
HIGH severity: TRST-Target signature threshold can be bypassed leading to minority TXs. **Description:**
`checkTransaction()` is the enforcer of the HSG logic, making sure signers are wearers of hats 
and so on. The check below makes sure sufficient hat wearers signed the TX:

The issue is that the safe's threshold is not guaranteed to be up to date. For example, 
initially there...

Vulnerable code:
uint256 validSigCount = countValidSignatures(txHash, signatures, signatures.length / 65);
                // revert if there aren't enough valid signatures
        if (validSigCount < safe.getThreshold()) {
                     revert InvalidSigners();
         }
HIGH severity: TRST-maxSigners can be bypassed. **Description:**
**maxSigners** is specified when creating an HSG and is left constant. It is enforced in two ways 
**–targetThreshold** may never be set above it, and new signers cannot register to the HSG 
when the signer count reached **maxSigners**. Below is the implementation code in...

Vulnerable code:
function claimSigner() public virtual {
             if (signerCount == maxSigners) {
                revert MaxSignersReached();
        }
        if (safe.isOwner(msg.sender)) {
                revert SignerAlreadyClaimed(msg.sender);
            }
        if (!isValidSigner(msg.sender)) {
             revert NotSignerHatWearer(msg.sender);
         }
         _grantSigner(msg.sender);
           }

address[] memory owners = safe.getOwners();
         uint256 ownerCount = owners.length;
    if (ownerCount >= maxSigs) {
        _swapSigner(owners, ownerCount, maxSigs, currentSignerCount, msg.sender);
    } else {
        _grantSigner(owners, currentSignerCount, msg.sender);
        }
HIGH severity: TRST-Minority may be able to call safe operations. **Description:**
Users can update the HSG's view of signers using reconcileSignerCount()

Notice that the safe's registered threshold is only updated if the new **validSignerCount** is 
lower than the **targetThreshold**. Actually, that is not desired behavior, because if signers
have reactivated...

Vulnerable code:
function reconcileSignerCount() public {
            address[] memory owners = safe.getOwners();
                 uint256 validSignerCount = _countValidSigners(owners);
        // update the signer count accordingly
        signerCount = validSignerCount;
        if (validSignerCount <= targetThreshold && validSignerCount != safe.getThreshold())
             {
        bytes memory data =  abi.encodeWithSignature("changeThreshold(uint256)", validSignerCount);
        bool success = safe.execTransactionFromModule(
        address(safe), // to 0, 
        // value data, // data
        Enum.Operation.Call // operation
        );
        if (!success) {
                   revert FailedExecChangeThreshold();
                }
             }
         }
MEDIUM severity: TRST-Attacker can DOS minting of new top hats in low-fee chains. **Description:**
In Hats protocol, anyone can be assigned a top hat via the `mintTopHat()` function. The top 
hats are structured with top 32 bits acting as a domain ID, and the lower 224 bits are 
cleared. There are therefore up to 2^32 = ~ 4 billion top hats. Once they are all consumed,...

Vulnerable code:
// uint32 lastTopHatId will overflow in brackets
             topHatId = uint256(++lastTopHatId) << 224;
MEDIUM severity: TRST-Linking of hat trees can freeze hat operations. **Description:**
Hats support tree-linking, where hats from one node link to the first level of a different 
domain. This way, the amount of levels for the linked-to tree increases by the linked-from 
level count. This is generally fine, however lack of checking of the new total level introduces...

Vulnerable code:
/// @notice Identifies the level a given hat in its hat tree
        /// @param _hatId the id of the hat in question
        /// @return level (0 to type(uint8).max)
     function getHatLevel(uint256 _hatId) public view returns (uint8) {

if (treeAdmin != 0) {
                 return 1 + uint8(i) + getHatLevel(treeAdmin);
            }
MEDIUM severity: TRST-Admin can transfer hat to a non-eligible target, potentially burning the hat. **Description:**
Hat admins may transfer child hats using `transferHat()`. It checks the hat receiver does not 
currently have a balance for this **hatId**.

The issue is that it does not also check that the recipient is eligible for the **hatId**. Therefore, 
an admin could transfer a hat and...

Vulnerable code:
// Check if recipient is already wearing hat; also checks storage to  maintain balance == 1 invariant
        if (_balanceOf[_to][_hatId] > 0) {
            revert AlreadyWearingHat(_to, _hatId);
         }
MEDIUM severity: TRST-Attacker can make a signer gate creation fail. **Description:** 
DAOs can deploy a HSG using `deployHatsSignerGateAndSafe()` or 
`deployMultiHatsSignerGateAndSafe()`.The parameters are encoded and passed to 
`moduleProxyFactory.deployModule()`:

This function will call `createProxy()`:

The second parameter is the generated salt, which is...

Vulnerable code:
bytes memory initializeParams = abi.encode(_ownerHatId, _signersHatId, _safe, hatsAddress, _minThreshold, 
    _targetThreshold, _maxSigners, version );
        hsg = moduleProxyFactory.deployModule(hatsSignerGateSingleton, abi.encodeWithSignature("setUp(bytes)", 
    initializeParams), _saltNonce );

proxy = createProxy( masterCopy, keccak256(abi.encodePacked(keccak256(initializer), saltNonce)) );

function createProxy(address target, bytes32 salt)  internal  returns (address result)
        {
            if (address(target) == address(0)) revert ZeroAddress(target);
            if (address(target).code.length == 0) revert 
        TargetHasNoCode(target);
                bytes memory deployment = abi.encodePacked(
                  hex"602d8060093d393df3363d3d373d3d3d363d73", target, hex"5af43d82803e903d91602b57fd5bf3" );
            // solhint-disable-next-line no-inline-assembly
                assembly {
                     result := create2(0, add(deployment, 0x20), 
        mload(deployment), salt)
              }
                  if (result == address(0)) revert TakenAddress(result);
             }
MEDIUM severity: TRST-Signers can backdoor the safe to execute any transaction in the future without consensus. **Description:** 
The function `checkAfterExecution()` is called by the safe after signer's request TX was 
executed (and authorized). It mainly checks that the linkage between the safe and the HSG 
has not been compromised.

However, it is missing a check that no new modules have been introduced...

Vulnerable code:
function checkAfterExecution(bytes32, bool) external override {
            if (abi.decode(StorageAccessible(address(safe)).getStorageAt(uint256(GUARD_STORAGE_SLOT), 1), (address))
                    != address(this)) 
                    {
                revert CannotDisableThisGuard(address(this));
            }
            if (!IAvatar(address(safe)).isModuleEnabled(address(this))) {
                    revert CannotDisableProtectedModules(address(this));
            }
            if (safe.getThreshold() != _correctThreshold()) {
                     revert SignersCannotChangeThreshold();
            }
            // leave checked to catch underflows triggered by re-erntry
        attempts
            --guardEntries;
        }
LOW severity: TRST-createHat does not detect MAX_LEVEL admin correctly. **Description:**
In `createHat()`, the contract checks user is not minting hats for the lowest hat tier:

The issue is that it does not check for max level correctly, as it looks only at the lowest 8 bits. 
Each level is composed of 16 bits, so ID xx00 would pass this check. 
Fortunately, although.....

Vulnerable code:
function createHat( uint256 _admin, string memory _details, uint32 _maxSupply, address _eligibility,
            address _toggle, bool _mutable,  string memory _imageURI)       
                 public returns (uint256 newHatId) {
        if (uint8(_admin) > 0) {
                    revert MaxLevelsReached();
                 }
             ….
        }
LOW severity: TRST-Incorrect imageURI is returned for hats in certain cases. **Description:**
Function `getImageURIForHat()` should return the most relevant imageURI for the requested 
hatId. It will iterate backwards from the current level down to level 0, and return an image if 
it exists for that level.

It can be observed that the loop body will not run for level 0....

Vulnerable code:
function getImageURIForHat(uint256 _hatId) public view returns (string memory) {
                // check _hatId first to potentially avoid the `getHatLevel` call
                      Hat memory hat = _hats[_hatId];
                        string memory imageURI = hat.imageURI; // save 1 SLOAD
                // if _hatId has an imageURI, we return it
                            if (bytes(imageURI).length > 0) {
                return imageURI;
                }
                // otherwise, we check its branch of admins
                        uint256 level = getHatLevel(_hatId);
                // but first we check if _hatId is a tophat, in which case we fall back to the global image uri
                if (level == 0) return baseImageURI;
                // otherwise, we check each of its admins for a valid imageURI
                     uint256 id;
                // already checked at `level` above, so we start the loop at `level - 1`
                for (uint256 i = level - 1; i > 0;) {
                     id = getAdminAtLevel(_hatId, uint8(i));
                        hat = _hats[id];
                            imageURI = hat.imageURI;
                if (bytes(imageURI).length > 0) {
                      return imageURI;
                 }
                // should not underflow given stopping condition is > 0
                    unchecked {
                         --i;
                    }
             }
                // if none of _hatId's admins has an imageURI of its own, we 
            again fall back to the global image uri
                return baseImageURI;
            }
LOW severity: TRST-Fetching of hat status may fail due to lack of input sanitization. **Description:**
The functions `_isActive()` and `_isEligible()` are used by `balanceOf()` and other functions, so 
they should not ever revert. However, they perform ABI decoding from external inputs.

If **toggle** returns invalid return data (whether malicious or by accident), `abi.decode()`...

Vulnerable code:
function _isActive(Hat memory _hat, uint256 _hatId) internal view  returns (bool) {
        bytes memory data = 
             abi.encodeWithSignature("getHatStatus(uint256)", _hatId);
                (bool success, bytes memory returndata) = 
        _hat.toggle.staticcall(data);
        if (success && returndata.length > 0) {
            return abi.decode(returndata, (bool));
                } else {
        return _getHatStatus(_hat);
                }
         }
LOW severity: TRST-Admin check is overly gas-intensive. **Description:**
Hats checks for most operations that the caller is an authorized hat admin. The function 
implementing this check is `isAdminOfHat()`. The function loops and checks if the sender is a 
wearer of a lower-level hat.

The issue is that calling `getAdminAtLevel()` for every level is...

Vulnerable code:
while (adminHatLevel > 0) {
             if (isWearerOfHat(_user, getAdminAtLevel(_hatId, adminHatLevel))) 
        {
                return true;
            }
        // should not underflow given stopping condition > 0
            unchecked {
                --adminHatLevel;
            }
        }

// if we get here, we're at the top of _hatId's local tree
             linkedTreeAdmin = linkedTreeAdmins[getTophatDomain(_hatId)];
        if (linkedTreeAdmin == 0) {
            // tree is not linked
                return isWearerOfHat(_user, getLocalAdminAtLevel(_hatId, 0));
        } else {
            if (isWearerOfHat(_user, linkedTreeAdmin)) return true; // user  wears the linkedTreeAdmin
            else return isAdminOfHat(_user, linkedTreeAdmin); 
                // check if  user is admin of linkedTreeAdmin (recursion)
        }
LOW severity: TRST-Lack of zero-address check for important parameters. **Description:** 
Hats allows admin hats to change the **toggle** and **eligibility** set for a given hat. For example:

The code lacks a zero-address check for _newToggle. There is no good reason why this value 
might need to be set to zero, and it's a standard best practice for...

Vulnerable code:
function changeHatToggle(uint256 _hatId, address _newToggle) external 
    {
        _checkAdmin(_hatId);
             Hat storage hat = _hats[_hatId];
    if (!_isMutable(hat)) {
            revert Immutable();
        }
         hat.toggle = _newToggle;
             emit HatToggleChanged(_hatId, _newToggle);
     }
HIGH severity: TRST-_calculateClaim() does not distribute boost emissions correctly. **Description:**
The function `_calculateClaim()` is responsible for the calculations of the amount of emissions a 
specific veSatin is entitled to claim. The idea is to distribute emissions only to veSatin tokens 
locked for more than **minLockDurationForReward** and only for the extra time the...

Vulnerable code:
if ((lockEndTime - oldUserPoint.ts) > (minLockDurationForReward)) {
        toDistribute +=
          (balanceOf * tokensPerWeek[weekCursor]) / veSupply[weekCursor];
      weekCursor += WEEK;
      }

if ((lockEndTime - weekCursor) > (minLockDurationForReward)) {
HIGH severity: TRST-Users will be unable to claim emissions from veSatin tokens if they withdraw it or merge it. **Description:**
The function `_calculateClaim()` uses the variable **lockEndTime** when checking if a veSatin is 
entitled to emissions for a particular week (code with mitigation from TRST-H-1):

However **lockEndTime** is set to 0 whenever a user withdraws a veSatin by calling `withdraw()` or...

Vulnerable code:
if ((lockEndTime - weekCursor) > (minLockDurationForReward)) {
            toDistribute +=
        (balanceOf * tokensPerWeek[weekCursor]) / veSupply[weekCursor];
        weekCursor += WEEK;
       }
HIGH severity: TRST-It’s never possible to vote for new pools until setMaxVotesForPool() is called. **Description:**
The function `_vote()` allows voting on a pool only when the current amount of votes plus the 
new votes is lower or equal to the value returned by `_calculateMaxVotePossible()`:

However, `_calculateMaxVotePossible()` returns 0 for every pool in which the variable...

Vulnerable code:
require(_poolWeights <= _calculateMaxVotePossible(_pool), "Max votes exceeded");

return ((totalVotingPower * maxVotesForPool[_pool]) / 100);
HIGH severity: TRST-The protocol might transfer extra SATIN emissions to veSatin holders potentially making SatinVoter.sol insolvent. **Description:**
The function `_distribute()` in SatinVoter.sol is generally responsible for distributing weekly 
emissions to a gauge based on the percentage of total votes the associated pool received. In 
particular, it’s called by `updatePeriod()` (as per fix TRST-H-4) on the gauge associated...

Vulnerable code:
uint _claimable = claimable[_gauge];
      if (SATIN_CASH_LP_GAUGE == _gauge) {
            veShare = calculateSatinCashLPVeShare(_claimable);
      _claimable -= veShare;
      }
      if (_claimable > IMultiRewardsPool(_gauge).left(token) && _claimable / DURATION > 0) {
          claimable[_gauge] = 0;
      if (is4poolGauge[_gauge]) {
              IGauge(_gauge).notifyRewardAmount(token, _claimable, true);
      } else {
                IGauge(_gauge).notifyRewardAmount(token, _claimable, false);
           }
      emit DistributeReward(msg.sender, _gauge, _claimable);
        }

if (SATIN_CASH_LP_GAUGE == _gauge && msg.sender == minter) {
        veShare = calculateSatinCashLPVeShare(_claimable);
           claimable[_gauge] -= veShare;
              _claimable -= veShare;
        }
HIGH severity: TRST-It’s possible to drain all the funds from ExternalBribe. **Description:**
The function `earned()` is used to calculate the amount rewards owed to a **tokenId**, to do so it 
performs a series operations over a loop and then it always executes:

which adds to **reward** the amount of rewards earned by the **tokenId** during the last epoch in 
which it...

Vulnerable code:
Checkpoint memory cp = checkpoints[tokenId][_endIndex];
      uint _lastEpochStart = _bribeStart(cp.timestamp);
        uint _lastEpochEnd = _lastEpochStart + DURATION;
    if (block.timestamp > _lastEpochEnd) {
            reward += (cp.balanceOf * 
         tokenRewardsPerEpoch[token][_lastEpochStart]) /
           supplyCheckpoints[getPriorSupplyIndex(_lastEpochEnd)].supply;

function earned(address token, uint tokenId) public view returns (uint) {
      if (numCheckpoints[tokenId] == 0) {
          return 0;
      }
        uint reward = 0;
          uint _ts = 0;
           uint _bal = 0;
             uint _supply = 1;
               uint _index = 0;
          uint _currTs = _bribeStart(lastEarn[token][tokenId]); // take epoch last claimed in as starting point
      _index = getPriorBalanceIndex(tokenId, _currTs);
          _ts = checkpoints[tokenId][_index].timestamp;
              _bal = checkpoints[tokenId][_index].balanceOf;
      // accounts for case where lastEarn is before first checkpoint
        _currTs = Math.max(_currTs, _bribeStart(_ts));
      // get epochs between current epoch and first checkpoint in same epoch as last claim
      uint numEpochs = (_bribeStart(block.timestamp) - _currTs) / DURATION;
      if (numEpochs > 0) {
      for (uint256 i = 0; i < numEpochs; i++) {
      // get index of last checkpoint in this epoch
      _index = getPriorBalanceIndex(tokenId, _currTs + DURATION);
      // get checkpoint in this epoch
      _ts = checkpoints[tokenId][_index].timestamp;
          _bal = checkpoints[tokenId][_index].balanceOf;
      // get supply of last checkpoint in this epoch
            _supply = supplyCheckpoints[getPriorSupplyIndex(_currTs + DURATION)].supply;
      reward += _bal * tokenRewardsPerEpoch[token][_currTs] / _supply;
            _currTs += DURATION;
             }
           }
      return reward;
        }
MEDIUM severity: TRST-BaseV1Pair could break because of overflow. **Description:**
In the function _update(), called internally by `mint()`, `burn()` and `swap()`, the following code 
is executed:

This is forked from UniswapV2 source code, and it’s meant and known to overflow. It works 
fine if solidity < 0.8.0 is used but reverts when solidity >= 0.8.0 is...

Vulnerable code:
uint256 timeElapsed = blockTimestamp - blockTimestampLast;
     // overflow is desired
    if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
      reserve0CumulativeLast += _reserve0 * timeElapsed;
        reserve1CumulativeLast += _reserve1 * timeElapsed;
     }
MEDIUM severity: TRST-createGauge4Pool() lacks proper checks and/or access control. **Description:** 
The function createGauge4Pool() can be called by anybody at any time and is used to create 
a Gauge for a special pool, the 4pool. It takes 5 parameters as inputs: 

None of the parameters are properly sanitized, meaning **_dai, _usdc, _usdt, _cash** could be 
any whitelisted...

Vulnerable code:
function createGauge4pool(
       address _4pool,
          address _dai,
            address _usdc,
               address _usdt,
             address _cash
       ) external returns (address) {
MEDIUM severity: TRST-_vote() doesn’t check the pool a user is voting for has a valid gauge. **Description:** 
The function `_vote()` allows veSatin holders to vote to which pool the weekly emissions 
should be redirected to but it doesn’t check the pool a user is voting for has a gauge 
associated, which could lead to emissions being lost.

**Recommended Mitigation:**
Ensure that the...

Vulnerable code:
if(isGauge[_gauge]) { //<== CHECK IT'S A VALID GAUGE
          int256 _poolWeight = (_weights[i] * _weight) / _totalVoteWeight;
    //
      emit Voted(msg.sender, _tokenId, _poolWeight);
    }
MEDIUM severity: TRST-Calculation of the new emissions amount is not 75%/25%/5%. **Description:**
The calculations of the emissions directed to SatinVoter.sol, VeDist.sol, and the treasury are 
done on top of the weekly emissions instead as part of it, resulting in a different percentage 
distribution. The calculations are as follows:

Where **growthDivider**** is set to 20,...

Vulnerable code:
uint _weekly = WEEKLY_EMISSION;
         WEEKLY_EMISSION =(_weekly * _WEEKLY_EMISSION_DECREASE) / _WEEKLY_EMISSION_DECREASE_DENOMINATOR;
    uint _growth = _calculateGrowth(_weekly);
        uint _required = _growth + _weekly;

uint _required = _weekly;
    // …snippet…
           token.approve(address(_voter()), _weekly - _growth - _toTeam);
    _voter().notifyRewardAmount(_weekly - _growth - _toTeam);
MEDIUM severity: TRST-updatePeriod() distributes the same amount of emissions regardless of the time passed from the last distribution. **Description:**
The function `updatePeriod()` distributes new rewards when it’s called more than one week 
after the last distribution. However, if a distribution only happens after two weeks or more, 
the distributed amount is the same as if only one week passed. This can lead to users 
earning...

Vulnerable code:
_period = (block.timestamp / _WEEK) * _WEEK;
           uint sinceLast = _period - activePeriod;
         uint emissionsMultiplier = sinceLast / _WEEK;
      uint _weekly = WEEKLY_EMISSION * emissionsMultiplier;