xanoutas/solidity-security-findings huggingface dataset:
The sender will unfairly loose refunds for their inbound tx (coinType is erc20) when their gas payment fail due to no liquidity pool
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#L236


# Vulnerability details

## Impact
When a sender's inbound tx with coinType erc20, to an external chain fails due to no liquidity pool to pay for gas. They will lose the entire refund.

## Proof of Concept
In x/crosschain/keeper/keeper_cross_chain_tx_vote_inbound_tx.go, when a sender's inbound tx to zetaChain fails, a revert outbound tx will be created which requires gas payment. When the gas payment fails due to no liquidity pool to swap zeta or zrc20 for gas coin, the sender will still be refunded with their entire funds through `RefundAmountOnZetaChain`. In `RefundAmountOnZetaChain`, the sender and foreign coin zrc20 address will be fetched from cctx, and `fungibleKeeper` will `DepositZRC20()` based on `inputAmount` to the sender. Sender's funds are not lost. See below code snippet.

However, when a sender's inbound tx is targeting an external chain (external swap), the same gas payment process will be performed for the outbound tx. And the gas payment fails due to no liquidity pool to swap for gas coin, the sender will NOT be refunded. The cctx status will be directly changed to aborted and sender's funds will be lost.

```go
//x/crosschain/keeper/keeper_cross_chain_tx_vote_inbound_tx.go
func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.MsgVoteOnObservedInboundTx) (*types.MsgVoteOnObservedInboundTxResponse, error) {
...
	if receiverChain.IsZetaChain() {
if err != nil && !isContractReverted { 
...
		} else if err != nil && isContractReverted { // contract call reverted; should refund
...
			err = func() error {
				//@audit-info ? How is this gas paid?
				err := k.PayGasAndUpdateCctx(
					tmpCtx,
					chain.ChainId,
					&cctx,
					cctx.InboundTxParams.Amount,
					false,
				)
				if err != nil {
					return err
				}
				err = k.UpdateNonce(tmpCtx, chain.ChainId, &cctx)
				if err != nil {
					return err
				}
				return nil
			}()
			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 {
                        //@audit `RefundAmountOnZetaChain()` will refund the sender with the initial inputAmount if gas payment fails due to no liquidity pool
|>				if err := k.RefundAmountOnZetaChain(ctx, cctx, cctx.InboundTxParams.Amount); err != nil {
...
					}
				}
	cctx.CctxStatus.ChangeStatus(types.CctxStatus_Aborted, err.Error())
```
(https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/keeper_cross_chain_tx_vote_inbound_tx.go#L193)
```go
//x/crosschain/keeper/keeper_cross_chain_tx_vote_in
Ballot Creator Must Not Be Allowed to Vote Against the Observation
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#L97-L108
https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L49-L78


# Vulnerability details

## Impact

The first voter for a type of observation creates the ballot. An issue is created when the ballot creator can cast a failure in observation as a vote or simply not vote. This opens a way for a compromised or faulty observer to waste network resources with useless ballots without being slashed when the ballot matures. Differently from the other isue I reported, this is another way to spam network resources with ballot creation.

## Proof of Concept

In `VoteOnObservedOutboundTx`, the use of `observerTypes.ConvertReceiveStatusToVoteType` allows the ballot creator to choose a `VoteType_SuccessObservation`, `VoteType_FailureObservation`, and even a `VoteType_NotYetVoted` due to the default branch in its `switch` statement.

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/keeper_cross_chain_tx_vote_outbound_tx.go#L105C1-L108C3
```go
	ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.ConvertReceiveStatusToVoteType(msg.Status))
	if err != nil {
		return nil, err
	}
```

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/types/parsers.go#L8C1-L17C2
```go
func ConvertReceiveStatusToVoteType(status common.ReceiveStatus) VoteType {
	switch status {
	case common.ReceiveStatus_Success:
		return VoteType_SuccessObservation
	case common.ReceiveStatus_Failed:
		return VoteType_FailureObservation
	default:
		return VoteType_NotYetVoted
	}
}
```

In the case of `CreateTSSVoter`, besides the success observation vote, the ballot creator is able to vote for `VoteType_FailureObservation`.

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/msg_tss_voter.go#L73C1-L77C4
```go
	} else if msg.Status == common.ReceiveStatus_Failed {
		ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.VoteType_FailureObservation)
		if err != nil {
			return &types.MsgCreateTSSVoterResponse{}, err
		}
```

A dishonest or faulty observer that is able to vote `VoteType_FailureObservation` or `VoteType_NotYetVoted` can always create spam ballots on false observations without being slashed [due to how the reward distribution algorithm works](https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/types/ballot.go#L77-L102).

For example, if an observer creates a ballot about a false observation, while voting `VoteType_FailureObservation`, he will not be slashed or punished in any way when other observers vote the same, true option. The same applies for `VoteType_NotYetVoted` as this type of vote it not counted in `BuildRewardsDistribution` and not punished in any way.

## Tools
Slippage Control 
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/ZetaTokenConsumerTrident.strategy.sol#L124


# Vulnerability details

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

Slippage Control: The contract allows users to specify a minAmountOut to control slippage. However, users must be aware of how to set this parameter to avoid trades with excessive slippage.

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

IPoolRouter.ExactInputParams memory params = IPoolRouter.ExactInputParams({
            tokenIn: inputToken,
            amountIn: inputTokenAmount,
            amountOutMinimum: minAmountOut,
            path: path,
            to: destinationAddress,
            unwrap: false
        });
## Tools Used
Manual Analysis
## Recommended Mitigation Steps


## Assessed type

Other
Missing deadline checks allow pending transactions to be maliciously executed
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/ZetaTokenConsumerTrident.strategy.sol#L74


# Vulnerability details

**Summary:** Missing deadline checks allow pending transactions to be maliciously executed

```solidity
function getZetaFromEth(
  // ...
) external payable override returns (uint256) {

  IPoolRouter.ExactInputSingleParams memory params = IPoolRouter.ExactInputSingleParams({
    // ...
    deadline: block.timestamp + MAX_DEADLINE
  });
  
  // Interact with router
}

```


## Assessed type

Timing
Limited Voting Options Allow Ballot Creation Spam
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_inbound_tx.go#L95
https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/keeper/msg_server_add_blame_vote.go#L39
https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/keeper/msg_server_add_block_header.go#L79


# Vulnerability details

## Impact

The message functions for certain types of voting allow only successful observation votes. This creates a problem if a compromised or faulty observer creates spam ballots with false observations. In this situation, honest observers have to spend resources processing the ballot but cannot cast an opposite vote to slash the spammer when the ballot reaches maturity.

Theoritically, a similar situation could happen with all ballots that do not reach the threshold. But in non-vulnerable cases, honest observer **can** cast an opposite vote to make the ballot reach threshold and be accounted for slashing. The real issue comes when honest observers **cannot** cast the honest opposite vote.

## Proof of Concept

For certain types of votes, the only vote that is possible to be cast is an `VoteType_SuccessObservation`.

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/crosschain/keeper/keeper_cross_chain_tx_vote_inbound_tx.go#L95
```go
ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.VoteType_SuccessObservation)
```

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/keeper/msg_server_add_blame_vote.go#L39
```go
ballot, err = k.AddVoteToBallot(ctx, ballot, vote.Creator, types.VoteType_SuccessObservation)
```

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/keeper/msg_server_add_block_header.go#L79
```go
ballot, err = k.AddVoteToBallot(ctx, ballot, msg.Creator, types.VoteType_SuccessObservation)
```

A compromised or faulty observer can then create ballots for spam observations that do not exist, and honest observers can never vote for the opposite option. Since the ballot's threshold will never be reached, the ballot will remain with the status `BallotStatus_BallotInProgress`.

Eventually, the ballot will be processed when the `DistributeObserverRewards` function gathers the list of matured ballots.

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/emissions/abci.go#L63
```go
ballotIdentifiers := keeper.GetObserverKeeper().GetMaturedBallotList(ctx)
```

But, since `BuildRewardsDistribution` does not account for ballots with status `BallotStatus_BallotInProgress`, the ballot spammer will have a `totalRewardUnits == 0` instead of a negative value. This way, no slashing will be executed as a punishment for the false observation, but observers will have to process the spam observation ballot anyway.

* https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/x/observer/types
`approve()`/`safeApprove()` may revert if the current approval is not zero
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
calling `approve()` without first calling `approve(0)` if the current approval is non-zero will revert with some tokens, such as Tether (USDT). While Tether is known to do this, it applies to other tokens as well, which are trying to protect against [this attack vector](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit). `safeApprove()` itself also implements this protection. 
## Proof of Concept
```solidity
File: repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerPancakeV3.strategy.sol

136:         IERC20(inputToken).safeApprove(address(pancakeV3Router), inputTokenAmount);

```

[136](https://github.com/code-423n4/2023-11-zetachain/tree/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerPancakeV3.strategy.sol#L136)

```solidity
File: repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerTrident.strategy.sol

109:         IERC20(inputToken).safeApprove(address(tridentRouter), inputTokenAmount);

```

[109](https://github.com/code-423n4/2023-11-zetachain/tree/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerTrident.strategy.sol#L109)

```solidity
File: repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerUniV2.strategy.sol

68:         IERC20(inputToken).safeApprove(address(uniswapV2Router), inputTokenAmount);

```

[68](https://github.com/code-423n4/2023-11-zetachain/tree/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerUniV2.strategy.sol#L68)

```solidity
File: repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerUniV3.strategy.sol

108:         IERC20(inputToken).safeApprove(address(uniswapV3Router), inputTokenAmount);

```

[108](https://github.com/code-423n4/2023-11-zetachain/tree/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerUniV3.strategy.sol#L108)
## Tools Used
Manual
## Recommended Mitigation Steps
Always reset the approval to zero before changing it to a new value


## Assessed type

ERC20
Missing return value validation
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/ZetaTokenConsumerTrident.strategy.sol#L74


# Vulnerability details

The external functions lack checks on the return values from the Trident router interactions. This could lead to unexpected behavior.

```solidity
function getZetaFromEth(
  // ...
) external payable override returns (uint256) {

  uint256 amountOut = tridentRouter.exactInputSingle{value: msg.value}(params);

  require(amountOut >= minAmountOut, "Insufficient output");

  return amountOut;
}
```


## Assessed type

Invalid Validation
Approval Front-Running
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/ZetaTokenConsumerTrident.strategy.sol#L109


# Vulnerability details

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

Approval Front-Running: The contract approves the Trident router to spend tokens on behalf of the contract. If the token does not implement the EIP-20 standard correctly, this could lead to front-running attacks where an attacker uses the approval before the intended trade occurs.

## 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/evm/tools/ZetaTokenConsumerTrident.strategy.sol#L109
IERC20(inputToken).safeApprove(address(tridentRouter), inputTokenAmount);


https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/protocol-contracts/contracts/evm/tools/ZetaTokenConsumerTrident.strategy.sol#L130
uint256 amountOut = tridentRouter.exactInput(params);

## Tools Used
Manual Analysis
## Recommended Mitigation Steps
token should implement the EIP-20 standard correctly


## Assessed type

Other
Missing Slippage Protection
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#L165


# Vulnerability details

The `deposit` and `withdraw` functions do not check for slippage. This could result in less tokens being deposited or withdrawn than expected.


```diff
function deposit(
   bytes calldata recipient,
   IERC20 asset,
   uint256 amount,
   bytes calldata message
) external nonReentrant {
+   uint256 beforeBal = asset.balanceOf(address(this));

   // transfer token

   uint256 receivedAmt = asset.balanceOf(address(this)) - beforeBal;
+   require(receivedAmt >= amount, "Slippage");
   emit Deposited(recipient, asset, receivedAmt, message); 
}

function withdraw(
   address recipient, 
   IERC20 asset,
   uint256 amount   
) external nonReentrant onlyTSS {
+   uint256 beforeBal = asset.balanceOf(address(this));
    
   asset.safeTransfer(recipient, amount);

+   uint256 withdrawnAmt = beforeBal - asset.balanceOf(address(this)); 
+   require(withdrawnAmt <= amount, "Slippage");
   emit Withdrawn(recipient, asset, amount);
}
```

This checks the actual balance changes and reverts if less than the expected amount was transferred.


## Assessed type

Invalid Validation
Arbitrarily Long Proof Leads to Wasted Node Resources
Audit: 2023-11-zetachain-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/common/bitcoin/bitcoin_spv.go#L84-L96
https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L120-L140


# Vulnerability details

## Impact

When users add a transaction to trackers, they are checked for their merkle proofs. This check does not limit the size of the proofs and this opens a vector for wasting nodes' resources without a equivalent requirement from the message sender.

## Proof of Concept

For Bitcoin transactions, both [`AddToInTxTracker`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/msg_server_add_to_intx_tracker.go#L15) and [`AddToOutTxTracker`](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/msg_server_add_to_outtx_tracker.go#L24) eventually submit the interpreted `msg.Proof` to be processed by the `Prove` function in `bitcoin_spv.go`. Until this step, there are no safety checks to the proof, and it is finally sent to `VerifyHash256Merkle`.

* https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/common/bitcoin/bitcoin_spv.go#L42
```go
	return VerifyHash256Merkle(proof, index)
```

* https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/common/bitcoin/bitcoin_spv.go#L84C1-L96C3
```go
	numSteps := (proofLength / 32) - 1

	for i := 1; i < numSteps; i++ {
		start := i * 32
		end := i*32 + 32
		next := proof[start:end:end]
		if idx%2 == 1 {
			current = Hash256MerkleStep(next, current[:])
		} else {
			current = Hash256MerkleStep(current[:], next)
		}
		idx >>= 1
	}
```

Without validating the `proofLength` for an upper limit, `numSteps` is calculated from it. The function then follows to loop from zero to `numSteps` while performing double SHA256 hashing operations at each step.

For Ethereum transactions, the proofs ends up being sent to `Verify` function in `proof.go`, where it is then sent to the `trie.VerifyProof` function in a library where it is finally processed in a loop.

* https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/common/ethereum/proof.go#L105C1-L113C2
```go
func (m *Proof) Verify(rootHash common.Hash, key int) ([]byte, error) {
	if key < 0 {
		return nil, errors.New("key not found")
	}
	var indexBuf []byte
	// #nosec G701 range is valid
	indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(key))
	return trie.VerifyProof(rootHash, indexBuf, m)
}
```

* https://github.com/ethereum/go-ethereum/blob/master/trie/proof.go#L120C1-L140C3
```go
	for i := 0; ; i++ {
		buf, _ := proofDb.Get(wantHash[:])
		if buf == nil {
			return nil, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash)
		}
		n, err := decodeNode(wantHash[:], buf)
		if err != nil {
			return nil, fmt.Errorf("bad
Missing Slippage Protection
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/ZetaTokenConsumerUniV2.strategy.sol#L58


# Vulnerability details

The `getZetaFromToken` and `getTokenFromZeta` functions do not check for slippage between the input token amount and received zeta tokens. This could result in loss of funds:

```diff
function getZetaFromToken(
  // params
) external override returns (uint256) {

  IERC20(inputToken).safeTransferFrom(msg.sender, address(this), inputTokenAmount);
  
  uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens(
    inputTokenAmount, 
    minAmountOut,
    path, 
    destinationAddress,
    block.timestamp + MAX_DEADLINE
  );

  uint256 amountOut = amounts[path.length - 1];  

+ require(amountOut >= minAmountOut, "Slippage");
  
  return amountOut;
}
```

Similar check should be added to `getTokenFromZeta`.



## Assessed type

Invalid Validation
Liquidity Checks not implemented
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/ZetaTokenConsumerTrident.strategy.sol#L203


# Vulnerability details

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

Liquidity Checks: The hasZetaLiquidity function is not implemented, which means there is no way to check for sufficient liquidity before attempting a trade. This could result in failed transactions or trades with unfavorable rates.
## 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) {
        //@TODO: Implement
        return false;
    }

## Tools Used
Manual Analysis
## Recommended Mitigation Steps
Implement the function


## Assessed type

Context