xanoutas/solidity-security-findings huggingface dataset:
`to_vec()` in `ItsMessage::InterchainTransfer` of `abi_decode` function could cause unexpected behavior
Audit: 2024-08-axelar-network-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-axelar-network/blob/4572617124bed39add9025317d2c326acfef29f1/axelar-amplifier/interchain-token-service/src/abi.rs#L139


# Vulnerability details

## Impact
In the `abi_decode` function within `abi.rs`, the `to_vec()` method is used when decoding the `source_address` field of `ItsMessage::InterchainTransfer`. This causes inefficiencies or unexpected behavior. It is recommended to use the `as_ref()` method instead. 

## Proof of Concept
In the `abi_decode` function within `abi.rs`, the `to_vec()` method is used when decoding the `source_address` field of `ItsMessage::InterchainTransfer`. Using `to_vec()` leads to unnecessary copying of data, which not only introduces inefficiency but also disrupts the expected decoding behavior, potentially causing failures during the decoding process. This usage can lead to issues during decoding, as to_vec() creates a new owned vector, which could causes inefficiencies or unexpected behavior if the data is large because it duplicates memory. Switching to `as_ref()` would avoid these issues by directly referencing the data without copying, ensuring correct and efficient decoding.

[axelar-amplifier/interchain-token-service/src/abi.rs:abi_decode_L139](https://github.com/code-423n4/2024-08-axelar-network/blob/4572617124bed39add9025317d2c326acfef29f1/axelar-amplifier/interchain-token-service/src/abi.rs#L139)
```rust
    pub fn abi_decode(payload: &[u8]) -> Result<Self, Report<Error>> {
        if payload.len() < 32 {
            return Err(Report::new(Error::InvalidMessage));
        }

        let message_type = MessageType::abi_decode(&payload[0..32], true)
            .change_context(Error::InvalidMessageType)?;

        let message = match message_type {
            MessageType::InterchainTransfer => {
                let decoded = InterchainTransfer::abi_decode_params(payload, true)
                    .change_context(Error::InvalidMessage)?;

                Ok(ItsMessage::InterchainTransfer {
                    token_id: TokenId::new(decoded.tokenId.into()),
@audit=>            source_address: HexBinary::from(decoded.sourceAddress.to_vec()),
                    destination_address: HexBinary::from(decoded.destinationAddress.as_ref()),
                    amount: Uint256::from_le_bytes(decoded.amount.to_le_bytes()),
                    data: HexBinary::from(decoded.data.as_ref()),
                })
            }

```



## Tools Used
Manual Review

## Recommended Mitigation Steps
It is recommended to use the as_ref() method instead, which provides a reference to the existing data as a slice (`&[u8]`), which is typically what is needed when decoding into a type like `HexBinary` that works with a reference to binary data.




## Assessed type

en/de-code
Messages cannot be retried if routing fails, as `route_incoming_messages` will read into an empty storage
Audit: 2024-08-axelar-network-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-axelar-network/blob/4572617124bed39add9025317d2c326acfef29f1/axelar-amplifier/contracts/axelarnet-gateway/src/contract/execute.rs#L77-L98


# Vulnerability details

## Impact

`route_incoming_messages` will be redundant as there is no storage being saved.

## Proof of Concept

In axelarnet-gateway, there is a function `call_contract` and a function `route_incoming_messages`. In the [docs](https://bright-ambert-2bd.notion.site/ITS-Token-Hub-External-dd30ee912d1e48e683d9488acf75e350#8f70ac2860de437aa729ec09a1d289f9), the ITS Hub calls `call_contract` on the axelarnet gateway.

This function creates a msg object, saves the incoming message into the INCOMING_MESSAGES storage, and calls route().

```
pub(crate) fn call_contract(
    ...
>   let msg = Message {
        cc_id: cc_id.clone(),
        source_address: Address::try_from(sender.clone().into_string())
            .expect("failed to convert sender address"),
        destination_chain,
        destination_address,
        payload_hash,
    };

>   state::save_incoming_msg(store, cc_id, &msg).change_context(Error::InvalidStoreAccess)?;

>   let (wasm_msg, events) = route(router, vec![msg.clone()])?;
```

There is a ? postfix operator behind `save_incoming_msg` and `route`. This is intended to propogate the error to the calling function.

If route() fails, then the state transition from `save_incoming_msg` will be reverted.

```
pub(crate) fn save_incoming_msg(
    storage: &mut dyn Storage,
    key: CrossChainId,
    msg: &Message,
) -> Result<(), Error> {
    match INCOMING_MESSAGES.may_load(storage, key.clone())? {
        Some(_) => Err(Error::MessageAlreadyExists(key)),
        None => INCOMING_MESSAGES
            .save(storage, key, msg)
            .map_err(Error::from),
    }
}
```

This is the `route()` function, which calls `router.route()`. If `router.route()` fails, it returns Err with a specified error.

```
fn route(
    router: &Router,
    msgs: Vec<Message>,
) -> Result<(WasmMsg, impl IntoIterator<Item = Event>), Error> {
    Ok((
>       router.route(msgs.clone()).ok_or(Error::RoutingFailed)?,
        msgs.into_iter()
            .map(|msg| AxelarnetGatewayEvent::Routing { msg }.into()),
    ))
}
```

There is another function which is `route_incoming_messages`. This function loads the message from storage through `may_load_incoming_msgs`, and checks if the `stored_msg` is the same as the message being routed. If there is no message, revert. The function then calls route().

```
pub(crate) fn route_incoming_messages(
    store: &mut dyn Storage,
    router: &Router,
    msgs: Vec<Message>,
) -> Result<Response, Error> {
    for msg in msgs.iter() {
>       let stored_msg = state::may_load_incoming_msg(store, &msg.cc_id)
            .change_context(Error::InvalidStoreAccess)?;

        match stored_msg {
>           Some(message) if msg != &message => {
                Err(report!(Error::MessageMismatch(msg.cc_id.clone())))
`isCommandExecuted` incorrectly returns that messages are executed
Audit: 2024-08-axelar-network-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-axelar-network/blob/4572617124bed39add9025317d2c326acfef29f1/axelar-gmp-sdk-solidity/contracts/gateway/BaseAmplifierGateway.sol#L115-L117
https://github.com/code-423n4/2024-08-axelar-network/blob/4572617124bed39add9025317d2c326acfef29f1/interchain-token-service/contracts/InterchainTokenService.sol#L386


# Vulnerability details

The `isCommandExecuted` function in the `BaseAmplifierGateway.sol` contract is designed as a backward-compatible function to check whether a message has been executed. However, it currently returns a boolean indicating that a message has been executed if it is not in the `MESSAGE_NONEXISTENT` state. This is problematic because messages that are approved (where the message state is the payload hash) will also be returned as executed, even though they have not been.

## Impact
This issue results in messages being incorrectly marked as executed, which can cause various functions to revert. For example, the `expressExecute` function in both the `InterchainTokenService.sol` and `AxelarExpressExecutable.sol` contracts will revert under the false assumption that the message has already been executed. In the `InterchainTokenService::expressExecute` function it would prevent users from "express" executing their interchain transfer messages.

## Proof of Concept
The `isCommandExecuted` function currently checks if the message is not equal to `MESSAGE_NONEXISTENT`:
```solidity
    /// @dev Message can be in one of three states: non-existent, approved, or executed
    /// Non-existent: The message has not been seen before. Equal to 0
    /// Approved: The message has been seen and approved. Equal to keccak256 of the message data
    /// Executed: The message has been seen and executed. Equal to 1
    bytes32 internal constant MESSAGE_NONEXISTENT = 0;
    bytes32 internal constant MESSAGE_EXECUTED = bytes32(uint256(1));

...

    function isCommandExecuted(bytes32 commandId) public view override returns (bool) {
@>      return _baseAmplifierGatewayStorage().messages[commandId] != MESSAGE_NONEXISTENT;
    }
```
In the `InterchainTokenService::expressExecute` function, the logic will revert if the message is approved while not executed:
```solidity
    function expressExecute(
        bytes32 commandId,
        string calldata sourceChain,
        string calldata sourceAddress,
        bytes calldata payload
    ) public payable whenNotPaused {
        ...

@>      if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted();

        ...
    }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Change the `isCommandExecuted` function so it correctly checks if a message is executed:
```diff
    function isCommandExecuted(bytes32 commandId) public view override returns (bool) {
-       return _baseAmplifierGatewayStorage().messages[commandId] != MESSAGE_NONEXISTENT;
+       return _baseAmplifierGatewayStorage().messages[commandId] == MESSAGE_EXECUTED;
    }
```
So
TokenBalance limit could be bypassed by deploying TokenManager
Audit: 2024-08-axelar-network-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-axelar-network/blob/main/axelar-amplifier/interchain-token-service/src/contract/execute.rs#L101-L406


# Vulnerability details

## Impact
If a chain is compromised, it can not transfer beyond its balance. However, there's a clever way to increase its balance arbitrarily.

A compromised chain can deploy token manager to a token controlled by the attacker on a different chain (or in the future, deploy ITS with their minter address). The balance on that chain would be untracked and the attacker can bridge an unlimited amount to the compromised chain to increase its balance. This is very severe because the main purpose of the hub and audit is to reduce damage to the wider ecosystem when a chain is compromised.

## Proof of Concept
* There are 3 chains: Ethereum, BNB and WoofChain.
* TokenA is an ITS on Ethereum and WoofChain, but not on BNB.
* TokenA balance on Ethereum is Untracked cause it is the original
* TokenA balance on WoofChain is 1,000
* WoofChain is compromised
* We can't bridge more than the 1,000 balance

So the goal is to create an untracked balance that we can bridge from. We don't want Ethereum, cause we would have to buy and own the real tokens. So instead
* Deploy FAKE Token on BNB. We can create UINT256_MAX totally owned by us
* Deploy token manager from WoofChain to BNB with the token address set to the FAKE token
* TokenA balance on BNB is now Untracked
* Transfer a billion TokenA from BNB to WoofChain (FAKE token would be burnt/locked)
* TokenA balance on WoofChain is now 1,000,000,1000
* Transfer the billion TokenA to Ethereum
* The hub confirms that it is less than the balance
* Ethereum mints the bridged billion TokenA

In fact, transferring to WoofChain to increase the tracked balance is unnecessary. We could have bridged from BNB to the original chain--Ethereum--if there's an ITS connection between them. According to the "Publicly Known Issues" section of README, The hub would be updated to not track balance when a minter is specified. This would enable the bug in a different function because the attacker could specify their address as the minter.

Examples of a compromise that could happen include
* Bad precompiles or revert handling: [Godwoken](https://medium.com/risk-dao/how-i-could-drain-an-entire-blockchain-post-mortem-on-a-bug-in-godwoken-chain-2451f83f72d2), [evmos](https://www.asymmetric.re/blog/evmos-precompile-state-commit-infinite-mint)
* Invalid transactions: [frontier](https://github.com/polkadot-evm/frontier/security/advisories/GHSA-hw4v-5x4h-c3xm)
* Consensus or malicious validator: [NEAR](https://hackenproof.com/blog/for-hackers/near-rewards-1-8-million-to-ethical-hackers-at-hackenproof#h2_2)
* Compiler bugs: [vyper](https://medium.com/rektify-ai/the-vyper-compiler-saga-unraveling-the-reentrancy-bug-that-shook-defi-86ade6c54265)
* Event spoofing
* Contract compromise: e.g admin key, take over, bug
* amplifier/routing architecture compromise: External Gat
Griefer could cause Economic damage/Temporary DoS on system due to lack of check on the source and destination address been the same
Audit: 2024-08-axelar-network-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-axelar-network/blob/4572617124bed39add9025317d2c326acfef29f1/axelar-amplifier/interchain-token-service/src/contract/execute.rs#L19-L79


# Vulnerability details

## Impact
The ITS system which is design to make use of the Axelarnet gateway contract to facilitate sending or receiving cross-chain message. Among other function the execute_message() which receives and executes an incoming ITS message verifies the source address, decodes the message sent, applies balance tracking and then forwards the message to the destination chain lack verification on the destination chain been the same with the source chain.
Thus a malicious user could exploit the absence of this verification in order to flood the Axelarnet gateway contract which are responsible for facilitating the sending or receiving of cross-chain messages with false transactions which could temporarily cause the system to halt execution of other transaction depending on the magnitude of the amount of false transaction sent by the malicious user.
This attack can be feasible because an attacker could cross-chain message calls on a trusted low-fee, low-latency network as the source chain/address to the same chain/address as many times as possible in which the gateway will have to act upon thereby consuming computational resources, bloating the storage which could potentially slow down the system and result to economic damage to the protocol.

## Proof of Concept
1. James which is a griefer creates a theoretically infinite series of transactions on Arbitrum, a low-fee, low-latency network as a source_chain with a address of "0x1" for example and destination_chain as Arbitrum and destination_address of "0x1" and a crafted message which is acceptable by the system.
2. Messages are sent to the system and this incoming ITS message are received by the execute_message function which validated the source and destination address to be from trusted source although they are from the same chain
3. The ITS Hub which make use of the Axelarnet gateway contract to facilitate sending this false cross-chain message continuously send this message back and forth to the same address
4. Meanwhile John requests a cross-chain message call; however, because the gateway must handle many of Jame’s transactions first, John has to wait an undefined amount of time for her transaction to be executed.

## Tools Used

## Recommended Mitigation Steps
Add this to execute_message() 
ensure!(source_address != destination_address);


## Assessed type

DoS
Token balance will not be updated on the source chain if the interchain token for this chain was not deployed via ITS Hub
Audit: 2024-08-axelar-network-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-axelar-network/blob/main/axelar-amplifier/interchain-token-service/src/contract/execute.rs#L112
https://github.com/code-423n4/2024-08-axelar-network/blob/main/axelar-amplifier/interchain-token-service/src/contract/execute.rs#L136


# Vulnerability details

## Impact

In the `execute.rs` contract of the ITS Hub, there is a function `apply_tracking_balances()` that tracks token amounts both on the source chain and on the destination chain. The problem is that when deploying interchain token on the destination chain, it only starts tracking balance for it ignoring the interchain token on the source chain. So when interchain transfer happens, the balance of the token on the source chain as it's untracked will be unchanged and the balance on the destination chain will be increased.

## Proof of Concept

Let's consider `apply_tracking_balances()` functionality:

https://github.com/code-423n4/2024-08-axelar-network/blob/main/axelar-amplifier/interchain-token-service/src/contract/execute.rs#L135-138
```
 ItsMessage::DeployInterchainToken { token_id, .. } => {
            start_token_balance(storage, token_id.clone(), destination_chain.clone(), true)
                .change_context(Error::InvalidStoreAccess)?
        }

```

When deploying the token on the destination chain, we start track balance for it on the destination chain:

https://github.com/code-423n4/2024-08-axelar-network/blob/main/axelar-amplifier/interchain-token-service/src/state.rs#L147-153
```
match TOKEN_BALANCES.may_load(storage, key.clone())? {
        None => {
            let initial_balance = if track_balance {
                TokenBalance::Tracked(Uint256::zero())
            } else {
                TokenBalance::Untracked
            };
```

The problem is that when we have the same token on the source chain and on the destination chain, we can make interchain transfer. But the balance on the source chain will be untracked if the chain was not deployed remotely:

1. Let's say we want to transfer some tokenA from chainX (native chain) to chainY.

2. We deploy tokenA on the native chainX so it does not go through ITS Hub and the balance of it is untracked.

3. Then we deploy tokenA on chainY remotely so the balance is tracked. Now when we implement interchain transfer, the balance on chainX will be unchanged as the tracking has not been started yet and on chainY it will be increased:

https://github.com/code-423n4/2024-08-axelar-network/blob/main/axelar-amplifier/interchain-token-service/src/state.rs#L177-188
```
 match token_balance {
        Some(TokenBalance::Tracked(balance)) => {
            let token_balance = if is_deposit {
                balance
                    .checked_add(amount)
                    .map_err(|_| Error::MissingConfig)?
            } else {
                balance
                    .checked_sub(amount)
                    .map_err(|_| Error::MissingConfig)?
            }
            .then(TokenBa
The token balance tracking logic for some supported tokens can be broken/sidestepped
Audit: 2024-08-axelar-network-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-axelar-network/blob/69c4f2c3fcefb1b8eb2129af9c3685a44ae5b6fe/axelar-amplifier/interchain-token-service/src/state.rs#L139-L164
 https://github.com/code-423n4/2024-08-axelar-network/blob/69c4f2c3fcefb1b8eb2129af9c3685a44ae5b6fe/axelar-amplifier/interchain-token-service/src/contract/execute.rs#L80-L90


# Vulnerability details


### Proof of Concept

First, per the readMe tokens that have multiple addresses are in scope, CC: https://github.com/code-423n4/2024-08-axelar-network/blob/69c4f2c3fcefb1b8eb2129af9c3685a44ae5b6fe/README.md#L158

|  |  |
|-------------|--------|
| [Multiple token addresses](https://github.com/d-xo/weird-erc20?tab=readme-ov-file#revert-on-zero-value-transfers) | In scope |

Now when executing a message, there is a need to apply the balance tracking logic and in the case of fresh deployments the balance tracking for the token on the destination chain when would be started, see https://github.com/code-423n4/2024-08-axelar-network/blob/69c4f2c3fcefb1b8eb2129af9c3685a44ae5b6fe/axelar-amplifier/interchain-token-service/src/contract/execute.rs#L80-L90

```rust
pub fn execute_message(
    deps: DepsMut,
    cc_id: CrossChainId,
    source_address: Address,
    payload: HexBinary,
) -> Result<Response, Error> {
// ..snip

        ItsMessage::DeployInterchainToken { token_id, .. } => {
            start_token_balance(storage, token_id.clone(), destination_chain.clone(), true)
                .change_context(Error::InvalidStoreAccess)?
        }
        ItsMessage::DeployTokenManager { token_id, .. } => {
            start_token_balance(storage, token_id.clone(), destination_chain.clone(), false)
                .change_context(Error::InvalidStoreAccess)?
        }
}

```

While starting the tracking we have a simple check that ensures that the token is not already registered https://github.com/code-423n4/2024-08-axelar-network/blob/69c4f2c3fcefb1b8eb2129af9c3685a44ae5b6fe/axelar-amplifier/interchain-token-service/src/state.rs#L139-L164

```rust
pub fn start_token_balance(
    storage: &mut dyn Storage,
    token_id: TokenId,
    chain: ChainName,
    track_balance: bool,
) -> Result<(), Error> {
    let key = TokenChainPair { token_id, chain };

    match TOKEN_BALANCES.may_load(storage, key.clone())? {
        None => {
            let initial_balance = if track_balance {
                TokenBalance::Tracked(Uint256::zero())
            } else {
                TokenBalance::Untracked
            };

            TOKEN_BALANCES
                .save(storage, key, &initial_balance)?
                .then(Ok)
        }
        Some(_) => Err(Error::TokenAlreadyRegistered {//@audit
            token_id: key.token_id,
            chain: key.chain,
        }),
    }
}
```

Issue however is that since we support tokens with multiple addresses this would allow different balance tracking logic to be applied and as such even if the token was previously registered via one of it's multiple