Farseen0/scar-pairs huggingface dataset:
HIGH severity: `GovPool::delegateTreasury` does not verify transfer of tokens and NFTs to delegatee leading to potential voting manipulation. **Description:** `GovPool::delegateTreasury` transfers ERC20 tokens & specific nfts from DAO treasury to `govUserKeeper`. Based on this transfer, the `tokenBalance` and `nftBalance` of the delegatee is increased. This allows a delegatee to use this delegated voting power to vote in critical...

Vulnerable code:
function delegateTreasury(
        address delegatee,
        uint256 amount,
        uint256[] calldata nftIds
    ) external override onlyThis {
        require(amount > 0 || nftIds.length > 0, "Gov: empty delegation");
        require(getExpertStatus(delegatee), "Gov: delegatee is not an expert");

        _unlock(delegatee);

        if (amount != 0) {
            address token = _govUserKeeper.tokenAddress();

  >          IERC20(token).transfer(address(_govUserKeeper), amount.from18(token.decimals())); //@audit no check if tokens are actually transferred

            _govUserKeeper.delegateTokensTreasury(delegatee, amount);
        }

        if (nftIds.length != 0) {
            IERC721 nft = IERC721(_govUserKeeper.nftAddress());

            for (uint256 i; i < nftIds.length; i++) {
  >              nft.safeTransferFrom(address(this), address(_govUserKeeper), nftIds[i]); //-n no check if nft's are actually transferred
            }

            _govUserKeeper.delegateNftsTreasury(delegatee, nftIds);
        }

        _revoteDelegated(delegatee, VoteType.TreasuryVote);

        emit DelegatedTreasury(delegatee, amount, nftIds, true);
    }
HIGH severity: Distribution proposals simultaneously funded by both ETH and ERtokens results in stuck eth. **Description:** [`DistributionProposal::execute()`](https://github.com/dexe-network/DeXe-Protocol/tree/f2fe12eeac0c4c63ac39670912640dc91d94bda5/contracts/gov/proposals/DistributionProposal.sol#L49-L69) allows distribution proposals to be simultaneously funded by both eth & erc20 tokens in the...

Vulnerable code:
it("audit new distribution proposals funded by both eth & erc20 tokens results in stuck eth", async () => {
        // DistributionProposal eth balance starts at 0
        let balanceBefore = toBN(await web3.eth.getBalance(dp.address));
        assert.equal(balanceBefore, 0);

        // mint reward tokens to sending address
        await token.mint(govPool.address, wei("10"));

        // use GovPool to create a proposal with 10 wei reward
        await govPool.createProposal(
          "example.com",
          [
            [token.address, 0, getBytesApprove(dp.address, wei("10"))],
            [dp.address, 0, getBytesDistributionProposal(1, token.address, wei("10"))],
          ],
          [],
          { from: SECOND }
        );

        // fully fund the proposal using both erc20 token and eth at the same time
        await impersonate(govPool.address);
        await token.approve(dp.address, wei("10"), { from: govPool.address });
        await dp.execute(1, token.address, wei("10"), { value: wei(10), from: govPool.address });

        // only 1 vote so SECOND should get the entire 10 wei reward
        await govPool.vote(1, true, 0, [1], { from: SECOND });

        // claiming the reward releases the erc20 tokens but the eth remains stuck
        await dp.claim(SECOND, [1]);

        // DistributionProposal eth balance at 10 wei, reward eth is stuck
        let balanceAfter = toBN(await web3.eth.getBalance(dp.address));
        assert.equal(balanceAfter, wei("10"));
      });
MEDIUM severity: Inconsistent decimal treatment for token amounts across codebase increases security risks for users interacting with Dexe DAO contracts. **Description:** Inconsistencies have been identified within the codebase regarding the assumed decimal format for token amounts. Some sections of the codebase assume token amounts to be in their native token decimals, converting them to 18 decimals when needed, while other sections assume all...

Vulnerable code:
function createTier(
        mapping(uint256 => ITokenSaleProposal.Tier) storage tiers,
        uint256 newTierId,
        ITokenSaleProposal.TierInitParams memory _tierInitParams
    ) external {
        _validateTierInitParams(_tierInitParams);

        uint256 saleTokenDecimals = _tierInitParams.saleTokenAddress.decimals();
        uint256 totalTokenProvided = _tierInitParams.totalTokenProvided;

  >      _tierInitParams.minAllocationPerUser = _tierInitParams.minAllocationPerUser.to18(
            saleTokenDecimals
        ); //@audit -> normalised to 18 decimals
   >    _tierInitParams.maxAllocationPerUser = _tierInitParams.maxAllocationPerUser.to18(
            saleTokenDecimals
        ); //@audit -> normalised to 18 decimals
   >     _tierInitParams.totalTokenProvided = totalTokenProvided.to18(saleTokenDecimals); //@audit -> normalised to 18 decimals

        ....
}

function getSaleTokenAmount(
        ITokenSaleProposal.Tier storage tier,
        address user,
        uint256 tierId,
        address tokenToBuyWith,
        uint256 amount
    ) public view returns (uint256) {
        ITokenSaleProposal.TierInitParams memory tierInitParams = tier.tierInitParams;
     require(amount > 0, "TSP: zero amount");
        require(canParticipate(tier, tierId, user), "TSP: cannot participate");
        require(
            tierInitParams.saleStartTime <= block.timestamp &&
                block.timestamp <= tierInitParams.saleEndTime,
            "TSP: cannot buy now"
        );

        uint256 exchangeRate = tier.rates[tokenToBuyWith];
>        uint256 saleTokenAmount = amount.ratio(exchangeRate, PRECISION); //@audit -> this saleTokenAmount is in  saleToken decimals -> unlike in the createTier function, this saleTokenAmount is not normalised to 18 decimals

        require(saleTokenAmount != 0, "TSP: incorrect token");

    >     require(
            tierInitParams.maxAllocationPerUser == 0 ||
                (tierInitParams.minAllocationPerUser <= saleTokenAmount &&
                    saleTokenAmount <= tierInitParams.maxAllocationPerUser),
            "TSP: wrong allocation"
        ); //@audit checks sale token amount is in valid limits
        require(
            tier.tierInfo.totalSold + saleTokenAmount <= tierInitParams.totalTokenProvided,
            "TSP: insufficient sale token amount"
        ); //@audit checks total sold is less than total provided
}
MEDIUM severity: Voting to change `RewardsInfo::voteRewardsCoefficient` has an unintended side-effect of retrospectively changing voting rewards for active proposals. **Description:** `GovSettings::editSettings` is one of the functions that can be executed via an internal proposal. When this function is called, setting are validated via `GovSettings::_validateProposalSettings`. This function does not check the value of `RewardsInfo::voteRewardsCoefficient`...

Vulnerable code:
function _getInitialVotingRewards(
        IGovPool.ProposalCore storage core,
        IGovPool.VoteInfo storage voteInfo
    ) internal view returns (uint256) {
        (uint256 coreVotes, uint256 coreRawVotes) = voteInfo.isVoteFor
            ? (core.votesFor, core.rawVotesFor)
            : (core.votesAgainst, core.rawVotesAgainst);

        return
            coreRawVotes.ratio(core.settings.rewardsInfo.voteRewardsCoefficient, PRECISION).ratio(
                voteInfo.totalVoted,
                coreVotes
            ); //@audit -> initial rewards are calculated proportionate to the vote rewards coefficient
    }
MEDIUM severity: Proposal execution can be DOSed with return bombs when calling untrusted execution contracts. **Description:** `GovPool::execute` does not check for return bombs when executing a low-level call. A return bomb is a large bytes array that expands the memory so much that any attempt to execute the transaction will lead to an `out-of-gas` exception.

This can create potentially risky outcomes...

Vulnerable code:
function execute(
        mapping(uint256 => IGovPool.Proposal) storage proposals,
        uint256 proposalId
    ) external {
        .... // code

        for (uint256 i; i < actionsLength; i++) {
>            (bool status, bytes memory returnedData) = actions[i].executor.call{
                value: actions[i].value
            }(actions[i].data); //@audit returnedData could expand memory and cause out-of-gas exception

            require(status, returnedData.getRevertMsg());
        }
   }

contract MaliciousProposalActionExecutor is IProposalValidator{

    function validate(IGovPool.ProposalAction[] calldata actions) external view override returns (bool valid){
    	valid = true;
    }

    function vote(
        uint256 proposalId,
        bool isVoteFor,
        uint256 voteAmount,
        uint256[] calldata voteNftIds
    ) external returns(bytes memory result){

	if(isVoteFor){
		// @audit implement actions for successful vote
        	return ""; // 0 bytes
        }
	else{
		// @audit implement actions for failed vote

		// Create a large bytes array
                assembly{
                     revert(0, 1_000_000)
              }
	}

   }
}
HIGH severity: Use low-level `call()` to prevent gas griefing attacks when returned data not required. **Description:** Using `call()` when the returned data is not required unnecessarily exposes to gas griefing attacks from huge returned data payload. For [example](https://github.com/dexe-network/DeXe-Protocol/tree/f2fe12eeac0c4c63ac39670912640dc91d94bda5/contracts/libs/utils/TokenBalance.sol#L31):....

Vulnerable code:
(bool status, ) = payable(receiver).call{value: amount}("");
require(status, "Gov: failed to send eth");

(bool status, bytes memory data ) = payable(receiver).call{value: amount}("");
require(status, "Gov: failed to send eth");

bool status;
assembly {
    status := call(gas(), receiver, amount, 0, 0, 0, 0)
}
HIGH severity: Events are missing indexed fields. **Description:** Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields).

**Impact:** Slower access for...

Vulnerable code:
File: factory/PoolFactory.sol

43:     event DaoPoolDeployed(

File: gov/ERC721/multipliers/AbstractERC721Multiplier.sol

25:     event Minted(uint256 tokenId, address to, uint256 multiplier, uint256 duration);

26:     event Locked(uint256 tokenId, address sender, bool isLocked);

27:     event Changed(uint256 tokenId, uint256 multiplier, uint256 duration);

File: gov/ERC721/multipliers/DexeERC721Multiplier.sol

21:     event AverageBalanceChanged(address user, uint256 averageBalance);
HIGH severity: Use of deprecated library function `safeApprove()`. **Description:** `safeApprove()` has been deprecated and the official OpenZeppelin documentation [recommends](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#SafeERC20-safeApprove-contract-IERC20-address-uint256-) using `safeIncreaseAllowance()` &...

Vulnerable code:
File: core/PriceFeed.sol

385:             IERC20(token).safeApprove(address(uniswapV2Router), MAX_UINT);
HIGH severity: Use `safeTransfer()` instead of `transfer()` for ER. **Description:** Use `safeTransfer` instead of `transfer` for ERC20.

**Impact:** INFO

**Proof of Concept:**

**Recommended Mitigation:** Use `safeTransfer` instead of `transfer` for ERC20.

**Dexe:**
Fixed in commit...

Vulnerable code:
File: gov/GovPool.sol

248:             IERC20(token).transfer(address(_govUserKeeper), amount.from18(token.decimals()));
HIGH severity: State variables should be cached in stack variables rather than re-reading them from storage. **Description:** The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable...

Vulnerable code:
File: core/PriceFeed.sol

385:             IERC20(token).safeApprove(address(uniswapV2Router), MAX_UINT);

File: gov/GovPool.sol

257:                 nft.safeTransferFrom(address(this), address(_govUserKeeper), nftIds[i]);

File: gov/user-keeper/GovUserKeeper.sol

567:         ERC721Power nftContract = ERC721Power(nftAddress);
HIGH severity: Use unchecked block to increment loop counter when overflow impossible. **Description:** Use unchecked block to increment loop counter when overflow impossible. Prefer `++i` to `i++` for loop counter increment. Included as [standard optimization](https://soliditylang.org/blog/2023/10/25/solidity-0.8.22-release-announcement/) in Solidity 0.8.22 under certain...

Vulnerable code:
File: gov/ERC20/ERC20Gov.sol

55:         for (uint256 i = 0; i < params.users.length; i++) {

55:         for (uint256 i = 0; i < params.users.length; i++) {

File: gov/GovPool.sol

256:             for (uint256 i; i < nftIds.length; i++) {

256:             for (uint256 i; i < nftIds.length; i++) {

305:         for (uint256 i; i < proposalIds.length; i++) {

305:         for (uint256 i; i < proposalIds.length; i++) {

318:         for (uint256 i; i < proposalIds.length; i++) {

318:         for (uint256 i; i < proposalIds.length; i++) {

File: gov/proposals/DistributionProposal.sol

79:         for (uint256 i; i < proposalIds.length; i++) {

79:         for (uint256 i; i < proposalIds.length; i++) {
HIGH severity: Don't initialize variables with default value. **Description:** Don't initialize variables with default value.

**Impact:** Gas optimization.

**Proof of Concept:**

From Solarity library:

**Recommended Mitigation:** Don't initialize variables with default value.

**Dexe:**
Acknowledged; we do not consider optimizations of 2-3 wei as a huge...

Vulnerable code:
File: gov/ERC20/ERC20Gov.sol

55:         for (uint256 i = 0; i < params.users.length; i++) {

File: gov/proposals/TokenSaleProposal.sol

82:         for (uint256 i = 0; i < tierInitParams.length; i++) {

96:         for (uint256 i = 0; i < requests.length; i++) {

102:         for (uint256 i = 0; i < tierIds.length; i++) {

108:         for (uint256 i = 0; i < tierIds.length; i++) {

114:         for (uint256 i = 0; i < tierIds.length; i++) {

120:         for (uint256 i = 0; i < tierIds.length; i++) {

184:         for (uint256 i = 0; i < tierIds.length; i++) {

195:         for (uint256 i = 0; i < tierIds.length; i++) {

205:         for (uint256 i = 0; i < recoveringAmounts.length; i++) {

223:         for (uint256 i = 0; i < userViews.length; i++) {

250:         for (uint256 i = 0; i < ids.length; i++) {

File: gov/user-keeper/GovUserKeeper.sol

461:         for (uint256 i = 0; i < nftIds.length; i++) {
LOW severity: Using bools for storage incurs overhead. **Description:** Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See...

Vulnerable code:
File: factory/PoolFactory.sol

41:     mapping(bytes32 => bool) private _usedSalts;

File: gov/GovPool.sol

76:     bool public onlyBABTHolders;

File: gov/validators/GovValidators.sol

35:     mapping(uint256 => mapping(bool => mapping(address => mapping(bool => uint256))))
HIGH severity: Broken check in `MysteryBox::fulfillRandomWords()` fails to prevent same request being fulfilled multiple times. **Description:** Consider the [check](https://github.com/Earnft/smart-contracts/blob/43d3a8305dd6c7325339ed35d188fe82070ee5c9/contracts/MysteryBox.sol#L221-L222) which attempts to prevent the same request from being fulfilled multiple times:

The problem is that `vrfRequests[_requestId].fulfilled`.....

Vulnerable code:
if (vrfRequests[_requestId].fulfilled) revert InvalidVrfState();
HIGH severity: Use low level `call()` to prevent gas griefing attacks when returned data not required. **Description:** Using `call()` when the returned data is not required unnecessarily exposes to gas griefing attacks from huge returned data payload. For [example](https://github.com/Earnft/smart-contracts/blob/43d3a8305dd6c7325339ed35d188fe82070ee5c9/contracts/MysteryBox.sol#L197-L198):

Is the...

Vulnerable code:
(bool sent, ) = address(operatorAddress).call{value: msg.value}("");
if (!sent) revert Unauthorized();

(bool sent, bytes memory data) = address(operatorAddress).call{value: msg.value}("");
if (!sent) revert Unauthorized();

bool sent;
assembly {
    sent := call(gas(), receiver, amount, 0, 0, 0, 0)
}
if (!sent) revert Unauthorized();
LOW severity: No precision scaling or minimum received amount check when subtracting `relayerFeeAmount` can revert due to underflow or return less tokens to user than specified. **Description:** `PorticoFinish::payOut` L376 attempts to subtract the `relayerFeeAmount` from the final post-bridge and post-swap token balance:

There is [no precision scaling](https://dacian.me/precision-loss-errors#heading-no-precision-scaling) to ensure that `PorticoFinish`'s token contract...

Vulnerable code:
finalUserAmount = finalToken.balanceOf(address(this)) - relayerFeeAmount;
LOW severity: Use low level `call()` to prevent gas griefing attacks when returned data not required. **Description:** Using `call()` when the returned data is not required unnecessarily exposes to gas griefing attacks from huge returned data payload. For example:

Is the same as writing:

In both cases the returned data will be copied into memory exposing the contract to gas griefing attacks,...

Vulnerable code:
(bool sentToUser, ) = recipient.call{ value: finalUserAmount }("");
require(sentToUser, "Failed to send Ether");

(bool sentToUser, bytes memory data) = recipient.call{ value: finalUserAmount }("");
require(sentToUser, "Failed to send Ether");

bool sent;
assembly {
    sent := call(gas(), recipient, finalUserAmount, 0, 0, 0, 0)
}
if (!sent) revert Unauthorized();
LOW severity: Remove unused code. **Description:**

**Wormhole:**
Fixed in commit 6208dd1.

**Cyfrin:** Verified.

\clearpage

Vulnerable code:
File: PorticoStructs.sol L67-79:
  //16 + 32 + 24 + 24 + 16 + 16 + 8 + 8 == 144
  struct packedData {
    uint16 recipientChain;
    uint32 bridgeNonce;
    uint24 startFee;
    uint24 endFee;
    int16 slipStart;
    int16 slipEnd;
    bool wrap;
    bool unwrap;
  }
HIGH severity: Attacker can abuse `RewardsDistributor::triggerRoot` to block reward claims and unpause a paused state. **Description:** Consider the code of [`RewardsDistributor::triggerRoot`](https://github.com/SolidlyV3/v3-rewards/blob/6dfb435392ffa64652c8f88c98698756ca80cf28/contracts/RewardsDistributor.sol#L511-L516):

This function:
* can be called by anyone
* if it succeeds, sets `root.value` to...

Vulnerable code:
function triggerRoot() external {
        bytes32 rootCandidateAValue = rootCandidateA.value;
        if (rootCandidateAValue != rootCandidateB.value || rootCandidateAValue == bytes32(0)) revert RootCandidatesInvalid();
        root = Root({value: rootCandidateAValue, lastUpdatedAt: block.timestamp});
        emit RootChanged(msg.sender, rootCandidateAValue);
    }
HIGH severity: `RewardsDistributor` doesn't correctly handle deposits of fee-on-transfer incentive tokens. **Description:** `the kenneth` stated in telegram that Fee-On-Transfer tokens are fine to use as incentive tokens with `RewardsDistributor`, however when receiving Fee-On-Transfer tokens and storing the reward amount the accounting does not account for the fee deducted from the transfer amount...

Vulnerable code:
function _depositLPIncentive(
    StoredReward memory reward,
    uint256 amount,
    uint256 periodReceived
) private {
    IERC20(reward.token).safeTransferFrom(
        msg.sender,
        address(this),
        amount
    );

    // @audit stored `amount` here will be incorrect since it doesn't account for
    // the actual amount received after the transfer fee was deducted in-transit
    _storeReward(periodReceived, reward, amount);
}
HIGH severity: Use low level `call()` to prevent gas griefing attacks when returned data not required. **Description:** Using `call()` when the returned data is not required unnecessarily exposes to gas griefing attacks from huge returned data payload. For [example](https://github.com/SolidlyV3/v3-rewards/blob/6dfb435392ffa64652c8f88c98698756ca80cf28/contracts/RewardsDistributor.sol#L563-L564):

Is.....

Vulnerable code:
(bool sent, ) = _to.call{value: _amount}("");
require(sent);

(bool sent, bytes memory data) = _to.call{value: _amount}("");
require(sent);

bool sent;
assembly {
    sent := call(gas(), _to, _amount, 0, 0, 0, 0)
}
if (!sent) revert FailedToSendEther();
HIGH severity: Cache array length outside of loop. **Description:** Cache array length outside of loop:

**Solidly:**
Fixed in commit [6481747](https://github.com/SolidlyV3/v3-rewards/commit/6481747737b98c8650a36f87b1aeace815505ba9).

**Cyfrin:**
Verified.

Vulnerable code:
File: contracts/RewardsDistributor.sol
// @audit use `numLeaves` from L263 instead of `earners.length`
265:         for (uint256 i; i < earners.length; ) {
HIGH severity: Don't initialize variables with default value. **Description:** Don't initialize variables with default value:

**Solidly:**
Fixed in commit [6481747](https://github.com/SolidlyV3/v3-rewards/commit/6481747737b98c8650a36f87b1aeace815505ba9) for `RewardsDistributor`; v3-core is already deployed and not upgradeable.

**Cyfrin:**
Verified.

Vulnerable code:
File: contracts/RewardsDistributor.sol

184:         for (uint256 i = 0; i < numClaims; ) {

File: libraries/TickMath.sol

67:         uint256 msb = 0;
HIGH severity: `SwellLib.BOT` can delete active validators when bot methods are paused. **Description:** Almost all of the functions callable by `SwellLib.BOT` contain the following check to prevent bot functions from working when bot methods are paused:

The one exception is [`NodeOperatorRegistry::deleteActiveValidators`](https://github.com/SwellNetwork/v3-contracts-lst/tree/a95ea794...

Vulnerable code:
if (AccessControlManager.botMethodsPaused()) {
  revert SwellLib.BotMethodsPaused();
}

bool isBot = AccessControlManager.hasRole(SwellLib.BOT, msg.sender);

// prevent bot from calling this function when bot methods are paused
if(isBot && AccessControlManager.botMethodsPaused()) {
  revert SwellLib.BotMethodsPaused();
}

// function only callable by admin & bot
if (!AccessControlManager.hasRole(SwellLib.PLATFORM_ADMIN, msg.sender) && !isBot) {
  revert OnlyPlatformAdminOrBotCanDeleteActiveValidators();
}
HIGH severity: `SwellLib.BOT` can subtly rug-pull withdrawals by setting `_processedRate = 0` when calling `swEXIT::processWithdrawals`. **Description:** When users create a withdrawal request, their `swETH` is [burned](https://github.com/SwellNetwork/v3-contracts-lst/blob/a95ea7942ba895ae84845ab7fec1163d667bee38/contracts/implementations/swEXIT.sol#L202-L205) then the current exchange rate `rateWhenCreated` is...

Vulnerable code:
uint256 rateWhenCreated = AccessControlManager.swETH().swETHToETHRate();

function processWithdrawals(
  uint256 _lastTokenIdToProcess,
  uint256 _processedRate
) external override checkRole(SwellLib.BOT) {

uint256 finalRate = _processedRate > rateWhenCreated
  ? rateWhenCreated
  : _processedRate;
HIGH severity: Precision loss in `swETH::reprice` from unnecessary division before multiplication. **Description:** `swETH::reprice` [L281-286](https://github.com/SwellNetwork/v3-contracts-lst/tree/a95ea7942ba895ae84845ab7fec1163d667bee38/contracts/implementations/swETH.sol#L281-L286) performs unnecessary [division before...

Vulnerable code:
UD60x18 nodeOperatorRewardPortion = wrap(nodeOperatorRewardPercentage)
  .div(wrap(rewardPercentageTotal));

nodeOperatorRewards = nodeOperatorRewardPortion
  .mul(rewardsInSwETH) // @audit mult after division
  .unwrap();

nodeOperatorRewards = wrap(nodeOperatorRewardPercentage)
  .mul(rewardsInSwETH)
  .div(wrap(rewardPercentageTotal))
  .unwrap();

uint256 operatorsRewardShare = wrap(operatorActiveValidators)
  .div(totalActiveValidators)
  .mul(wrap(nodeOperatorRewards)) // @audit mult after division
  .unwrap();