From 71a70830d7364b50ba8bac04c37df72b0e65d450 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 19:04:27 +0200 Subject: [PATCH 01/30] chore: improve some help text in cast --- cli/src/opts/cast.rs | 248 +++++++++++++++++++++++++++---------------- 1 file changed, 159 insertions(+), 89 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 42a0320dd2a14..f0afa850db989 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -1,16 +1,14 @@ -use std::{path::PathBuf, str::FromStr}; - -use clap::{Parser, Subcommand}; -use ethers::{ - abi::token::{LenientTokenizer, Tokenizer}, - types::{Address, BlockId, BlockNumber, NameOrAddress, H256, U256}, -}; - use super::{ClapChain, EthereumOpts, Wallet}; use crate::{ cmd::cast::{call::CallArgs, find_block::FindBlockArgs}, utils::parse_u256, }; +use clap::{Parser, Subcommand, ValueHint}; +use ethers::{ + abi::token::{LenientTokenizer, Tokenizer}, + types::{Address, BlockId, BlockNumber, NameOrAddress, H256, U256}, +}; +use std::{path::PathBuf, str::FromStr}; #[derive(Debug, Subcommand)] #[clap(about = "Perform Ethereum RPC calls from the comfort of your command line.")] @@ -121,14 +119,19 @@ pub enum Subcommands { to_json: bool, }, #[clap(name = "block")] - #[clap( - about = "Prints information about . If is given, print only the value of that field" - )] + #[clap(about = "Get information about a block.")] Block { - #[clap(help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))] + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id) + )] block: BlockId, #[clap(long, env = "CAST_FULL_BLOCK")] full: bool, + #[clap(long, short, help = "If specified, only get the given field of the block.")] field: Option, #[clap(long = "json", short = 'j')] to_json: bool, @@ -136,13 +139,13 @@ pub enum Subcommands { rpc_url: String, }, #[clap(name = "block-number")] - #[clap(about = "Prints latest block number")] + #[clap(about = "Get the latest block number.")] BlockNumber { #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, }, #[clap(name = "call")] - #[clap(about = "Perform a local call to without publishing a transaction.")] + #[clap(about = "Perform a call on an account without publishing a transaction.")] Call(CallArgs), #[clap(about = "Pack a signature and an argument list into hexadecimal calldata.")] Calldata { @@ -157,19 +160,19 @@ pub enum Subcommands { args: Vec, }, #[clap(name = "chain")] - #[clap(about = "Prints symbolic name of current blockchain by checking genesis hash")] + #[clap(about = "Get the symbolic name of the current chain.")] Chain { #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, }, #[clap(name = "chain-id")] - #[clap(about = "Returns ethereum chain id")] + #[clap(about = "Get the Ethereum chain ID.")] ChainId { #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, }, #[clap(name = "client")] - #[clap(about = "Returns the current client version")] + #[clap(about = "Get the current client version.")] Client { #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, @@ -185,10 +188,10 @@ pub enum Subcommands { nonce: Option, }, #[clap(name = "namehash")] - #[clap(about = "Returns ENS namehash of provided name")] + #[clap(about = "Calculate the ENS namehash of a name.")] Namehash { name: String }, #[clap(name = "tx")] - #[clap(about = "Show information about the transaction ")] + #[clap(about = "Get information about a transaction.")] Tx { hash: String, field: Option, @@ -198,14 +201,15 @@ pub enum Subcommands { rpc_url: String, }, #[clap(name = "receipt")] - #[clap(about = "Print information about the transaction receipt for ")] + #[clap(about = "Get the transaction receipt for a transaction.")] Receipt { + #[clap(value_name = "TX_HASH")] hash: String, field: Option, #[clap( short, long, - help = "the number of confirmations until the receipt is fetched", + help = "The number of confirmations until the receipt is fetched", default_value = "1" )] confirmations: usize, @@ -217,19 +221,34 @@ pub enum Subcommands { rpc_url: String, }, #[clap(name = "send")] - #[clap(about = "Publish a transaction signed by to call with ")] + #[clap(about = "Sign and publish a transaction.")] SendTx { - #[clap(help = "the address you want to transact with", parse(try_from_str = parse_name_or_address))] + #[clap( + help = "The destination of the transaction.", + parse(try_from_str = parse_name_or_address) + )] to: NameOrAddress, - #[clap(help = "the function signature or name you want to call")] + #[clap(help = "The signature of the function to call.")] sig: Option, - #[clap(help = "the list of arguments you want to call the function with")] + #[clap(help = "The arguments of the function to call.")] args: Vec, - #[clap(long, help = "gas quantity for the transaction", parse(try_from_str = parse_u256))] + #[clap(long, help = "Gas limit for the transaction.", parse(try_from_str = parse_u256))] gas: Option, - #[clap(long = "gas-price", help = "gas price for the transaction", env = "ETH_GAS_PRICE", parse(try_from_str = parse_ether_value))] + #[clap( + long = "gas-price", + help = "Gas price for the transaction.", + env = "ETH_GAS_PRICE", + parse(try_from_str = parse_ether_value) + )] gas_price: Option, - #[clap(long, help = "ether value (in wei or string with unit type e.g. 1ether, 10gwei, 0.01ether) for the transaction", parse(try_from_str = parse_ether_value))] + #[clap( + long, + help = "Ether to send in the transaction.", + long_help = "Ether to send in the transaction, either specified in wei, or as a string with a unit type. + + Examples: 1ether, 10gwei, 0.01ether", + parse(try_from_str = parse_ether_value) + )] value: Option, #[clap(long, help = "nonce for the transaction", parse(try_from_str = parse_u256))] nonce: Option, @@ -239,25 +258,32 @@ pub enum Subcommands { eth: EthereumOpts, #[clap( long, - help = "use legacy transactions instead of EIP1559 ones. this is auto-enabled for common networks without EIP1559" + help = "Send a legacy transaction instead of an EIP1559 transaction.", + long_help = "Send a legacy transaction instead of an EIP1559 transaction. + + This is automatically enabled for common networks without EIP1559." )] legacy: bool, #[clap( short, long, - help = "the number of confirmations until the receipt is fetched", + help = "The number of confirmations until the receipt is fetched.", default_value = "1" )] confirmations: usize, #[clap(long = "json", short = 'j')] to_json: bool, - #[clap(long = "resend", help = "reuse account latest nonce", conflicts_with = "nonce")] + #[clap( + long = "resend", + help = "Reuse the latest nonce for the sender account.", + conflicts_with = "nonce" + )] resend: bool, }, #[clap(name = "publish")] - #[clap(about = "Publish a raw transaction to the network")] + #[clap(about = "Publish a raw transaction to the network.")] PublishTx { - #[clap(help = "the raw transaction you want to publish")] + #[clap(help = "The raw transaction", value_name = "RAW_TX")] raw_tx: String, #[clap(long, env = "CAST_ASYNC")] cast_async: bool, @@ -265,15 +291,22 @@ pub enum Subcommands { eth: EthereumOpts, }, #[clap(name = "estimate")] - #[clap(about = "Estimate the gas cost of a transaction from to with ")] + #[clap(about = "Estimate the gas cost of a transaction.")] Estimate { - #[clap(help = "the address you want to transact with", parse(try_from_str = parse_name_or_address))] + #[clap(help = "The destination of the transaction.", parse(try_from_str = parse_name_or_address))] to: NameOrAddress, - #[clap(help = "the function signature or name you want to call")] + #[clap(help = "The signature of the function to call.")] sig: String, - #[clap(help = "the list of arguments you want to call the function with")] + #[clap(help = "The arguments of the function to call.")] args: Vec, - #[clap(long, help = "value for tx estimate (in wei)")] + #[clap( + long, + help = "Ether to send in the transaction.", + long_help = "Ether to send in the transaction, either specified in wei, or as a string with a unit type. + + Examples: 1ether, 10gwei, 0.01ether", + parse(try_from_str = parse_ether_value) + )] value: Option, #[clap(flatten)] eth: EthereumOpts, @@ -303,13 +336,11 @@ pub enum Subcommands { input: bool, }, #[clap(name = "abi-encode")] - #[clap( - about = "ABI encodes the given arguments with the function signature, excluding the selector" - )] + #[clap(about = "ABI encode the given function argument, excluding the selector.")] AbiEncode { - #[clap(help = "the function signature")] + #[clap(help = "The function signature.")] sig: String, - #[clap(help = "the list of function arguments")] + #[clap(help = "The arguments of the function.")] #[clap(allow_hyphen_values = true)] args: Vec, }, @@ -328,25 +359,31 @@ pub enum Subcommands { slot_number: String, }, #[clap(name = "4byte")] - #[clap(about = "Fetches function signatures given the selector from 4byte.directory")] + #[clap(about = "Get the function signatures for the given the selector from 4byte.directory.")] FourByte { - #[clap(help = "the function selector")] + #[clap(help = "The function selector.")] selector: String, }, #[clap(name = "4byte-decode")] - #[clap(about = "Decodes transaction calldata by fetching the signature using 4byte.directory")] + #[clap(about = "Decode ABI-encoded calldata using 4byte.directory.")] FourByteDecode { - #[clap(help = "the ABI-encoded calldata")] + #[clap(help = "The ABI-encoded calldata.")] calldata: String, - #[clap(long, help = "the 4byte selector id to use, can also be earliest/latest")] + #[clap( + long, + help = "The index of the resolved signature to use.", + long_help = "The index of the resolved signature to use. + + 4byte.directory can have multiple possible signatures for a given selector. + + The index can also be earliest or latest." + )] id: Option, }, #[clap(name = "4byte-event")] - #[clap( - about = "Takes a 32 byte topic and prints the response from querying 4byte.directory for that topic" - )] + #[clap(about = "Get the event signature for a given topic 0 from 4byte.directory.")] FourByteEvent { - #[clap(help = "the 32 byte topic")] + #[clap(help = "Topic 0", value_name = "TOPIC_0")] topic: String, }, #[clap(name = "pretty-calldata")] @@ -359,43 +396,67 @@ pub enum Subcommands { }, #[clap(name = "age")] - #[clap(about = "Prints the timestamp of a block")] + #[clap(about = "Get the timestamp of a block.")] Age { - #[clap(global = true, help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))] + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id) + )] block: Option, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, }, #[clap(name = "balance")] - #[clap(about = "Print the balance of in wei")] + #[clap(about = "Get the balance of an account in wei.")] Balance { - #[clap(long, short, help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))] + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id) + )] block: Option, - #[clap(help = "the account you want to query", parse(try_from_str = parse_name_or_address))] - who: NameOrAddress, + #[clap(help = "The account you want to query", parse(try_from_str = parse_name_or_address))] + account: NameOrAddress, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, }, #[clap(name = "basefee")] - #[clap(about = "Print the basefee of a block")] + #[clap(about = "Get the basefee of a block.")] BaseFee { - #[clap(global = true, help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))] + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id) + )] block: Option, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, }, #[clap(name = "code")] - #[clap(about = "Prints the bytecode at
")] + #[clap(about = "Get the bytecode of a contract.")] Code { - #[clap(long, short, help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))] + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id) + )] block: Option, - #[clap(help = "the address you want to query", parse(try_from_str = parse_name_or_address))] + #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address))] who: NameOrAddress, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, }, #[clap(name = "gas-price")] - #[clap(about = "Prints current gas price of target chain")] + #[clap(about = "Get the current gas price.")] GasPrice { #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, @@ -404,61 +465,73 @@ pub enum Subcommands { #[clap(about = "Keccak-256 hashes arbitrary data")] Keccak { data: String }, #[clap(name = "resolve-name")] - #[clap(about = "Returns the address the provided ENS name resolves to")] + #[clap(about = "Perform an ENS lookup.")] ResolveName { - #[clap(help = "the account you want to resolve")] + #[clap(help = "The name to lookup.")] who: Option, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, - #[clap(long, short, help = "do a forward resolution to ensure the ENS name is correct")] + #[clap(long, short, help = "Perform a reverse lookup to verify that the name is correct.")] verify: bool, }, #[clap(name = "lookup-address")] - #[clap(about = "Returns the name the provided address resolves to")] + #[clap(about = "Perform an ENS reverse lookup.")] LookupAddress { - #[clap(help = "the account you want to resolve")] + #[clap(help = "The account to perform the lookup for.")] who: Option
, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, - #[clap(long, short, help = "do a forward resolution to ensure the address is correct")] + #[clap( + long, + short, + help = "Perform a normal lookup to verify that the address is correct." + )] verify: bool, }, - #[clap(name = "storage", about = "Show the raw value of a contract's storage slot")] + #[clap(name = "storage", about = "Get the raw value of a contract's storage slot.")] Storage { - #[clap(help = "the contract address", parse(try_from_str = parse_name_or_address))] + #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address))] address: NameOrAddress, - #[clap(help = "the storage slot number (hex or number)", parse(try_from_str = parse_slot))] + #[clap(help = "The storage slot number (hex or decimal)", parse(try_from_str = parse_slot))] slot: H256, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, #[clap( long, - short, - help = "the block you want to query, can also be earliest/latest/pending", + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", parse(try_from_str = parse_block_id) )] block: Option, }, - #[clap(name = "proof", about = "Generate a storage proof for a given slot")] + #[clap(name = "proof", about = "Generate a storage proof for a given storage slot.")] Proof { - #[clap(help = "the contract address", parse(try_from_str = parse_name_or_address))] + #[clap(help = "The contract address.", parse(try_from_str = parse_name_or_address))] address: NameOrAddress, - #[clap(help = "the storage slot numbers (hex or number)", parse(try_from_str = parse_slot))] + #[clap(help = "The storage slot numbers (hex or decimal).", parse(try_from_str = parse_slot))] slots: Vec, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, #[clap( long, - short, - help = "the block you want to query, can also be earliest/latest/pending", + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", parse(try_from_str = parse_block_id) )] block: Option, }, #[clap(name = "nonce")] - #[clap(about = "Prints the number of transactions sent from
")] + #[clap(about = "Get the nonce for an account.")] Nonce { - #[clap(long, short = 'B', help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))] + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id) + )] block: Option, #[clap(help = "the address you want to query", parse(try_from_str = parse_name_or_address))] who: NameOrAddress, @@ -466,13 +539,13 @@ pub enum Subcommands { rpc_url: String, }, #[clap(name = "etherscan-source")] - #[clap(about = "Prints the source code of a contract from Etherscan")] + #[clap(about = "Get the source code of a contract from Etherscan.")] EtherscanSource { #[clap(flatten)] chain: ClapChain, - #[clap(help = "the contract address")] + #[clap(help = "The contract's address.")] address: String, - #[clap(short, help = "output directory to expand source tree")] + #[clap(short, help = "The output directory to expand source tree into.", value_hint = ValueHint::DirPath)] directory: Option, #[clap(long, env = "ETHERSCAN_API_KEY")] etherscan_api_key: String, @@ -503,10 +576,7 @@ pub enum Subcommands { #[clap(help = "The human-readable function signature, e.g. 'transfer(address,uint256)'")] sig: String, }, - #[clap( - name = "find-block", - about = "Prints the block number closest to the provided timestamp" - )] + #[clap(name = "find-block", about = "Get the block number closest to the provided timestamp.")] FindBlock(FindBlockArgs), #[clap(about = "Generate shell completions script")] Completions { From ecda2767ff1d1ba51a0608e05eaa1894a73d766a Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 19:21:05 +0200 Subject: [PATCH 02/30] fix: re-rename arg --- cli/src/opts/cast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index f0afa850db989..5dc640060a744 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -421,7 +421,7 @@ pub enum Subcommands { )] block: Option, #[clap(help = "The account you want to query", parse(try_from_str = parse_name_or_address))] - account: NameOrAddress, + who: NameOrAddress, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, }, From 3a0c34fdd7e3cfd960c9fcad224b94447c0ed5ba Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 19:26:13 +0200 Subject: [PATCH 03/30] chore: `cast calldata` --- cli/src/opts/cast.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 5dc640060a744..56a234f2a8c14 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -147,16 +147,17 @@ pub enum Subcommands { #[clap(name = "call")] #[clap(about = "Perform a call on an account without publishing a transaction.")] Call(CallArgs), - #[clap(about = "Pack a signature and an argument list into hexadecimal calldata.")] + #[clap(about = "ABI-encode a function with arguments.")] Calldata { #[clap( - help = r#"When called with of the form (...), then perform ABI encoding to produce the hexadecimal calldata. - If the value given—containing at least one slash character—then treat it as a file name to read, and proceed as if the contents were passed as hexadecimal data. - Given data, ensure it is hexadecimal calldata starting with 0x and normalize it to lowercase. - "# + help = "The function signature.", + long_help = " + The function signature in the form (...), or if the value given contains at least one slash character, then it is treated the path to a file. + + The file is treated as if the contents were passed as hexadecimal data." )] sig: String, - #[clap(allow_hyphen_values = true)] // negative values not yet supported internally + #[clap(allow_hyphen_values = true)] args: Vec, }, #[clap(name = "chain")] From 568d2ac7d35898bab75344a410604394082dfd13 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 19:34:00 +0200 Subject: [PATCH 04/30] chore: more cast commands --- cli/src/opts/cast.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 56a234f2a8c14..2ab80c59e03f2 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -313,27 +313,26 @@ pub enum Subcommands { eth: EthereumOpts, }, #[clap(name = "--calldata-decode")] - #[clap(about = "Decode ABI-encoded hex input data. Use `--abi-decode` to decode output data")] + #[clap(about = "Decode ABI-encoded input data.")] CalldataDecode { - #[clap( - help = "the function signature you want to decode, in the format `()()`" - )] + #[clap(help = "The function signature in the format `()()`.")] sig: String, - #[clap(help = "the encoded calldata, in hex format")] + #[clap(help = "The ABI-encoded calldata.")] calldata: String, }, #[clap(name = "--abi-decode")] #[clap( - about = "Decode ABI-encoded hex output data. Pass --input to decode as input, or use `--calldata-decode`" + about = "Decode ABI-encoded input or output data", + long_about = "Decode ABI-encoded input or output data. + + Defaults to decoding output data. To decode input data pass --input or use cast --calldata-decode." )] AbiDecode { - #[clap( - help = "the function signature you want to decode, in the format `()()`" - )] + #[clap(help = "The function signature in the format `()()`.")] sig: String, - #[clap(help = "the encoded calldata, in hex format")] + #[clap(help = "The ABI-encoded calldata.")] calldata: String, - #[clap(long, short, help = "the encoded output, in hex format")] + #[clap(long, short, help = "The ABI-encoded output data.")] input: bool, }, #[clap(name = "abi-encode")] @@ -388,14 +387,18 @@ pub enum Subcommands { topic: String, }, #[clap(name = "pretty-calldata")] - #[clap(about = "Pretty prints calldata, if available gets signature from 4byte.directory")] + #[clap( + about = "Pretty print calldata.", + long_about = "Pretty print calldata. + + Tries to decode the calldata using 4byte.directory unless --offline is passed." + )] PrettyCalldata { - #[clap(help = "Hex encoded calldata")] + #[clap(help = "The calldata.")] calldata: String, #[clap(long, short, help = "Skip the 4byte directory lookup.")] offline: bool, }, - #[clap(name = "age")] #[clap(about = "Get the timestamp of a block.")] Age { From d20ce0623aba653008e4bad116fee81c004d935d Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 19:51:54 +0200 Subject: [PATCH 05/30] chore: the rest of cast conversion commands --- cli/src/cast.rs | 2 +- cli/src/opts/cast.rs | 50 ++++++++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/cli/src/cast.rs b/cli/src/cast.rs index a3af979ce3aa5..7ba4823b87406 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -133,7 +133,7 @@ async fn main() -> eyre::Result<()> { } Subcommands::ToUnit { value, unit } => { let val = unwrap_or_stdin(value)?; - println!("{}", SimpleCast::to_unit(val, unit.unwrap_or_else(|| String::from("wei")))?); + println!("{}", SimpleCast::to_unit(val, unit)); } Subcommands::ToWei { value, unit } => { let val = unwrap_or_stdin(value)?; diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 2ab80c59e03f2..2ab49357d379d 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -24,64 +24,70 @@ pub enum Subcommands { MaxUint, #[clap(aliases = &["--from-ascii"])] #[clap(name = "--from-utf8")] - #[clap(about = "Convert text data into hexdata")] + #[clap(about = "Convert UTF8 text to hex.")] FromUtf8 { text: Option }, #[clap(name = "--to-hex")] - #[clap(about = "Convert a decimal number into hex")] + #[clap(about = "Convert an integer to hex.")] ToHex { decimal: Option }, #[clap(name = "--concat-hex")] #[clap(about = "Concatencate hex strings")] ConcatHex { data: Vec }, #[clap(name = "--from-bin")] - #[clap(about = "Convert binary data into hex data")] + #[clap(about = "Convert binary data into hex data.")] FromBin, #[clap(name = "--to-hexdata")] - #[clap(about = r#"[||<@tag>] - Output lowercase, 0x-prefixed hex, converting from the - input, which can be: + #[clap( + about = " + Normalize the input to lowercase, 0x-prefixed hex. See --help for more info.", + long_about = "Normalize the input to lowercase, 0x-prefixed hex. + + The input can be: - mixed case hex with or without 0x prefix - 0x prefixed hex, concatenated with a ':' - - absolute path to file - - @tag, where $TAG is defined in environment variables - "#)] + - an absolute path to file + - @tag, where the tag is defined in an environment variable + " + )] ToHexdata { input: Option }, #[clap(aliases = &["--to-checksum"])] // Compatibility with dapptools' cast #[clap(name = "--to-checksum-address")] #[clap(about = "Convert an address to a checksummed format (EIP-55)")] ToCheckSumAddress { address: Option
}, #[clap(name = "--to-ascii")] - #[clap(about = "Convert hex data to text data")] + #[clap(about = "Convert hex data to an ASCII string.")] ToAscii { hexdata: Option }, #[clap(name = "--from-fix")] - #[clap(about = "Convert fixed point into specified number of decimals")] + #[clap(about = "Convert a fixed point number into an integer.")] FromFix { decimals: Option, #[clap(allow_hyphen_values = true)] // negative values not yet supported internally value: Option, }, #[clap(name = "--to-bytes32")] - #[clap(about = "Right-pads a hex bytes string to 32 bytes")] + #[clap(about = "Right-pads hex data to 32 bytes.")] ToBytes32 { bytes: Option }, #[clap(name = "--to-dec")] - #[clap(about = "Convert hex value into decimal number")] + #[clap(about = "Convert hex value into a decimal number.")] ToDec { hexvalue: Option }, #[clap(name = "--to-fix")] - #[clap(about = "Convert integers into fixed point with specified decimals")] + #[clap(about = "Convert an integer into a fixed point number.")] ToFix { decimals: Option, #[clap(allow_hyphen_values = true)] // negative values not yet supported internally value: Option, }, #[clap(name = "--to-uint256")] - #[clap(about = "Convert a number into uint256 hex string with 0x prefix")] + #[clap(about = "Convert a number to a hex-encoded uint256.")] ToUint256 { value: Option }, #[clap(name = "--to-int256")] - #[clap(about = "Convert a number into int256 hex string with 0x prefix")] + #[clap(about = "Convert a number to a hex-encoded int256.")] ToInt256 { value: Option }, #[clap(name = "--to-unit")] #[clap( - about = r#"Convert an ETH amount into a specified unit: ether, gwei or wei (default: wei). - Usage: + about = "Convert an ETH amount into another unit (ether, gwei or wei).", + long_about = r#"Convert an ETH amount into another unit (ether, gwei or wei). + + Examples: - 1ether wei | converts 1 ether to wei - "1 ether" wei | converts 1 ether to wei - 1ether | converts 1 ether to wei @@ -89,9 +95,13 @@ pub enum Subcommands { - 1gwei ether | converts 1 gwei to ether "# )] - ToUnit { value: Option, unit: Option }, + ToUnit { + value: Option, + #[clap(help = "The unit to convert to (ether, gwei, wei).", default_value = "wei")] + unit: String, + }, #[clap(name = "--to-wei")] - #[clap(about = "Convert an ETH amount into wei. Consider using --to-unit.")] + #[clap(about = "Convert an ETH amount to wei. Consider using --to-unit.")] ToWei { #[clap(allow_hyphen_values = true)] // negative values not yet supported internally value: Option, From 91d432262ae251546bbc45f8c420c4915a02e36c Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 20:09:35 +0200 Subject: [PATCH 06/30] chore: all cast util commands --- cli/src/opts/cast.rs | 66 +++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 2ab49357d379d..11035f20650ae 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -14,13 +14,13 @@ use std::{path::PathBuf, str::FromStr}; #[clap(about = "Perform Ethereum RPC calls from the comfort of your command line.")] pub enum Subcommands { #[clap(name = "--max-int")] - #[clap(about = "Maximum i256 value")] + #[clap(about = "Get the maximum i256 value.")] MaxInt, #[clap(name = "--min-int")] - #[clap(about = "Minimum i256 value")] + #[clap(about = "Get the minimum i256 value.")] MinInt, #[clap(name = "--max-uint")] - #[clap(about = "Maximum u256 value")] + #[clap(about = "Get the maximum u256 value.")] MaxUint, #[clap(aliases = &["--from-ascii"])] #[clap(name = "--from-utf8")] @@ -30,7 +30,7 @@ pub enum Subcommands { #[clap(about = "Convert an integer to hex.")] ToHex { decimal: Option }, #[clap(name = "--concat-hex")] - #[clap(about = "Concatencate hex strings")] + #[clap(about = "Concatencate hex strings.")] ConcatHex { data: Vec }, #[clap(name = "--from-bin")] #[clap(about = "Convert binary data into hex data.")] @@ -51,7 +51,7 @@ pub enum Subcommands { ToHexdata { input: Option }, #[clap(aliases = &["--to-checksum"])] // Compatibility with dapptools' cast #[clap(name = "--to-checksum-address")] - #[clap(about = "Convert an address to a checksummed format (EIP-55)")] + #[clap(about = "Convert an address to a checksummed format (EIP-55).")] ToCheckSumAddress { address: Option
}, #[clap(name = "--to-ascii")] #[clap(about = "Convert hex data to an ASCII string.")] @@ -115,13 +115,21 @@ pub enum Subcommands { unit: Option, }, #[clap(name = "access-list")] - #[clap(about = "Create an access list for a transaction")] + #[clap(about = "Create an access list for a transaction.")] AccessList { - #[clap(help = "the address you want to query", parse(try_from_str = parse_name_or_address))] + #[clap(help = "The destination of the transaction.", parse(try_from_str = parse_name_or_address))] address: NameOrAddress, + #[clap(help = "The signature of the function to call.")] sig: String, + #[clap(help = "The arguments of the function to call.")] args: Vec, - #[clap(long, short, help = "the block you want to query, can also be earliest/latest/pending", parse(try_from_str = parse_block_id))] + #[clap( + long, + short = 'B', + help = "The block height you want to query at.", + long_help = "The block height you want to query at. Can also be the tags earliest, latest, or pending.", + parse(try_from_str = parse_block_id) + )] block: Option, #[clap(flatten)] eth: EthereumOpts, @@ -189,13 +197,13 @@ pub enum Subcommands { rpc_url: String, }, #[clap(name = "compute-address")] - #[clap(about = "Returns the computed address from a given address and nonce pair")] + #[clap(about = "Compute the contract address from a given nonce and deployer address.")] ComputeAddress { #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, - #[clap(help = "the address to create from")] + #[clap(help = "The deployer address.")] address: String, - #[clap(long, help = "address nonce", parse(try_from_str = parse_u256))] + #[clap(long, help = "The nonce of the deployer address.", parse(try_from_str = parse_u256))] nonce: Option, }, #[clap(name = "namehash")] @@ -355,17 +363,15 @@ pub enum Subcommands { args: Vec, }, #[clap(name = "index")] - #[clap( - about = "Get storage slot of value from mapping type, mapping slot number and input value" - )] + #[clap(about = "Compute the storage slot for an entry in a mapping.")] Index { - #[clap(help = "mapping key type")] + #[clap(help = "The mapping key type.")] from_type: String, - #[clap(help = "mapping value type")] + #[clap(help = "The mapping value type.")] to_type: String, - #[clap(help = "the value")] + #[clap(help = "The mapping key.")] from_value: String, - #[clap(help = "storage slot of the mapping")] + #[clap(help = "The storage slot of the mapping.")] slot_number: String, }, #[clap(name = "4byte")] @@ -476,7 +482,7 @@ pub enum Subcommands { rpc_url: String, }, #[clap(name = "keccak")] - #[clap(about = "Keccak-256 hashes arbitrary data")] + #[clap(about = "Hash arbitrary data using keccak-256.")] Keccak { data: String }, #[clap(name = "resolve-name")] #[clap(about = "Perform an ENS lookup.")] @@ -571,23 +577,33 @@ pub enum Subcommands { }, #[clap( name = "interface", - about = "Generate contract's interface from ABI. Currently it doesn't support ABI encoder V2" + about = "Generate a Solidity interface from a given ABI.", + long_about = "Generate a Solidity interface from a given ABI. Currently does not support ABI encoder v2." )] Interface { - #[clap(help = "The contract address or path to ABI file")] + #[clap( + help = "The contract address, or the path to an ABI file.", + long_help = "The contract address, or the path to an ABI file. + + If an address is specified, then the ABI is fetched from Etherscan." + )] path_or_address: String, - #[clap(long, short, default_value = "^0.8.10", help = "pragma version")] + #[clap(long, short, default_value = "^0.8.10", help = "Solidity pragma version.")] pragma: String, - #[clap(short, help = "Path to output file. Defaults to stdout")] + #[clap( + short, + help = "The path to the output file.", + long_help = "The path to the output file. If not specified, the interface will be output to stdout." + )] output_location: Option, #[clap(short, env = "ETHERSCAN_API_KEY", help = "etherscan API key")] etherscan_api_key: Option, #[clap(flatten)] chain: ClapChain, }, - #[clap(name = "sig", about = "Print a function's 4-byte selector")] + #[clap(name = "sig", about = "Get the selector for a function.")] Sig { - #[clap(help = "The human-readable function signature, e.g. 'transfer(address,uint256)'")] + #[clap(help = "The function signature, e.g. transfer(address,uint256).")] sig: String, }, #[clap(name = "find-block", about = "Get the block number closest to the provided timestamp.")] From 2bffc57083a2068bd21435fa2b9302475db10731 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 20:10:42 +0200 Subject: [PATCH 07/30] fix: `cast --to-unit` --- cli/src/cast.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/cast.rs b/cli/src/cast.rs index 7ba4823b87406..a3c38fbfa4d34 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -133,7 +133,7 @@ async fn main() -> eyre::Result<()> { } Subcommands::ToUnit { value, unit } => { let val = unwrap_or_stdin(value)?; - println!("{}", SimpleCast::to_unit(val, unit)); + println!("{}", SimpleCast::to_unit(val, unit)?); } Subcommands::ToWei { value, unit } => { let val = unwrap_or_stdin(value)?; From 0f1be9b09278d487529fc906c9999c203552979e Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 11 Apr 2022 20:15:12 +0200 Subject: [PATCH 08/30] chore: `cast wallet` --- cli/src/opts/cast.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 11035f20650ae..96e4f11ce3212 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -570,7 +570,7 @@ pub enum Subcommands { #[clap(long, env = "ETHERSCAN_API_KEY")] etherscan_api_key: String, }, - #[clap(name = "wallet", about = "Set of wallet management utilities")] + #[clap(name = "wallet", about = "Wallet management utilities.")] Wallet { #[clap(subcommand)] command: WalletSubcommands, @@ -617,57 +617,57 @@ pub enum Subcommands { #[derive(Debug, Parser)] pub enum WalletSubcommands { - #[clap(name = "new", about = "Create and output a new random keypair")] + #[clap(name = "new", about = "Create a new random keypair.")] New { - #[clap(help = "If provided, then keypair will be written to encrypted json keystore")] + #[clap(help = "If provided, then keypair will be written to an encrypted JSON keystore.")] path: Option, #[clap( long, short, - help = "Triggers a hidden password prompt for the json keystore", + help = "Triggers a hidden password prompt for the JSON keystore.", conflicts_with = "unsafe-password", requires = "path" )] password: bool, #[clap( long, - help = "Password for json keystore in cleartext. This is UNSAFE to use and we recommend using the --password parameter", + help = "Password for the JSON keystore in cleartext. This is UNSAFE to use and we recommend using the --password.", requires = "path", env = "CAST_PASSWORD" )] unsafe_password: Option, }, - #[clap(name = "vanity", about = "Generate a vanity address")] + #[clap(name = "vanity", about = "Generate a vanity address.")] Vanity { - #[clap(long, help = "Prefix for vanity address", required_unless_present = "ends-with")] + #[clap(long, help = "Prefix for vanity address.", required_unless_present = "ends-with")] starts_with: Option, - #[clap(long, help = "Suffix for vanity address")] + #[clap(long, help = "Suffix for vanity address.")] ends_with: Option, #[clap( long, - help = "Generate a vanity contract address created by the generated account with specified nonce" + help = "Generate a vanity contract address created by the generated account with specified nonce." )] nonce: Option, /* 2^64-1 is max possible nonce per https://eips.ethereum.org/EIPS/eip-2681 */ }, - #[clap(name = "address", about = "Convert a private key to an address")] + #[clap(name = "address", about = "Convert a private key to an address.")] Address { #[clap(flatten)] wallet: Wallet, }, - #[clap(name = "sign", about = "Sign the message with provided private key")] + #[clap(name = "sign", about = "Sign a message.")] Sign { #[clap(help = "message to sign")] message: String, #[clap(flatten)] wallet: Wallet, }, - #[clap(name = "verify", about = "Verify the signature on the message")] + #[clap(name = "verify", about = "Verify the signature of a message.")] Verify { - #[clap(help = "original message")] + #[clap(help = "The original message.")] message: String, - #[clap(help = "signature to verify")] + #[clap(help = "The signature to verify.")] signature: String, - #[clap(long, short, help = "pubkey of message signer")] + #[clap(long, short, help = "The public key of the message signer.")] address: String, }, } From 9b3003914df7570b6f3ce415d34c2580c7d2ffe7 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 01:37:14 +0200 Subject: [PATCH 09/30] chore: minor changes to help text --- cli/src/opts/cast.rs | 13 ++++++------- cli/src/opts/mod.rs | 16 ++++++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 96e4f11ce3212..39fa846e4446e 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -169,10 +169,7 @@ pub enum Subcommands { Calldata { #[clap( help = "The function signature.", - long_help = " - The function signature in the form (...), or if the value given contains at least one slash character, then it is treated the path to a file. - - The file is treated as if the contents were passed as hexadecimal data." + long_help = "The function signature in the form ()" )] sig: String, #[clap(allow_hyphen_values = true)] @@ -306,6 +303,7 @@ pub enum Subcommands { raw_tx: String, #[clap(long, env = "CAST_ASYNC")] cast_async: bool, + // FIXME: We only need the RPC URL and `--flashbots` options from this. #[clap(flatten)] eth: EthereumOpts, }, @@ -328,6 +326,7 @@ pub enum Subcommands { )] value: Option, #[clap(flatten)] + // TODO: We only need RPC URL and Etherscan API key here. eth: EthereumOpts, }, #[clap(name = "--calldata-decode")] @@ -350,7 +349,7 @@ pub enum Subcommands { sig: String, #[clap(help = "The ABI-encoded calldata.")] calldata: String, - #[clap(long, short, help = "The ABI-encoded output data.")] + #[clap(long, short, help = "Decode input data.")] input: bool, }, #[clap(name = "abi-encode")] @@ -375,7 +374,7 @@ pub enum Subcommands { slot_number: String, }, #[clap(name = "4byte")] - #[clap(about = "Get the function signatures for the given the selector from 4byte.directory.")] + #[clap(about = "Get the function signatures for the given selector from 4byte.directory.")] FourByte { #[clap(help = "The function selector.")] selector: String, @@ -553,7 +552,7 @@ pub enum Subcommands { parse(try_from_str = parse_block_id) )] block: Option, - #[clap(help = "the address you want to query", parse(try_from_str = parse_name_or_address))] + #[clap(help = "The address you want to get the nonce for.", parse(try_from_str = parse_name_or_address))] who: NameOrAddress, #[clap(short, long, env = "ETH_RPC_URL")] rpc_url: String, diff --git a/cli/src/opts/mod.rs b/cli/src/opts/mod.rs index d8de4960e07a3..62af33ef173d5 100644 --- a/cli/src/opts/mod.rs +++ b/cli/src/opts/mod.rs @@ -189,28 +189,32 @@ The wallet options can either be: "# )] pub struct Wallet { - #[clap(long, short, help = "Interactive prompt to insert your private key")] + #[clap(long, short, help = "Open an interactive prompt to enter your private key.")] pub interactive: bool, #[clap(long = "private-key", help = "Your private key string")] pub private_key: Option, - #[clap(env = "ETH_KEYSTORE", long = "keystore", help = "Path to your keystore folder / file")] + #[clap( + env = "ETH_KEYSTORE", + long = "keystore", + help = "The path to a keystore folder or file." + )] pub keystore_path: Option, #[clap(long = "password", help = "Your keystore password", requires = "keystore-path")] pub keystore_password: Option, - #[clap(long = "mnemonic-path", help = "Path to your mnemonic file")] + #[clap(long = "mnemonic-path", help = "Path to a mnemonic file.")] pub mnemonic_path: Option, - #[clap(short, long = "ledger", help = "Use your Ledger hardware wallet")] + #[clap(short, long = "ledger", help = "Use your Ledger hardware wallet.")] pub ledger: bool, - #[clap(short, long = "trezor", help = "Use your Trezor hardware wallet")] + #[clap(short, long = "trezor", help = "Use your Trezor hardware wallet.")] pub trezor: bool, - #[clap(long = "hd-path", help = "Derivation path for your hardware wallet (trezor or ledger)")] + #[clap(long = "hd-path", help = "Derivation path for your hardware wallet.")] pub hd_path: Option, #[clap( From f4def8abd4ebc645c19ff91f6916bf5e482d864f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 03:05:13 +0200 Subject: [PATCH 10/30] style: add long option for etherscan key --- cli/src/opts/cast.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 39fa846e4446e..c3a6aff575dbb 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -132,6 +132,7 @@ pub enum Subcommands { )] block: Option, #[clap(flatten)] + // TODO: We only need RPC URL + etherscan stuff from this struct eth: EthereumOpts, #[clap(long = "json", short = 'j')] to_json: bool, @@ -595,7 +596,7 @@ pub enum Subcommands { long_help = "The path to the output file. If not specified, the interface will be output to stdout." )] output_location: Option, - #[clap(short, env = "ETHERSCAN_API_KEY", help = "etherscan API key")] + #[clap(long, short, env = "ETHERSCAN_API_KEY", help = "etherscan API key")] etherscan_api_key: Option, #[clap(flatten)] chain: ClapChain, From acf53f874f2e35a0148056c54f5e72b4d59d5973 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 03:19:30 +0200 Subject: [PATCH 11/30] style: clearer arg names for `cast index` --- cli/src/cast.rs | 4 ++-- cli/src/opts/cast.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/src/cast.rs b/cli/src/cast.rs index a3c38fbfa4d34..5e0fb3a7593a2 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -391,8 +391,8 @@ async fn main() -> eyre::Result<()> { Subcommands::AbiEncode { sig, args } => { println!("{}", SimpleCast::abi_encode(&sig, &args)?); } - Subcommands::Index { from_type, to_type, from_value, slot_number } => { - let encoded = SimpleCast::index(&from_type, &to_type, &from_value, &slot_number)?; + Subcommands::Index { key_type, value_type, key, slot_number } => { + let encoded = SimpleCast::index(&key_type, &value_type, &key, &slot_number)?; println!("{}", encoded); } Subcommands::FourByte { selector } => { diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index c3a6aff575dbb..4b6f0a6debd66 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -366,11 +366,11 @@ pub enum Subcommands { #[clap(about = "Compute the storage slot for an entry in a mapping.")] Index { #[clap(help = "The mapping key type.")] - from_type: String, + key_type: String, #[clap(help = "The mapping value type.")] - to_type: String, + value_type: String, #[clap(help = "The mapping key.")] - from_value: String, + key: String, #[clap(help = "The storage slot of the mapping.")] slot_number: String, }, From 366545ee7cd726d14070d3277ca7526db8002e95 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 05:15:52 +0200 Subject: [PATCH 12/30] style: minor adjustments --- cli/src/opts/cast.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 4b6f0a6debd66..589249d34d428 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -639,13 +639,17 @@ pub enum WalletSubcommands { }, #[clap(name = "vanity", about = "Generate a vanity address.")] Vanity { - #[clap(long, help = "Prefix for vanity address.", required_unless_present = "ends-with")] + #[clap( + long, + help = "Prefix for the vanity address.", + required_unless_present = "ends-with" + )] starts_with: Option, - #[clap(long, help = "Suffix for vanity address.")] + #[clap(long, help = "Suffix for the vanity address.")] ends_with: Option, #[clap( long, - help = "Generate a vanity contract address created by the generated account with specified nonce." + help = "Generate a vanity contract address created by the generated keypair with the specified nonce." )] nonce: Option, /* 2^64-1 is max possible nonce per https://eips.ethereum.org/EIPS/eip-2681 */ }, @@ -667,7 +671,7 @@ pub enum WalletSubcommands { message: String, #[clap(help = "The signature to verify.")] signature: String, - #[clap(long, short, help = "The public key of the message signer.")] + #[clap(long, short, help = "The address of the message signer.")] address: String, }, } From 5bd7b0aec1394763d2e56780455ad240dde75ce6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 06:41:52 +0200 Subject: [PATCH 13/30] style: wording in `forge --help` --- cli/src/opts/forge.rs | 49 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 50849e8083006..1cbcabdcca8a0 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -33,28 +33,32 @@ pub struct Opts { } #[derive(Debug, Subcommand)] -#[clap(about = "Build, test, fuzz, formally verify, debug & deploy solidity contracts.")] +#[clap(about = "Build, test, fuzz, debug and deploy Solidity contracts.")] #[allow(clippy::large_enum_variant)] pub enum Subcommands { - #[clap(about = "Test your smart contracts")] + #[clap(about = "Run the project's tests.")] #[clap(alias = "t")] Test(test::TestArgs), - #[clap(about = "Generate rust bindings for your smart contracts")] + #[clap(about = "Generate Rust bindings for smart contracts.")] Bind(BindArgs), - #[clap(about = "Build your smart contracts")] + #[clap(about = "Build the project's smart contracts.")] #[clap(alias = "b")] Build(BuildArgs), - #[clap(about = "Run a single smart contract as a script")] + #[clap(about = "Run a single smart contract as a script.")] #[clap(alias = "r")] Run(RunArgs), - #[clap(alias = "u", about = "Fetches all upstream lib changes")] + #[clap( + alias = "u", + about = "Update one or multiple dependencies.", + long_about = "Update one or multiple dependencies. If no arguments are provided, then all dependencies are updated." + )] Update { #[clap( - help = "The submodule name of the library you want to update (will update all if none is provided)", + help = "The path to the dependency you want to update.", value_hint = ValueHint::DirPath )] lib: Option, @@ -62,33 +66,36 @@ pub enum Subcommands { #[clap( alias = "i", - about = "Installs one or more dependencies as git submodules (will install existing dependencies if no arguments are provided)" + about = "Install one or multiple dependencies.", + long_about = "Install one or more dependencies as git submodules. If no arguments are provided, then existing dependencies will be installed." )] Install(InstallArgs), - #[clap(alias = "rm", about = "Removes one or more dependencies from git submodules")] + #[clap(alias = "rm", about = "Remove one or multiple dependencies.")] Remove { - #[clap(help = "The submodule name of the library you want to remove")] + #[clap(help = "The path to the dependency you want to remove.")] dependencies: Vec, }, - #[clap(about = "Prints the automatically inferred remappings for this repository")] + #[clap(about = "Get the automatically inferred remappings for this project.")] Remappings(RemappingArgs), #[clap( - about = "Verify your smart contracts source code on Etherscan. Requires `ETHERSCAN_API_KEY` to be set." + about = "Verify smart contracts on Etherscan.", + long_about = "Verify smart contracts on Etherscan. Requires `ETHERSCAN_API_KEY` to be set." )] VerifyContract(VerifyArgs), #[clap( - about = "Check verification status on Etherscan. Requires `ETHERSCAN_API_KEY` to be set." + about = "Check verification status on Etherscan.", + long_about = "Check verification status on Etherscan. Requires `ETHERSCAN_API_KEY` to be set." )] VerifyCheck(VerifyCheckArgs), - #[clap(alias = "c", about = "Deploy a compiled contract")] + #[clap(alias = "c", about = "Deploy a smart contract.")] Create(CreateArgs), - #[clap(alias = "i", about = "Initializes a new forge sample project")] + #[clap(alias = "i", about = "Create a new Forge project.")] Init(InitArgs), #[clap(about = "Generate shell completions script")] @@ -97,7 +104,7 @@ pub enum Subcommands { shell: clap_complete::Shell, }, - #[clap(about = "Removes the build artifacts and cache directories")] + #[clap(about = "Remove the build artifacts and cache directories.")] Clean { #[clap( help = "The project's root path, default being the current working directory", @@ -107,19 +114,19 @@ pub enum Subcommands { root: Option, }, - #[clap(about = "Creates a snapshot of each test's gas usage")] + #[clap(about = "Create a snapshot of each test's gas usage.")] Snapshot(snapshot::SnapshotArgs), - #[clap(about = "Shows the currently set config values")] + #[clap(about = "Display the current config.")] Config(config::ConfigArgs), - #[clap(about = "Concats a file with all of its imports")] + #[clap(about = "Flatten a source file and all of its imports into one file.")] Flatten(flatten::FlattenArgs), // #[clap(about = "formats Solidity source files")] // Fmt(FmtArgs), - #[clap(about = "Outputs a contract in a specified format (ir, assembly, ...)")] + #[clap(about = "Get specialized information about a smart contract")] Inspect(inspect::InspectArgs), - #[clap(about = "Display a tree visualization of the project's dependency graph")] + #[clap(about = "Display a tree visualization of the project's dependency graph.")] Tree(tree::TreeArgs), } From 7e6e2dba7c2ba39aa93ad8071f9315eb3f7f35f6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 07:44:28 +0200 Subject: [PATCH 14/30] style: adjustments to `forge init` --- cli/src/cmd/forge/init.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cli/src/cmd/forge/init.rs b/cli/src/cmd/forge/init.rs index 178da95e148e7..096bf711f66e5 100644 --- a/cli/src/cmd/forge/init.rs +++ b/cli/src/cmd/forge/init.rs @@ -21,33 +21,33 @@ use std::{ #[derive(Debug, Clone, Parser)] pub struct InitArgs { #[clap( - help = "the project's root path, default being the current working directory", - value_hint = ValueHint::DirPath + help = "The root directory of the new project. Defaults to the current working directory.", + value_hint = ValueHint::DirPath )] root: Option, - #[clap(help = "optional solidity template to start from", long, short)] + #[clap(help = "The template to start from.", long, short)] template: Option, - #[clap( - help = "initialize without creating a git repository", - conflicts_with = "template", - long - )] + #[clap(help = "Do not create a git repository.", conflicts_with = "template", long)] no_git: bool, - #[clap(help = "do not create initial commit", conflicts_with = "template", long)] + #[clap(help = "Do not create an initial commit.", conflicts_with = "template", long)] no_commit: bool, - #[clap(help = "do not print messages", short, long)] + #[clap(help = "Do not print any messages.", short, long)] quiet: bool, #[clap( - help = "run without installing libs from the network", + help = "Do not install dependencies from the network.", conflicts_with = "template", long, alias = "no-deps" )] offline: bool, - #[clap(help = "force init if project dir is not empty", conflicts_with = "template", long)] + #[clap( + help = "Create the project even if the specified root directory is not empty.", + conflicts_with = "template", + long + )] force: bool, #[clap( - help = "initialize .vscode/settings.json file with solidity settings and generate a remappings.txt file.", + help = "Create a .vscode/settings.json file with Solidity settings, and generate a remappings.txt file.", conflicts_with = "template", long )] From d72e4b6c03e7362a6c476b5f0aae0035a0a1222d Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 08:01:04 +0200 Subject: [PATCH 15/30] style: `forge clean` adjustments --- cli/src/opts/forge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 1cbcabdcca8a0..805048acd70db 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -107,7 +107,7 @@ pub enum Subcommands { #[clap(about = "Remove the build artifacts and cache directories.")] Clean { #[clap( - help = "The project's root path, default being the current working directory", + help = "The project's root path. Defaults to the current working directory.", long, value_hint = ValueHint::DirPath )] From 3e7136e65308a7d2090c8a313b3e9f0637d8cf69 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Tue, 12 Apr 2022 08:19:20 +0200 Subject: [PATCH 16/30] style: adjustments to `forge install` --- cli/src/cmd/forge/install.rs | 22 +++++++++++++++------- cli/src/opts/forge.rs | 9 ++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cli/src/cmd/forge/install.rs b/cli/src/cmd/forge/install.rs index 53806b0c68e49..082a3b7d7dfa4 100644 --- a/cli/src/cmd/forge/install.rs +++ b/cli/src/cmd/forge/install.rs @@ -14,14 +14,22 @@ use std::{ /// Command to install dependencies #[derive(Debug, Clone, Parser)] pub struct InstallArgs { - #[clap( - help = "Installs one or more dependencies as git submodules (will install existing dependencies if no arguments are provided)" - )] + /// The dependencies to install. + /// + /// A dependency can be a raw URL, or the path to a GitHub repository. + /// + /// Additionally, a ref can be provided by adding @ to the dependency path. + /// + /// A ref can be: + /// - A branch: master + /// - A tag: v1.2.3 + /// - A commit: 8e8128 dependencies: Vec, #[clap(flatten)] opts: DependencyInstallOpts, #[clap( - help = "the project's root path. By default, this is the root directory of the current Git repository or the current working directory if it is not part of a Git repository", + help = "The project's root path.", + long_help = "The project's root path. By default, this is the root directory of the current Git repository, or the current working directory.", long, value_hint = ValueHint::DirPath )] @@ -40,11 +48,11 @@ impl Cmd for InstallArgs { #[derive(Debug, Clone, Copy, Default, Parser)] pub struct DependencyInstallOpts { - #[clap(help = "install without creating a submodule repository", long)] + #[clap(help = "Install without adding the dependency as a submodule.", long)] pub no_git: bool, - #[clap(help = "do not create a commit", long)] + #[clap(help = "Do not create a commit.", long)] pub no_commit: bool, - #[clap(help = "do not print messages", short, long)] + #[clap(help = "Do not print any messages.", short, long)] pub quiet: bool, } diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 805048acd70db..613a2aa45e9bc 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -64,11 +64,10 @@ pub enum Subcommands { lib: Option, }, - #[clap( - alias = "i", - about = "Install one or multiple dependencies.", - long_about = "Install one or more dependencies as git submodules. If no arguments are provided, then existing dependencies will be installed." - )] + /// Install one or multiple dependencies. + /// + /// If no arguments are provided, then existing dependencies will be installed. + #[clap(alias = "i")] Install(InstallArgs), #[clap(alias = "rm", about = "Remove one or multiple dependencies.")] From 187794ab67b7f1561e0701987ba59a1f2f35304f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 13 Apr 2022 13:18:57 +0200 Subject: [PATCH 17/30] style: improvements to `forge remappings` --- cli/src/cmd/forge/remappings.rs | 12 ++++++------ cli/src/opts/forge.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cli/src/cmd/forge/remappings.rs b/cli/src/cmd/forge/remappings.rs index 3b162b00828b2..e501b551b9fcf 100644 --- a/cli/src/cmd/forge/remappings.rs +++ b/cli/src/cmd/forge/remappings.rs @@ -9,17 +9,17 @@ use std::path::{Path, PathBuf}; #[derive(Debug, Clone, Parser)] pub struct RemappingArgs { #[clap( - help = "the project's root path, default being the current working directory", + help = "The project's root path. Defaults to the current working directory.", long, value_hint = ValueHint::DirPath )] root: Option, #[clap( - help = "the paths where your libraries are installed", + help = "The path to the library folder.", long, value_hint = ValueHint::DirPath )] - lib_paths: Vec, + lib_path: Vec, } impl Cmd for RemappingArgs { @@ -29,13 +29,13 @@ impl Cmd for RemappingArgs { let root = self.root.unwrap_or_else(|| std::env::current_dir().unwrap()); let root = dunce::canonicalize(root)?; - let lib_paths = if self.lib_paths.is_empty() { + let lib_path = if self.lib_path.is_empty() { ProjectPathsConfig::find_libs(&root) } else { - self.lib_paths + self.lib_path }; let remappings: Vec<_> = - lib_paths.iter().flat_map(|lib| relative_remappings(lib, &root)).collect(); + lib_path.iter().flat_map(|lib| relative_remappings(lib, &root)).collect(); remappings.iter().for_each(|x| println!("{}", x)); Ok(()) } diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 613a2aa45e9bc..6c0eed0d00707 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -76,7 +76,7 @@ pub enum Subcommands { dependencies: Vec, }, - #[clap(about = "Get the automatically inferred remappings for this project.")] + #[clap(about = "Get the automatically inferred remappings for the project.")] Remappings(RemappingArgs), #[clap( From b0b20b202e1da7e6d03d745bf621a594100a54c7 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 13 Apr 2022 18:28:37 +0200 Subject: [PATCH 18/30] style: improvements to `forge flatten` --- cli/src/cmd/forge/flatten.rs | 43 ++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/cli/src/cmd/forge/flatten.rs b/cli/src/cmd/forge/flatten.rs index 07d4a73757391..f516f8c19604f 100644 --- a/cli/src/cmd/forge/flatten.rs +++ b/cli/src/cmd/forge/flatten.rs @@ -9,42 +9,45 @@ use foundry_config::Config; #[derive(Debug, Clone, Parser)] pub struct CoreFlattenArgs { #[clap( - help = "the project's root path. By default, this is the root directory of the current Git repository or the current working directory if it is not part of a Git repository", - long, - value_hint = ValueHint::DirPath + help = "The project's root path.", + long_help = "The project's root path. By default, this is the root directory of the current Git repository, or the current working directory.", + long, + value_hint = ValueHint::DirPath )] pub root: Option, #[clap( - env = "DAPP_SRC", - help = "the directory relative to the root under which the smart contracts are", - long, - short, - value_hint = ValueHint::DirPath + env = "DAPP_SRC", + help = "The contract's source directory, relative to the project root.", + long, + short, + value_hint = ValueHint::DirPath )] pub contracts: Option, - #[clap(help = "the remappings", long, short)] + #[clap(help = "The project's remappings.", long, short)] pub remappings: Vec, + #[clap(long = "remappings-env", env = "DAPP_REMAPPINGS")] pub remappings_env: Option, #[clap( - help = "the path where cached compiled contracts are stored", + help = "The path to the compiler cache.", long = "cache-path", value_hint = ValueHint::DirPath )] pub cache_path: Option, #[clap( - help = "the paths where your libraries are installed", - long, - value_hint = ValueHint::DirPath + help = "The path to the library folder.", + long, + value_hint = ValueHint::DirPath )] pub lib_paths: Vec, #[clap( - help = "uses hardhat style project layout. This a convenience flag and is the same as `--contracts contracts --lib-paths node_modules`", + help = "Use the Hardhat-style project layout.", + long_help = "Use the Hardhat-style project layout.", long, conflicts_with = "contracts", alias = "hh" @@ -54,10 +57,16 @@ pub struct CoreFlattenArgs { #[derive(Debug, Clone, Parser)] pub struct FlattenArgs { - #[clap(help = "the path to the contract to flatten", value_hint = ValueHint::FilePath)] + #[clap(help = "The path to the contract to flatten.", value_hint = ValueHint::FilePath)] pub target_path: PathBuf, - #[clap(long, short, help = "output path for the flattened contract", value_hint = ValueHint::FilePath)] + #[clap( + long, + short, + help = "The path to output the flattened contract.", + long_help = "The path to output the flattened contract. If not specified, the flattened contract will be output to stdout.", + value_hint = ValueHint::FilePath + )] pub output: Option, #[clap(flatten)] @@ -99,7 +108,7 @@ impl Cmd for FlattenArgs { let target_path = dunce::canonicalize(target_path)?; let flattened = paths .flatten(&target_path) - .map_err(|err| eyre::Error::msg(format!("failed to flatten the file: {}", err)))?; + .map_err(|err| eyre::Error::msg(format!("Failed to flatten the file: {}", err)))?; match output { Some(output) => { From 301d69664898773e652e2ae4ab59e878e9fd02a6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 13 Apr 2022 19:55:44 +0200 Subject: [PATCH 19/30] refactor: extract `ProjectPathsArgs` --- cli/src/cmd/forge/build.rs | 211 ++++++++++++++++++++--------------- cli/src/cmd/forge/flatten.rs | 64 +---------- cli/src/cmd/forge/tree.rs | 5 +- cli/src/cmd/forge/verify.rs | 28 +---- 4 files changed, 134 insertions(+), 174 deletions(-) diff --git a/cli/src/cmd/forge/build.rs b/cli/src/cmd/forge/build.rs index 60dd56e803e27..98d69c07b5579 100644 --- a/cli/src/cmd/forge/build.rs +++ b/cli/src/cmd/forge/build.rs @@ -17,7 +17,7 @@ use foundry_config::{ value::{Dict, Map, Value}, Figment, Metadata, Profile, Provider, }, - find_project_root_path, remappings_from_env_var, Config, + find_project_root_path, impl_figment_convert, remappings_from_env_var, Config, }; use serde::Serialize; use watchexec::config::{InitConfig, RuntimeConfig}; @@ -35,11 +35,11 @@ impl<'a> From<&'a BuildArgs> for Figment { let config_path = canonicalized(config_path); Config::figment_with_root(config_path.parent().unwrap()) } else { - Config::figment_with_root(args.project_root()) + Config::figment_with_root(args.project_paths.project_root()) }; // remappings should stack - let mut remappings = args.get_remappings(); + let mut remappings = args.project_paths.get_remappings(); remappings .extend(figment.extract_inner::>("remappings").unwrap_or_default()); remappings.sort_by(|a, b| a.name.cmp(&b.name)); @@ -55,7 +55,7 @@ impl<'a> From<&'a BuildArgs> for Config { // if `--config-path` is set we need to adjust the config's root path to the actual root // path for the project, otherwise it will the parent dir of the `--config-path` if args.config_path.is_some() { - config.__root = args.project_root().into(); + config.__root = args.project_paths.project_root().into(); } config } @@ -83,56 +83,9 @@ impl<'a> From<&'a BuildArgs> for Config { // `figment::Provider` implementation #[derive(Debug, Clone, Parser, Serialize)] pub struct BuildArgs { - #[clap( - help = "the project's root path. By default, this is the root directory of the current Git repository or the current working directory if it is not part of a Git repository", - long, - value_hint = ValueHint::DirPath - )] - #[serde(skip)] - pub root: Option, - - #[clap( - env = "DAPP_SRC", - help = "the directory relative to the root under which the smart contracts are", - long, - short, - value_hint = ValueHint::DirPath - )] - #[serde(rename = "src", skip_serializing_if = "Option::is_none")] - pub contracts: Option, - - #[clap(help = "the remappings", long, short)] - #[serde(skip)] - pub remappings: Vec, - - #[clap(help = "the env var that holds remappings", long = "remappings-env")] - #[serde(skip)] - pub remappings_env: Option, - - #[clap( - help = "the path where cached compiled contracts are stored", - long = "cache-path", - value_hint = ValueHint::DirPath - )] - #[serde(skip_serializing_if = "Option::is_none")] - pub cache_path: Option, - - #[clap( - help = "the paths where your libraries are installed", - long, - value_hint = ValueHint::DirPath - )] - #[serde(rename = "libs", skip_serializing_if = "Vec::is_empty")] - pub lib_paths: Vec, - - #[clap( - help = "path to where the contract artifacts are stored", - long = "out", - short, - value_hint = ValueHint::DirPath - )] - #[serde(rename = "out", skip_serializing_if = "Option::is_none")] - pub out_path: Option, + #[clap(flatten)] + #[serde(flatten)] + pub project_paths: ProjectPathsArgs, #[clap(flatten)] #[serde(flatten)] @@ -180,15 +133,6 @@ pub struct BuildArgs { #[serde(skip)] pub force: bool, - #[clap( - help = "uses hardhat style project layout. This a convenience flag and is the same as `--contracts contracts --lib-paths node_modules`", - long, - conflicts_with = "contracts", - alias = "hh" - )] - #[serde(skip)] - pub hardhat: bool, - #[clap(help = "add linked libraries", long, env = "DAPP_LIBRARIES")] #[serde(skip_serializing_if = "Vec::is_empty")] pub libraries: Vec, @@ -232,13 +176,6 @@ impl BuildArgs { Ok(config.project()?) } - /// Returns the root directory to use for configuring the [Project] - /// - /// This will be the `--root` argument if provided, otherwise see [find_project_root_path()] - fn project_root(&self) -> PathBuf { - self.root.clone().unwrap_or_else(|| find_project_root_path().unwrap()) - } - /// Returns whether `BuildArgs` was configured with `--watch` pub fn is_watch(&self) -> bool { self.watch.watch.is_some() @@ -252,14 +189,9 @@ impl BuildArgs { } /// Returns the remappings to add to the config + #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")] pub fn get_remappings(&self) -> Vec { - let mut remappings = self.remappings.clone(); - if let Some(env_remappings) = - self.remappings_env.as_ref().and_then(|env| remappings_from_env_var(env)) - { - remappings.extend(env_remappings.expect("Failed to parse env var remappings")); - } - remappings + self.project_paths.get_remappings() } } @@ -274,17 +206,6 @@ impl Provider for BuildArgs { let error = InvalidType(value.to_actual(), "map".into()); let mut dict = value.into_dict().ok_or(error)?; - let mut libs = - self.lib_paths.iter().map(|p| format!("{}", p.display())).collect::>(); - if self.hardhat { - dict.insert("src".to_string(), "contracts".to_string().into()); - libs.push("node_modules".to_string()); - } - - if !libs.is_empty() { - dict.insert("libs".to_string(), libs.into()); - } - if self.no_auto_detect { dict.insert("auto_detect_solc".to_string(), false.into()); } @@ -330,3 +251,117 @@ impl Provider for BuildArgs { Ok(Map::from([(Config::selected_profile(), dict)])) } } + +#[derive(Debug, Clone, Parser, Serialize)] +pub struct ProjectPathsArgs { + #[clap( + help = "The project's root path.", + long_help = "The project's root path. By default, this is the root directory of the current Git repository, or the current working directory.", + long, + value_hint = ValueHint::DirPath + )] + #[serde(skip)] + pub root: Option, + + #[clap( + env = "DAPP_SRC", + help = "The contracts source directory.", + long, + short, + value_hint = ValueHint::DirPath + )] + #[serde(rename = "src", skip_serializing_if = "Option::is_none")] + pub contracts: Option, + + #[clap(help = "The project's remappings.", long, short)] + #[serde(skip)] + pub remappings: Vec, + + #[clap(help = "The project's remappings from the environment.", long = "remappings-env")] + #[serde(skip)] + pub remappings_env: Option, + + #[clap( + help = "The path to the compiler cache.", + long = "cache-path", + value_hint = ValueHint::DirPath + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub cache_path: Option, + + #[clap( + help = "The path to the library folder.", + long, + value_hint = ValueHint::DirPath + )] + #[serde(rename = "libs", skip_serializing_if = "Vec::is_empty")] + pub lib_paths: Vec, + + #[clap( + help = "The path to the contract artifacts folder.", + long = "out", + short, + value_hint = ValueHint::DirPath + )] + #[serde(rename = "out", skip_serializing_if = "Option::is_none")] + pub out_path: Option, + + #[clap( + help = "Use the Hardhat-style project layout.", + long_help = "This a convenience flag and is the same as passing `--contracts contracts --lib-paths node_modules`.", + long, + conflicts_with = "contracts", + alias = "hh" + )] + #[serde(skip)] + pub hardhat: bool, +} + +impl ProjectPathsArgs { + /// Returns the root directory to use for configuring the [Project] + /// + /// This will be the `--root` argument if provided, otherwise see [find_project_root_path()] + fn project_root(&self) -> PathBuf { + self.root.clone().unwrap_or_else(|| find_project_root_path().unwrap()) + } + + /// Returns the remappings to add to the config + pub fn get_remappings(&self) -> Vec { + let mut remappings = self.remappings.clone(); + if let Some(env_remappings) = + self.remappings_env.as_ref().and_then(|env| remappings_from_env_var(env)) + { + remappings.extend(env_remappings.expect("Failed to parse env var remappings")); + } + remappings + } +} + +// Make this args a `figment::Provider` so that it can be merged into the `Config` +impl Provider for ProjectPathsArgs { + fn metadata(&self) -> Metadata { + Metadata::named("Project Paths Args Provider") + } + + fn data(&self) -> Result, figment::Error> { + let value = Value::serialize(self)?; + let error = InvalidType(value.to_actual(), "map".into()); + let mut dict = value.into_dict().ok_or(error)?; + + let mut libs = + self.lib_paths.iter().map(|p| format!("{}", p.display())).collect::>(); + + if self.hardhat { + dict.insert("src".to_string(), "contracts".to_string().into()); + libs.push("node_modules".to_string()); + } + + if !libs.is_empty() { + dict.insert("libs".to_string(), libs.into()); + } + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +impl_figment_convert!(ProjectPathsArgs); diff --git a/cli/src/cmd/forge/flatten.rs b/cli/src/cmd/forge/flatten.rs index f516f8c19604f..3e1ac1988c12e 100644 --- a/cli/src/cmd/forge/flatten.rs +++ b/cli/src/cmd/forge/flatten.rs @@ -1,59 +1,10 @@ use std::path::PathBuf; -use ethers::solc::remappings::Remapping; - use crate::cmd::{forge::build::BuildArgs, Cmd}; use clap::{Parser, ValueHint}; use foundry_config::Config; -#[derive(Debug, Clone, Parser)] -pub struct CoreFlattenArgs { - #[clap( - help = "The project's root path.", - long_help = "The project's root path. By default, this is the root directory of the current Git repository, or the current working directory.", - long, - value_hint = ValueHint::DirPath - )] - pub root: Option, - - #[clap( - env = "DAPP_SRC", - help = "The contract's source directory, relative to the project root.", - long, - short, - value_hint = ValueHint::DirPath - )] - pub contracts: Option, - - #[clap(help = "The project's remappings.", long, short)] - pub remappings: Vec, - - #[clap(long = "remappings-env", env = "DAPP_REMAPPINGS")] - pub remappings_env: Option, - - #[clap( - help = "The path to the compiler cache.", - long = "cache-path", - value_hint = ValueHint::DirPath - )] - pub cache_path: Option, - - #[clap( - help = "The path to the library folder.", - long, - value_hint = ValueHint::DirPath - )] - pub lib_paths: Vec, - - #[clap( - help = "Use the Hardhat-style project layout.", - long_help = "Use the Hardhat-style project layout.", - long, - conflicts_with = "contracts", - alias = "hh" - )] - pub hardhat: bool, -} +use super::build::ProjectPathsArgs; #[derive(Debug, Clone, Parser)] pub struct FlattenArgs { @@ -70,23 +21,17 @@ pub struct FlattenArgs { pub output: Option, #[clap(flatten)] - core_flatten_args: CoreFlattenArgs, + project_paths: ProjectPathsArgs, } impl Cmd for FlattenArgs { type Output = (); fn run(self) -> eyre::Result { - let FlattenArgs { target_path, output, core_flatten_args } = self; + let FlattenArgs { target_path, output, project_paths } = self; // flatten is a subset of `BuildArgs` so we can reuse that to get the config let build_args = BuildArgs { - root: core_flatten_args.root, - contracts: core_flatten_args.contracts, - remappings: core_flatten_args.remappings, - remappings_env: core_flatten_args.remappings_env, - cache_path: core_flatten_args.cache_path, - lib_paths: core_flatten_args.lib_paths, - out_path: None, + project_paths, compiler: Default::default(), names: false, sizes: false, @@ -95,7 +40,6 @@ impl Cmd for FlattenArgs { use_solc: None, offline: false, force: false, - hardhat: core_flatten_args.hardhat, libraries: vec![], watch: Default::default(), via_ir: false, diff --git a/cli/src/cmd/forge/tree.rs b/cli/src/cmd/forge/tree.rs index 2e7b4b540c308..f23e0a0fdcfe9 100644 --- a/cli/src/cmd/forge/tree.rs +++ b/cli/src/cmd/forge/tree.rs @@ -1,6 +1,6 @@ //! tree command -use crate::cmd::{forge::build::BuildArgs, Cmd}; +use crate::cmd::{forge::build::ProjectPathsArgs, Cmd}; use clap::Parser; use ethers::solc::Graph; use foundry_config::Config; @@ -11,9 +11,8 @@ use ethers::solc::resolver::{Charset, TreeOptions}; /// Command to display the project's dependency tree #[derive(Debug, Clone, Parser)] pub struct TreeArgs { - // TODO extract path related args from BuildArgs #[clap(flatten)] - opts: BuildArgs, + opts: ProjectPathsArgs, #[clap(help = "Do not de-duplicate (repeats all shared dependencies)", long)] no_dedupe: bool, #[clap(help = "Character set to use in output: utf8, ascii", default_value = "utf8", long)] diff --git a/cli/src/cmd/forge/verify.rs b/cli/src/cmd/forge/verify.rs index b584ecbaf3757..50a781c825190 100644 --- a/cli/src/cmd/forge/verify.rs +++ b/cli/src/cmd/forge/verify.rs @@ -1,9 +1,6 @@ //! Verify contract source on etherscan -use crate::{ - cmd::forge::{build::BuildArgs, flatten::CoreFlattenArgs}, - opts::forge::ContractInfo, -}; +use crate::{cmd::forge::build::BuildArgs, opts::forge::ContractInfo}; use clap::Parser; use ethers::{ abi::Address, @@ -22,6 +19,8 @@ use semver::Version; use std::{collections::BTreeMap, path::Path}; use tracing::{trace, warn}; +use super::build::ProjectPathsArgs; + /// Verification arguments #[derive(Debug, Clone, Parser)] pub struct VerifyArgs { @@ -59,7 +58,7 @@ pub struct VerifyArgs { flatten: bool, #[clap(flatten)] - opts: CoreFlattenArgs, + project_paths: ProjectPathsArgs, #[clap( short, @@ -126,24 +125,8 @@ impl VerifyArgs { /// If `--flatten` is set to `true` then this will send with [`CodeFormat::SingleFile`] /// otherwise this will use the [`CodeFormat::StandardJsonInput`] fn create_verify_request(&self) -> eyre::Result { - let CoreFlattenArgs { - root, - contracts, - remappings, - remappings_env, - cache_path, - lib_paths, - hardhat, - } = self.opts.clone(); - let build_args = BuildArgs { - root, - contracts, - remappings, - remappings_env, - cache_path, - lib_paths, - out_path: None, + project_paths: self.project_paths.clone(), compiler: Default::default(), names: false, sizes: false, @@ -152,7 +135,6 @@ impl VerifyArgs { use_solc: None, offline: false, force: false, - hardhat, libraries: vec![], watch: Default::default(), via_ir: false, From e032b3cef33697a9ad52a068ac7c6c31c36ba120 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 13 Apr 2022 20:45:05 +0200 Subject: [PATCH 20/30] style: various adjustments --- cli/src/cmd/forge/build.rs | 42 ++++++++++++++++++------------------ cli/src/cmd/forge/flatten.rs | 3 ++- cli/src/cmd/forge/tree.rs | 4 ++-- cli/src/cmd/forge/verify.rs | 31 +++++++++++++------------- cli/src/opts/forge.rs | 4 ++-- 5 files changed, 43 insertions(+), 41 deletions(-) diff --git a/cli/src/cmd/forge/build.rs b/cli/src/cmd/forge/build.rs index 98d69c07b5579..789815e392584 100644 --- a/cli/src/cmd/forge/build.rs +++ b/cli/src/cmd/forge/build.rs @@ -83,14 +83,6 @@ impl<'a> From<&'a BuildArgs> for Config { // `figment::Provider` implementation #[derive(Debug, Clone, Parser, Serialize)] pub struct BuildArgs { - #[clap(flatten)] - #[serde(flatten)] - pub project_paths: ProjectPathsArgs, - - #[clap(flatten)] - #[serde(flatten)] - pub compiler: CompilerArgs, - #[clap(help = "print compiled contract names", long = "names")] #[serde(skip)] pub names: bool, @@ -137,10 +129,6 @@ pub struct BuildArgs { #[serde(skip_serializing_if = "Vec::is_empty")] pub libraries: Vec, - #[clap(flatten)] - #[serde(skip)] - pub watch: WatchArgs, - #[clap( help = "if set to true, changes compilation pipeline to go through the Yul intermediate representation.", long @@ -155,6 +143,27 @@ pub struct BuildArgs { )] #[serde(skip)] pub config_path: Option, + + #[clap(flatten, next_help_heading = "COMPILER OPTIONS")] + #[serde(flatten)] + pub compiler: CompilerArgs, + + #[clap(flatten, next_help_heading = "PROJECT OPTIONS")] + #[serde(flatten)] + pub project_paths: ProjectPathsArgs, + + #[clap( + help = "The path to the contract artifacts folder.", + long = "out", + short, + value_hint = ValueHint::DirPath + )] + #[serde(rename = "out", skip_serializing_if = "Option::is_none")] + pub out_path: Option, + + #[clap(flatten, next_help_heading = "WATCH OPTIONS")] + #[serde(skip)] + pub watch: WatchArgs, } impl Cmd for BuildArgs { @@ -297,15 +306,6 @@ pub struct ProjectPathsArgs { #[serde(rename = "libs", skip_serializing_if = "Vec::is_empty")] pub lib_paths: Vec, - #[clap( - help = "The path to the contract artifacts folder.", - long = "out", - short, - value_hint = ValueHint::DirPath - )] - #[serde(rename = "out", skip_serializing_if = "Option::is_none")] - pub out_path: Option, - #[clap( help = "Use the Hardhat-style project layout.", long_help = "This a convenience flag and is the same as passing `--contracts contracts --lib-paths node_modules`.", diff --git a/cli/src/cmd/forge/flatten.rs b/cli/src/cmd/forge/flatten.rs index 3e1ac1988c12e..2669c5afe2037 100644 --- a/cli/src/cmd/forge/flatten.rs +++ b/cli/src/cmd/forge/flatten.rs @@ -20,7 +20,7 @@ pub struct FlattenArgs { )] pub output: Option, - #[clap(flatten)] + #[clap(flatten, next_help_heading = "PROJECT OPTIONS")] project_paths: ProjectPathsArgs, } @@ -32,6 +32,7 @@ impl Cmd for FlattenArgs { // flatten is a subset of `BuildArgs` so we can reuse that to get the config let build_args = BuildArgs { project_paths, + out_path: Default::default(), compiler: Default::default(), names: false, sizes: false, diff --git a/cli/src/cmd/forge/tree.rs b/cli/src/cmd/forge/tree.rs index f23e0a0fdcfe9..1608f831ef57b 100644 --- a/cli/src/cmd/forge/tree.rs +++ b/cli/src/cmd/forge/tree.rs @@ -11,12 +11,12 @@ use ethers::solc::resolver::{Charset, TreeOptions}; /// Command to display the project's dependency tree #[derive(Debug, Clone, Parser)] pub struct TreeArgs { - #[clap(flatten)] - opts: ProjectPathsArgs, #[clap(help = "Do not de-duplicate (repeats all shared dependencies)", long)] no_dedupe: bool, #[clap(help = "Character set to use in output: utf8, ascii", default_value = "utf8", long)] charset: Charset, + #[clap(flatten, next_help_heading = "PROJECT OPTIONS")] + opts: ProjectPathsArgs, } impl Cmd for TreeArgs { diff --git a/cli/src/cmd/forge/verify.rs b/cli/src/cmd/forge/verify.rs index 50a781c825190..1afcc11186cd4 100644 --- a/cli/src/cmd/forge/verify.rs +++ b/cli/src/cmd/forge/verify.rs @@ -24,19 +24,23 @@ use super::build::ProjectPathsArgs; /// Verification arguments #[derive(Debug, Clone, Parser)] pub struct VerifyArgs { - #[clap(help = "the target contract address")] + #[clap(help = "The address of the contract to verify.")] address: Address, - #[clap(help = "the contract source info `:`")] + #[clap(help = "The contract identifier in the form `:`.")] contract: ContractInfo, #[clap(long, help = "the encoded constructor arguments")] constructor_args: Option, - #[clap(long, help = "the compiler version used during build")] + #[clap(long, help = "The compiler version used to build the smart contract.")] compiler_version: String, - #[clap(alias = "optimizer-runs", long, help = "the number of optimization runs used")] + #[clap( + alias = "optimizer-runs", + long, + help = "The number of optimization runs used to build the smart contract." + )] num_of_optimizations: Option, #[clap( @@ -48,25 +52,21 @@ pub struct VerifyArgs { )] chain: Chain, - #[clap(help = "your etherscan api key", env = "ETHERSCAN_API_KEY")] + #[clap(help = "Your Etherscan API key.", env = "ETHERSCAN_API_KEY")] etherscan_key: String, - #[clap( - help = "flatten the source code. Make sure to use bytecodehash='ipfs'", - long = "flatten" - )] + #[clap(help = "Flatten the source code before verifying.", long = "flatten")] flatten: bool, - #[clap(flatten)] - project_paths: ProjectPathsArgs, - #[clap( short, long, - help = r#"usually the command will try to compile the flattened code locally first to ensure it's valid. -This flag we skip that process and send the content directly to the endpoint."# + help = "Do not compile the flattened smart contract before verifying (if --flatten is passed)." )] force: bool, + + #[clap(flatten, next_help_heading = "PROJECT OPTIONS")] + project_paths: ProjectPathsArgs, } impl VerifyArgs { @@ -127,6 +127,7 @@ impl VerifyArgs { fn create_verify_request(&self) -> eyre::Result { let build_args = BuildArgs { project_paths: self.project_paths.clone(), + out_path: Default::default(), compiler: Default::default(), names: false, sizes: false, @@ -227,7 +228,7 @@ impl VerifyArgs { eprintln!( r#"Failed to compile the flattened code locally. This could be a bug, please inspect the outout of `forge flatten {}` and report an issue. -To skip this solc dry, have a look at the `--force` flag of this command. +To skip this solc dry, pass `--force`. "#, self.contract.path.as_ref().expect("Path is some;") ); diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 6c0eed0d00707..025123c77f617 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -81,13 +81,13 @@ pub enum Subcommands { #[clap( about = "Verify smart contracts on Etherscan.", - long_about = "Verify smart contracts on Etherscan. Requires `ETHERSCAN_API_KEY` to be set." + long_about = "Verify smart contracts on Etherscan." )] VerifyContract(VerifyArgs), #[clap( about = "Check verification status on Etherscan.", - long_about = "Check verification status on Etherscan. Requires `ETHERSCAN_API_KEY` to be set." + long_about = "Check verification status on Etherscan." )] VerifyCheck(VerifyCheckArgs), From 1bb36d2ef2aaf9cd44649cf7c57ab46c04624a4a Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 13 Apr 2022 20:53:10 +0200 Subject: [PATCH 21/30] chore: add link to book at end of `--help` --- cli/src/opts/cast.rs | 5 ++++- cli/src/opts/forge.rs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 589249d34d428..f12138029fe3d 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -11,7 +11,10 @@ use ethers::{ use std::{path::PathBuf, str::FromStr}; #[derive(Debug, Subcommand)] -#[clap(about = "Perform Ethereum RPC calls from the comfort of your command line.")] +#[clap( + about = "Perform Ethereum RPC calls from the comfort of your command line.", + after_help = "Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html" +)] pub enum Subcommands { #[clap(name = "--max-int")] #[clap(about = "Get the maximum i256 value.")] diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 025123c77f617..2c4b1b6536f23 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -33,7 +33,10 @@ pub struct Opts { } #[derive(Debug, Subcommand)] -#[clap(about = "Build, test, fuzz, debug and deploy Solidity contracts.")] +#[clap( + about = "Build, test, fuzz, debug and deploy Solidity contracts.", + after_help = "Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html" +)] #[allow(clippy::large_enum_variant)] pub enum Subcommands { #[clap(about = "Run the project's tests.")] From 4ba95840deab58b2c63fc0f117326b21ff4cb3c4 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 13 Apr 2022 21:10:20 +0200 Subject: [PATCH 22/30] style: more verify improvements --- cli/src/cmd/forge/verify.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/src/cmd/forge/verify.rs b/cli/src/cmd/forge/verify.rs index 1afcc11186cd4..1fbe4a8af843b 100644 --- a/cli/src/cmd/forge/verify.rs +++ b/cli/src/cmd/forge/verify.rs @@ -47,7 +47,7 @@ pub struct VerifyArgs { long, alias = "chain-id", env = "CHAIN", - help = "the name or id of chain of the network you are verifying for", + help = "The chain ID the contract is deployed to.", default_value = "mainnet" )] chain: Chain, @@ -242,19 +242,19 @@ To skip this solc dry, pass `--force`. /// Check verification status arguments #[derive(Debug, Clone, Parser)] pub struct VerifyCheckArgs { - #[clap(help = "the verification guid")] + #[clap(help = "The verification GUID.")] guid: String, #[clap( long, alias = "chain-id", env = "CHAIN", - help = "the name or id of chain of the network you are verifying for", + help = "The chain ID the contract is deployed to.", default_value = "mainnet" )] chain: Chain, - #[clap(help = "your etherscan api key", env = "ETHERSCAN_API_KEY")] + #[clap(help = "Your Etherscan API key.", env = "ETHERSCAN_API_KEY")] etherscan_key: String, } From 94f3c9862c49f82b70b7c48d668339aaeddff57a Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Wed, 13 Apr 2022 21:30:58 +0200 Subject: [PATCH 23/30] style: minor adjustments to `forge bind` --- cli/src/cmd/forge/bind.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/cmd/forge/bind.rs b/cli/src/cmd/forge/bind.rs index 4956d46025d3a..b5df2001023bb 100644 --- a/cli/src/cmd/forge/bind.rs +++ b/cli/src/cmd/forge/bind.rs @@ -40,7 +40,7 @@ pub struct BindArgs { #[clap( long = "crate-name", - help = "The name of the rust crate to generate. This should be a valid crates.io crate name. However, it is not currently validated by this command.", + help = "The name of the Rust crate to generate. This should be a valid crates.io crate name. However, it is not currently validated by this command.", default_value = DEFAULT_CRATE_NAME, )] #[serde(skip)] @@ -48,7 +48,7 @@ pub struct BindArgs { #[clap( long = "crate-version", - help = "The version of the rust crate to generate. This should be a standard semver version string. However, it is not currently validated by this command.", + help = "The version of the Rust crate to generate. This should be a standard semver version string. However, it is not currently validated by this command.", default_value = DEFAULT_CRATE_VERSION, )] #[serde(skip)] @@ -59,7 +59,7 @@ pub struct BindArgs { #[clap( long = "overwrite", - help = "Overwrite existing generated bindings. If set to false, the command will check that the bindings are correct, and then exit. If set to true, it will instead delete and overwrite the bindings." + help = "Overwrite existing generated bindings. By default, the command will check that the bindings are correct, and then exit. If --overwrite is passed, it will instead delete and overwrite the bindings." )] #[serde(skip)] overwrite: bool, From 6f76cb6d2b6623d215954d4eb40e086a92c610ee Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 14 Apr 2022 09:20:50 +0200 Subject: [PATCH 24/30] refactor: split `BuildArgs` into smaller pieces --- cli/src/cmd/forge/build.rs | 222 ++++++++++++++++++++-------------- cli/src/cmd/forge/create.rs | 18 ++- cli/src/cmd/forge/flatten.rs | 13 +- cli/src/cmd/forge/inspect.rs | 9 +- cli/src/cmd/forge/run.rs | 4 +- cli/src/cmd/forge/snapshot.rs | 13 +- cli/src/cmd/forge/test.rs | 23 +++- cli/src/cmd/forge/verify.rs | 10 +- cli/src/cmd/forge/watch.rs | 40 +++--- cli/src/forge.rs | 2 +- cli/src/opts/forge.rs | 25 ++-- 11 files changed, 203 insertions(+), 176 deletions(-) diff --git a/cli/src/cmd/forge/build.rs b/cli/src/cmd/forge/build.rs index 789815e392584..ae58931e89459 100644 --- a/cli/src/cmd/forge/build.rs +++ b/cli/src/cmd/forge/build.rs @@ -23,8 +23,8 @@ use serde::Serialize; use watchexec::config::{InitConfig, RuntimeConfig}; // Loads project's figment and merges the build cli arguments into it -impl<'a> From<&'a BuildArgs> for Figment { - fn from(args: &'a BuildArgs) -> Self { +impl<'a> From<&'a CoreBuildArgs> for Figment { + fn from(args: &'a CoreBuildArgs) -> Self { let figment = if let Some(ref config_path) = args.config_path { if !config_path.exists() { panic!("error: config-path `{}` does not exist", config_path.display()) @@ -48,8 +48,8 @@ impl<'a> From<&'a BuildArgs> for Figment { } } -impl<'a> From<&'a BuildArgs> for Config { - fn from(args: &'a BuildArgs) -> Self { +impl<'a> From<&'a CoreBuildArgs> for Config { + fn from(args: &'a CoreBuildArgs) -> Self { let figment: Figment = args.into(); let mut config = Config::from_provider(figment).sanitized(); // if `--config-path` is set we need to adjust the config's root path to the actual root @@ -61,48 +61,30 @@ impl<'a> From<&'a BuildArgs> for Config { } } -// All `forge build` related arguments -// -// CLI arguments take the highest precedence in the Config/Figment hierarchy. -// In order to override them in the foundry `Config` they need to be merged into an existing -// `figment::Provider`, like `foundry_config::Config` is. -// -// # Example -// -// ```ignore -// use foundry_config::Config; -// # fn t(args: BuildArgs) { -// let config = Config::from(&args); -// # } -// ``` -// -// `BuildArgs` implements `figment::Provider` in which all config related fields are serialized and -// then merged into an existing `Config`, effectively overwriting them. -// -// Some arguments are marked as `#[serde(skip)]` and require manual processing in -// `figment::Provider` implementation #[derive(Debug, Clone, Parser, Serialize)] -pub struct BuildArgs { - #[clap(help = "print compiled contract names", long = "names")] +pub struct CoreBuildArgs { + #[clap(help = "Clear the cache and artifacts folder and recompile.", long)] #[serde(skip)] - pub names: bool, + pub force: bool, - #[clap(help = "print compiled contract sizes", long = "sizes")] - #[serde(skip)] - pub sizes: bool, + #[clap(help = "Set pre-linked libraries.", long, env = "DAPP_LIBRARIES")] + #[serde(skip_serializing_if = "Vec::is_empty")] + pub libraries: Vec, + + #[clap(flatten, next_help_heading = "COMPILER OPTIONS")] + #[serde(flatten)] + pub compiler: CompilerArgs, - #[clap(help = "ignore warnings with specific error codes", long)] + #[clap(help_heading = "COMPILER OPTIONS", help = "Ignore solc warnings by error code.", long)] #[serde(skip_serializing_if = "Vec::is_empty")] pub ignored_error_codes: Vec, - #[clap( - help = "if set to true, skips auto-detecting solc and uses what is in the user's $PATH ", - long - )] + #[clap(help_heading = "COMPILER OPTIONS", help = "Do not auto-detect solc.", long)] #[serde(skip)] pub no_auto_detect: bool, #[clap( + help_heading = "COMPILER OPTIONS", help = "specify the solc version or path to a local solc to run with.\ This accepts values of the form `x.y.z`, `solc:x.y.z` or `path/to/existing/solc`", value_name = "use", @@ -112,47 +94,28 @@ pub struct BuildArgs { pub use_solc: Option, #[clap( - help = "if set to true, runs without accessing the network (missing solc versions will not be installed)", + help_heading = "COMPILER OPTIONS", + help = "Do not access the network.", + long_help = "Do not access the network. Missing solc versions will not be installed.", long )] #[serde(skip)] pub offline: bool, #[clap( - help = "force recompilation of the project, deletes the cache and artifacts folders", - long - )] - #[serde(skip)] - pub force: bool, - - #[clap(help = "add linked libraries", long, env = "DAPP_LIBRARIES")] - #[serde(skip_serializing_if = "Vec::is_empty")] - pub libraries: Vec, - - #[clap( - help = "if set to true, changes compilation pipeline to go through the Yul intermediate representation.", + help_heading = "COMPILER OPTIONS", + help = "Use the Yul intermediate representation compilation pipeline.", long )] #[serde(skip)] pub via_ir: bool, - #[clap( - help = "path to the foundry.toml", - long = "config-path", - value_hint = ValueHint::FilePath - )] - #[serde(skip)] - pub config_path: Option, - - #[clap(flatten, next_help_heading = "COMPILER OPTIONS")] - #[serde(flatten)] - pub compiler: CompilerArgs, - #[clap(flatten, next_help_heading = "PROJECT OPTIONS")] #[serde(flatten)] pub project_paths: ProjectPathsArgs, #[clap( + help_heading = "PROJECT OPTIONS", help = "The path to the contract artifacts folder.", long = "out", short, @@ -161,20 +124,17 @@ pub struct BuildArgs { #[serde(rename = "out", skip_serializing_if = "Option::is_none")] pub out_path: Option, - #[clap(flatten, next_help_heading = "WATCH OPTIONS")] + #[clap( + help_heading = "PROJECT OPTIONS", + help = "Path to the config file.", + long = "config-path", + value_hint = ValueHint::FilePath + )] #[serde(skip)] - pub watch: WatchArgs, -} - -impl Cmd for BuildArgs { - type Output = ProjectCompileOutput; - fn run(self) -> eyre::Result { - let project = self.project()?; - compile::compile(&project, self.names, self.sizes) - } + pub config_path: Option, } -impl BuildArgs { +impl CoreBuildArgs { /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see @@ -185,18 +145,6 @@ impl BuildArgs { Ok(config.project()?) } - /// Returns whether `BuildArgs` was configured with `--watch` - pub fn is_watch(&self) -> bool { - self.watch.watch.is_some() - } - - /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to - /// bootstrap a new [`watchexe::Watchexec`] loop. - pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> { - // use the path arguments or if none where provided the `src` dir - self.watch.watchexec_config(|| Config::from(self).src) - } - /// Returns the remappings to add to the config #[deprecated(note = "Use ProjectPathsArgs::get_remappings() instead")] pub fn get_remappings(&self) -> Vec { @@ -204,10 +152,9 @@ impl BuildArgs { } } -// Make this args a `figment::Provider` so that it can be merged into the `Config` -impl Provider for BuildArgs { +impl Provider for CoreBuildArgs { fn metadata(&self) -> Metadata { - Metadata::named("Build Args Provider") + Metadata::named("Core Build Args Provider") } fn data(&self) -> Result, figment::Error> { @@ -231,14 +178,6 @@ impl Provider for BuildArgs { dict.insert("via_ir".to_string(), true.into()); } - if self.names { - dict.insert("names".to_string(), true.into()); - } - - if self.sizes { - dict.insert("sizes".to_string(), true.into()); - } - if self.force { dict.insert("force".to_string(), self.force.into()); } @@ -261,6 +200,101 @@ impl Provider for BuildArgs { } } +// All `forge build` related arguments +// +// CLI arguments take the highest precedence in the Config/Figment hierarchy. +// In order to override them in the foundry `Config` they need to be merged into an existing +// `figment::Provider`, like `foundry_config::Config` is. +// +// # Example +// +// ```ignore +// use foundry_config::Config; +// # fn t(args: BuildArgs) { +// let config = Config::from(&args); +// # } +// ``` +// +// `BuildArgs` implements `figment::Provider` in which all config related fields are serialized and +// then merged into an existing `Config`, effectively overwriting them. +// +// Some arguments are marked as `#[serde(skip)]` and require manual processing in +// `figment::Provider` implementation +#[derive(Debug, Clone, Parser, Serialize)] +pub struct BuildArgs { + #[clap(flatten)] + #[serde(flatten)] + pub args: CoreBuildArgs, + + #[clap(help = "Print compiled contract names.", long = "names")] + #[serde(skip)] + pub names: bool, + + #[clap(help = "Print compiled contract sizes.", long = "sizes")] + #[serde(skip)] + pub sizes: bool, + + #[clap(flatten, next_help_heading = "WATCH OPTIONS")] + #[serde(skip)] + pub watch: WatchArgs, +} + +impl Cmd for BuildArgs { + type Output = ProjectCompileOutput; + fn run(self) -> eyre::Result { + let project = self.project()?; + compile::compile(&project, self.names, self.sizes) + } +} + +impl BuildArgs { + /// Returns the `Project` for the current workspace + /// + /// This loads the `foundry_config::Config` for the current workspace (see + /// [`utils::find_project_root_path`] and merges the cli `BuildArgs` into it before returning + /// [`foundry_config::Config::project()`] + pub fn project(&self) -> eyre::Result { + self.args.project() + } + + /// Returns whether `BuildArgs` was configured with `--watch` + pub fn is_watch(&self) -> bool { + self.watch.watch.is_some() + } + + /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to + /// bootstrap a new [`watchexe::Watchexec`] loop. + pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> { + // use the path arguments or if none where provided the `src` dir + self.watch.watchexec_config(|| Config::from(self).src) + } +} + +// Make this args a `figment::Provider` so that it can be merged into the `Config` +impl Provider for BuildArgs { + fn metadata(&self) -> Metadata { + Metadata::named("Build Args Provider") + } + + fn data(&self) -> Result, figment::Error> { + let value = Value::serialize(self)?; + let error = InvalidType(value.to_actual(), "map".into()); + let mut dict = value.into_dict().ok_or(error)?; + + if self.names { + dict.insert("names".to_string(), true.into()); + } + + if self.sizes { + dict.insert("sizes".to_string(), true.into()); + } + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +impl_figment_convert!(BuildArgs, args); + #[derive(Debug, Clone, Parser, Serialize)] pub struct ProjectPathsArgs { #[clap( diff --git a/cli/src/cmd/forge/create.rs b/cli/src/cmd/forge/create.rs index 23b5801f16ea4..a097aa6f05355 100644 --- a/cli/src/cmd/forge/create.rs +++ b/cli/src/cmd/forge/create.rs @@ -1,24 +1,20 @@ //! Create command - use crate::{ - cmd::{forge::build::BuildArgs, Cmd}, - opts::{EthereumOpts, WalletType}, + cmd::{forge::build::CoreBuildArgs, Cmd}, + compile, + opts::{forge::ContractInfo, EthereumOpts, WalletType}, utils::parse_u256, }; +use clap::{Parser, ValueHint}; use ethers::{ abi::{Abi, Constructor, Token}, prelude::{artifacts::BytecodeObject, ContractFactory, Http, Middleware, Provider}, types::{transaction::eip2718::TypedTransaction, Chain, U256}, }; - use eyre::{Context, Result}; use foundry_utils::parse_tokens; -use std::fs; - -use crate::{compile, opts::forge::ContractInfo}; -use clap::{Parser, ValueHint}; use serde_json::json; -use std::{path::PathBuf, sync::Arc}; +use std::{fs, path::PathBuf, sync::Arc}; #[derive(Debug, Clone, Parser)] pub struct CreateArgs { @@ -41,7 +37,7 @@ pub struct CreateArgs { constructor_args_path: Option, #[clap(flatten)] - opts: BuildArgs, + opts: CoreBuildArgs, #[clap(flatten)] eth: EthereumOpts, @@ -81,7 +77,7 @@ impl Cmd for CreateArgs { // Supress compile stdout messages when printing json output compile::suppress_compile(&project)? } else { - compile::compile(&project, self.opts.names, self.opts.sizes)? + compile::compile(&project, false, false)? }; // Get ABI and BIN diff --git a/cli/src/cmd/forge/flatten.rs b/cli/src/cmd/forge/flatten.rs index 2669c5afe2037..8311968ec9ba4 100644 --- a/cli/src/cmd/forge/flatten.rs +++ b/cli/src/cmd/forge/flatten.rs @@ -1,10 +1,8 @@ -use std::path::PathBuf; - -use crate::cmd::{forge::build::BuildArgs, Cmd}; +use super::build::{CoreBuildArgs, ProjectPathsArgs}; +use crate::cmd::Cmd; use clap::{Parser, ValueHint}; use foundry_config::Config; - -use super::build::ProjectPathsArgs; +use std::path::PathBuf; #[derive(Debug, Clone, Parser)] pub struct FlattenArgs { @@ -30,19 +28,16 @@ impl Cmd for FlattenArgs { let FlattenArgs { target_path, output, project_paths } = self; // flatten is a subset of `BuildArgs` so we can reuse that to get the config - let build_args = BuildArgs { + let build_args = CoreBuildArgs { project_paths, out_path: Default::default(), compiler: Default::default(), - names: false, - sizes: false, ignored_error_codes: vec![], no_auto_detect: false, use_solc: None, offline: false, force: false, libraries: vec![], - watch: Default::default(), via_ir: false, config_path: None, }; diff --git a/cli/src/cmd/forge/inspect.rs b/cli/src/cmd/forge/inspect.rs index 80681d549e25a..bbac4949586f4 100644 --- a/cli/src/cmd/forge/inspect.rs +++ b/cli/src/cmd/forge/inspect.rs @@ -1,8 +1,6 @@ -use std::{fmt, str::FromStr}; - use crate::{ cmd::{ - forge::build::{self, BuildArgs}, + forge::build::{self, CoreBuildArgs}, Cmd, }, compile, @@ -13,6 +11,7 @@ use ethers::prelude::artifacts::output_selection::{ ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection, }; use serde_json::{to_value, Value}; +use std::{fmt, str::FromStr}; /// Contract level output selection #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -98,7 +97,7 @@ pub struct InspectArgs { /// All build arguments are supported #[clap(flatten)] - build: build::BuildArgs, + build: build::CoreBuildArgs, } impl Cmd for InspectArgs { @@ -146,7 +145,7 @@ impl Cmd for InspectArgs { }; // Build modified Args - let modified_build_args = BuildArgs { + let modified_build_args = CoreBuildArgs { compiler: CompilerArgs { extra_output: Some(cos), optimize: optimized, diff --git a/cli/src/cmd/forge/run.rs b/cli/src/cmd/forge/run.rs index d00b9e7b841d9..c5b6c23de2b8b 100644 --- a/cli/src/cmd/forge/run.rs +++ b/cli/src/cmd/forge/run.rs @@ -1,5 +1,5 @@ use crate::{ - cmd::{forge::build::BuildArgs, Cmd}, + cmd::{forge::build::CoreBuildArgs, Cmd}, compile, opts::evm::EvmArgs, utils, @@ -58,7 +58,7 @@ pub struct RunArgs { pub debug: bool, #[clap(flatten, next_help_heading = "BUILD OPTIONS")] - pub opts: BuildArgs, + pub opts: CoreBuildArgs, #[clap(flatten, next_help_heading = "EVM OPTIONS")] pub evm_opts: EvmArgs, diff --git a/cli/src/cmd/forge/snapshot.rs b/cli/src/cmd/forge/snapshot.rs index 92fedf651a18f..34cd476b96858 100644 --- a/cli/src/cmd/forge/snapshot.rs +++ b/cli/src/cmd/forge/snapshot.rs @@ -1,8 +1,7 @@ //! Snapshot command - use crate::cmd::{ forge::{ - build::BuildArgs, + build::CoreBuildArgs, test, test::{custom_run, Test, TestOutcome}, }, @@ -35,7 +34,7 @@ pub static RE_BASIC_SNAPSHOT_ENTRY: Lazy = Lazy::new(|| { pub struct SnapshotArgs { /// All test arguments are supported #[clap(flatten)] - test: test::TestArgs, + pub(crate) test: test::TestArgs, /// Additional configs for test results #[clap(flatten)] @@ -78,17 +77,17 @@ pub struct SnapshotArgs { impl SnapshotArgs { /// Returns whether `SnapshotArgs` was configured with `--watch` pub fn is_watch(&self) -> bool { - self.test.build_args().is_watch() + self.test.is_watch() } /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> { - self.test.build_args().watchexec_config() + self.test.watchexec_config() } - /// Returns the nested [`BuildArgs`] - pub fn build_args(&self) -> &BuildArgs { + /// Returns the nested [`CoreBuildArgs`] + pub fn build_args(&self) -> &CoreBuildArgs { self.test.build_args() } } diff --git a/cli/src/cmd/forge/test.rs b/cli/src/cmd/forge/test.rs index bac0dd6b81e95..ee5aaa06a7ea2 100644 --- a/cli/src/cmd/forge/test.rs +++ b/cli/src/cmd/forge/test.rs @@ -1,7 +1,7 @@ //! Test command use crate::{ cmd::{ - forge::{build::BuildArgs, run::RunArgs}, + forge::{build::CoreBuildArgs, run::RunArgs, watch::WatchArgs}, Cmd, }, compile::ProjectCompiler, @@ -31,6 +31,7 @@ use std::{ thread, time::Duration, }; +use watchexec::config::{InitConfig, RuntimeConfig}; #[derive(Debug, Clone, Parser)] pub struct Filter { @@ -173,12 +174,15 @@ pub struct TestArgs { evm_opts: EvmArgs, #[clap(flatten, next_help_heading = "BUILD OPTIONS")] - opts: BuildArgs, + opts: CoreBuildArgs, + + #[clap(flatten, next_help_heading = "WATCH OPTIONS")] + pub watch: WatchArgs, } impl TestArgs { - /// Returns the flattened [`BuildArgs`] - pub fn build_args(&self) -> &BuildArgs { + /// Returns the flattened [`CoreBuildArgs`] + pub fn build_args(&self) -> &CoreBuildArgs { &self.opts } @@ -195,6 +199,17 @@ impl TestArgs { let config = Config::from_provider(figment).sanitized(); Ok((config, evm_opts)) } + + /// Returns whether `BuildArgs` was configured with `--watch` + pub fn is_watch(&self) -> bool { + self.watch.watch.is_some() + } + + /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to + /// bootstrap a new [`watchexe::Watchexec`] loop. + pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> { + self.watch.watchexec_config(|| Config::from(self).src) + } } impl Cmd for TestArgs { diff --git a/cli/src/cmd/forge/verify.rs b/cli/src/cmd/forge/verify.rs index 1fbe4a8af843b..4b01217d82ca7 100644 --- a/cli/src/cmd/forge/verify.rs +++ b/cli/src/cmd/forge/verify.rs @@ -1,6 +1,7 @@ //! Verify contract source on etherscan -use crate::{cmd::forge::build::BuildArgs, opts::forge::ContractInfo}; +use super::build::{CoreBuildArgs, ProjectPathsArgs}; +use crate::opts::forge::ContractInfo; use clap::Parser; use ethers::{ abi::Address, @@ -19,8 +20,6 @@ use semver::Version; use std::{collections::BTreeMap, path::Path}; use tracing::{trace, warn}; -use super::build::ProjectPathsArgs; - /// Verification arguments #[derive(Debug, Clone, Parser)] pub struct VerifyArgs { @@ -125,19 +124,16 @@ impl VerifyArgs { /// If `--flatten` is set to `true` then this will send with [`CodeFormat::SingleFile`] /// otherwise this will use the [`CodeFormat::StandardJsonInput`] fn create_verify_request(&self) -> eyre::Result { - let build_args = BuildArgs { + let build_args = CoreBuildArgs { project_paths: self.project_paths.clone(), out_path: Default::default(), compiler: Default::default(), - names: false, - sizes: false, ignored_error_codes: vec![], no_auto_detect: false, use_solc: None, offline: false, force: false, libraries: vec![], - watch: Default::default(), via_ir: false, config_path: None, }; diff --git a/cli/src/cmd/forge/watch.rs b/cli/src/cmd/forge/watch.rs index 16e3a03d83aa7..0f9224173beab 100644 --- a/cli/src/cmd/forge/watch.rs +++ b/cli/src/cmd/forge/watch.rs @@ -22,9 +22,9 @@ use watchexec::{ #[derive(Debug, Clone, Parser, Default)] pub struct WatchArgs { - /// File updates debounce delay + /// File update debounce delay /// - /// During this time, incoming change events are accumulated and + /// During the delay, incoming change events are accumulated and /// only once the delay has passed, is an action taken. Note that /// this does not mean a command will be started: if --no-restart is /// given and a command is already running, the outcome of the @@ -32,28 +32,24 @@ pub struct WatchArgs { /// /// Defaults to 50ms. Parses as decimal seconds by default, but /// using an integer with the `ms` suffix may be more convenient. + /// /// When using --poll mode, you'll want a larger duration, or risk /// overloading disk I/O. - #[clap( - short = 'd', - long = "delay", - forbid_empty_values = true, - help = "File updates debounce delay. Only take action (detect changes) after this time period has passed." - )] + #[clap(short = 'd', long = "delay", forbid_empty_values = true)] pub delay: Option, - #[clap(long = "no-restart", help = "Don’t restart command while it’s still running.")] + #[clap(long = "no-restart", help = "Do not restart the command while it's still running.")] pub no_restart: bool, - #[clap( - long = "run-all", - help = "By default, only the tests of the last modified test file are executed. This explicitly runs all tests when a change is made." - )] + /// Explicitly re-run all tests when a change is made. + /// + /// By default, only the tests of the last modified test file are executed. + #[clap(long = "run-all")] pub run_all: bool, /// Watch specific file(s) or folder(s) /// - /// By default, the project's source dir is watched + /// By default, the project's source directory is watched. #[clap( short = 'w', long = "watch", @@ -112,15 +108,13 @@ pub async fn watch_build(args: BuildArgs) -> eyre::Result<()> { /// snapshot` pub async fn watch_snapshot(args: SnapshotArgs) -> eyre::Result<()> { let (init, mut runtime) = args.watchexec_config()?; - let cmd = cmd_args( - args.build_args().watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default(), - ); + let cmd = cmd_args(args.test.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); trace!("watch snapshot cmd={:?}", cmd); runtime.command(cmd.clone()); let wx = Watchexec::new(init, runtime.clone())?; - on_action(args.build_args().watch.clone(), runtime, Arc::clone(&wx), cmd, (), |_| {}); + on_action(args.test.watch.clone(), runtime, Arc::clone(&wx), cmd, (), |_| {}); // start executing the command immediately wx.send_event(Event::default()).await?; @@ -132,10 +126,8 @@ pub async fn watch_snapshot(args: SnapshotArgs) -> eyre::Result<()> { /// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge /// test` pub async fn watch_test(args: TestArgs) -> eyre::Result<()> { - let (init, mut runtime) = args.build_args().watchexec_config()?; - let cmd = cmd_args( - args.build_args().watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default(), - ); + let (init, mut runtime) = args.watchexec_config()?; + let cmd = cmd_args(args.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); trace!("watch test cmd={:?}", cmd); runtime.command(cmd.clone()); let wx = Watchexec::new(init, runtime.clone())?; @@ -145,7 +137,7 @@ pub async fn watch_test(args: TestArgs) -> eyre::Result<()> { args.filter().test_pattern.is_some() || args.filter().path_pattern.is_some() || args.filter().contract_pattern.is_some() || - args.build_args().watch.run_all; + args.watch.run_all; let config: Config = args.build_args().into(); let state = WatchTestState { @@ -153,7 +145,7 @@ pub async fn watch_test(args: TestArgs) -> eyre::Result<()> { no_reconfigure, last_test_files: Default::default(), }; - on_action(args.build_args().watch.clone(), runtime, Arc::clone(&wx), cmd, state, on_test); + on_action(args.watch.clone(), runtime, Arc::clone(&wx), cmd, state, on_test); // start executing the command immediately wx.send_event(Event::default()).await?; diff --git a/cli/src/forge.rs b/cli/src/forge.rs index 8c462c8aee818..0ef2df7301278 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -18,7 +18,7 @@ fn main() -> eyre::Result<()> { let opts = Opts::parse(); match opts.sub { Subcommands::Test(cmd) => { - if cmd.build_args().is_watch() { + if cmd.is_watch() { utils::block_on(watch::watch_test(cmd))?; } else { let outcome = cmd.run()?; diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 2c4b1b6536f23..436d9629c58b0 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -138,30 +138,31 @@ pub enum Subcommands { // See also [`BuildArgs`] #[derive(Default, Debug, Clone, Parser, Serialize)] pub struct CompilerArgs { - #[clap(help = "Choose the evm version", long)] + #[clap(help = "The target EVM version.", long)] #[serde(skip_serializing_if = "Option::is_none")] pub evm_version: Option, - #[clap(help = "Activate the solidity optimizer", long)] - // skipped because, optimize is opt-in + #[clap(help = "Activate the Solidity optimizer.", long)] #[serde(skip)] pub optimize: bool, - #[clap(help = "Optimizer parameter runs", long)] + #[clap(help = "The number of optimizer runs.", long)] #[serde(skip_serializing_if = "Option::is_none")] pub optimize_runs: Option, - #[clap( - help = "Extra output types to include in the contract's json artifact [evm.assembly, ewasm, ir, irOptimized, metadata] eg: `--extra-output evm.assembly`", - long - )] + /// Extra output to include in the contract's artifact. + /// + /// Example keys: evm.assembly, ewasm, ir, irOptimized, metadata + /// + /// For a full description, see https://docs.soliditylang.org/en/v0.8.13/using-the-compiler.html#input-description + #[clap(long)] #[serde(skip_serializing_if = "Option::is_none")] pub extra_output: Option>, - #[clap( - help = "Extra output types to write to a separate file [metadata, ir, irOptimized, ewasm] eg: `--extra-output-files metadata`", - long - )] + /// Extra output to write to separate files. + /// + /// Valid values: metadata, ir, irOptimized, ewasm, assembly + #[clap(long)] #[serde(skip_serializing_if = "Option::is_none")] pub extra_output_files: Option>, } From bb27e7dd4b6715a3cc0d7334f69f9ea456c6b664 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 14 Apr 2022 10:42:30 +0200 Subject: [PATCH 25/30] style: minor adjustments to `forge build` --- cli/src/cmd/forge/build.rs | 11 ++++------- cli/src/opts/forge.rs | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/cli/src/cmd/forge/build.rs b/cli/src/cmd/forge/build.rs index ae58931e89459..c759ebdb8781d 100644 --- a/cli/src/cmd/forge/build.rs +++ b/cli/src/cmd/forge/build.rs @@ -83,13 +83,10 @@ pub struct CoreBuildArgs { #[serde(skip)] pub no_auto_detect: bool, - #[clap( - help_heading = "COMPILER OPTIONS", - help = "specify the solc version or path to a local solc to run with.\ - This accepts values of the form `x.y.z`, `solc:x.y.z` or `path/to/existing/solc`", - value_name = "use", - long = "use" - )] + /// Specify the solc version, or a path to a local solc, to build with. + /// + /// Valid values are in the format `x.y.z`, `solc:x.y.z` or `path/to/solc`. + #[clap(help_heading = "COMPILER OPTIONS", value_name = "use", long = "use")] #[serde(skip)] pub use_solc: Option, diff --git a/cli/src/opts/forge.rs b/cli/src/opts/forge.rs index 436d9629c58b0..ae4637273bb63 100644 --- a/cli/src/opts/forge.rs +++ b/cli/src/opts/forge.rs @@ -161,7 +161,7 @@ pub struct CompilerArgs { /// Extra output to write to separate files. /// - /// Valid values: metadata, ir, irOptimized, ewasm, assembly + /// Valid values: metadata, ir, irOptimized, ewasm, evm.assembly #[clap(long)] #[serde(skip_serializing_if = "Option::is_none")] pub extra_output_files: Option>, From 26bf3ffffdb12d7799763d107740ece066e872e2 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 14 Apr 2022 11:30:46 +0200 Subject: [PATCH 26/30] style: `forge inspect` improvements --- cli/src/cmd/forge/inspect.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cli/src/cmd/forge/inspect.rs b/cli/src/cmd/forge/inspect.rs index bbac4949586f4..094ff8827ce0f 100644 --- a/cli/src/cmd/forge/inspect.rs +++ b/cli/src/cmd/forge/inspect.rs @@ -82,18 +82,18 @@ impl FromStr for ContractArtifactFields { "metadata" | "meta" => Ok(ContractArtifactFields::Metadata), "userdoc" | "userDoc" | "user-doc" => Ok(ContractArtifactFields::UserDoc), "ewasm" | "e-wasm" => Ok(ContractArtifactFields::Ewasm), - _ => Err(format!("Unknown mode: {}", s)), + _ => Err(format!("Unknown field: {}", s)), } } } #[derive(Debug, Clone, Parser)] pub struct InspectArgs { - #[clap(help = "the contract to inspect")] + #[clap(help = "The name of the contract to inspect.")] pub contract: String, - #[clap(help = "the contract artifact field to inspect")] - pub mode: ContractArtifactFields, + #[clap(help = "The contract artifact field to inspect.")] + pub field: ContractArtifactFields, /// All build arguments are supported #[clap(flatten)] @@ -103,12 +103,12 @@ pub struct InspectArgs { impl Cmd for InspectArgs { type Output = (); fn run(self) -> eyre::Result { - let InspectArgs { contract, mode, build } = self; + let InspectArgs { contract, field, build } = self; - // Map mode to ContractOutputSelection + // Map field to ContractOutputSelection let mut cos = build.compiler.extra_output.unwrap_or_default(); - if !cos.iter().any(|&i| i.to_string() == mode.to_string()) { - match mode { + if !cos.iter().any(|&i| i.to_string() == field.to_string()) { + match field { ContractArtifactFields::Abi => cos.push(ContractOutputSelection::Abi), ContractArtifactFields::Bytecode => { /* Auto Generated */ } ContractArtifactFields::DeployedBytecode => { /* Auto Generated */ } @@ -138,7 +138,7 @@ impl Cmd for InspectArgs { } // Run Optimized? - let optimized = if let ContractArtifactFields::AssemblyOptimized = mode { + let optimized = if let ContractArtifactFields::AssemblyOptimized = field { true } else { build.compiler.optimize @@ -167,7 +167,7 @@ impl Cmd for InspectArgs { })?; // Match on ContractArtifactFields and Pretty Print - match mode { + match field { ContractArtifactFields::Abi => { println!("{}", serde_json::to_string_pretty(&to_value(&artifact.abi)?)?); } From 3b7a3ecfa692317414c8cbb25509dd6e4c9277e6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 14 Apr 2022 12:25:34 +0200 Subject: [PATCH 27/30] style: various improvements --- cli/src/cast.rs | 6 +-- cli/src/cmd/cast/call.rs | 2 +- cli/src/cmd/forge/build.rs | 13 +++++- cli/src/cmd/forge/create.rs | 70 ++++++++++++++++++++--------- cli/src/opts/cast.rs | 61 ++++++++++++------------- cli/src/opts/mod.rs | 88 +++++++++++++++++++++++++++---------- 6 files changed, 158 insertions(+), 82 deletions(-) diff --git a/cli/src/cast.rs b/cli/src/cast.rs index 5e0fb3a7593a2..aaee3ed032b8c 100644 --- a/cli/src/cast.rs +++ b/cli/src/cast.rs @@ -159,7 +159,7 @@ async fn main() -> eyre::Result<()> { let provider = Provider::try_from(eth.rpc_url()?)?; let mut builder = TxBuilder::new( &provider, - eth.from.unwrap_or(Address::zero()), + eth.wallet.from.unwrap_or(Address::zero()), address, eth.chain, false, @@ -322,7 +322,7 @@ async fn main() -> eyre::Result<()> { .await?; } } - } else if let Some(from) = eth.from { + } else if let Some(from) = eth.wallet.from { if resend { nonce = Some(provider.get_transaction_count(from, None).await?); } @@ -669,7 +669,6 @@ async fn main() -> eyre::Result<()> { // TODO: Figure out better way to get wallet only. let wallet = EthereumOpts { wallet, - from: None, rpc_url: Some("http://localhost:8545".to_string()), flashbots: false, chain: Chain::Mainnet, @@ -690,7 +689,6 @@ async fn main() -> eyre::Result<()> { // TODO: Figure out better way to get wallet only. let wallet = EthereumOpts { wallet, - from: None, rpc_url: Some("http://localhost:8545".to_string()), flashbots: false, chain: Chain::Mainnet, diff --git a/cli/src/cmd/cast/call.rs b/cli/src/cmd/cast/call.rs index 3b5cc81cdb36d..28f72973f8f14 100644 --- a/cli/src/cmd/cast/call.rs +++ b/cli/src/cmd/cast/call.rs @@ -49,7 +49,7 @@ impl figment::Provider for CallArgs { dict.insert("eth_rpc_url".to_string(), Value::from(rpc_url.to_string())); } - if let Some(from) = self.eth.from { + if let Some(from) = self.eth.wallet.from { dict.insert("sender".to_string(), Value::from(format!("{:?}", from))); } diff --git a/cli/src/cmd/forge/build.rs b/cli/src/cmd/forge/build.rs index c759ebdb8781d..b8050246bf5d9 100644 --- a/cli/src/cmd/forge/build.rs +++ b/cli/src/cmd/forge/build.rs @@ -63,11 +63,20 @@ impl<'a> From<&'a CoreBuildArgs> for Config { #[derive(Debug, Clone, Parser, Serialize)] pub struct CoreBuildArgs { - #[clap(help = "Clear the cache and artifacts folder and recompile.", long)] + #[clap( + help_heading = "CACHE OPTIONS", + help = "Clear the cache and artifacts folder and recompile.", + long + )] #[serde(skip)] pub force: bool, - #[clap(help = "Set pre-linked libraries.", long, env = "DAPP_LIBRARIES")] + #[clap( + help_heading = "LINKER OPTIONS", + help = "Set pre-linked libraries.", + long, + env = "DAPP_LIBRARIES" + )] #[serde(skip_serializing_if = "Vec::is_empty")] pub libraries: Vec, diff --git a/cli/src/cmd/forge/create.rs b/cli/src/cmd/forge/create.rs index a097aa6f05355..727afbb8be4e8 100644 --- a/cli/src/cmd/forge/create.rs +++ b/cli/src/cmd/forge/create.rs @@ -18,10 +18,13 @@ use std::{fs, path::PathBuf, sync::Arc}; #[derive(Debug, Clone, Parser)] pub struct CreateArgs { + #[clap(help = "The contract identifier in the form `:`.")] + contract: ContractInfo, + #[clap( long, multiple_values = true, - help = "constructor args calldata arguments", + help = "Constructor arguments.", name = "constructor_args", conflicts_with = "constructor_args_path" )] @@ -29,41 +32,64 @@ pub struct CreateArgs { #[clap( long, - help = "path to a file containing the constructor args", + help = "The path to a file containing the constructor arguments.", value_hint = ValueHint::FilePath, name = "constructor_args_path", conflicts_with = "constructor_args", )] constructor_args_path: Option, - #[clap(flatten)] - opts: CoreBuildArgs, - - #[clap(flatten)] - eth: EthereumOpts, - - #[clap(help = "contract source info `:` or ``")] - contract: ContractInfo, - #[clap( long, - help = "use legacy transactions instead of EIP1559 ones. this is auto-enabled for common networks without EIP1559" + help_heading = "TRANSACTION OPTIONS", + help = "Send a legacy transaction instead of an EIP1559 transaction.", + long_help = r#"Send a legacy transaction instead of an EIP1559 transaction. + +This is automatically enabled for common networks without EIP1559."# )] legacy: bool, - #[clap(long = "gas-price", help = "gas price for legacy txs or maxFeePerGas for EIP1559 txs", env = "ETH_GAS_PRICE", parse(try_from_str = parse_u256))] + #[clap( + long = "gas-price", + help_heading = "TRANSACTION OPTIONS", + help = "Gas price for legacy transactions, or max fee per gas for EIP1559 transactions.", + env = "ETH_GAS_PRICE", + parse(try_from_str = parse_u256) + )] gas_price: Option, - #[clap(long = "gas-limit", help = "maximum amount of gas that can be consumed for txs", env = "ETH_GAS_LIMIT", parse(try_from_str = parse_u256))] + #[clap( + long = "gas-limit", + help_heading = "TRANSACTION OPTIONS", + help = "Gas limit for the transaction.", + env = "ETH_GAS_LIMIT", + parse(try_from_str = parse_u256) + )] gas_limit: Option, - #[clap(long = "priority-fee", help = "gas priority fee for EIP1559 txs", env = "ETH_GAS_PRIORITY_FEE", parse(try_from_str = parse_u256))] + #[clap( + long = "priority-fee", + help_heading = "TRANSACTION OPTIONS", + help = "Gas priority fee for EIP1559 transactions.", + env = "ETH_GAS_PRIORITY_FEE", parse(try_from_str = parse_u256) + )] priority_fee: Option, - #[clap(long = "value", help = "value to send with the contract creation tx", env = "ETH_VALUE", parse(try_from_str = parse_u256))] + #[clap( + long, + help_heading = "TRANSACTION OPTIONS", + help = "Ether to send in the transaction in wei.", + parse(try_from_str = parse_u256) + )] value: Option, - #[clap(long = "json", help = "print the deployment information as json")] + #[clap(flatten, next_help_heading = "BUILD OPTIONS")] + opts: CoreBuildArgs, + + #[clap(flatten, next_help_heading = "ETHEREUM OPTIONS")] + eth: EthereumOpts, + + #[clap(long = "json", help = "Print the deployment information as JSON.")] json: bool, } @@ -197,10 +223,14 @@ impl CreateArgs { let (deployed_contract, receipt) = deployer.send_with_receipt().await?; if self.json { - let output = json!({"deployer": deployer_address, "deployedTo": deployed_contract.address(), "transactionHash": receipt.transaction_hash}); - println!("{}", output); + let output = json!({ + "deployer": deployer_address, + "deployedTo": deployed_contract.address(), + "transactionHash": receipt.transaction_hash + }); + println!("{output}"); } else { - println!("Deployer: {:?}", deployer_address); + println!("Deployer: {deployer_address:?}"); println!("Deployed to: {:?}", deployed_contract.address()); println!("Transaction hash: {:?}", receipt.transaction_hash); } diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index f12138029fe3d..08420ad60b59c 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -40,16 +40,14 @@ pub enum Subcommands { FromBin, #[clap(name = "--to-hexdata")] #[clap( - about = " - Normalize the input to lowercase, 0x-prefixed hex. See --help for more info.", - long_about = "Normalize the input to lowercase, 0x-prefixed hex. + about = "Normalize the input to lowercase, 0x-prefixed hex. See --help for more info.", + long_about = r#"Normalize the input to lowercase, 0x-prefixed hex. - The input can be: - - mixed case hex with or without 0x prefix - - 0x prefixed hex, concatenated with a ':' - - an absolute path to file - - @tag, where the tag is defined in an environment variable - " +The input can be: +- mixed case hex with or without 0x prefix +- 0x prefixed hex, concatenated with a ':' +- an absolute path to file +- @tag, where the tag is defined in an environment variable"# )] ToHexdata { input: Option }, #[clap(aliases = &["--to-checksum"])] // Compatibility with dapptools' cast @@ -88,15 +86,14 @@ pub enum Subcommands { #[clap(name = "--to-unit")] #[clap( about = "Convert an ETH amount into another unit (ether, gwei or wei).", - long_about = r#"Convert an ETH amount into another unit (ether, gwei or wei). + long_about = r#"Convert an ETH amount into another unit (ether, gwei or wei).\ - Examples: - - 1ether wei | converts 1 ether to wei - - "1 ether" wei | converts 1 ether to wei - - 1ether | converts 1 ether to wei - - 1 gwei | converts 1 wei to gwei - - 1gwei ether | converts 1 gwei to ether - "# +Examples: +- 1ether wei +- "1 ether" wei +- 1ether +- 1 gwei +- 1gwei ether"# )] ToUnit { value: Option, @@ -264,9 +261,9 @@ pub enum Subcommands { #[clap( long, help = "Ether to send in the transaction.", - long_help = "Ether to send in the transaction, either specified in wei, or as a string with a unit type. + long_help = r#"Ether to send in the transaction, either specified in wei, or as a string with a unit type. - Examples: 1ether, 10gwei, 0.01ether", +Examples: 1ether, 10gwei, 0.01ether"#, parse(try_from_str = parse_ether_value) )] value: Option, @@ -279,9 +276,9 @@ pub enum Subcommands { #[clap( long, help = "Send a legacy transaction instead of an EIP1559 transaction.", - long_help = "Send a legacy transaction instead of an EIP1559 transaction. + long_help = r#"Send a legacy transaction instead of an EIP1559 transaction. - This is automatically enabled for common networks without EIP1559." +This is automatically enabled for common networks without EIP1559."# )] legacy: bool, #[clap( @@ -323,9 +320,9 @@ pub enum Subcommands { #[clap( long, help = "Ether to send in the transaction.", - long_help = "Ether to send in the transaction, either specified in wei, or as a string with a unit type. + long_help = r#"Ether to send in the transaction, either specified in wei, or as a string with a unit type. - Examples: 1ether, 10gwei, 0.01ether", +Examples: 1ether, 10gwei, 0.01ether"#, parse(try_from_str = parse_ether_value) )] value: Option, @@ -344,9 +341,9 @@ pub enum Subcommands { #[clap(name = "--abi-decode")] #[clap( about = "Decode ABI-encoded input or output data", - long_about = "Decode ABI-encoded input or output data. + long_about = r#"Decode ABI-encoded input or output data. - Defaults to decoding output data. To decode input data pass --input or use cast --calldata-decode." +Defaults to decoding output data. To decode input data pass --input or use cast --calldata-decode."# )] AbiDecode { #[clap(help = "The function signature in the format `()()`.")] @@ -391,11 +388,11 @@ pub enum Subcommands { #[clap( long, help = "The index of the resolved signature to use.", - long_help = "The index of the resolved signature to use. + long_help = r#"The index of the resolved signature to use. - 4byte.directory can have multiple possible signatures for a given selector. +4byte.directory can have multiple possible signatures for a given selector. - The index can also be earliest or latest." +The index can also be earliest or latest."# )] id: Option, }, @@ -408,9 +405,9 @@ pub enum Subcommands { #[clap(name = "pretty-calldata")] #[clap( about = "Pretty print calldata.", - long_about = "Pretty print calldata. + long_about = r#"Pretty print calldata. - Tries to decode the calldata using 4byte.directory unless --offline is passed." +Tries to decode the calldata using 4byte.directory unless --offline is passed."# )] PrettyCalldata { #[clap(help = "The calldata.")] @@ -586,9 +583,9 @@ pub enum Subcommands { Interface { #[clap( help = "The contract address, or the path to an ABI file.", - long_help = "The contract address, or the path to an ABI file. + long_help = r#"The contract address, or the path to an ABI file. - If an address is specified, then the ABI is fetched from Etherscan." +If an address is specified, then the ABI is fetched from Etherscan."# )] path_or_address: String, #[clap(long, short, default_value = "^0.8.10", help = "Solidity pragma version.")] diff --git a/cli/src/opts/mod.rs b/cli/src/opts/mod.rs index 62af33ef173d5..d66f54fc7eda5 100644 --- a/cli/src/opts/mod.rs +++ b/cli/src/opts/mod.rs @@ -29,6 +29,7 @@ use serde::Serialize; const FLASHBOTS_URL: &str = "https://rpc.flashbots.net"; // Helper for exposing enum values for `Chain` +// TODO: Is this a duplicate of config/src/chain.rs? #[derive(Debug, Clone, Parser)] pub struct ClapChain { #[clap( @@ -60,16 +61,9 @@ pub struct ClapChain { #[derive(Parser, Debug, Clone, Serialize)] pub struct EthereumOpts { - #[clap(env = "ETH_RPC_URL", long = "rpc-url", help = "The tracing / archival node's URL")] + #[clap(env = "ETH_RPC_URL", long = "rpc-url", help = "The RPC endpoint.")] pub rpc_url: Option, - #[clap(env = "ETH_FROM", short, long = "from", help = "The sender account")] - pub from: Option
, - - #[clap(flatten)] - #[serde(skip)] - pub wallet: Wallet, - #[clap(long, help = "Use the flashbots RPC URL (https://rpc.flashbots.net)")] pub flashbots: bool, @@ -79,6 +73,10 @@ pub struct EthereumOpts { #[clap(long, env = "CHAIN", default_value = "mainnet")] #[serde(skip)] pub chain: Chain, + + #[clap(flatten, next_help_heading = "WALLET OPTIONS")] + #[serde(skip)] + pub wallet: Wallet, } impl EthereumOpts { @@ -92,7 +90,7 @@ impl EthereumOpts { WalletType::Trezor(signer) => signer.address(), } } else { - self.from.unwrap_or_else(Address::zero) + self.wallet.from.unwrap_or_else(Address::zero) } } @@ -189,40 +187,83 @@ The wallet options can either be: "# )] pub struct Wallet { - #[clap(long, short, help = "Open an interactive prompt to enter your private key.")] + #[clap( + long, + short, + help_heading = "WALLET OPTIONS - RAW", + help = "Open an interactive prompt to enter your private key." + )] pub interactive: bool, - #[clap(long = "private-key", help = "Your private key string")] + #[clap( + long = "private-key", + help_heading = "WALLET OPTIONS - RAW", + help = "Use the provided private key." + )] pub private_key: Option, + #[clap( + long = "mnemonic-path", + help_heading = "WALLET OPTIONS - RAW", + help = "Use the mnemonic file at the specified path." + )] + pub mnemonic_path: Option, + + #[clap( + long = "mnemonic-index", + help_heading = "WALLET OPTIONS - RAW", + help = "Use the private key from the given mnemonic index. Used with --mnemonic-path.", + default_value = "0" + )] + pub mnemonic_index: u32, + #[clap( env = "ETH_KEYSTORE", long = "keystore", - help = "The path to a keystore folder or file." + help_heading = "WALLET OPTIONS - KEYSTORE", + help = "Use the keystore in the given folder or file." )] pub keystore_path: Option, - #[clap(long = "password", help = "Your keystore password", requires = "keystore-path")] + #[clap( + long = "password", + help_heading = "WALLET OPTIONS - KEYSTORE", + help = "The keystore password. Used with --keystore.", + requires = "keystore-path" + )] pub keystore_password: Option, - #[clap(long = "mnemonic-path", help = "Path to a mnemonic file.")] - pub mnemonic_path: Option, - - #[clap(short, long = "ledger", help = "Use your Ledger hardware wallet.")] + #[clap( + short, + long = "ledger", + help_heading = "WALLET OPTIONS - HARDWARE WALLET", + help = "Use a Ledger hardware wallet." + )] pub ledger: bool, - #[clap(short, long = "trezor", help = "Use your Trezor hardware wallet.")] + #[clap( + short, + long = "trezor", + help_heading = "WALLET OPTIONS - HARDWARE WALLET", + help = "Use a Trezor hardware wallet." + )] pub trezor: bool, - #[clap(long = "hd-path", help = "Derivation path for your hardware wallet.")] + #[clap( + long = "hd-path", + help_heading = "WALLET OPTIONS - HARDWARE WALLET", + help = "The derivation path to use with hardware wallets." + )] pub hd_path: Option, #[clap( - long = "mnemonic-index", - help = "your index in the standard hd path", - default_value = "0" + env = "ETH_FROM", + short, + long = "from", + help_heading = "WALLET OPTIONS - REMOTE", + help = "The sender account." )] - pub mnemonic_index: u32, + pub from: Option
, } impl Wallet { @@ -283,6 +324,7 @@ mod tests { #[test] fn illformed_private_key_generates_user_friendly_error() { let wallet = Wallet { + from: None, interactive: false, private_key: Some("123".to_string()), keystore_path: None, From 82aeb4c9009e202ee3a61a8c9fffd36c35df6ae5 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 14 Apr 2022 13:14:43 +0200 Subject: [PATCH 28/30] style: various adjustments --- cli/src/cmd/forge/create.rs | 22 ++++++++++++++-------- cli/src/cmd/forge/test.rs | 2 +- cli/src/opts/cast.rs | 27 ++++++++------------------- cli/src/utils.rs | 25 +++++++++++++++++++++---- 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/cli/src/cmd/forge/create.rs b/cli/src/cmd/forge/create.rs index 727afbb8be4e8..6aec24378d1e6 100644 --- a/cli/src/cmd/forge/create.rs +++ b/cli/src/cmd/forge/create.rs @@ -3,7 +3,7 @@ use crate::{ cmd::{forge::build::CoreBuildArgs, Cmd}, compile, opts::{forge::ContractInfo, EthereumOpts, WalletType}, - utils::parse_u256, + utils::parse_ether_value, }; use clap::{Parser, ValueHint}; use ethers::{ @@ -24,7 +24,7 @@ pub struct CreateArgs { #[clap( long, multiple_values = true, - help = "Constructor arguments.", + help = "The constructor arguments.", name = "constructor_args", conflicts_with = "constructor_args_path" )] @@ -54,7 +54,7 @@ This is automatically enabled for common networks without EIP1559."# help_heading = "TRANSACTION OPTIONS", help = "Gas price for legacy transactions, or max fee per gas for EIP1559 transactions.", env = "ETH_GAS_PRICE", - parse(try_from_str = parse_u256) + parse(try_from_str = parse_ether_value) )] gas_price: Option, @@ -71,15 +71,17 @@ This is automatically enabled for common networks without EIP1559."# long = "priority-fee", help_heading = "TRANSACTION OPTIONS", help = "Gas priority fee for EIP1559 transactions.", - env = "ETH_GAS_PRIORITY_FEE", parse(try_from_str = parse_u256) + env = "ETH_GAS_PRIORITY_FEE", parse(try_from_str = parse_ether_value) )] priority_fee: Option, - #[clap( long, help_heading = "TRANSACTION OPTIONS", - help = "Ether to send in the transaction in wei.", - parse(try_from_str = parse_u256) + help = "Ether to send in the transaction.", + long_help = r#"Ether to send in the transaction, either specified in wei, or as a string with a unit type. + +Examples: 1ether, 10gwei, 0.01ether"#, + parse(try_from_str = parse_ether_value) )] value: Option, @@ -89,7 +91,11 @@ This is automatically enabled for common networks without EIP1559."# #[clap(flatten, next_help_heading = "ETHEREUM OPTIONS")] eth: EthereumOpts, - #[clap(long = "json", help = "Print the deployment information as JSON.")] + #[clap( + long = "json", + help_heading = "DISPLAY OPTIONS", + help = "Print the deployment information as JSON." + )] json: bool, } diff --git a/cli/src/cmd/forge/test.rs b/cli/src/cmd/forge/test.rs index ee5aaa06a7ea2..155b885e38083 100644 --- a/cli/src/cmd/forge/test.rs +++ b/cli/src/cmd/forge/test.rs @@ -167,7 +167,7 @@ pub struct TestArgs { allow_failure: bool, /// Output test results in JSON format. - #[clap(long, short)] + #[clap(long, short, help_heading = "DISPLAY OPTIONS")] json: bool, #[clap(flatten, next_help_heading = "EVM OPTIONS")] diff --git a/cli/src/opts/cast.rs b/cli/src/opts/cast.rs index 08420ad60b59c..01a00af2fe956 100644 --- a/cli/src/opts/cast.rs +++ b/cli/src/opts/cast.rs @@ -1,13 +1,10 @@ use super::{ClapChain, EthereumOpts, Wallet}; use crate::{ cmd::cast::{call::CallArgs, find_block::FindBlockArgs}, - utils::parse_u256, + utils::{parse_ether_value, parse_u256}, }; use clap::{Parser, Subcommand, ValueHint}; -use ethers::{ - abi::token::{LenientTokenizer, Tokenizer}, - types::{Address, BlockId, BlockNumber, NameOrAddress, H256, U256}, -}; +use ethers::types::{Address, BlockId, BlockNumber, NameOrAddress, H256, U256}; use std::{path::PathBuf, str::FromStr}; #[derive(Debug, Subcommand)] @@ -134,7 +131,7 @@ Examples: #[clap(flatten)] // TODO: We only need RPC URL + etherscan stuff from this struct eth: EthereumOpts, - #[clap(long = "json", short = 'j')] + #[clap(long = "json", short = 'j', help_heading = "DISPLAY OPTIONS")] to_json: bool, }, #[clap(name = "block")] @@ -152,7 +149,7 @@ Examples: full: bool, #[clap(long, short, help = "If specified, only get the given field of the block.")] field: Option, - #[clap(long = "json", short = 'j')] + #[clap(long = "json", short = 'j', help_heading = "DISPLAY OPTIONS")] to_json: bool, #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, @@ -212,7 +209,7 @@ Examples: Tx { hash: String, field: Option, - #[clap(long = "json", short = 'j')] + #[clap(long = "json", short = 'j', help_heading = "DISPLAY OPTIONS")] to_json: bool, #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, @@ -232,7 +229,7 @@ Examples: confirmations: usize, #[clap(long, env = "CAST_ASYNC")] cast_async: bool, - #[clap(long = "json", short = 'j')] + #[clap(long = "json", short = 'j', help_heading = "DISPLAY OPTIONS")] to_json: bool, #[clap(long, env = "ETH_RPC_URL")] rpc_url: String, @@ -253,7 +250,7 @@ Examples: gas: Option, #[clap( long = "gas-price", - help = "Gas price for the transaction.", + help = "Gas price for legacy transactions, or max fee per gas for EIP1559 transactions.", env = "ETH_GAS_PRICE", parse(try_from_str = parse_ether_value) )] @@ -288,7 +285,7 @@ This is automatically enabled for common networks without EIP1559."# default_value = "1" )] confirmations: usize, - #[clap(long = "json", short = 'j')] + #[clap(long = "json", short = 'j', help_heading = "DISPLAY OPTIONS")] to_json: bool, #[clap( long = "resend", @@ -703,14 +700,6 @@ fn parse_slot(s: &str) -> eyre::Result { }) } -fn parse_ether_value(value: &str) -> eyre::Result { - Ok(if value.starts_with("0x") { - U256::from_str(value)? - } else { - U256::from(LenientTokenizer::tokenize_uint(value)?) - }) -} - #[derive(Debug, Parser)] #[clap(name = "cast", version = crate::utils::VERSION_MESSAGE)] pub struct Opts { diff --git a/cli/src/utils.rs b/cli/src/utils.rs index 818d7c134e4fc..0c55bbd5f8c54 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -1,13 +1,16 @@ +use ethers::{ + abi::token::{LenientTokenizer, Tokenizer}, + solc::EvmVersion, + types::U256, +}; +use forge::executor::{opts::EvmOpts, Fork, SpecId}; +use foundry_config::{caching::StorageCachingConfig, Config}; use std::{ future::Future, path::{Path, PathBuf}, str::FromStr, time::Duration, }; - -use ethers::{solc::EvmVersion, types::U256}; -use forge::executor::{opts::EvmOpts, Fork, SpecId}; -use foundry_config::{caching::StorageCachingConfig, Config}; use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; @@ -126,6 +129,20 @@ pub fn parse_u256(s: &str) -> eyre::Result { Ok(if s.starts_with("0x") { U256::from_str(s)? } else { U256::from_dec_str(s)? }) } +/// Parses an ether value from a string. +/// +/// The amount can be tagged with a unit, e.g. "1ether". +/// +/// If the string represents an untagged amount (e.g. "100") then +/// it is interpreted as wei. +pub fn parse_ether_value(value: &str) -> eyre::Result { + Ok(if value.starts_with("0x") { + U256::from_str(value)? + } else { + U256::from(LenientTokenizer::tokenize_uint(value)?) + }) +} + /// Parses a `Duration` from a &str pub fn parse_delay(delay: &str) -> eyre::Result { let delay = if delay.ends_with("ms") { From 5b87e904c3b6337d8d97ae5b2ea46ace004d8e30 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 14 Apr 2022 14:09:03 +0200 Subject: [PATCH 29/30] style: remaining help text --- cli/src/cmd/forge/run.rs | 4 ++-- cli/src/cmd/forge/test.rs | 2 +- cli/src/opts/evm.rs | 25 ++++++++++++------------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/cli/src/cmd/forge/run.rs b/cli/src/cmd/forge/run.rs index c5b6c23de2b8b..a92b4dc294d96 100644 --- a/cli/src/cmd/forge/run.rs +++ b/cli/src/cmd/forge/run.rs @@ -46,11 +46,11 @@ pub struct RunArgs { pub args: Vec, /// The name of the contract you want to run. - #[clap(long, short)] + #[clap(long, short, value_name = "CONTRACT_NAME")] pub target_contract: Option, /// The signature of the function you want to call in the contract, or raw calldata. - #[clap(long, short, default_value = "run()")] + #[clap(long, short, default_value = "run()", value_name = "SIGNATURE")] pub sig: String, /// Open the script in the debugger. diff --git a/cli/src/cmd/forge/test.rs b/cli/src/cmd/forge/test.rs index 155b885e38083..55c0460d96c1a 100644 --- a/cli/src/cmd/forge/test.rs +++ b/cli/src/cmd/forge/test.rs @@ -162,7 +162,7 @@ pub struct TestArgs { #[clap(long, env = "FORGE_GAS_REPORT")] gas_report: bool, - /// Force the process to exit with code 0, even if the tests fail. + /// Exit with code 0 even if a test fails. #[clap(long, env = "FORGE_ALLOW_FAILURE")] allow_failure: bool, diff --git a/cli/src/opts/evm.rs b/cli/src/opts/evm.rs index 419a0a344d9d1..e8545dcfa53b1 100644 --- a/cli/src/opts/evm.rs +++ b/cli/src/opts/evm.rs @@ -49,15 +49,14 @@ pub struct EvmArgs { #[serde(skip_serializing_if = "Option::is_none")] pub fork_block_number: Option, - /// Disables storage caching entirely. This overrides any settings made in - /// [foundry_config::caching::StorageCachingConfig] + /// Explicitly disables the use of RPC caching. + /// + /// All storage slots are read entirely from the endpoint. + /// + /// This flag overrides the project's configuration file. /// /// See --fork-url. - #[clap( - long, - requires = "fork-url", - help = "Explicitly disables the use of storage. All storage slots are read entirely from the endpoint." - )] + #[clap(long, requires = "fork-url")] #[serde(skip)] pub no_storage_caching: bool, @@ -72,7 +71,7 @@ pub struct EvmArgs { pub sender: Option
, /// Enable the FFI cheatcode. - #[clap(help = "enables the FFI cheatcode", long)] + #[clap(help = "Enables the FFI cheatcode.", long)] #[serde(skip)] pub ffi: bool, @@ -81,11 +80,11 @@ pub struct EvmArgs { /// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). /// /// Verbosity levels: - /// 2: Print logs for all tests - /// 3: Print execution traces for failing tests - /// 4: Print execution traces for all tests, and setup traces for failing tests - /// 5: Print execution and setup traces for all tests - #[clap(long, short, parse(from_occurrences))] + /// - 2: Print logs for all tests + /// - 3: Print execution traces for failing tests + /// - 4: Print execution traces for all tests, and setup traces for failing tests + /// - 5: Print execution and setup traces for all tests + #[clap(long, short, parse(from_occurrences), verbatim_doc_comment)] #[serde(skip)] pub verbosity: u8, From a639286cf03bb06d35b7e77eceae67f82ea1d0ae Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 14 Apr 2022 14:14:46 +0200 Subject: [PATCH 30/30] fix: missing import after rebase --- cli/src/cmd/forge/create.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/cmd/forge/create.rs b/cli/src/cmd/forge/create.rs index 6aec24378d1e6..aa4d590d5a696 100644 --- a/cli/src/cmd/forge/create.rs +++ b/cli/src/cmd/forge/create.rs @@ -3,7 +3,7 @@ use crate::{ cmd::{forge::build::CoreBuildArgs, Cmd}, compile, opts::{forge::ContractInfo, EthereumOpts, WalletType}, - utils::parse_ether_value, + utils::{parse_ether_value, parse_u256}, }; use clap::{Parser, ValueHint}; use ethers::{