Repository: foundry-rs/foundry (language: rust)

Issue:
`forge test --gas-report` Omits Fallback with Calldata
### Component

Forge

### Have you ensured that all of these are up to date?

- [x] Foundry
- [x] Foundryup

### What version of Foundry are you on?

forge Version: 1.0.0-nightly Commit SHA: db3d9fc95398450dbed83d4841042c62c155bcfc Build Timestamp: 2025-02-11T00:21:38.355325000Z (1739233298) Build Profile: maxperf

### What version of Foundryup are you on?

foundryup: 1.0.0

### What command(s) is the bug in?

forge test

### Operating System

macOS (Apple Silicon)

### Describe the bug

`forge test --gas-report` doesn't include gas usage for fallback functions invoked *with* calldata (e.g., `address(contract).call("hello")`), but *does* include fallback functions invoked *without* calldata (e.g., `address(contract).call("")`).  This makes gas reporting inaccurate.

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Counter {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }

    fallback() external {
        number++;
    }
}
```

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {Counter} from "../src/Counter.sol";

contract CounterTest is Test {
    Counter public counter;

    function setUp() public {
        counter = new Counter();
        // counter.setNumber(0);
    }

    function test_Fallback() external {
        (bool success,) = address(counter).call("hello");
        require(success);
    }
}

```



Discussion / hints:

Fix patch:
diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs
index f904bb46ecc5..d96e8d5dc6c8 100644
--- a/crates/evm/traces/src/decoder/mod.rs
+++ b/crates/evm/traces/src/decoder/mod.rs
@@ -366,7 +366,12 @@ impl CallTraceDecoder {
             let [func, ..] = &functions[..] else {
                 return DecodedCallTrace {
                     label,
-                    call_data: None,
+                    call_data: self.fallback_contracts.get(&trace.address).map(|_| {
+                        DecodedCallData {
+                            signature: "fallback()".to_string(),
+                            args: vec![cdata.to_string()],
+                        }
+                    }),
                     return_data: self.default_return_data(trace),
                 };
             };
@@ -376,7 +381,7 @@ impl CallTraceDecoder {
             let mut call_data = self.decode_function_input(trace, func);
             if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address) {
                 if !fallback_functions.contains(&func.signature()) {
-                    call_data.signature = "fallback()".into();
+                    call_data.signature = "fallback()".to_string();
                 }
             }
 
@@ -388,7 +393,8 @@ impl CallTraceDecoder {
         } else {
             let has_receive = self.receive_contracts.contains(&trace.address);
             let signature =
-                if cdata.is_empty() && has_receive { "receive()" } else { "fallback()" }.into();
+                if cdata.is_empty() && has_receive { "receive()" } else { "fallback()" }
+                    .to_string();
             let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] };
             DecodedCallTrace {
                 label,


Test patch:
diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs
index 59ab2ba08025..67327b0dc0b5 100644
--- a/crates/forge/tests/cli/cmd.rs
+++ b/crates/forge/tests/cli/cmd.rs
@@ -2725,6 +2725,105 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)
         );
 });
 
+// <https://github.com/foundry-rs/foundry/issues/9858>
+forgetest_init!(gas_report_fallback_with_calldata, |prj, cmd| {
+    prj.add_test(
+        "FallbackWithCalldataTest.sol",
+        r#"
+import {Test} from "forge-std/Test.sol";
+
+contract CounterWithFallback {
+    uint256 public number;
+
+    function increment() public {
+        number++;
+    }
+
+    fallback() external {
+        number++;
+    }
+}
+
+contract CounterWithFallbackTest is Test {
+    CounterWithFallback public counter;
+
+    function setUp() public {
+        counter = new CounterWithFallback();
+    }
+
+    function test_fallback_with_calldata() public {
+        (bool success,) = address(counter).call("hello");
+        require(success);
+    }
+}
+"#,
+    )
+    .unwrap();
+
+    cmd.args(["test", "--mt", "test_fallback_with_calldata", "-vvvv", "--gas-report"])
+        .assert_success()
+        .stdout_eq(str![[r#"
+[COMPILING_FILES] with [SOLC_VERSION]
+[SOLC_VERSION] [ELAPSED]
+Compiler run successful!
+
+Ran 1 test for test/FallbackWithCalldataTest.sol:CounterWithFallbackTest
+[PASS] test_fallback_with_calldata() ([GAS])
+Traces:
+  [48777] CounterWithFallbackTest::test_fallback_with_calldata()
+    ├─ [43461] CounterWithFallback::fallback(0x68656c6c6f)
+    │   └─ ← [Stop]
+    └─ ← [Stop]
+
+Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]
+
+╭----------------------------------------------------------------+-----------------+-------+--------+-------+---------╮
+| test/FallbackWithCalldataTest.sol:CounterWithFallback Contract |                 |       |        |       |         |
++=====================================================================================================================+
+| Deployment Cost                                                | Deployment Size |       |        |       |         |
+|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
+| 132471                                                         | 396             |       |        |       |         |
+|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
+|                                                                |                 |       |        |       |         |
+|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
+| Function Name                                                  | Min             | Avg   | Median | Max   | # Calls |
+|----------------------------------------------------------------+-----------------+-------+--------+-------+---------|
+| fallback                                                       | 43461           | 43461 | 43461  | 43461 | 1       |
+╰----------------------------------------------------------------+-----------------+-------+--------+-------+---------╯
+
+
+Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)
+
+"#]]);
+
+    cmd.forge_fuse()
+        .args(["test", "--mt", "test_fallback_with_calldata", "--gas-report", "--json"])
+        .assert_success()
+        .stdout_eq(
+            str![[r#"
+[
+  {
+    "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback",
+    "deployment": {
+      "gas": 132471,
+      "size": 396
+    },
+    "functions": {
+      "fallback()": {
+        "calls": 1,
+        "min": 43461,
+        "mean": 43461,
+        "median": 43461,
+        "max": 43461
+      }
+    }
+  }
+]
+"#]]
+            .is_json(),
+        );
+});
+
 // <https://github.com/foundry-rs/foundry/issues/9300>
 forgetest_init!(gas_report_size_for_nested_create, |prj, cmd| {
     prj.add_test(