Farseen0/scar-pairs huggingface dataset:
HIGH severity: Use != 0 instead of > 0 for unsigned integer comparison. **Protocol:** Fixed in commit [4679de1](https://github.com/SwapExchangeio/Contracts/commit/4679de1c3f8adfc4122698fc82a529322ba4b5ed).

**Cyfrin:** Verified.

Vulnerable code:
File: helpers/FeeData.sol

64:         while (feeTokenKeys.length > 0) {

File: libraries/SwapUtils.sol

96:         else if (sentAmount > 0) {
HIGH severity: Migration of unripe LP from BEAN:3CRV to BEAN:ETH does not account for recapitalization accounting error. **Description:** The global [`AppStorage::recapitalized`](https://github.com/BeanstalkFarms/Beanstalk/blob/12c608a22535e3a1fe379db1153185fe43851ea7/protocol/contracts/beanstalk/AppStorage.sol#L485) state refers to the dollar amount recapitalized when Fertilizer was bought with USDC and paired with.....

Vulnerable code:
function addMigratedUnderlying(address unripeToken, uint256 amount) external payable nonReentrant {
        LibDiamond.enforceIsContractOwner();
        IERC20(s.u[unripeToken].underlyingToken).safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );
        LibUnripe.incrementUnderlying(unripeToken, amount);

+       uint256 recapitalized = amount.mul(LibEthUsdOracle.getEthUsdPrice()).div(1e18);
+       require(recapitalized != 0, "UnripeFacet: cannot calculate recapitalized");
+       s.recapitalized = s.recapitalized.add(recapitalized);
    }
HIGH severity: Typo in comment within `LibSilo::_mow`. The following [typo](https://github.com/BeanstalkFarms/Beanstalk/blob/12c608a22535e3a1fe379db1153185fe43851ea7/protocol/contracts/libraries/Silo/LibSilo.sol#L351-L352) in `LibSilo::_mow` should be corrected:

**Beanstalk Farms:** Fixed in commit...

Vulnerable code:
- //sop stuff only needs to be updated once per season
- //if it started raininga nd it's still raining, or there was a sop
+ // sop stuff only needs to be updated once per season
+ // if it started raining and it's still raining, or there was a sop
HIGH severity: `VoteKickPolicy._endVote()` might revert forever due to underflow. **Severity:** High

**Description:** In `onFlag()`, `targetStakeAtRiskWei[target]` might be less than the total rewards for the flagger/reviewers due to rounding.

- Let's assume `flaggerRewardWei + flagReviewerCount * flagReviewerRewardWei = 100, StreamrConfig.slashingFraction = 0.03e18(3%),...

Vulnerable code:
File: contracts\OperatorTokenomics\StreamrConfig.sol
22:     /**
23:      * Minimum amount to pay reviewers+flagger
24:      * That is: minimumStakeWei >= (flaggerRewardWei + flagReviewerCount * flagReviewerRewardWei) / slashingFraction
25:      */
26:     function minimumStakeWei() public view returns (uint) {
27:         return (flaggerRewardWei + flagReviewerCount * flagReviewerRewardWei) * 1 ether / slashingFraction;
28:     }
HIGH severity: Possible overflow in `_payOutFirstInQueue`. **Severity:** High

**Description:** In `_payOutFirstInQueue()`, possible revert during `operatorTokenToDataInverse()`.

If a delegator calls `undelegate()` with `type(uint256).max`, `operatorTokenToDataInverse()` will revert due to uint overflow and the queue logic will be broken...

Vulnerable code:
uint amountOperatorTokens = moduleCall(address(exchangeRatePolicy), abi.encodeWithSelector(exchangeRatePolicy.operatorTokenToDataInverse.selector, amountDataWei));

function operatorTokenToDataInverse(uint dataWei) external view returns (uint operatorTokenWei) {
       return dataWei * this.totalSupply() / valueWithoutEarnings();
   }
HIGH severity: Wrong validation in `DefaultUndelegationPolicy.onUndelegate()`. **Severity:** High

**Description:** In `onUndelegate()`, it checks if the operator owner still holds at least `minimumSelfDelegationFraction` of total supply.

But `amount` means the DATA token amount and `balanceOf(owner)` indicates the `Operator` token balance and it's impossible to compare...

Vulnerable code:
function onUndelegate(address delegator, uint amount) external {
       // limitation only applies to the operator, others can always undelegate
       if (delegator != owner) { return; }

       uint actualAmount = amount < balanceOf(owner) ? amount : balanceOf(owner); //@audit amount:DATA, balanceOf:Operator
       uint balanceAfter = balanceOf(owner) - actualAmount;
       uint totalSupplyAfter = totalSupply() - actualAmount;
       require(1 ether * balanceAfter >= totalSupplyAfter * streamrConfig.minimumSelfDelegationFraction(), "error_selfDelegationTooLow");
   }
HIGH severity: Malicious target can make `_endVote()` revert forever by forceUnstaking/staking again. **Severity:** High

**Description:** In `_endVote()`, we update `forfeitedStakeWei` or `lockedStakeWei[target]` according to the `target`'s staking status.

We consider the target is still active if he has a positive staking amount. But we don't know if he has unstaked and staked again, so the...

Vulnerable code:
File: contracts\OperatorTokenomics\SponsorshipPolicies\VoteKickPolicy.sol
179:     function _endVote(address target) internal {
180:         address flagger = flaggerAddress[target];
181:         bool flaggerIsGone = stakedWei[flagger] == 0;
182:         bool targetIsGone = stakedWei[target] == 0;
183:         uint reviewerCount = reviewers[target].length;
184:
185:         // release stake locks before vote resolution so that slashings and kickings during resolution aren't affected
186:         // if either the flagger or the target has forceUnstaked or been kicked, the lockedStakeWei was moved to forfeitedStakeWei
187:         if (flaggerIsGone) {
188:             forfeitedStakeWei -= flagStakeWei[target];
189:         } else {
190:             lockedStakeWei[flagger] -= flagStakeWei[target];
191:         }
192:         if (targetIsGone) {
193:             forfeitedStakeWei -= targetStakeAtRiskWei[target];
194:         } else {
195:             lockedStakeWei[target] -= targetStakeAtRiskWei[target]; //@audit revert after forceUnstake() => stake() again
196:         }
MEDIUM severity: In `VoteKickPolicy.onFlag()`, `targetStakeAtRiskWei[target]` might be greater than `stakedWei[target]` and `_endVote()` would revert.. **Severity:** Medium

**Description:** `targetStakeAtRiskWei[target]` might be greater than `stakedWei[target]` in `onFlag()`.

For example,
- At the first time, `streamrConfig.minimumStakeWei() = 100` and an operator(=target) has staked 100.
- `streamrConfig.minimumStakeWei()` was increased to...

Vulnerable code:
targetStakeAtRiskWei[target] = max(stakedWei[target], streamrConfig.minimumStakeWei()) * streamrConfig.slashingFraction() / 1 ether;
MEDIUM severity: Possible front running of `flag()`. **Severity:** Medium

**Description:** The `target` might call `unstake()/forceUnstake()` before a flagger calls `flag()` to avoid a possible fund loss. Also, there would be no slash during the unstaking for `target` when it meets the `penaltyPeriodSeconds` requirement.

**Impact:** A malicious...

Vulnerable code:
File: contracts\OperatorTokenomics\SponsorshipPolicies\VoteKickPolicy.sol
65:     function onFlag(address target, address flagger) external {
66:         require(flagger != target, "error_cannotFlagSelf");
67:         require(voteStartTimestamp[target] == 0 && block.timestamp > protectionEndTimestamp[target], "error_cannotFlagAgain"); // solhint-disable-line not-rely-on-time
68:         require(stakedWei[flagger] >= minimumStakeOf(flagger), "error_notEnoughStake");
69:         require(stakedWei[target] > 0, "error_flagTargetNotStaked"); //@audit possible front run
70:
MEDIUM severity: In `Operator._transfer()`, `onDelegate()` should be called after updating the token balances. **Severity:** Medium

**Description:** In `_transfer()`, `onDelegate()` is called to validate the owner's `minimumSelfDelegationFraction` requirement.

But `onDelegate()` is called before updating the token balances and the below scenario would be possible.

- The operator owner has 100...

Vulnerable code:
File: contracts\OperatorTokenomics\Operator.sol
324:         // transfer creates a new delegator: check if the delegation policy allows this "delegation"
325:         if (balanceOf(to) == 0) {
326:             if (address(delegationPolicy) != address(0)) {
327:                 moduleCall(address(delegationPolicy), abi.encodeWithSelector(delegationPolicy.onDelegate.selector, to)); //@audit
should be called after _transfer()
328:             }
329:         }
330:
331:         super._transfer(from, to, amount);
332:
MEDIUM severity: `onTokenTransfer` does not validate if the call is from the DATA token contract. **Severity:** Medium

**Description:** `SponsorshipFactory::onTokenTransfer` and `OperatorFactory::onTokenTransfer` are used to handle the token transfer and contract deployment in a single transaction. But there is no validation that the call is from the DATA token contract and anyone can call...

Vulnerable code:
if (msg.sender != address(token)) {
           revert AccessDeniedDATATokenOnly();
       }
HIGH severity: Unsafe use of `transfer()/transferFrom()` with `IER`. Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s `transfer()` and `transferFrom()` functions on L1 do not return booleans as the specification requires, and instead have no return value. Consider...

Vulnerable code:
File: contracts\OperatorTokenomics\Operator.sol
264:         token.transferFrom(_msgSender(), address(this), amountWei);
442:         token.transfer(msgSender, rewardDataWei);

File: contracts\OperatorTokenomics\Sponsorship.sol
187:         token.transferFrom(_msgSender(), address(this), amountWei);
215:         token.transferFrom(_msgSender(), address(this), amountWei);
261:         token.transfer(streamrConfig.protocolFeeBeneficiary(), slashedWei);
273:         token.transfer(operator, payoutWei);

File: contracts\OperatorTokenomics\OperatorPolicies\QueueModule.sol
86:           token.transfer(delegator, amountDataWei);

File: contracts\OperatorTokenomics\OperatorPolicies\StakeModule.sol
128:         token.transfer(streamrConfig.protocolFeeBeneficiary(), protocolFee);
HIGH severity: Variables need not be initialized to zero. The default value for variables is zero, so initializing them to zero is redundant.

**Client:** Fixed in commit [c847fab](https://github.com/streamr-dev/network-contracts/commit/c847fab3e57f1f2dc3aa26e8cb79d44c8ed86f5e).

**Cyfrin:** Verified.

Vulnerable code:
File: contracts\OperatorTokenomics\SponsorshipPolicies\DefaultLeavePolicy.sol
10:           uint public penaltyPeriodSeconds = 0;

File: contracts\OperatorTokenomics\SponsorshipPolicies\VoteKickPolicy.sol
100:         uint sameSponsorshipPeerCount = 0;
226:         uint rewardsWei = 0;
MEDIUM severity: A removal signature might be applied to the wrong `fid`.. **Severity:** Medium

**Description:** A remove signature is used to remove a key from `fidOwner` using `KeyRegistry.removeFor()`. And the signature is verified in `_verifyRemoveSig()`.

But the signature doesn't specify a `fid` to remove and the below scenario would be possible.

- Alice is an...

Vulnerable code:
function _verifyRemoveSig(address fidOwner, bytes memory key, uint256 deadline, bytes memory sig) internal {
        _verifySig(
            _hashTypedDataV4(
                keccak256(abi.encode(REMOVE_TYPEHASH, fidOwner, keccak256(key), _useNonce(fidOwner), deadline))
            ),
            fidOwner,
            deadline,
            sig
        );
    }
LOW severity: Inconsistent validation of `vaultAddr`. In `KeyManager.setVault()` and `StorageRegistry.setVault()`, there is a validation for address(0) but we don't check in the constructors.

**Client:**
After internal discussion, we’ve decided to remove payments from the `KeyGateway` altogether and rely on per-fid limits in the `KeyRegistry` for...

Vulnerable code:
File: audit-farcaster\src\KeyManager.sol
123:         vault = _initialVault;
124:         emit SetVault(address(0), _initialVault);
...
211:     function setVault(address vaultAddr) external onlyOwner {
212:         if (vaultAddr == address(0)) revert InvalidAddress();
213:         emit SetVault(vault, vaultAddr);
214:         vault = vaultAddr;
215:     }
216:
LOW severity: Lack of validations for some admin functions. In `KeyManager.setUsdFee()` and `StorageRegistry.setPrice()`, there are no upper limits.

While the protocol owner is regarded as a trusted party, it's still kind of an inconsistent implementation because there are min/max limits for `fixedEthUsdPrice` in...

Vulnerable code:
File: audit-farcaster\src\KeyManager.sol
203:     function setUsdFee(uint256 _usdFee) external onlyOwner {
204:         emit SetUsdFee(usdFee, _usdFee);
205:         usdFee = _usdFee;
206:     }

File: audit-farcaster\src\StorageRegistry.sol
716:     function setPrice(uint256 usdPrice) external onlyOwner {
717:         emit SetPrice(usdUnitPrice, usdPrice);
718:         usdUnitPrice = usdPrice;
719:     }
HIGH severity: `TokenSaleProposal::buy` implicitly assumes that buy token has 18 decimals resulting in a potential total loss scenario for Dao Pool. **Description:** `TokenSaleProposalBuy::buy` is called by users looking to buy the DAO token using a pre-approved token. The exchange rate for this sale is pre-assigned for the specific tier. This function internally calls `TokenSaleProposalBuy::_purchaseWithCommission` to transfer funds from the...

Vulnerable code:
function _sendFunds(address token, address to, uint256 amount) internal {
        if (token == ETHEREUM_ADDRESS) {
            (bool success, ) = to.call{value: amount}("");
            require(success, "TSP: failed to transfer ether");
        } else {
  >>          IERC20(token).safeTransferFrom(msg.sender, to, amount.from18(token.decimals())); //@audit -> amount is assumed to be 18 decimals
        }
    }

purchaseToken3 = await ERC20Mock.new("PurchaseMockedToken3", "PMT3", 6);

{
          metadata: {
            name: "tier 9",
            description: "the ninth tier",
          },
          totalTokenProvided: wei(1000),
          saleStartTime: timeNow.toString(),
          saleEndTime: (timeNow + 10000).toString(),
          claimLockDuration: "0",
          saleTokenAddress: saleToken.address,
          purchaseTokenAddresses: [purchaseToken3.address],
          exchangeRates: [PRECISION.times(1).toFixed()],
          minAllocationPerUser: 0,
          maxAllocationPerUser: 0,
          vestingSettings: {
            vestingPercentage: "0",
            vestingDuration: "0",
            cliffPeriod: "0",
            unlockStep: "0",
          },
          participationDetails: [],
        },
HIGH severity: Under-funded eth distribution proposals can be created causing claiming rewards to revert. **Description:** It is possible to create under-funded eth distribution proposals as `DistributionProposal::execute()` [L62-63](https://github.com/dexe-network/DeXe-Protocol/tree/f2fe12eeac0c4c63ac39670912640dc91d94bda5/contracts/gov/proposals/DistributionProposal.sol#L62-L63) doesn't check...

Vulnerable code:
it("under-funded eth distribution proposals prevents claiming rewards", async () => {
        // use GovPool to create a proposal with 10 wei reward
        await govPool.createProposal(
          "example.com",
          [[dp.address, wei("10"), getBytesDistributionProposal(1, ETHER_ADDR, wei("10"))]],
          [],
          { from: SECOND }
        );

        // Under-fund the proposal by calling DistributionProposal::execute() with:
        // 1) token     = ether
        // 2) amount    = X
        // 3) msg.value = Y, where Y < X
        //
        // This creates an under-funded proposal breaking the subsequent claim()
        await impersonate(govPool.address);
        await dp.execute(1, ETHER_ADDR, wei("10"), { value: wei(1), from: govPool.address });

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

        // attempting to claim the reward fails as the proposal is under-funded
        await truffleAssert.reverts(dp.claim(SECOND, [1]), "Gov: failed to send eth");
      });
HIGH severity: A malicious DAO Pool can create a token sale tier without actually transferring any DAO tokens. **Description:** `TokenSaleProposalCreate::createTier` is called by a DAO Pool owner to create a new token sale tier. A fundamental prerequisite for creating a tier is that the DAO Pool owner must transfer the `totalTokenProvided` amount of DAO tokens to the `TokenSaleProposal`.

Current...

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

       ....
         /// @dev return value is not checked intentionally
  >      tierInitParams.saleTokenAddress.call(
            abi.encodeWithSelector(
                IERC20.transferFrom.selector,
                msg.sender,
                address(this),
                totalTokenProvided
            )
        );  //@audit -> no check if the contract balance has increased proportional to the totalTokenProvided
   }

// transfer sale tokens to TokenSaleProposal and validate the transfer
        IERC20 saleToken = IERC20(_tierInitParams.saleTokenAddress);

        // record balance before transfer in 18 decimals
        uint256 balanceBefore18 = saleToken.balanceOf(address(this)).to18(_tierInitParams.saleTokenAddress);

        // perform the transfer
        saleToken.safeTransferFrom(
            msg.sender,
            address(this),
            _tierInitParams.totalTokenProvided.from18Safe(_tierInitParams.saleTokenAddress)
        );

        // record balance after the transfer in 18 decimals
        uint256 balanceAfter18 = saleToken.balanceOf(address(this)).to18(_tierInitParams.saleTokenAddress);

        // verify that the transfer has actually occured to protect users from malicious
        // sale tokens that don't actually send the tokens for the token sale
        require(balanceAfter18 - balanceBefore18 == _tierInitParams.totalTokenProvided,
                "TSP: token sale proposal creation received incorrect amount of tokens"
        );
HIGH severity: Attacker can use delegation to bypass voting restriction to vote on proposals they are restricted from voting on. **Description:** Attacker can use delegation to bypass voting restriction to vote on proposals they are restricted from voting on.

**Impact:** Attacker can vote on proposals they are restricted from voting on.

**Proof of Concept:** Add PoC to `GovPool.test.js` under section `describe("vote()",...

Vulnerable code:
it("audit bypass user restriction on voting via delegation", async () => {
        let votingPower = wei("100000000000000000000");
        let proposalId  = 1;

        // create a proposal where SECOND is restricted from voting
        await govPool.createProposal(
          "example.com",
          [[govPool.address, 0, getBytesUndelegateTreasury(SECOND, 1, [])]],
          []
        );

        // if SECOND tries to vote directly this fails
        await truffleAssert.reverts(
          govPool.vote(proposalId, true, votingPower, [], { from: SECOND }),
          "Gov: user restricted from voting in this proposal"
        );

        // SECOND has another address SLAVE which they control
        let SLAVE = await accounts(10);

        // SECOND delegates their voting power to SLAVE
        await govPool.delegate(SLAVE, votingPower, [], { from: SECOND });

        // SLAVE votes on the proposal; votes "0" as SLAVE has no
        // personal voting power, only the delegated power from SECOND
        await govPool.vote(proposalId, true, "0", [], { from: SLAVE });

        // verify SLAVE's voting
        assert.equal(
          (await govPool.getUserVotes(proposalId, SLAVE, VoteType.PersonalVote)).totalRawVoted,
          "0" // personal votes remain the same
        );
        assert.equal(
          (await govPool.getUserVotes(proposalId, SLAVE, VoteType.MicropoolVote)).totalRawVoted,
          votingPower // delegated votes from SECOND now included
        );
        assert.equal(
          (await govPool.getTotalVotes(proposalId, SLAVE, VoteType.PersonalVote))[0].toFixed(),
          votingPower // delegated votes from SECOND now included
        );

        // SECOND was able to abuse delegation to vote on a proposal they were
        // restricted from voting on.
      });
HIGH severity: `DistributionProposal` 'for' voter rewards diluted by 'against' voters and missing rewards permanently stuck in `DistributionProposal` contract. **Description:** `DistributionProposal` [only pays rewards to users who voted "for" the proposal](https://github.com/dexe-network/DeXe-Protocol/tree/f2fe12eeac0c4c63ac39670912640dc91d94bda5/contracts/gov/proposals/DistributionProposal.sol#L115-L117), not "against" it.

But when [calculating the...

Vulnerable code:
it("audit for voter rewards diluted by against voter, remaining rewards permanently stuck in DistributionProposal contract", async () => {
        let rewardAmount = wei("10");
        let halfRewardAmount = wei("5");

        // mint reward tokens to sending address
        await token.mint(govPool.address, rewardAmount);

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

        // only 1 vote "for" by SECOND who should get the entire 10 wei reward
        await govPool.vote(1, true, 0, [1], { from: SECOND });
        // but THIRD votes "against", these votes are excluded from getting the reward
        await govPool.vote(1, false, 0, [6], { from: THIRD });

        // fully fund the proposal using erc20 token
        await impersonate(govPool.address);
        await token.approve(dp.address, rewardAmount, { from: govPool.address });
        await dp.execute(1, token.address, rewardAmount, { from: govPool.address });

        // verify SECOND has received no reward
        assert.equal((await token.balanceOf(SECOND)).toFixed(), "0");

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

        // SECOND only receives half the total reward as the reward is diluted
        // by the "against" vote, even though that vote is excluded from the reward.
        // as a consequence only half of the reward is paid out to the "for" voter when
        // they should get 100% of the reward since they were the only "for" voter and
        // only "for" votes qualify for rewards
        assert.equal((await token.balanceOf(SECOND)).toFixed(), halfRewardAmount);

        // the remaining half of the reward is permanently stuck
        // inside the DistributionProposal contract!
        assert.equal((await token.balanceOf(dp.address)).toFixed(), halfRewardAmount);
      });