Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: forge script should adhere to --json flag #9404

Merged
merged 13 commits into from
Nov 26, 2024
78 changes: 78 additions & 0 deletions crates/forge/tests/cli/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use anvil::{spawn, NodeConfig};
use forge_script_sequence::ScriptSequence;
use foundry_test_utils::{
rpc,
snapbox::IntoData,
util::{OTHER_SOLC_VERSION, SOLC_VERSION},
ScriptOutcome, ScriptTester,
};
Expand Down Expand Up @@ -1821,6 +1822,83 @@ Warning: Script contains a transaction to 0x000000000000000000000000000000000000
"#]]);
});

// 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("");
}
}
"#,
)
.unwrap();

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.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("");
}
}
"#,
)
.unwrap();

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":"0x6080604052600c805462ff00ff191662010001179055348015601f575f5ffd5b506101568061002d5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063c040622614610038578063f8ccbf4714610054575b5f5ffd5b610040610067565b604051901515815260200160405180910390f35b600c546100409062010000900460ff1681565b5f7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d5f1c6001600160a01b0316637fb5297f6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156100c2575f5ffd5b505af11580156100d4573d5f5f3e3d5ffd5b50506040515f925090508181818181805af19150503d805f8114610113576040519150601f19603f3d011682016040523d82523d5f602084013e610118565b606091505b50909291505056fea264697066735822122060ba6332e526de9b6bc731fb4682b44e42845196324ec33068982984d700cdd964736f6c634300081b0033","output":"0x608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063c040622614610038578063f8ccbf4714610054575b5f5ffd5b610040610067565b604051901515815260200160405180910390f35b600c546100409062010000900460ff1681565b5f7f885cb69240a935d632d79c317109709ecfa91a80626ff3989d68f67f5b1dd12d5f1c6001600160a01b0316637fb5297f6040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156100c2575f5ffd5b505af11580156100d4573d5f5f3e3d5ffd5b50506040515f925090508181818181805af19150503d805f8114610113576040519150601f19603f3d011682016040523d82523d5f602084013e610118565b606091505b50909291505056fea264697066735822122060ba6332e526de9b6bc731fb4682b44e42845196324ec33068982984d700cdd964736f6c634300081b0033","gas_used":90639,"gas_limit":1073682810,"status":"Return","steps":[],"decoded":{"label":null,"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":3214,"gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"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":0,"gas_limit":1056940983,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","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":0,"gas_limit":1056940820,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":24278,"labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null}
{"chain":31337,"estimated_gas_price":"2.000000001","estimated_total_gas_used":29005,"estimated_amount_required":"0.000058010000029005"}
{"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":21000,"gas_price":1000000001}
{"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"}

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

// https://github.com/foundry-rs/foundry/pull/7742
forgetest_async!(unlocked_no_sender, |prj, cmd| {
foundry_test_utils::util::initialize(prj.root());
Expand Down
17 changes: 14 additions & 3 deletions crates/script-sequence/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::transaction::TransactionWithMetadata;
use alloy_primitives::{hex, map::HashMap, TxHash};
use alloy_rpc_types::AnyTransactionReceipt;
use eyre::{ContextCompat, Result, WrapErr};
use foundry_common::{fs, TransactionMaybeSigned, SELECTOR_LEN};
use foundry_common::{fs, shell, TransactionMaybeSigned, SELECTOR_LEN};
use foundry_compilers::ArtifactId;
use foundry_config::Config;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -127,8 +127,19 @@ impl ScriptSequence {
}

if !silent {
sh_println!("\nTransactions saved to: {}\n", path.display())?;
sh_println!("Sensitive values saved to: {}\n", sensitive_path.display())?;
if shell::is_json() {
sh_println!(
"{}",
serde_json::json!({
"status": "success",
"transactions": path.display().to_string(),
"sensitive": sensitive_path.display().to_string(),
})
)?;
} else {
sh_println!("\nTransactions saved to: {}\n", path.display())?;
sh_println!("Sensitive values saved to: {}\n", sensitive_path.display())?;
}
}

Ok(())
Expand Down
8 changes: 5 additions & 3 deletions crates/script/src/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use foundry_cheatcodes::Wallets;
use foundry_cli::utils::{has_batch_support, has_different_gas_calc};
use foundry_common::{
provider::{get_http_provider, try_get_http_provider, RetryProvider},
TransactionMaybeSigned,
shell, TransactionMaybeSigned,
};
use foundry_config::Config;
use futures::{future::join_all, StreamExt};
Expand Down Expand Up @@ -424,8 +424,10 @@ impl BundledState {
seq_progress.inner.write().finish();
}

sh_println!("\n\n==========================")?;
sh_println!("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?;
if !shell::is_json() {
sh_println!("\n\n==========================")?;
sh_println!("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?;
}

Ok(BroadcastedState {
args: self.args,
Expand Down
9 changes: 7 additions & 2 deletions crates/script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,10 @@ impl ScriptArgs {

// Check if there are any missing RPCs and exit early to avoid hard error.
if pre_simulation.execution_artifacts.rpc_data.missing_rpc {
sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?;
if !shell::is_json() {
sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?;
}

return Ok(());
}

Expand All @@ -298,7 +301,9 @@ impl ScriptArgs {

// Exit early in case user didn't provide any broadcast/verify related flags.
if !bundled.args.should_broadcast() {
sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?;
if !shell::is_json() {
sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?;
}
return Ok(());
}

Expand Down
17 changes: 14 additions & 3 deletions crates/script/src/multi_sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use eyre::{ContextCompat, Result, WrapErr};
use forge_script_sequence::{
now, sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR,
};
use foundry_common::fs;
use foundry_common::{fs, shell};
use foundry_compilers::ArtifactId;
use foundry_config::Config;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -146,8 +146,19 @@ impl MultiChainSequence {
}

if !silent {
sh_println!("\nTransactions saved to: {}\n", self.path.display())?;
sh_println!("Sensitive details saved to: {}\n", self.sensitive_path.display())?;
if shell::is_json() {
sh_println!(
"{}",
serde_json::json!({
"status": "success",
"transactions": self.path.display().to_string(),
"sensitive": self.sensitive_path.display().to_string(),
})
)?;
} else {
sh_println!("\nTransactions saved to: {}\n", self.path.display())?;
sh_println!("Sensitive details saved to: {}\n", self.sensitive_path.display())?;
}
}

Ok(())
Expand Down
95 changes: 56 additions & 39 deletions crates/script/src/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use alloy_primitives::{
use eyre::Result;
use forge_script_sequence::ScriptSequence;
use foundry_cli::utils::init_progress;
use foundry_common::provider::RetryProvider;
use foundry_common::{provider::RetryProvider, shell};
use futures::StreamExt;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use parking_lot::RwLock;
Expand All @@ -31,33 +31,42 @@ pub struct SequenceProgressState {

impl SequenceProgressState {
pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self {
let mut template = "{spinner:.green}".to_string();
write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain))
.unwrap();
template.push_str("{msg}");

let top_spinner = ProgressBar::new_spinner()
.with_style(ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅"));
let top_spinner = multi.add(top_spinner);

let txs = multi.insert_after(
&top_spinner,
init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "),
);

let receipts = multi.insert_after(
&txs,
init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "),
);

top_spinner.enable_steady_tick(Duration::from_millis(100));
txs.enable_steady_tick(Duration::from_millis(1000));
receipts.enable_steady_tick(Duration::from_millis(1000));

txs.set_position(sequence.receipts.len() as u64);
receipts.set_position(sequence.receipts.len() as u64);

let mut state = Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi };
let mut state = if shell::is_quiet() || shell::is_json() {
let top_spinner = ProgressBar::hidden();
let txs = ProgressBar::hidden();
let receipts = ProgressBar::hidden();

Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi }
} else {
let mut template = "{spinner:.green}".to_string();
write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain))
.unwrap();
template.push_str("{msg}");

let top_spinner = ProgressBar::new_spinner().with_style(
ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅"),
);
let top_spinner = multi.add(top_spinner);

let txs = multi.insert_after(
&top_spinner,
init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "),
);

let receipts = multi.insert_after(
&txs,
init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "),
);

top_spinner.enable_steady_tick(Duration::from_millis(100));
txs.enable_steady_tick(Duration::from_millis(1000));
receipts.enable_steady_tick(Duration::from_millis(1000));

txs.set_position(sequence.receipts.len() as u64);
receipts.set_position(sequence.receipts.len() as u64);

Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi }
};

for tx_hash in sequence.pending.iter() {
state.tx_sent(*tx_hash);
Expand All @@ -71,16 +80,21 @@ impl SequenceProgressState {
pub fn tx_sent(&mut self, tx_hash: B256) {
// Avoid showing more than 10 spinners.
if self.tx_spinners.len() < 10 {
let spinner = ProgressBar::new_spinner()
.with_style(
ProgressStyle::with_template(" {spinner:.green} {msg}")
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"),
)
.with_message(format!("{} {}", "[Pending]".yellow(), tx_hash));

let spinner = self.multi.insert_before(&self.txs, spinner);
spinner.enable_steady_tick(Duration::from_millis(100));
let spinner = if shell::is_quiet() || shell::is_json() {
ProgressBar::hidden()
} else {
let spinner = ProgressBar::new_spinner()
.with_style(
ProgressStyle::with_template(" {spinner:.green} {msg}")
.unwrap()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"),
)
.with_message(format!("{} {}", "[Pending]".yellow(), tx_hash));

let spinner = self.multi.insert_before(&self.txs, spinner);
spinner.enable_steady_tick(Duration::from_millis(100));
spinner
};

self.tx_spinners.insert(tx_hash, spinner);
}
Expand All @@ -98,7 +112,10 @@ impl SequenceProgressState {
/// Same as finish_tx_spinner but also prints a message to stdout above all other progress bars.
pub fn finish_tx_spinner_with_msg(&mut self, tx_hash: B256, msg: &str) -> std::io::Result<()> {
self.finish_tx_spinner(tx_hash);
self.multi.println(msg)?;

if !(shell::is_quiet() || shell::is_json()) {
self.multi.println(msg)?;
}

Ok(())
}
Expand Down
Loading
Loading