diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a71d7b0..bd229d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. +## Version: 0.26.6 + +### New + + ## Version: 0.26.4 ### New diff --git a/Cargo.lock b/Cargo.lock index a156865a..3fb25087 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2812,7 +2812,7 @@ dependencies = [ [[package]] name = "tonos-cli" -version = "0.26.4" +version = "0.26.6" dependencies = [ "assert_cmd", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 4e7122b5..8d20b9de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ license = 'Apache-2.0' name = 'tonos-cli' readme = 'README.md' repository = 'https://github.com/tonlabs/tonos-cli' -version = '0.26.5' +version = '0.26.6' [dependencies] async-trait = '0.1.42' diff --git a/src/account.rs b/src/account.rs index 46896f9d..a0340183 100644 --- a/src/account.rs +++ b/src/account.rs @@ -17,6 +17,7 @@ use ton_client::error::ClientError; use ton_client::net::{ParamsOfQueryCollection, query_collection, ResultOfSubscription, ParamsOfSubscribeCollection}; use ton_client::utils::{calc_storage_fee, ParamsOfCalcStorageFee}; use ton_block::{Account, Deserializable, Serializable}; +use crate::decode::print_account_data; const ACCOUNT_FIELDS: &str = r#" id @@ -68,7 +69,22 @@ async fn query_accounts(config: &Config, addresses: Vec, fields: &str) - Ok(res) } -pub async fn get_account(config: &Config, addresses: Vec, dumpfile: Option<&str>, dumpboc: Option<&str>) -> Result<(), String> { +pub async fn get_account(config: &Config, addresses: Vec, dumptvc: Option<&str>, dumpboc: Option<&str>, is_boc: bool) -> Result<(), String> { + if is_boc { + let mut accounts = vec![]; + for path in addresses { + let account = Account::construct_from_file(&path) + .map_err(|e| format!(" failed to load account from the boc file {}: {}", path, e))?; + accounts.push(account); + } + if !config.is_json { + println!("\nSucceeded.\n"); + } + for account in accounts { + print_account_data(&account, dumptvc, config, false).await?; + } + return Ok(()); + } let accounts = query_accounts(&config, addresses.clone(), ACCOUNT_FIELDS).await?; if !config.is_json { println!("Succeeded."); @@ -185,22 +201,22 @@ pub async fn get_account(config: &Config, addresses: Vec, dumpfile: Opti } } - if dumpfile.is_some() || dumpboc.is_some() && addresses.len() == 1 && accounts.len() == 1 { + if dumptvc.is_some() || dumpboc.is_some() && addresses.len() == 1 && accounts.len() == 1 { let acc = &accounts[0]; let boc = acc["boc"].as_str() .ok_or("failed to get boc of the account".to_owned())?; let account = Account::construct_from_base64(boc) .map_err(|e| format!("failed to load account from the boc: {}", e))?; - if dumpfile.is_some() { + if dumptvc.is_some() { if account.state_init().is_some() { account.state_init().unwrap() - .write_to_file(dumpfile.unwrap()) - .map_err(|e| format!("failed to write data to the file {}: {}", dumpfile.unwrap(), e))?; + .write_to_file(dumptvc.unwrap()) + .map_err(|e| format!("failed to write data to the file {}: {}", dumptvc.unwrap(), e))?; } else { return Err("account doesn't contain state init.".to_owned()); } if !config.is_json { - println!("Saved contract to file {}", &dumpfile.unwrap()); + println!("Saved contract to file {}", &dumptvc.unwrap()); } } if dumpboc.is_some() { diff --git a/src/decode.rs b/src/decode.rs index 8033e312..d8c54772 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -145,10 +145,10 @@ async fn decode_account_from_boc(m: &ArgMatches<'_>, config: &Config) -> Result< let account = Account::construct_from_file(boc.unwrap()) .map_err(|e| format!(" failed to load account from the boc file: {}", e))?; - print_account_data(&account, tvc_path, config).await + print_account_data(&account, tvc_path, config, true).await } -pub async fn print_account_data(account: &Account, tvc_path: Option<&str>, config: &Config) -> Result<(), String> { +pub async fn print_account_data(account: &Account, tvc_path: Option<&str>, config: &Config, decode_stateinit: bool) -> Result<(), String> { if account.is_none() { if !config.is_json { println!("\nAccount is None"); @@ -179,7 +179,7 @@ pub async fn print_account_data(account: &Account, tvc_path: Option<&str>, confi }; let trans_lt = account.last_tr_time() - .map_or("Undefined".to_owned(), |v| format!("{}", v)); + .map_or("Undefined".to_owned(), |v| format!("{:#x}", v)); let paid = format!("{}", account.last_paid()); let (si, code_hash) = match state_init { @@ -198,6 +198,9 @@ pub async fn print_account_data(account: &Account, tvc_path: Option<&str>, confi _ => ("Undefined".to_owned(), None) }; + let data = tree_of_cells_into_base64(account.get_data().as_ref())?; + let data = hex::encode(base64::decode(&data) + .map_err(|e| format!("Failed to decode base64: {}", e))?); print_account( &config, Some(state), @@ -205,9 +208,9 @@ pub async fn print_account_data(account: &Account, tvc_path: Option<&str>, confi Some(balance), Some(paid), Some(trans_lt), - None, + Some(data), code_hash, - Some(si), + if decode_stateinit { Some(si) } else { None }, ); if tvc_path.is_some() && state_init.is_some() { diff --git a/src/main.rs b/src/main.rs index 63264513..27909193 100644 --- a/src/main.rs +++ b/src/main.rs @@ -608,10 +608,11 @@ async fn main_internal() -> Result <(), String> { .about("Obtains and prints account information.") .version(&*version_string) .author("TONLabs") + .arg(boc_flag.clone()) .arg(Arg::with_name("ADDRESS") .required(true) .takes_value(true) - .help("List of addresses.") + .help("List of addresses or file paths (if flag --boc is used).") .multiple(true)) .arg(Arg::with_name("DUMPTVC") .long("--dumptvc") @@ -624,6 +625,7 @@ async fn main_internal() -> Result <(), String> { .short("-b") .takes_value(true) .conflicts_with("DUMPTVC") + .conflicts_with("BOC") .help("Dumps the whole account state boc to the specified file. Works only if one address was given. Use 'tonos-cli dump account` to dump several accounts.")); let account_wait_cmd = SubCommand::with_name("account-wait") @@ -1498,10 +1500,22 @@ async fn genaddr_command(matches: &ArgMatches<'_>, config: &Config) -> Result<() async fn account_command(matches: &ArgMatches<'_>, config: &Config) -> Result<(), String> { let addresses_list = matches.values_of("ADDRESS").unwrap().collect::>(); + if addresses_list.len() > 1 && + (matches.is_present("DUMPTVC") || matches.is_present("DUMPTVC")) { + return Err("`DUMPTVC` and `DUMPBOC` options are not applicable to a list of addresses.".to_string()); + } + let is_boc = matches.is_present("BOC"); let mut formatted_list = vec![]; for address in addresses_list.iter() { - let formatted = load_ton_address(address, &config)?; - formatted_list.push(formatted); + if !is_boc { + let formatted = load_ton_address(address, &config)?; + formatted_list.push(formatted); + } else { + if !std::path::Path::new(address).exists() { + return Err(format!("File {} doesn't exist.", address)); + } + formatted_list.push(address.to_string()); + } } let tvcname = matches.value_of("DUMPTVC"); let bocname = matches.value_of("DUMPBOC"); @@ -1509,7 +1523,7 @@ async fn account_command(matches: &ArgMatches<'_>, config: &Config) -> Result<() if !config.is_json { print_args!(addresses); } - get_account(&config, formatted_list, tvcname, bocname).await + get_account(&config, formatted_list, tvcname, bocname, is_boc).await } async fn dump_accounts_command(matches: &ArgMatches<'_>, config: &Config) -> Result<(), String> { diff --git a/tests/cli.rs b/tests/cli.rs index 8b52f481..d5bd71b3 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -43,6 +43,7 @@ fn set_config(config: &str, argument: &str) -> Result<(), Box Result> { let mut cmd = Command::cargo_bin(BIN_NAME)?; let out = cmd.arg("genpubkey") @@ -826,6 +827,19 @@ fn test_account_command() -> Result<(), Box> { cmd.assert() .success() .stdout(predicate::str::contains("Account not found")); + + let mut cmd = Command::cargo_bin(BIN_NAME)?; + cmd.arg("account") + .arg("--boc") + .arg("tests/account.boc"); + cmd.assert() + .success() + .stdout(predicate::str::contains("acc_type: Active")) + .stdout(predicate::str::contains("balance:")) + .stdout(predicate::str::contains("last_paid:")) + .stdout(predicate::str::contains("last_trans_lt:")) + .stdout(predicate::str::contains("data(boc):")); + Ok(()) }