//! Contains various tests related to `forge script`.

use crate::{
    constants::TEMPLATE_CONTRACT,
    utils::{assert_debug_dump_identifies_contract, generate_large_runtime_contract},
};
use alloy_hardforks::EthereumHardfork;
use alloy_network::Ethereum;
use alloy_primitives::{Address, Bytes, U256, address, hex};
use anvil::{NodeConfig, spawn};
use axum::{Router, body::Bytes as BodyBytes};
use forge_script_sequence::ScriptSequence;
use foundry_compilers::artifacts::EvmVersion;
use foundry_test_utils::{
    ScriptOutcome, ScriptTester,
    rpc::{self, next_http_archive_rpc_url},
    snapbox::IntoData,
    util::{OTHER_SOLC_VERSION, SOLC_VERSION},
};
use regex::Regex;
use serde_json::Value;
use std::{
    env, fs,
    path::{Path, PathBuf},
    sync::atomic::{AtomicUsize, Ordering},
};

fn latest_dry_run_sequence(root: &Path) -> ScriptSequence<Ethereum> {
    let path = foundry_common::fs::json_files(&root.join("broadcast"))
        .find(|path| path.ends_with("dry-run/run-latest.json"))
        .unwrap();
    foundry_common::fs::read_json_file(&path).unwrap()
}

// Tests that fork cheat codes can be used in script
forgetest_init!(
    #[ignore]
    can_use_fork_cheat_codes_in_script,
    |prj, cmd| {
        let script = prj.add_source(
            "Foo",
            r#"
import "forge-std/Script.sol";

contract ContractScript is Script {
    function setUp() public {}

    function run() public {
        uint256 fork = vm.activeFork();
        vm.rollFork(11469702);
    }
}
   "#,
        );

        let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint();

        cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvvv"]).assert_success();
    }
);

forgetest_async!(script_debug_dump_identifies_contracts_loaded_from_fork, |prj, cmd| {
    prj.add_source(
        "ScriptForkDebugTarget.sol",
        r#"
contract ScriptForkDebugTarget {
    uint256 public value;

    function set(uint256 newValue) public {
        value = newValue;
    }
}
"#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;
    let rpc = handle.http_endpoint();
    let pk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";

    let create_output = cmd
        .forge_fuse()
        .args([
            "create",
            "./src/ScriptForkDebugTarget.sol:ScriptForkDebugTarget",
            "--rpc-url",
            rpc.as_str(),
            "--private-key",
            pk,
            "--broadcast",
            "--json",
        ])
        .assert_success()
        .get_output()
        .stdout
        .clone();
    let create_output: Value = serde_json::from_slice(&create_output).unwrap();
    let deployed = create_output["deployedTo"].as_str().unwrap().to_owned();

    prj.add_script(
        "DebugRemote.s.sol",
        &format!(
            r#"
interface IScriptForkDebugTarget {{
    function set(uint256 newValue) external;
    function value() external view returns (uint256);
}}

contract DebugRemote {{
    IScriptForkDebugTarget private target = IScriptForkDebugTarget({deployed});

    function setUp() public {{
        target.set(7);
        require(target.value() == 7, "setup value");
    }}

    function run() public {{
        target.set(19);
        require(target.value() == 19, "value");
    }}
}}
"#
        ),
    );

    let dump_path = prj.root().join("script_dump.json");
    cmd.forge_fuse().args([
        "script",
        "script/DebugRemote.s.sol:DebugRemote",
        "--fork-url",
        rpc.as_str(),
        "--debug",
        "--dump",
        dump_path.to_str().unwrap(),
    ]);
    cmd.assert_success();
    assert_debug_dump_identifies_contract(&dump_path, &deployed, "ScriptForkDebugTarget");
});

// Tests that the `run` command works correctly
forgetest!(can_execute_script_command2, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
contract Demo {
    event log_string(string);
    function run() external {
        emit log_string("script ran");
    }
}
   "#,
    );

    cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Logs ==
  script ran

"#]]);
});

forgetest!(verbosity_five_shows_script_storage_changes, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let script = prj.add_script(
        "StorageChanges",
        r#"
import "forge-std/Script.sol";

contract Counter {
    uint256 public number;

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

contract StorageChanges is Script {
    Counter public counter = new Counter();

    function run() external {
        counter.setNumber(1);
    }
}
   "#,
    );

    let target = format!("{}:StorageChanges", script.display());
    cmd.args(["script", &target, "-vvvvv"]).assert_success().stdout_eq(str![[r#"
...
Traces:
  [..] StorageChanges::run()
    ├─ [..] Counter::setNumber(1)
    │   ├─  storage changes:
    │   │   @ 0: 0 → 1
    │   └─ ← [Stop]
    └─ ← [Stop]


Script ran successfully.
[GAS]

"#]]);
});

// Tests that the `run` command works correctly when path *and* script name is specified
forgetest!(can_execute_script_command_fqn, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
contract Demo {
    event log_string(string);
    function run() external {
        emit log_string("script ran");
    }
}
   "#,
    );

    cmd.arg("script").arg(format!("{}:Demo", script.display())).assert_success().stdout_eq(str![[
        r#"
...
Script ran successfully.
[GAS]

== Logs ==
  script ran
...
"#
    ]]);
});

// Tests that the run command can run arbitrary functions
forgetest!(can_execute_script_command_with_sig, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
contract Demo {
    event log_string(string);
    function myFunction() external {
        emit log_string("script ran");
    }
}
   "#,
    );

    cmd.arg("script").arg(script).arg("--sig").arg("myFunction()").assert_success().stdout_eq(
        str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Logs ==
  script ran

"#]],
    );
});

static FAILING_SCRIPT: &str = r#"
import "forge-std/Script.sol";

contract FailingScript is Script {
    function run() external {
        revert("failed");
    }
}
"#;

static OUT_OF_GAS_SCRIPT: &str = r#"
import "forge-std/Script.sol";

contract OutOfGasScript is Script {
    function run() external {
        uint256 i;
        while (true) {
            unchecked {
                ++i;
            }
        }
    }
}
"#;

static MULTI_DEPLOY_SCRIPT: &str = r#"
import "forge-std/Script.sol";

contract A { function ping() external pure returns (uint256) { return 1; } }
contract B { address public a; constructor(address _a) { a = _a; } }
contract C { bytes32 public tag; constructor(bytes32 _t) { tag = _t; } }

contract MultiDeploy is Script {
    function run() external {
        vm.startBroadcast();
        A a = new A();
        new B(address(a));
        new C(bytes32(uint256(0x1234)));
        vm.stopBroadcast();
    }
}
"#;

// Tests that execution throws upon encountering a revert in the script.
forgetest_async!(assert_exit_code_error_on_failure_script, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let script = prj.add_source("FailingScript", FAILING_SCRIPT);

    // set up command
    cmd.arg("script").arg(script);

    // run command and assert error exit code
    cmd.assert_failure().stderr_eq(str![[r#"
Error: script failed: failed

"#]]);
});

// Tests that execution throws upon encountering a revert in the script with --json option.
// <https://github.com/foundry-rs/foundry/issues/2508>
forgetest_async!(assert_exit_code_error_on_failure_script_with_json, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let script = prj.add_source("FailingScript", FAILING_SCRIPT);

    // set up command
    cmd.arg("script").arg(script).arg("--json");

    // run command and assert error exit code
    cmd.assert_failure().stderr_eq(str![[r#"
Error: script failed: failed

"#]]);
});

// Tests that script failures surface halt reasons for empty revert data.
forgetest_async!(assert_exit_code_error_on_out_of_gas_script, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let script = prj.add_source("OutOfGasScript", OUT_OF_GAS_SCRIPT);

    // Use a small block gas limit so the infinite loop exhausts gas in milliseconds rather than
    // >25s at the default ~1B limit, which often exceeds the nextest slow-timeout window on CI.
    cmd.arg("script").arg(script).args(["--block-gas-limit", "1000000"]);

    cmd.assert_failure().stderr_eq(str![[r#"
Error: script failed: EvmError: OutOfGas

"#]]);
});

// Tests that --json script failures also surface halt reasons for empty revert data.
forgetest_async!(assert_exit_code_error_on_out_of_gas_script_with_json, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let script = prj.add_source("OutOfGasScript", OUT_OF_GAS_SCRIPT);

    // See `assert_exit_code_error_on_out_of_gas_script`
    cmd.arg("script").arg(script).arg("--json").args(["--block-gas-limit", "1000000"]);

    cmd.assert_failure().stderr_eq(str![[r#"
Error: script failed: EvmError: OutOfGas

"#]]);
});

// Tests that the manually specified gas limit is used when using the --unlocked option
forgetest_async!(can_execute_script_command_with_manual_gas_limit_unlocked, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let deploy_script = prj.add_source(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract GasWaster {
    function wasteGas(uint256 minGas) public {
        require(gasleft() >= minGas, "Gas left needs to be higher");
    }
}
contract DeployScript is Script {
    function run() external {
        vm.startBroadcast();
        GasWaster gasWaster = new GasWaster();
        gasWaster.wasteGas{gas: 500000}(200000);
    }
}
   "#,
    );

    let deploy_contract = deploy_script.display().to_string() + ":DeployScript";

    let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()));
    let (_api, handle) = spawn(node_config).await;
    let dev = handle.dev_accounts().next().unwrap();
    cmd.set_current_dir(prj.root());

    cmd.args([
        "script",
        &deploy_contract,
        "--root",
        prj.root().to_str().unwrap(),
        "--fork-url",
        &handle.http_endpoint(),
        "--sender",
        format!("{dev:?}").as_str(),
        "-vvvvv",
        "--slow",
        "--broadcast",
        "--unlocked",
        "--ignored-error-codes=2018", // `wasteGas` can be restricted to view
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] DeployScript::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return]
    ├─ [..] → new GasWaster@[..]
    │   └─ ← [Return] 415 bytes of code
    ├─ [..] GasWaster::wasteGas(200000 [2e5])
    │   └─ ← [Stop]
    └─ ← [Stop]


Script ran successfully.

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [..] → new GasWaster@[..]
    └─ ← [Return] 415 bytes of code

  [..] GasWaster::wasteGas(200000 [2e5])
    └─ ← [Stop]


==========================

Chain 1

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

// Tests that the manually specified gas limit is used.
forgetest_async!(can_execute_script_command_with_manual_gas_limit, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let deploy_script = prj.add_source(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract GasWaster {
    function wasteGas(uint256 minGas) public {
        require(gasleft() >= minGas, "Gas left needs to be higher");
    }
}
contract DeployScript is Script {
    function run() external {
        vm.startBroadcast();
        GasWaster gasWaster = new GasWaster();
        gasWaster.wasteGas{gas: 500000}(200000);
    }
}
   "#,
    );

    let deploy_contract = deploy_script.display().to_string() + ":DeployScript";

    let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()));
    let (_api, handle) = spawn(node_config).await;
    let private_key =
        "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string();
    cmd.set_current_dir(prj.root());

    cmd.args([
        "script",
        &deploy_contract,
        "--root",
        prj.root().to_str().unwrap(),
        "--fork-url",
        &handle.http_endpoint(),
        "-vvvvv",
        "--slow",
        "--broadcast",
        "--private-key",
        &private_key,
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning (2018): Function state mutability can be restricted to view
 [FILE]:7:5:
  |
7 |     function wasteGas(uint256 minGas) public {
  |     ^ (Relevant source part starts here and spans across multiple lines).

Traces:
  [..] DeployScript::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return]
    ├─ [..] → new GasWaster@[..]
    │   └─ ← [Return] 415 bytes of code
    ├─ [..] GasWaster::wasteGas(200000 [2e5])
    │   └─ ← [Stop]
    └─ ← [Stop]


Script ran successfully.

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [..] → new GasWaster@[..]
    └─ ← [Return] 415 bytes of code

  [..] GasWaster::wasteGas(200000 [2e5])
    └─ ← [Stop]


==========================

Chain 1

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

// Tests that the run command can run functions with arguments
forgetest!(can_execute_script_command_with_args, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
contract Demo {
    event log_string(string);
    event log_uint(uint);
    function run(uint256 a, uint256 b) external {
        emit log_string("script ran");
        emit log_uint(a);
        emit log_uint(b);
    }
}
   "#,
    );

    cmd.arg("script")
        .arg(script)
        .arg("--sig")
        .arg("run(uint256,uint256)")
        .arg("1")
        .arg("2")
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Logs ==
  script ran
  1
  2

"#]]);
});

// Tests that the run command can run functions with arguments without specifying the signature
// <https://github.com/foundry-rs/foundry/issues/11240>
forgetest!(can_execute_script_command_with_args_no_sig, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
contract Demo {
    event log_string(string);
    event log_uint(uint);
    function run(uint256 a, uint256 b) external {
        emit log_string("script ran");
        emit log_uint(a);
        emit log_uint(b);
    }
}
   "#,
    );

    cmd.arg("script").arg(script).arg("1").arg("2").assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Logs ==
  script ran
  1
  2

"#]]);
});

// Tests that the run command can run functions with return values
forgetest!(can_execute_script_command_with_returned, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
contract Demo {
    event log_string(string);
    function run() external returns (uint256 result, uint8) {
        emit log_string("script ran");
        return (255, 3);
    }
}"#,
    );

    cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Return ==
result: uint256 255
1: uint8 3

== Logs ==
  script ran

"#]]);
});

forgetest_async!(can_broadcast_script_skipping_simulation, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    // This example script would fail in on-chain simulation
    let deploy_script = prj.add_source(
        "DeployScript",
        r#"
import "forge-std/Script.sol";

contract HashChecker {
    bytes32 public lastHash;

    function update() public {
        bytes32 newHash = blockhash(block.number - 1);
        require(newHash != lastHash, "Hash didn't change");
        lastHash = newHash;
    }

    function checkLastHash() public view {
        require(lastHash != bytes32(0), "Hash shouldn't be zero");
    }
}

contract DeployScript is Script {
    HashChecker public hashChecker;

    function run() external {
        vm.startBroadcast();
        hashChecker = new HashChecker();
    }
}"#,
    );

    let deploy_contract = deploy_script.display().to_string() + ":DeployScript";

    let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()));
    let (_api, handle) = spawn(node_config).await;
    let private_key =
        "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string();
    cmd.set_current_dir(prj.root());

    cmd.args([
        "script",
        &deploy_contract,
        "--root",
        prj.root().to_str().unwrap(),
        "--fork-url",
        &handle.http_endpoint(),
        "-vvvvv",
        "--broadcast",
        "--slow",
        "--skip-simulation",
        "--private-key",
        &private_key,
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] DeployScript::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return]
    ├─ [..] → new HashChecker@[..]
    │   └─ ← [Return] 718 bytes of code
    ├─  storage changes:
    │   @ 12: 65537 → [..]
    └─ ← [Stop]


Script ran successfully.

SKIPPING ON CHAIN SIMULATION.


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);

    let run_log = std::fs::read_to_string("broadcast/DeployScript.sol/1/run-latest.json").unwrap();
    let run_object: Value = serde_json::from_str(&run_log).unwrap();
    let contract_address = &run_object["receipts"][0]["contractAddress"]
        .as_str()
        .unwrap()
        .parse::<Address>()
        .unwrap()
        .to_string();

    let run_code = r#"
import "forge-std/Script.sol";
import { HashChecker } from "./DeployScript.sol";

contract RunScript is Script {
    HashChecker public hashChecker;

    function run() external {
        vm.startBroadcast();
        hashChecker = HashChecker(CONTRACT_ADDRESS);
        uint numUpdates = 8;
        vm.roll(block.number - numUpdates);
        for(uint i = 0; i < numUpdates; i++) {
            vm.roll(block.number + 1);
            hashChecker.update();
            hashChecker.checkLastHash();
        }
    }
}"#
    .replace("CONTRACT_ADDRESS", contract_address);

    let run_script = prj.add_source("RunScript", &run_code);
    let run_contract = run_script.display().to_string() + ":RunScript";

    cmd.forge_fuse()
        .args([
            "script",
            &run_contract,
            "--root",
            prj.root().to_str().unwrap(),
            "--fork-url",
            &handle.http_endpoint(),
            "-vvvvv",
            "--broadcast",
            "--slow",
            "--skip-simulation",
            "--gas-estimate-multiplier",
            "200",
            "--private-key",
            &private_key,
        ])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] RunScript::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─ [0] VM::roll([..])
    │   └─ ← [Return]
    ├─ [..] [..]::update()
    │   ├─  storage changes:
    │   │   @ 0: [..]
    │   └─ ← [Stop]
    ├─ [..] [..]::checkLastHash() [staticcall]
    │   └─ ← [Stop]
    ├─  storage changes:
    │   @ 12: 65537 → [..]
    └─ ← [Stop]


Script ran successfully.

SKIPPING ON CHAIN SIMULATION.


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

forgetest_async!(can_deploy_script_without_lib, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0, 1])
        .await
        .add_sig("BroadcastTestNoLinking", "deployDoesntPanic()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 1), (1, 2)])
        .await;
});

forgetest_async!(can_deploy_script_with_lib, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0, 1])
        .await
        .add_sig("BroadcastTest", "deploy()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 2), (1, 1)])
        .await;
});

forgetest_async!(can_deploy_script_private_key, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_addresses(&[address!("0x90F79bf6EB2c4f870365E785982E1f101E93b906")])
        .await
        .add_sig("BroadcastTest", "deployPrivateKey()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment_addresses(&[(
            address!("0x90F79bf6EB2c4f870365E785982E1f101E93b906"),
            3,
        )])
        .await;
});

forgetest_async!(can_deploy_unlocked, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .sender("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap())
        .unlocked()
        .add_sig("BroadcastTest", "deployOther()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast);
});

forgetest_async!(can_deploy_script_remember_key, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_addresses(&[address!("0x90F79bf6EB2c4f870365E785982E1f101E93b906")])
        .await
        .add_sig("BroadcastTest", "deployRememberKey()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment_addresses(&[(
            address!("0x90F79bf6EB2c4f870365E785982E1f101E93b906"),
            2,
        )])
        .await;
});

forgetest_async!(can_deploy_script_remember_key_and_resume, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .add_deployer(0)
        .load_addresses(&[address!("0x90F79bf6EB2c4f870365E785982E1f101E93b906")])
        .await
        .add_sig("BroadcastTest", "deployRememberKeyResume()")
        .simulate(ScriptOutcome::OkSimulation)
        .resume(ScriptOutcome::MissingWallet)
        // load missing wallet
        .load_private_keys(&[0])
        .await
        .run(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment_addresses(&[(
            address!("0x90F79bf6EB2c4f870365E785982E1f101E93b906"),
            1,
        )])
        .await
        .assert_nonce_increment(&[(0, 2)])
        .await;
});

forgetest_async!(can_resume_script, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastTest", "deploy()")
        .simulate(ScriptOutcome::OkSimulation)
        .resume(ScriptOutcome::MissingWallet)
        // load missing wallet
        .load_private_keys(&[1])
        .await
        .run(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 2), (1, 1)])
        .await;
});

forgetest_async!(can_deploy_broadcast_wrap, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .add_deployer(2)
        .load_private_keys(&[0, 1, 2])
        .await
        .add_sig("BroadcastTest", "deployOther()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 4), (1, 4), (2, 1)])
        .await;
});

forgetest_async!(panic_no_deployer_set, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0, 1])
        .await
        .add_sig("BroadcastTest", "deployOther()")
        .simulate(ScriptOutcome::WarnSpecifyDeployer)
        .broadcast(ScriptOutcome::MissingSender);
});

forgetest_async!(can_deploy_no_arg_broadcast, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .add_deployer(0)
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastTest", "deployNoArgs()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 3)])
        .await;
});

forgetest_async!(can_deploy_with_create2, |prj, cmd| {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    // Prepare CREATE2 Deployer
    api.anvil_set_code(
        foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER,
        Bytes::from_static(foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE),
    )
    .await
    .unwrap();

    tester
        .add_deployer(0)
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastTestNoLinking", "deployCreate2()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 2)])
        .await
        // Running again results in error, since we're repeating the salt passed to CREATE2
        .run(ScriptOutcome::ScriptFailed);
});

forgetest_async!(can_deploy_with_custom_create2, |prj, cmd| {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());
    let create2 = address!("0x0000000000000000000000000000000000b4956c");

    // Prepare CREATE2 Deployer
    api.anvil_set_code(
        create2,
        Bytes::from_static(foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE),
    )
    .await
    .unwrap();

    tester
        .add_deployer(0)
        .load_private_keys(&[0])
        .await
        .add_create2_deployer(create2)
        .add_sig("BroadcastTestNoLinking", "deployCreate2(address)")
        .arg(&create2.to_string())
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 2)])
        .await;
});

forgetest_async!(can_deploy_with_custom_create2_notmatched_bytecode, |prj, cmd| {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());
    let create2 = address!("0x0000000000000000000000000000000000b4956c");

    // Prepare CREATE2 Deployer
    api.anvil_set_code(
        create2,
        Bytes::from_static(&hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cef")),
    )
    .await
    .unwrap();

    tester
        .add_deployer(0)
        .load_private_keys(&[0])
        .await
        .add_create2_deployer(create2)
        .add_sig("BroadcastTestNoLinking", "deployCreate2()")
        .simulate(ScriptOutcome::ScriptFailed)
        .broadcast(ScriptOutcome::ScriptFailed);
});

forgetest_async!(cannot_deploy_with_nonexist_create2, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());
    let create2 = address!("0x0000000000000000000000000000000000b4956c");

    tester
        .add_deployer(0)
        .load_private_keys(&[0])
        .await
        .add_create2_deployer(create2)
        .add_sig("BroadcastTestNoLinking", "deployCreate2()")
        .simulate(ScriptOutcome::ScriptFailed)
        .broadcast(ScriptOutcome::ScriptFailed);
});

forgetest_async!(can_deploy_and_simulate_25_txes_concurrently, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastTestNoLinking", "deployMany()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 25)])
        .await;
});

forgetest_async!(fork_script_reuses_chain_ids, |prj, cmd| {
    static CHAIN_ID_REQUESTS: AtomicUsize = AtomicUsize::new(0);
    CHAIN_ID_REQUESTS.store(0, Ordering::Relaxed);

    let (_api, handle) = spawn(NodeConfig::test()).await;
    let upstream = handle.http_endpoint();
    let client = reqwest::Client::new();
    let app = Router::new().fallback(move |body: BodyBytes| {
        let upstream = upstream.clone();
        let client = client.clone();
        async move {
            let request: Value = serde_json::from_slice(&body).unwrap();
            if request.get("method").and_then(Value::as_str) == Some("eth_chainId") {
                CHAIN_ID_REQUESTS.fetch_add(1, Ordering::Relaxed);
            }
            client
                .post(upstream)
                .header("content-type", "application/json")
                .body(body)
                .send()
                .await
                .unwrap()
                .bytes()
                .await
                .unwrap()
        }
    });
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let endpoint = format!("http://{}", listener.local_addr().unwrap());
    let _proxy = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });

    ScriptTester::new_broadcast(cmd, &endpoint, prj.root())
        .add_deployer(0)
        .add_sig("ScriptAdditionalContracts", "run()")
        .args(&["--chain", "1"])
        .simulate(ScriptOutcome::OkSimulation);

    assert_eq!(CHAIN_ID_REQUESTS.load(Ordering::Relaxed), 2);
    assert_eq!(latest_dry_run_sequence(prj.root()).chain, 31337);
});

forgetest_async!(can_deploy_and_simulate_mixed_broadcast_modes, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastMix", "deployMix()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 15)])
        .await;
});

forgetest_async!(deploy_with_setup, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastTestSetup", "run()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 6)])
        .await;
});

forgetest_async!(fail_broadcast_staticcall, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastTestNoLinking", "errorStaticCall()")
        .simulate(ScriptOutcome::StaticCallNotAllowed);
});

forgetest_async!(check_broadcast_log, |prj, cmd| {
    let (api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    // Prepare CREATE2 Deployer
    let addr = address!("0x4e59b44847b379578588920ca78fbf26c0b4956c");
    let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into();
    api.anvil_set_code(addr, code).await.unwrap();

    tester
        .load_private_keys(&[0])
        .await
        .add_sig("BroadcastTestSetup", "run()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 6)])
        .await;

    // Uncomment to recreate the broadcast log
    // std::fs::copy(
    //     "broadcast/Broadcast.t.sol/31337/run-latest.json",
    //     PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/fixtures/broadcast.
    // log. json" ), );

    // Check broadcast logs
    // Ignore timestamp, blockHash, blockNumber, cumulativeGasUsed, effectiveGasPrice,
    // transactionIndex and logIndex values since they can change in between runs
    let re = Regex::new(r#"((timestamp":).[0-9]*)|((blockHash":).*)|((blockNumber":).*)|((cumulativeGasUsed":).*)|((effectiveGasPrice":).*)|((transactionIndex":).*)|((logIndex":).*)"#).unwrap();

    let fixtures_log = std::fs::read_to_string(
        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("../../testdata/fixtures/broadcast.log.json"),
    )
    .unwrap();
    let _fixtures_log = re.replace_all(&fixtures_log, "");

    let run_log =
        std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap();
    let _run_log = re.replace_all(&run_log, "");

    // similar_asserts::assert_eq!(fixtures_log, run_log);

    // Uncomment to recreate the sensitive log
    // std::fs::copy(
    //     "cache/Broadcast.t.sol/31337/run-latest.json",
    //     PathBuf::from(env!("CARGO_MANIFEST_DIR"))
    //         .join("../../testdata/fixtures/broadcast.sensitive.log.json"),
    // );

    // Check sensitive logs
    // Ignore port number since it can change in between runs
    let re = Regex::new(r":[0-9]+").unwrap();

    let fixtures_log = std::fs::read_to_string(
        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("../../testdata/fixtures/broadcast.sensitive.log.json"),
    )
    .unwrap();
    let fixtures_log = re.replace_all(&fixtures_log, "");

    let run_log = std::fs::read_to_string("cache/Broadcast.t.sol/31337/run-latest.json").unwrap();
    let run_log = re.replace_all(&run_log, "");

    // Clean up carriage return OS differences
    let fixtures_log = fixtures_log.replace("\r\n", "\n");
    let run_log = run_log.replace("\r\n", "\n");

    similar_asserts::assert_eq!(fixtures_log, run_log);
});

forgetest_async!(test_default_sender_balance, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    // Expect the default sender to have uint256.max balance.
    tester
        .add_sig("TestInitialBalance", "runDefaultSender()")
        .simulate(ScriptOutcome::OkSimulation);
});

forgetest_async!(test_custom_sender_balance, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    // Expect the sender to have its starting balance.
    tester
        .add_deployer(0)
        .add_sig("TestInitialBalance", "runCustomSender()")
        .simulate(ScriptOutcome::OkSimulation);
});

// <https://github.com/foundry-rs/foundry/issues/3887>
forgetest_async!(broadcast_log_includes_full_function_abi, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let script = prj.add_script(
        "FullSignature.s.sol",
        r#"
interface Vm {
    function startBroadcast() external;
    function stopBroadcast() external;
}

contract Target {
    uint256 configuredAmount;
    address configuredRecipient;

    function configure(uint256 amount, address recipient)
        external
        returns (bool accepted)
    {
        configuredAmount = amount;
        configuredRecipient = recipient;
        return amount > 0 && recipient != address(0);
    }
}

contract SignatureScript {
    Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

    function run() external {
        vm.startBroadcast();
        Target target = new Target();
        target.configure(1, address(1));
        vm.stopBroadcast();
    }
}
"#,
    );

    cmd.forge_fuse()
        .arg("script")
        .arg(script)
        .args(["--tc", "SignatureScript", "--rpc-url", &handle.http_endpoint()])
        .assert_success();

    let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast"))
        .find(|path| path.ends_with("run-latest.json"))
        .expect("No broadcast artifacts");
    let sequence: ScriptSequence<Ethereum> =
        foundry_common::fs::read_json_file(&run_latest).unwrap();

    assert_eq!(sequence.transactions.len(), 2);
    assert_eq!(sequence.transactions[1].function.as_deref(), Some("configure(uint256,address)"));
    assert_eq!(
        sequence.transactions[1].function_abi.as_deref(),
        Some("function configure(uint256 amount, address recipient) returns (bool accepted)")
    );
});

#[derive(serde::Deserialize)]
struct Transactions {
    transactions: Vec<Transaction>,
}

#[derive(serde::Deserialize)]
struct Transaction {
    arguments: Vec<String>,
}

// test we output arguments <https://github.com/foundry-rs/foundry/issues/3053>
forgetest_async!(can_execute_script_with_arguments, |prj, cmd| {
    cmd.args(["init", "--force"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;
    let script = prj.add_script(
                "Counter.s.sol",
                r#"
import "forge-std/Script.sol";

struct Point {
    uint256 x;
    uint256 y;
}

contract A {
    address a;
    uint b;
    int c;
    bytes32 d;
    bool e;
    bytes f;
    Point g;
    string h;

  constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, Point memory _g, string memory _h) {
    a = _a;
    b = _b;
    c = _c;
    d = _d;
    e = _e;
    f = _f;
    g = _g;
    h = _h;
  }
}

contract Script0 is Script {
  function run() external {
    vm.broadcast();

    new A(msg.sender, 2 ** 32, -1 * (2 ** 32), keccak256(abi.encode(1)), true, "abcdef", Point(10, 99), "hello");
  }
}
   "#,
            );

    cmd
        .forge_fuse()
        .arg("script")
        .arg(script)
        .args([
            "--tc",
            "Script0",
            "--sender",
            "0x00a329c0648769A73afAc7F9381E08FB43dBEA72",
            "--rpc-url",
            handle.http_endpoint().as_str(),
        ])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
...
Script ran successfully.

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================

SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);

    let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast"))
        .find(|path| path.ends_with("run-latest.json"))
        .expect("No broadcast artifacts");

    let content = foundry_common::fs::read_to_string(run_latest).unwrap();

    let transactions: Transactions = serde_json::from_str(&content).unwrap();
    let transactions = transactions.transactions;
    assert_eq!(transactions.len(), 1);
    assert_eq!(
        transactions[0].arguments,
        vec![
            "0x00a329c0648769A73afAc7F9381E08FB43dBEA72".to_string(),
            "4294967296".to_string(),
            "-4294967296".to_string(),
            "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6".to_string(),
            "true".to_string(),
            "0x616263646566".to_string(),
            "(10, 99)".to_string(),
            "hello".to_string(),
        ]
    );
});

// test we output arguments <https://github.com/foundry-rs/foundry/issues/3053>
forgetest_async!(can_execute_script_with_arguments_nested_deploy, |prj, cmd| {
    cmd.args(["init", "--force"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;
    let script = prj.add_script(
        "Counter.s.sol",
        r#"
import "forge-std/Script.sol";

contract A {
  address a;
  uint b;
  int c;
  bytes32 d;
  bool e;
  bytes f;
  string g;

  constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, string memory _g) {
    a = _a;
    b = _b;
    c = _c;
    d = _d;
    e = _e;
    f = _f;
    g = _g;
  }
}

contract B {
  constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, string memory _g) {
    new A(_a, _b, _c, _d, _e, _f, _g);
  }
}

contract Script0 is Script {
  function run() external {
    vm.broadcast();
    new B(msg.sender, 2 ** 32, -1 * (2 ** 32), keccak256(abi.encode(1)), true, "abcdef", "hello");
  }
}
   "#,
    );

    cmd
        .forge_fuse()
        .arg("script")
        .arg(script)
        .args([
            "--tc",
            "Script0",
            "--sender",
            "0x00a329c0648769A73afAc7F9381E08FB43dBEA72",
            "--rpc-url",
            handle.http_endpoint().as_str(),
        ])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
...
Script ran successfully.

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================

SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);

    let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast"))
        .find(|file| file.ends_with("run-latest.json"))
        .expect("No broadcast artifacts");

    let content = foundry_common::fs::read_to_string(run_latest).unwrap();

    let transactions: Transactions = serde_json::from_str(&content).unwrap();
    let transactions = transactions.transactions;
    assert_eq!(transactions.len(), 1);
    assert_eq!(
        transactions[0].arguments,
        vec![
            "0x00a329c0648769A73afAc7F9381E08FB43dBEA72".to_string(),
            "4294967296".to_string(),
            "-4294967296".to_string(),
            "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6".to_string(),
            "true".to_string(),
            "0x616263646566".to_string(),
            "hello".to_string(),
        ]
    );
});

// checks that skipping build
forgetest_init!(can_execute_script_and_skip_contracts, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
contract Demo {
    event log_string(string);
    function run() external returns (uint256 result, uint8) {
        emit log_string("script ran");
        return (255, 3);
    }
}"#,
    );
    cmd.arg("script")
        .arg(script)
        .args(["--skip", "tests", "--skip", TEMPLATE_CONTRACT])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Return ==
result: uint256 255
1: uint8 3

== Logs ==
  script ran

"#]]);
});

forgetest_async!(can_run_script_with_empty_setup, |prj, cmd| {
    let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root());

    tester.add_sig("BroadcastEmptySetUp", "run()").simulate(ScriptOutcome::OkNoEndpoint);
});

forgetest_async!(does_script_override_correctly, |prj, cmd| {
    let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root());

    tester.add_sig("CheckOverrides", "run()").simulate(ScriptOutcome::OkNoEndpoint);
});

forgetest_async!(assert_tx_origin_is_not_overwritten, |prj, cmd| {
    cmd.args(["init", "--force"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    let script = prj.add_script(
        "ScriptTxOrigin.s.sol",
        r#"
import { Script } from "forge-std/Script.sol";

contract ScriptTxOrigin is Script {
    function run() public {
        uint256 pk = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
        vm.startBroadcast(pk); // 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

        ContractA contractA = new ContractA();
        ContractB contractB = new ContractB();

        contractA.test(address(contractB));
        contractB.method(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);

        require(tx.origin == 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38);
        vm.stopBroadcast();
    }
}

contract ContractA {
    function test(address _contractB) public {
        require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "sender 1");
        require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin 1");
        ContractB contractB = ContractB(_contractB);
        ContractC contractC = new ContractC();
        require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "sender 2");
        require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin 2");
        contractB.method(address(this));
        contractC.method(address(this));
        require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "sender 3");
        require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin 3");
    }
}

contract ContractB {
    function method(address sender) public view {
        require(msg.sender == sender, "sender");
        require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin");
    }
}

contract ContractC {
    function method(address sender) public view {
        require(msg.sender == sender, "sender");
        require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin");
    }
}
   "#,
    );

    cmd.forge_fuse()
        .arg("script")
        .arg(script)
        .args(["--tc", "ScriptTxOrigin"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

If you wish to simulate on-chain transactions pass a RPC URL.

"#]]);
});

forgetest_async!(assert_can_create_multiple_contracts_with_correct_nonce, |prj, cmd| {
    cmd.args(["init", "--force"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    let script = prj.add_script(
        "ScriptTxOrigin.s.sol",
        r#"
import {Script, console} from "forge-std/Script.sol";

contract Contract {
  constructor() {
    console.log(tx.origin);
  }
}

contract SubContract {
  constructor() {
    console.log(tx.origin);
  }
}

contract BadContract {
  constructor() {
    new SubContract();
    console.log(tx.origin);
  }
}
contract NestedCreate is Script {
  function run() public {
    address sender = address(uint160(uint(keccak256("woops"))));

    vm.broadcast(sender);
    new BadContract();

    vm.broadcast(sender);
    new Contract();
  }
}
   "#,
    );

    cmd.forge_fuse()
        .arg("script")
        .arg(script)
        .args(["--tc", "NestedCreate"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Logs ==
  0x159E2f2F1C094625A2c6c8bF59526d91454c2F3c
  0x159E2f2F1C094625A2c6c8bF59526d91454c2F3c
  0x159E2f2F1C094625A2c6c8bF59526d91454c2F3c

If you wish to simulate on-chain transactions pass a RPC URL.

"#]]);
});

forgetest_async!(assert_can_detect_target_contract_with_interfaces, |prj, cmd| {
    let script = prj.add_script(
        "ScriptWithInterface.s.sol",
        r#"
contract Script {
  function run() external {}
}

interface Interface {}
            "#,
    );

    cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

"#]]);
});

forgetest_async!(assert_can_detect_unlinked_target_with_libraries, |prj, cmd| {
    let script = prj.add_script(
        "ScriptWithExtLib.s.sol",
        r#"
library Lib {
    function f() public {}
}

contract Script {
    function run() external {
        Lib.f();
    }
}
            "#,
    );

    cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

If you wish to simulate on-chain transactions pass a RPC URL.

"#]]);
});

forgetest_async!(assert_can_resume_with_additional_contracts, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .add_deployer(0)
        .add_sig("ScriptAdditionalContracts", "run()")
        .broadcast(ScriptOutcome::MissingWallet)
        .load_private_keys(&[0])
        .await
        .resume(ScriptOutcome::OkBroadcast);
});

forgetest_async!(can_detect_contract_when_multiple_versions, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());

    prj.add_script(
        "A.sol",
        &format!(
            r#"
pragma solidity {SOLC_VERSION};
import "./B.sol";

contract ScriptA {{}}
"#
        ),
    );

    prj.add_script(
        "B.sol",
        &format!(
            r#"
pragma solidity >={OTHER_SOLC_VERSION} <={SOLC_VERSION};
import 'forge-std/Script.sol';

contract ScriptB is Script {{
    function run() external {{
        vm.broadcast();
        address(0).call("");
    }}
}}
"#
        ),
    );

    prj.add_script(
        "C.sol",
        &format!(
            r#"
pragma solidity {OTHER_SOLC_VERSION};
import "./B.sol";

contract ScriptC {{}}
"#
        ),
    );

    let mut tester = ScriptTester::new(cmd, None, prj.root(), "script/B.sol");
    tester.cmd.forge_fuse().args(["script", "script/B.sol"]);
    tester.simulate(ScriptOutcome::OkNoEndpoint);
});

forgetest_async!(can_sign_with_script_wallet_single, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());

    let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root());
    tester
        .add_sig("ScriptSign", "run()")
        .load_private_keys(&[0])
        .await
        .simulate(ScriptOutcome::OkNoEndpoint);
});

forgetest_async!(can_sign_with_script_wallet_multiple, |prj, cmd| {
    let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root());
    let acc = tester.accounts_pub[0].to_checksum(None);
    tester
        .add_sig("ScriptSign", "run(address)")
        .arg(&acc)
        .load_private_keys(&[0, 1, 2])
        .await
        .simulate(ScriptOutcome::OkRun);
});

forgetest_async!(fails_with_function_name_and_overloads, |prj, cmd| {
    let script = prj.add_script(
        "Script.s.sol",
        r#"
contract Script {
    function run() external {}

    function run(address,uint256) external {}
}
            "#,
    );

    cmd.arg("script").args([&script.to_string_lossy(), "--sig", "run"]);
    cmd.assert_failure().stderr_eq(str![[r#"
Error: Multiple functions with the same name `run` found in the ABI

"#]]);
});

forgetest_async!(can_decode_custom_errors, |prj, cmd| {
    cmd.args(["init", "--force"]).arg(prj.root()).assert_success().stdout_eq(str![""]).stderr_eq(
        str![[r#"
Warning: Target directory is not empty, but `--force` was specified
Initializing [..]...
Installing forge-std in [..] (url: https://github.com/foundry-rs/forge-std, tag: None)
...
    Installed forge-std[..]
    Initialized forge project

"#]],
    );

    let script = prj.add_script(
        "CustomErrorScript.s.sol",
        r#"
import { Script } from "forge-std/Script.sol";

contract ContractWithCustomError {
    error CustomError();

    constructor() {
        revert CustomError();
    }
}

contract CustomErrorScript is Script {
    ContractWithCustomError test;

    function run() public {
        test = new ContractWithCustomError();
    }
}
"#,
    );

    cmd.forge_fuse().arg("script").arg(script).args(["--tc", "CustomErrorScript"]);
    cmd.assert_failure().stderr_eq(str![[r#"
Error: script failed: CustomError()

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/7620
forgetest_async!(can_run_zero_base_fee, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract SimpleScript is Script {
    function run() external returns (bool success) {
        vm.startBroadcast();
        (success, ) = address(0).call("");
    }
}
   "#,
    );

    let node_config = NodeConfig::test().with_base_fee(Some(0));
    let (_api, handle) = spawn(node_config).await;
    let dev = handle.dev_accounts().next().unwrap();

    // Firstly run script with non-zero gas prices to ensure that eth_feeHistory contains non-zero
    // values.
    cmd.args([
        "script",
        "SimpleScript",
        "--fork-url",
        &handle.http_endpoint(),
        "--sender",
        format!("{dev:?}").as_str(),
        "--broadcast",
        "--unlocked",
        "--with-gas-price",
        "2000000",
        "--priority-gas-price",
        "100000",
        "--non-interactive",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
...
Script ran successfully.

== Return ==
success: bool true

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_GAS_PRICE]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]).stderr_eq(str![[r#"
Warning: Script contains a transaction to 0x0000000000000000000000000000000000000000 which does not contain any code.

"#]]);

    // Ensure that we can correctly estimate gas when base fee is zero but priority fee is not.
    cmd.forge_fuse()
        .args([
            "script",
            "SimpleScript",
            "--fork-url",
            &handle.http_endpoint(),
            "--sender",
            format!("{dev:?}").as_str(),
            "--broadcast",
            "--unlocked",
            "--non-interactive",
        ])
        .assert_success()
        .stdout_eq(str![[r#"
No files changed, compilation skipped
...
Script ran successfully.

== Return ==
success: bool true

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]).stderr_eq(str![[r#"
Warning: Script contains a transaction to 0x0000000000000000000000000000000000000000 which does not contain any code.

"#]]);
});

// Asserts that the script runs with expected non-output using `--quiet` flag
forgetest_async!(adheres_to_quiet_flag, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract SimpleScript is Script {
    function run() external returns (bool success) {
        vm.startBroadcast();
        (success, ) = address(0).call("");
    }
}
   "#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;

    cmd.args([
        "script",
        "SimpleScript",
        "--fork-url",
        &handle.http_endpoint(),
        "--sender",
        "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
        "--broadcast",
        "--unlocked",
        "--non-interactive",
        "--quiet",
    ])
    .assert_empty_stdout();
});

// Asserts that the script runs with expected non-output using `--quiet` flag
forgetest_async!(adheres_to_json_flag, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.update_config(|config| {
        config.tracing.verbosity = 4;
        config.tracing.trace_depth = Some(1);
    });
    prj.add_script(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract SimpleScript is Script {
    function run() external returns (bool success) {
        vm.startBroadcast();
        (success, ) = address(0).call("");
    }
}
   "#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;

    cmd.args([
        "script",
        "SimpleScript",
        "--fork-url",
        &handle.http_endpoint(),
        "--sender",
        "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
        "--broadcast",
        "--unlocked",
        "--non-interactive",
        "--json",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"SimpleScript","return_data":"true","call_data":{"signature":"run()","args":[]}}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940999,"gas_refund_counter":0,"status":"Return","steps":[],"decoded":{"label":"VM","return_data":null,"call_data":{"signature":"startBroadcast()","args":[]}}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940650,"gas_refund_counter":0,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null}
{"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}","token_symbol":"ETH","estimated_max_fee_per_gas":"{...}","estimated_base_fee_per_gas":"{...}","estimated_max_priority_fee_per_gas":"{...}"}
{"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"}
{"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"}

"#]].is_jsonlines());
});

forgetest!(script_json_trace_depth_removes_nested_nodes, |prj, cmd| {
    prj.update_config(|config| {
        config.verbosity = 0;
        config.tracing.verbosity = 4;
        config.tracing.trace_depth = Some(0);
    });
    prj.add_script(
        "DepthScript",
        r#"
contract Child {
    function call() external {}
}

contract DepthScript {
    function run() external {
        Child child = new Child();
        child.call();
    }
}
   "#,
    );

    let output =
        cmd.args(["script", "DepthScript", "--json"]).assert_success().get_output().stdout.clone();
    let output: Value = serde_json::from_slice(&output).unwrap();
    let traces = output["traces"].as_array().unwrap();
    assert!(!traces.is_empty());

    for trace in traces {
        let arena = trace[1]["arena"].as_array().unwrap();
        assert_eq!(arena.len(), 1);
        assert!(arena[0]["children"].as_array().unwrap().is_empty());
    }
});

// https://github.com/foundry-rs/foundry/issues/10050
forgetest_init!(json_trace_uses_pranked_caller, |prj, cmd| {
    prj.add_source(
        "Target",
        r#"
contract Target {
    address public observedCaller;

    function increment() external {
        observedCaller = msg.sender;
    }
}
   "#,
    );
    prj.add_script(
        "PrankTrace",
        r#"
import "forge-std/Script.sol";
import {Target} from "../src/Target.sol";

contract PrankTrace is Script {
    function run() external {
        vm.startPrank(address(0x1234));
        Target target = Target(vm.deployCode("src/Target.sol:Target"));
        target.increment();
        require(target.observedCaller() == address(0x1234));
        vm.stopPrank();
    }
}
   "#,
    );

    let output = cmd
        .args(["script", "PrankTrace", "-vvvv", "--json"])
        .assert_success()
        .get_output()
        .stdout
        .clone();
    let output: Value = serde_json::from_slice(&output).unwrap();
    let execution =
        output["traces"].as_array().unwrap().iter().find(|trace| trace[0] == "Execution").unwrap();
    let increment = execution[1]["arena"]
        .as_array()
        .unwrap()
        .iter()
        .find(|node| node["trace"]["data"] == "0xd09de08a")
        .unwrap();
    let deployment = execution[1]["arena"]
        .as_array()
        .unwrap()
        .iter()
        .find(|node| node["trace"]["kind"] == "CREATE")
        .unwrap();

    assert_eq!(increment["trace"]["caller"], "0x0000000000000000000000000000000000001234");
    assert_eq!(deployment["trace"]["caller"], "0x0000000000000000000000000000000000001234");
});

forgetest_init!(json_trace_uses_pranked_create_caller, |prj, cmd| {
    prj.add_script(
        "PrankCreateTrace",
        r#"
import "forge-std/Script.sol";

contract Target {
    address public creator = msg.sender;
}

contract PrankCreateTrace is Script {
    function run() external {
        vm.startPrank(address(0x1234));
        Target created = new Target();
        Target created2 = new Target{salt: bytes32(uint256(1))}();
        require(created.creator() == address(0x1234));
        require(created2.creator() == address(0x1234));
        vm.stopPrank();
    }
}
   "#,
    );

    let output = cmd
        .args(["script", "PrankCreateTrace", "-vvvv", "--json"])
        .assert_success()
        .get_output()
        .stdout
        .clone();
    let output: Value = serde_json::from_slice(&output).unwrap();
    let execution =
        output["traces"].as_array().unwrap().iter().find(|trace| trace[0] == "Execution").unwrap();
    let deployments = execution[1]["arena"]
        .as_array()
        .unwrap()
        .iter()
        .filter(|node| matches!(node["trace"]["kind"].as_str(), Some("CREATE" | "CREATE2")))
        .collect::<Vec<_>>();

    assert_eq!(deployments.len(), 2);
    assert!(deployments.iter().any(|node| node["trace"]["kind"] == "CREATE2"));
    assert!(
        deployments
            .iter()
            .all(|node| node["trace"]["caller"] == "0x0000000000000000000000000000000000001234")
    );
});

// https://github.com/foundry-rs/foundry/pull/7742
forgetest_async!(unlocked_no_sender, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract SimpleScript is Script {
    function run() external returns (bool success) {
        vm.startBroadcast(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);
        (success, ) = address(0).call("");
    }
}
   "#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;

    cmd.args([
        "script",
        "SimpleScript",
        "--fork-url",
        &handle.http_endpoint(),
        "--broadcast",
        "--unlocked",
        "--non-interactive",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
...
Script ran successfully.

== Return ==
success: bool true

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]).stderr_eq(str![[r#"
Warning: Script contains a transaction to 0x0000000000000000000000000000000000000000 which does not contain any code.

"#]]);
});

// https://github.com/foundry-rs/foundry/issues/7833
forgetest_async!(error_no_create2, |prj, cmd| {
    let (_api, handle) =
        spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await;

    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract SimpleContract {}

contract SimpleScript is Script {
    function run() external {
        vm.startBroadcast(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);
        new SimpleContract{salt: bytes32(0)}();
    }
}
   "#,
    );

    cmd.args([
        "script",
        "SimpleScript",
        "--fork-url",
        &handle.http_endpoint(),
        "--broadcast",
        "--unlocked",
    ]);

    cmd.assert_failure().stderr_eq(str![[r#"
Error: script failed: missing CREATE2 deployer: 0x4e59b44847b379578588920cA78FbF26c0B4956C

"#]]);
});

forgetest_async!(can_switch_forks_in_setup, |prj, cmd| {
    let (_api, handle) =
        spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await;

    foundry_test_utils::util::initialize(prj.root());
    let url = handle.http_endpoint();

    prj.add_script(
        "Foo",
        &r#"
import "forge-std/Script.sol";

contract SimpleScript is Script {
    function setUp() external {
        uint256 initialFork = vm.activeFork();
        vm.createSelectFork("<url>");
        vm.selectFork(initialFork);
    }

    function run() external {
        assert(vm.getNonce(msg.sender) == 0);
    }
}
   "#
        .replace("<url>", &url),
    );

    cmd.args([
        "script",
        "SimpleScript",
        "--fork-url",
        &url,
        "--sender",
        "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful with warnings:
Warning (2018): Function state mutability can be restricted to view
  [FILE]:13:5:
   |
13 |     function run() external {
   |     ^ (Relevant source part starts here and spans across multiple lines).

Script ran successfully.

"#]]);
});

// Asserts that running the same script twice only deploys library once.
forgetest_async!(can_deploy_library_create2, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0, 1])
        .await
        .add_sig("BroadcastTest", "deploy()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 2), (1, 1)])
        .await;

    tester.clear();

    tester
        .load_private_keys(&[0, 1])
        .await
        .add_sig("BroadcastTest", "deploy()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 1), (1, 1)])
        .await;
});

// Asserts that running the same script twice only deploys library once when using different
// senders.
forgetest_async!(can_deploy_library_create2_different_sender, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root());

    tester
        .load_private_keys(&[0, 1])
        .await
        .add_sig("BroadcastTest", "deploy()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(0, 2), (1, 1)])
        .await;

    tester.clear();

    // Run different script from the same contract (which requires the same library).
    tester
        .load_private_keys(&[2])
        .await
        .add_sig("BroadcastTest", "deployNoArgs()")
        .simulate(ScriptOutcome::OkSimulation)
        .broadcast(ScriptOutcome::OkBroadcast)
        .assert_nonce_increment(&[(2, 2)])
        .await;
});

// <https://github.com/foundry-rs/foundry/issues/8993>
forgetest_async!(test_broadcast_raw_create2_deployer, |prj, cmd| {
    let (api, handle) = spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await;

    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract SimpleScript is Script {
    function run() external {
        // send funds to create2 factory deployer
        vm.startBroadcast();
        payable(0x3fAB184622Dc19b6109349B94811493BF2a45362).transfer(10000000 gwei);
        // deploy create2 factory
        vm.broadcastRawTransaction(
            hex"f8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222"
        );
    }
}
   "#,
    );

    cmd.args([
        "script",
        "--private-key",
        "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
        "--rpc-url",
        &handle.http_endpoint(),
        "--broadcast",
        "--slow",
        "SimpleScript",
    ]);

    cmd.assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);

    assert!(
        !api.get_code(address!("0x4e59b44847b379578588920cA78FbF26c0B4956C"), Default::default())
            .await
            .unwrap()
            .is_empty()
    );
});

forgetest_init!(can_get_script_wallets, |prj, cmd| {
    let script = prj.add_source(
        "Foo",
        r#"
import "forge-std/Script.sol";

interface Vm {
    function getWallets() external view returns (address[] memory wallets);
}

contract WalletScript is Script {
    function run() public view {
        address[] memory wallets = Vm(address(vm)).getWallets();
        console.log(wallets[0]);
    }
}"#,
    );
    cmd.arg("script")
        .arg(script)
        .args([
            "--private-key",
            "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6",
            "-v",
        ])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Logs ==
  0xa0Ee7A142d267C1f36714E4a8F75612F20a79720

"#]]);
});

forgetest_init!(can_remember_keys, |prj, cmd| {
    let script = prj
        .add_source(
            "Foo",
            r#"
import "forge-std/Script.sol";

interface Vm {
    function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs);
}

contract WalletScript is Script {
    function run() public {
        string memory mnemonic = "test test test test test test test test test test test junk";
        string memory derivationPath = "m/44'/60'/0'/0/";
        address[] memory wallets = Vm(address(vm)).rememberKeys(mnemonic, derivationPath, 3);
        for (uint256 i = 0; i < wallets.length; i++) {
            console.log(wallets[i]);
        }
    }
}"#,
        );
    cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.
[GAS]

== Logs ==
  0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  0x70997970C51812dc3A010C7d01b50e0d17dc79C8
  0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC

"#]]);
});

forgetest_async!(can_simulate_with_default_sender, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    foundry_test_utils::util::initialize(prj.root());
    prj.update_config(|config| {
        config.verbosity = 0;
        config.tracing.verbosity = 4;
        config.tracing.trace_depth = Some(0);
    });
    prj.add_script(
        "Script.s.sol",
        r#"
import "forge-std/Script.sol";
contract A {
    function getValue() external pure returns (uint256) {
        return 100;
    }
}
contract B {
    constructor(A a) {
        require(a.getValue() == 100);
    }
}
contract SimpleScript is Script {
    function run() external {
        vm.startBroadcast();
        A a = new A();
        new B(a);
    }
}
            "#,
    );

    cmd.arg("script").args(["SimpleScript", "--fork-url", &handle.http_endpoint()]);
    cmd.assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] SimpleScript::run()
    └─ ← [Stop]


Script ran successfully.

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [..] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519
    └─ ← [Return] 175 bytes of code

  [..] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
    └─ ← [Return] 62 bytes of code
...
"#]]);
});

forgetest_async!(should_detect_additional_contracts, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract Simple {}

contract Deployer {
    function deploy() public {
        new Simple();
    }
}

contract ContractScript is Script {
    function run() public {
        vm.startBroadcast();
        Deployer deployer = new Deployer();
        deployer.deploy();
    }
}
   "#,
    );
    cmd.arg("script")
        .args([
            "ContractScript",
            "--private-key",
            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
            "--rpc-url",
            &handle.http_endpoint(),
        ])
        .assert_success();

    let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast"))
        .find(|file| file.ends_with("run-latest.json"))
        .expect("No broadcast artifacts");

    let sequence: ScriptSequence<Ethereum> =
        foundry_common::fs::read_json_file(&run_latest).unwrap();

    assert_eq!(sequence.transactions.len(), 2);
    assert_eq!(sequence.transactions[1].additional_contracts.len(), 1);
});

// <https://github.com/foundry-rs/foundry/issues/9661>
forgetest_async!(should_set_correct_sender_nonce_via_cli, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "MyScript.s.sol",
        r#"
        import {Script, console} from "forge-std/Script.sol";

    contract MyScript is Script {
        function run() public view {
            console.log("sender nonce", vm.getNonce(msg.sender));
        }
    }
    "#,
    );

    let rpc_url = next_http_archive_rpc_url();

    let fork_bn = 21614115;

    cmd.forge_fuse()
        .args([
            "script",
            "MyScript",
            "--sender",
            "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97",
            "--fork-block-number",
            &fork_bn.to_string(),
            "--rpc-url",
            &rpc_url,
        ])
        .assert_success()
        .stdout_eq(str![[r#"[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
...
== Logs ==
  sender nonce 1124703[..]"#]]);
});

forgetest_async!(should_override_sender_nonce_via_cli, |prj, cmd| {
    let (_api, handle) =
        spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await;

    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "MyScript.s.sol",
        r#"
        import {Script} from "forge-std/Script.sol";

        library Lib {
            function value() public pure returns (uint256) {
                return 42;
            }
        }

        contract UsesLib {
            uint256 public immutable value;

            constructor() {
                value = Lib.value();
            }
        }

        contract MyScript is Script {
            function run() public {
                vm.startBroadcast();
                new UsesLib();
                vm.stopBroadcast();
            }
        }
        "#,
    );

    cmd.args([
        "script",
        "MyScript",
        "--sender",
        "0x1000000000000000000000000000000000000000",
        "--sender-nonce",
        "7",
        "--rpc-url",
        &handle.http_endpoint(),
    ])
    .assert_success()
    .stdout_eq(str![[r#"[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================

SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

forgetest_async!(dryrun_without_broadcast, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;

    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "Foo",
        r#"
import "forge-std/Script.sol";

contract Called {
    event log_string(string);
    uint256 public x;
    uint256 public y;
    function run(uint256 _x, uint256 _y) external returns (uint256 result) {
        x = _x;
        y = _y;
        emit log_string("script ran");
        return _x + _y;
    }
}

contract DryRunTest is Script {
    function run() external {
        vm.startBroadcast();
        Called called = new Called();
        called.run(123, 456);
    }
}
   "#,
    );

    cmd.arg("script")
        .args([
            "DryRunTest",
            "--private-key",
            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
            "--rpc-url",
            &handle.http_endpoint(),
            "-vvvv",
        ])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] DryRunTest::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return]
    ├─ [..] → new Called@0x5FbDB2315678afecb367f032d93F642f64180aa3
    │   └─ ← [Return] 700 bytes of code
    ├─ [..] Called::run(123, 456)
    │   ├─ emit log_string(val: "script ran")
    │   └─ ← [Return] 579
    └─ ← [Stop]


Script ran successfully.

== Logs ==
  script ran

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [140181] → new Called@0x5FbDB2315678afecb367f032d93F642f64180aa3
    └─ ← [Return] 700 bytes of code

  [46966] Called::run(123, 456)
    ├─ emit log_string(val: "script ran")
    └─ ← [Return] 579


==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================

=== Transactions that will be broadcast ===


Chain 31337

### Transaction 1 ###

accessList           []
chainId              31337
gasLimit             [..]
gasPrice             
input                [..]
maxFeePerBlobGas     
maxFeePerGas         
maxPriorityFeePerGas 
nonce                0
to                   
type                 EIP-1559
value                0

### Transaction 2 ###

accessList           []
chainId              31337
gasLimit             99920
gasPrice             
input                0x7357f5d2000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8
maxFeePerBlobGas     
maxFeePerGas         
maxPriorityFeePerGas 
nonce                1
to                   0x5FbDB2315678afecb367f032d93F642f64180aa3
type                 EIP-1559
value                0
contract: Called(0x5FbDB2315678afecb367f032d93F642f64180aa3)
data (decoded): run(
  123,
  456
)


SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

forgetest_async!(unused_libraries_conditional_6215, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "UnusedLibraries",
        r#"
import "forge-std/Script.sol";
library Lib1 { function value() external pure returns (uint256) { return 1; } }
library Lib2 { function value() external pure returns (uint256) { return 2; } }
contract ContractUsingLib1 { function value() external view returns (uint256) { return Lib1.value(); } }
contract ContractUsingLib2 { function value() external view returns (uint256) { return Lib2.value(); } }
contract UnusedLibraries is Script {
    function run(uint256 which) external {
        vm.startBroadcast();
        if (which == 1) new ContractUsingLib1();
        else new ContractUsingLib2();
        vm.stopBroadcast();
    }
}
"#,
    );
    cmd.arg("script")
        .args(["UnusedLibraries", "--sig", "run(uint256)", "1", "--rpc-url"])
        .arg(handle.http_endpoint())
        .assert_success();
    let sequence = latest_dry_run_sequence(prj.root());
    let names = sequence
        .transactions
        .iter()
        .filter_map(|tx| tx.contract_name.as_deref())
        .collect::<Vec<_>>();
    assert_eq!(names, ["Lib1", "ContractUsingLib1"]);
    assert_eq!(sequence.libraries.len(), 1);
    assert!(sequence.libraries[0].contains(":Lib1:"));
});

forgetest_async!(wallet_signing_skips_library_optimization, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "WalletSigningLibraries",
        r#"
import "forge-std/Script.sol";
library SignLib1 { function value() external pure returns (uint256) { return 1; } }
library SignLib2 { function value() external pure returns (uint256) { return 2; } }
contract UsesSignLib1 { function value() external view returns (uint256) { return SignLib1.value(); } }
contract UsesSignLib2 { function value() external view returns (uint256) { return SignLib2.value(); } }
contract WalletSigningLibraries is Script {
    function run(uint256 overload) external {
        bytes32 digest = keccak256("digest");
        address signer = vm.rememberKey(1);
        bytes32 r;
        if (overload == 0) (, r,) = vm.sign(digest);
        else if (overload == 1) (, r,) = vm.sign(signer, digest);
        else if (overload == 2) (r,) = vm.signCompact(digest);
        else (r,) = vm.signCompact(signer, digest);
        require(r != bytes32(0));

        vm.startBroadcast(1);
        if (overload < 4) new UsesSignLib1();
        else new UsesSignLib2();
        vm.stopBroadcast();
    }
}
"#,
    );
    for overload in 0..4 {
        cmd.forge_fuse()
            .arg("script")
            .args(["WalletSigningLibraries", "--sig", "run(uint256)"])
            .arg(overload.to_string())
            .args([
                "--rpc-url",
                handle.http_endpoint().as_str(),
                "--sender",
                "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf",
            ])
            .assert_success();
        let sequence = latest_dry_run_sequence(prj.root());
        let names = sequence
            .transactions
            .iter()
            .filter_map(|tx| tx.contract_name.as_deref())
            .collect::<Vec<_>>();
        assert_eq!(names, ["SignLib1", "SignLib2", "UsesSignLib1"], "overload {overload}");
    }
});

forgetest_async!(candidate_rpc_side_effect_is_blocked, |prj, cmd| {
    let (api, handle) = spawn(NodeConfig::test()).await;
    foundry_test_utils::util::initialize(prj.root());
    assert_eq!(
        foundry_common::LIBRARY_DEPLOYER.create(0),
        address!("0x5F65cD7D792E9746EF82929D60de9a1C526f93A5")
    );
    prj.add_source(
        "CandidateRpcSideEffect",
        r#"
import "forge-std/Script.sol";
library LocalLib { function value() external pure returns (uint256) { return 2; } }
library RequiredLib { function value() external pure returns (uint256) { return 1; } }
contract UsesRequiredLib { function value() external view returns (uint256) { return RequiredLib.value(); } }
contract CandidateRpcSideEffect is Script {
    address constant LOCAL_LIB = 0x5F65cD7D792E9746EF82929D60de9a1C526f93A5;
    address constant MUTATED = 0x0000000000000000000000000000000000001234;

    function run() external {
        if (address(LocalLib) == LOCAL_LIB) {
            vm.rpc("anvil_setBalance", string.concat("[\"", vm.toString(MUTATED), "\", \"0x1\"]"));
        }
        vm.startBroadcast();
        new UsesRequiredLib();
        vm.stopBroadcast();
    }
}
"#,
    );
    cmd.arg("script")
        .args(["CandidateRpcSideEffect", "--rpc-url"])
        .arg(handle.http_endpoint())
        .assert_success();

    let sequence = latest_dry_run_sequence(prj.root());
    let names = sequence
        .transactions
        .iter()
        .filter_map(|tx| tx.contract_name.as_deref())
        .collect::<Vec<_>>();
    assert_eq!(names, ["LocalLib", "RequiredLib", "UsesRequiredLib"]);
    let mutated = address!("0x0000000000000000000000000000000000001234");
    assert_eq!(api.balance(mutated, None).await.unwrap(), U256::ZERO);
});

forgetest_async!(library_optimization_skips_changed_fork_block, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "ChangedForkLibraries",
        r#"
import "forge-std/Script.sol";
library ForkLib1 { function value() external pure returns (uint256) { return 1; } }
library ForkLib2 { function value() external pure returns (uint256) { return 2; } }
contract UsesForkLib1 { function value() external view returns (uint256) { return ForkLib1.value(); } }
contract UsesForkLib2 { function value() external view returns (uint256) { return ForkLib2.value(); } }
contract ChangedForkLibraries is Script {
    function run(uint256 which) external {
        vm.rollFork(block.number);
        vm.startBroadcast();
        if (which == 1) new UsesForkLib1();
        else new UsesForkLib2();
        vm.stopBroadcast();
    }
}
"#,
    );
    cmd.arg("script")
        .args(["ChangedForkLibraries", "--sig", "run(uint256)", "1", "--rpc-url"])
        .arg(handle.http_endpoint())
        .assert_success();
    let sequence = latest_dry_run_sequence(prj.root());
    let names = sequence
        .transactions
        .iter()
        .filter_map(|tx| tx.contract_name.as_deref())
        .collect::<Vec<_>>();
    assert_eq!(names, ["ForkLib1", "ForkLib2", "UsesForkLib1"]);
});

forgetest_async!(unused_library_called_locally_before_direct_create, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "OutsideLibrary",
        r#"
import "forge-std/Script.sol";
library OutsideLib { function value() external pure returns (uint256) { return 1; } }
library RequiredLib { function value() external pure returns (uint256) { return 2; } }
contract UsesRequiredLib { function value() external view returns (uint256) { return RequiredLib.value(); } }
contract OutsideLibrary is Script {
    function run() external {
        OutsideLib.value();
        vm.startBroadcast();
        new UsesRequiredLib();
        vm.stopBroadcast();
    }
}
"#,
    );
    cmd.arg("script")
        .args(["OutsideLibrary", "--rpc-url"])
        .arg(handle.http_endpoint())
        .assert_success();
    let sequence = latest_dry_run_sequence(prj.root());
    let names = sequence
        .transactions
        .iter()
        .filter_map(|tx| tx.contract_name.as_deref())
        .collect::<Vec<_>>();
    assert_eq!(names, ["RequiredLib", "UsesRequiredLib"]);
    assert_eq!(sequence.libraries.len(), 1);
    assert!(sequence.libraries[0].contains(":RequiredLib:"));
});

// Tests warn when artifact source file no longer exists.
// <https://github.com/foundry-rs/foundry/issues/9068>
forgetest_init!(should_warn_if_artifact_source_no_longer_exists, |prj, cmd| {
    prj.initialize_default_contracts();
    cmd.args(["script", "script/Counter.s.sol"]).assert_success().stdout_eq(str![[r#"
...
Script ran successfully.
...

"#]]);
    fs::rename(
        prj.paths().scripts.join("Counter.s.sol"),
        prj.paths().scripts.join("Counter1.s.sol"),
    )
    .unwrap();
    cmd.forge_fuse().args(["script", "script/Counter1.s.sol"]).assert_success().stderr_eq(str![[r#"
...
Warning: Detected artifacts built from source files that no longer exist. Run `forge clean` to make sure builds are in sync with project files.
 - [..]script/Counter.s.sol
...

"#]])
        .stdout_eq(str![[r#"
...
Script ran successfully.
...

"#]]);
});

// Tests that script reverts if it uses `address(this)`.
forgetest_init!(should_revert_on_address_opcode, |prj, cmd| {
    prj.add_script(
        "ScriptWithAddress.s.sol",
        r#"
        import {Script, console} from "forge-std/Script.sol";

    contract ScriptWithAddress is Script {
        function run() public view {
            console.log("script address", address(this));
        }
    }
    "#,
    );

    cmd.arg("script").arg("ScriptWithAddress").assert_failure().stderr_eq(str![[r#"
Error: script failed: Usage of `address(this)` detected in script contract. Script contracts are ephemeral and their addresses should not be relied upon.

"#]]);

    // Disable script protection.
    prj.update_config(|config| {
        config.script_execution_protection = false;
    });
    cmd.assert_success().stdout_eq(str![[r#"
...
Script ran successfully.
...

"#]]);
});

// Tests that script warns if no tx to broadcast.
// <https://github.com/foundry-rs/foundry/issues/10015>
forgetest_async!(warns_if_no_transactions_to_broadcast, |prj, cmd| {
    let (_api, handle) = spawn(NodeConfig::test()).await;
    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "NoTxScript.s.sol",
        r#"
        import {Script} from "forge-std/Script.sol";

    contract NoTxScript is Script {
        function run() public {
            vm.startBroadcast();
            // No real tx created
            vm.stopBroadcast();
        }
    }
    "#,
    );

    cmd.args([
        "script",
        "--private-key",
        "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
        "--rpc-url",
        &handle.http_endpoint(),
        "--broadcast",
        "NoTxScript",
    ])
    .assert_success()
    .stderr_eq(str![
        r#"
Warning: No transactions to broadcast.

"#
    ]);
});

// Tests EIP-7702 broadcast <https://github.com/foundry-rs/foundry/issues/10461>
forgetest_async!(can_broadcast_txes_with_signed_auth, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.initialize_default_contracts();
    prj.add_script(
            "EIP7702Script.s.sol",
            r#"
import "forge-std/Script.sol";
import {Vm} from "forge-std/Vm.sol";
import {Counter} from "../src/Counter.sol";
contract EIP7702Script is Script {
    uint256 constant PRIVATE_KEY = uint256(bytes32(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80));
    address constant SENDER = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
    function setUp() public {}
    function run() public {
        vm.startBroadcast(PRIVATE_KEY);
        Counter counter = new Counter();
        Counter counter1 = new Counter();
        Counter counter2 = new Counter();
        vm.signAndAttachDelegation(address(counter), PRIVATE_KEY);
        Counter(SENDER).increment();
        Counter(SENDER).increment();
        vm.signAndAttachDelegation(address(counter1), PRIVATE_KEY);
        Counter(SENDER).setNumber(0);
        vm.signAndAttachDelegation(address(counter2), PRIVATE_KEY);
        Counter(SENDER).setNumber(0);
        vm.stopBroadcast();
    }
}
   "#,
        );

    let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
    let (_api, handle) = spawn(node_config).await;

    cmd.args([
        "script",
        "script/EIP7702Script.s.sol",
        "--rpc-url",
        &handle.http_endpoint(),
        "-vvvvv",
        "--non-interactive",
        "--slow",
        "--broadcast",
        "--evm-version",
        "prague",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] EIP7702Script::setUp()
    └─ ← [Stop]

  [..] EIP7702Script::run()
    ├─ [0] VM::startBroadcast(<pk>)
    │   └─ ← [Return]
    ├─ [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3
    │   └─ ← [Return] 481 bytes of code
    ├─ [..] → new Counter@0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
    │   └─ ← [Return] 481 bytes of code
    ├─ [..] → new Counter@0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
    │   └─ ← [Return] 481 bytes of code
    ├─ [0] VM::signAndAttachDelegation(0x5FbDB2315678afecb367f032d93F642f64180aa3, "<pk>")
    │   └─ ← [Return] (0, 0xd4301eb9f82f747137a5f2c3dc3a5c2d253917cf99ecdc0d49f7bb85313c3159, 0x786d354f0bbd456f44116ddd3aa50475e989d72d8396005e5b3a12cede83fb68, 4, 0x5FbDB2315678afecb367f032d93F642f64180aa3)
    ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment()
    │   ├─  storage changes:
    │   │   @ 0: 0 → 1
    │   └─ ← [Stop]
    ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment()
    │   ├─  storage changes:
    │   │   @ 0: 1 → 2
    │   └─ ← [Stop]
    ├─ [0] VM::signAndAttachDelegation(0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512, "<pk>")
    │   └─ ← [Return] (0, 0xaba9128338f7ff036a0d2ecb96d4f4376389005cd565f87aba33b312570af962, 0x69acbe0831fb8ca95338bc4b908dcfebaf7b81b0f770a12c073ceb07b89fbdf3, 7, 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512)
    ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0)
    │   ├─  storage changes:
    │   │   @ 0: 2 → 0
    │   └─ ← [Stop]
    ├─ [0] VM::signAndAttachDelegation(0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0, "<pk>")
    │   └─ ← [Return] (1, 0x3a3427b66e589338ce7ea06135650708f9152e93e257b4a5ec6eb86a3e09a2ce, 0x444651c354c89fd3312aafb05948e12c0a16220827a5e467705253ab4d8aa8d3, 9, 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0)
    ├─ [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0)
    │   └─ ← [Stop]
    ├─ [0] VM::stopBroadcast()
    │   └─ ← [Return]
    └─ ← [Stop]


Script ran successfully.

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3
    └─ ← [Return] 481 bytes of code

  [..] → new Counter@0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
    └─ ← [Return] 481 bytes of code

  [..] → new Counter@0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
    └─ ← [Return] 481 bytes of code

  [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment()
    ├─  storage changes:
    │   @ 0: 0 → 1
    └─ ← [Stop]

  [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::increment()
    ├─  storage changes:
    │   @ 0: 1 → 2
    └─ ← [Stop]

  [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0)
    ├─  storage changes:
    │   @ 0: 2 → 0
    └─ ← [Stop]

  [..] 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266::setNumber(0)
    └─ ← [Stop]


==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

// Tests EIP-7702 with multiple auth <https://github.com/foundry-rs/foundry/issues/10551>
// Alice sends 5 ETH from Bob to Receiver1 and 1 ETH to Receiver2
forgetest_async!(can_broadcast_txes_with_multiple_auth, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "BatchCallDelegation.sol",
        r#"
contract BatchCallDelegation {
    event CallExecuted(address indexed to, uint256 indexed value, bytes data, bool success);

    struct Call {
        bytes data;
        address to;
        uint256 value;
    }

    function execute(Call[] calldata calls) external payable {
        for (uint256 i = 0; i < calls.length; i++) {
            Call memory call = calls[i];
            (bool success,) = call.to.call{value: call.value}(call.data);
            require(success, "call reverted");
            emit CallExecuted(call.to, call.value, call.data, success);
        }
    }
}
   "#,
    );

    prj.add_script(
            "BatchCallDelegationScript.s.sol",
            r#"
import {Script, console} from "forge-std/Script.sol";
import {Vm} from "forge-std/Vm.sol";
import {BatchCallDelegation} from "../src/BatchCallDelegation.sol";

contract BatchCallDelegationScript is Script {
    // Alice's address and private key (EOA with no initial contract code).
    address payable ALICE_ADDRESS = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8);
    uint256 constant ALICE_PK = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;

    // Bob's address and private key (Bob will execute transactions on Alice's behalf).
    address constant BOB_ADDRESS = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC;
    uint256 constant BOB_PK = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a;

    address constant RECEIVER_1 = 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955;
    address constant RECEIVER_2 = 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc;

    uint256 constant DEPLOYER_PK = 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6;

    function run() public {
        BatchCallDelegation.Call[] memory aliceCalls = new BatchCallDelegation.Call[](1);
        aliceCalls[0] = BatchCallDelegation.Call({to: RECEIVER_1, value: 5 ether, data: ""});

        BatchCallDelegation.Call[] memory bobCalls = new BatchCallDelegation.Call[](2);
        bobCalls[0] = BatchCallDelegation.Call({to: RECEIVER_1, value: 5 ether, data: ""});
        bobCalls[1] = BatchCallDelegation.Call({to: RECEIVER_2, value: 1 ether, data: ""});

        vm.startBroadcast(DEPLOYER_PK);
        BatchCallDelegation batcher = new BatchCallDelegation();
        vm.stopBroadcast();

        vm.startBroadcast(ALICE_PK);
        vm.signAndAttachDelegation(address(batcher), ALICE_PK);
        vm.signAndAttachDelegation(address(batcher), BOB_PK);
        vm.signAndAttachDelegation(address(batcher), BOB_PK);

        BatchCallDelegation(BOB_ADDRESS).execute(bobCalls);

        vm.stopBroadcast();
    }
}
   "#,
        );

    let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
    let (api, handle) = spawn(node_config).await;

    cmd.args([
        "script",
        "script/BatchCallDelegationScript.s.sol",
        "--rpc-url",
        &handle.http_endpoint(),
        "--non-interactive",
        "--slow",
        "--broadcast",
        "--evm-version",
        "prague",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Script ran successfully.

## Setting up 1 EVM.

==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);

    // Alice nonce should be 2 (tx sender and one auth)
    let alice_acc = api
        .get_account(address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"), None)
        .await
        .unwrap();
    assert_eq!(alice_acc.nonce, 2);

    // Bob nonce should be 2 (two auths) and balance reduced by 6 ETH.
    let bob_acc = api
        .get_account(address!("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"), None)
        .await
        .unwrap();
    assert_eq!(bob_acc.nonce, 2);
    assert_eq!(bob_acc.balance.to_string(), "94000000000000000000");

    // Receiver balances should be updated with 5 ETH and 1 ETH.
    let receiver1 = api
        .get_account(address!("0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"), None)
        .await
        .unwrap();
    assert_eq!(receiver1.nonce, 0);
    assert_eq!(receiver1.balance.to_string(), "105000000000000000000");
    let receiver2 = api
        .get_account(address!("0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"), None)
        .await
        .unwrap();
    assert_eq!(receiver2.nonce, 0);
    assert_eq!(receiver2.balance.to_string(), "101000000000000000000");
});

// <https://github.com/foundry-rs/foundry/issues/11159>
forgetest_async!(check_broadcast_log_with_additional_contracts, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_source(
        "Counter.sol",
        r#"
contract Counter {
    uint256 public number;

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

    function increment() public {
        number++;
    }
}
   "#,
    );
    prj.add_source(
        "Factory.sol",
        r#"
import {Counter} from "./Counter.sol";

contract Factory {
    function deployCounter() public returns (Counter) {
        return new Counter();
    }
}
   "#,
    );
    let deploy_script = prj.add_script(
        "Factory.s.sol",
        r#"
import "forge-std/Script.sol";
import {Factory} from "../src/Factory.sol";
import {Counter} from "../src/Counter.sol";

contract FactoryScript is Script {
    Factory public factory;
    Counter public counter;

    function setUp() public {}

    function run() public {
        vm.startBroadcast();

        factory = new Factory();
        counter = factory.deployCounter();

        vm.stopBroadcast();
    }
}
   "#,
    );

    let deploy_contract = deploy_script.display().to_string() + ":FactoryScript";
    let (_api, handle) = spawn(NodeConfig::test()).await;
    cmd.args([
        "script",
        &deploy_contract,
        "--root",
        prj.root().to_str().unwrap(),
        "--fork-url",
        &handle.http_endpoint(),
        "--slow",
        "--broadcast",
        "--private-key",
        "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
    ])
    .assert_success();

    let broadcast_log = prj.root().join("broadcast/Factory.s.sol/31337/run-latest.json");
    let script_sequence: ScriptSequence<Ethereum> = serde_json::from_reader(
        fs::File::open(prj.artifacts().join(broadcast_log)).expect("no broadcast log"),
    )
    .expect("no script sequence");

    let counter_contract = script_sequence
        .transactions
        .get(1)
        .expect("no tx")
        .additional_contracts
        .first()
        .expect("no Counter contract");
    assert_eq!(counter_contract.contract_name, Some("Counter".to_string()));
});

// <https://github.com/foundry-rs/foundry/issues/11213>
forgetest_async!(call_to_non_contract_address_does_not_panic, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());

    let endpoint = rpc::next_http_archive_rpc_url();

    prj.add_source(
        "Counter.sol",
        r#"
contract Counter {
    uint256 public number;

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

    function increment() public {
        number++;
    }
}
   "#,
    );

    let deploy_script = prj.add_script(
        "Counter.s.sol",
        &r#"
import "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";

contract CounterScript is Script {
    Counter public counter;
    function setUp() public {}
    function run() public {
        vm.createSelectFork("<url>");
        vm.startBroadcast();
        counter = new Counter();
        vm.stopBroadcast();

        vm.createSelectFork("<url>");
        vm.startBroadcast();
        counter.increment();
        vm.stopBroadcast();
    }
}
   "#
        .replace("<url>", &endpoint),
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;
    cmd.args([
        "script",
        &deploy_script.display().to_string(),
        "--root",
        prj.root().to_str().unwrap(),
        "--fork-url",
        &handle.http_endpoint(),
        "--slow",
        "--broadcast",
        "--private-key",
        "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
    ])
    .assert_failure()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] → new CounterScript@[..]
    └─ ← [Return] 2162 bytes of code

  [..] CounterScript::setUp()
    └─ ← [Stop]

  [..] CounterScript::run()
    ├─ [..] VM::createSelectFork("<rpc url>")
    │   └─ ← [Return] 1
    ├─ [..] VM::startBroadcast()
    │   └─ ← [Return]
    ├─ [..] → new Counter@[..]
    │   └─ ← [Return] 481 bytes of code
    ├─ [..] VM::stopBroadcast()
    │   └─ ← [Return]
    ├─ [..] VM::createSelectFork("<rpc url>")
    │   └─ ← [Return] 2
    ├─ [..] VM::startBroadcast()
    │   └─ ← [Return]
    └─ ← [Revert] call to non-contract address [..]



"#]])
    .stderr_eq(str![[r#"
Error: script failed: EvmError: Revert

"#]]);
});

// Test that --verify without --broadcast fails with a clear error message
forgetest!(verify_without_broadcast_fails, |prj, cmd| {
    let script = prj.add_source(
        "Counter",
        r#"
import "forge-std/Script.sol";

contract CounterScript is Script {
    function run() external {
        // Simple script that does nothing
    }
}
   "#,
    );

    cmd.args([
        "script",
        script.to_str().unwrap(),
        "--verify",
        "--rpc-url",
        "https://sepolia.infura.io/v3/test",
    ])
    .assert_failure()
    .stderr_eq(str![[r#"
error: the following required arguments were not provided:
  --broadcast

Usage: [..] script --broadcast --verify --rpc-url <URL> <PATH> [ARGS]...

For more information, try '--help'.

"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/11855>
forgetest_async!(can_broadcast_from_deploy_code_cheatcode, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.initialize_default_contracts();
    prj.add_script(
        "Counter.s.sol",
        r#"
import "forge-std/Script.sol";
import {Vm} from "forge-std/Vm.sol";
import {Counter} from "../src/Counter.sol";
contract CounterScript is Script {
    function run() public {
        vm.startBroadcast();
        address addr1 = vm.deployCode("src/Counter.sol:Counter");
        Counter(addr1).increment();
        vm.stopBroadcast();
    }
}
   "#,
    );

    let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
    let (_api, handle) = spawn(node_config).await;

    cmd.args([
        "script",
        "script/Counter.s.sol:CounterScript",
        "--rpc-url",
        &handle.http_endpoint(),
        "-vvvv",
        "--broadcast",
        "--private-key",
        "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [..] CounterScript::run()
    ├─ [0] VM::startBroadcast()
    │   └─ ← [Return]
    ├─ [0] VM::deployCode("src/Counter.sol:Counter")
    │   ├─ [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3
    │   │   └─ ← [Return] 481 bytes of code
    │   └─ ← [Return] Counter: [0x5FbDB2315678afecb367f032d93F642f64180aa3]
    ├─ [..] Counter::increment()
    │   └─ ← [Stop]
    ├─ [0] VM::stopBroadcast()
    │   └─ ← [Return]
    └─ ← [Stop]


Script ran successfully.

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [..] → new Counter@0x5FbDB2315678afecb367f032d93F642f64180aa3
    └─ ← [Return] 481 bytes of code

  [..] Counter::increment()
    └─ ← [Stop]


==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

// Regression: Salted `new Foo{salt: ...}(args)` in scripts must NOT be rewritten to
// `vm.deployCode(...)` by the dynamic test linking preprocessor.
//
// NOTE: `dynamic_test_linking` config option is not yet taken into account by `forge script`.
// The config is set explicitly here so the test fails if there is a regression.
forgetest_async!(can_broadcast_salted_create2_in_script, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.update_config(|c| c.dynamic_test_linking = true);
    prj.add_source(
        "Token.sol",
        r#"
contract Token {
    string public name;
    constructor(string memory _name) { name = _name; }
}
        "#,
    );
    prj.add_script(
        "Salted.s.sol",
        r#"
import "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
contract SaltedScript is Script {
    function run() external {
        vm.startBroadcast();
        new Token{salt: bytes32(uint256(0xDEADBEEF))}("SaltedTok");
        vm.stopBroadcast();
    }
}
        "#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;
    cmd.args([
        "script",
        "script/Salted.s.sol:SaltedScript",
        "--rpc-url",
        &handle.http_endpoint(),
        "--broadcast",
        "--private-key",
        "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
    ])
    .assert_success();

    let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast"))
        .find(|path| path.ends_with("run-latest.json"))
        .expect("No broadcast artifacts");
    let content = foundry_common::fs::read_to_string(run_latest).unwrap();
    let json: Value = serde_json::from_str(&content).unwrap();
    let tx = &json["transactions"][0];

    assert_eq!(
        tx["transactionType"], "CREATE2",
        "salted broadcast must be recorded as CREATE2, got: {tx}"
    );
    assert_eq!(tx["contractName"], "Token");
    assert_eq!(tx["arguments"][0], "SaltedTok");
});

// Regression: same as above but the script contract lives under `src/` (not the configured
// script directory). The preprocessor must still detect it as a script via inheritance
// (`is Script`) and leave salted new-expressions untouched.
forgetest_async!(can_broadcast_salted_create2_in_src_script, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.update_config(|c| c.dynamic_test_linking = true);
    prj.add_source(
        "Token.sol",
        r#"
contract Token {
    string public name;
    constructor(string memory _name) { name = _name; }
}
        "#,
    );
    prj.add_source(
        "SaltedSrc.s.sol",
        r#"
import "forge-std/Script.sol";
import {Token} from "./Token.sol";
contract SaltedSrcScript is Script {
    function run() external {
        vm.startBroadcast();
        new Token{salt: bytes32(uint256(0xDEADBEEF))}("SaltedSrc");
        vm.stopBroadcast();
    }
}
        "#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;
    cmd.args([
        "script",
        "src/SaltedSrc.s.sol:SaltedSrcScript",
        "--rpc-url",
        &handle.http_endpoint(),
        "--broadcast",
        "--private-key",
        "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
    ])
    .assert_success();

    let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast"))
        .find(|path| path.ends_with("run-latest.json"))
        .expect("No broadcast artifacts");
    let content = foundry_common::fs::read_to_string(run_latest).unwrap();
    let json: Value = serde_json::from_str(&content).unwrap();
    let tx = &json["transactions"][0];

    assert_eq!(
        tx["transactionType"], "CREATE2",
        "salted broadcast must be recorded as CREATE2, got: {tx}"
    );
    assert_eq!(tx["contractName"], "Token");
    assert_eq!(tx["arguments"][0], "SaltedSrc");
});

// Regression: `type(Foo).creationCode` in scripts must not be rewritten to `vm.getCode(...)`.
// The injected cheatcode is `view`, so using it in a `pure` script helper breaks compilation.
forgetest_init!(can_build_script_creation_code_in_pure_function, |prj, cmd| {
    prj.update_config(|c| c.dynamic_test_linking = true);
    prj.add_source(
        "Token.sol",
        r#"
contract Token {
    string public name;
    constructor(string memory _name) { name = _name; }
}
        "#,
    );
    prj.add_script(
        "CreationCode.s.sol",
        r#"
import "forge-std/Script.sol";
import {Token} from "../src/Token.sol";

contract CreationCodeScript is Script {
    function run() public {
        vm.broadcast();
        (bool ok,) = address(0).call(_getCreationCode());
        ok;
    }

    function _getCreationCode() internal pure returns (bytes memory) {
        return type(Token).creationCode;
    }
}
        "#,
    );

    cmd.args(["build"]).assert_success();
});

// Regression: tests and scripts must link shared libraries to the same CREATE2 address so that
// `type(Consumer).creationCode` is stable across both commands.
forgetest_init!(test_and_script_use_same_create2_library_linking, |prj, cmd| {
    prj.update_config(|c| {
        c.dynamic_test_linking = false;
        c.create2_library_salt = alloy_primitives::B256::with_last_byte(0x42);
    });
    prj.add_source(
        "Consumer.sol",
        r#"
library ExternalLibrary {
    function value() external pure returns (uint256) { return 42; }
}

contract Consumer {
    function value() external pure returns (uint256) { return ExternalLibrary.value(); }
}
        "#,
    );
    prj.add_test(
        "CreationCode.t.sol",
        r#"
import {Test, console2} from "forge-std/Test.sol";
import {Consumer} from "../src/Consumer.sol";

contract CreationCodeTest is Test {
    function testCreationCodeHash() external {
        console2.log("CREATION_CODE_HASH", vm.toString(keccak256(type(Consumer).creationCode)));
        assertEq(new Consumer().value(), 42);
    }
}
        "#,
    );
    prj.add_script(
        "CreationCode.s.sol",
        r#"
import {Script, console2} from "forge-std/Script.sol";
import {Consumer} from "../src/Consumer.sol";

contract CreationCodeScript is Script {
    function run() external {
        console2.log("CREATION_CODE_HASH", vm.toString(keccak256(type(Consumer).creationCode)));
        require(new Consumer().value() == 42);
    }
}
        "#,
    );

    fn creation_code_hash(output: &[u8]) -> String {
        let output = String::from_utf8_lossy(output);
        let marker = "CREATION_CODE_HASH ";
        let start = output.find(marker).expect("missing creation code hash marker") + marker.len();
        output[start..].split_whitespace().next().unwrap().to_owned()
    }

    let test_output = cmd
        .forge_fuse()
        .args(["test", "--match-test", "testCreationCodeHash", "-vv"])
        .assert_success()
        .get_output()
        .stdout
        .clone();
    let script_output = cmd
        .forge_fuse()
        .args(["script", "script/CreationCode.s.sol:CreationCodeScript", "-vv"])
        .assert_success()
        .get_output()
        .stdout
        .clone();

    assert_eq!(creation_code_hash(&test_output), creation_code_hash(&script_output));
});

forgetest_async!(flaky_can_deploy_with_broadcast_in_setup, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "Deploy.s.sol",
        r#"
import "forge-std/Script.sol";
import {Vm} from "forge-std/Vm.sol";
contract DeployScript is Script {
    function setUp() public {
        vm.startBroadcast();
    }

    function run() public {
        payable(address(0)).transfer(1 ether);

        vm.stopBroadcast();
    }
}
   "#,
    );

    let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into()));
    let (_api, handle) = spawn(node_config).await;

    cmd.args([
        "script",
        "script/Deploy.s.sol:DeployScript",
        "--rpc-url",
        &handle.http_endpoint(),
        "-vvvv",
        "--broadcast",
        "--private-key",
        "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
    ])
    .assert_success()
    .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Traces:
  [9882] DeployScript::run()
    ├─ [0] 0x0000000000000000000000000000000000000000::receive{value: 1000000000000000000}()
    │   └─ ← [Stop]
    ├─ [0] VM::stopBroadcast()
    │   └─ ← [Return]
    └─ ← [Stop]


Script ran successfully.

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

  [0] 0x0000000000000000000000000000000000000000::receive{value: 1000000000000000000}()
    └─ ← [Stop]


==========================

Chain 31337

[ESTIMATED_MAX_FEE_PER_GAS]
[ESTIMATED_BASE_FEE_PER_GAS]
[ESTIMATED_PRIORITY_FEE_PER_GAS]

[ESTIMATED_TOTAL_GAS_USED]

[ESTIMATED_AMOUNT_REQUIRED]

==========================


==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

[SAVED_TRANSACTIONS]

[SAVED_SENSITIVE_VALUES]


"#]]);
});

// <https://github.com/foundry-rs/foundry/issues/12151>
forgetest_async!(can_execute_script_with_createx_and_via_ir, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.update_config(|config| {
        config.optimizer = Some(true);
        config.via_ir = true;
    });
    prj.add_script("CreateXScript.s.sol", include_str!("../fixtures/CreateXScript.sol"));

    let (_api, handle) = spawn(NodeConfig::test().with_auto_impersonate(true)).await;
    cmd.cast_fuse()
        .args([
            "send",
            "0xeD456e05CaAb11d66C4c797dD6c1D6f9A7F352b5",
            "--value",
            "1000000000000000000",
            "--from",
            "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
            "--unlocked",
            "--rpc-url",
            &handle.http_endpoint(),
        ])
        .assert_success();
    cmd.cast_fuse()
        .args(["publish", " ", "--rpc-url", &handle.http_endpoint()])
        .assert_success();
    cmd.forge_fuse()
        .args([
            "script",
            "script/CreateXScript.s.sol:CreateXScript",
            "--rpc-url",
            &handle.http_endpoint(),
            "--slow",
            "--sender",
            "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
            "--private-key",
            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
            "--broadcast",
        ])
        .assert_success();
});

forgetest_async!(script_can_run_with_live_logs_flag, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.add_script(
        "Foo.s.sol",
        r#"
import {Script, console} from "forge-std/Script.sol";

contract Foo is Script {
    function setUp() pure public {
        console.log("Setup");
    }

    function run() pure public {
        console.log("Run %d", uint256(1));
    }
}
    "#,
    );

    cmd.forge_fuse()
        .args(["script", "script/Foo.s.sol", "--live-logs"])
        .assert_success()
        .stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Setup
Run 1
Script ran successfully.
[GAS]

"#]]);
});

forgetest_async!(script_can_run_with_live_logs_config, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    prj.update_config(|config| {
        config.live_logs = true;
    });

    prj.add_script(
        "Foo.s.sol",
        r#"
import {Script, console} from "forge-std/Script.sol";

contract Foo is Script {
    function setUp() pure public {
        console.log("Setup");
    }

    function run() pure public {
        console.log("Run %d", uint256(1));
    }
}
    "#,
    );

    cmd.forge_fuse().args(["script", "script/Foo.s.sol"]).assert_success().stdout_eq(str![[r#"
[COMPILING_FILES] with [SOLC_VERSION]
[SOLC_VERSION] [ELAPSED]
Compiler run successful!
Setup
Run 1
Script ran successfully.
[GAS]

"#]]);
});

// Regression test for https://github.com/foundry-rs/foundry/issues/13576
// On Arbitrum, `block.number` is remapped to the L1 block number. Previously,
// fork block pinning used the remapped L1 block number, causing the fork to
// fetch state from an ancient block where contracts did not exist.
forgetest_init!(
    #[ignore]
    flaky_can_call_arbitrum_contract_in_script,
    |prj, cmd| {
        let script = prj.add_source(
            "ArbScript",
            r#"
import "forge-std/Script.sol";

interface IERC20 {
    function name() external view returns (string memory);
}

contract ArbScript is Script {
    function run() external view {
        // USDC on Arbitrum — a contract with zero ETH balance.
        // Before the fix, the fork pinned to the L1 block number, fetching state from
        // an ancient block (Sept 2022) where USDC did not exist, causing
        // "call to non-contract address".
        IERC20 usdc = IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831);
        string memory n = usdc.name();
        require(bytes(n).length > 0, "name should not be empty");
    }
}
    "#,
        );

        let rpc = foundry_test_utils::rpc::next_rpc_endpoint(alloy_chains::NamedChain::Arbitrum);

        cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvv"]).assert_success();
    }
);

forgetest!(script_rejects_unsupported_remote_sponsor, |_prj, cmd| {
    cmd.args([
        "script",
        "src/Counter.s.sol:CounterScript",
        "--sponsor-url",
        "https://sponsor.tempo.xyz/tp_test",
    ])
    .assert_failure()
    .stderr_eq(str![[r#"
Error: --sponsor-url is not supported by forge script; use --tempo.sponsor with --tempo.sponsor-signer or --tempo.sponsor-sig

"#]]);
});

forgetest_async!(script_batch_rejects_non_tempo_network, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());

    let script = prj.add_source(
        "BatchOnly",
        r#"
import "forge-std/Script.sol";
contract BatchOnly is Script {
    function run() external {
        vm.startBroadcast();
        vm.stopBroadcast();
    }
}
"#,
    );

    let (_api, handle) = spawn(NodeConfig::test()).await;

    cmd.arg("script")
        .arg(script)
        .args([
            "--rpc-url",
            handle.http_endpoint().as_str(),
            "--private-key",
            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
            "--broadcast",
            "--batch",
        ])
        .assert_failure()
        .stderr_eq(str![[r#"
Error: --batch mode is only supported on Tempo networks

"#]]);
});

/// Asserts that the dry-run sequence under `root` rewrote every CREATE to a unique CREATE2
/// call targeting the Arachnid factory.
fn assert_create2_rewrite_dry_run(root: &std::path::Path) {
    let run_latest = foundry_common::fs::json_files(&root.join("broadcast"))
        .find(|file| file.ends_with("dry-run/run-latest.json"))
        .expect("no dry-run broadcast artifact found");

    let json: Value = foundry_common::fs::read_json_file(&run_latest).unwrap();
    let txs = json["transactions"].as_array().unwrap();
    assert_eq!(txs.len(), 3, "three CREATE attempts in script");

    let factory = foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER.to_string().to_lowercase();
    for tx in txs {
        assert_eq!(tx["transactionType"], "CREATE2", "plain CREATE was rewritten");
        assert_eq!(tx["transaction"]["to"].as_str().unwrap().to_lowercase(), factory);
    }

    let addrs: std::collections::HashSet<_> =
        txs.iter().map(|t| t["contractAddress"].as_str().unwrap().to_owned()).collect();
    assert_eq!(addrs.len(), 3, "rewritten CREATE2 addresses should be unique");
}

// Dry-run against a local anvil to verify CREATE→CREATE2 rewriting.
forgetest_async!(script_batch_rewrites_creates_to_create2, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());

    let script = prj.add_source("MultiDeploy", MULTI_DEPLOY_SCRIPT);

    // Tempo mode pre-deploys the Arachnid CREATE2 factory and PATH_USD fee token.
    let (_api, handle) = spawn(NodeConfig::test_tempo()).await;
    let dev = handle.dev_accounts().next().unwrap();

    cmd.arg("script").arg(script).args([
        "--tc",
        "MultiDeploy",
        "--rpc-url",
        &handle.http_endpoint(),
        "--sender",
        format!("{dev:?}").as_str(),
        "--batch",
        "--network",
        "tempo",
    ]);
    cmd.assert_success();

    assert_create2_rewrite_dry_run(prj.root());
});

// Same dry-run assertions against the live Moderato testnet.
forgetest_async!(
    #[ignore]
    script_batch_rewrites_creates_to_create2_moderato,
    |prj, cmd| {
        foundry_test_utils::util::initialize(prj.root());

        let script = prj.add_source("MultiDeploy", MULTI_DEPLOY_SCRIPT);

        cmd.arg("script").arg(script).args([
            "--tc",
            "MultiDeploy",
            "--rpc-url",
            "https://rpc.moderato.tempo.xyz",
            "--batch",
            "--network",
            "tempo",
        ]);
        cmd.assert_success();

        assert_create2_rewrite_dry_run(prj.root());
    }
);

// Tests that `forge script` works in Tempo mode without CreateCollision.
// Tempo genesis pre-deploys the Arachnid CREATE2 factory at the same address as the default
// CREATE2 deployer, so `deploy_create2_deployer` must be skipped to avoid a collision.
forgetest!(can_execute_script_command_with_tempo, |prj, cmd| {
    prj.wipe();

    // Initialize a Tempo project (installs forge-std, tempo-std, generates Mail template).
    cmd.args(["init", "--network", "tempo"]).arg(prj.root()).assert_success();

    // Run the generated Mail.s.sol script with a salt argument.
    cmd.forge_fuse()
        .arg("script")
        .arg("script/Mail.s.sol")
        .arg("temposalt")
        .arg("--tempo")
        .arg("--root")
        .arg(prj.root())
        .assert_success();
});

forgetest_async!(tempo_aa_script_broadcast_deploys_with_fee_token, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let script = prj.add_script(
        "DeployTempoAA.s.sol",
        r#"
import "forge-std/Script.sol";

contract TempoAADeployment {}

contract DeployTempoAA is Script {
    function run() external {
        vm.startBroadcast();
        new TempoAADeployment();
        vm.stopBroadcast();
    }
}
"#,
    );

    let (_api, handle) = spawn(NodeConfig::test_tempo()).await;
    let rpc = handle.http_endpoint();
    let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";

    cmd.arg("script").arg(script).args([
        "--rpc-url",
        &rpc,
        "--private-key",
        private_key,
        "--broadcast",
        "--tempo.fee-token",
        "0x20c0000000000000000000000000000000000000",
    ]);
    cmd.assert_success();
});

forgetest_async!(tempo_aa_script_broadcasts_with_local_sponsor, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    let script = prj.add_script(
        "DeploySponsoredTempoAA.s.sol",
        r#"
import "forge-std/Script.sol";

contract SponsoredTempoAADeployment {}

contract DeploySponsoredTempoAA is Script {
    function run() external {
        vm.startBroadcast();
        new SponsoredTempoAADeployment();
        vm.stopBroadcast();
    }
}
"#,
    );

    let (_api, handle) = spawn(NodeConfig::test_tempo()).await;
    let rpc = handle.http_endpoint();
    let wallets = handle.dev_wallets().take(2).collect::<Vec<_>>();
    let sender_key = format!("0x{}", hex::encode(wallets[0].credential().to_bytes()));
    let sponsor_key =
        format!("private-key://0x{}", hex::encode(wallets[1].credential().to_bytes()));
    let sponsor = format!("{:?}", wallets[1].address());

    let assert = cmd
        .arg("script")
        .arg(script)
        .args([
            "--rpc-url",
            &rpc,
            "--private-key",
            &sender_key,
            "--broadcast",
            "--tempo.fee-token",
            "PathUSD",
            "--tempo.sponsor",
            &sponsor,
            "--tempo.sponsor-signer",
            &sponsor_key,
        ])
        .assert_success();
    let output = assert.get_output();

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("Estimated amount required:"), "{stdout}");
    assert!(stdout.contains(" PathUSD"), "{stdout}");
    assert!(!stdout.contains(" ETH"), "{stdout}");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.to_ascii_lowercase().contains(&format!("tempo sponsor: {sponsor}")), "{stderr}");
});

// Helper: write a script that deploys `LargeRuntime` with runtime > default limit via
// `vm.startBroadcast`.
fn write_large_runtime_deploy_script(prj: &foundry_test_utils::TestProject, runtime_bytes: usize) {
    prj.add_source("LargeRuntime.sol", generate_large_runtime_contract(runtime_bytes).as_str());
    prj.add_source(
        "DeployLarge.s.sol",
        r#"
import "forge-std/Script.sol";
import "./LargeRuntime.sol";

contract DeployLarge is Script {
    function run() external {
        vm.startBroadcast();
        new LargeRuntime();
    }
}
"#,
    );
}

// Tests that `forge script` reports a contract-size violation against the default runtime limit
// when neither the CLI nor foundry.toml override `code_size_limit`.
forgetest_async!(script_check_contract_sizes_warns_at_default_limit, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    write_large_runtime_deploy_script(&prj, 30_000);

    let (_api, handle) = spawn(NodeConfig::test()).await;
    cmd.set_current_dir(prj.root());
    for var in ["FOUNDRY_CODE_SIZE_LIMIT", "DAPP_CODE_SIZE_LIMIT", "DAPP_TEST_CODE_SIZE_LIMIT"] {
        cmd.unset_env(var);
    }
    cmd.args(["script", "DeployLarge", "--rpc-url", &handle.http_endpoint()])
        .assert_success()
        .stderr_eq(str![[r#"
Error: `LargeRuntime` is above the contract size limit ([..] > 24576).

"#]]);
});

forgetest_async!(script_check_contract_sizes_uses_amsterdam_code_size_limit, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    write_large_runtime_deploy_script(&prj, 50_000);
    prj.update_config(|config| {
        config.evm_version = EvmVersion::Amsterdam;
    });

    let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(1_000_000_000))).await;
    cmd.set_current_dir(prj.root());
    for var in ["FOUNDRY_CODE_SIZE_LIMIT", "DAPP_CODE_SIZE_LIMIT", "DAPP_TEST_CODE_SIZE_LIMIT"] {
        cmd.unset_env(var);
    }
    let assert = cmd
        .args([
            "script",
            "DeployLarge",
            "--rpc-url",
            &handle.http_endpoint(),
            "--gas-limit",
            "1000000000",
        ])
        .assert_success();
    let stderr = String::from_utf8_lossy(&assert.get_output().stderr);
    assert!(!stderr.contains("above the contract size limit"), "{stderr}");
});

forgetest_async!(script_check_contract_sizes_uses_network_specific_spec, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    write_large_runtime_deploy_script(&prj, 50_000);

    let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(1_000_000_000))).await;
    let (_tempo_api, tempo_handle) =
        spawn(NodeConfig::test_tempo().with_gas_limit(Some(1_000_000_000))).await;
    let rpc = handle.http_endpoint();
    let tempo_rpc = tempo_handle.http_endpoint();
    cmd.set_current_dir(prj.root());
    for (networks, hardfork, rpc_url, is_tempo) in [
        (foundry_evm_networks::NetworkConfigs::with_optimism(), None, rpc.as_str(), false),
        (Default::default(), Some("optimism:karst"), rpc.as_str(), false),
        (foundry_evm_networks::NetworkConfigs::with_tempo(), None, tempo_rpc.as_str(), true),
        (Default::default(), Some("tempo:T8"), tempo_rpc.as_str(), true),
    ] {
        prj.update_config(|config| {
            config.evm_version = EvmVersion::Amsterdam;
            config.networks = networks;
            config.hardfork = hardfork.map(|hardfork| hardfork.parse().unwrap());
        });

        cmd.forge_fuse();
        for var in ["FOUNDRY_CODE_SIZE_LIMIT", "DAPP_CODE_SIZE_LIMIT", "DAPP_TEST_CODE_SIZE_LIMIT"]
        {
            cmd.unset_env(var);
        }
        cmd.args(["script", "DeployLarge", "--rpc-url", rpc_url, "--gas-limit", "1000000000"]);
        if is_tempo {
            cmd.args([
                "--private-key",
                "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
                "--tempo.fee-token",
                "0x20c0000000000000000000000000000000000000",
            ]);
        }
        let assert = cmd.assert_success();
        let stderr = String::from_utf8_lossy(&assert.get_output().stderr);
        assert!(
            stderr.contains("above the contract size limit (50308 > 24576)"),
            "missing size warning for {hardfork:?}: {stderr}"
        );
    }
});

// Tests that `forge script` honors `code_size_limit` configured via foundry.toml
// (the bug fix: previously only the CLI flag was honored).
forgetest_async!(script_check_contract_sizes_honors_config_limit, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());
    write_large_runtime_deploy_script(&prj, 30_000);
    prj.update_config(|config| {
        config.code_size_limit = Some(64_000);
    });

    let (_api, handle) = spawn(NodeConfig::test()).await;
    cmd.set_current_dir(prj.root());
    for var in ["FOUNDRY_CODE_SIZE_LIMIT", "DAPP_CODE_SIZE_LIMIT", "DAPP_TEST_CODE_SIZE_LIMIT"] {
        cmd.unset_env(var);
    }
    cmd.args(["script", "DeployLarge", "--rpc-url", &handle.http_endpoint()])
        .assert_success()
        .stderr_eq(str![[r#""#]]);
});

// Regression test for https://github.com/foundry-rs/foundry/issues/15207: the broadcast simulation
// must observe an `anvil_setBalance` done via `vm.rpc` earlier in the same script.
forgetest_async!(can_broadcast_with_vm_rpc_set_balance_on_fork, |prj, cmd| {
    foundry_test_utils::util::initialize(prj.root());

    // Default genesis balance is 100 ETH; fund above the broadcast amount so the send only succeeds
    // if the simulation observes the mutation.
    prj.add_script(
        "FundViaRpc.s.sol",
        r#"
import {Script} from "forge-std/Script.sol";

contract FundViaRpc is Script {
    address constant SENDER = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
    address constant RECIPIENT = 0x000000000000000000000000000000000000dEaD;

    function run() external {
        uint256 fundedAmount = 1000 ether;
        uint256 sendAmount = 500 ether;

        vm.deal(SENDER, fundedAmount);

        // Fund the account on the forked node directly, bypassing the fork cache.
        vm.rpc(
            "anvil_setBalance",
            string.concat("[\"", vm.toString(SENDER), "\", \"", vm.toString(fundedAmount), "\"]")
        );

        vm.startBroadcast(SENDER);
        (bool ok,) = RECIPIENT.call{value: sendAmount}("");
        require(ok, "send failed");
        vm.stopBroadcast();
    }
}
"#,
    );

    let (api, handle) = spawn(NodeConfig::test()).await;

    cmd.arg("script")
        .args([
            "FundViaRpc",
            "--rpc-url",
            &handle.http_endpoint(),
            "--sender",
            "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
            "--broadcast",
            "--unlocked",
        ])
        .assert_success();

    // The broadcast transaction must actually have landed on-chain.
    let recipient = address!("0x000000000000000000000000000000000000dEaD");
    let balance = api.balance(recipient, None).await.unwrap();
    assert_eq!(balance, U256::from(500) * U256::from(10).pow(U256::from(18)));
});
