foundry icon indicating copy to clipboard operation
foundry copied to clipboard

test cannot construct a proxy object while gas metering is on

Open drortirosh opened this issue 1 year ago • 1 comments

Component

Forge

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

  • [ ] Foundry
  • [ ] Foundryup

What version of Foundry are you on?

forge 0.2.0 (34f684d 2023-11-01T22:19:24.989266000Z)

What command(s) is the bug in?

forge test

Operating System

None

Describe the bug

The following code reverts in setup. It does run OK gas metering is NOT disabled (which means, we can't filter out meter gas only on inner call)

pragma solidity ^0.8.17;

import {Test} from "forge-std/Test.sol";
import "forge-std/Vm.sol";
import {console} from "hardhat/console.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

contract MyObject is Ownable, UUPSUpgradeable, Initializable {

    constructor() {
        _disableInitializers();
    }

    function initialize(address anOwner) public virtual initializer {
        _transferOwnership(anOwner);
    }

    function _authorizeUpgrade(address newImplementation) internal override {}
}

contract TestProxy is Test {

    MyObject singleton = new MyObject();

    function setUp() public {
        //with this line enabled, the test revert. comment this line, and the test will pass
        vm.pauseGasMetering();
    }

    function newObj(address owner, uint salt) internal returns (MyObject) {
        return MyObject(address(new ERC1967Proxy{salt: bytes32(salt)}(
            address(singleton),
            abi.encodeCall(MyObject.initialize, (owner))
        )));
    }

    function test_send1() public {
        MyObject obj = newObj(msg.sender, 0);
        vm.resumeGasMetering();
        //this is the method we want to test (and meter for gas)
        obj.owner();
    }
}

This is the test result when running with forge test -vvv

[FAIL. Reason: EvmError: Revert] test_send1() (gas: 9223372036854754743)
Traces:
  [0] TestProxy::test_send1()
    ├─ [0] → new <Unknown>@0x3E6e1b3d0069A017c50Cc1d758207728ABb19fdc
    │   ├─ emit Upgraded(implementation: MyObject: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f])
    │   ├─ [0] MyObject::initialize(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38]) [delegatecall]
    │   │   ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38])
    │   │   ├─ emit Initialized(version: 1)
    │   │   └─ ← ()
    │   └─ ← 0 bytes of code   <<<< this is a constructor call that returns ZERO BYTES
    └─ ← "EvmError: Revert"

drortirosh avatar Nov 12 '23 16:11 drortirosh

I just ran into this trying to deploy a contract that deploys a (non-proxy) implementation contract for clones

I suspect that this is an issue with nested-creates in general

The pauseGasMetering cheatcode has a special case for temporarily re-enabling during contract creates – I suspect that it does not handle nested creates correctly

See code here: https://github.com/foundry-rs/foundry/blob/ff391b5fbef93a5dc6d28055501d5bdc39aee1c8/crates/cheatcodes/src/inspector.rs#L204

This should probably be an Option<Vec<Option<Gas>>> for each level of CREATE

emo-eth avatar Feb 12 '24 19:02 emo-eth