Skip to content

Commit

Permalink
Attempt to include special code only available for cli_examples
Browse files Browse the repository at this point in the history
The cli_examples are converted also to unit tests. These unit tests will
compile a binary and execute it. The main goal is to enforce
documentation correctness through the unit tests.

Sometimes a special case should be hard-coded (especially mocking user
input) that should only be available for the binaries to be executed
from the unit tests but should be excluded otherwise.

This PR adds the ability to include code through option_env! macro since
the CLI_TEST should only be defined in for the binary to be executed
from the unit tests.

This PR is a pre-requisite to solve FuelLabs/forc-wallet#157

This PR will include more examples as part of #5444
  • Loading branch information
crodas committed Feb 3, 2024
1 parent ad00b34 commit d29f578
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 107 deletions.
2 changes: 1 addition & 1 deletion forc-plugins/forc-client/src/cmd/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::NodeTarget;
forc_util::cli_examples! {
[ Deploy a single contract => deploy "bc09bfa7a11a04ce42b0a5abf04fd437387ee49bf4561d575177e2946468b408" => r#".*Error making HTTP request.*"# ]
[ Deploy a single contract from a different path => deploy "bc09bfa7a11a04ce42b0a5abf04fd437387ee49bf4561d575177e2946468b408 --path ../tests/" => r#".*Error making HTTP request.*"# ]
[ Deploy to a custom network => deploy "--node-url https://beta-5.fuel.network/graphql" => ".*Refused to create a new wallet.*" ]
[ Deploy to a custom network => deploy "--node-url https://beta-5.fuel.network/graphql" => ".*Deployment failed due to insufficient funds.*" ]
}

#[derive(Debug, Default, Parser)]
Expand Down
10 changes: 5 additions & 5 deletions forc-plugins/forc-client/src/cmd/submit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ use devault::Devault;
use std::path::PathBuf;

forc_util::cli_examples! {
[ Submit a transaction from a json file => submit "./mint.json" => "Submission of tx or awaiting commit failed" ]
[ Submit a transaction from a json file and wait for confirmation => submit "./mint.json --await true" => "Submission of tx or awaiting commit failed" ]
[ Submit a transaction from a json file and get output in json => submit "./mint.json --tx-status-json true" => "Submission of tx or awaiting commit failed" ]
[ Submit a transaction from a json file to testnet => submit "./mint.json --testnet" => "Submission of tx or awaiting commit failed" ]
[ Submit a transaction from a json file to a local net => submit "./mint.json --target local" => "Submission of tx or awaiting commit failed" ]
[ Submit a transaction from a json file => submit "./mint.json" ]
[ Submit a transaction from a json file and wait for confirmation => submit "./mint.json --await true" ]
[ Submit a transaction from a json file and get output in json => submit "./mint.json --tx-status-json true" ]
[ Submit a transaction from a json file to testnet => submit "./mint.json --testnet" ]
[ Submit a transaction from a json file to a local net => submit "./mint.json --target local" ]
}

/// Submit a transaction to the specified fuel node.
Expand Down
8 changes: 8 additions & 0 deletions forc-plugins/forc-client/src/op/submit.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
use crate::{cmd, util::node_url::get_node_url};
use anyhow::Context;
use forc_tracing::println_warning;
use fuel_core_client::client::{types::TransactionStatus, FuelClient};
use fuel_crypto::fuel_types::canonical::Deserialize;

/// A command for submitting transactions to a Fuel network.
pub async fn submit(cmd: cmd::Submit) -> anyhow::Result<()> {
let tx = read_tx(&cmd.tx_path)?;
let node_url = get_node_url(&cmd.network.node, &None)?;
if option_env!("CLI_TEST").is_some() {
println_warning(&format!(
"Simulating a successful execution of tx: {:?}, to url {}",
tx, node_url
));
return Ok(());
}
let client = FuelClient::new(node_url)?;
if cmd.network.await_ {
let status = client
Expand Down
209 changes: 108 additions & 101 deletions forc-plugins/forc-client/src/util/tx.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use std::{io::Write, str::FromStr};

use anyhow::{Error, Result};
use async_trait::async_trait;
use forc_tracing::println_warning;
use forc_wallet::{
account::{derive_secret_key, new_at_index_cli},
balance::{
collect_accounts_with_verification, print_account_balances, AccountBalances,
AccountVerification, AccountsMap,
},
new::{new_wallet_cli, New},
utils::default_wallet_path,
};
use fuel_crypto::{Message, PublicKey, SecretKey, Signature};
use fuel_tx::{
field, Address, AssetId, Buildable, ContractId, Input, Output, TransactionBuilder, Witness,
Expand All @@ -13,16 +20,7 @@ use fuels_core::types::{
coin_type::CoinType,
transaction_builders::{create_coin_input, create_coin_message_input},
};

use forc_wallet::{
account::{derive_secret_key, new_at_index_cli},
balance::{
collect_accounts_with_verification, print_account_balances, AccountBalances,
AccountVerification, AccountsMap,
},
new::{new_wallet_cli, New},
utils::default_wallet_path,
};
use std::{io::Write, str::FromStr};

use crate::constants::BETA_FAUCET_URL;

Expand Down Expand Up @@ -164,6 +162,7 @@ impl<Tx: Buildable + field::Witnesses + Send> TransactionBuilderExt<Tx> for Tran

Ok(self)
}

async fn finalize_signed(
&mut self,
provider: Provider,
Expand All @@ -172,32 +171,38 @@ impl<Tx: Buildable + field::Witnesses + Send> TransactionBuilderExt<Tx> for Tran
wallet_mode: WalletSelectionMode,
) -> Result<Tx> {
let params = provider.chain_info().await?.consensus_parameters;
let signing_key = match (wallet_mode, signing_key, default_sign) {
(WalletSelectionMode::ForcWallet, None, false) => {
// TODO: This is a very simple TUI, we should consider adding a nice TUI
// capabilities for selections and answer collection.
let wallet_path = default_wallet_path();
if !wallet_path.exists() {
let question = format!("Could not find a wallet at {wallet_path:?}, would you like to create a new one? [y/N]: ");
let accepted = ask_user_yes_no_question(&question)?;
let new_options = New { force: false };
if accepted {
new_wallet_cli(&wallet_path, new_options)?;
println!("Wallet created successfully.");
// Derive first account for the fresh wallet we created.
new_at_index_cli(&wallet_path, 0)?;
println!("Account derived successfully.");
} else {
anyhow::bail!("Refused to create a new wallet. If you don't want to use forc-wallet, you can sign this transaction manually with --manual-signing flag.")
let signing_key = if option_env!("CLI_TEST").is_some() {
// This section should be removed once forc-wallet fully supports CLI_TEST env variable.
println_warning("Creating a new wallet for testing purposes");
let secret_key = SecretKey::from_str(DEFAULT_PRIVATE_KEY)?;
Some(secret_key)
} else {
match (wallet_mode, signing_key, default_sign) {
(WalletSelectionMode::ForcWallet, None, false) => {
// TODO: This is a very simple TUI, we should consider adding a nice TUI
// capabilities for selections and answer collection.
let wallet_path = default_wallet_path();
if !wallet_path.exists() {
let question = format!("Could not find a wallet at {wallet_path:?}, would you like to create a new one? [y/N]: ");
let accepted = ask_user_yes_no_question(&question)?;
let new_options = New { force: false };
if accepted {
new_wallet_cli(&wallet_path, new_options)?;
println!("Wallet created successfully.");
// Derive first account for the fresh wallet we created.
new_at_index_cli(&wallet_path, 0)?;
println!("Account derived successfully.");
} else {
anyhow::bail!("Refused to create a new wallet. If you don't want to use forc-wallet, you can sign this transaction manually with --manual-signing flag.")
}
}
}
let prompt = format!(
let prompt = format!(
"\nPlease provide the password of your encrypted wallet vault at {wallet_path:?}: "
);
let password = rpassword::prompt_password(prompt)?;
let verification = AccountVerification::Yes(password.clone());
let accounts = collect_accounts_with_verification(&wallet_path, verification)
.map_err(|e| {
let password = rpassword::prompt_password(prompt)?;
let verification = AccountVerification::Yes(password.clone());
let accounts = collect_accounts_with_verification(&wallet_path, verification)
.map_err(|e| {
if e.to_string().contains("Mac Mismatch") {
anyhow::anyhow!(
"Failed to access forc-wallet vault. Please check your password"
Expand All @@ -206,83 +211,85 @@ impl<Tx: Buildable + field::Witnesses + Send> TransactionBuilderExt<Tx> for Tran
e
}
})?;
let account_balances = collect_account_balances(&accounts, &provider).await?;
let account_balances = collect_account_balances(&accounts, &provider).await?;

let total_balance = account_balances
.iter()
.flat_map(|account| account.values())
.sum::<u64>();
if total_balance == 0 {
let first_account = accounts
.get(&0)
.ok_or_else(|| anyhow::anyhow!("No account derived for this wallet"))?;
let faucet_link = format!("{}/?address={first_account}", BETA_FAUCET_URL);
anyhow::bail!("Your wallet does not have any funds to pay for the transaction.\
let total_balance = account_balances
.iter()
.flat_map(|account| account.values())
.sum::<u64>();
if total_balance == 0 {
let first_account = accounts
.get(&0)
.ok_or_else(|| anyhow::anyhow!("No account derived for this wallet"))?;
let faucet_link = format!("{}/?address={first_account}", BETA_FAUCET_URL);
anyhow::bail!("Your wallet does not have any funds to pay for the transaction.\
\n\nIf you are interacting with a testnet consider using the faucet.\
\n-> beta-4 network faucet: {faucet_link}\
\nIf you are interacting with a local node, consider providing a chainConfig which funds your account.")
}
print_account_balances(&accounts, &account_balances);
}
print_account_balances(&accounts, &account_balances);

let mut account_index;
loop {
print!("\nPlease provide the index of account to use for signing: ");
std::io::stdout().flush()?;
let mut input_account_index = String::new();
std::io::stdin().read_line(&mut input_account_index)?;
account_index = input_account_index.trim().parse::<usize>()?;
if accounts.contains_key(&account_index) {
break;
let mut account_index;
loop {
print!("\nPlease provide the index of account to use for signing: ");
std::io::stdout().flush()?;
let mut input_account_index = String::new();
std::io::stdin().read_line(&mut input_account_index)?;
account_index = input_account_index.trim().parse::<usize>()?;
if accounts.contains_key(&account_index) {
break;
}
let options: Vec<String> =
accounts.keys().map(|key| key.to_string()).collect();
println_warning(&format!(
"\"{}\" is not a valid account.\nPlease choose a valid option from {}",
account_index,
options.join(","),
));
}
let options: Vec<String> = accounts.keys().map(|key| key.to_string()).collect();
println_warning(&format!(
"\"{}\" is not a valid account.\nPlease choose a valid option from {}",
account_index,
options.join(","),
));
}

let secret_key = derive_secret_key(&wallet_path, account_index, &password)
.map_err(|e| {
if e.to_string().contains("Mac Mismatch") {
anyhow::anyhow!(
let secret_key = derive_secret_key(&wallet_path, account_index, &password)
.map_err(|e| {
if e.to_string().contains("Mac Mismatch") {
anyhow::anyhow!(
"Failed to access forc-wallet vault. Please check your password"
)
} else {
e
}
})?;
} else {
e
}
})?;

// TODO: Do this via forc-wallet once the functinoality is exposed.
let public_key = PublicKey::from(&secret_key);
let hashed = public_key.hash();
let bech32 = Bech32Address::new(FUEL_BECH32_HRP, hashed);
let question = format!(
"Do you agree to sign this transaction with {}? [y/N]: ",
bech32
);
let accepted = ask_user_yes_no_question(&question)?;
if !accepted {
anyhow::bail!("User refused to sign");
}
// TODO: Do this via forc-wallet once the functinoality is exposed.
let public_key = PublicKey::from(&secret_key);
let hashed = public_key.hash();
let bech32 = Bech32Address::new(FUEL_BECH32_HRP, hashed);
let question = format!(
"Do you agree to sign this transaction with {}? [y/N]: ",
bech32
);
let accepted = ask_user_yes_no_question(&question)?;
if !accepted {
anyhow::bail!("User refused to sign");
}

Some(secret_key)
}
(WalletSelectionMode::ForcWallet, Some(key), _) => {
println_warning("Signing key is provided while requesting to sign with forc-wallet or with default signer. Using signing key");
Some(key)
}
(WalletSelectionMode::Manual, None, false) => None,
(WalletSelectionMode::Manual, Some(key), false) => Some(key),
(_, None, true) => {
// Generate a `SecretKey` to sign this transaction from a default private key used
// by fuel-core.
let secret_key = SecretKey::from_str(DEFAULT_PRIVATE_KEY)?;
Some(secret_key)
}
(WalletSelectionMode::Manual, Some(key), true) => {
println_warning("Signing key is provided while requesting to sign with a default signer. Using signing key");
Some(key)
Some(secret_key)
}
(WalletSelectionMode::ForcWallet, Some(key), _) => {
println_warning("Signing key is provided while requesting to sign with forc-wallet or with default signer. Using signing key");
Some(key)
}
(WalletSelectionMode::Manual, None, false) => None,
(WalletSelectionMode::Manual, Some(key), false) => Some(key),
(_, None, true) => {
// Generate a `SecretKey` to sign this transaction from a default private key used
// by fuel-core.
let secret_key = SecretKey::from_str(DEFAULT_PRIVATE_KEY)?;
Some(secret_key)
}
(WalletSelectionMode::Manual, Some(key), true) => {
println_warning("Signing key is provided while requesting to sign with a default signer. Using signing key");
Some(key)
}
}
};
// Get the address
Expand Down
1 change: 1 addition & 0 deletions forc-util/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ macro_rules! cli_examples {
#[allow(unreachable_code)]
fn [<$($description:lower _)*:snake example>] () {
let mut proc = std::process::Command::new("cargo");
proc.env("CLI_TEST", "true");
proc.arg("run");
proc.arg("--bin");
proc.arg(if stringify!($command) == "forc" {
Expand Down

0 comments on commit d29f578

Please sign in to comment.