xanoutas/solidity-security-findings huggingface dataset:
Hardcoded gas used in ERC20 queries allows for node crash from infinite recursion out-of-memory
Audit: 2024-11-nibiru-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-11-nibiru/blob/8ed91a036f664b421182e183f19f6cef1a4e28ea/x/evm/precompile/funtoken.go#L149
https://github.com/code-423n4/2024-11-nibiru/blob/8ed91a036f664b421182e183f19f6cef1a4e28ea/x/evm/precompile/funtoken.go#L285


# Vulnerability details

The `funtoken` precompile allows an EVM caller to access information about tokens that coexist in the Cosmos ("coin") and EVM ("ERC20") spaces.

Some operations performed by this precompile consist of EVM calls; for example, if we look at the `balance` method:
```go
File: funtoken.go
265: func (p precompileFunToken) balance(
266: 	start OnRunStartResult,
267: 	contract *vm.Contract,
268: ) (bz []byte, err error) {
---
285: 	erc20Bal, err := p.evmKeeper.ERC20().BalanceOf(funtoken.Erc20Addr.Address, addrEth, ctx)
286: 	if err != nil {
287: 		return
288: 	}
```
we see that for fetching the EVM info, it calls the `evmKeeper.ERC20().BalanceOf` function:
```go
File: erc20.go
125: func (e erc20Calls) BalanceOf(
126: 	contract, account gethcommon.Address,
127: 	ctx sdk.Context,
128: ) (out *big.Int, err error) {
129: 	return e.LoadERC20BigInt(ctx, e.ABI, contract, "balanceOf", account)
130: }
```
which in turn calls `LoadERC20BigInt`:
```go
File: erc20.go
222: func (k Keeper) LoadERC20BigInt(
223: 	ctx sdk.Context,
224: 	abi *gethabi.ABI,
225: 	contract gethcommon.Address,
226: 	methodName string,
227: 	args ...any,
228: ) (out *big.Int, err error) {
229: 	res, err := k.CallContract(
230: 		ctx,
231: 		abi,
232: 		evm.EVM_MODULE_ADDRESS, // @audit from
233: 		&contract,
234: 		false, // @audit commit = false
235: 		Erc20GasLimitQuery, // @audit 100_000
236: 		methodName,
237: 		args...,
238: 	)
239: 	if err != nil {
240: 		return nil, err
241: 	}
```

If we look closely to how this callback to the EVM is done, we see that the gas allowed for this call is hardcoded to `100_000` and is charged only after the call returned.

This is problematic because `100_000` is allocated regardless of the gas limit used to call the `funtoken` precompile, and this breaks the [core invariant of the 63/64 gas allocation](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md) that ultimately secures EVM implementation from infinite recursions.

While the `balance` example was described in detail, the same applies to the `Transfer` call in `sendToBank`:

```go
File: funtoken.go
109: func (p precompileFunToken) sendToBank(
110: 	startResult OnRunStartResult,
111: 	caller gethcommon.Address,
112: 	readOnly bool,
113: ) (bz []byte, err error) {
---
149: 	gotAmount, transferResp, err := p.evmKeeper.ERC20().Transfer(erc20, caller, transferTo, amount, ctx)
150: 	if err != nil {
151: 		return nil, fmt.Errorf("error in ERC20.transfer from caller to EVM account: %w", err)
152: 	}
```

The `Burn` call in `sendToBank` is instead secure because it only applies to ERC20 tokens deployed from Coins whose EVM contract is safe.

## Proof of Concept
For the `sendToBank/transfer` attack path, the i
IOracle.queryExchangeRate returns incorrect blockTimeMs
Audit: 2024-11-nibiru-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-11-nibiru/blob/8ed91a036f664b421182e183f19f6cef1a4e28ea/x/evm/precompile/oracle.go#L92


# Vulnerability details

The IOracle.queryExchangeRate offers the following lookup functionality, in analogy to other oracles like Chainlinks':

```Solidity
    /// @notice Queries the dated exchange rate for a given pair
    /// @param pair The asset pair to query. For example, "ubtc:uusd" is the
    /// USD price of BTC and "unibi:uusd" is the USD price of NIBI.
    /// @return price The exchange rate for the given pair
    /// @return blockTimeMs The block time in milliseconds when the price was
    /// last updated
    /// @return blockHeight The block height when the price was last updated
    /// @dev This function is view-only and does not modify state.
    function queryExchangeRate(
        string memory pair
    ) external view returns (uint256 price, uint64 blockTimeMs, uint64 blockHeight);
```

If we focus on the `blockTimeMs` returned value, this is meant to correspond to the time when the price was last updated
and, in analogy to how Chainlink oracles are typically used, is most likely to be used in staleness checks.

If we see how this is implemented, we see that values are passed through from OracleKeeper (L92):

```go
File: oracle.go
78: func (p precompileOracle) queryExchangeRate(
79: 	ctx sdk.Context,
80: 	method *gethabi.Method,
81: 	args []any,
82: ) (bz []byte, err error) {
83: 	pair, err := p.parseQueryExchangeRateArgs(args)
84: 	if err != nil {
85: 		return nil, err
86: 	}
87: 	assetPair, err := asset.TryNewPair(pair)
88: 	if err != nil {
89: 		return nil, err
90: 	}
91: 
92: 	price, blockTime, blockHeight, err := p.oracleKeeper.GetDatedExchangeRate(ctx, assetPair)
93: 	if err != nil {
94: 		return nil, err
95: 	}
96: 
97: 	return method.Outputs.Pack(price.BigInt(), uint64(blockTime), blockHeight)
98: }
```

However, the `blockTime` returned by `oracleKeeper.GetDatedExchangeRate` corresponds to the "current" block time and
therefore does not correspond to what should be returned by the precompile, that is the block time of the last price update.

The impact of this misalignment is quite severe, because since the returned value is the timestamp returned is always that
of the current block, the price will always look as freshly set when validated against `blockTime`, which is the most
common validation. Consequently, downstream contracts will likely always pass staleness checks on potentially extremely old prices.

## Proof of Concept
A simple coded PoC for this issue consists of updating the `TestOracle_HappyPath` test in 
`x/evm/precompile/oracle_test.go` to simulate a precompile call at a later block than that of the price update - 
changing L66 as follows will cause the test to fail:

```go
File: x/evm/precompile/oracle_test.go
57: func (s *OracleSuite) TestOracle_HappyPath() {
58: 	deps := evmtest.NewTestDeps()
59: 
60: 	s.T().Log("Query exchange rate")
61: 	{
62: 		deps.Ctx = deps
Upgraded Q -> 3 from #92 [1730425460664]
Audit: 2024-08-chakra-findings
Severity: HIGH

Judge has assessed an item in Issue #92 as 3 risk. The relevant finding follows:

 ## [07] The receive_cross_chain_msg function will never return false
## Link
github:[https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/cairo/handler/src/settlement.cairo#L354](https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/cairo/handler/src/settlement.cairo#L354)
github:[https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/cairo/handler/src/handler_erc20.cairo#L108C12-L108C35](https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/cairo/handler/src/handler_erc20.cairo#L108C12-L108C35)
### Description
When a cross-chain message is received, the settlement contract calls the `receive_cross_chain_msg` function of the `handler_erc20` to process the message. The execution status is set based on the result of `receive_cross_chain_msg`: if `receive_cross_chain_msg` returns `true`, the status is set to success; if it returns `false`, the status is set to failure.
```rust
        fn receive_cross_chain_msg(
            ref self: ContractState,
            cross_chain_msg_id: u256,
            from_chain: felt252,
            to_chain: felt252,
            from_handler: u256,
            to_handler: ContractAddress,
            sign_type: u8,
            signatures: Array<(felt252, felt252, bool)>,
            payload: Array<u8>,
            payload_type: u8,
        ) -> bool {
               
            ...
            // call handler receive_cross_chain_msg
            let handler = IHandlerDispatcher{contract_address: to_handler};
            let success = handler.receive_cross_chain_msg(cross_chain_msg_id, from_chain, to_chain, from_handler, to_handler , payload);

            let mut status = CrossChainMsgStatus::SUCCESS;
            if success{
                status = CrossChainMsgStatus::SUCCESS;
            }else{
                status = CrossChainMsgStatus::FAILED;
            }

            self.received_tx.write(cross_chain_msg_id, ReceivedTx{
                tx_id:cross_chain_msg_id,
                from_chain: from_chain,
                from_handler: from_handler,
                to_chain: to_chain,
                to_handler: to_handler,
                tx_status: status
            });

            ...
        }
```
However, the `receive_cross_chain_msg` function will never return false.
If an error occurs, it will cause a revert.
```rust
       fn receive_cross_chain_msg(ref self: ContractState, cross_chain_msg_id: u256, from_chain: felt252, to_chain: felt252,
        from_handler: u256, to_handler: ContractAddress, payload: Array<u8>) -> bool{
            assert(to_handler == get_contract_address(),'error to_handler');

            assert(self.settlement_address.read() == get_caller_address(), 'not settlement');

            assert(self.support_
Upgraded Q -> 3 from #553 [1730407169329]
Audit: 2024-08-chakra-findings
Severity: HIGH

Judge has assessed an item in Issue #553 as 3 risk. The relevant finding follows:

 ## [QA-18] Cross-chain txs always revert in case are invalid, instead of setting status = `FAILED`

**Issue Description:**

There are `else` clauses in Cairo contracts that are unreachable because all calls before them always return the same value, and the `else` will never be entered.

The first case is in `receive_cross_chain_msg`. A call made to `handler.receive_cross_chain_msg` will always return `true`, not allowing the `Status` to be `Failed`, since all cases that result in a cross-chain tx to fail are strictly asserted in the handler, and this else will never be reached.

[settlement.cairo#L387-L396](https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/cairo/handler/src/settlement.cairo#L356-L361)

```rust
fn receive_cross_chain_msg(
    ref self: ContractState,
    cross_chain_msg_id: u256,       
    from_chain: felt252,
    to_chain: felt252,
    from_handler: u256,
    to_handler: ContractAddress,
    sign_type: u8,
    signatures: Array<(felt252, felt252, bool)>,
    payload: Array<u8>,
    payload_type: u8,
) -> bool {
    ...
    

    // call handler receive_cross_chain_msg
    let handler = IHandlerDispatcher{contract_address: to_handler};
    let success = handler.receive_cross_chain_msg(cross_chain_msg_id, from_chain, to_chain, from_handler, to_handler , payload);

    let mut status = CrossChainMsgStatus::SUCCESS;
    if success{ 
        status = CrossChainMsgStatus::SUCCESS;
    }else{
        status = CrossChainMsgStatus::FAILED;
    }
```

[handler_erc20.cairo#L108-L136](https://github.com/code-423n4/2024-08-chakra/blob/main/cairo/handler/src/handler_erc20.cairo#L108-L136)

```rust
fn receive_cross_chain_msg(ref self: ContractState, cross_chain_msg_id: u256, from_chain: felt252, to_chain: felt252,
from_handler: u256, to_handler: ContractAddress, payload: Array<u8>) -> bool{
    assert(to_handler == get_contract_address(),'error to_handler');

    assert(self.settlement_address.read() == get_caller_address(), 'not settlement');

    assert(self.support_handler.read((from_chain, from_handler)) && 
            self.support_handler.read((to_chain, contract_address_to_u256(to_handler))), 'not support handler');

    let message :Message= decode_message(payload);
    let payload_type = message.payload_type;
    assert(payload_type == PayloadType::ERC20, 'payload type not erc20');
    let payload_transfer = message.payload;
    let transfer = decode_transfer(payload_transfer);
    assert(transfer.method_id == ERC20Method::TRANSFER, 'ERC20Method must TRANSFER');
    let erc20 = IERC20MintDispatcher{contract_address: self.token_address.read()};
    let token = IERC20Dispatcher{contract_address: self.token_address.read()};
    if self.mode.read() == SettlementMode::MintBurn{
        erc20.mint_to(u256_to_contract_address(transfer.to), transfer.amount);
Upgraded Q -> 3 from #544 [1730393923071]
Audit: 2024-08-chakra-findings
Severity: HIGH

Judge has assessed an item in Issue #544 as 3 risk. The relevant finding follows:

 ## [NC-3] Wrong event parameters in `cross_chain_erc20_settlement`
The `cross_chain_erc20_settlement` function of the `ChakraSettlementHandler` contract the following event on success:
```solidity
emit CrossChainLocked(txid, msg.sender, to, chain, to_chain, address(this), to_token, amount, mode);
```
[ChakraSettlementHandler.sol#L218](https://github.com/code-423n4/2024-08-chakra/blob/f61c899c22492bdf5bdcb07cdb62ea9b4cd38825/solidity/handler/contracts/ChakraSettlementHandler.sol#L218)

The `CrossChainLocked` event is defined as:
```solidity
event CrossChainLocked(uint256 txid,
            address from,
            uint256 to,
            string from_chain,
            string to_chain,
            address from_token,
            uint256 to_token,
            uint256 amount,
            SettlementMode mode) 
```
[BaseSettlementHandler.sol#L41-L51](https://github.com/code-423n4/2024-08-chakra/blob/f61c899c22492bdf5bdcb07cdb62ea9b4cd38825/solidity/handler/contracts/BaseSettlementHandler.sol#L41-L51)

According to the [docs](https://github.com/code-423n4/2024-08-chakra/blob/f61c899c22492bdf5bdcb07cdb62ea9b4cd38825/solidity/handler/contracts/libraries/ERC20Payload.sol#L25), `from_token` is *The token address on the source chain.* However, instead of emitting the `token` storage variable, the event emits the address of a handler contract (`address(this)`). 

**Recommended Mitigation Steps**
Replace `address(this)` in [ChakraSettlementHandler.sol#L218](https://github.com/code-423n4/2024-08-chakra/blob/f61c899c22492bdf5bdcb07cdb62ea9b4cd38825/solidity/handler/contracts/ChakraSettlementHandler.sol#L218) with `token`.
Upgraded Q -> 3 from #558 [1730393580619]
Audit: 2024-08-chakra-findings
Severity: HIGH

Judge has assessed an item in Issue #558 as 3 risk. The relevant finding follows:

 ## [L-07] No check of message pending status in receive_cross_chain_callback 
### Proof of Concept
https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/cairo/handler/src/handler_erc20.cairo#L138-L165
There is no check of message pending status in handler_erc20.cairo::receive_cross_chain_callback function.
### Recommended Mitigation Steps
```diff
            assert(to_handler == get_contract_address(),'error to_handler');

            assert(self.settlement_address.read() == get_caller_address(), 'not settlement');

            assert(self.support_handler.read((from_chain, from_handler)) && 
                    self.support_handler.read((to_chain, contract_address_to_u256(to_handler))), 'not support handler');
            
+           assert(self.created_tx.read(cross_chain_msg_id).tx_status == CrossChainTxStatus::PENDING, 'tx status error');
```
Upgraded Q -> 3 from #558 [1730393513289]
Audit: 2024-08-chakra-findings
Severity: HIGH

Judge has assessed an item in Issue #558 as 3 risk. The relevant finding follows:

 ## [L-02] wrong address stored in create_cross_txs mapping
### Proof of Concept
https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/solidity/handler/contracts/ChakraSettlementHandler.sol#L154-L164
```
    struct CreatedCrossChainTx {
       uint256 txid;
        string from_chain;
        string to_chain;
        address from;
        uint256 to;
        address from_token;
        uint256 to_token;
        uint256 amount;
        CrossChainTxStatus status;
    }
```
According to the definition of struct CreatedCrossChainTx, `address(this)` should be changed to `token`.
### Recommended Mitigation Steps
```diff
            create_cross_txs[txid] = CreatedCrossChainTx(
-               txid, chain, to_chain, msg.sender, to, address(this), to_token, amount, CrossChainTxStatus.Pending
+               txid, chain, to_chain, msg.sender, to, token, to_token, amount, CrossChainTxStatus.Pending
            );
```

## [L-04] wrong emit parameter about CrossChainLocked event
### Proof of Concept
https://github.com/code-423n4/2024-08-chakra/blob/d0d45ae1d26ca1b87034e67180fac07ce9642fd9/solidity/handler/contracts/ChakraSettlementHandler.sol#L212-L222
```
    event CrossChainLocked(
        uint256 indexed txid,
        address indexed from,
        uint256 indexed to,
        string from_chain,
        string to_chain,
        address from_token,
        uint256 to_token,
        uint256 amount,
        SettlementMode mode
    );
```
According to the definition of event CrossChainLocked, `address(this)` should be changed to `token`.
### Recommended Mitigation Steps
```diff
-       emit CrossChainLocked(txid, msg.sender, to, chain, to_chain, address(this), to_token, amount, mode);
+       emit CrossChainLocked(txid, msg.sender, to, chain, to_chain, token, to_token, amount, mode);
```
Upgraded Q -> 3 from #130 [1730393165667]
Audit: 2024-08-chakra-findings
Severity: HIGH

Judge has assessed an item in Issue #130 as 3 risk. The relevant finding follows:

 ## [01] `ChakraSettlementHandler.cross_chain_erc20_settlement` function incorrectly uses `address(this)` as data for `CreatedCrossChainTx` struct's `from_token` when setting `create_cross_txs[txid]`

### Description
The `ChakraSettlementHandler.cross_chain_erc20_settlement` function sets `create_cross_txs[txid]` by using `address(this)` as the data for the `CreatedCrossChainTx` struct's `from_token`. However, this is incorrect because `address(this)` is the address of the `ChakraSettlementHandler` contract and is not the address of the `from_token`. When there is a need to retrieve `create_cross_txs[txid]`'s `from_token` for a corresponding `txid`, the `ChakraSettlementHandler` contract's address would be mistakenly considered as such `from_token`, which can cause the protocol to misbehave.

https://github.com/code-423n4/2024-08-chakra/blob/f61c899c22492bdf5bdcb07cdb62ea9b4cd38825/solidity/handler/contracts/ChakraSettlementHandler.sol#L111-L223
```solidity
    function cross_chain_erc20_settlement(
        string memory to_chain,
        uint256 to_handler,
        uint256 to_token,
        uint256 to,
        uint256 amount
    ) external {
        ...

        {
            // Save the cross chain tx
            create_cross_txs[txid] = CreatedCrossChainTx(
                txid,
                chain,
                to_chain,
                msg.sender,
                to,
@>              address(this),
                to_token,
                amount,
                CrossChainTxStatus.Pending
            );
        }

        ...
    }
```

https://github.com/code-423n4/2024-08-chakra/blob/f61c899c22492bdf5bdcb07cdb62ea9b4cd38825/solidity/handler/contracts/BaseSettlementHandler.sol#L53-L63
```solidity
    struct CreatedCrossChainTx {
        uint256 txid;
        string from_chain;
        string to_chain;
        address from;
        uint256 to;
@>      address from_token;
        uint256 to_token;
        uint256 amount;
        CrossChainTxStatus status;
    }
```

### Recommended Mitigation
The `ChakraSettlementHandler.cross_chain_erc20_settlement` function can be updated to use the `ChakraSettlementHandler` contract's `token` as the data for the `CreatedCrossChainTx` struct's `from_token` when setting `create_cross_txs[txid]`.

## [02] `ChakraSettlementHandler.cross_chain_erc20_settlement` function incorrectly uses `address(this)` as data for `CrossChainLocked` event when emitting such event

### Description
The `ChakraSettlementHandler.cross_chain_erc20_settlement` function emits the `CrossChainLocked` event through using `address(this)` as the data for such event's `from_token`. Yet, `address(this)` is the `ChakraSettlementHandler` contract's address, which is not such `from_token` address. Thus, the `from_token` data emitted for the `CrossChainLocked` event is incorrect. When consuming such `Cro
Upgraded Q -> 3 from #127 [1730393047774]
Audit: 2024-08-chakra-findings
Severity: HIGH

Judge has assessed an item in Issue #127 as 3 risk. The relevant finding follows:

 # Emitting an event using unverified parameter `payload_type`

## Links to affected code

[https://github.com/code-423n4/2024-08-chakra/blob/abef77d95866f2fec93270491fc5abc9ab14f86d/cairo/handler/src/settlement.cairo#L340](https://github.com/code-423n4/2024-08-chakra/blob/abef77d95866f2fec93270491fc5abc9ab14f86d/cairo/handler/src/settlement.cairo#L340)

[https://github.com/code-423n4/2024-08-chakra/blob/abef77d95866f2fec93270491fc5abc9ab14f86d/cairo/handler/src/settlement.cairo#L380](https://github.com/code-423n4/2024-08-chakra/blob/abef77d95866f2fec93270491fc5abc9ab14f86d/cairo/handler/src/settlement.cairo#L380)

## Impact

Can emit event with wrong information.

## Proof of Concept

An event is emitted using an unverified parameter `payload_type`.

```rust
fn receive_cross_chain_msg(
    ref self: ContractState,
    cross_chain_msg_id: u256,
    from_chain: felt252,
    to_chain: felt252,
    from_handler: u256,
    to_handler: ContractAddress,
    sign_type: u8,
    signatures: Array<(felt252, felt252, bool)>,
    payload: Array<u8>,
@>  payload_type: u8,
) -> bool {
    assert(to_chain == self.chain_name.read(), 'error to_chain');

    // verify signatures
@>  let mut message_hash: felt252 = LegacyHash::hash(from_chain, (cross_chain_msg_id, to_chain, from_handler, to_handler));
    let payload_span = payload.span();
    let mut i = 0;
    loop {
        if i > payload_span.len()-1{
            break;
        }
        message_hash = LegacyHash::hash(message_hash, * payload_span.at(i));
        i += 1;
    };
    self.check_chakra_signatures(message_hash, signatures);

    ...

    // emit event
    self.emit(CrossChainHandleResult{
        cross_chain_settlement_id: cross_chain_msg_id,
        from_chain: to_chain,
        from_handler: to_handler,
        to_chain: from_chain,
        to_handler: from_handler,
        cross_chain_msg_status: status,
@>      payload_type: payload_type
    });
    return true;
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Use the parsed value of the payload instead of the `payload_type` parameter.