xanoutas/solidity-security-findings huggingface dataset:
Duplicate observers may be introduced, disrupting the observation voting procedure
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/0df9a114103b6ed7536d65a03e2e4876eb8bdbc0/repos/node/x/observer/keeper/msg_server_add_observer.go#L47
https://github.com/code-423n4/2023-11-zetachain/blob/0df9a114103b6ed7536d65a03e2e4876eb8bdbc0/repos/node/x/observer/keeper/msg_server_update_observer.go#L36


# Vulnerability details

## Impact

### Attack Vector 1: Duplicates may be introduced via `AddObserver()`

Function [AddObserver()](https://github.com/code-423n4/2023-11-zetachain/blob/0df9a114103b6ed7536d65a03e2e4876eb8bdbc0/repos/node/x/observer/keeper/msg_server_add_observer.go#L14-L54) adds a new observer to all chain observers as follows:

```go
observerMappers := k.GetAllObserverMappers(ctx)
totalObserverCountCurrentBlock := uint64(0)
for _, mapper := range observerMappers {
    mapper.ObserverList = append(mapper.ObserverList, msg.ObserverAddress)
    totalObserverCountCurrentBlock += uint64(len(mapper.ObserverList))
    k.SetObserverMapper(ctx, mapper)
}
```

As can be seen, `msg.ObserverAddress` is appended to `mapper.ObserverList`, which will create duplicates in the observer lists if the address is already present.

### Attack Vector 2: Duplicates may be introduced via `UpdateObserver()`

Function [UpdateObserver()](https://github.com/code-423n4/2023-11-zetachain/blob/0df9a114103b6ed7536d65a03e2e4876eb8bdbc0/repos/node/x/observer/keeper/msg_server_update_observer.go#L12-L64) provides two possibilities to update the observer address:

- For a tombstoned validator to update their own address to a new one;
- For an administrator to update the address of any observer.

In both cases, the address is updated via the call `k.UpdateObserverAddress(ctx, msg.OldObserverAddress, msg.NewObserverAddress)`, with function [UpdateObserverAddress()](https://github.com/code-423n4/2023-11-zetachain/blob/0df9a114103b6ed7536d65a03e2e4876eb8bdbc0/repos/node/x/observer/keeper/msg_server_update_observer.go#L86-L100) being:

```go
func (k Keeper) UpdateObserverAddress(ctx sdk.Context, oldObserverAddress, newObserverAddress string) {
	observerMappers := k.GetAllObserverMappers(ctx)
	for _, om := range observerMappers {
		UpdateObserverList(om.ObserverList, oldObserverAddress, newObserverAddress)
		k.SetObserverMapper(ctx, om)
	}
}

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

As can be seen, `newObserverAddress` replaces `oldObserverAddress` irrespective of whether another entry with the same address is present in the list, thus making it possible to introduce duplicates.


### Impact 1: a duplicated observer, deemed to be removed, will still be able to vote

There are multiple circumstances when a validator, acting also as observer, should be removed from the list of observers: 
e.g. when it's delegated amount falls below minimum (due to undelegations, or being slashed), or whe
Unable to withdraw even though zeta is in balance
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L225
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L191
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L256
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L157
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L178


# Vulnerability details

## Impact
***The identified issue pertains to the absence of an overflow check in the token minting function of the [ZRC20](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L225C5-L230C6)smart contract. The [_mint](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/protocol-contracts/contracts/zevm/ZRC20.sol#L191C5-L197C6) function, responsible for minting additional tokens, does not validate whether incrementing the `_totalSupply` and `_balances[account]` variables could result in an overflow. In the absence of this check, there is a risk that if the sum exceeds the maximum representable value for a uint256, it may lead to an overflow.***

***In the event of an overflow, both the total supply `(_totalSupply)` and individual user balances `(_balances[account])` would wrap around to zero. This could introduce severe inaccuracies in subsequent calculations involving token transfers, withdrawals, or balance queries, potentially causing a loss of funds for users.***

## Proof of Concept
```
function deposit(address to, uint256 amount) external override returns (bool) {
        if (msg.sender != FUNGIBLE_MODULE_ADDRESS && msg.sender != SYSTEM_CONTRACT_ADDRESS) revert InvalidSender();
        _mint(to, amount);      <----- Vulnerability 
        emit Deposit(abi.encodePacked(FUNGIBLE_MODULE_ADDRESS), to, amount);
        return true
}
```
```
  function _mint(address account, uint256 amount) internal virtual {
        if (account == address(0)) revert ZeroAddress();

        _totalSupply += amount;               <----@audit overflow prone here
        _balances[account] += amount;         <----@audit overflow prone here
        emit Transfer(address(0), account, amount);
    }
```
***Loss of funds when trying to withdraw because of incorrect calculation in the _balance[A]***
```
 function withdraw(bytes memory to, uint256 amount) external override returns (bool) {
        (address gasZRC20, uint256 gasFee) = withdrawGasFee();
        if (!IZRC20(gasZRC20).transferFrom(msg.sender, FUNGIBLE_MODULE_ADDRESS, gasFee)) { <----- Vulnerability
Zeta Sent Event Spoofing Leads to Arbitrary Minting
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/inbound_tracker.go#L157


# Vulnerability details

## Impact
When parsing incoming events from the inbound tracker for event handling, there is a missing check for the *emitter* of the event. The function ``ParseZetaSent`` only finds a specific event but does not actually filter out the ``Connector`` contract. As a result, it is possible to create arbitrary Zeta going to the zEVM or send arbitrary Zeta going to other chains. This finding is very similar to the ERC20 Deposit faking, ZetaMessage confirmation faking and ZetaRevert confirmation faking. However, these require separate exploit steps and separate fixes, which is why they were reported as separate and unique reports.

The vulnerable line of code is within [inbound_tracker.go](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/inbound_tracker.go#L157). This bug is very similar to a Zellic audit finding. Using this, it's possible to create arbitrary ZETA out of nothing.
  
The inbound tracker is accessible to any user by using a proof. Additionally, an observer can upload arbitrary transaction hashes, which can also be processed. Since the first option is permissionless with a proof, this can be used by any user to create arbitrary Zeta from an EVM chain. For an arbitrary user to bypass [this](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/x/crosschain/keeper/verify_proof.go#L75) check that the ``To()`` is the ConnectorContract address, this event emission could be done within another CCTX to bypass this.

## Proof of Concept
This requires setting up a test environment to see. So, start up the test environment with the steps at [here](https://github.com/code-423n4/2023-11-zetachain/blob/main/repos/node/contrib/localnet/README.md). This PoC will send Zeta from the connector to the contract. Everything else should be done in order as follows: 

If you would like a video demonstrating the vulnerability, [here](https://www.youtube.com/watch?v=Q7cDrQtLv9k) is one that goes through these steps with a verbal explanation. 

1. Deploy a contract with the following code in something. For the sake of simplicity, we'll use remix.

```
    contract ZetaSentExploit {
        event ZetaSent(
            address sourceTxOriginAddress,
            address indexed zetaTxSenderAddress,
            uint256 indexed destinationChainId,
            bytes destinationAddress,
            uint256 zetaValueAndGas,
            uint256 destinationGasLimit,
            bytes message,
            bytes zetaParams
        ); 

        function zetaSentExploit(bool zeta) external{

            if(zeta == true){
                emit ZetaSent(
                    address(this),
                    address(this),
                    7000, // Chain ID for zeta
Zeta Revert Received Event Spoofing
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L423


# Vulnerability details

## Impact
The event handler at [evm_client.go](evm_client.go) is used for checking if an event occurred or not on Ethereum. This is done by taking the hash of the CCTX and looking at the ``events`` of the transaction. In the ``zetareverted`` handler, it only looks for a single event then exits the flow. This finding is very similar to the ERC20 Deposit faking, ZetaMessage confirmation faking and ZetaSent confirmation faking. However, these require separate exploit steps and separate fixes, which is why they were reported separately and uniquely.

The *filtering* of the events is flawed though. This is similar to Zellic ``3.0``, just in a different section of code. Although it is parsing the proper event type, it is NOT filtering by the contract address, even though it literally use ``connector.`` on it. We feel that this was a poor design choice by GETH and we are sorry about that! 
  
By having the CCTX emit the same event as the Connector contract (``zetareverted``) the users contract will get processed first since only a single one is processed and the event emission on the connector happens last. If an invalid tx_hash is passed in, it will be used for *voting* on the confirmation check. This will keep the function in the ``PendingReverted`` queue forever, clogging up the key signing and CCTX process. If this was replicated several thousand times, it would likely bring this to a halt since the key signing with a bad nonce still occurs and all of the transactions in the queue need to be processed.
  
From our testing, we could not make this exploitable enough to create arbitrary ZETA though. The zetaclient will continue to sign the TX, since it never left the queue. However, the TSS nonce is hardcoded in the outbound parameters, meaning that it will get signed with the same nonce over and over again, which will fail. This was a great design choice for security, which we love to see. Additionally, when the TSS address is changed (which would reset the nonce), it no longer pulls the *previous* TSS addresses CCTXs to be signed. If this issue is not fixed, it's possible that some future change could cause this to be exploitable to duplicate ZETA instead of just a DoS.

## Proof of Concept
These steps are for reproducing the bug. We will demonstrate how to keep this in the ``PendingRevert`` state forever. 

1. Deploy the following smart contract with the connector address and zeta token address.
```
contract ZetaReceivedExploit2 is ZetaInteractor, ZetaReceiver {

    uint256 public called; 

    // 0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9, 0xA8D5060feb6B456e886F023709A2795373691E63
    constructor(
        address connectorAddress,
        address zetaTokenAddress
    ) ZetaInteractor(connectorAddress) {
        _zetaToken = IERC20(zetaTokenAddress);
    }

    /**
     *
Zeta Message Received Event Spoofing
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L386


# Vulnerability details

## Impact
The event handler at [evm_client.go](https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L386) is used for checking if an event occurred or not on Ethereum. This is done by taking the hash of the CCTX and looking at the ``events`` of the transaction. In the ``ZetaMessage`` handler, it only looks for a single event then exits the flow. This finding is very similar to the ERC20 Deposit faking, ZetaReverted confirmation faking and ZetaSent confirmation faking. However, these require separate exploit steps and separate fixes, which is why they were reported uniquely and separately.

The *filtering* of the events is flawed though. This is similar to Zellic ``3.0``, just in a different section of code. Although it is parsing the proper event type, it is NOT filtering by the contract address, even though it literally use ``connector.`` on it. We feel that this was a poor design choice by GETH and we are sorry about that! 
  
By having the CCTX emit the same event as the Connector contract (``ZetaMessage``) the users contract will get processed first since only a single one is processed and the event emission on the connector happens last. If an invalid tx_hash is passed in, it will be used for <i>voting</i> on the confirmation check. This will keep the function in the ``OutboundPending`` queue forever, clogging up the tx signing and CCTX process. If this was replicated several thousand times, it would likely bring this to a halt.
  
From our testing, we could not make this exploitable enough to create arbitrary ZETA though. The zetaclient will continue to sign the TX, since it never left the queue. However, the TSS nonce is hardcoded in the outbound parameters, meaning that it will get signed with the same nonce over and over again, which will fail. This was a great design choice for security, which we love to see. Additionally, when the TSS address is changed (which would reset the nonce), it no longer pulls the *previous* TSS addresses CCTXs to be signed. If this issue was not fixed, some change or other exploit method could be made to make this possible to duplicate ZETA but we only found a way to cause a denial of service using this.

## Proof of Concept
These steps are for reproducing the bug. We will demonstrate how to keep this in the ``OutboundPending`` state forever. 

1. Deploy the following smart contract with the connector and zeta token address. You will need to copy in the contracts for the ``ZetaInteractor, ZetaReceiver`` for this.
```
contract ZetaReceivedExploit is ZetaInteractor, ZetaReceiver {

    uint256 public called; 

    // 0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9, 0xA8D5060feb6B456e886F023709A2795373691E63
    constructor(
        address connectorAddress,
Outbound Confirmation Tracker Race Condition Leads to a Double Spend
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L610
https://github.com/code-423n4/2023-11-zetachain/blob/b237708ed5e86f12c4bddabddfd42f001e81941a/repos/node/zetaclient/evm_client.go#L295


# Vulnerability details

## Impact
In the Zellic audit, the report ``3.2 Bonded validators can trigger reverts for successful transactions`` points out that the ``RemoveFromOutTxTracker`` can be called by any bonded validator. They mention this as being an issue then mention an exploit path to use, which is the focus of that finding. The ``RemoveFromOutTxTracker`` issue resulting from bad access control issue was remediated, however there is another way to use the same exploit.
  
Their method of exploitation is as follows: 

1. Create a CCTX from one EVM chain to a different EVM chain, transferring ZETA
2. Process the incoming event so that it's put into the ``PendingOutbound`` state. 
3. The zetaclient signs the transaction with the ``TryProcessOutTx`` then broadcasts this to the EVM. Right after the voting, the zetaclient will also add the transaction to the ``OutboundTxTracker``. 
4. Call ``RemoveFromOutTxTracker`` to remove the outbound transaction so that the Zetaclient will never see it. While this exact method is fixed, we can replicate the *removal* with a race condition that will be described below.
5. Call ``AddToOutTxTracker`` where the same nonce as the transaction from the TSS address. Make this a reverted transaction so that we trick the processing to eventually call ``onRevert``. 
6. ``observeTxOut`` picks up the FAKE transaction as being reverted, even though it actually succeeded. This will put the tx into the ``ob.outTXConfirmationTransaction`` and ``ob.outTXConfirmationReceipts`` structure. 
7. With the transactions and receipt in the structures, another thread calls ``PostReceiveConfirmation`` on our fake transaction as being reverted. After enough of these, a vote passes that validates that the transaction did in fact fail.
8. The revert flow will occur. This will send us back the funds on the EVM chain even though they were already sent once in the original transaction.
  
By adding a reverted transaction to the ``OutTxTracker`` at just the right moment, it is possible to cause a **race condition** where the real transaction is broadcasted to the outbound chain but the wrong tx is processed through the queue for the ``OutTracker``. This is able to replicate the effect of step 4 of the Zellic method, since we can get our fake transaction processed *first* in the voting process. The timing for performing a double spend is very tight though. In particular, the function must be within the *signing* process of our CCTX but has NOT added the TX to the outbound queue yet when we add the function to the ``OutTxTracker``. If this is done, a double spend will occur. However, causing a denial of service by sending the fake transaction as soon as possi
Trident router call  seems don't  support  ` address(0)' 
Audit: 2023-11-zetachain-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Trident router call seems don't support  `address(0)`, the function call may revert(). 

## Proof of Concept
In `ZetaTokenConsumerTrident`, param is set to `address(0)`, but I read the related documention and code, I don't find similar address(0) logic.
```solidity 
IPoolRouter.ExactInputSingleParams({
@>            tokenIn: address(0),
            amountIn: msg.value,
            amountOutMinimum: minAmountOut,
            pool: pairPools[0],
            to: destinationAddress,
            unwrap: false
        });

```

I only find sushiswap router function `exactInputSingle` match this param, but [exactInputSingle](https://github.com/sushiswap/trident/blob/2263b21e91f0c5e229d25e22797a6f067eb7398b/contracts/TridentRouter.sol#L59) seems dont have logic to porcess the address(0). So the function call is possible to revert().

 

## Tools Used
manual 

## Recommended Mitigation Steps

 replace `address(0)` with the input token


## Assessed type

Uniswap
Token spending by Uniswap router doesn't get approved
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#L162


# Vulnerability details

## Impact
Tokens do not get approved to be spent by the Uniswap router, which will always make sellProfits revert and lock any tokens sent to this contract in the process.

This issue will make any ERC20 tokens sent to be contract to be permanently frozen in the contract, hence the high-severity of this finding.


## Proof of Concept
In ZetaTokenConsumerPancakeV3.sol, getEthFromZeta does not approve tokens to be spent by the Uniswap router. This will cause any call to getEthFromZeta to always revert upon calling and will results in all tokens sent to the contract to be locked forever.


## Tools Used
Manual Review
## Recommended Mitigation Steps



Consider adding something like the following snippet just before to the function to mitigate the issue:

IERC20(_profits).approve(address(swapRouter), IERC20(_profits).balanceOf(address(this)))
Also, consider making the contract take the tokens out of the user upon calling so that even if something else makes the execution fail the funds will still be in the user's balance.


## Assessed type

Uniswap
Centralization Risk for trusted owners
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#L11


# Vulnerability details

## Impact
Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds. 
Note that  `TSSAddress` refers to the TSS address collectively possessed by Zeta blockchain validators.

## Proof of Concept

#### Impact:
Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds. 
Note that  `TSSAddress` refers to the TSS address collectively possessed by Zeta blockchain validators.

*Instances (18)*:
```solidity
File: contracts/evm/ERC20Custody.sol

11: contract ERC20Custody is ReentrancyGuard {

80:      function updateTSSAddress(address TSSAddress_) external onlyTSSUpdater {

92:      function updateZetaFee(uint256 zetaFee_) external onlyTSS {

107:     function renounceTSSAddressUpdater() external onlyTSSUpdater {

118:     function pause() external onlyTSS {

132:     function unpause() external onlyTSS {

144:     function whitelist(IERC20 asset) external onlyTSS {

153:     function unwhitelist(IERC20 asset) external onlyTSS {

193:     function withdraw(address recipient, IERC20 asset, uint256 amount) external nonReentrant onlyTSS {

```

```solidity
File: contracts/evm/ZetaConnector.base.sol

15: contract ZetaConnectorBase is ConnectorErrors, Pausable {

140:     function renounceTssAddressUpdater() external onlyTssUpdater {

```

```solidity
File: contracts/evm/ZetaConnector.non-eth.sol

15: contract ZetaConnectorNonEth is ZetaConnectorBase {

31:         function setMaxSupply(uint256 maxSupply_) external onlyTssAddress {

61:         function onReceive(
                bytes calldata zetaTxSenderAddress,
                uint256 sourceChainId,
                address destinationAddress,
                uint256 zetaValue,
                bytes calldata message,
                bytes32 internalSendHash
            ) external override onlyTssAddress {

87:        function onRevert(
                address zetaTxSenderAddress,
                uint256 sourceChainId,
                bytes calldata destinationAddress,
                uint256 destinationChainId,
                uint256 remainingZetaValue,
                bytes calldata message,
                bytes32 internalSendHash
            ) external override whenNotPaused onlyTssAddress {

```

```solidity
File: contracts/evm/ZetaConnector.eth.sol

15: contract ZetaConnectorEth is ZetaConnectorBase {

52:     function onReceive(
            bytes calldata zetaTxSenderAddress,
            uint256 sourceChainId,
            address destinationAddress,
            uint256 zetaValue,
            bytes calldata message,
            bytes32 internalSendHash
        ) external override onlyTssAddress {

77:      function onRevert(
            address zetaTxSenderAddress,
            uint2