xanoutas/solidity-security-findings huggingface dataset:
DOS voting on Outbound Transaction / inbound transaction
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/keeper_cross_chain_tx_vote_outbound_tx.go#L93
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/keeper_utils.go#L74
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_update_observer.go#L97
https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/types/ballot.go#L34


# Vulnerability details

## Impact
Inbound and Outbound transaction will DOS under special scenario

## Proof of Concept
1. Let's say for Observer Mapper om1, the Observer list is A,B,C
2. `VoteOnObservedOutboundTx` function is called to Vote which will later help process the outbound transaction (once threshold crosses)
3. Since this is first vote so ballot is created:

```
func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.MsgVoteOnObservedOutboundTx) (*types.MsgVoteOnObservedOutboundTxResponse, error) {

...
ballot, isNew, err := k.zetaObserverKeeper.FindBallot(ctx, ballotIndex, observationChain, observationType)
	...
```

```
func (k Keeper) FindBallot(ctx sdk.Context, index string, chain *common.Chain, observationType types.ObservationType) (ballot types.Ballot, isNew bool, err error) {
...
ballot = types.Ballot{
			Index:                "",
			BallotIdentifier:     index,
			VoterList:            observerMapper.ObserverList,
			Votes:                types.CreateVotes(len(observerMapper.ObserverList)),
			ObservationType:      observationType,
			BallotThreshold:      obsParams.BallotThreshold,
			BallotStatus:         types.BallotStatus_BallotInProgress,
			BallotCreationHeight: ctx.BlockHeight(),
		}
...
```

4. Observe that ballot is created with VoterList as `observerMapper.ObserverList` which in our case is A,B,C

5. Finally vote is casted by msg.Creator which in this case is `A`

```
ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.ConvertReceiveStatusToVoteType(msg.Status))
	
```

6. Now lets say for some reason, Observer `B` need to be updated using `UpdateObserver` function to `D`

```
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_update_observer.go#L97

func UpdateObserverList(list []string, oldObserverAddresss, newObserverAddress string) {
	for i, observer := range list {
		if observer == oldObserverAddresss {
			list[i] = newObserverAddress
		}
	}
```

7. So the new list of Observer is A,D,C

8. Now `VoteOnObservedOutboundTx` function is again called with new Observer `D` being msg.Creator. This again calls `AddVoteToBallot` which internally does below:

```
func (m Ballot) AddVote(address string, vote VoteType) (Ballot, error) {
	if m.HasVoted(address) {
		return m, errors.Wrap(ErrUnableToAddVote, fmt.Sprintf(" Voter : %s | Ballot
Input Validation of minAmountOut
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerPancakeV3.strategy.sol#L142


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
Input Validation: The contract checks for zero input amounts and invalid addresses, which is good. However, it does not validate the minAmountOut parameter, which could be set to an unrealistic value, causing the transaction to fail.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

 function getZetaFromEth(
        address destinationAddress,
        uint256 minAmountOut
    ) external payable override returns (uint256) {
        if (destinationAddress == address(0)) revert ZetaCommonErrors.InvalidAddress();
        if (msg.value == 0) revert InputCantBeZero();

        ISwapRouterPancake.ExactInputSingleParams memory params = ISwapRouterPancake.ExactInputSingleParams({
            tokenIn: WETH9Address,
            tokenOut: zetaToken,
            fee: zetaPoolFee,
            recipient: destinationAddress,
            amountIn: msg.value,
            amountOutMinimum: minAmountOut,
            sqrtPriceLimitX96: 0
        });
## Tools Used
Manual Analysis
## Recommended Mitigation Steps
there should be a minimum amount of  minAmountOut 


## Assessed type

Context
Liquidity Checks for large trade
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerPancakeV3.strategy.sol#L209


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

Liquidity Checks: The hasZetaLiquidity function only checks if the pool exists and has liquidity. It does not check the depth of liquidity, which could be relevant for large trades.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

 function hasZetaLiquidity() external view override returns (bool) {
        address poolAddress = uniswapV3Factory.getPool(WETH9Address, zetaToken, zetaPoolFee);

        if (poolAddress == address(0)) {
            return false;
        }

        
        IUniswapV3Pool pool = IUniswapV3Pool(poolAddress);
        return pool.liquidity() > 0;

## Tools Used
Manual Analysis
## Recommended Mitigation Steps


## Assessed type

Context
Withdraw Pattern 
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerPancakeV3.strategy.sol#L178


# Vulnerability details

## Impact
Detailed description of the impact of this finding.


Withdraw Pattern: The getEthFromZeta function uses the withdraw pattern to send ETH to a destination address. While this is a common pattern, it's important to ensure that the destination address is not a contract that could fail the transaction or execute malicious code.



## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

 (bool sent, ) = destinationAddress.call{value: amountOut}("");
## Tools Used

## Recommended Mitigation Steps


## Assessed type

ETH-Transfer
`CreateTSSVoter` Hard-Coded Threshold
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L61


# Vulnerability details

## Impact

`CreateTSSVoter` has a hard-coded value of `"1.00"` for its `BallotThreshold`. A threshold of `"1.00` means that 100% of the enfranchised observers must vote before the ballot for the same options before the ballot is considered finalised. Besides not using the seemingly intended method to configure ballot thresholds for chains ([evidence](https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/types/params.go#L30)), this threshold is eventually unattainable and can permanently disrupt a critical voting type. The problem is made worse by the fact that the purpose of this voting type is to agree on creating a new TSS key.

Disclaimer: I finally clicked on the link within the `TODO` comment inside this function. This links to an **open** issue containing [a comment](https://github.com/zeta-chain/node/issues/896#issuecomment-1661903429) where the sponsor is clearly concerned about this very same hard-coded threshold. So, I'm not sure about the rules in this specific case, but this may be considered known issues. In any case, the report was already written.

## Proof of Concept

The hard-coded threshold for the `CreateTSSVoter` ballot can be found on this line:

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/msg_tss_voter.go#L61
```go
			BallotThreshold:      sdk.MustNewDecFromStr("1.00"),
```

And this is the logic used to decide whether a ballot is finalised:

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/types/ballot.go#L37C1-L65C2
```go
func (m Ballot) IsBallotFinalized() (Ballot, bool) {
	if m.BallotStatus != BallotStatus_BallotInProgress {
		return m, false
	}
	success, failure := sdk.ZeroDec(), sdk.ZeroDec()
	total := sdk.NewDec(int64(len(m.VoterList)))
	for _, vote := range m.Votes {
		if vote == VoteType_SuccessObservation {
			success = success.Add(sdk.OneDec())
		}
		if vote == VoteType_FailureObservation {
			failure = failure.Add(sdk.OneDec())
		}

	}
	if failure.IsPositive() {
		if failure.Quo(total).GTE(m.BallotThreshold) {
			m.BallotStatus = BallotStatus_BallotFinalized_FailureObservation
			return m, true
		}
	}
	if success.IsPositive() {
		if success.Quo(total).GTE(m.BallotThreshold) {
			m.BallotStatus = BallotStatus_BallotFinalized_SuccessObservation
			return m, true
		}
	}
	return m, false
}
```

`CreateTSSVoter` then needs 100% of the enfranchised observers to agree on the TSS creation. Thus, only one compromised or faulty observer, or even a destroyed key, will permanently disrupt this voting type for this software version. This breaks basic assumptions about trust on observers, and exposes the system to dangerous situations that will, more likely than not, happen sometime in the future.

## Tools Used

Manual: code editor.

## Re
No Input Validation for Updates
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L60


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

No Input Validation for Updates: The update functions do not validate the new values being set. For example, setting a GAS_LIMIT to an extremely high value could result in excessive gas fees.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

 constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_,
        uint256 chainid_,
        CoinType coinType_,
        uint256 gasLimit_,
        address systemContractAddress_
    ) {
        if (msg.sender != FUNGIBLE_MODULE_ADDRESS) revert CallerIsNotFungibleModule();
        _name = name_;
        _symbol = symbol_;
        _decimals = decimals_;
        CHAIN_ID = chainid_;
        COIN_TYPE = coinType_;
        GAS_LIMIT = gasLimit_;
        SYSTEM_CONTRACT_ADDRESS = systemContractAddress_;
    }
## Tools Used
Manual Analysis
## Recommended Mitigation Steps
we should use a require statement to check the zero address.




## Assessed type

Invalid Validation
Centralization Risk  due to  onlyFungible modifier.
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L270
https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L288


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

Centralization Risk: The contract relies heavily on the FUNGIBLE_MODULE_ADDRESS for critical operations and updates, which introduces a central point of control and failure. If the private key for this address is compromised, the attacker could potentially manipulate the contract in harmful ways.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L288C4-L290C54

 function updateProtocolFlatFee(uint256 protocolFlatFee) external onlyFungible {
        PROTOCOL_FLAT_FEE = protocolFlatFee;
        emit UpdatedProtocolFlatFee(protocolFlatFee);

 function updateSystemContractAddress(address addr) external onlyFungible {
        SYSTEM_CONTRACT_ADDRESS = addr;
        emit UpdatedSystemContract(addr);
    }

## Tools Used
MANUAL ANALYSIS
## Recommended Mitigation Steps


## Assessed type

Rug-Pull
UNABLE TO UPDATE THE `TSSAddress` IF BOTH THE `TSSAddressUpdater` ADDRESS HAS BEEN RENOUNCED AND VALIDATOR NODES HAVE CHURNED THUS PUTTING THE PROTOCOL FUNCTIONALITY IN DANGER
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/ERC20Custody.sol#L80-L86
https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/ERC20Custody.sol#L107-L113


# Vulnerability details

## Impact

The `ERC20Custody.updateTSSAddress` function is used by the TSSUpdater to update the TSSAddress in case of Zeta blockchain validator nodes churn. 

The `ERC20Custody.renounceTSSAddressUpdater` function is used to change the ownership of TSSAddressUpdater to the Zeta blockchain TSS nodes (`TSSAddress`) as shown below:

    function renounceTSSAddressUpdater() external onlyTSSUpdater {
        if (TSSAddress == address(0)) {
            revert ZeroAddress();
        }
        TSSAddressUpdater = TSSAddress;
        emit RenouncedTSSUpdater(msg.sender);
    }

The issue here is that if the `ERC20Custody.renounceTSSAddressUpdater` is called by the `TSSUpdater` and `TSSAddressUpdater` is renounced, then the `TSSAddress can never be updated` if the Zeta blockchain `validator nodes churn` in the future thus putting the protocol in danger.

## Proof of Concept

```solidity
    function updateTSSAddress(address TSSAddress_) external onlyTSSUpdater {
        if (TSSAddress_ == address(0)) {
            revert ZeroAddress();
        }
        TSSAddress = TSSAddress_;
        emit UpdatedTSSAddress(TSSAddress_);
    }
```

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/ERC20Custody.sol#L80-L86

```solidity
    function renounceTSSAddressUpdater() external onlyTSSUpdater {
        if (TSSAddress == address(0)) {
            revert ZeroAddress();
        }
        TSSAddressUpdater = TSSAddress;
        emit RenouncedTSSUpdater(msg.sender);
    }
```

https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/ERC20Custody.sol#L107-L113

## Tools Used
Manual Review and VSCode

## Recommended Mitigation Steps

Hence it is recommended to add functionality in the `ERC20Custody` contract to update the `TSSAddress`, if both the `TSSAddressUpdater` is renounced and the Zeta blockchain validator nodes have churned. Thus ensuring the proper functioning of the protocol if such condition occurs.


## Assessed type

Other
PayGasFeeInZetaAndUpdateCctx() is prone to slippage, causing sender overpays the revert gas and lose returned funds
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/gas_payment.go#L321
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/gas_payment.go#L292


# Vulnerability details

## Impact
uniswapV2 swap on zetaChain is used in multiple flows where the protocol will swap either zeta or ERC20 token to gas erc20 token to pay for outbound tx gas. However, there is no slippage protection for the swapping transaction which makes the process of charging gas fees slippage-prone, easily causing the sender to overpay the revert gas and lose returned funds.

## Proof of Concept
When an outbound tx is initiated whether as a revert tx or diverted outbound tx, `PayGasAndUpdateCctx()` on gas_payment.go will be called. Based on `CointType`, `PayGasAndUpdateCctx()` divert the flow into different process that swaps either zeta token or ERC20 token into gas token of the external chain to pay for revert or outbound gas.

For example, `CointType` is zeta coin. `PayGasInZetaAndUpdateCctx()` will be called, which will first calculate gas token amount needed based on the external chainId. Then it first calls `QueryUniswapV2RouterGetZetaAmountsIn()` which query `wzeta/gasZRC20` uniswapv2 pool for zeta token amount (`outTxGasFeeInZeta`) needed to swap out `outTxGasFee.BigInt()` amount of gasZRC20 token. Then it will call `CallUniswapV2RouterSwapExactETHForToken()` with exact input `outTxGasFeeInZeta` amount. 

The above is problematic because after `QueryUniswapV2RouterGetZetaAmountsIn()`, there is no check to ensure the quoted input token amount `outTxGasFeeInZeta` is acceptable within a certain slippage parameter. When there is significant slippage in the uniswapV2 pool before protocol's `PayGasInZetaAndUpdateCctx()` call, gasZRC20 token price in zeta amount will be inflated,causing gas overcharged. 

When gas is overcharged, (1) this either cause `feeInZeta.GT(zetaBurnt) == true` which means sender doesn't have enough zeta token to burn, which directly return an error, which might cause a revert cctx to be aborted and sender will lose all refunds. (2) Or if `feeInZeta.GT(zetaBurnt) == false`, sender's zeta balance will be substracted by inflated gas payment, and the sender is overcharged.

```go
//x/crosschain/keeper/gas_payment.go
func (k Keeper) PayGasInZetaAndUpdateCctx(
	ctx sdk.Context,
	chainID int64,
	cctx *types.CrossChainTx,
	zetaBurnt math.Uint,
	noEthereumTxEvent bool,
) error {
...
// get the gas fee in Zeta using system uniswapv2 pool wzeta/gasZRC20 and adding the protocol fee
//@audit this quote the price of gasZRC20 in zeta denomination. There is no slippage check to ensure `outTxGasFeeInZeta` is not inflated. 
|>	outTxGasFeeInZeta, err := k.fungibleKeeper.QueryUniswapV2RouterGetZetaAmountsIn(ctx, outTxGasFee.BigInt(), gasZRC20)
...
feeInZeta := types.GetProtocolFee().Add(math.NewUintFromBigInt(outTxG
Blames are stored even if observers voted against it in the Ballot
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/observer/keeper/msg_server_add_blame_vote.go#L54


# Vulnerability details

## Impact
The `AddBlameVote` function allows observers to submit a `Blame` vote. The function creates a new `Ballot` in case it has not been already created and then submits the vote.
This function is also responsible for checking if the `Ballot` is finalized, in case it is, the ballot is closed.
However the `Blame` is stored in line 54 regardless of the result of the `Ballot`, it is stored either if the result is success or failure, it is only required that `BallotThreshold` is fulfilled.
Consequently, it will be stored on chain a `Blame` instance even if observers voted against it.

## Tools Used
Manual review

## Recommended Mitigation Steps
It is recommended to implement a way to register the `Blame` only if the `Ballot` is successful.


## Assessed type

Error
User not refunded for failed Zeta gas payment in cross chain transaction
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/keeper_cross_chain_tx_vote_inbound_tx.go#L169-L185
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/keeper_cross_chain_tx_vote_inbound_tx.go#L191-L201
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/keeper_cross_chain_tx_vote_inbound_tx.go#L215-L239


# Vulnerability details

When observed CCTX (cross chain transaction) gathers enough votes in VoteOnObservedInboundTx, it is being processed immediately. In case that the transaction is reverted for whatever reason, it returns an error and changes CCTX status to `CctxStatus_Aborted` - such a transaction won't be processed:

```go
			if err != nil {
				// do not commit anything here as the CCTX should be aborted

				// gas payment for erc20 type might fail because no liquidity pool is defined to swap the zrc20 token into the gas token
				// in this gas we should refund the sender on ZetaChain
				if cctx.InboundTxParams.CoinType == common.CoinType_ERC20 {

					if err := k.RefundAmountOnZetaChain(ctx, cctx, cctx.InboundTxParams.Amount); err != nil {
						// log the error
						k.Logger(ctx).Error("failed to refund amount of aborted cctx on ZetaChain",
							"error", err,
							"sender", cctx.InboundTxParams.Sender,
							"amount", cctx.InboundTxParams.Amount.String(),
						)
					}
				}

				cctx.CctxStatus.ChangeStatus(types.CctxStatus_Aborted, err.Error())
				return &types.MsgVoteOnObservedInboundTxResponse{}, nil
```

There is also no mechanism for retrieving the funds back. TSS could probably initialize such a refund, however it required quorum of nodes to do so, and there is no automatic process for this in code in scope. Possible issues for transaction errors are numerous, I'll list just some of them that immediately come to my head:
* too little amount to cover gas fees. Please note that UniswapV2 is used here, and as described in another issue, it doesn't have proper price manipulation protection, meaning that one of the results may be swapping in highly manipulated pool for overly small fee token amount, making the CCTX error out and abort
* no valid pool for UniswapV2 swap
* too little gas paid to process the transaction
* stale gas prices recorded by keepers
* any issues with gas coin setting in the zetacored

For cross chain swaps it's even worse, because there is no notion of a refund at all, meaning that the funds are frozen:

```javascript
	// [...]
} else { // Cross Chain SWAP
		tmpCtx, commit := ctx.CacheContext()
		err = func() error {
			err := k.PayGasAndUpdateCctx(
				tmpCtx,
				receiverChain.ChainId,
				&cctx,
				cctx.InboundTxParams.Amount,
				false,
			)
			if err != nil {
				return err
			}
			// [...]
		}()
		if err != nil {
			// do not commit anything here as the CCTX