Skip to content

Commit

Permalink
fix: forge script should adhere to --json flag (foundry-rs#9404)
Browse files Browse the repository at this point in the history
* adhere to --quiet flag

* revert case-specific handling of writing to progress, redundant

* handle writing to multiprogress, previously panic

* make verification process compatible with --json flag

* revert verifaction --json flow, too messy

* clean up

* revert

* handle json correctly for script deployment logs, incl. receipts

* avoid incompatible lines with json output

* revert unnecessary change

* add json and quiet test

* address feedback

* fix incorrect ordering
  • Loading branch information
zerosnacks authored and rplusq committed Nov 29, 2024
1 parent 2644f0e commit 129b8aa
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 102 deletions.
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 @@ -429,8 +429,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

0 comments on commit 129b8aa

Please sign in to comment.