diff --git a/rusk-wallet/src/bin/command.rs b/rusk-wallet/src/bin/command.rs index 1e3da7dc8c..52b1b601a7 100644 --- a/rusk-wallet/src/bin/command.rs +++ b/rusk-wallet/src/bin/command.rs @@ -27,7 +27,7 @@ use rusk_wallet::{ }; use wallet_core::BalanceInfo; -use crate::io::prompt; +use crate::io::prompt::{self}; use crate::settings::Settings; use crate::{WalletFile, WalletPath}; @@ -229,6 +229,12 @@ pub(crate) enum Command { #[arg(short = 'f', long)] fn_args: Vec, + /// If query is true then HTTP contract call is done to obtain return + /// value of function if query is false then a transaction is + /// sent for mutation of the state of the contract + #[arg(short, long)] + query: bool, + /// Max amount of gas for this transaction #[arg(short = 'l', long, default_value_t = DEFAULT_LIMIT_CALL)] gas_limit: u64, @@ -559,6 +565,7 @@ impl Command { fn_args, gas_limit, gas_price, + query, } => { let gas = Gas::new(gas_limit).with_price(gas_price); @@ -569,42 +576,52 @@ impl Command { .try_into() .map_err(|_| Error::InvalidContractId)?; - let call = - ContractCall::new(contract_id, fn_name.clone(), &fn_args) - .map_err(|_| Error::Rkyv)?; + match query { + true => { + let contract_id = hex::encode(contract_id); - let tx = match address { - Address::Shielded(_) => { - wallet.sync().await?; - wallet - .phoenix_execute( - addr_idx, - Dusk::from(0), - gas, - call.into(), - ) - .await - } - Address::Public(_) => { - wallet - .moonlight_execute( - addr_idx, - Dusk::from(0), - Dusk::from(0), - gas, - call.into(), - ) - .await - } - }?; + let http_call = wallet + .http_contract_call(contract_id, &fn_name, fn_args) + .await?; - let contract_id = hex::encode(contract_id); + Ok(RunResult::ContractCallQuery(http_call)) + } + false => { + let call = ContractCall::new( + contract_id, + fn_name.clone(), + &fn_args, + ) + .map_err(|_| Error::Rkyv)?; - let http_call = wallet - .http_contract_call(contract_id, &fn_name, fn_args) - .await?; + let tx = match address { + Address::Shielded(_) => { + wallet.sync().await?; + wallet + .phoenix_execute( + addr_idx, + Dusk::from(0), + gas, + call.into(), + ) + .await + } + Address::Public(_) => { + wallet + .moonlight_execute( + addr_idx, + Dusk::from(0), + Dusk::from(0), + gas, + call.into(), + ) + .await + } + }?; - Ok(RunResult::ContractCall(tx.hash(), http_call)) + Ok(RunResult::ContractCallTx(tx.hash())) + } + } } Self::ContractDeploy { @@ -695,7 +712,8 @@ pub enum RunResult<'a> { Profile((u8, &'a Profile)), Profiles(&'a Vec), ContractId([u8; CONTRACT_ID_BYTES]), - ContractCall(BlsScalar, Vec), + ContractCallTx(BlsScalar), + ContractCallQuery(Vec), ExportedKeys(PathBuf, PathBuf), Create(), Restore(), @@ -773,11 +791,13 @@ impl fmt::Display for RunResult<'_> { ContractId(bytes) => { write!(f, "> Contract ID: {}", hex::encode(bytes)) } - ContractCall(scalar, bytes) => { + ContractCallTx(scalar) => { let hash = hex::encode(scalar.to_bytes()); - writeln!(f, "> Contract call transaction hash: {hash}",)?; - writeln!(f, "> Http contract query: {:?}", bytes) + writeln!(f, "> Contract call transaction hash: {hash}",) + } + ContractCallQuery(bytes) => { + writeln!(f, "> Http contract query: {:?}", hex::encode(bytes)) } ExportedKeys(pk, kp) => { let pk = pk.display(); diff --git a/rusk-wallet/src/bin/interactive.rs b/rusk-wallet/src/bin/interactive.rs index 75c3b79572..7c2c84d0b2 100644 --- a/rusk-wallet/src/bin/interactive.rs +++ b/rusk-wallet/src/bin/interactive.rs @@ -94,7 +94,7 @@ pub(crate) async fn run_loop( println!("\r{}", res); match res { RunResult::Tx(hash) - | RunResult::ContractCall(hash, _) => { + | RunResult::ContractCallTx(hash) => { let tx_id = hex::encode(hash.to_bytes()); // Wait for transaction confirmation @@ -468,6 +468,32 @@ fn confirm(cmd: &Command, wallet: &Wallet) -> anyhow::Result { } prompt::ask_confirm() } + Command::ContractCall { + address, + contract_id, + fn_name, + fn_args, + query, + gas_limit, + gas_price, + } => { + println!(" > Function name {}", fn_name); + println!(" > Function arguments {}", hex::encode(fn_args)); + println!(" > Contract ID {}", hex::encode(contract_id)); + println!(" > Is HTTP query? {}", query); + + if !query { + let max_fee = gas_limit * gas_price; + + println!(" > Max fee = {} DUSK", Dusk::from(max_fee)); + + if let Some(Address::Public(_)) = address { + println!(" > ALERT: THIS IS A PUBLIC TRANSACTION"); + } + } + + prompt::ask_confirm() + } _ => Ok(true), } } diff --git a/rusk-wallet/src/bin/interactive/command_menu.rs b/rusk-wallet/src/bin/interactive/command_menu.rs index d5e5b8b945..f5d24e69a5 100644 --- a/rusk-wallet/src/bin/interactive/command_menu.rs +++ b/rusk-wallet/src/bin/interactive/command_menu.rs @@ -17,6 +17,7 @@ use rusk_wallet::{ Address, Error, Wallet, MAX_CONTRACT_INIT_ARG_SIZE, MAX_FUNCTION_NAME_SIZE, }; +use super::prompt::request_contract_call_method; use super::ProfileOp; use crate::settings::Settings; use crate::{prompt, Command, WalletFile}; @@ -296,27 +297,37 @@ pub(crate) async fn online( })) } MenuItem::ContractCall => { - let (addr, balance) = pick_transaction_model( - wallet, - profile_idx, - phoenix_spendable, - moonlight_balance, - )?; - - if check_min_gas_balance( - balance, - DEFAULT_LIMIT_CALL, - "a contract call", - ) - .is_err() - { - return Ok(ProfileOp::Stay); - } - let mempool_gas_prices = wallet.get_mempool_gas_prices().await?; + let mut address = None; + + let query = match request_contract_call_method()? { + prompt::ContractCall::Query => true, + prompt::ContractCall::Transaction => { + let (addr, balance) = pick_transaction_model( + wallet, + profile_idx, + phoenix_spendable, + moonlight_balance, + )?; + + address = Some(addr); + + if check_min_gas_balance( + balance, + DEFAULT_LIMIT_CALL, + "a contract call", + ) + .is_err() + { + return Ok(ProfileOp::Stay); + } + + false + } + }; ProfileOp::Run(Box::new(Command::ContractCall { - address: Some(addr), + address, contract_id: prompt::request_bytes("contract id")?, fn_name: prompt::request_str( "function name to call", @@ -330,6 +341,7 @@ pub(crate) async fn online( DEFAULT_PRICE, mempool_gas_prices, )?, + query, })) } MenuItem::History => { diff --git a/rusk-wallet/src/bin/io/prompt.rs b/rusk-wallet/src/bin/io/prompt.rs index 7d941b5f41..7b98beb44c 100644 --- a/rusk-wallet/src/bin/io/prompt.rs +++ b/rusk-wallet/src/bin/io/prompt.rs @@ -354,6 +354,32 @@ pub(crate) fn request_transaction_model() -> anyhow::Result { ) } +pub enum ContractCall { + /// Call by http query (return values) + Query, + /// Call by a transaction (mutating state) + Transaction, +} + +impl Display for ContractCall { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + ContractCall::Query => write!(f, "By HTTP Query"), + ContractCall::Transaction => write!(f, "By Transaction"), + } + } +} + +/// Request transaction model to use +pub(crate) fn request_contract_call_method() -> anyhow::Result { + let choices = vec![ContractCall::Query, ContractCall::Transaction]; + + Ok( + Select::new("Please specify the contract call method to use", choices) + .prompt()?, + ) +} + /// Request transaction model to use pub(crate) fn request_address( current_idx: u8, @@ -371,7 +397,8 @@ pub(crate) fn request_address( pub(crate) fn request_contract_code() -> anyhow::Result { let validator = |path_str: &str| { let path = PathBuf::from(path_str); - if path.extension().map_or(false, |ext| ext == "wasm") { + if path.extension().map_or(false, |ext| ext == "wasm") && path.exists() + { Ok(Validation::Valid) } else { Ok(Validation::Invalid("Not a valid directory".into())) diff --git a/rusk-wallet/src/bin/main.rs b/rusk-wallet/src/bin/main.rs index 9863358d56..0dc8e69dd2 100644 --- a/rusk-wallet/src/bin/main.rs +++ b/rusk-wallet/src/bin/main.rs @@ -401,7 +401,7 @@ async fn exec() -> anyhow::Result<()> { RunResult::ContractId(id) => { println!("Contract ID: {:?}", id); } - RunResult::ContractCall(scalar, result) => { + RunResult::ContractCallTx(scalar) => { let tx_id = hex::encode(scalar.to_bytes()); // Wait for transaction confirmation from network @@ -410,7 +410,10 @@ async fn exec() -> anyhow::Result<()> { println!("{tx_id}"); - println!("HTTP call result: {:?}", result); + + } + RunResult::ContractCallQuery(bytes) => { + println!("HTTP call result: {:?}", bytes); } RunResult::Settings() => {} RunResult::Create() | RunResult::Restore() => {}