diff --git a/Cargo.toml b/Cargo.toml index c9110afa..8468c535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" license = "Apache-2.0" keywords = ["TON", "SDK", "smart contract", "tonlabs", "solidity"] edition = "2018" -version = "0.4.0" +version = "0.5.0" [dependencies] async-trait = "0.1.42" @@ -35,7 +35,6 @@ ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git' } ton_types = { git = "https://github.com/tonlabs/ton-labs-types.git" } ton_block = { git = "https://github.com/tonlabs/ton-labs-block.git" } - [dev-dependencies] assert_cmd = "0.11" predicates = "1" diff --git a/src/call.rs b/src/call.rs index 7ab2d8c2..9ab98466 100644 --- a/src/call.rs +++ b/src/call.rs @@ -57,13 +57,13 @@ async fn prepare_message( let params = serde_json::from_str(¶ms) .map_err(|e| format!("arguments are not in json format: {}", e))?; - + let call_set = Some(CallSet { function_name: method.into(), input: Some(params), header: header.clone(), }); - + let msg = encode_message( ton, ParamsOfEncodeMessage { @@ -71,8 +71,8 @@ async fn prepare_message( address: Some(addr.to_owned()), deploy_set: None, call_set, - signer: if keys.is_some() { - Signer::Keys { keys: keys.unwrap() } + signer: if keys.is_some() { + Signer::Keys { keys: keys.unwrap() } } else { Signer::None }, @@ -81,7 +81,7 @@ async fn prepare_message( ).await .map_err(|e| format!("failed to create inbound message: {}", e))?; - Ok(EncodedMessage { + Ok(EncodedMessage { message: msg.message, message_id: msg.message_id, expire: header.and_then(|h| h.expire), @@ -121,7 +121,7 @@ fn pack_message(msg: &EncodedMessage, method: &str, is_raw: bool) -> Vec { fn unpack_message(str_msg: &str) -> Result<(EncodedMessage, String), String> { let bytes = hex::decode(str_msg) .map_err(|e| format!("couldn't unpack message: {}", e))?; - + let str_msg = std::str::from_utf8(&bytes) .map_err(|e| format!("message is corrupted: {}", e))?; @@ -141,21 +141,23 @@ fn unpack_message(str_msg: &str) -> Result<(EncodedMessage, String), String> { let address = json_msg["msg"]["address"].as_str() .ok_or(r#"couldn't find "address" key in message"#)? .to_owned(); - + let msg = EncodedMessage { message_id, message, expire, address }; Ok((msg, method)) } -fn decode_call_parameters(ton: TonClient, msg: &EncodedMessage, abi: Abi) -> Result<(String, String), String> { +async fn decode_call_parameters(ton: TonClient, msg: &EncodedMessage, abi: Abi) -> Result<(String, String), String> { let result = decode_message( ton, ParamsOfDecodeMessage { abi, message: msg.message.clone(), }, - ).map_err(|e| format!("couldn't decode message: {}", e))?; + ) + .await + .map_err(|e| format!("couldn't decode message: {}", e))?; Ok(( result.name, @@ -178,7 +180,7 @@ fn parse_integer_param(value: &str) -> Result { fn build_json_from_params(params_vec: Vec<&str>, abi: &str, method: &str) -> Result { let abi_obj = Contract::load(abi.as_bytes()).map_err(|e| format!("failed to parse ABI: {}", e))?; let functions = abi_obj.functions(); - + let func_obj = functions.get(method).unwrap(); let inputs = func_obj.input_params(); @@ -257,6 +259,8 @@ async fn send_message_and_wait( account: acc_boc, execution_options: None, abi: Some(abi.clone()), + return_updated_account: Some(true), + boc_cache: None, }, ).await .map_err(|e| format!("run failed: {:#}", e))?; @@ -264,7 +268,7 @@ async fn send_message_and_wait( } else { println!("Processing... "); let callback = |_| { - async move {} + async move {} }; let result = send_message( @@ -397,7 +401,7 @@ pub async fn call_contract_with_msg(conf: Config, str_msg: String, abi: String) let (msg, _) = unpack_message(&str_msg)?; print_encoded_message(&msg); - let params = decode_call_parameters(ton.clone(), &msg, abi.clone())?; + let params = decode_call_parameters(ton.clone(), &msg, abi.clone()).await?; println!("Calling method {} with parameters:", params.0); println!("{}", params.1); @@ -445,7 +449,7 @@ pub async fn run_get_method(conf: Config, addr: &str, method: &str, params: Opti ).await .map_err(|e| format!("run failed: {}", e.to_string()))? .output; - + println!("Succeded."); println!("Result: {}", result); Ok(()) diff --git a/src/config.rs b/src/config.rs index dd375735..4414d5b6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,7 +37,7 @@ fn default_false() -> bool { false } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct Config { #[serde(default = "default_url")] pub url: String, @@ -91,7 +91,7 @@ pub fn clear_config( wc: bool, retries: bool, timeout: bool, - depool_fee: bool, + depool_fee: bool, ) -> Result<(), String> { if url { conf.url = default_url(); @@ -153,7 +153,7 @@ pub fn set_config( wc: Option<&str>, retries: Option<&str>, timeout: Option<&str>, - depool_fee: Option<&str>, + depool_fee: Option<&str>, ) -> Result<(), String> { if let Some(s) = url { conf.url = s.to_string(); diff --git a/src/debot/interfaces/address_input.rs b/src/debot/interfaces/address_input.rs new file mode 100644 index 00000000..d00a333e --- /dev/null +++ b/src/debot/interfaces/address_input.rs @@ -0,0 +1,73 @@ +use crate::debot::term_browser::terminal_input; +use crate::helpers::load_ton_address; +use serde_json::Value; +use ton_client::abi::Abi; +use ton_client::debot::{DebotInterface, InterfaceResult}; +use super::dinterface::decode_answer_id; +use crate::config::Config; + +const ID: &'static str = "d7ed1bd8e6230871116f4522e58df0a93c5520c56f4ade23ef3d8919a984653b"; + +pub const ABI: &str = r#" +{ + "ABI version": 2, + "header": ["time"], + "functions": [ + { + "name": "select", + "inputs": [ + {"name":"answerId","type":"uint32"} + ], + "outputs": [ + {"name":"value","type":"address"} + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ] +} +"#; + +pub struct AddressInput { + conf: Config +} +impl AddressInput { + pub fn new(conf: Config) -> Self { + Self {conf} + } + fn select(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + let value = terminal_input("", |val| { + let _ = load_ton_address(val, &self.conf).map_err(|e| format!("Invalid address: {}", e))?; + Ok(()) + }); + Ok((answer_id, json!({ "value": value }))) + } +} + +#[async_trait::async_trait] +impl DebotInterface for AddressInput { + fn get_id(&self) -> String { + ID.to_string() + } + + fn get_abi(&self) -> Abi { + Abi::Json(ABI.to_owned()) + } + + async fn call(&self, func: &str, args: &Value) -> InterfaceResult { + match func { + "select" => self.select(args), + _ => Err(format!("function \"{}\" is not implemented", func)), + } + } +} diff --git a/src/debot/interfaces/dinterface.rs b/src/debot/interfaces/dinterface.rs new file mode 100644 index 00000000..b0c24799 --- /dev/null +++ b/src/debot/interfaces/dinterface.rs @@ -0,0 +1,84 @@ +use super::address_input::AddressInput; +use super::echo::Echo; +use super::stdout::Stdout; +use super::terminal::Terminal; +use super::menu::Menu; +use crate::config::Config; +use crate::helpers::TonClient; +use serde_json::Value; +use std::collections::HashMap; +use std::sync::Arc; +use ton_client::debot::{DebotInterface, DebotInterfaceExecutor}; + +pub struct SupportedInterfaces { + client: TonClient, + interfaces: HashMap>, +} + +#[async_trait::async_trait] +impl DebotInterfaceExecutor for SupportedInterfaces { + fn get_interfaces<'a>(&'a self) -> &'a HashMap> { + &self.interfaces + } + fn get_client(&self) -> TonClient { + self.client.clone() + } +} + +impl SupportedInterfaces { + pub fn new(client: TonClient, conf: &Config) -> Self { + let mut interfaces = HashMap::new(); + + let iface: Arc = Arc::new(AddressInput::new(conf.clone())); + interfaces.insert(iface.get_id(), iface); + + let iface: Arc = Arc::new(Stdout::new()); + interfaces.insert(iface.get_id(), iface); + + let iface: Arc = Arc::new(Echo::new()); + interfaces.insert(iface.get_id(), iface); + + let iface: Arc = Arc::new(Terminal::new()); + interfaces.insert(iface.get_id(), iface); + + let iface: Arc = Arc::new(Menu::new()); + interfaces.insert(iface.get_id(), iface); + + Self { client, interfaces } + } +} + +pub fn decode_answer_id(args: &Value) -> Result { + u32::from_str_radix( + args["answerId"] + .as_str() + .ok_or(format!("answer id not found in argument list"))?, + 10, + ) + .map_err(|e| format!("{}", e)) +} + +pub fn decode_arg(args: &Value, name: &str) -> Result { + args[name] + .as_str() + .ok_or(format!("\"{}\" not found", name)) + .map(|x| x.to_string()) +} + +pub fn decode_bool_arg(args: &Value, name: &str) -> Result { + args[name] + .as_bool() + .ok_or(format!("\"{}\" not found", name)) +} + +pub fn decode_string_arg(args: &Value, name: &str) -> Result { + let bytes = hex::decode(&decode_arg(args, name)?) + .map_err(|e| format!("{}", e))?; + std::str::from_utf8(&bytes) + .map_err(|e| format!("{}", e)) + .map(|x| x.to_string()) +} + +pub fn decode_prompt(args: &Value) -> Result { + decode_string_arg(args, "prompt") +} diff --git a/src/debot/interfaces/echo.rs b/src/debot/interfaces/echo.rs new file mode 100644 index 00000000..651398cf --- /dev/null +++ b/src/debot/interfaces/echo.rs @@ -0,0 +1,68 @@ +use serde_json::Value; +use ton_client::debot::{DebotInterface, InterfaceResult}; +use ton_client::abi::Abi; + +const ECHO_ID: &'static str = "f6927c0d4bdb69e1b52d27f018d156ff04152f00558042ff674f0fec32e4369d"; + +pub const ECHO_ABI: &str = r#" +{ + "ABI version": 2, + "header": ["time"], + "functions": [ + { + "name": "echo", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"request","type":"bytes"} + ], + "outputs": [ + {"name":"response","type":"bytes"} + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ] +} +"#; + +pub struct Echo {} +impl Echo { + + pub fn new() -> Self { + Self{} + } + + fn echo(&self, args: &Value) -> InterfaceResult { + let answer_id = u32::from_str_radix(args["answerId"].as_str().unwrap(), 10).unwrap(); + let request_vec = hex::decode(args["request"].as_str().unwrap()).unwrap(); + let request = std::str::from_utf8(&request_vec).unwrap(); + Ok(( answer_id, json!({ "response": hex::encode(request.as_bytes()) }) )) + } +} + +#[async_trait::async_trait] +impl DebotInterface for Echo { + fn get_id(&self) -> String { + ECHO_ID.to_string() + } + + fn get_abi(&self) -> Abi { + Abi::Json(ECHO_ABI.to_owned()) + } + + async fn call(&self, func: &str, args: &Value) -> InterfaceResult { + match func { + "echo" => self.echo(args), + _ => Err(format!("function \"{}\" is not implemented", func)), + } + } +} diff --git a/src/debot/interfaces/menu.rs b/src/debot/interfaces/menu.rs new file mode 100644 index 00000000..5074c67e --- /dev/null +++ b/src/debot/interfaces/menu.rs @@ -0,0 +1,132 @@ +use super::dinterface::{decode_string_arg}; +use crate::debot::term_browser::{action_input}; +use serde_json::Value; +use serde::{de, Deserialize, Deserializer}; +use ton_client::abi::Abi; +use ton_client::debot::{DebotInterface, InterfaceResult}; +use std::fmt::Display; +use std::str::FromStr; +use ton_client::encoding::decode_abi_number; + +const ID: &'static str = "ac1a4d3ecea232e49783df4a23a81823cdca3205dc58cd20c4db259c25605b48"; + +const ABI: &str = r#" +{ + "ABI version": 2, + "header": ["time"], + "functions": [ + { + "name": "select", + "inputs": [ + {"name":"title","type":"bytes"}, + {"name":"description","type":"bytes"}, + {"components":[{"name":"title","type":"bytes"},{"name":"description","type":"bytes"},{"name":"handlerId","type":"uint32"}],"name":"items","type":"tuple[]"} + ], + "outputs": [ + {"name":"index","type":"uint32"} + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ] +} +"#; + +#[derive(Deserialize, Default)] +#[serde(rename_all = "camelCase")] +struct MenuItem { + #[serde(deserialize_with = "from_hex_to_utf8_str")] + title: String, + #[serde(deserialize_with = "from_hex_to_utf8_str")] + description: String, + #[serde(deserialize_with = "from_abi_num")] + handler_id: u32, +} + +fn str_hex_to_utf8(s: &str) -> Option { + String::from_utf8(hex::decode(s).ok()?).ok() +} + +fn from_hex_to_utf8_str<'de, S, D>(des: D) -> Result +where + S: FromStr, + S::Err: Display, + D: Deserializer<'de> +{ + let s: String = Deserialize::deserialize(des)?; + let s = str_hex_to_utf8(&s) + .ok_or(format!("failed to convert bytes to utf8 string")).unwrap(); + S::from_str(&s).map_err(de::Error::custom) +} + +fn from_abi_num<'de, D>(des: D) -> Result +where + D: Deserializer<'de> +{ + let s: String = Deserialize::deserialize(des)?; + decode_abi_number(&s).map_err(de::Error::custom) +} + +pub struct Menu {} +impl Menu { + + pub fn new() -> Self { + Self{} + } + + fn select(&self, args: &Value) -> InterfaceResult { + let menu_items: Vec = serde_json::from_value(args["items"].clone()).unwrap(); + let title = decode_string_arg(args, "title")?; + let description = decode_string_arg(args, "description")?; + println!("{}\n{}", title, description); + for (i, menu) in menu_items.iter().enumerate() { + println!("{}) {}", i + 1, menu.title); + if menu.description != "" { + println!(" {}", menu.description); + } + } + loop { + let res = action_input(menu_items.len()); + if res.is_err() { + println!("{}", res.unwrap_err()); + continue; + } + let (n, _, _) = res.unwrap(); + let menu = menu_items.get(n - 1); + if menu.is_none() { + println!("Invalid menu. Try again."); + continue; + } + + return Ok(( menu.unwrap().handler_id, json!({ "index": n - 1 }) )); + } + + } +} + +#[async_trait::async_trait] +impl DebotInterface for Menu { + fn get_id(&self) -> String { + ID.to_string() + } + + fn get_abi(&self) -> Abi { + Abi::Json(ABI.to_owned()) + } + + async fn call(&self, func: &str, args: &Value) -> InterfaceResult { + match func { + "select" => self.select(args), + _ => Err(format!("function \"{}\" is not implemented", func)), + } + } +} diff --git a/src/debot/interfaces/mod.rs b/src/debot/interfaces/mod.rs new file mode 100644 index 00000000..dcd6b08f --- /dev/null +++ b/src/debot/interfaces/mod.rs @@ -0,0 +1,19 @@ +/* +* Copyright 2018-2020 TON DEV SOLUTIONS LTD. +* +* Licensed under the SOFTWARE EVALUATION License (the "License"); you may not use +* this file except in compliance with the License. +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific TON DEV software governing permissions and +* limitations under the License. +*/ + +pub mod dinterface; +pub mod echo; +pub mod stdout; +pub mod address_input; +pub mod terminal; +pub mod menu; \ No newline at end of file diff --git a/src/debot/interfaces/stdout.rs b/src/debot/interfaces/stdout.rs new file mode 100644 index 00000000..b1983db7 --- /dev/null +++ b/src/debot/interfaces/stdout.rs @@ -0,0 +1,63 @@ +use serde_json::Value; +use ton_client::debot::{DebotInterface, InterfaceResult}; +use ton_client::abi::Abi; + +const STDOUT_ID: &'static str = "c91dcc3fddb30485a3a07eb7c1e5e2aceaf75f4bc2678111de1f25291cdda80b"; + +pub const STDOUT_ABI: &str = r#"{ + "ABI version": 2, + "header": ["time"], + "functions": [ + { + "name": "print", + "inputs": [ + {"name":"message","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ] +}"#; + + +pub struct Stdout {} +impl Stdout { + pub fn new() -> Self { + Self {} + } + pub fn print(&self, args: &Value) -> InterfaceResult { + let text_vec = hex::decode(args["message"].as_str().unwrap()).unwrap(); + let text = std::str::from_utf8(&text_vec).unwrap(); + println!("{}", text); + Ok((0, json!({}))) + } +} + +#[async_trait::async_trait] +impl DebotInterface for Stdout { + fn get_id(&self) -> String { + STDOUT_ID.to_string() + } + + fn get_abi(&self) -> Abi { + Abi::Json(STDOUT_ABI.to_owned()) + } + + async fn call(&self, func: &str, args: &Value) -> InterfaceResult { + match func { + "print" => self.print(args), + _ => Err(format!("function \"{}\" is not implemented", func)), + } + } +} \ No newline at end of file diff --git a/src/debot/interfaces/terminal.rs b/src/debot/interfaces/terminal.rs new file mode 100644 index 00000000..04f38365 --- /dev/null +++ b/src/debot/interfaces/terminal.rs @@ -0,0 +1,186 @@ +use super::dinterface::{decode_answer_id, decode_bool_arg, decode_prompt, decode_string_arg}; +use crate::debot::term_browser::terminal_input; +use serde_json::Value; +use ton_client::abi::Abi; +use ton_client::debot::{DebotInterface, InterfaceResult}; +use crate::convert::convert_token; +use ton_client::encoding::decode_abi_bigint; +use std::io::{Read}; + +const ID: &'static str = "8796536366ee21852db56dccb60bc564598b618c865fc50c8b1ab740bba128e3"; + +const ABI: &str = r#" +{ + "ABI version": 2, + "header": ["time"], + "functions": [ + { + "name": "inputStr", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"prompt","type":"bytes"}, + {"name":"multiline","type":"bool"} + ], + "outputs": [ + {"name":"value","type":"bytes"} + ] + }, + { + "name": "inputInt", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"prompt","type":"bytes"} + ], + "outputs": [ + {"name":"value","type":"int256"} + ] + }, + { + "name": "inputUint", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"prompt","type":"bytes"} + ], + "outputs": [ + {"name":"value","type":"uint256"} + ] + }, + { + "name": "inputTons", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"prompt","type":"bytes"} + ], + "outputs": [ + {"name":"value","type":"uint128"} + ] + }, + { + "name": "inputBoolean", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"prompt","type":"bytes"} + ], + "outputs": [ + {"name":"value","type":"bool"} + ] + }, + { + "name": "print", + "inputs": [ + {"name":"answerId","type":"uint32"}, + {"name":"message","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "constructor", + "inputs": [ + ], + "outputs": [ + ] + } + ], + "data": [ + ], + "events": [ + ] +} +"#; + +pub struct Terminal {} +impl Terminal { + pub fn new() -> Self { + Self {} + } + fn input_str(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + let prompt = decode_prompt(args)?; + let multiline = decode_bool_arg(args, "multiline")?; + let mut value = String::new(); + if multiline { + println!("{}", &prompt); + println!("(Ctrl+D to exit)"); + std::io::stdin().read_to_string(&mut value) + .map_err(|e| format!("input error: {}", e))?; + println!(); + } else { + value = terminal_input(&prompt, |_val| Ok(())); + } + Ok((answer_id, json!({ "value": hex::encode(value.as_bytes()) }))) + } + + fn input_int(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + let value = terminal_input(&decode_prompt(args)?, |val| { + let _ = decode_abi_bigint(val).map_err(|e| format!("{}", e))?; + Ok(()) + }); + Ok((answer_id, json!({ "value": value }))) + } + + fn input_uint(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + let value = terminal_input(&decode_prompt(args)?, |val| { + let _ = decode_abi_bigint(val).map_err(|e| format!("{}", e))?; + Ok(()) + }); + Ok((answer_id, json!({ "value": value }))) + } + + fn input_tokens(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + let mut nanotokens = String::new(); + let _ = terminal_input(&decode_prompt(args)?, |val| { + nanotokens = convert_token(val)?; + Ok(()) + }); + Ok((answer_id, json!({ "value": nanotokens }))) + } + + fn input_boolean(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + println!("{}", decode_prompt(args)?); + let mut yes_no = false; + let _ = terminal_input("(y/n)", |val| { + yes_no = match val.as_str() { + "y" => true, + "n" => false, + _ => Err(format!("invalid enter"))?, + }; + Ok(()) + }); + Ok((answer_id, json!({ "value": yes_no }))) + } + + pub fn print(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + let message = decode_string_arg(args, "message")?; + println!("{}", message); + Ok((answer_id, json!({}))) + } +} + +#[async_trait::async_trait] +impl DebotInterface for Terminal { + fn get_id(&self) -> String { + ID.to_string() + } + + fn get_abi(&self) -> Abi { + Abi::Json(ABI.to_owned()) + } + + async fn call(&self, func: &str, args: &Value) -> InterfaceResult { + match func { + "inputStr" => self.input_str(args), + "inputInt" => self.input_int(args), + "inputUint" => self.input_uint(args), + "inputTons" => self.input_tokens(args), + "inputBoolean" => self.input_boolean(args), + "print" => self.print(args), + _ => Err(format!("function \"{}\" is not implemented", func)), + } + } +} \ No newline at end of file diff --git a/src/debot/mod.rs b/src/debot/mod.rs index d0124598..2ad43488 100644 --- a/src/debot/mod.rs +++ b/src/debot/mod.rs @@ -17,6 +17,8 @@ use term_browser::run_debot_browser; use crate::helpers::load_ton_address; pub mod term_browser; +mod interfaces; +pub use interfaces::dinterface::SupportedInterfaces; mod term_signing_box; pub fn create_debot_command<'a, 'b>() -> App<'a, 'b> { diff --git a/src/debot/term_browser.rs b/src/debot/term_browser.rs index a14f92b4..5bdf5478 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -15,13 +15,17 @@ use crate::config::Config; use crate::helpers::{create_client, load_ton_address, TonClient}; use std::io::{self, BufRead, Write}; use std::sync::{Arc, RwLock}; +use ton_client::boc::{ParamsOfParse, parse_message}; use ton_client::crypto::SigningBoxHandle; -use ton_client::debot::{BrowserCallbacks, DAction, DEngine, STATE_EXIT}; +use ton_client::debot::{DebotInterfaceExecutor, BrowserCallbacks, DAction, DEngine, STATE_EXIT}; +use std::collections::VecDeque; +use super::{SupportedInterfaces}; struct TerminalBrowser { state_id: u8, active_actions: Vec, client: TonClient, + msg_queue: VecDeque, } impl TerminalBrowser { @@ -29,7 +33,8 @@ impl TerminalBrowser { Self { state_id: 0, active_actions: vec![], - client, + client: client.clone(), + msg_queue: Default::default(), } } @@ -57,6 +62,41 @@ impl TerminalBrowser { return act.map(|a| a.clone()); } } + + async fn handle_interface_call( + client: TonClient, + msg: String, + debot: &mut DEngine, + interfaces: &SupportedInterfaces, + ) -> Result<(), String> { + + let parsed = parse_message( + client.clone(), + ParamsOfParse { boc: msg.clone() }, + ) + .await + .map_err(|e| format!("{}", e))?; + + let iface_addr = parsed.parsed["dst"] + .as_str() + .ok_or(format!("parsed message has no dst address"))?; + let wc_and_addr: Vec<_> = iface_addr.split(':').collect(); + let interface_id = wc_and_addr[1].to_string(); + + if let Some(result) = interfaces.try_execute(&msg, &interface_id).await { + let (func_id, return_args) = result?; + debug!("response: {} ({})", func_id, return_args); + let result = debot.send( + iface_addr.to_owned(), func_id, return_args.to_string() + ).await; + if let Err(e) = result { + println!("debot call failed: {}", e); + } + } + + Ok(()) + } + } struct Callbacks { @@ -82,7 +122,6 @@ impl BrowserCallbacks for Callbacks { debug!("switched to ctx {}", ctx_id); browser.state_id = ctx_id; if ctx_id == STATE_EXIT { - println!("Debot shutdown"); return; } @@ -159,8 +198,9 @@ impl BrowserCallbacks for Callbacks { Ok(()) } - async fn send(&self, _message: String) { - unimplemented!() + async fn send(&self, message: String) { + let mut browser = self.browser.write().unwrap(); + browser.msg_queue.push_back(message); } } @@ -190,7 +230,21 @@ where input_str.trim().to_owned() } -fn action_input(max: usize) -> Result<(usize, usize, Vec), String> { +pub(crate) fn terminal_input(prompt: &str, mut validator: F) -> String +where + F: FnMut(&String) -> Result<(), String> +{ + let stdio = io::stdin(); + let mut reader = stdio.lock(); + let mut writer = io::stdout(); + let mut value = input(prompt, &mut reader, &mut writer); + while let Err(e) = validator(&value) { + println!("{}. Try again.", e); + value = input(prompt, &mut reader, &mut writer); + } + value +} +pub fn action_input(max: usize) -> Result<(usize, usize, Vec), String> { let mut a_str = String::new(); let mut argc = 0; let mut argv = vec![]; @@ -222,6 +276,8 @@ pub async fn run_debot_browser( ) -> Result<(), String> { println!("Connecting to {}", config.url); let ton = create_client(&config)?; + let interfaces = SupportedInterfaces::new(ton.clone(), &config); + let browser = Arc::new(RwLock::new(TerminalBrowser::new(ton.clone()))); let callbacks = Arc::new(Callbacks::new(Arc::clone(&browser))); @@ -229,12 +285,23 @@ pub async fn run_debot_browser( debot.start().await?; loop { + let mut next_msg = browser.write().unwrap().msg_queue.pop_front(); + while let Some(msg) = next_msg { + TerminalBrowser::handle_interface_call( + ton.clone(), + msg, + &mut debot, + &interfaces, + ).await?; + next_msg = browser.write().unwrap().msg_queue.pop_front(); + } let action = browser.read().unwrap().select_action(); match action { Some(act) => debot.execute_action(&act).await?, None => break, } } + println!("Debot Browser shutdown"); Ok(()) } diff --git a/src/decode.rs b/src/decode.rs index d99f905b..529c1c03 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -21,13 +21,13 @@ use std::fmt::Write; fn match_abi_path(matches: &ArgMatches, config: &Config) -> Option { matches.value_of("ABI") .map(|s| s.to_string()) - .or(config.abi_path.clone()) + .or(config.abi_path.clone()) } pub fn create_decode_command<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("decode") .about("Decode commands.") - .setting(AppSettings::AllowLeadingHyphen) + .setting(AppSettings::AllowLeadingHyphen) .setting(AppSettings::TrailingVarArg) .setting(AppSettings::DontCollapseArgsInUsage) .subcommand(SubCommand::with_name("body") @@ -48,17 +48,17 @@ pub fn create_decode_command<'a, 'b>() -> App<'a, 'b> { .help("Path to ABI file."))) } -pub fn decode_command(m: &ArgMatches, config: Config) -> Result<(), String> { +pub async fn decode_command(m: &ArgMatches<'_>, config: Config) -> Result<(), String> { if let Some(m) = m.subcommand_matches("body") { - return decode_body_command(m, config); + return decode_body_command(m, config).await; } if let Some(m) = m.subcommand_matches("msg") { - return decode_message_command(m, config); + return decode_message_command(m, config).await; } Err("unknown command".to_owned()) } -fn decode_body_command(m: &ArgMatches, config: Config) -> Result<(), String> { +async fn decode_body_command(m: &ArgMatches<'_>, config: Config) -> Result<(), String> { let body = m.value_of("BODY"); let abi = Some( match_abi_path(m, &config) @@ -67,11 +67,11 @@ fn decode_body_command(m: &ArgMatches, config: Config) -> Result<(), String> { if !config.is_json { print_args!(m, body, abi); } - println!("{}", decode_body(body.unwrap(), &abi.unwrap(), config.is_json)?); + println!("{}", decode_body(body.unwrap(), &abi.unwrap(), config.is_json).await?); Ok(()) } -fn decode_message_command(m: &ArgMatches, config: Config) -> Result<(), String> { +async fn decode_message_command(m: &ArgMatches<'_>, config: Config) -> Result<(), String> { let msg = m.value_of("MSG"); let abi = Some( match_abi_path(m, &config) @@ -84,11 +84,11 @@ fn decode_message_command(m: &ArgMatches, config: Config) -> Result<(), String> .transpose() .map_err(|e| format!(" failed to read msg boc file: {}", e))? .unwrap(); - println!("{}", decode_message(msg, abi, config.is_json)?); + println!("{}", decode_message(msg, abi, config.is_json).await?); Ok(()) } -fn print_decoded_body(body_vec: Vec, abi: &str, is_json: bool) -> Result { +async fn print_decoded_body(body_vec: Vec, abi: &str, is_json: bool) -> Result { let ton = create_client_local()?; let mut empty_boc = vec![]; serialize_tree_of_cells(&Cell::default(), &mut empty_boc).unwrap(); @@ -97,9 +97,12 @@ fn print_decoded_body(body_vec: Vec, abi: &str, is_json: bool) -> Result res, + Err(_) => decode_msg_body(ton.clone(), abi, &body_base64, true).await?, + } + }; let output = res.value.take().unwrap(); Ok(if is_json { format!(" \"BodyCall\": {{\n \"{}\": {}\n }}", res.name, output) @@ -108,7 +111,7 @@ fn print_decoded_body(body_vec: Vec, abi: &str, is_json: bool) -> Result Result { +async fn decode_body(body: &str, abi: &str, is_json: bool) -> Result { let abi = std::fs::read_to_string(abi) .map_err(|e| format!("failed to read ABI file: {}", e))?; @@ -118,19 +121,19 @@ fn decode_body(body: &str, abi: &str, is_json: bool) -> Result { let mut result = String::new(); let s = &mut result; if is_json { writeln!(s, "{{").unwrap(); } - writeln!(s, "{}", print_decoded_body(body_vec, &abi, is_json)?).unwrap(); + writeln!(s, "{}", print_decoded_body(body_vec, &abi, is_json).await?).unwrap(); if is_json { writeln!(s, "}}").unwrap(); } Ok(result) } -fn decode_message(msg_boc: Vec, abi: Option, is_json: bool) -> Result { +async fn decode_message(msg_boc: Vec, abi: Option, is_json: bool) -> Result { let abi = abi.map(|f| std::fs::read_to_string(f)) .transpose() .map_err(|e| format!("failed to read ABI file: {}", e))?; - + let tvm_msg = ton_sdk::Contract::deserialize_message(&msg_boc[..]) .map_err(|e| format!("failed to deserialize message boc: {}", e))?; - + let mut printer = msg_printer::MsgPrinter::new(&tvm_msg, is_json); let mut result = String::new(); let s = &mut result; @@ -141,8 +144,8 @@ fn decode_message(msg_boc: Vec, abi: Option, is_json: bool) -> Resul let mut body_vec = Vec::new(); serialize_tree_of_cells(&tvm_msg.body().unwrap().into_cell(), &mut body_vec) .map_err(|e| format!("failed to serialize body: {}", e))?; - - writeln!(s, "{}", print_decoded_body(body_vec, &abi, is_json)?).unwrap(); + + writeln!(s, "{}", print_decoded_body(body_vec, &abi, is_json).await?).unwrap(); } if is_json { writeln!(s, "}}").unwrap(); } Ok(result) @@ -154,7 +157,7 @@ mod msg_printer { use ton_types::cells_serialization::serialize_tree_of_cells; use ton_types::Cell; use std::fmt::Write as FmtWrite; - + pub struct MsgPrinter<'a> { start: &'static str, off: &'static str, @@ -192,7 +195,7 @@ mod msg_printer { } result } - + fn print_msg_type(&self) -> String { match self.msg.header() { CommonMsgInfo::IntMsgInfo(_) => "internal", @@ -200,12 +203,12 @@ mod msg_printer { CommonMsgInfo::ExtOutMsgInfo(_) => "external outbound", }.to_owned() + " message" } - - + + fn json(&self, s: &mut String, name: &str, value: &T) { write!(s, "{}\"{}\": {}{}{}\n", self.off, name, self.start, value, self.end).unwrap(); } - + fn print_msg_header(&mut self) -> String { let mut result = String::new(); let s = &mut result; @@ -242,7 +245,7 @@ mod msg_printer { }; result } - + fn state_init_printer(&self, s: &mut String) { match self.msg.state_init().as_ref() { Some(x) => { @@ -260,7 +263,7 @@ mod msg_printer { }; } } - + pub fn tree_of_cells_into_base64(root_cell: Option<&Cell>) -> String { match root_cell { Some(cell) => { @@ -271,11 +274,11 @@ mod msg_printer { None => "".to_string() } } - + fn print_grams(grams: &Grams) -> String { grams.0.to_string() } - + fn print_cc(cc: &CurrencyCollection) -> String { let mut result = print_grams(&cc.grams); if !cc.other.is_empty() { @@ -295,17 +298,17 @@ mod msg_printer { mod tests { use super::*; - #[test] - fn test_decode_msg_json() { + #[tokio::test] + async fn test_decode_msg_json() { let msg_boc = std::fs::read("tests/samples/wallet.boc").unwrap(); - let out = decode_message(msg_boc, Some("tests/samples/wallet.abi.json".to_owned()), true).unwrap(); + let out = decode_message(msg_boc, Some("tests/samples/wallet.abi.json".to_owned()), true).await.unwrap(); let _ : serde_json::Value = serde_json::from_str(&out).unwrap(); } - #[test] - fn test_decode_body_json() { + #[tokio::test] + async fn test_decode_body_json() { let body = "te6ccgEBAQEARAAAgwAAALqUCTqWL8OX7JivfJrAAzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMQAAAAAAAAAAAAAAAEeGjADA=="; - let out = decode_body(body, "tests/samples/wallet.abi.json", true).unwrap(); + let out = decode_body(body, "tests/samples/wallet.abi.json", true).await.unwrap(); let _ : serde_json::Value = serde_json::from_str(&out).unwrap(); } } \ No newline at end of file diff --git a/src/deploy.rs b/src/deploy.rs index 0f8e1b1d..9d0dac87 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -18,7 +18,7 @@ use ton_client::abi::{Signer, CallSet, DeploySet, ParamsOfEncodeMessage}; pub async fn deploy_contract(conf: Config, tvc: &str, abi: &str, params: &str, keys_file: &str, wc: i32) -> Result<(), String> { let ton = create_client_verbose(&conf)?; - + let abi = std::fs::read_to_string(abi) .map_err(|e| format!("failed to read ABI file: {}", e))?; let abi = load_abi(&abi)?; @@ -27,7 +27,7 @@ pub async fn deploy_contract(conf: Config, tvc: &str, abi: &str, params: &str, k let tvc_bytes = &std::fs::read(tvc) .map_err(|e| format!("failed to read smart contract file: {}", e))?; - + let tvc_base64 = base64::encode(&tvc_bytes); let addr = calc_acc_address( @@ -42,7 +42,8 @@ pub async fn deploy_contract(conf: Config, tvc: &str, abi: &str, params: &str, k let dset = DeploySet { tvc: tvc_base64, workchain_id: Some(wc), - ..Default::default() + initial_data: None, + initial_pubkey: None, }; let params = serde_json::from_str(params) .map_err(|e| format!("function arguments is not a json: {}", e))?; diff --git a/src/depool.rs b/src/depool.rs index f36e4be1..d8a52308 100644 --- a/src/depool.rs +++ b/src/depool.rs @@ -300,16 +300,16 @@ async fn events_command(m: &ArgMatches<'_>, conf: Config, depool: &str) -> Resul } fn events_filter(addr: &str, since: u32) -> serde_json::Value { - json!({ + json!({ "src": { "eq": addr }, "msg_type": {"eq": 2 }, "created_at": {"ge": since } }) } -fn print_event(ton: TonClient, event: &serde_json::Value) { +async fn print_event(ton: TonClient, event: &serde_json::Value) { println!("event {}", event["id"].as_str().unwrap()); - + let body = event["body"].as_str().unwrap(); let result = ton_client::abi::decode_message_body( ton.clone(), @@ -318,7 +318,7 @@ fn print_event(ton: TonClient, event: &serde_json::Value) { body: body.to_owned(), is_internal: false, }, - ); + ).await; let (name, args) = if result.is_err() { ("unknown".to_owned(), "{}".to_owned()) } else { @@ -326,7 +326,7 @@ fn print_event(ton: TonClient, event: &serde_json::Value) { (result.name, serde_json::to_string(&result.value).unwrap()) }; - println!("{} {} ({})\n{}\n", + println!("{} {} ({})\n{}\n", name, event["created_at"].as_u64().unwrap(), event["created_at_string"].as_str().unwrap(), @@ -350,7 +350,7 @@ async fn get_events(conf: Config, depool: &str, since: u32) -> Result<(), String ).await.map_err(|e| format!("failed to query depool events: {}", e))?; println!("{} events found", events.result.len()); for event in &events.result { - print_event(ton.clone(), event); + print_event(ton.clone(), event).await; } println!("Done"); Ok(()) @@ -368,10 +368,10 @@ async fn wait_for_event(conf: Config, depool: &str) -> Result<(), String> { result: "id body created_at created_at_string".to_owned(), timeout: Some(conf.timeout), }, - + ).await.map_err(|e| println!("failed to query event: {}", e.to_string())); if event.is_ok() { - print_event(ton.clone(), &event.unwrap().result); + print_event(ton.clone(), &event.unwrap().result).await; } Ok(()) } @@ -383,7 +383,7 @@ async fn ordinary_stake_command( m: &ArgMatches<'_>, cmd: CommandData<'_>, ) -> Result<(), String> { - let (depool, wallet, stake, keys) = + let (depool, wallet, stake, keys) = (Some(&cmd.depool), Some(&cmd.wallet), Some(cmd.stake), Some(&cmd.keys)); print_args!(m, depool, wallet, stake, keys); add_ordinary_stake(cmd).await @@ -393,7 +393,7 @@ async fn replenish_command( m: &ArgMatches<'_>, cmd: CommandData<'_>, ) -> Result<(), String> { - let (depool, wallet, stake, keys) = + let (depool, wallet, stake, keys) = (Some(&cmd.depool), Some(&cmd.wallet), Some(cmd.stake), Some(&cmd.keys)); print_args!(m, depool, wallet, stake, keys); replenish_stake(cmd).await @@ -417,7 +417,7 @@ async fn transfer_stake_command( ) -> Result<(), String> { let dest = Some(m.value_of("DEST") .ok_or("destination address is not defined.".to_string())?); - let (depool, wallet, stake, keys) = + let (depool, wallet, stake, keys) = (Some(&cmd.depool), Some(&cmd.wallet), Some(cmd.stake), Some(&cmd.keys)); print_args!(m, depool, wallet, stake, keys, dest); transfer_stake(cmd, dest.unwrap()).await @@ -452,10 +452,10 @@ async fn exotic_stake_command( let (depool, wallet, stake, keys) = (Some(&cmd.depool), Some(&cmd.wallet), Some(cmd.stake), Some(&cmd.keys)); print_args!(m, depool, wallet, stake, keys, beneficiary, withdrawal_period, total_period); let period_checker = |v| { - if v > 0 && v <= 36500 { - Ok(v) - } else { - Err(format!("period cannot be more than 36500 days")) + if v > 0 && v <= 36500 { + Ok(v) + } else { + Err(format!("period cannot be more than 36500 days")) } }; let wperiod = u32::from_str_radix(withdrawal_period.unwrap(), 10) diff --git a/src/helpers.rs b/src/helpers.rs index 31174987..0ca2270b 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -56,11 +56,15 @@ pub fn read_keys(filename: &str) -> Result { } pub fn load_ton_address(addr: &str, conf: &Config) -> Result { - // TODO: checks - if addr.find(':').is_none() { - return Ok(format!("{}:{}", conf.wc, addr)) - } - Ok(addr.to_string()) + use std::str::FromStr; + let addr = if addr.find(':').is_none() { + format!("{}:{}", conf.wc, addr) + } else { + addr.to_owned() + }; + let _ = ton_block::MsgAddressInt::from_str(&addr) + .map_err(|e| format!("{}", e))?; + Ok(addr) } pub fn now() -> u32 { @@ -97,9 +101,10 @@ pub fn create_client(conf: &Config) -> Result { message_processing_timeout: 30000, wait_for_timeout: 30000, out_of_sync_threshold: (conf.timeout / 2), - reconnect_timeout: 1000, + max_reconnect_timeout: 1000, ..Default::default() }, + boc: Default::default(), }; let cli = ClientContext::new(cli_conf).map_err(|e| format!("failed to create tonclient: {}", e))?; @@ -136,7 +141,7 @@ pub async fn query( .map(|r| r.result) } -pub fn decode_msg_body( +pub async fn decode_msg_body( ton: TonClient, abi: &str, body: &str, @@ -151,6 +156,7 @@ pub fn decode_msg_body( is_internal, }, ) + .await .map_err(|e| format!("failed to decode body: {}", e)) } diff --git a/src/main.rs b/src/main.rs index 418d3381..0b72fe78 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,7 +96,7 @@ async fn main_internal() -> Result <(), String> { let callex_sub_command = SubCommand::with_name("callex") .about("Sends external message to contract with encoded function call (alternative syntax).") .setting(AppSettings::AllowMissingPositional) - .setting(AppSettings::AllowLeadingHyphen) + .setting(AppSettings::AllowLeadingHyphen) .setting(AppSettings::TrailingVarArg) .setting(AppSettings::DontCollapseArgsInUsage) .arg(Arg::with_name("METHOD") @@ -113,7 +113,7 @@ async fn main_internal() -> Result <(), String> { let runget_sub_command = SubCommand::with_name("runget") .about("Runs contract get-method.") - .setting(AppSettings::AllowLeadingHyphen) + .setting(AppSettings::AllowLeadingHyphen) .setting(AppSettings::TrailingVarArg) .setting(AppSettings::DontCollapseArgsInUsage) .arg(Arg::with_name("ADDRESS") @@ -147,7 +147,7 @@ async fn main_internal() -> Result <(), String> { (@subcommand tokens => (about: "Converts tokens to nanotokens.") (@arg AMOUNT: +required +takes_value "Token amount value") - ) + ) ) (@subcommand genphrase => (about: "Generates seed phrase.") @@ -375,9 +375,9 @@ async fn main_internal() -> Result <(), String> { if let Some(m) = matches.subcommand_matches("send") { return send_command(m, conf).await; } - if let Some(m) = matches.subcommand_matches("deploy") { + if let Some(m) = matches.subcommand_matches("deploy") { return deploy_command(m, conf).await; - } + } if let Some(m) = matches.subcommand_matches("config") { return config_command(m, conf, config_file); } @@ -423,7 +423,7 @@ async fn main_internal() -> Result <(), String> { return sendfile_command(m, conf).await; } if let Some(m) = matches.subcommand_matches("decode") { - return decode_command(m, conf); + return decode_command(m, conf).await; } if let Some(m) = matches.subcommand_matches("debot") { return debot_command(m, conf).await; @@ -473,7 +473,7 @@ async fn send_command(matches: &ArgMatches<'_>, config: Config) -> Result<(), St .or(config.abi_path.clone()) .ok_or("ABI file not defined. Supply it in config file or command line.".to_string())? ); - + print_args!(matches, message, abi); let abi = std::fs::read_to_string(abi.unwrap()) @@ -509,7 +509,7 @@ async fn body_command(matches: &ArgMatches<'_>, config: Config) -> Result<(), St let abi = std::fs::read_to_string(abi.unwrap()) .map_err(|e| format!("failed to read ABI file: {}", e.to_string()))?; - + let client = create_client_local()?; let body = ton_client::abi::encode_message_body( @@ -544,7 +544,7 @@ async fn call_command(matches: &ArgMatches<'_>, config: Config, call: CallType) .or(config.abi_path.clone()) .ok_or("ABI file not defined. Supply it in config file or command line.".to_string())? ); - + let keys = match call { CallType::Call | CallType::Msg => { matches.value_of("SIGN") @@ -621,10 +621,10 @@ async fn callex_command(matches: &ArgMatches<'_>, config: Config) -> Result<(), let keys = matches.value_of("SIGN") .map(|s| s.to_string()) .or(config.keys_path.clone()); - + print_args!(matches, address, method, params, abi, keys); let address = load_ton_address(address.unwrap().as_str(), &config)?; - + call_contract( config, address.as_str(), diff --git a/src/voting.rs b/src/voting.rs index 69f3c432..5444b0a6 100644 --- a/src/voting.rs +++ b/src/voting.rs @@ -26,7 +26,7 @@ pub async fn create_proposal( ) -> Result<(), String> { let payload = encode_transfer_body(text).await?; - + let params = json!({ "dest": dest, "value": 1000000, @@ -134,14 +134,16 @@ pub async fn decode_proposal( TRANSFER_WITH_COMMENT, body, true, - ).map_err(|e| format!("failed to decode proposal payload: {}", e))?; - + ) + .await + .map_err(|e| format!("failed to decode proposal payload: {}", e))?; + let comment = String::from_utf8( hex::decode( result.value.unwrap()["comment"].as_str().unwrap() ).map_err(|e| format!("failed to parse comment from transaction payload: {}", e))? ).map_err(|e| format!("failed to convert comment to string: {}", e))?; - + println!("Comment: {}", comment); return Ok(()); } diff --git a/tests/cli.rs b/tests/cli.rs index 87f806db..3454a461 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -211,7 +211,7 @@ fn test_deploy() -> Result<(), Box> { } #[test] -fn test_depool() -> Result<(), Box> { +fn test_depool_0() -> Result<(), Box> { let giver_abi_name = "tests/samples/giver.abi.json"; let giver_addr = "0:841288ed3b55d9cdafa806807f02a0ae0c169aa5edfe88a789a6482429756a94"; let depool_abi = "tests/samples/fakeDepool.abi.json"; @@ -341,7 +341,7 @@ fn test_depool_1() -> Result<(), Box> { let depool_addr = "0:cf72b41b704b7173467ffcd2c7bbc2a30d251996e3e3d848a74f9f72c8bc65e6"; let msig_addr = "0:507fc74745d5a259b9939dfbdfd97cd186d13e8a7160206f3054746c1f0518cd"; let seed_phrase = "blanket time net universe ketchup maid way poem scatter blur limit drill"; - + let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("config") .arg("--wallet") @@ -416,7 +416,7 @@ fn test_depool_2() -> Result<(), Box> { let depool_abi = "tests/samples/fakeDepool.abi.json"; let depool_addr = "0:cf72b41b704b7173467ffcd2c7bbc2a30d251996e3e3d848a74f9f72c8bc65e6"; let seed_phrase = "blanket time net universe ketchup maid way poem scatter blur limit drill"; - + let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("config") .arg("--depool_fee") @@ -484,7 +484,7 @@ fn test_depool_3() -> Result<(), Box> { let depool_abi = "tests/samples/fakeDepool.abi.json"; let depool_addr = "0:cf72b41b704b7173467ffcd2c7bbc2a30d251996e3e3d848a74f9f72c8bc65e6"; let seed_phrase = "blanket time net universe ketchup maid way poem scatter blur limit drill"; - + let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("depool") .arg("stake") @@ -610,7 +610,7 @@ fn test_depool_4() -> Result<(), Box> { let depool_abi = "tests/samples/fakeDepool.abi.json"; let depool_addr = "0:cf72b41b704b7173467ffcd2c7bbc2a30d251996e3e3d848a74f9f72c8bc65e6"; let seed_phrase = "blanket time net universe ketchup maid way poem scatter blur limit drill"; - + let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("depool") .arg("stake") @@ -635,7 +635,7 @@ fn test_depool_4() -> Result<(), Box> { .stdout(predicate::str::contains(r#"sender": "0:507fc74745d5a259b9939dfbdfd97cd186d13e8a7160206f3054746c1f0518cd"#)) .stdout(predicate::str::contains(r#"stake": "3000000000"#)) .stdout(predicate::str::contains(r#"value": "800000000"#)); - + let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("depool") .arg("stake") @@ -660,7 +660,7 @@ fn test_depool_4() -> Result<(), Box> { .stdout(predicate::str::contains(r#"sender": "0:507fc74745d5a259b9939dfbdfd97cd186d13e8a7160206f3054746c1f0518cd"#)) .stdout(predicate::str::contains(r#"stake": "4000000000"#)) .stdout(predicate::str::contains(r#"value": "800000000"#)); - + Ok(()) } @@ -669,7 +669,7 @@ fn test_depool_5() -> Result<(), Box> { let depool_abi = "tests/samples/fakeDepool.abi.json"; let depool_addr = "0:cf72b41b704b7173467ffcd2c7bbc2a30d251996e3e3d848a74f9f72c8bc65e6"; let seed_phrase = "blanket time net universe ketchup maid way poem scatter blur limit drill"; - + let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("depool") .arg("donor") @@ -692,7 +692,7 @@ fn test_depool_5() -> Result<(), Box> { cmd.assert() .success() .stdout(predicate::str::contains(r#"receiver": "0:0123456789012345012345678901234501234567890123450123456789012345"#)); - + let mut cmd = Command::cargo_bin(BIN_NAME)?; cmd.arg("depool") .arg("donor") @@ -715,7 +715,7 @@ fn test_depool_5() -> Result<(), Box> { cmd.assert() .success() .stdout(predicate::str::contains(r#"receiver": "0:0123456789012345012345678901234501234567890123450123456789012346"#)); - + Ok(()) }