-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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.5.1-stable Commit SHA: b0a9dd9 Build Timestamp: 2025-12-22T11:41:09.812070000Z (1766403669) Build Profile: maxperf
What version of Foundryup are you on?
foundryup: 1.0.1
What command(s) is the bug in?
forge test
Operating System
macOS (Apple Silicon)
Describe the bug
I am observing inconsistent behaviour of vm.mockFunction. Given there is no cheatcode to clear mocked functions, I am using vm.mockFunction(addr1, addr1, ...) to effectively clear mocked functions. However, this results in an infinite loop when used against proxies.
In the POC below, you'll see that test_mockFunction_proxy results in an infinite loop, while test_mockFunction_impl works fine. The only solution I found to "clear" mocked functions on proxies is to redirect to implementation (see test_mockFunction_proxy_impl).
test_mockFunction_impl succeeding and test_mockFunction_proxy failing does not make sense to me, since both tests do the same thing: use address' bytecode to cancel previous mocks.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {Test} from 'forge-std/Test.sol';
contract Impl {
function foo() public pure returns (string memory) {
return 'foo';
}
}
contract Proxy {
address public impl;
constructor(address impl_) {
impl = impl_;
}
fallback() external {
_delegate(impl);
}
// code from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/239795bea728c8dca4deb6c66856dd58a6991112/contracts/proxy/Proxy.sol#L22-L45
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0x00, 0x00, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0x00, calldatasize(), 0x00, 0x00)
// Copy the returned data.
returndatacopy(0x00, 0x00, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0x00, returndatasize())
}
default {
return(0x00, returndatasize())
}
}
}
}
contract MockImpl {
function foo() public pure returns (string memory) {
return 'bar';
}
}
contract MockFunctionTest is Test {
Impl public impl;
Impl public proxy;
MockImpl public mockImpl;
function setUp() public {
impl = new Impl();
proxy = Impl(address(new Proxy(address(impl))));
mockImpl = new MockImpl();
}
function test_mockFunction_impl() public {
vm.mockFunction(address(impl), address(mockImpl), abi.encodeWithSelector(Impl.foo.selector));
assertEq(impl.foo(), 'bar');
vm.mockFunction(address(impl), address(impl), abi.encodeWithSelector(Impl.foo.selector));
assertEq(impl.foo(), 'foo');
}
function test_mockFunction_proxy() public {
vm.mockFunction(address(proxy), address(mockImpl), abi.encodeWithSelector(Impl.foo.selector));
assertEq(proxy.foo(), 'bar');
vm.mockFunction(address(proxy), address(proxy), abi.encodeWithSelector(Impl.foo.selector));
assertEq(proxy.foo(), 'foo');
}
function test_mockFunction_proxy_impl() public {
vm.mockFunction(address(proxy), address(mockImpl), abi.encodeWithSelector(Impl.foo.selector));
assertEq(proxy.foo(), 'bar');
vm.mockFunction(address(proxy), address(impl), abi.encodeWithSelector(Impl.foo.selector));
assertEq(proxy.foo(), 'foo');
}
}
Metadata
Metadata
Assignees
Labels
Type
Projects
Status