Open
Description
Component
Forge
Have you ensured that all of these are up to date?
- Foundry
- Foundryup
What version of Foundry are you on?
forge Version: 1.2.3-nightly
Commit SHA: bfc53de
Build Timestamp: 2025-06-19T06:03:45.745585000Z (1750313025)
Build Profile: maxperf
What version of Foundryup are you on?
Installed via nix
What command(s) is the bug in?
forge test
Operating System
macOS (Apple Silicon)
Describe the bug
If the mock call simulates a payable function, then when the address is called, the call succeeds but the balance is not transferred.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
import { Test } from "forge-std/Test.sol";
contract MockCallBugTest is Test {
function setUp() public { }
function testMockCallBug() public {
vm.deal(address(this), 1 ether);
address target = makeAddr("target");
uint256 value = 0.1 ether;
bytes memory data = "someData";
bytes memory resp = "mockedResponse";
assertEq(target.balance, 0, "Initial balance should be zero");
// Mock a call to the target address
vm.mockCall(target, value, data, resp);
// Make the call
(bool success, bytes memory returnData) = target.call{value: value}(data);
assertTrue(success, "Call should succeed");
assertEq(returnData, resp, "Return data should match");
// Check the target's balance after the call
assertEq(target.balance, 0, "Target balance should NOT be zero after mock call");
// BUG: This is a bug since the value is not transferred to the target but the call is not reverted.
// should pass: assertEq(target.balance, value, "Target balance should NOT be zero after mock call");
}
function testRealCall() public {
vm.deal(address(this), 1 ether);
address target = address(new RealCallContract());
uint256 value = 0.1 ether;
bytes memory data = abi.encodeCall(RealCallContract.payableFunction, ());
bytes memory resp = abi.encode("mockedResponse");
assertEq(target.balance, 0, "Initial balance should be zero");
// Make the real call
(bool success, bytes memory returnData) = target.call{value: value}(data);
assertTrue(success, "Call should succeed");
assertEq(returnData, resp, "Return data should match");
// Check the target's balance after the call
assertEq(target.balance, value, "Target balance should be equal to the value sent");
}
}
contract RealCallContract {
function payableFunction() external payable returns (bytes memory) {
return "mockedResponse";
}
}
I don't know why this occurs. I have verified this behavior with slight variations:
- Making sure
target
has some code length. (vm.etch(target, "mock")
) - Casting
target
into a payable address
Metadata
Metadata
Assignees
Type
Projects
Status
Backlog