xanoutas/solidity-security-findings huggingface dataset:
A Price Feed role can disapprove and approve a price on the same proposal due to a missing check
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


# Vulnerability details

## Impact
Due to a missing check on `approveUSDPrice()` a single price feed role can first disapprove a price and then approve it, basically casting two votes instead of one.

## Proof of Concept
When a price feed role wants to disapprove a price there are checks which make sure that they are not the proposer, or they haven't already approved the price:

```
        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();
```

Although some of the above-mentioned validations are not present in the `approveUSDPrice()` function.

A price feed role is able to both disapprove and approve a proposed price, as long as they first disapprove it, and then approve it. 

```
    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++;
```

- A price feed role can "disapprove" a price, which will set the `disapprovals[msg.sender]` to the current id.
- Then due to the non-existing checks if the price feed role who is calling the `approveUSDPrice()` if they've casted a vote for the price disapproval, will be able to also approve the price, effectively casting two votes.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Include the following check in `approveUSDPrice()`:
```
if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
```





## Assessed type

Invalid Validation
An oracle can still approve an USD price after it has already disapproved the price.
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-L200


# Vulnerability details

## Impact
An oracle can approve an USD price even if it has disapproved the price already.

## Proof of Concept
When an oracle approves an USD price, function `approveUSDPrice` only check if the oracle has *approved* the price already, and reverts the tx if it has. But the function does not check if the oracle has *disapproved* the price already. As a result, the oracle can first disapprove the price, and then approve the price again. This may mess up the pricing of tokens.
```solidity
177:    function approveUSDPrice(
178:        uint256 _price
179:    )
180:        external
181:        onlyOneOfRoles(
182:            [
183:                Role.PriceFeed_1,
184:                Role.PriceFeed_2,
185:                Role.PriceFeed_3,
186:                Role.PriceFeed_4,
187:                Role.PriceFeed_5
188:            ]
189:        )
190:    {
191:        if (usdUpdateProposal.proposer == address(0)) revert NoProposalError();
192:        if (usdUpdateProposal.proposer == msg.sender)
193:            revert ProposerCannotApproveError();
194:@>      if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
195:            revert ProposalAlreadyApprovedError();
196:        if (usdUpdateProposal.proposedPrice != _price)
197:            revert ProposalPriceNotMatchedError();
198:
199:        usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
200:        usdUpdateProposal.approvalsCount++;
```
https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L177-L200

## Tools Used
VS Code

## Recommended Mitigation Steps
In function `approveUSDPrice`, check if the oracle has disapproved the USD price, and revert the tx if it has.
```solidity
    function approveUSDPrice(
        uint256 _price
    )
        ...
    {
        ...
        if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
+       if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
+           revert ProposalAlreadyDisapprovedError();
```


## Assessed type

Other
Disapproved Proposal can still be approved
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
Disapproved proposals can still be approved, leading to double voting. The impact of this is medium as it can skew the change of the price of `tokenContracts`, but still found in a permissioned function controlled by `onlyOneOfRoles` with priceFeeds.

## Proof of Concept
In the disapproveUSDPrice function, the initial checks are

```solidity
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 seen at https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L224-L231

We see, that approvals and disapprovals of msg.sender is checked before allowing a disapproval



But we check the same for `approveUSDPrice` function

```solidity
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();
```
as seen at https://github.com/code-423n4/2024-05-munchables/blob/main/src/managers/LockManager.sol#L19-L200

There is no check for disapproval an can cause a feed to vote twice effectively skewing the outcome of the proposal

## Tools Used

Manual Review

## Recommended Mitigation Steps
Add a disapproval check for `approveUSDPrice` function

```solidity
if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
```


## Assessed type

Other
A User can Approve the proposed Price even though he has disapproved before and no decrement is done for disapprovalCounts.
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
A User can Approve the proposed Price even though he has disapproved before and no decrement is done for disapprovalCounts.

## Proof of Concept
In the below code snippet the function is not checking whether the msg.sender has already disapproved.

https://github.com/code-423n4/2024-05-munchables/blob/57dff486c3cd905f21b330c2157fe23da2a4807d/src/managers/LockManager.sol#L210-L243
```solidity
    function approveUSDPrice(uint256 _price) external onlyOneOfRoles([ ....])
    {
        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++;

       ....

    }
```

As a result any user who is already disapproved can approve the Proposal thus making the voting power of other roles irrelevant

## Tools Used
 Manual Review

## Recommended Mitigation Steps
 If contract doesnt want to allow a user who has already disapproved to approve the proposal.
 Function should add  this check
```solidity
        if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
```
Else if , the contract allows a user who has already disapproved to approve the proposal.
Function should the code to decrement the  usdUpdateProposal.disapprovalsCount and set the usdUpdateProposal.disapprovals[msg.sender] to 0;




## Assessed type

Context
A price feed can disapprove and approve an USD price simultaneously
Audit: 2024-05-munchables-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
When dealing with usd price proposals, price feeds ought to have two options:
vote to approve or to disapprove a proposed price.
This is not enforced on its entirety at the LockManager contract, however.
The disapproveUSDPrice function properly enforces that by checking whether the price feed hasn't approved nor disapproved a proposed price before accounting the disapprove vote:
```solidity
function disapproveUSDPrice(
        uint256 _price
    )
    ...
    if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
        if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
            ...
```

The approveUSDPrice doesn't enforce the same checks. It only verifies the price feed hasn't approved the price priorly:
```solidity
function approveUSDPrice(
        uint256 _price
    )
    ...
    if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyApprovedError();
    ...
```
## Impact
The current price approval mechanism is not fair on all price fees.
This issue allows privilege escalation as a price feed is able to vote twice.

Allowing one voter to vote on both approval and disapproval exceeds the intended permissions by creating the possibility of voting ties: the system has 5 price feeds so that ties are impossible, but this privilege escalation allows both for and against positions to have 3 or more votes. This enables an unexpected logic for price approvals - price feeds will have incentives to vote as soon as possible.

## Tools Used
Manual review
## Recommended Mitigation Steps
Add the following check to approveUSDPrice:
```solidity
        if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId)
            revert ProposalAlreadyDisapprovedError();
```


## Assessed type

Other
Missing check that proposal was not disapproved in the `approveUSDPrice()` function.
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


# Vulnerability details

In the `approveUSDPrice()` function, there is no check to prevent the PriceFeed role from double voting, first disapproving and later approving the new price for the token. 

A similar check is present in the `disapproveUSDPrice()` function to prevent double voting by the same actor.

## Impact

The PriceFeed role can disapprove a new price update proposal and later approve it, resulting in double event emission that can corrupt off-chain data.

## Recommended Mitigation Steps

Update the `approveUSDPrice()` function to include a check that ensures a proposal that has been disapproved by a voter cannot be approved by the same voter.

```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();
    
    ...
}
```


## Assessed type

Invalid Validation
Users Can Disapprove and Then Approve Proposals
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-L242


# Vulnerability details

# Vulnerability details

A user with the pricefeed_n role can disapprove a proposal and then approve it again.

# Impact

Voting Manipulation: The ability to change votes can lead to manipulation of the voting process.
Governance Instability: Governance decisions may become unstable if votes can be easily changed.
Increased Contract Complexity: Allowing vote changes adds unnecessary complexity to the contract logic.
## Proof of Concept

There are 5 addresses with the pricefeed_n role. One of these addresses can disapprove a proposal and then approve it again, leading to the issues mentioned.

Tool use
Manual

## Recommended Mitigation Steps
```
function approveUSDPrice(uint256 _price) external onlyOneOfRoles([...]) {
   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
Proposal Approval/Disapproval Inconsistency
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


# Vulnerability details

## Impact

The contract allows a PriceFeed to both approve and disapprove the same USD price proposal, leading to an inconsistent and invalid state where a single entity has conflicting actions on the same proposal.

## Proof of Concept

In `LockManager.sol:177`, the `approveUSDPrice` function does not check if the `msg.sender` has previously disapproved the proposal before allowing them to approve it. The contract already has a `disapprovals` mapping to track disapprovals, but this mapping is not being checked in the `approveUSDPrice` function. This means that a PriceFeed can potentially both approve and disapprove the same proposal, leading to an inconsistent state.

## Tools Used

Manual Review

## Recommended Mitigation Steps

To prevent this inconsistency, the `approveUSDPrice` function should check the `disapprovals` mapping to ensure that the `msg.sender` has not previously disapproved the proposal. If they have, the function should revert to maintain consistency.

```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 (disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError();
    if (usdUpdateProposal.proposedPrice != _price) revert ProposalPriceNotMatchedError();

    usdUpdateProposal.approvals[msg.sender] = _usdProposalId;
    emit ProposalApproved(msg.sender, _price);
// ...
}
```

By checking the `disapprovals` mapping, the function will ensure that a PriceFeed cannot both approve and disapprove the same proposal, maintaining consistency in the contract's state.

## Issue Type

Governance


## Assessed type

Governance
`PreBlocker` is set incorrectly
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/main/canto-main/app/app.go#L823


# Vulnerability details

## Impact


In the current implementation of `app.go`, the `PreBlocker` is incorrectly set incorrectly which affects current functionality.

## Proof of Concept

According to the Cosmos SDK v0.50.x docs:

https://github.com/cosmos/cosmos-sdk/blob/v0.50.6/UPGRADING.md

```
The following must be added to app.go:
```

```
+app.ModuleManager.SetOrderPreBlockers(
+	upgradetypes.ModuleName,
+)

app.ModuleManager.SetOrderBeginBlockers(
-	upgradetypes.ModuleName,
)

+ app.SetPreBlocker(app.PreBlocker)

// ... //

+func (app *SimApp) PreBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) {
+	return app.ModuleManager.PreBlock(ctx, req)
+}

```


However, in the current implementation of `app.go`, `upgradetypes.ModuleName` is not deleted from `SetOrderBeginBlockers()`:

https://github.com/code-423n4/2024-05-canto/blob/main/canto-main/app/app.go#L823
```
upgradetypes.ModuleName
```

That makes the deviation from the spec.

## Tools Used

Manual review.

## Recommended Mitigation Steps

Delete `upgradetypes.ModuleName`





## Assessed type

Other
Unhandled type assertion failure in AutoCliOpts function
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/app/app.go#L1124-L1127


# Vulnerability details

## Impact
AutoCliOpts function continues to execute when type assertion fails

## Proof of Concept


The AutoCliOpts function does not handle the case where the type assertion fails.

`if moduleWithName, ok := m.(module.HasName); ok {
			moduleName := moduleWithName.Name()
			if appModule, ok := moduleWithName.(appmodule.AppModule); ok {
				modules[moduleName] = appModule
			}`

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/app/app.go#L1124-L1127

Note that the ok value from the type assertions is not handled when it is false. If the type assertion fails (i.e., ok is false), the code silently continues execution without handling the error. Both moduleName and modules[moduleName] will not be set.

## Tools Used
Manual review

## Recommended Mitigation Steps
To address this issue, the code should handle the case where the type assertion fails (ok is false).


## Assessed type

Context
sdk.Context is still used instead of context.Context, causing interface mismatch.
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/govshuttle/keeper/keeper.go#L63


# Vulnerability details

## Impact 

sdk.Context is still used instead of context.Context, causing interface mismatch.

## Proof of Concept

According to

https://github.com/cosmos/cosmos-sdk/blob/v0.50.6/UPGRADING.md#modules

> The following modules' Keeper methods now take in a context.Context instead of sdk.Context. Any module that has an interfaces for them (like "expected keepers") will need to update and re-generate mocks if needed:

x/authz
x/bank
x/mint
x/crisis
x/distribution
x/evidence
x/gov
x/slashing
x/upgrade

but many module such as coinswap, x/csr, x/epoch/keepers and x/govshuttle/ (which import x/gov) keeper still use sdk.Context instead of context.Context,

which result in incorrect interface mistmatch, some example code:

```solidity
// GetAuthority returns the x/govshuttle module's authority.
func (k Keeper) GetAuthority() string {
	return k.authority
}

func (k Keeper) Logger(ctx sdk.Context) log.Logger {
	return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}

// retrieve the port address from state
func (k Keeper) GetPort(ctx sdk.Context) (common.Address, bool) {
	store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
	prefixStore := prefix.NewStore(store, types.PortKey)
	bz := prefixStore.Get(types.PortKey)
	// if not found return false
	if len(bz) == 0 {
		return common.Address{}, false
	}
	return common.BytesToAddress(bz), true
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

change from sdk.Context to context.Context


## Assessed type

Other
Govshuttle's RegisterLegacyAminoCodec is Implemented Incorrectly
Audit: 2024-05-canto-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/govshuttle/module.go#L52-L54


# Vulnerability details

The current implementation of the RegisterLegacyAminoCodec function in Govshuttle is incorrect:


```go
func (AppModuleBasic) RegisterCodec(cdc *codec.LegacyAmino) {
	types.RegisterCodec(cdc)
}

func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
	types.RegisterCodec(cdc)
}
```


When calling AppModuleBasic -> RegisterLegacyAminoCodec -> types.RegisterCodec(cdc), it is evident that the function within Govshuttle contains no executable code.

https://github.com/code-423n4/2024-05-canto/blob/d1d51b2293d4689f467b8b1c82bba84f8f7ea008/canto-main/x/govshuttle/types/codec.go#L39-L42

```go
func RegisterCodec(cdc *codec.LegacyAmino) {
	// this line is used by starport scaffolding # 2
}

```

This oversight means that the cdc.RegisterConcrete calls performed by other modules are not executed for messages sent to the Govshuttle module. Consequently, these types will not be properly decoded when signed with the LegacyAmino codec.

## Impact
Messages to the Govshuttle module will fail when signed with the legacy Amino method

## Proof of Concept

Send message signed with the Legacy Amino method to the GovShuttle module

## Tools Used
Code review

## Recommended Mitigation Steps


```diff
func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
+    cdc.RegisterConcrete(..)
}
```


## Assessed type

Other