Skip to content

feat(cli): Improve UX for key commands with better help, about, and usage examples #10917

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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
98 changes: 42 additions & 56 deletions crates/cast/src/cmd/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,72 +42,64 @@ use super::run::fetch_contracts_bytecode_from_trace;
static OVERRIDE_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^([^:]+):([^:]+):([^:]+)$").unwrap());

/// CLI arguments for `cast call`.
/// Call a contract function locally (eth_call) and print the result.
///
/// ## State Override Flags
/// Example:
///
/// The following flags can be used to override the state for the call:
///
/// * `--override-balance <address>:<balance>` - Override the balance of an account
/// * `--override-nonce <address>:<nonce>` - Override the nonce of an account
/// * `--override-code <address>:<code>` - Override the code of an account
/// * `--override-state <address>:<slot>:<value>` - Override a storage slot of an account
///
/// Multiple overrides can be specified for the same account. For example:
///
/// ```bash
/// cast call 0x... "transfer(address,uint256)" 0x... 100 \
/// --override-balance 0x123:0x1234 \
/// --override-nonce 0x123:1 \
/// --override-code 0x123:0x1234 \
/// --override-state 0x123:0x1:0x1234
/// --override-state-diff 0x123:0x1:0x1234
/// ```
/// cast call 0xAbC... "balanceOf(address)" 0x123... --rpc-url &lt;URL&gt;
#[derive(Debug, Parser)]
#[command(
about = "Call a contract function locally (eth_call) and print the result.",
long_about = "Call a contract function locally (eth_call) and print the result.\n\
EXAMPLES:\n\
cast call 0xAbC... 'balanceOf(address)' 0x123... --rpc-url <URL>\n\
cast call 0xAbC... --data 0xabcdef... --rpc-url <URL>\n\
cast call 0xAbC... 'transfer(address,uint256)' 0x123... 100 --override-balance 0x123:1000\n\
See more: https://book.getfoundry.sh/reference/cast/cast-call.html"
)]
pub struct CallArgs {
/// The destination of the transaction.
#[arg(value_parser = NameOrAddress::from_str)]
/// Destination address of the contract to call.
#[arg(value_name = "TO", value_parser = NameOrAddress::from_str)]
to: Option<NameOrAddress>,

/// The signature of the function to call.
/// Function signature to call, e.g. `balanceOf(address)`.
#[arg(value_name = "SIG")]
sig: Option<String>,

/// The arguments of the function to call.
/// Arguments for the function call.
#[arg(value_name = "ARGS")]
args: Vec<String>,

/// Raw hex-encoded data for the transaction. Used instead of \[SIG\] and \[ARGS\].
#[arg(
long,
conflicts_with_all = &["sig", "args"]
conflicts_with_all = &["sig", "args"],
value_name = "DATA",
)]
data: Option<String>,

/// Forks the remote rpc, executes the transaction locally and prints a trace
/// Forks the remote rpc, executes the transaction locally and prints a trace.
#[arg(long, default_value_t = false)]
trace: bool,

/// Opens an interactive debugger.
/// Can only be used with `--trace`.
/// Opens an interactive debugger (requires --trace).
#[arg(long, requires = "trace")]
debug: bool,

/// Decode internal calls in traces (requires --trace).
#[arg(long, requires = "trace")]
decode_internal: bool,

/// Labels to apply to the traces; format: `address:label`.
/// Can only be used with `--trace`.
/// Labels to apply to the traces; format: `address:label` (requires --trace).
#[arg(long, requires = "trace")]
labels: Vec<String>,

/// The EVM Version to use.
/// Can only be used with `--trace`.
#[arg(long, requires = "trace")]
/// EVM Version to use (requires --trace).
#[arg(long, requires = "trace", value_name = "EVM_VERSION")]
evm_version: Option<EvmVersion>,

/// The block height to query at.
///
/// Can also be the tags earliest, finalized, safe, latest, or pending.
#[arg(long, short)]
/// Block height to query at (number or tag: earliest, latest, pending, etc).
#[arg(long, short, value_name = "BLOCK")]
block: Option<BlockId>,

/// Enable Odyssey features.
Expand All @@ -127,28 +119,23 @@ pub struct CallArgs {
#[arg(long, visible_alias = "la")]
pub with_local_artifacts: bool,

/// Override the balance of an account.
/// Format: address:balance
/// Override the balance of an account. Format: address:balance
#[arg(long = "override-balance", value_name = "ADDRESS:BALANCE")]
pub balance_overrides: Option<Vec<String>>,

/// Override the nonce of an account.
/// Format: address:nonce
/// Override the nonce of an account. Format: address:nonce
#[arg(long = "override-nonce", value_name = "ADDRESS:NONCE")]
pub nonce_overrides: Option<Vec<String>>,

/// Override the code of an account.
/// Format: address:code
/// Override the code of an account. Format: address:code
#[arg(long = "override-code", value_name = "ADDRESS:CODE")]
pub code_overrides: Option<Vec<String>>,

/// Override the state of an account.
/// Format: address:slot:value
/// Override the state of an account. Format: address:slot:value
#[arg(long = "override-state", value_name = "ADDRESS:SLOT:VALUE")]
pub state_overrides: Option<Vec<String>>,

/// Override the state diff of an account.
/// Format: address:slot:value
/// Override the state diff of an account. Format: address:slot:value
#[arg(long = "override-state-diff", value_name = "ADDRESS:SLOT:VALUE")]
pub state_diff_overrides: Option<Vec<String>>,

Expand All @@ -163,24 +150,23 @@ pub struct CallArgs {

#[derive(Debug, Parser)]
pub enum CallSubcommands {
/// ignores the address field and simulates creating a contract
/// Simulate contract creation (ignores the address field).
#[command(name = "--create")]
Create {
/// Bytecode of contract.
/// Bytecode of contract to deploy.
#[arg(value_name = "BYTECODE")]
code: String,

/// The signature of the constructor.
/// Constructor signature, e.g. `constructor(uint256)`.
#[arg(value_name = "SIG")]
sig: Option<String>,

/// The arguments of the constructor.
/// Arguments for the constructor.
#[arg(value_name = "ARGS")]
args: Vec<String>,

/// Ether to send in the transaction.
///
/// Either specified in wei, or as a string with a unit type.
///
/// Examples: 1ether, 10gwei, 0.01ether
#[arg(long, value_parser = parse_ether_value)]
/// Ether to send in the transaction (e.g. 1ether, 10gwei, 0.01ether).
#[arg(long, value_parser = parse_ether_value, value_name = "VALUE")]
value: Option<U256>,
},
}
Expand Down
50 changes: 34 additions & 16 deletions crates/cast/src/cmd/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,53 @@ use foundry_cli::{
};
use std::{path::PathBuf, str::FromStr};

/// CLI arguments for `cast send`.
/// Send a transaction to a contract or deploy a new contract.
///
/// Example:
///
/// cast send 0xAbC... "transfer(address,uint256)" 0x123... 100 --private-key &lt;KEY&gt; --rpc-url
/// &lt;URL&gt; cast send --create &lt;BYTECODE&gt; --private-key &lt;KEY&gt; --rpc-url &lt;URL&gt;
#[derive(Debug, Parser)]
#[command(
about = "Send a transaction to a contract or deploy a new contract.",
long_about = "Send a transaction to a contract or deploy a new contract.\n\
EXAMPLES:\n\
cast send 0xAbC... 'transfer(address,uint256)' 0x123... 100 --private-key &lt;KEY&gt; --rpc-url &lt;URL&gt;\n\
cast send --create &lt;BYTECODE&gt; --private-key &lt;KEY&gt; --rpc-url &lt;URL&gt;\n\
See more: https://book.getfoundry.sh/reference/cast/cast-send.html"
)]
pub struct SendTxArgs {
/// The destination of the transaction.
/// Destination address of the transaction (contract or EOA).
///
/// If not provided, you must use cast send --create.
#[arg(value_parser = NameOrAddress::from_str)]
/// If not provided, you must use `cast send --create`.
#[arg(value_name = "TO", value_parser = NameOrAddress::from_str)]
to: Option<NameOrAddress>,

/// The signature of the function to call.
/// Function signature to call, e.g. `transfer(address,uint256)`.
#[arg(value_name = "SIG")]
sig: Option<String>,

/// The arguments of the function to call.
/// Arguments for the function call.
#[arg(value_name = "ARGS")]
args: Vec<String>,

/// Only print the transaction hash and exit immediately.
#[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")]
cast_async: bool,

/// The number of confirmations until the receipt is fetched.
#[arg(long, default_value = "1")]
/// Number of confirmations to wait for the receipt.
#[arg(long, default_value = "1", value_name = "NUM")]
confirmations: u64,

#[command(subcommand)]
command: Option<SendTxSubcommands>,

/// Send via `eth_sendTransaction` using the `--from` argument or $ETH_FROM as sender
/// Use `eth_sendTransaction` with an unlocked account (requires --from or $ETH_FROM).
#[arg(long, requires = "from")]
unlocked: bool,

/// Timeout for sending the transaction.
#[arg(long, env = "ETH_TIMEOUT")]
/// Timeout (in seconds) for sending the transaction.
#[arg(long, env = "ETH_TIMEOUT", value_name = "SECONDS")]
pub timeout: Option<u64>,

#[command(flatten)]
Expand All @@ -57,7 +72,7 @@ pub struct SendTxArgs {
#[command(flatten)]
eth: EthereumOpts,

/// The path of blob data to be sent.
/// Path to a file containing blob data to be sent.
#[arg(
long,
value_name = "BLOB_DATA_PATH",
Expand All @@ -70,16 +85,19 @@ pub struct SendTxArgs {

#[derive(Debug, Parser)]
pub enum SendTxSubcommands {
/// Use to deploy raw contract bytecode.
/// Deploy raw contract bytecode as a new contract.
#[command(name = "--create")]
Create {
/// The bytecode of the contract to deploy.
/// Bytecode of the contract to deploy.
#[arg(value_name = "BYTECODE")]
code: String,

/// The signature of the function to call.
/// Constructor signature, e.g. `constructor(uint256)`.
#[arg(value_name = "SIG")]
sig: Option<String>,

/// The arguments of the function to call.
/// Arguments for the constructor.
#[arg(value_name = "ARGS")]
args: Vec<String>,
},
}
Expand Down
41 changes: 28 additions & 13 deletions crates/forge/src/cmd/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,31 @@ use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::D

merge_impl_figment_convert!(CreateArgs, build, eth);

/// CLI arguments for `forge create`.
/// Deploy a smart contract to the specified network.
///
/// This command compiles the contract, encodes constructor arguments, and sends a deployment
/// transaction.
///
/// Example:
///
/// forge create src/Counter.sol:Counter --constructor-args 42 --rpc-url &lt;URL&gt; --private-key
/// &lt;KEY&gt; --broadcast
#[derive(Clone, Debug, Parser)]
#[command(
about = "Deploy a smart contract to the specified network.",
long_about = "Deploy a smart contract to the specified network.\n\
Compiles the contract, encodes constructor arguments, and sends a deployment transaction.\n\
EXAMPLES:\n\
forge create src/Counter.sol:Counter --constructor-args 42 --rpc-url &lt;URL&gt; --private-key &lt;KEY&gt; --broadcast\n\
Use --verify to automatically verify the contract after deployment.\n\
See more: https://book.getfoundry.sh/reference/forge/forge-create.html"
)]
pub struct CreateArgs {
/// The contract identifier in the form `<path>:<contractname>`.
/// Contract identifier in the form `<path>:<contractname>`, e.g. `src/Counter.sol:Counter`.
#[arg(value_name = "CONTRACT")]
contract: ContractInfo,

/// The constructor arguments.
/// Constructor arguments as a space-separated list, e.g. `42 \"hello\"`.
#[arg(
long,
num_args(1..),
Expand All @@ -53,35 +71,32 @@ pub struct CreateArgs {
)]
constructor_args: Vec<String>,

/// The path to a file containing the constructor arguments.
/// Path to a file containing constructor arguments (one per line or as JSON array).
#[arg(
long,
value_hint = ValueHint::FilePath,
value_name = "PATH",
)]
constructor_args_path: Option<PathBuf>,

/// Broadcast the transaction.
/// Broadcast the transaction to the network (otherwise, dry-run only).
#[arg(long)]
pub broadcast: bool,

/// Verify contract after creation.
/// Verify contract after creation (using the selected block explorer).
#[arg(long)]
verify: bool,

/// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender
/// Use `eth_sendTransaction` with an unlocked account (requires --from or $ETH_FROM).
#[arg(long, requires = "from")]
unlocked: bool,

/// Prints the standard json compiler input if `--verify` is provided.
///
/// The standard json compiler input can be used to manually submit contract verification in
/// the browser.
/// Print the standard JSON compiler input (for manual verification in a browser).
#[arg(long, requires = "verify")]
show_standard_json_input: bool,

/// Timeout to use for broadcasting transactions.
#[arg(long, env = "ETH_TIMEOUT")]
/// Timeout (in seconds) for broadcasting transactions.
#[arg(long, env = "ETH_TIMEOUT", value_name = "SECONDS")]
pub timeout: Option<u64>,

#[command(flatten)]
Expand Down
Loading