Skip to content

Commit

Permalink
Put cast rpc implementation into its own file
Browse files Browse the repository at this point in the history
And add stdin parsing

Signed-off-by: Julian Popescu <[email protected]>
  • Loading branch information
jpopesculian committed Jun 22, 2022
1 parent 4fb22a7 commit 8159b09
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 66 deletions.
22 changes: 1 addition & 21 deletions cli/src/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -681,27 +681,7 @@ async fn main() -> eyre::Result<()> {
generate(shell, &mut Opts::command(), "cast", &mut std::io::stdout())
}
Subcommands::Run(cmd) => cmd.run()?,
Subcommands::Rpc { rpc_url, direct_params, method, params } => {
let rpc_url = consume_config_rpc_url(rpc_url);
let provider = Provider::try_from(rpc_url)?;
let params = if direct_params {
if params.len() != 1 {
eyre::bail!(r#"Expected exactly one argument for "params""#);
}
let param = params.into_iter().next().unwrap();
serde_json::from_str(&param).unwrap_or(serde_json::Value::String(param))
} else {
serde_json::Value::Array(
params
.into_iter()
.map(|param| {
serde_json::from_str(&param).unwrap_or(serde_json::Value::String(param))
})
.collect(),
)
};
println!("{}", Cast::new(provider).rpc(&method, params).await?);
}
Subcommands::Rpc(cmd) => cmd.run()?.await?,
};
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions cli/src/cmd/cast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
//! [`foundry_config::Config`].

pub mod find_block;
pub mod rpc;
pub mod run;
pub mod wallet;
75 changes: 75 additions & 0 deletions cli/src/cmd/cast/rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use crate::{cmd::Cmd, utils::consume_config_rpc_url};
use cast::Cast;
use clap::Parser;
use ethers::prelude::*;
use eyre::Result;
use futures::future::BoxFuture;

#[derive(Debug, Clone, Parser)]
pub struct RpcArgs {
#[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")]
rpc_url: Option<String>,
#[clap(
short = 'w',
long,
help = r#"Pass the "params" as is"#,
long_help = r#"Pass the "params" as is
If --raw is passed the first PARAM will be taken as the value of "params". If no params are given, stdin will be used. For example:
rpc eth_getBlockByNumber '["0x123", false]' --raw
=> {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... }"#
)]
raw: bool,
#[clap(value_name = "METHOD", help = "RPC method name")]
method: String,
#[clap(
value_name = "PARAMS",
help = "RPC parameters",
long_help = r#"RPC parameters
Parameters are interpreted as JSON and then fall back to string. For example:
rpc eth_getBlockByNumber 0x123 false
=> {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... }"#
)]
params: Vec<String>,
}

impl Cmd for RpcArgs {
type Output = BoxFuture<'static, Result<()>>;
fn run(self) -> eyre::Result<Self::Output> {
let RpcArgs { rpc_url, raw, method, params } = self;
Ok(Box::pin(Self::do_rpc(rpc_url, raw, method, params)))
}
}

impl RpcArgs {
async fn do_rpc(
rpc_url: Option<String>,
raw: bool,
method: String,
params: Vec<String>,
) -> Result<()> {
let rpc_url = consume_config_rpc_url(rpc_url);
let provider = Provider::try_from(rpc_url)?;
let params = if raw {
if params.is_empty() {
serde_json::Deserializer::from_reader(std::io::stdin())
.into_iter()
.next()
.transpose()?
.unwrap()
} else {
Self::to_json_or_string(params.into_iter().next().unwrap())
}
} else {
serde_json::Value::Array(params.into_iter().map(Self::to_json_or_string).collect())
};
println!("{}", Cast::new(provider).rpc(&method, params).await?);
Ok(())
}
fn to_json_or_string(value: String) -> serde_json::Value {
serde_json::from_str(&value).unwrap_or(serde_json::Value::String(value))
}
}
32 changes: 2 additions & 30 deletions cli/src/opts/cast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{ClapChain, EthereumOpts};
use crate::{
cmd::cast::{find_block::FindBlockArgs, run::RunArgs, wallet::WalletSubcommands},
cmd::cast::{find_block::FindBlockArgs, rpc::RpcArgs, run::RunArgs, wallet::WalletSubcommands},
utils::{parse_ether_value, parse_u256},
};
use clap::{Parser, Subcommand, ValueHint};
Expand Down Expand Up @@ -825,35 +825,7 @@ If an address is specified, then the ABI is fetched from Etherscan."#,
#[clap(name = "rpc")]
#[clap(visible_alias = "rp")]
#[clap(about = "Perform a raw JSON-RPC request")]
Rpc {
#[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")]
rpc_url: Option<String>,
#[clap(
short,
long,
help = "Do not put parameters in an array",
long_help = r#"Do not put parameters in an array
If --direct-params is passed the first PARAM will be taken as the value of "params". For example:
rpc --direct-params eth_getBlockByNumber '["0x123", false]'
=> {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... }"#
)]
direct_params: bool,
#[clap(value_name = "METHOD", help = "RPC method name")]
method: String,
#[clap(
value_name = "PARAMS",
help = "RPC parameters",
long_help = r#"RPC parameters
Parameters are interpreted as JSON and then fall back to string. For example:
rpc eth_getBlockByNumber 0x123 false
=> {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... }"#
)]
params: Vec<String>,
},
Rpc(RpcArgs),
}

pub fn parse_name_or_address(s: &str) -> eyre::Result<NameOrAddress> {
Expand Down
42 changes: 32 additions & 10 deletions cli/test-utils/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ impl TestProject {
cmd,
current_dir_lock: None,
saved_cwd: pretty_err("<current dir>", std::env::current_dir()),
stdin_fun: None,
}
}

Expand All @@ -264,6 +265,7 @@ impl TestProject {
cmd,
current_dir_lock: None,
saved_cwd: pretty_err("<current dir>", std::env::current_dir()),
stdin_fun: None,
}
}

Expand Down Expand Up @@ -334,7 +336,6 @@ pub fn read_string(path: impl AsRef<Path>) -> String {
}

/// A simple wrapper around a process::Command with some conveniences.
#[derive(Debug)]
pub struct TestCommand {
saved_cwd: PathBuf,
/// The project used to launch this command.
Expand All @@ -343,6 +344,7 @@ pub struct TestCommand {
cmd: Command,
// initial: Command,
current_dir_lock: Option<parking_lot::lock_api::MutexGuard<'static, parking_lot::RawMutex, ()>>,
stdin_fun: Option<Box<dyn FnOnce(process::ChildStdin)>>,
}

impl TestCommand {
Expand Down Expand Up @@ -391,6 +393,11 @@ impl TestCommand {
self
}

pub fn stdin(&mut self, fun: impl FnOnce(process::ChildStdin) + 'static) -> &mut TestCommand {
self.stdin_fun = Some(Box::new(fun));
self
}

/// Convenience function to add `--root project.root()` argument
pub fn root_arg(&mut self) -> &mut TestCommand {
let root = self.project.root().to_path_buf();
Expand Down Expand Up @@ -449,7 +456,7 @@ impl TestCommand {

/// Returns the `stderr` of the output as `String`.
pub fn stderr_lossy(&mut self) -> String {
let output = self.cmd.output().unwrap();
let output = self.execute();
String::from_utf8_lossy(&output.stderr).to_string()
}

Expand All @@ -460,18 +467,33 @@ impl TestCommand {

/// Returns the output but does not expect that the command was successful
pub fn unchecked_output(&mut self) -> process::Output {
self.cmd.output().unwrap()
self.execute()
}

/// Gets the output of a command. If the command failed, then this panics.
pub fn output(&mut self) -> process::Output {
let output = self.cmd.output().unwrap();
let output = self.execute();
self.expect_success(output)
}

/// Executes command, applies stdin function and returns output
pub fn execute(&mut self) -> process::Output {
let mut child = self
.cmd
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.stdin(process::Stdio::piped())
.spawn()
.unwrap();
if let Some(fun) = self.stdin_fun.take() {
fun(child.stdin.take().unwrap())
}
child.wait_with_output().unwrap()
}

/// Runs the command and prints its output
pub fn print_output(&mut self) {
let output = self.cmd.output().unwrap();
let output = self.execute();
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
}
Expand All @@ -482,14 +504,14 @@ impl TestCommand {
if let Some(parent) = name.parent() {
fs::create_dir_all(parent).unwrap();
}
let output = self.cmd.output().unwrap();
let output = self.execute();
fs::write(format!("{}.stdout", name.display()), &output.stdout).unwrap();
fs::write(format!("{}.stderr", name.display()), &output.stderr).unwrap();
}

/// Runs the command and asserts that it resulted in an error exit code.
pub fn assert_err(&mut self) {
let o = self.cmd.output().unwrap();
let o = self.execute();
if o.status.success() {
panic!(
"\n\n===== {:?} =====\n\
Expand All @@ -509,7 +531,7 @@ impl TestCommand {

/// Runs the command and asserts that something was printed to stderr.
pub fn assert_non_empty_stderr(&mut self) {
let o = self.cmd.output().unwrap();
let o = self.execute();
if o.status.success() || o.stderr.is_empty() {
panic!(
"\n\n===== {:?} =====\n\
Expand All @@ -529,7 +551,7 @@ impl TestCommand {

/// Runs the command and asserts that something was printed to stdout.
pub fn assert_non_empty_stdout(&mut self) {
let o = self.cmd.output().unwrap();
let o = self.execute();
if !o.status.success() || o.stdout.is_empty() {
panic!(
"\n\n===== {:?} =====\n\
Expand All @@ -549,7 +571,7 @@ impl TestCommand {

/// Runs the command and asserts that nothing was printed to stdout.
pub fn assert_empty_stdout(&mut self) {
let o = self.cmd.output().unwrap();
let o = self.execute();
if !o.status.success() || !o.stderr.is_empty() {
panic!(
"\n\n===== {:?} =====\n\
Expand Down
24 changes: 19 additions & 5 deletions cli/tests/it/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use foundry_cli_test_utils::{
util::{TestCommand, TestProject},
};
use foundry_utils::rpc::next_http_rpc_endpoint;
use std::path::PathBuf;
use std::{io::Write, path::PathBuf};

// tests that the `cast find-block` command works correctly
casttest!(finds_block, |_: TestProject, mut cmd: TestCommand| {
Expand Down Expand Up @@ -113,19 +113,33 @@ casttest!(cast_rpc_with_args, |_: TestProject, mut cmd: TestCommand| {
assert!(output.contains(r#""number":"0x123""#), "{}", output);
});

// test for cast_rpc with direct params
casttest!(cast_rpc_direct_params, |_: TestProject, mut cmd: TestCommand| {
// test for cast_rpc with raw params
casttest!(cast_rpc_raw_params, |_: TestProject, mut cmd: TestCommand| {
let eth_rpc_url = next_http_rpc_endpoint();

// Call `cast rpc --direct-params eth_getBlockByNumber '["0x123", false]'`
// Call `cast rpc eth_getBlockByNumber --raw '["0x123", false]'`
cmd.args([
"rpc",
"--rpc-url",
eth_rpc_url.as_str(),
"--direct-params",
"eth_getBlockByNumber",
"--raw",
r#"["0x123", false]"#,
]);
let output = cmd.stdout_lossy();
assert!(output.contains(r#""number":"0x123""#), "{}", output);
});

// test for cast_rpc with direct params
casttest!(cast_rpc_raw_params_stdin, |_: TestProject, mut cmd: TestCommand| {
let eth_rpc_url = next_http_rpc_endpoint();

// Call `echo "\n[\n\"0x123\",\nfalse\n]\n" | cast rpc eth_getBlockByNumber --raw
cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "--raw"]).stdin(
|mut stdin| {
stdin.write_all(b"\n[\n\"0x123\",\nfalse\n]\n").unwrap();
},
);
let output = cmd.stdout_lossy();
assert!(output.contains(r#""number":"0x123""#), "{}", output);
});

0 comments on commit 8159b09

Please sign in to comment.