xanoutas/solidity-security-findings huggingface dataset:
It is possible for a Role.PriceFeed to both approve and disapprove the same proposal
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L210-L242
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177-L207


# Vulnerability details

## Impact
It is possible for a Role.PriceFeed to both approve and disapprove the same proposal unethically.

## Proof of Concept
According to the contest page, ```Role.PriceFeed``` is not a trusted role.
In the ```disapproveUSDPrice``` function:
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L210-L242
```solidity
       function disapproveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        usdUpdateProposal.disapprovalsCount++;
        usdUpdateProposal.disapprovals[msg.sender] = _usdProposalId;

        emit DisapprovedUSDPrice(msg.sender);

        if (usdUpdateProposal.disapprovalsCount >= DISAPPROVE_THRESHOLD) {
            delete usdUpdateProposal;

            emit RemovedUSDProposal();
        }
    }

```
Here it is observed that Role.PriceFeed is not allowed to call this function after calling the ```approveUSDPrice``` function due to ```ProposalAlreadyApprovedError``` revert. 
This eventually increases the ```disapprovalsCount```.

However in the ```approveUSDPrice``` function:

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177-L207
   
```solidity
    function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
        usdUpdateProposal.approvalsCount++;

        if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) {
Price Feeds can disapprove and approve the same proposal
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L177-L242


# Vulnerability details

## Summary

The protocol utilizes five price feeds to establish consensus on the USD price of an asset. To determine the current reported price, these feeds can either disapprove or approve a proposed price. Upon reaching a predefined threshold, a disapproved price is removed, whereas an approved price is accepted. However, due to a missing validation check in the `approveUSDPrice` function, a single price feed can concurrently disapprove and approve the same price proposal.

## Vulnerability Detail

there are 5 price Feed roles in the protocol that push the price of tokens supported by the protocol in USD, they approve and disapprove using functions `approveUSDPrice` and `disapproveUSDPrice`.

- approveUSDPrice
```solidity=177
function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        ...
```

- disapproveUSDPrice
```solidity=210
function disapproveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

```

as we can see in the `disapproveUSDPrice` it is checked that the msgSender did not previously approve or disapprove the price. but this check is lacking in the `approveUSDPrice`. allowing a priceFeed to both approve and disapprove a usd price.

## Impact

a price Feed role is allowed to approve and disapprove a price , leading to wrong threshold calculation.

## Tool used

Manual Review

## Recommendation

```diff=177
function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.Pr
Double Approval Vulnerability in Approve and Disapprove Update Mechanism
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L191-L197
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L224-L230


# Vulnerability details

## Impact

The vulnerability in the price update mechanism allows for disapproving and reapprove of the same price. The current implementation allows a user to both approve and disapprove the same price for a proposal, leading to double approvals.

## **Proof of Concept:**

The code snippet reveals an inconsistency in how approvals and disapprovals are handled:

```solidity
// disapproveUSDPrice function
 function disapproveUSDPrice(
        uint256 _price
    )
      .................................
 if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
   @ audit >>       if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
 @ audit >>       if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();
}
```



```solidity
// approveUSDPrice function (missing check)
// ... no check for disapprovals before approval


  function approveUSDPrice(
        uint256 _price
    )
       ............................................
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();
```

The `disapproveUSDPrice` function prevents a user from disapproving a proposal they already approved of.
```solidity
       if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
      if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
           revert ProposalAlreadyDisapprovedError();
```
 However, there's no corresponding check in the `approveUSDPrice` function to prevent a user from approving a price they previously disapproved.
```solidity
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
 @ audit >>    // This check is missing after the above  if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
// revert ProposalAlreadyDisapprovedError();

```

**Impact:**

This missing check allows a user to:

1. Disapprove a price, potentially influencing the voting process.
2. Later, approve the same price.


## Tools Used

Manual Code analysis

## **Recommended Mitigation Steps:**

- Implement a check for disapprovals in
`Pricefeed roles` who disapproved USD price can still approve the same USD price, breaking protocol invariant
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L225-L226
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L191-L197


# Vulnerability details

## Impact
`LockManager` allows users to lock tokens in return for `rewards`. They can unlock their tokens once their respective `unlockTime` has passed.

When users unlock their tokens, `AccountManager::forceHarvest()` is called, which gives users reward based off the `USD` value of their locked tokens.

This `USD` value can be changed at anytime by a `proposer` with `Pricefeed` role via a call to `proposeUSDPrice()`.

If enough `Pricefeed` roles approve the new `USD` value, that value will then be updated.

`Pricefeed` roles can also disapprove the proposed `USD` value. If enough disapprove, then the proposal will be rejected.

There is a check in place to ensure that if a `Pricefeed` role has already approved a proposal, then they cannot disapprove it.

However, this invariant can be broken by disapproving first and then proceeding to approve the same proposal.

## Proof of Concept
When an address with `Pricefeed` role disapproves a proposed `USD` value, there is a check to see if they already approved

[LockManager.sol#L225-L226](https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L225-L226)
```javascript
if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
    revert ProposalAlreadyApprovedError();
```

This is to ensure that when a proposal is approved by a `Pricefeed` role, the same address cannot disapprove the same proposal. This is an invariant where the same msg.sender cannot approve and disapprove the same proposal.

However, when approving, there is no check to see if the `Pricefeed` role has already disapproved the proposal.

[#L191-L197](https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L191-L197)
```javascript
    if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
    if (usdUpdateProposal.proposer == msg.sender)
        revert ProposerCannotApproveError();
    if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
        revert ProposalAlreadyApprovedError();
    if (usdUpdateProposal.proposedPrice != _price)
        revert ProposalPriceNotMatchedError();
```

The same `Pricefeed` role can approve a proposal they already disapproved, breaking a protocol invariant.

## Tools Used
Manual Review.

## Recommended Mitigation Steps
Upon approval, revert if the caller had already disapproved the proposal:

```diff
    function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposa
The `approveUSDPrice()` Function Allows a Price Feed Updater to Approve a Proposal After Disapproving It Without Decreasing `disapprovalsCount`
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L177-L197


# Vulnerability details

The `approveUSDPrice()` function in the `LockManager` contract is designed to approve a price update proposal, while the `disapproveUSDPrice()` function is meant to mark disapproval for the proposed price. The vulnerability is, the `approveUSDPrice()` function allows price feed updaters to mark their approval even after their disapproval to the same proposal without reducing the `disapprovalsCount`.

## Impact
This vulnerability allows price feed updaters to double-cast their votes. This means a price feed updater can disapprove a proposal and then approve it without their disapproval being properly accounted for, potentially manipulating the proposal approval process.

## Proof of Concept
The vulnerability lies in the code below. The `approveUSDPrice()` function checks if the user has already approved in line 194, but it fails to check whether the user has already marked their disapproval.

```solidity
    function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
194: @> if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();
```
https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L177C1-L197C51

The `disapproveUSDPrice()` function successfully implements this check, preventing double voting. The checks in the `disapproveUSDPrice()` function are shown below:

```solidity
225:    if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
226:        revert ProposalAlreadyApprovedError();
227:    if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
228:        revert ProposalAlreadyDisapprovedError();
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Include a check to ensure that the user has not disapproved the proposal before allowing them to approve it.

Sample code snippet:

```diff
    function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if
A PriceFeed is able to both approve and disapprove usd price
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L177-L242


# Vulnerability details

## Impact
A PriceFeed is able to both approve and disapprove usd price.

## Proof of Concept
1. Lets say that Role.PriceFeed_1 proposes a new price for given contracts.
2. Role.PriceFeed_2 calls `disapproveUSDPrice` and his `usdUpdateProposal.disapprovals[msg.sender] = _usdProposalId;` are set.
3. Due to a missing check in the `approveUSDPrice` function, Role.PriceFeed_2 can now also call `disapproveUSDPrice`.

This is not intended because in the `disapprove function` there are checks implemented for checking if the PriceFeed has already approved or disapproved. How ever the `approve function` only checks if the PriceFeed has already approved(it doesn't check if he's already disapproved). Which means that the PriceFeed roles can first disapprove and afterwards approve.

```Solidity
function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
        usdUpdateProposal.approvalsCount++;

        if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) {
            _execUSDPriceUpdate();
        }

        emit ApprovedUSDPrice(msg.sender);
    }
```
```Solidity
function disapproveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        usdUpdateProposal.disapprovalsCount++;
        usdUpdateProposal.disapprovals[msg.sender] = _usdProposalId;

        emit DisapprovedUSDPrice(msg.sender);

        if (usdUpdateProposal.disapprovalsCount >= DISAPPROVE_THRESHOLD) {
            delete usdUpdateP
Missing disapproval check in `LockManager.sol::approveUSDPrice` allows simultaneous approval and disapproval of a price proposal
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L177-L207


# Vulnerability details

## Impact
Due to the missing disapproval check, a price feed can both disapprove and subsequently approve a newly proposed price. Price feeds are intended to vote either for approval or disapproval, not both. Hence, this can be considered an unintended functionality.

## Proof of Concept
<details>
<summary>Code</summary>

Place this in `MunchablesTest.sol`.

```javascript
    function test_priceFeedCanVoteBoth() public {
        address priceFeed2 = makeAddr("priceFeed2");
        address tokenUpdated = makeAddr("token");

        address[] memory tokensUpdated = new address[](1);
        tokensUpdated[0] = tokenUpdated;

        deployContracts();

        console.log("--------------- PoC ---------------");

        cs.setRole(Role.PriceFeed_1, address(lm), address(this));
        cs.setRole(Role.PriceFeed_2, address(lm), priceFeed2);

        lm.proposeUSDPrice(1 ether, tokensUpdated);

        vm.startPrank(priceFeed2);
        lm.disapproveUSDPrice(1 ether);
        lm.approveUSDPrice(1 ether);
        vm.stopPrank();
    }
```

</details>

## Tools Used
Manual review.

## Recommended Mitigation Steps
Add a check to see if the price feed has already disapproved the price proposal. If so, revert with a custom error.

```diff
    function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
+       if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
+           revert ProposalAlreadyDisapprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
        usdUpdateProposal.approvalsCount++;

        if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) {
            _execUSDPriceUpdate();
        }

        emit ApprovedUSDPrice(msg.sender);
    }

```


## Assessed type

Other
Missing validation for already disapproved proposal in approveUSDPrice function
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177-L207


# Vulnerability details

## Impact

`approveUSDPrice` is missing the validation for `ProposalAlreadyDisapprovedError()`. This can lead to
unintended USD price voting outcomes, as the `msg.sender`, can make an approved and also a disapproved vote,
essentially lowering the threshold for executing a proposal. There is no way to revoke approvals or disapprovals.

## Proof of Concept

The `disapproveUSDPrice` validates if the caller has already approved the USD price.

```solidity
if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
    revert ProposalAlreadyApprovedError();
```

Because of this validation, the caller can not achieve a state with both an approval and a disapproval if first the
`approveUSDPrice` is called and then `disapproveUSDPrice`, as the `disapproveUSDPrice` will revert, and the
caller will end up only with an approval. However, this state can be achieved if first the `disapproveUSDPrice` is
called and then the `approveUSDPrice`, as the `approveUSDPrice` is missing the validation if the `msg.sender` has
already disapproved the USD price.

## Tools Used

Manual review.

## Recommended Mitigation Steps

Add validation to the `approveUSDPrice` if the `msg.sender` has already disapproved the USD price.

```diff
    function approveUSDPrice(
        uint256 _price
    )
        external
        onlyOneOfRoles(
            [
                Role.PriceFeed_1,
                Role.PriceFeed_2,
                Role.PriceFeed_3,
                Role.PriceFeed_4,
                Role.PriceFeed_5
            ]
        )
    {
        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
        if (usdUpdateProposal.proposer == msg.sender)
            revert ProposerCannotApproveError();
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
+       if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
+           revert ProposalAlreadyDisapprovedError();
        if (usdUpdateProposal.proposedPrice != _price)
            revert ProposalPriceNotMatchedError();

        usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
        usdUpdateProposal.approvalsCount++;
        if (usdUpdateProposal.approvalsCount >= APPROVE_THRESHOLD) {
            _execUSDPriceUpdate();
        }

        emit ApprovedUSDPrice(msg.sender);
    }
```





## Assessed type

Invalid Validation