Farseen0/scar-pairs huggingface dataset:
HIGH severity: Attacker Can Grief A Victim Creating Many Little Deposits On Victim’s Behalf. **Severity** - High

**Status** - Resolved

**Description**

An attacker can create many small deposits on behalf of the victim using just dust amount (using the `stakeFor()` function) , with each subsequent stake a new deposit struct would be pushed to `user.deposits` (L563) , the gas cost of...

Vulnerable code:
for (i = 0; i < user.deposits.length; i++) {
      Deposit storage stakeDeposit = user.deposits[i];
      if (!stakeDeposit.isYield) {
        totalSum =
          totalSum +
          stakeDeposit.tokenAmount *
          (stakeDeposit.lockedUntil - stakeDeposit.lockedFrom);
      }
    }
MEDIUM severity: `vaultRewardsPerWeight` Should Be Updated When `_processVaultRewards()`. **Severity** - Medium

**Status** - Resolved

**Description**

The `vaultRewardsPerWeight`  is incremented whenever there are rewards transferred in to the core pool contract →

But it is not decremented when `BUIDL` is transferred out of the contract in `_processVaultRewards()`...

Vulnerable code:
SafeERC20.safeTransferFrom(IERC20(buidl), msg.sender, address(this), _rewardsAmount);


    vaultRewardsPerWeight += rewardToWeight(_rewardsAmount, usersLockingWeight);

// transfer fails if pool BUIDL balance is not enough - which is a desired behavior
    SafeERC20.safeTransfer(IERC20(buidl), _staker, pendingVaultClaim);
MEDIUM severity: Rounding Issue While Calculating `liquidPercentage`. **Severity** - Medium

**Status** - Resolved

**Description**

The `liquidPercentage` calculated inside `_processRewards()` (L773) is as follows →

Here a new deposit is being created (for rewards) and the `liquidPercentage` is `user.liquidWeight*100 / user.totalWeight` , if the `totalWeight` of...

Vulnerable code:
Deposit memory newDeposit = Deposit({
        tokenAmount: pendingYield,
        lockedFrom: uint64(block.timestamp),
        lockedUntil: uint64(block.timestamp + getRewardLockPeriod(_staker)), // staking yield for Reward Lock Period
        weight: depositWeight,
        liquidPercentage: (user.liquidWeight * 100) / user.totalWeight, //AUDIT-round
        isYield: true
      });
MEDIUM severity: Staker Can Extend Expired Locks. **Severity** - Medium

**Status** - Resolved

**Description**

When a user stakes , his stake gets assigned a weight which is dependent on the length of the stake and the amount staked. A user can also update his stake which updates the deadline (`lockedUntil`) of his stake , so if the stake was...

Vulnerable code:
int256 newWeight = (((stakeDeposit.lockedUntil - stakeDeposit.lockedFrom) *
      WEIGHT_MULTIPLIER) /
      365 days +
      WEIGHT_MULTIPLIER) * stakeDeposit.tokenAmount;
LOW severity: Missing Deposit ID Emission on New Deposit. **Severity**: Low

**Status**: Resolved

**Location**: PoolBase.sol

**Description**

When a new deposit is pushed to the deposits array using `user.deposits.push(deposit);`, the `depositId`, which corresponds to the index of this new deposit, is not emitted. This ID is necessary for subsequent...

Vulnerable code:
event DepositMade(address indexed user, uint256 depositId, uint256 amount);

function deposit(uint256 _amount) external {
    // Assume necessary validations and logic here
    
    uint256 depositId = user.deposits.length;
    user.deposits.push(deposit);
    
    emit DepositMade(msg.sender, depositId, _amount);
    
    // Continue with other logic if necessary
}
HIGH severity: The referrer receives the lowest available amount for the first purchase, even when he referred above the maximum threshold, leading to receiving fewer funds than expected.. **Severity**: Critical

**Status**: Acknowledged 

**Description**

When a user purchases, let's say, 10 vaults using a referrer's referral code, the number of referrals for the referrer increases by 10. This number is used to calculate the percentage of the paid funds that the referrer receives...

Vulnerable code:
function testPOCReferrerIsNotGettingPaidForReferrals() public {
       vm.startPrank(randomUser);
      
       (uint256 price, ) = lumiaVault.pricePerVault();
       paymentToken.dispense();
       uint256 numOfBuyingVaults = 1000;
       paymentToken.approve(address(lumiaVault), price * numOfBuyingVaults);

       string memory referralCode = lumiaVault.getReferralCode(lumiaWalletAddress);

       // User buys with lumia referral code buy it counts 0 referrals
       uint256 referrerBalanceBefore = paymentToken.balanceOf(lumiaWalletAddress);
       lumiaVault.purchaseFirstVaults(referralCode, numOfBuyingVaults);
       uint256 referrerBalanceAfter = paymentToken.balanceOf(lumiaWalletAddress);
       uint256 referrerAmount = ( price * numOfBuyingVaults * 5) / 100;
       assert(referrerBalanceAfter == referrerBalanceBefore + referrerAmount);

       // User buys again with lumia referral code and now it counts 1000 referrals
       paymentToken.approve(address(lumiaVault), price);
       uint256 secondBuyAmount = 1;
       uint256 referrerBalanceBefore2 = paymentToken.balanceOf(lumiaWalletAddress);
       lumiaVault.purchaseMoreVaults(secondBuyAmount);
       uint256 referrerBalanceAfter2 = paymentToken.balanceOf(lumiaWalletAddress);
       uint256 referrerAmount2 = ( price * secondBuyAmount * 10) / 100;
       assert(referrerBalanceAfter2 == referrerBalanceBefore2 + referrerAmount2);
       vm.stopPrank();
   }
HIGH severity: Users can purchase vaults by paying less amount of funds than expected, even by the least. **Severity**: Critical	

**Status**: Acknowledged 

**Description**

Users call to `purchaseFirstVaults()` or `purchaseMoreVaults()` in `NodeSale.sol` in order to buy vaults. These functions receive `numberOfVaults` as parameter to indicate the amount of vaults that a user wants to purchase. 
The...

Vulnerable code:
function testPOCByPassIncreasePrice() public {
       vm.startPrank(randomUser);
      
       (uint256 price, uint256 amountUntilNextPrice) = lumiaVault.pricePerVault();
       paymentToken.dispense();
       paymentToken.approve(address(lumiaVault), price * (amountUntilNextPrice - 1));

       string memory referralCode = lumiaVault.getReferralCode(lumiaWalletAddress);
       uint256 numOfBuyingVaults = 1;

       lumiaVault.purchaseFirstVaults(referralCode, amountUntilNextPrice - 1);

       (uint256 price2, uint256 amountUntilNextPrice2) = lumiaVault.pricePerVault();

       uint256 balanceBefore = paymentToken.balanceOf(randomUser);
       uint256 numbreOfBuyingVaults = 5;
       paymentToken.approve(address(lumiaVault), price * numbreOfBuyingVaults);
       lumiaVault.purchaseMoreVaults(numbreOfBuyingVaults);
       uint256 balanceAfter = paymentToken.balanceOf(randomUser);

       assert(balanceAfter == (balanceBefore - (numbreOfBuyingVaults * price2)));
       vm.stopPrank();
   }

function testPOCBuyAllVaultsByMinPrice() public {
      uint256 MAX_ALLOWED_VAULTS = 50000;

       vm.startPrank(randomUser);
      
       (uint256 price, uint256 amountUntilNextPrice) = lumiaVault.pricePerVault();
       paymentToken.dispense();
       paymentToken.approve(address(lumiaVault), price * MAX_ALLOWED_VAULTS);

       string memory referralCode = lumiaVault.getReferralCode(lumiaWalletAddress);

       uint256 balanceBefore = paymentToken.balanceOf(randomUser);
       lumiaVault.purchaseFirstVaults(referralCode, MAX_ALLOWED_VAULTS);
       uint256 balanceAfter = paymentToken.balanceOf(randomUser);

       assert(balanceAfter == (balanceBefore - (price * MAX_ALLOWED_VAULTS)));
   }
MEDIUM severity: The same referral code can be set for 2 different addresses.. **Severity**: Medium	

**Status**: Resolved

**Description**

The `setUniqReferralCode()` function allows the `ADMIN_ROLE` to set a specific referral code to an address. There are not checks to ensure that the set code is not already being used to another address. This can lead to an scenario...

Vulnerable code:
function testPOCReeplaceAlreadyUsedCODE() public {
       vm.startPrank(deployer);
  
       string memory referralCode = lumiaVault.getReferralCode(lumiaWalletAddress);
       address referrerAddress = lumiaVault.codeToAddress(referralCode);
       assert(lumiaWalletAddress == referrerAddress);

       // Set the same code to other address
       lumiaVault.setUniqReferralCode(randomUser, referralCode);
       address referrerAddressAfter = lumiaVault.codeToAddress(referralCode);
       assert(referrerAddressAfter == randomUser);

       vm.stopPrank();
   }
MEDIUM severity: MasterAccounts are not able to buy vaults.. **Severity**: Medium	

**Status**: Acknowledged

**Description**

When `NodeSale.sol` is deployed 2 calls to `createMasterAccount` are executed within the contract constructor. These calls creates 2 accounts without the need of a referred. The addresses created as `master accounts` without a...

Vulnerable code:
require(referrer != address(0), "LumiaVault: Invalid Referral Code");

function testMasterAccountBuysVault() public {
       vm.startPrank(partnerWalletAddress);
       (uint256 price, ) = lumiaVault.pricePerVault();
       paymentToken.dispense();
       paymentToken.approve(address(lumiaVault), price);

       string memory referralCode = lumiaVault.getReferralCode(lumiaWalletAddress);
       uint256 numOfBuyingVaults = 1;
       vm.expectRevert();
       lumiaVault.purchaseFirstVaults(referralCode, numOfBuyingVaults);

       vm.expectRevert();
       lumiaVault.purchaseMoreVaults(1);
      
       vm.stopPrank();
   }
HIGH severity: Incorrect logic in a while loop. **Severity**: High

**Status**: Resolved

**Description**

In the `_removeBurnedBatches()` function, if `batch.amount` is less than `burnAmount`, it means that remaining is greater than `batch.amount` and the function goes to the “else” case.
In the else case, `ownerBatches[from][i]` is updated...

Vulnerable code:
while (remaining > 0 && i < len) {
    uint256 batchId = ownerBatches[from][i];
    Batch storage batch = batches[batchId];
    uint256 burnAmount = batch.amount > remaining ? remaining : batch.amount;


    if (burnAmount > 0) {
        if (batch.amount > burnAmount) {
            batch.amount -= burnAmount;
            remaining -= burnAmount;
        } else {
            remaining -= batch.amount;
            ownerBatches[from][i] = ownerBatches[from][len - 1];
            ownerBatches[from].pop();
            delete batches[batchId];
            len--;
        }
    }
    unchecked {
        ++i;
    }
}

while (remaining > 0 && i < len) {
    uint256 batchId = ownerBatches[from][i];
    Batch storage batch = batches[batchId];
    uint256 burnAmount = batch.amount > remaining ? remaining : batch.amount;


    if (burnAmount > 0) {
        if (batch.amount > burnAmount) {
            batch.amount -= burnAmount;
            remaining -= burnAmount;
        } else {
            remaining -= batch.amount;
            ownerBatches[from][i] = ownerBatches[from][len - 1];
            ownerBatches[from].pop();
            delete batches[batchId];
            len--;
            continue;
        }
    }
    unchecked {
        ++i;
    }
}

while (remaining > 0 && i < len) {
    uint256 batchId = fromOwnerBatches[i];
    Batch storage fromBatch = batches[batchId];
    uint256 transferAmount = fromBatch.amount > remaining ? remaining : fromBatch.amount;


    if (transferAmount > 0) {
        uint256 toBatchId = minterBatchMap[to][fromBatch.originalMinter];
        if (toBatchId != 0) {
            batches[toBatchId].amount += transferAmount;
        } else {
            toBatchId = batchCounter++;
            batches[toBatchId] = Batch(toBatchId, transferAmount, fromBatch.originalMinter);
            ownerBatches[to].push(toBatchId);
            minterBatchMap[to][fromBatch.originalMinter] = toBatchId;
        }


        if (fromBatch.amount > transferAmount) {
            fromBatch.amount -= transferAmount;
            remaining -= transferAmount;
        } else {
            remaining -= fromBatch.amount;
            fromOwnerBatches[i] = fromOwnerBatches[len - 1];
            fromOwnerBatches.pop();
            len--;
            continue;
        }
    }
    unchecked {
        ++i;
    }
}
HIGH severity: Possible Read-only Reentrance in Refund. **Severity**: Critical

**Status**: Resolved

**Description**

It was identified that the `treasury.sol` smart contract doesn’t fully implement the checks-effects-interactions pattern in most functions. For instance, An attacker could exploit the contract via Read-only Reentrancy between...

Vulnerable code:
File: treasury.sol
1241:     function refundFromAdminInETH(uint projectId, uint slotId) external  { 
..
1248:             
1249:             payable(user).transfer(investorInvestedAmountPerProjectInETH[projectId][user]); 
1250:          projectTotalCollectedInETH[projectId]-=investorInvestedAmountPerProjectInETH[projectId][user];
1251:          investorInvestedAmountPerProjectInETH[projectId][user]-=investorInvestedAmountPerProjectInETH[projectId][user];
1252:         }
1253: 
1254:     }


File: treasury.sol
1199:     function refund(Whitelist memory users) external payable nonReentrant { 
..
1212:         } else {
1213:             require (!refundAmountInETH[users.userAddress][users.projectId],"!Refund completed");
1214:             require (investorInvestedAmountPerProjectInETH[users.projectId][users.userAddress] >= amount,'Invalid Amount to retrieve');
1215:             investorInvestedAmountPerProjectInETH[users.projectId][users.userAddress] -= amount;
1216:             payable (users.userAddress).transfer(amount);
1217:             projectTotalCollectedInETH[users.projectId]-=amount;
1218:             refundAmountInETH[users.userAddress][users.projectId]=true;
1219:         }
1220:     }
HIGH severity: Incorrect Handling of Investment Amounts. **Severity**: High

**Status**: Resolved 

**Description**

The `investInProjects()` function in Treasury.sol does not validate that `msg.value` matches `users.amount`. Instead, it adds `msg.value` to `investorInvestedAmountPerProjectInETH`. 

The following attack vectors may happen:

1. Users can.....

Vulnerable code:
File: treasury.sol
1178:     function settleProjectOwnerFunds(Whitelist memory users) external  {
..
1191:             else {
1192:                 fundsWithdrawn[users.projectId][users.inETH] += _amountToWithdraw;
1193:                 uint deducedAmount = projectTotalCollectedInETH[users.projectId] - (projectTotalCollectedInETH[users.projectId] *(users.platformFee))/100;
1194:                 require (deducedAmount >= fundsWithdrawn[users.projectId][users.inETH],"check deducedAmount");
1195:                 payable(users.userAddress).transfer(_amountToWithdraw); //@audit this should be investorInvestedAmountPerProjectInETH
1196:                 projectTotalCollectedInETH[users.projectId]-=_amountToWithdraw;
1197:             }
HIGH severity: Missing Decimals Conversion in `settleProjectOwnerFunds`. **Severity**: High

**Status**: Resolved

**Description**

The `settleProjectOwnerFunds()` function in Treasury.sol does not account for the decimal conversion of the `users.amount` when calculating `_amountToWithdraw`. This can lead to incorrect fund settlements

**Recommendation**: 

Ensure that.....

Vulnerable code:
File: treasury.sol
1178:     function settleProjectOwnerFunds(Whitelist memory users) external  {
..
1183:             uint _amountToWithdraw = users.amount ;
MEDIUM severity: User can bypass the max amount restriction. **Severity**: Medium	

**Status**: Resolved

**Description**

The function `upgradeStakeTokens()` implements a `require` statement to ensure that the total staked amount by the user is less or equal to 1000 tokens when the lockType is 1.

This restriction is missing in the `stakeTokens` function,...

Vulnerable code:
if(_locktype == 1){
               require(totalamount<=1000*10**18 ,"max amount reached"); 
}
MEDIUM severity: `stakeLength()` function does not return the actual length of stakers. **Severity**: Medium	

**Status**: Resolved

**Description**
  
The `stakeLength()` function in `staking.sol` is a function that returns the length of `userStakeInformation`. A new element is added `userStakeInformation` each time a user stakes tokens. When a user unstakes tokens the information...

Vulnerable code:
function testPOCDeleteMapping() public {
       vm.startPrank(address(alice));
       stakingToken.mint(address(alice), 1 ether);
       uint _amount = 1 ether;
       uint _locktype = 0;
       whitelistChecker.Signer memory _signer;
       _signer.userAddress = address(alice);
       _signer.contractAddress = address(staking);
       _signer.id = 0;
       _signer.timestamp = block.timestamp;
       _signer.signature = '0x';

       // Stake tokens
       stakingToken.approve(address(staking), _amount);
       staking.stakeTokens(_amount, _locktype, _signer);

       // Unstake
       vm.warp(block.timestamp + 8 days);
       uint256 stakeLengthBeforeUnstake = staking.stakeLength(address(alice));
       (uint256 stakedAmountBefore,,,) = staking.userStakeInformation(address(alice), 0);
       staking.unStake(0, _signer);
       uint256 stakeLengthAfterUnstake = staking.stakeLength(address(alice));
       assert(stakeLengthAfterUnstake == stakeLengthBeforeUnstake);

       (uint256 stakedAmountAfter,,,) = staking.userStakeInformation(address(alice), 0);
       console.log("StakedLengthLogs", stakeLengthBeforeUnstake, stakeLengthAfterUnstake);
       console.log("StakedAmountLogs", stakedAmountBefore, stakedAmountAfter);
   }

StakedLengthLogs 1 1
StakedAmountLogs 1000000000000000000 0

function removeArrayElement(uint poolId) internal {
           stakeInfo memory toDelete = userStakeInformation[msg.sender][poolId];
           for (uint256 i = 0; i < userStakeInformation[msg.sender].length; i++) {
               if (userStakeInformation[msg.sender][i].id == toDelete.id) {
                   userStakeInformation[msg.sender][i] = userStakeInformation[msg.sender][userStakeInformation[msg.sender].length - 1];
                   userStakeInformation[msg.sender].pop();
                   break;
               }
           }
       }
MEDIUM severity: Malicious Owner can set the fee to 100 percent and then withdraw the shares. **Severity**: Medium

**Status**: Resolved

**Description**

The `setplatformFee` function in Treasury.sol allows setting a platform fee for a project without imposing any maximum limit. This poses a significant risk if the owner’s private key got leaked, a malicious owner could drain all the...

Vulnerable code:
File: treasury.sol
1233:      function setplatformFee(uint projectId,uint _platformFee) external view { 
1234:       require(msg.sender==owner() || isOwner[msg.sender],"!Owner");
1235:         PlatformFee[projectId][_platformFee]; 
1236:         Whitelist memory users;
1237:         users.projectId=projectId;
1238:         users.platformFee=_platformFee;
1239:     }
HIGH severity: Insufficient Validation of Referral Address in mint. **Severity**: High

**Status**: Acknowledged

**Description**

The `mint` function allows users to specify a referral address to receive a discount on the minting fee. However, the referral address is not validated. This means that users can set the referral address to any arbitrary address,...

Vulnerable code:
_updateGlobalTimeThreshold();
        nextMint[partnerNFTAddress][partnerNFTtokenId] = globalTimeThreshold;

_updateGlobalTimeThreshold();
        nextSignatureMint[signature] = globalTimeThreshold;
HIGH severity: `LiquidityFreeLaunch` Contract Will Never Finalize a Free Launch Due to Incorrect Order of Operations for `isFinalized` and `factory.getPair()`. **Severity**: Critical

**Status**: Resolved

**Description**

The `LiquidityFreeLaunch` contract is responsible for handling the bootstrapping and sales of the newly created `DegenERC20` token. Should the maximum ETH to be raised be met, purchases are processed through the `performFinalTrade()`...

Vulnerable code:
- When the `_performFinalTrade` function is called, it attempts to finalize the contract by sending the remaining tokens to the DEX.
- The `_sendToDex` function is called to create a liquidity pool on PancakeSwap and add liquidity.
- The contract tries to retrieve the pair address using `factory.getPair` before calling `router.addLiquidityETH`.
- Since the pair does not exist yet, `factory.getPair` returns an address of zero.
- This causes the subsequent LP token transfer to the dead address to fail, preventing the contract from being finalized.


- Users can never transfer or sell their acquired tokens because the contract is never finalized.
- The `DegenERC20` token contract has a condition that restricts transfers until the sale is finalized, rendering the tokens useless.
HIGH severity: FundingFee is incorrectly accrued, leading it to always be 0.. **Severity**: Critical	

**Status**: Resolved

**Description**

The `netFundingAmount`, which represents the funding fee amount for a trader based on the size and funding rates, is calculated and returned by the `getFundingFee()` function withing the `Formula.sol` smart contract. This function...

Vulnerable code:
function getFundingFee(address _asset, uint256 _size, FundingRate memory _entryFundingRate, bool _islong)
   external
   view
   returns (uint256 netFundingAmount, bool tobePaid)
{
   // @note-rajeeb: fundingRate decimals 10^4
   FundingRate memory currentFundingRate = cumulativeFundingRates[_asset];
   FundingRate memory netFundingRate = subtractFundingRates(_entryFundingRate, currentFundingRate);

    netFundingAmount *= (_size * netFundingRate.value) / 1e6; 
   // fundingRate decimals 4
   // @note-rajeeb: size (100) = 100_000_000, fundingRate (0.1%) = 1000
   // funding amount = (100_000_000 * 1000)/1_000_000 = 100_000 (i.e. = 0.1)
   if (
       (_islong && netFundingRate._direction == Direction.LongToShort)
           || (!_islong && netFundingRate._direction == Direction.ShortToLong)
   ) {
       tobePaid = true;
   } else {
       tobePaid = false;
   }
HIGH severity: Position average price is wrongly calculated leading to users paying more/less for their position than the actual amount required.. **Severity**: Critical	

**Status**: Resolved

**Description**

When a user opens a position calling `increasePosition()` within the `TradeFacet.sol` smart contract the position’s average price is calculated by `getNextAveragePrice()`. This function calculates the average price considering...

Vulnerable code:
function updateReserve(IncreaseParamaters memory _increasePositionParams, uint256 price) internal {
       if (_increasePositionParams.isLong) {
           if (s.reservedSizeLong[_increasePositionParams.indexToken] == 0) {
               s.averagePriceLong[_increasePositionParams.indexToken] = price;
               s.reservedSizeLong[_increasePositionParams.indexToken] = (_increasePositionParams.indexDelta) / price;

} else {
               updateAveragePriceShort(_increasePositionParams.indexToken, price, _increasePositionParams.indexDelta);
               _increaseGlobalShortSize(
                   _increasePositionParams.indexToken, (_increasePositionParams.indexDelta) / (price) 
               );
           }
HIGH severity: User’s funds are locked and lost when decreasing a position. **Severity**: Critical	

**Status**: Resolved

**Description**

When a user decreases a position calling `decreasePosition()` within the `TradeFacet.sol` smart contract, a call to `lockForAnOrder` is executed. `lockForAnOrder` is locking user’s fundings which can later not get unlocked:

Locking...

Vulnerable code:
/// @notice Lock an amount of USDC for an order
   /// @param _account The address of the user whose funds will be locked
   /// @param _amount The amount of USDC to lock
   /// @dev Can only be called by the diamond contract
   function lockForAnOrder(address _account, uint256 _amount) external onlyDiamond whenNotPaused {
       if (_amount > balances[_account]) {
           revert LockForOrderFailed(_account);
       }
       locked[_account] += _amount;
       balances[_account] = balances[_account] - _amount;
   }