//! Regression tests for specific GitHub issues

use foundry_test_utils::str;

// https://github.com/foundry-rs/foundry/issues/3055
forgetest_init!(issue_3055, |prj, cmd| {
    prj.add_test(
        "Issue3055.t.sol",
        r#"
import "forge-std/Test.sol";

/// forge-config: default.assertions_revert = false
contract Issue3055Test is Test {
    function test_snapshot() external {
        uint256 snapshotId = vm.snapshotState();
        assertEq(uint256(0), uint256(1));
        vm.revertToState(snapshotId);
    }

    function test_snapshot2() public {
        uint256 snapshotId = vm.snapshotState();
        assertTrue(false);
        vm.revertToState(snapshotId);
        assertTrue(true);
    }

    function test_snapshot3(uint256) public {
        vm.expectRevert();
        // Call exposed_snapshot3() using this to perform an external call,
        // so we can properly test for reverts.
        this.exposed_snapshot3();
    }

    function exposed_snapshot3() public {
        uint256 snapshotId = vm.snapshotState();
        assertTrue(false);
        vm.revertToState(snapshotId);
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 3 tests for test/Issue3055.t.sol:Issue3055Test
[FAIL] test_snapshot() ([GAS])
[FAIL] test_snapshot2() ([GAS])
[FAIL: next call did not revert as expected; counterexample: calldata=[..] args=[..] test_snapshot3(uint256) (runs: 0, [AVG_GAS])
Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 3 failed, 0 skipped (3 total tests)

Failing tests:
Encountered 3 failing tests in test/Issue3055.t.sol:Issue3055Test
[FAIL] test_snapshot() ([GAS])
[FAIL] test_snapshot2() ([GAS])
[FAIL: next call did not revert as expected; counterexample: calldata=[..] args=[..] test_snapshot3(uint256) (runs: 0, [AVG_GAS])

Encountered a total of 3 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 3 failed tests
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

[SEED] (use `--fuzz-seed` to reproduce)

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/3189
forgetest_init!(issue_3189, |prj, cmd| {
    prj.add_test(
        "Issue3189.t.sol",
        r#"
import "forge-std/Test.sol";

contract MyContract {
    function foo(uint256 arg) public returns (uint256) {
        return arg + 2;
    }
}

contract MyContractUser is Test {
    MyContract immutable myContract;

    constructor() {
        myContract = new MyContract();
    }

    function foo(uint256 arg) public returns (uint256 ret) {
        ret = myContract.foo(arg);
        assertEq(ret, arg + 1, "Invariant failed");
    }
}

contract Issue3189Test is Test {
    function testFoo() public {
        MyContractUser user = new MyContractUser();
        user.foo(123);
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
...
Ran 1 test for test/Issue3189.t.sol:Issue3189Test
[FAIL: Invariant failed: 125 != 124] testFoo() ([GAS])
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/Issue3189.t.sol:Issue3189Test
[FAIL: Invariant failed: 125 != 124] testFoo() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/3596
forgetest_init!(issue_3596, |prj, cmd| {
    prj.add_test(
        "Issue3596.t.sol",
        r#"
import "forge-std/Test.sol";

contract Issue3596Test is Test {
    function testDealTransfer() public {
        address addr = vm.addr(1337);
        vm.startPrank(addr);
        vm.deal(addr, 20000001 ether);
        payable(address(this)).transfer(20000000 ether);

        Nested nested = new Nested();
        nested.doStuff();
        vm.stopPrank();
    }
}

contract Nested {
    function doStuff() public {
        doRevert();
    }

    function doRevert() public {
        revert("This fails");
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
...
Ran 1 test for test/Issue3596.t.sol:Issue3596Test
[FAIL: EvmError: Revert] testDealTransfer() ([GAS])
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/Issue3596.t.sol:Issue3596Test
[FAIL: EvmError: Revert] testDealTransfer() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/2851
forgetest_init!(issue_2851, |prj, cmd| {
    prj.add_test(
        "Issue2851.t.sol",
        r#"
import "forge-std/Test.sol";

contract Backdoor {
    uint256 public number = 1;

    function backdoor(uint256 newNumber) public payable {
        uint256 x = newNumber - 1;
        if (x == 6912213124124531) {
            number = 0;
        }
    }
}

contract Issue2851Test is Test {
    Backdoor back;

    function setUp() public {
        back = new Backdoor();
    }

    /// forge-config: default.fuzz.seed = "111"
    function invariantNotZero() public {
        assertEq(back.number(), 1);
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
...
Ran 1 test for test/Issue2851.t.sol:Issue2851Test
[FAIL: assertion failed: 0 != 1]
...
 invariantNotZero() ([..])
...
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)
...
"#]]);
});

// https://github.com/foundry-rs/foundry/issues/6170
forgetest_init!(issue_6170, |prj, cmd| {
    prj.add_test(
        "Issue6170.t.sol",
        r#"
import "forge-std/Test.sol";

contract Emitter {
    event Values(uint256 indexed a, uint256 indexed b);

    function plsEmit(uint256 a, uint256 b) external {
        emit Values(a, b);
    }
}

contract Issue6170Test is Test {
    event Values(uint256 indexed a, uint256 b);

    Emitter e = new Emitter();

    function test() public {
        vm.expectEmit(true, true, false, true);
        emit Values(69, 420);
        e.plsEmit(69, 420);
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Issue6170.t.sol:Issue6170Test
[FAIL: Values indexed topic count mismatch: expected 1, got 2] test() ([GAS])
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests)

Failing tests:
Encountered 1 failing test in test/Issue6170.t.sol:Issue6170Test
[FAIL: Values indexed topic count mismatch: expected 1, got 2] test() ([GAS])

Encountered a total of 1 failing tests, 0 tests succeeded

Tip: Run `forge test --rerun` to retry only the 1 failed test
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/6355
forgetest_init!(issue_6355, |prj, cmd| {
    prj.add_test(
        "Issue6355.t.sol",
        r#"
import "forge-std/Test.sol";

contract Issue6355Test is Test {
    uint256 snapshotId;
    Target targ;

    function setUp() public {
        snapshotId = vm.snapshotState();
        targ = new Target();
    }

    // this non-deterministically fails sometimes and passes sometimes
    function test_shouldPass() public {
        assertEq(2, targ.num());
    }

    // always fails
    function test_shouldFailWithRevertToState() public {
        assertEq(3, targ.num());
        vm.revertToState(snapshotId);
    }

    // always fails
    function test_shouldFail() public {
        assertEq(3, targ.num());
    }
}

contract Target {
    function num() public pure returns (uint256) {
        return 2;
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
...
Ran 3 tests for test/Issue6355.t.sol:Issue6355Test
[FAIL: assertion failed: 3 != 2] test_shouldFail() ([GAS])
[FAIL: assertion failed: 3 != 2] test_shouldFailWithRevertToState() ([GAS])
[PASS] test_shouldPass() ([GAS])
Suite result: FAILED. 1 passed; 2 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 2 failed, 0 skipped (3 total tests)

Failing tests:
Encountered 2 failing tests in test/Issue6355.t.sol:Issue6355Test
[FAIL: assertion failed: 3 != 2] test_shouldFail() ([GAS])
[FAIL: assertion failed: 3 != 2] test_shouldFailWithRevertToState() ([GAS])

Encountered a total of 2 failing tests, 1 tests succeeded

Tip: Run `forge test --rerun` to retry only the 2 failed tests
Tip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/3347
forgetest_init!(issue_3347, |prj, cmd| {
    prj.add_test(
        "Issue3347.t.sol",
        r#"
import "forge-std/Test.sol";

contract Issue3347Test is Test {
    event log2(uint256, uint256);

    function test() public {
        emit log2(1, 2);
    }
}
"#,
    );

    cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 1 test for test/Issue3347.t.sol:Issue3347Test
[PASS] test() ([GAS])
Traces:
  [..] Issue3347Test::test()
    ├─ emit log2(: 1, : 2)
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/6501
// Make sure we decode Hardhat-style `console.log`s correctly, in both logs and traces.
forgetest_init!(issue_6501, |prj, cmd| {
    prj.add_test(
        "Issue6501.t.sol",
        r#"
import "forge-std/Test.sol";

contract Issue6501Test is Test {
    function test_hhLogs() public {
        console.log("a");
        console.log(uint256(1));
        console.log("b", uint256(2));
    }
}
"#,
    );

    cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#"
...
Ran 1 test for test/Issue6501.t.sol:Issue6501Test
[PASS] test_hhLogs() ([GAS])
Logs:
  a
  1
  b 2

Traces:
  [..] Issue6501Test::test_hhLogs()
    ├─ [0] console::log("a") [staticcall]
    │   └─ ← [Stop]
    ├─ [0] console::log(1) [staticcall]
    │   └─ ← [Stop]
    ├─ [0] console::log("b", 2) [staticcall]
    │   └─ ← [Stop]
    └─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests)

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/8383
forgetest_init!(issue_8383, |prj, cmd| {
    prj.update_config(|config| {
        config.optimizer = Some(true);
        config.optimizer_runs = Some(200);
    });
    prj.add_test(
        "Issue8383.t.sol",
        r#"
import "forge-std/Test.sol";

contract Issue8383Test is Test {
    address internal _verifier;

    mapping(bytes32 => bool) internal _vectorTested;
    mapping(bytes32 => bool) internal _vectorResult;

    function setUp() public {
        _verifier = address(new P256Verifier());
    }

    function _verifyViaVerifier(bytes32 hash, uint256 r, uint256 s, uint256 x, uint256 y) internal returns (bool) {
        return _verifyViaVerifier(hash, bytes32(r), bytes32(s), bytes32(x), bytes32(y));
    }

    function _verifyViaVerifier(bytes32 hash, bytes32 r, bytes32 s, bytes32 x, bytes32 y) internal returns (bool) {
        bytes memory payload = abi.encode(hash, r, s, x, y);
        if (uint256(y) & 0xff == 0) {
            bytes memory truncatedPayload = abi.encodePacked(hash, r, s, x, bytes31(y));
            _verifierCall(truncatedPayload);
        }
        if (uint256(keccak256(abi.encode(payload, "1"))) & 0x1f == 0) {
            uint256 r = uint256(keccak256(abi.encode(payload, "2")));
            payload = abi.encodePacked(payload, new bytes(r & 0xff));
        }
        bytes32 payloadHash = keccak256(payload);
        if (_vectorTested[payloadHash]) return _vectorResult[payloadHash];
        _vectorTested[payloadHash] = true;
        return (_vectorResult[payloadHash] = _verifierCall(payload));
    }

    function _verifierCall(bytes memory payload) internal returns (bool) {
        (bool success, bytes memory result) = _verifier.call(payload);
        return abi.decode(result, (bool));
    }

    function testP256VerifyOutOfBounds() public {
        vm.pauseGasMetering();
        uint256 p = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF;
        _verifyViaVerifier(bytes32(0), 1, 1, 1, 1);
        _verifyViaVerifier(bytes32(0), 1, 1, 0, 1);
        _verifyViaVerifier(bytes32(0), 1, 1, 1, 0);
        _verifyViaVerifier(bytes32(0), 1, 1, 1, p);
        _verifyViaVerifier(bytes32(0), 1, 1, p, 1);
        _verifyViaVerifier(bytes32(0), 1, 1, p - 1, 1);
        vm.resumeGasMetering();
    }
}

contract P256Verifier {
    uint256 private constant GX = 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296;
    uint256 private constant GY = 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5;
    uint256 private constant P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF; // `A = P - 3`.
    uint256 private constant N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551;
    uint256 private constant B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B;

    fallback() external payable {
        assembly {
            // For this implementation, we will use the memory without caring about
            // the free memory pointer or zero pointer.
            // The slots `0x00`, `0x20`, `0x40`, `0x60`, will not be accessed for the `Points[16]` array,
            // and can be used for storing other variables.

            mstore(0x40, P) // Set `0x40` to `P`.

            function jAdd(x1, y1, z1, x2, y2, z2) -> x3, y3, z3 {
                if iszero(z1) {
                    x3 := x2
                    y3 := y2
                    z3 := z2
                    leave
                }
                if iszero(z2) {
                    x3 := x1
                    y3 := y1
                    z3 := z1
                    leave
                }
                let p := mload(0x40)
                let zz1 := mulmod(z1, z1, p)
                let zz2 := mulmod(z2, z2, p)
                let u1 := mulmod(x1, zz2, p)
                let u2 := mulmod(x2, zz1, p)
                let s1 := mulmod(y1, mulmod(zz2, z2, p), p)
                let s2 := mulmod(y2, mulmod(zz1, z1, p), p)
                let h := addmod(u2, sub(p, u1), p)
                let hh := mulmod(h, h, p)
                let hhh := mulmod(h, hh, p)
                let r := addmod(s2, sub(p, s1), p)
                x3 := addmod(addmod(mulmod(r, r, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1, hh, p), p)), p)
                y3 := addmod(mulmod(r, addmod(mulmod(u1, hh, p), sub(p, x3), p), p), sub(p, mulmod(s1, hhh, p)), p)
                z3 := mulmod(h, mulmod(z1, z2, p), p)
            }

            function setJPoint(i, x, y, z) {
                // We will multiply by `0x80` (i.e. `shl(7, i)`) instead
                // since the memory expansion costs are cheaper than doing `mul(0x60, i)`.
                // Also help combine the lookup expression for `u1` and `u2` in `jMultShamir`.
                i := shl(7, i)
                mstore(i, x)
                mstore(add(i, returndatasize()), y)
                mstore(add(i, 0x40), z)
            }

            function setJPointDouble(i, j) {
                j := shl(7, j)
                let x := mload(j)
                let y := mload(add(j, returndatasize()))
                let z := mload(add(j, 0x40))
                let p := mload(0x40)
                let yy := mulmod(y, y, p)
                let zz := mulmod(z, z, p)
                let s := mulmod(4, mulmod(x, yy, p), p)
                let m := addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p)
                let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p)
                let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p)
                let z2 := mulmod(2, mulmod(y, z, p), p)
                setJPoint(i, x2, y2, z2)
            }

            function setJPointAdd(i, j, k) {
                j := shl(7, j)
                k := shl(7, k)
                let x, y, z :=
                    jAdd(
                        mload(j),
                        mload(add(j, returndatasize())),
                        mload(add(j, 0x40)),
                        mload(k),
                        mload(add(k, returndatasize())),
                        mload(add(k, 0x40))
                    )
                setJPoint(i, x, y, z)
            }

            let r := calldataload(0x20)
            let n := N

            {
                let s := calldataload(0x40)
                if lt(shr(1, n), s) { s := sub(n, s) }

                // Perform `modExp(s, N - 2, N)`.
                // After which, we can abuse `returndatasize()` to get `0x20`.
                mstore(0x800, 0x20)
                mstore(0x820, 0x20)
                mstore(0x840, 0x20)
                mstore(0x860, s)
                mstore(0x880, sub(n, 2))
                mstore(0x8a0, n)

                let p := mload(0x40)
                mstore(0x20, xor(3, p)) // Set `0x20` to `A`.
                let Qx := calldataload(0x60)
                let Qy := calldataload(0x80)

                if iszero(
                    and( // The arguments of `and` are evaluated last to first.
                        and(
                            and(gt(calldatasize(), 0x9f), and(lt(iszero(r), lt(r, n)), lt(iszero(s), lt(s, n)))),
                            eq(
                                mulmod(Qy, Qy, p),
                                addmod(mulmod(addmod(mulmod(Qx, Qx, p), mload(returndatasize()), p), Qx, p), B, p)
                            )
                        ),
                        and(
                            // We need to check that the `returndatasize` is indeed 32,
                            // so that we can return false if the chain does not have the modexp precompile.
                            eq(returndatasize(), 0x20),
                            staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), 0x20)
                        )
                    )
                ) {
                    // POC Note:
                    // Changing this to `return(0x80, 0x20)` fixes it.
                    // Alternatively, adding `if mload(0x8c0) { invalid() }` just before the return also fixes it.
                    return(0x8c0, 0x20)
                }

                setJPoint(0x01, Qx, Qy, 1)
                setJPoint(0x04, GX, GY, 1)
                setJPointDouble(0x02, 0x01)
                setJPointDouble(0x08, 0x04)
                setJPointAdd(0x03, 0x01, 0x02)
                setJPointAdd(0x05, 0x01, 0x04)
                setJPointAdd(0x06, 0x02, 0x04)
                setJPointAdd(0x07, 0x03, 0x04)
                setJPointAdd(0x09, 0x01, 0x08)
                setJPointAdd(0x0a, 0x02, 0x08)
                setJPointAdd(0x0b, 0x03, 0x08)
                setJPointAdd(0x0c, 0x04, 0x08)
                setJPointAdd(0x0d, 0x01, 0x0c)
                setJPointAdd(0x0e, 0x02, 0x0c)
                setJPointAdd(0x0f, 0x03, 0x0c)
            }

            let i := 0
            let u1 := mulmod(calldataload(0x00), mload(0x00), n)
            let u2 := mulmod(r, mload(0x00), n)
            let y := 0
            let z := 0
            let x := 0
            let p := mload(0x40)
            for {} 1 {} {
                if z {
                    let yy := mulmod(y, y, p)
                    let zz := mulmod(z, z, p)
                    let s := mulmod(4, mulmod(x, yy, p), p)
                    let m :=
                        addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p)
                    let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p)
                    let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p)
                    let z2 := mulmod(2, mulmod(y, z, p), p)
                    yy := mulmod(y2, y2, p)
                    zz := mulmod(z2, z2, p)
                    s := mulmod(4, mulmod(x2, yy, p), p)
                    m := addmod(
                        mulmod(3, mulmod(x2, x2, p), p),
                        mulmod(mload(returndatasize()), mulmod(zz, zz, p), p),
                        p
                    )
                    x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p)
                    z := mulmod(2, mulmod(y2, z2, p), p)
                    y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p)
                }
                for { let o := or(and(shr(245, shl(i, u1)), 0x600), and(shr(247, shl(i, u2)), 0x180)) } o {} {
                    let z2 := mload(add(o, 0x40))
                    if iszero(z2) { break }
                    if iszero(z) {
                        x := mload(o)
                        y := mload(add(o, returndatasize()))
                        z := z2
                        break
                    }
                    let zz1 := mulmod(z, z, p)
                    let zz2 := mulmod(z2, z2, p)
                    let u1_ := mulmod(x, zz2, p)
                    let s1 := mulmod(y, mulmod(zz2, z2, p), p)
                    let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p)
                    let hh := mulmod(h, h, p)
                    let hhh := mulmod(h, hh, p)
                    let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p)
                    x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p)
                    y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p)
                    z := mulmod(h, mulmod(z, z2, p), p)
                    break
                }
                // Just unroll twice. Fully unrolling will only save around 1% to 2% gas, but make the
                // bytecode very bloated, which may incur more runtime costs after Verkle.
                // See: https://notes.ethereum.org/%40vbuterin/verkle_tree_eip
                // It's very unlikely that Verkle will come before the P256 precompile. But who knows?
                if z {
                    let yy := mulmod(y, y, p)
                    let zz := mulmod(z, z, p)
                    let s := mulmod(4, mulmod(x, yy, p), p)
                    let m :=
                        addmod(mulmod(3, mulmod(x, x, p), p), mulmod(mload(returndatasize()), mulmod(zz, zz, p), p), p)
                    let x2 := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p)
                    let y2 := addmod(mulmod(m, addmod(s, sub(p, x2), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p)
                    let z2 := mulmod(2, mulmod(y, z, p), p)
                    yy := mulmod(y2, y2, p)
                    zz := mulmod(z2, z2, p)
                    s := mulmod(4, mulmod(x2, yy, p), p)
                    m := addmod(
                        mulmod(3, mulmod(x2, x2, p), p),
                        mulmod(mload(returndatasize()), mulmod(zz, zz, p), p),
                        p
                    )
                    x := addmod(mulmod(m, m, p), sub(p, mulmod(2, s, p)), p)
                    z := mulmod(2, mulmod(y2, z2, p), p)
                    y := addmod(mulmod(m, addmod(s, sub(p, x), p), p), sub(p, mulmod(8, mulmod(yy, yy, p), p)), p)
                }
                for { let o := or(and(shr(243, shl(i, u1)), 0x600), and(shr(245, shl(i, u2)), 0x180)) } o {} {
                    let z2 := mload(add(o, 0x40))
                    if iszero(z2) { break }
                    if iszero(z) {
                        x := mload(o)
                        y := mload(add(o, returndatasize()))
                        z := z2
                        break
                    }
                    let zz1 := mulmod(z, z, p)
                    let zz2 := mulmod(z2, z2, p)
                    let u1_ := mulmod(x, zz2, p)
                    let s1 := mulmod(y, mulmod(zz2, z2, p), p)
                    let h := addmod(mulmod(mload(o), zz1, p), sub(p, u1_), p)
                    let hh := mulmod(h, h, p)
                    let hhh := mulmod(h, hh, p)
                    let r_ := addmod(mulmod(mload(add(o, returndatasize())), mulmod(zz1, z, p), p), sub(p, s1), p)
                    x := addmod(addmod(mulmod(r_, r_, p), sub(p, hhh), p), sub(p, mulmod(2, mulmod(u1_, hh, p), p)), p)
                    y := addmod(mulmod(r_, addmod(mulmod(u1_, hh, p), sub(p, x), p), p), sub(p, mulmod(s1, hhh, p)), p)
                    z := mulmod(h, mulmod(z, z2, p), p)
                    break
                }
                i := add(i, 4)
                if eq(i, 256) { break }
            }

            if iszero(z) {
                mstore(returndatasize(), iszero(r))
                return(returndatasize(), 0x20)
            }

            // Perform `modExp(z, P - 2, P)`.
            // `0x800`, `0x820, `0x840` are still set to `0x20`.
            mstore(0x860, z)
            mstore(0x880, sub(p, 2))
            mstore(0x8a0, p)

            mstore(
                returndatasize(),
                and( // The arguments of `and` are evaluated last to first.
                    eq(mod(mulmod(x, mulmod(mload(returndatasize()), mload(returndatasize()), p), p), n), r),
                    staticcall(gas(), 0x05, 0x800, 0xc0, returndatasize(), returndatasize())
                )
            )
            return(returndatasize(), returndatasize())
        }
    }
}
"#,
    );

    cmd.arg("test").with_no_redact().assert_success().stdout_eq(str![[r#"
...
Ran 1 test for test/Issue8383.t.sol:Issue8383Test
[PASS] testP256VerifyOutOfBounds() (gas: 3139)
...
"#]]);
});

// https://github.com/foundry-rs/foundry/issues/9272
forgetest_init!(issue_9272, |prj, cmd| {
    prj.update_config(|config| {
        config.allow_paths.push("..".into());
    });

    prj.add_source(
        "Contract.sol",
        r#"
pragma solidity ^0.8.0;
import '../Missing.sol';
contract Contract {}
"#,
    );

    // We expect a compilation error due to the missing import
    cmd.arg("build").assert_failure().stderr_eq(str![[r#"
Error: Compiler run failed:
Error (6275): Source "Missing.sol" not found: File not found. Searched the following locations: [..]
ParserError: Source "Missing.sol" not found: File not found. Searched the following locations: [..]
 [FILE]:4:1:
  |
4 | import '../Missing.sol';
  | ^^^^^^^^^^^^^^^^^^^^^^^^

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/10463
forgetest_init!(issue_10463, |prj, cmd| {
    prj.add_test(
        "Issue10463.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Issue10463Test is Test {
    event Foo();

    error CustomError(uint256 code);

    function revertingBefore(bool shouldRevert) external {
        if (shouldRevert) revert();
        emit Foo();
    }

    function revertingWithReason() external pure {
        revert("revert reason");
    }

    function revertingWithCustomError() external pure {
        revert CustomError(42);
    }

    function testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() public {
        vm.expectEmit();
        emit Foo();

        this.revertingBefore(true);
    }

    function testExpectEmitPreservesRevertReason() public {
        vm.expectEmit();
        emit Foo();

        this.revertingWithReason();
    }

    function testExpectEmitPreservesCustomError() public {
        vm.expectEmit();
        emit Foo();

        this.revertingWithCustomError();
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
...
Ran 3 tests for test/Issue10463.t.sol:Issue10463Test
[FAIL: CustomError(42)] testExpectEmitPreservesCustomError() ([GAS])
[FAIL: revert reason] testExpectEmitPreservesRevertReason() ([GAS])
[FAIL: EvmError: Revert] testExpectEmitPreservesRevertWhenCallRevertsBeforeLog() ([GAS])
Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED]
...
"#]]);
});

// https://github.com/foundry-rs/foundry/issues/12803
// Test gas underflow prevention on Cancun (no EIP-7702 gas floor)
forgetest_init!(issue_12803_cancun, |prj, cmd| {
    prj.add_test(
        "Issue12803.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Issue12803Test is Test {
    uint a;
    function test_negativeGas() public {
        vm.pauseGasMetering();
        a = 100;
        vm.resumeGasMetering();
        delete a;
    }
}
"#,
    );

    cmd.args(["test", "--evm-version=cancun"]).with_no_redact().assert_success().stdout_eq(str![[
        r#"
...
Ran 1 test for test/Issue12803.t.sol:Issue12803Test
[PASS] test_negativeGas() (gas: 0)
...
"#
    ]]);
});

// https://github.com/foundry-rs/foundry/issues/12803
// Test gas underflow prevention on Shanghai (also no EIP-7702 gas floor)
forgetest_init!(issue_12803_shanghai, |prj, cmd| {
    prj.add_test(
        "Issue12803.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Issue12803Test is Test {
    uint a;
    function test_negativeGas() public {
        vm.pauseGasMetering();
        a = 100;
        vm.resumeGasMetering();
        delete a;
    }
}
"#,
    );

    cmd.args(["test", "--evm-version=shanghai"]).with_no_redact().assert_success().stdout_eq(str![
        [r#"
...
Ran 1 test for test/Issue12803.t.sol:Issue12803Test
[PASS] test_negativeGas() (gas: 0)
...
"#]
    ]);
});

// https://github.com/foundry-rs/foundry/issues/13766
// vm.expectRevert(bytes("")) should not panic when actual revert has data.
// https://github.com/foundry-rs/foundry/issues/15545
// An expected reason shorter than 4 bytes (e.g. bytes("C38")) must not panic
// when it cannot be decoded as an `Error(string)`; it should report a mismatch.
forgetest_init!(issue_13766, |prj, cmd| {
    prj.add_test(
        "Issue13766.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Reverter {
    error CustomError();
    function revertWithData() public pure { revert CustomError(); }
    function revertWithMessage(string memory message) public pure { revert(message); }
}

contract Issue13766Test is Test {
    function test_expectRevertEmptyBytes() public {
        Reverter r = new Reverter();
        vm.expectRevert(bytes(""));
        r.revertWithData();
    }

    function test_expectRevertShortReason() public {
        Reverter r = new Reverter();
        vm.expectRevert(bytes("C38"));
        r.revertWithMessage("some other message");
    }
}
"#,
    );

    cmd.arg("test").assert_failure().stdout_eq(str![[r#"
...
[FAIL: Error != expected error: CustomError() != EvmError: Revert] test_expectRevertEmptyBytes() ([GAS])
...
[FAIL: Error != expected error: some other message != C38] test_expectRevertShortReason() ([GAS])
...
"#]]);
});

// https://github.com/foundry-rs/foundry/issues/12803
// Test multiple storage deletions (higher refund) don't cause underflow
forgetest_init!(issue_12803_multiple_deletes, |prj, cmd| {
    prj.add_test(
        "Issue12803Multi.t.sol",
        r#"
import {Test} from "forge-std/Test.sol";

contract Issue12803MultiTest is Test {
    uint a;
    uint b;
    uint c;
    function test_multipleDeletes() public {
        vm.pauseGasMetering();
        a = 100;
        b = 200;
        c = 300;
        vm.resumeGasMetering();
        delete a;
        delete b;
        delete c;
    }
}
"#,
    );

    cmd.args(["test", "--evm-version=cancun"]).with_no_redact().assert_success().stdout_eq(str![[
        r#"
...
Ran 1 test for test/Issue12803Multi.t.sol:Issue12803MultiTest
[PASS] test_multipleDeletes() (gas: 0)
...
"#
    ]]);
});

// Regression: `revertToState` / `revertToStateAndDelete` taken before any
// `vm.blobhashes` override must exercise the `None` arm of
// `sync_tx_after_env_override_restore` (pre_override_blob_hashes path).
// The snapshot is taken while env_overrides.blob_hashes is None; after the
// override is applied and reverted the hashes must return to empty.
//
// NOTE: Testing restoration of *non-empty* native blob hashes (EIP-4844 fork
// mode where tx.blob_hashes is non-empty without a cheatcode) is not reachable
// from Solidity.
forgetest_init!(issue_blobhashes_pre_override_snapshot, |prj, cmd| {
    prj.add_test(
        "BlobhashesPreOverride.t.sol",
        r#"
import "forge-std/Test.sol";

contract BlobhashesPreOverrideSnapshotTest is Test {
    function test_blobhashes_none_arm_revertToState() public {
        assertEq(vm.getBlobhashes().length, 0, "no hashes before override");

        uint256 id = vm.snapshotState();

        bytes32[] memory h = new bytes32[](2);
        h[0] = bytes32(uint256(0xAABB));
        h[1] = bytes32(uint256(0xCCDD));
        vm.blobhashes(h);
        assertEq(vm.getBlobhashes().length, 2, "override visible");

        vm.revertToState(id);
        assertEq(vm.getBlobhashes().length, 0, "None arm: hashes cleared after revert");
    }

    function test_blobhashes_none_arm_revertToStateAndDelete() public {
        assertEq(vm.getBlobhashes().length, 0, "no hashes before override");

        uint256 id = vm.snapshotState();

        bytes32[] memory h = new bytes32[](1);
        h[0] = bytes32(uint256(0xDEAD));
        vm.blobhashes(h);
        assertEq(vm.getBlobhashes().length, 1, "override visible");

        vm.revertToStateAndDelete(id);
        assertEq(vm.getBlobhashes().length, 0, "None arm: hashes cleared after revertAndDelete");
    }
}
"#,
    );

    cmd.args(["test", "--evm-version=cancun"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 2 tests for test/BlobhashesPreOverride.t.sol:BlobhashesPreOverrideSnapshotTest
[PASS] test_blobhashes_none_arm_revertToState() ([GAS])
[PASS] test_blobhashes_none_arm_revertToStateAndDelete() ([GAS])
Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)

"#]]);
});

// Regression: `revertToState` taken before `vm.txGasPrice` must restore the
// configured pre override gas price, not zero.
forgetest_init!(issue_txgasprice_pre_override_snapshot, |prj, cmd| {
    prj.update_config(|config| {
        config.gas_price = Some(10_000_000_000); // 10 gwei
    });
    prj.add_test(
        "TxGasPricePreOverride.t.sol",
        r#"
import "forge-std/Test.sol";

contract TxGasPricePreOverrideSnapshotTest is Test {
    function test_pre_override_gas_price_restored_after_revert() public {
        uint256 pre_override = tx.gasprice;
        assertEq(pre_override, 10 gwei, "pre override should be 10 gwei from config");

        uint256 id = vm.snapshotState();
        vm.txGasPrice(222 gwei);
        assertEq(tx.gasprice, 222 gwei, "override should be visible");

        vm.revertToState(id);
        assertEq(tx.gasprice, pre_override, "should restore pre override gas price after revert");
    }

    function test_pre_override_gas_price_restored_after_revertAndDelete() public {
        uint256 pre_override = tx.gasprice;
        assertEq(pre_override, 10 gwei, "pre override should be 10 gwei from config");

        uint256 id = vm.snapshotState();
        vm.txGasPrice(333 gwei);
        assertEq(tx.gasprice, 333 gwei, "override should be visible");

        vm.revertToStateAndDelete(id);
        assertEq(tx.gasprice, pre_override, "should restore pre override gas price after revertAndDelete");
    }
}
"#,
    );

    cmd.args(["test", "--evm-version=cancun"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!

Ran 2 tests for test/TxGasPricePreOverride.t.sol:TxGasPricePreOverrideSnapshotTest
[PASS] test_pre_override_gas_price_restored_after_revert() ([GAS])
[PASS] test_pre_override_gas_price_restored_after_revertAndDelete() ([GAS])
Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]

Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)

"#]]);
});
