From 67ad0077b731db392baa631eeca06d43ede32a44 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 22 Jan 2021 15:33:12 +0300 Subject: [PATCH 01/15] wip: support for DInterfaces --- Cargo.toml | 2 ++ src/debot/interfaces/dinterface.rs | 6 ++++ src/debot/interfaces/echo.rs | 50 ++++++++++++++++++++++++++ src/debot/interfaces/mod.rs | 15 ++++++++ src/debot/mod.rs | 3 ++ src/debot/term_browser.rs | 56 ++++++++++++++++++++++++++++-- 6 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 src/debot/interfaces/dinterface.rs create mode 100644 src/debot/interfaces/echo.rs create mode 100644 src/debot/interfaces/mod.rs diff --git a/Cargo.toml b/Cargo.toml index c9110afa..34f8cd0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,8 @@ 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" } +[patch."https://github.com/tonlabs/TON-SDK.git"] +ton_client = { path = "../TON-SDK/ton_client" } [dev-dependencies] assert_cmd = "0.11" diff --git a/src/debot/interfaces/dinterface.rs b/src/debot/interfaces/dinterface.rs new file mode 100644 index 00000000..316fcc70 --- /dev/null +++ b/src/debot/interfaces/dinterface.rs @@ -0,0 +1,6 @@ + +pub const SUPPORTED_INTERFACES: &[&str] = &["f6927c0d4bdb69e1b52d27f018d156ff04152f00558042ff674f0fec32e4369d"]; + +trait DebotInterface { + +} \ No newline at end of file diff --git a/src/debot/interfaces/echo.rs b/src/debot/interfaces/echo.rs new file mode 100644 index 00000000..8b0f31d1 --- /dev/null +++ b/src/debot/interfaces/echo.rs @@ -0,0 +1,50 @@ +use serde_json::Value; + +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 { + fn echo(answer_id: u32, request: &str) -> (u32, Value) { + ( answer_id, json!({ "response": hex::encode(request.as_bytes()) }) ) + } + + pub fn call(func: &str, args: &Value) -> (u32, Value) { + match func { + "echo" => { + 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(); + Self::echo(answer_id, request) + }, + _ => panic!("interface function not found"), + } + } +} diff --git a/src/debot/interfaces/mod.rs b/src/debot/interfaces/mod.rs new file mode 100644 index 00000000..c9186539 --- /dev/null +++ b/src/debot/interfaces/mod.rs @@ -0,0 +1,15 @@ +/* +* 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; \ No newline at end of file diff --git a/src/debot/mod.rs b/src/debot/mod.rs index d0124598..77c5d1d6 100644 --- a/src/debot/mod.rs +++ b/src/debot/mod.rs @@ -17,6 +17,9 @@ use term_browser::run_debot_browser; use crate::helpers::load_ton_address; pub mod term_browser; +mod interfaces; +pub use interfaces::dinterface::{SUPPORTED_INTERFACES}; +pub use interfaces::echo::{Echo, ECHO_ABI}; 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..c82752da 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -15,13 +15,18 @@ 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::abi::{Abi, ParamsOfDecodeMessageBody, decode_message_body}; +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::{DEBOT_WC, BrowserCallbacks, DAction, DEngine, STATE_EXIT}; +use std::collections::VecDeque; +use super::{SUPPORTED_INTERFACES, ECHO_ABI, Echo}; struct TerminalBrowser { state_id: u8, active_actions: Vec, client: TonClient, + msg_queue: VecDeque, } impl TerminalBrowser { @@ -30,6 +35,7 @@ impl TerminalBrowser { state_id: 0, active_actions: vec![], client, + msg_queue: Default::default(), } } @@ -57,6 +63,48 @@ impl TerminalBrowser { return act.map(|a| a.clone()); } } + + async fn handle_interface_calls(&mut self, debot: &mut DEngine) -> Result<(), String> { + for msg in self.msg_queue.drain(0..) { + let parsed = parse_message( + self.client.clone(), + ParamsOfParse { boc: msg.clone() }, + ).map_err(|e| format!("{}", e))?; + let body = parsed.parsed["body"].as_str().unwrap().to_owned(); + let iface_addr = parsed.parsed["dst"].as_str().unwrap(); + let wc_and_addr: Vec<_> = iface_addr.split(':').collect(); + let interface_id = wc_and_addr[1]; + let wc = i8::from_str_radix(wc_and_addr[0], 10).unwrap(); + if wc != DEBOT_WC { + println!("invalid interface workchain id: {}", wc); + continue; + } + if !SUPPORTED_INTERFACES.contains(&interface_id) { + println!("DInterface {} not supported", interface_id); + continue; + } + let decoded = decode_message_body( + self.client.clone(), + ParamsOfDecodeMessageBody { + abi: Abi::Json(ECHO_ABI.to_owned()), + body, + is_internal: true, + }, + ).map_err(|e| format!(" failed to decode msg for interface: {}", e))?; + debug!("call for interface id {}", interface_id); + debug!("request: {} ({})", decoded.name, decoded.value.as_ref().unwrap()); + let (func_id, return_args) = Echo::call(&decoded.name, &decoded.value.unwrap()); + 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 { + debug!("debot.send failed: {}", e); + println!("debot call failed: {}", e); + } + } + Ok(()) + } } struct Callbacks { @@ -159,8 +207,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); } } @@ -229,6 +278,7 @@ pub async fn run_debot_browser( debot.start().await?; loop { + browser.write().unwrap().handle_interface_calls(&mut debot).await?; let action = browser.read().unwrap().select_action(); match action { Some(act) => debot.execute_action(&act).await?, From e5224e862765a37a3161e5b4b9874d114c6e8395 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 22 Jan 2021 19:58:44 +0300 Subject: [PATCH 02/15] support echo and stdout ifaces --- src/debot/interfaces/dinterface.rs | 5 +- src/debot/interfaces/mod.rs | 3 +- src/debot/interfaces/stdout.rs | 46 ++++++++++++++ src/debot/mod.rs | 1 + src/debot/term_browser.rs | 96 +++++++++++++++++++----------- 5 files changed, 115 insertions(+), 36 deletions(-) create mode 100644 src/debot/interfaces/stdout.rs diff --git a/src/debot/interfaces/dinterface.rs b/src/debot/interfaces/dinterface.rs index 316fcc70..de9c6b5e 100644 --- a/src/debot/interfaces/dinterface.rs +++ b/src/debot/interfaces/dinterface.rs @@ -1,5 +1,8 @@ -pub const SUPPORTED_INTERFACES: &[&str] = &["f6927c0d4bdb69e1b52d27f018d156ff04152f00558042ff674f0fec32e4369d"]; +pub const SUPPORTED_INTERFACES: &[&str] = &[ + "f6927c0d4bdb69e1b52d27f018d156ff04152f00558042ff674f0fec32e4369d", + "c91dcc3fddb30485a3a07eb7c1e5e2aceaf75f4bc2678111de1f25291cdda80b" +]; trait DebotInterface { diff --git a/src/debot/interfaces/mod.rs b/src/debot/interfaces/mod.rs index c9186539..9a18d857 100644 --- a/src/debot/interfaces/mod.rs +++ b/src/debot/interfaces/mod.rs @@ -12,4 +12,5 @@ */ pub mod dinterface; -pub mod echo; \ No newline at end of file +pub mod echo; +pub mod stdout; \ 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..a132cf15 --- /dev/null +++ b/src/debot/interfaces/stdout.rs @@ -0,0 +1,46 @@ +use serde_json::Value; + +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 { + fn print(message: &str) { + println!("{}", message); + } + + pub fn call(func: &str, args: &Value) { + match func { + "print" => { + let text_vec = hex::decode(args["message"].as_str().unwrap()).unwrap(); + let text = std::str::from_utf8(&text_vec).unwrap(); + Self::print(text); + }, + _ => panic!("interface function not found"), + } + } +} diff --git a/src/debot/mod.rs b/src/debot/mod.rs index 77c5d1d6..cf3108d9 100644 --- a/src/debot/mod.rs +++ b/src/debot/mod.rs @@ -20,6 +20,7 @@ pub mod term_browser; mod interfaces; pub use interfaces::dinterface::{SUPPORTED_INTERFACES}; pub use interfaces::echo::{Echo, ECHO_ABI}; +pub use interfaces::stdout::{Stdout, STDOUT_ABI}; 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 c82752da..3b58db5f 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -20,7 +20,7 @@ use ton_client::boc::{ParamsOfParse, parse_message}; use ton_client::crypto::SigningBoxHandle; use ton_client::debot::{DEBOT_WC, BrowserCallbacks, DAction, DEngine, STATE_EXIT}; use std::collections::VecDeque; -use super::{SUPPORTED_INTERFACES, ECHO_ABI, Echo}; +use super::{SUPPORTED_INTERFACES, ECHO_ABI, STDOUT_ABI, Echo, Stdout}; struct TerminalBrowser { state_id: u8, @@ -64,47 +64,71 @@ impl TerminalBrowser { } } - async fn handle_interface_calls(&mut self, debot: &mut DEngine) -> Result<(), String> { - for msg in self.msg_queue.drain(0..) { - let parsed = parse_message( - self.client.clone(), - ParamsOfParse { boc: msg.clone() }, - ).map_err(|e| format!("{}", e))?; - let body = parsed.parsed["body"].as_str().unwrap().to_owned(); - let iface_addr = parsed.parsed["dst"].as_str().unwrap(); - let wc_and_addr: Vec<_> = iface_addr.split(':').collect(); - let interface_id = wc_and_addr[1]; - let wc = i8::from_str_radix(wc_and_addr[0], 10).unwrap(); - if wc != DEBOT_WC { - println!("invalid interface workchain id: {}", wc); - continue; - } - if !SUPPORTED_INTERFACES.contains(&interface_id) { - println!("DInterface {} not supported", interface_id); - continue; - } - let decoded = decode_message_body( - self.client.clone(), - ParamsOfDecodeMessageBody { - abi: Abi::Json(ECHO_ABI.to_owned()), - body, - is_internal: true, - }, - ).map_err(|e| format!(" failed to decode msg for interface: {}", e))?; - debug!("call for interface id {}", interface_id); - debug!("request: {} ({})", decoded.name, decoded.value.as_ref().unwrap()); - let (func_id, return_args) = Echo::call(&decoded.name, &decoded.value.unwrap()); + async fn handle_interface_calls( + client: TonClient, + msg: String, + debot: &mut DEngine + ) -> Result<(), String> { + + let parsed = parse_message( + client.clone(), + ParamsOfParse { boc: msg }, + ).map_err(|e| format!("{}", e))?; + + let body = parsed.parsed["body"] + .as_str() + .ok_or(format!("parsed message has no body"))? + .to_owned(); + 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]; + let wc = i8::from_str_radix(wc_and_addr[0], 10) + .map_err(|e| format!("interface dst address has invalid workchain id {}", e))?; + + if wc != DEBOT_WC { + Err(format!("invalid interface workchain id {}", wc))?; + } + if !SUPPORTED_INTERFACES.contains(&interface_id) { + Err(format!("interface {} not supported", interface_id))?; + } + debug!("call for interface id {}", interface_id); + + let abi = if interface_id == SUPPORTED_INTERFACES[0] { + Abi::Json(ECHO_ABI.to_owned()) + } else if interface_id == SUPPORTED_INTERFACES[1] { + Abi::Json(STDOUT_ABI.to_owned()) + } else { + return Err(format!("unknown interface")); + }; + let decoded = decode_message_body( + client.clone(), + ParamsOfDecodeMessageBody { + abi, + body, + is_internal: true, + }, + ).map_err(|e| format!(" failed to decode message body: {}", e))?; + + debug!("request: {} ({})", decoded.name, decoded.value.as_ref().unwrap()); + + if interface_id == SUPPORTED_INTERFACES[0] { + let (func_id, return_args) = Echo::call(&decoded.name, decoded.value.as_ref().unwrap()); 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 { - debug!("debot.send failed: {}", e); println!("debot call failed: {}", e); } } + if interface_id == SUPPORTED_INTERFACES[1] { + Stdout::call(&decoded.name, decoded.value.as_ref().unwrap()); + } Ok(()) } + } struct Callbacks { @@ -130,7 +154,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; } @@ -278,13 +301,18 @@ pub async fn run_debot_browser( debot.start().await?; loop { - browser.write().unwrap().handle_interface_calls(&mut debot).await?; + let mut next_msg = browser.write().unwrap().msg_queue.pop_front(); + while let Some(msg) = next_msg { + TerminalBrowser::handle_interface_calls(ton.clone(), msg, &mut debot).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(()) } From 4a765d20f944d7047a894b604c341bfc96f18926 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 29 Jan 2021 15:35:44 +0300 Subject: [PATCH 03/15] Switch cli to debot dev sdk branch --- Cargo.toml | 8 ++++---- src/deploy.rs | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 34f8cd0b..509c436a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,13 +30,13 @@ log = {version = "0.4.11", features = ["std"] } tokio = { version = "0.2", features = ["full"], default-features = false } ton_abi = { git = "https://github.com/tonlabs/ton-labs-abi.git" } -ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git' } -ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git' } +ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'builtin-dinterfaces' } +ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'builtin-dinterfaces' } ton_types = { git = "https://github.com/tonlabs/ton-labs-types.git" } ton_block = { git = "https://github.com/tonlabs/ton-labs-block.git" } -[patch."https://github.com/tonlabs/TON-SDK.git"] -ton_client = { path = "../TON-SDK/ton_client" } +#[patch."https://github.com/tonlabs/TON-SDK.git"] +#ton_client = { path = "../TON-SDK/ton_client" } [dev-dependencies] assert_cmd = "0.11" 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))?; From e827da834be894f3035334286bfddb1f273cb2a9 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 29 Jan 2021 16:54:15 +0300 Subject: [PATCH 04/15] Refactor supported interfaces --- src/debot/interfaces/dinterface.rs | 45 ++++++++++++++++++--- src/debot/interfaces/echo.rs | 38 +++++++++++++----- src/debot/interfaces/stdout.rs | 37 +++++++++++++----- src/debot/mod.rs | 4 +- src/debot/term_browser.rs | 63 +++++++++--------------------- 5 files changed, 113 insertions(+), 74 deletions(-) diff --git a/src/debot/interfaces/dinterface.rs b/src/debot/interfaces/dinterface.rs index de9c6b5e..242a4b67 100644 --- a/src/debot/interfaces/dinterface.rs +++ b/src/debot/interfaces/dinterface.rs @@ -1,9 +1,42 @@ +use ton_client::debot::{DebotInterfaceExecutor, DebotInterface}; +use std::collections::HashMap; +use crate::helpers::{TonClient}; +use std::sync::Arc; +use super::echo::Echo; +use super::stdout::Stdout; -pub const SUPPORTED_INTERFACES: &[&str] = &[ - "f6927c0d4bdb69e1b52d27f018d156ff04152f00558042ff674f0fec32e4369d", - "c91dcc3fddb30485a3a07eb7c1e5e2aceaf75f4bc2678111de1f25291cdda80b" -]; +pub struct SupportedInterfaces { + client: TonClient, + interfaces: HashMap>, +} -trait DebotInterface { - +#[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) -> Self { + let mut interfaces = HashMap::new(); + + /*let iface: Arc = + Arc::new(AddressInputInterface::new()); + 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); + + Self { client, interfaces } + } } \ No newline at end of file diff --git a/src/debot/interfaces/echo.rs b/src/debot/interfaces/echo.rs index 8b0f31d1..651398cf 100644 --- a/src/debot/interfaces/echo.rs +++ b/src/debot/interfaces/echo.rs @@ -1,4 +1,8 @@ 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#" { @@ -32,19 +36,33 @@ pub const ECHO_ABI: &str = r#" pub struct Echo {} impl Echo { - fn echo(answer_id: u32, request: &str) -> (u32, Value) { - ( answer_id, json!({ "response": hex::encode(request.as_bytes()) }) ) + + 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()) } - pub fn call(func: &str, args: &Value) -> (u32, Value) { + async fn call(&self, func: &str, args: &Value) -> InterfaceResult { match func { - "echo" => { - 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(); - Self::echo(answer_id, request) - }, - _ => panic!("interface function not found"), + "echo" => self.echo(args), + _ => Err(format!("function \"{}\" is not implemented", func)), } } } diff --git a/src/debot/interfaces/stdout.rs b/src/debot/interfaces/stdout.rs index a132cf15..b1983db7 100644 --- a/src/debot/interfaces/stdout.rs +++ b/src/debot/interfaces/stdout.rs @@ -1,4 +1,8 @@ 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, @@ -29,18 +33,31 @@ pub const STDOUT_ABI: &str = r#"{ pub struct Stdout {} impl Stdout { - fn print(message: &str) { - println!("{}", message); + 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() } - pub fn call(func: &str, args: &Value) { + fn get_abi(&self) -> Abi { + Abi::Json(STDOUT_ABI.to_owned()) + } + + async fn call(&self, func: &str, args: &Value) -> InterfaceResult { match func { - "print" => { - let text_vec = hex::decode(args["message"].as_str().unwrap()).unwrap(); - let text = std::str::from_utf8(&text_vec).unwrap(); - Self::print(text); - }, - _ => panic!("interface function not found"), + "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 cf3108d9..2ad43488 100644 --- a/src/debot/mod.rs +++ b/src/debot/mod.rs @@ -18,9 +18,7 @@ use crate::helpers::load_ton_address; pub mod term_browser; mod interfaces; -pub use interfaces::dinterface::{SUPPORTED_INTERFACES}; -pub use interfaces::echo::{Echo, ECHO_ABI}; -pub use interfaces::stdout::{Stdout, STDOUT_ABI}; +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 3b58db5f..c325678d 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -15,12 +15,11 @@ 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::abi::{Abi, ParamsOfDecodeMessageBody, decode_message_body}; use ton_client::boc::{ParamsOfParse, parse_message}; use ton_client::crypto::SigningBoxHandle; -use ton_client::debot::{DEBOT_WC, BrowserCallbacks, DAction, DEngine, STATE_EXIT}; +use ton_client::debot::{DebotInterfaceExecutor, BrowserCallbacks, DAction, DEngine, STATE_EXIT}; use std::collections::VecDeque; -use super::{SUPPORTED_INTERFACES, ECHO_ABI, STDOUT_ABI, Echo, Stdout}; +use super::{SupportedInterfaces}; struct TerminalBrowser { state_id: u8, @@ -34,7 +33,7 @@ impl TerminalBrowser { Self { state_id: 0, active_actions: vec![], - client, + client: client.clone(), msg_queue: Default::default(), } } @@ -67,54 +66,23 @@ impl TerminalBrowser { async fn handle_interface_calls( client: TonClient, msg: String, - debot: &mut DEngine + debot: &mut DEngine, + interfaces: &SupportedInterfaces, ) -> Result<(), String> { let parsed = parse_message( client.clone(), - ParamsOfParse { boc: msg }, + ParamsOfParse { boc: msg.clone() }, ).map_err(|e| format!("{}", e))?; - let body = parsed.parsed["body"] - .as_str() - .ok_or(format!("parsed message has no body"))? - .to_owned(); 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]; - let wc = i8::from_str_radix(wc_and_addr[0], 10) - .map_err(|e| format!("interface dst address has invalid workchain id {}", e))?; + let interface_id = wc_and_addr[1].to_string(); - if wc != DEBOT_WC { - Err(format!("invalid interface workchain id {}", wc))?; - } - if !SUPPORTED_INTERFACES.contains(&interface_id) { - Err(format!("interface {} not supported", interface_id))?; - } - debug!("call for interface id {}", interface_id); - - let abi = if interface_id == SUPPORTED_INTERFACES[0] { - Abi::Json(ECHO_ABI.to_owned()) - } else if interface_id == SUPPORTED_INTERFACES[1] { - Abi::Json(STDOUT_ABI.to_owned()) - } else { - return Err(format!("unknown interface")); - }; - let decoded = decode_message_body( - client.clone(), - ParamsOfDecodeMessageBody { - abi, - body, - is_internal: true, - }, - ).map_err(|e| format!(" failed to decode message body: {}", e))?; - - debug!("request: {} ({})", decoded.name, decoded.value.as_ref().unwrap()); - - if interface_id == SUPPORTED_INTERFACES[0] { - let (func_id, return_args) = Echo::call(&decoded.name, decoded.value.as_ref().unwrap()); + 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() @@ -123,9 +91,7 @@ impl TerminalBrowser { println!("debot call failed: {}", e); } } - if interface_id == SUPPORTED_INTERFACES[1] { - Stdout::call(&decoded.name, decoded.value.as_ref().unwrap()); - } + Ok(()) } @@ -294,6 +260,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()); + let browser = Arc::new(RwLock::new(TerminalBrowser::new(ton.clone()))); let callbacks = Arc::new(Callbacks::new(Arc::clone(&browser))); @@ -303,7 +271,12 @@ pub async fn run_debot_browser( loop { let mut next_msg = browser.write().unwrap().msg_queue.pop_front(); while let Some(msg) = next_msg { - TerminalBrowser::handle_interface_calls(ton.clone(), msg, &mut debot).await?; + TerminalBrowser::handle_interface_calls( + ton.clone(), + msg, + &mut debot, + &interfaces, + ).await?; next_msg = browser.write().unwrap().msg_queue.pop_front(); } let action = browser.read().unwrap().select_action(); From 63827347214ca8ce55fe3fd816a4bd5ee846d14a Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 29 Jan 2021 19:06:48 +0300 Subject: [PATCH 05/15] Add AddressInput iface --- src/debot/interfaces/address_input.rs | 70 +++++++++++++++++++++++++++ src/debot/interfaces/dinterface.rs | 34 ++++++++----- src/debot/interfaces/mod.rs | 3 +- src/debot/term_browser.rs | 14 ++++++ src/helpers.rs | 14 ++++-- 5 files changed, 116 insertions(+), 19 deletions(-) create mode 100644 src/debot/interfaces/address_input.rs diff --git a/src/debot/interfaces/address_input.rs b/src/debot/interfaces/address_input.rs new file mode 100644 index 00000000..01d9079a --- /dev/null +++ b/src/debot/interfaces/address_input.rs @@ -0,0 +1,70 @@ +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; + +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 {} +impl AddressInput { + pub fn new() -> Self { + Self {} + } + fn select(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; + let value = terminal_input("", |val| { + let _ = load_ton_address(val).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 index 242a4b67..1e0eb1f0 100644 --- a/src/debot/interfaces/dinterface.rs +++ b/src/debot/interfaces/dinterface.rs @@ -1,9 +1,11 @@ -use ton_client::debot::{DebotInterfaceExecutor, DebotInterface}; -use std::collections::HashMap; -use crate::helpers::{TonClient}; -use std::sync::Arc; +use super::address_input::AddressInput; use super::echo::Echo; use super::stdout::Stdout; +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, @@ -15,7 +17,6 @@ impl DebotInterfaceExecutor for SupportedInterfaces { fn get_interfaces<'a>(&'a self) -> &'a HashMap> { &self.interfaces } - fn get_client(&self) -> TonClient { self.client.clone() } @@ -25,18 +26,25 @@ impl SupportedInterfaces { pub fn new(client: TonClient) -> Self { let mut interfaces = HashMap::new(); - /*let iface: Arc = - Arc::new(AddressInputInterface::new()); - interfaces.insert(iface.get_id(), iface);*/ + let iface: Arc = Arc::new(AddressInput::new()); + interfaces.insert(iface.get_id(), iface); - let iface: Arc = - Arc::new(Stdout::new()); + let iface: Arc = Arc::new(Stdout::new()); interfaces.insert(iface.get_id(), iface); - let iface: Arc = - Arc::new(Echo::new()); + let iface: Arc = Arc::new(Echo::new()); interfaces.insert(iface.get_id(), iface); Self { client, interfaces } } -} \ No newline at end of file +} + +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)) +} diff --git a/src/debot/interfaces/mod.rs b/src/debot/interfaces/mod.rs index 9a18d857..d82c24ce 100644 --- a/src/debot/interfaces/mod.rs +++ b/src/debot/interfaces/mod.rs @@ -13,4 +13,5 @@ pub mod dinterface; pub mod echo; -pub mod stdout; \ No newline at end of file +pub mod stdout; +pub mod address_input; \ No newline at end of file diff --git a/src/debot/term_browser.rs b/src/debot/term_browser.rs index c325678d..37d3ec0f 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -228,6 +228,20 @@ where input_str.trim().to_owned() } +pub(crate) fn terminal_input(prompt: &str, validator: F) -> String +where + F: Fn(&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 +} fn action_input(max: usize) -> Result<(usize, usize, Vec), String> { let mut a_str = String::new(); let mut argc = 0; diff --git a/src/helpers.rs b/src/helpers.rs index 31174987..336c1900 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 { From 2fbb7e1461fd78e8726607621e2f03873cf24674 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 29 Jan 2021 21:12:17 +0300 Subject: [PATCH 06/15] Add Terminal interface --- src/debot/interfaces/dinterface.rs | 4 + src/debot/interfaces/mod.rs | 3 +- src/debot/interfaces/terminal.rs | 256 +++++++++++++++++++++++++++++ src/debot/term_browser.rs | 4 +- 4 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 src/debot/interfaces/terminal.rs diff --git a/src/debot/interfaces/dinterface.rs b/src/debot/interfaces/dinterface.rs index 1e0eb1f0..4d457ce9 100644 --- a/src/debot/interfaces/dinterface.rs +++ b/src/debot/interfaces/dinterface.rs @@ -1,6 +1,7 @@ use super::address_input::AddressInput; use super::echo::Echo; use super::stdout::Stdout; +use super::terminal::Terminal; use crate::helpers::TonClient; use serde_json::Value; use std::collections::HashMap; @@ -35,6 +36,9 @@ impl SupportedInterfaces { 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); + Self { client, interfaces } } } diff --git a/src/debot/interfaces/mod.rs b/src/debot/interfaces/mod.rs index d82c24ce..363ec8ad 100644 --- a/src/debot/interfaces/mod.rs +++ b/src/debot/interfaces/mod.rs @@ -14,4 +14,5 @@ pub mod dinterface; pub mod echo; pub mod stdout; -pub mod address_input; \ No newline at end of file +pub mod address_input; +pub mod terminal; \ 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..9299d8cd --- /dev/null +++ b/src/debot/interfaces/terminal.rs @@ -0,0 +1,256 @@ +use super::dinterface::decode_answer_id; +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_types::cells_serialization::deserialize_tree_of_cells; +use ton_client::encoding::decode_abi_bigint; + +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"} + ], + "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":"message","type":"bytes"} + ], + "outputs": [ + ] + }, + { + "name": "printf", + "inputs": [ + {"name":"fmt","type":"bytes"}, + {"name":"fargs","type":"cell"} + ], + "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 value = terminal_input(&decode_prompt(args)?, |_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))?; + /* number.is negative(){ + Err(format!("number must be positive"))?; + }*/ + 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 message = decode_string_arg(args, "message")?; + println!("{}", message); + Ok((0, json!({}))) + } + + pub fn printf(&self, args: &Value) -> InterfaceResult { + let fmt = decode_string_arg(args, "fmt")?; + let fargs = args["fargs"].as_str().ok_or(format!(r#"argument "fargs" not found"#))?; + let boc_bytes = base64::decode(&fargs) + .map_err(|e| format!("failed to decode cell from base64: {}", e))?; + let mut args_cell = deserialize_tree_of_cells(&mut boc_bytes.as_slice()) + .map_err(|e| format!("failed to deserialize cell: {}", e))?; + + let message = printf(&fmt, |_arg| { + "1".to_string() + }); + + println!("{}", message); + Ok((0, 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), + "printf" => self.printf(args), + _ => Err(format!("function \"{}\" is not implemented", func)), + } + } +} + +fn decode_string_arg(args: &Value, name: &str) -> Result { + let hex_str = args[name] + .as_str() + .ok_or(format!("\"{}\" not found", name))?; + let bytes = hex::decode(hex_str).map_err(|e| format!("{}", e))?; + std::str::from_utf8(&bytes) + .map_err(|e| format!("{}", e)) + .map(|x| x.to_string()) +} + +fn decode_prompt(args: &Value) -> Result { + decode_string_arg(args, "prompt") +} + + +fn printf(fmt: &str, formatter: F) -> String +where + F: Fn(&str) -> String, +{ + let mut message = String::new(); + let mut cursor = fmt; + while let Some(i) = cursor.find("{") { + let left = cursor.get(..i).unwrap(); + let right = cursor.get(i+1..).unwrap(); + message += left; + //println!("right: {}", right); + if left.ends_with("\\") { + message += "{"; + cursor = right; + continue; + } + if let Some(i) = right.find("}") { + let arg = right.get(..i).unwrap(); + let right = right.get(i+1..).unwrap(); + message += &formatter(arg); + cursor = right; + } + } + message += cursor; + message +} + +#[cfg(test)] +mod tests { + use super::printf; + #[test] + fn test_printf() { + let result = printf("Hello, \\{string {}! My \\age\\ is {uint32} and {}", |arg| { + println!("arg: {}", arg); + "TYPE".to_owned() + }); + println!("{}", result); + } +} \ No newline at end of file diff --git a/src/debot/term_browser.rs b/src/debot/term_browser.rs index 37d3ec0f..4946745f 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -228,9 +228,9 @@ where input_str.trim().to_owned() } -pub(crate) fn terminal_input(prompt: &str, validator: F) -> String +pub(crate) fn terminal_input(prompt: &str, mut validator: F) -> String where - F: Fn(&String) -> Result<(), String> + F: FnMut(&String) -> Result<(), String> { let stdio = io::stdin(); let mut reader = stdio.lock(); From 8afeecbaab6d9af332d3518dc82cde8e57623684 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Mon, 1 Feb 2021 13:11:45 +0300 Subject: [PATCH 07/15] Switch to SDK branch debot-getters --- Cargo.toml | 4 ++-- src/debot/interfaces/terminal.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 509c436a..4995ed86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ log = {version = "0.4.11", features = ["std"] } tokio = { version = "0.2", features = ["full"], default-features = false } ton_abi = { git = "https://github.com/tonlabs/ton-labs-abi.git" } -ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'builtin-dinterfaces' } -ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'builtin-dinterfaces' } +ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-getters' } +ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-getters' } ton_types = { git = "https://github.com/tonlabs/ton-labs-types.git" } ton_block = { git = "https://github.com/tonlabs/ton-labs-block.git" } diff --git a/src/debot/interfaces/terminal.rs b/src/debot/interfaces/terminal.rs index 9299d8cd..d7f368a3 100644 --- a/src/debot/interfaces/terminal.rs +++ b/src/debot/interfaces/terminal.rs @@ -164,7 +164,7 @@ impl Terminal { let fargs = args["fargs"].as_str().ok_or(format!(r#"argument "fargs" not found"#))?; let boc_bytes = base64::decode(&fargs) .map_err(|e| format!("failed to decode cell from base64: {}", e))?; - let mut args_cell = deserialize_tree_of_cells(&mut boc_bytes.as_slice()) + let args_cell = deserialize_tree_of_cells(&mut boc_bytes.as_slice()) .map_err(|e| format!("failed to deserialize cell: {}", e))?; let message = printf(&fmt, |_arg| { From 4aa2f8133540d6b4da3496d9b23b42642b7c301e Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Mon, 1 Feb 2021 22:28:32 +0300 Subject: [PATCH 08/15] Update Terminal iface --- Cargo.toml | 4 ++-- src/debot/interfaces/terminal.rs | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4995ed86..3340f8b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,8 +30,8 @@ log = {version = "0.4.11", features = ["std"] } tokio = { version = "0.2", features = ["full"], default-features = false } ton_abi = { git = "https://github.com/tonlabs/ton-labs-abi.git" } -ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-getters' } -ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-getters' } +ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-extcall' } +ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-extcall' } ton_types = { git = "https://github.com/tonlabs/ton-labs-types.git" } ton_block = { git = "https://github.com/tonlabs/ton-labs-block.git" } diff --git a/src/debot/interfaces/terminal.rs b/src/debot/interfaces/terminal.rs index d7f368a3..50e547eb 100644 --- a/src/debot/interfaces/terminal.rs +++ b/src/debot/interfaces/terminal.rs @@ -67,6 +67,7 @@ const ABI: &str = r#" { "name": "print", "inputs": [ + {"name":"answerId","type":"uint32"}, {"name":"message","type":"bytes"} ], "outputs": [ @@ -75,6 +76,7 @@ const ABI: &str = r#" { "name": "printf", "inputs": [ + {"name":"answerId","type":"uint32"}, {"name":"fmt","type":"bytes"}, {"name":"fargs","type":"cell"} ], @@ -154,12 +156,14 @@ impl Terminal { } pub fn print(&self, args: &Value) -> InterfaceResult { - let message = decode_string_arg(args, "message")?; + let answer_id = decode_answer_id(args)?; + let message = decode_string_arg(args, "message")?; println!("{}", message); - Ok((0, json!({}))) + Ok((answer_id, json!({}))) } pub fn printf(&self, args: &Value) -> InterfaceResult { + let answer_id = decode_answer_id(args)?; let fmt = decode_string_arg(args, "fmt")?; let fargs = args["fargs"].as_str().ok_or(format!(r#"argument "fargs" not found"#))?; let boc_bytes = base64::decode(&fargs) @@ -172,7 +176,7 @@ impl Terminal { }); println!("{}", message); - Ok((0, json!({}))) + Ok((answer_id, json!({}))) } } From e8ae759ff7f32a1401a3bcdc3da641638603c5c4 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Tue, 2 Feb 2021 12:01:16 +0300 Subject: [PATCH 09/15] Support for multiline input --- src/debot/interfaces/terminal.rs | 35 ++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/debot/interfaces/terminal.rs b/src/debot/interfaces/terminal.rs index 50e547eb..d56686fc 100644 --- a/src/debot/interfaces/terminal.rs +++ b/src/debot/interfaces/terminal.rs @@ -6,6 +6,7 @@ use ton_client::debot::{DebotInterface, InterfaceResult}; use crate::convert::convert_token; use ton_types::cells_serialization::deserialize_tree_of_cells; use ton_client::encoding::decode_abi_bigint; +use std::io::{self, Read}; const ID: &'static str = "8796536366ee21852db56dccb60bc564598b618c865fc50c8b1ab740bba128e3"; @@ -18,7 +19,8 @@ const ABI: &str = r#" "name": "inputStr", "inputs": [ {"name":"answerId","type":"uint32"}, - {"name":"prompt","type":"bytes"} + {"name":"prompt","type":"bytes"}, + {"name":"multiline","type":"bool"} ], "outputs": [ {"name":"value","type":"bytes"} @@ -105,7 +107,17 @@ impl Terminal { } fn input_str(&self, args: &Value) -> InterfaceResult { let answer_id = decode_answer_id(args)?; - let value = terminal_input(&decode_prompt(args)?, |_val| Ok(())); + 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))?; + } else { + value = terminal_input(&prompt, |_val| Ok(())); + } Ok((answer_id, json!({ "value": hex::encode(value.as_bytes()) }))) } @@ -204,11 +216,22 @@ impl DebotInterface for Terminal { } } -fn decode_string_arg(args: &Value, name: &str) -> Result { - let hex_str = args[name] +fn decode_arg(args: &Value, name: &str) -> Result { + args[name] .as_str() - .ok_or(format!("\"{}\" not found", name))?; - let bytes = hex::decode(hex_str).map_err(|e| format!("{}", e))?; + .ok_or(format!("\"{}\" not found", name)) + .map(|x| x.to_string()) +} + +fn decode_bool_arg(args: &Value, name: &str) -> Result { + args[name] + .as_bool() + .ok_or(format!("\"{}\" not found", name)) +} + +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()) From 14eedea57b4b424290547d598c25ffc7dfb3cc60 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Tue, 2 Feb 2021 13:17:50 +0300 Subject: [PATCH 10/15] Add Menu iface --- src/debot/interfaces/dinterface.rs | 29 +++++++ src/debot/interfaces/menu.rs | 132 +++++++++++++++++++++++++++++ src/debot/interfaces/mod.rs | 3 +- src/debot/interfaces/terminal.rs | 32 +------ src/debot/term_browser.rs | 2 +- 5 files changed, 167 insertions(+), 31 deletions(-) create mode 100644 src/debot/interfaces/menu.rs diff --git a/src/debot/interfaces/dinterface.rs b/src/debot/interfaces/dinterface.rs index 4d457ce9..9a7168df 100644 --- a/src/debot/interfaces/dinterface.rs +++ b/src/debot/interfaces/dinterface.rs @@ -2,6 +2,7 @@ use super::address_input::AddressInput; use super::echo::Echo; use super::stdout::Stdout; use super::terminal::Terminal; +use super::menu::Menu; use crate::helpers::TonClient; use serde_json::Value; use std::collections::HashMap; @@ -39,6 +40,9 @@ impl SupportedInterfaces { 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 } } } @@ -52,3 +56,28 @@ pub fn decode_answer_id(args: &Value) -> Result { ) .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/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 index 363ec8ad..dcd6b08f 100644 --- a/src/debot/interfaces/mod.rs +++ b/src/debot/interfaces/mod.rs @@ -15,4 +15,5 @@ pub mod dinterface; pub mod echo; pub mod stdout; pub mod address_input; -pub mod terminal; \ No newline at end of file +pub mod terminal; +pub mod menu; \ No newline at end of file diff --git a/src/debot/interfaces/terminal.rs b/src/debot/interfaces/terminal.rs index d56686fc..d38d108b 100644 --- a/src/debot/interfaces/terminal.rs +++ b/src/debot/interfaces/terminal.rs @@ -1,4 +1,4 @@ -use super::dinterface::decode_answer_id; +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; @@ -6,7 +6,7 @@ use ton_client::debot::{DebotInterface, InterfaceResult}; use crate::convert::convert_token; use ton_types::cells_serialization::deserialize_tree_of_cells; use ton_client::encoding::decode_abi_bigint; -use std::io::{self, Read}; +use std::io::{Read}; const ID: &'static str = "8796536366ee21852db56dccb60bc564598b618c865fc50c8b1ab740bba128e3"; @@ -180,7 +180,7 @@ impl Terminal { let fargs = args["fargs"].as_str().ok_or(format!(r#"argument "fargs" not found"#))?; let boc_bytes = base64::decode(&fargs) .map_err(|e| format!("failed to decode cell from base64: {}", e))?; - let args_cell = deserialize_tree_of_cells(&mut boc_bytes.as_slice()) + let _args_cell = deserialize_tree_of_cells(&mut boc_bytes.as_slice()) .map_err(|e| format!("failed to deserialize cell: {}", e))?; let message = printf(&fmt, |_arg| { @@ -216,32 +216,6 @@ impl DebotInterface for Terminal { } } -fn decode_arg(args: &Value, name: &str) -> Result { - args[name] - .as_str() - .ok_or(format!("\"{}\" not found", name)) - .map(|x| x.to_string()) -} - -fn decode_bool_arg(args: &Value, name: &str) -> Result { - args[name] - .as_bool() - .ok_or(format!("\"{}\" not found", name)) -} - -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()) -} - -fn decode_prompt(args: &Value) -> Result { - decode_string_arg(args, "prompt") -} - - fn printf(fmt: &str, formatter: F) -> String where F: Fn(&str) -> String, diff --git a/src/debot/term_browser.rs b/src/debot/term_browser.rs index 4946745f..70b25693 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -242,7 +242,7 @@ where } value } -fn action_input(max: usize) -> Result<(usize, usize, Vec), String> { +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![]; From 0c29c07ccd56aabcce4863bfbcb348606e438a03 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 5 Feb 2021 13:04:06 +0300 Subject: [PATCH 11/15] FIx after rebase --- src/config.rs | 6 +++--- src/debot/interfaces/address_input.rs | 11 +++++++---- src/debot/interfaces/dinterface.rs | 5 +++-- src/debot/term_browser.rs | 10 +++++----- src/helpers.rs | 2 +- 5 files changed, 19 insertions(+), 15 deletions(-) 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 index 01d9079a..d00a333e 100644 --- a/src/debot/interfaces/address_input.rs +++ b/src/debot/interfaces/address_input.rs @@ -4,6 +4,7 @@ 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"; @@ -36,15 +37,17 @@ pub const ABI: &str = r#" } "#; -pub struct AddressInput {} +pub struct AddressInput { + conf: Config +} impl AddressInput { - pub fn new() -> Self { - Self {} + 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).map_err(|e| format!("Invalid address: {}", e))?; + let _ = load_ton_address(val, &self.conf).map_err(|e| format!("Invalid address: {}", e))?; Ok(()) }); Ok((answer_id, json!({ "value": value }))) diff --git a/src/debot/interfaces/dinterface.rs b/src/debot/interfaces/dinterface.rs index 9a7168df..b0c24799 100644 --- a/src/debot/interfaces/dinterface.rs +++ b/src/debot/interfaces/dinterface.rs @@ -3,6 +3,7 @@ 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; @@ -25,10 +26,10 @@ impl DebotInterfaceExecutor for SupportedInterfaces { } impl SupportedInterfaces { - pub fn new(client: TonClient) -> Self { + pub fn new(client: TonClient, conf: &Config) -> Self { let mut interfaces = HashMap::new(); - let iface: Arc = Arc::new(AddressInput::new()); + let iface: Arc = Arc::new(AddressInput::new(conf.clone())); interfaces.insert(iface.get_id(), iface); let iface: Arc = Arc::new(Stdout::new()); diff --git a/src/debot/term_browser.rs b/src/debot/term_browser.rs index 70b25693..c4e3f8aa 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -69,7 +69,7 @@ impl TerminalBrowser { debot: &mut DEngine, interfaces: &SupportedInterfaces, ) -> Result<(), String> { - + let parsed = parse_message( client.clone(), ParamsOfParse { boc: msg.clone() }, @@ -80,7 +80,7 @@ impl TerminalBrowser { .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); @@ -94,7 +94,7 @@ impl TerminalBrowser { Ok(()) } - + } struct Callbacks { @@ -228,7 +228,7 @@ where input_str.trim().to_owned() } -pub(crate) fn terminal_input(prompt: &str, mut validator: F) -> String +pub(crate) fn terminal_input(prompt: &str, mut validator: F) -> String where F: FnMut(&String) -> Result<(), String> { @@ -274,7 +274,7 @@ pub async fn run_debot_browser( ) -> Result<(), String> { println!("Connecting to {}", config.url); let ton = create_client(&config)?; - let interfaces = SupportedInterfaces::new(ton.clone()); + let interfaces = SupportedInterfaces::new(ton.clone(), &config); let browser = Arc::new(RwLock::new(TerminalBrowser::new(ton.clone()))); diff --git a/src/helpers.rs b/src/helpers.rs index 336c1900..846c0246 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -101,7 +101,7 @@ 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() }, }; From 8e66ceab715cfa31d8f6c74117807c8cb460cafe Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 5 Feb 2021 20:00:24 +0300 Subject: [PATCH 12/15] remove printf from Terminal --- src/debot/interfaces/terminal.rs | 72 -------------------------------- 1 file changed, 72 deletions(-) diff --git a/src/debot/interfaces/terminal.rs b/src/debot/interfaces/terminal.rs index d38d108b..34660548 100644 --- a/src/debot/interfaces/terminal.rs +++ b/src/debot/interfaces/terminal.rs @@ -4,7 +4,6 @@ use serde_json::Value; use ton_client::abi::Abi; use ton_client::debot::{DebotInterface, InterfaceResult}; use crate::convert::convert_token; -use ton_types::cells_serialization::deserialize_tree_of_cells; use ton_client::encoding::decode_abi_bigint; use std::io::{Read}; @@ -75,16 +74,6 @@ const ABI: &str = r#" "outputs": [ ] }, - { - "name": "printf", - "inputs": [ - {"name":"answerId","type":"uint32"}, - {"name":"fmt","type":"bytes"}, - {"name":"fargs","type":"cell"} - ], - "outputs": [ - ] - }, { "name": "constructor", "inputs": [ @@ -134,9 +123,6 @@ impl Terminal { 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))?; - /* number.is negative(){ - Err(format!("number must be positive"))?; - }*/ Ok(()) }); Ok((answer_id, json!({ "value": value }))) @@ -173,23 +159,6 @@ impl Terminal { println!("{}", message); Ok((answer_id, json!({}))) } - - pub fn printf(&self, args: &Value) -> InterfaceResult { - let answer_id = decode_answer_id(args)?; - let fmt = decode_string_arg(args, "fmt")?; - let fargs = args["fargs"].as_str().ok_or(format!(r#"argument "fargs" not found"#))?; - let boc_bytes = base64::decode(&fargs) - .map_err(|e| format!("failed to decode cell from base64: {}", e))?; - let _args_cell = deserialize_tree_of_cells(&mut boc_bytes.as_slice()) - .map_err(|e| format!("failed to deserialize cell: {}", e))?; - - let message = printf(&fmt, |_arg| { - "1".to_string() - }); - - println!("{}", message); - Ok((answer_id, json!({}))) - } } #[async_trait::async_trait] @@ -210,48 +179,7 @@ impl DebotInterface for Terminal { "inputTons" => self.input_tokens(args), "inputBoolean" => self.input_boolean(args), "print" => self.print(args), - "printf" => self.printf(args), _ => Err(format!("function \"{}\" is not implemented", func)), } } -} - -fn printf(fmt: &str, formatter: F) -> String -where - F: Fn(&str) -> String, -{ - let mut message = String::new(); - let mut cursor = fmt; - while let Some(i) = cursor.find("{") { - let left = cursor.get(..i).unwrap(); - let right = cursor.get(i+1..).unwrap(); - message += left; - //println!("right: {}", right); - if left.ends_with("\\") { - message += "{"; - cursor = right; - continue; - } - if let Some(i) = right.find("}") { - let arg = right.get(..i).unwrap(); - let right = right.get(i+1..).unwrap(); - message += &formatter(arg); - cursor = right; - } - } - message += cursor; - message -} - -#[cfg(test)] -mod tests { - use super::printf; - #[test] - fn test_printf() { - let result = printf("Hello, \\{string {}! My \\age\\ is {uint32} and {}", |arg| { - println!("arg: {}", arg); - "TYPE".to_owned() - }); - println!("{}", result); - } } \ No newline at end of file From b7d0c6aaa7f3de6e6b3571d83bc194d3f660acb1 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 5 Feb 2021 20:04:31 +0300 Subject: [PATCH 13/15] Fix multiline input: Add new line char --- src/debot/interfaces/terminal.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/debot/interfaces/terminal.rs b/src/debot/interfaces/terminal.rs index 34660548..04f38365 100644 --- a/src/debot/interfaces/terminal.rs +++ b/src/debot/interfaces/terminal.rs @@ -104,6 +104,7 @@ impl Terminal { 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(())); } From e8c074b09f3586d97c9045d2df6c5b96958e2e88 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Fri, 5 Feb 2021 21:13:54 +0300 Subject: [PATCH 14/15] Update ver --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3340f8b4..31ede59f 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" From cd0e24d808d408c7c4b61903ee4b6ba6f3748d85 Mon Sep 17 00:00:00 2001 From: "Nikita.m" Date: Tue, 9 Feb 2021 12:59:45 +0300 Subject: [PATCH 15/15] Fix cli after switching to master sdk --- Cargo.toml | 7 ++-- src/call.rs | 30 +++++++++------- src/debot/term_browser.rs | 8 +++-- src/decode.rs | 73 ++++++++++++++++++++------------------- src/depool.rs | 30 ++++++++-------- src/helpers.rs | 4 ++- src/main.rs | 22 ++++++------ src/voting.rs | 10 +++--- tests/cli.rs | 20 +++++------ 9 files changed, 107 insertions(+), 97 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 31ede59f..8468c535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,14 +30,11 @@ log = {version = "0.4.11", features = ["std"] } tokio = { version = "0.2", features = ["full"], default-features = false } ton_abi = { git = "https://github.com/tonlabs/ton-labs-abi.git" } -ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-extcall' } -ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git', branch = 'debot-extcall' } +ton_client = { git = 'https://github.com/tonlabs/TON-SDK.git' } +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" } -#[patch."https://github.com/tonlabs/TON-SDK.git"] -#ton_client = { path = "../TON-SDK/ton_client" } - [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/debot/term_browser.rs b/src/debot/term_browser.rs index c4e3f8aa..5bdf5478 100644 --- a/src/debot/term_browser.rs +++ b/src/debot/term_browser.rs @@ -63,7 +63,7 @@ impl TerminalBrowser { } } - async fn handle_interface_calls( + async fn handle_interface_call( client: TonClient, msg: String, debot: &mut DEngine, @@ -73,7 +73,9 @@ impl TerminalBrowser { let parsed = parse_message( client.clone(), ParamsOfParse { boc: msg.clone() }, - ).map_err(|e| format!("{}", e))?; + ) + .await + .map_err(|e| format!("{}", e))?; let iface_addr = parsed.parsed["dst"] .as_str() @@ -285,7 +287,7 @@ pub async fn run_debot_browser( loop { let mut next_msg = browser.write().unwrap().msg_queue.pop_front(); while let Some(msg) = next_msg { - TerminalBrowser::handle_interface_calls( + TerminalBrowser::handle_interface_call( ton.clone(), msg, &mut debot, 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/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 846c0246..0ca2270b 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -104,6 +104,7 @@ pub fn create_client(conf: &Config) -> Result { max_reconnect_timeout: 1000, ..Default::default() }, + boc: Default::default(), }; let cli = ClientContext::new(cli_conf).map_err(|e| format!("failed to create tonclient: {}", e))?; @@ -140,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, @@ -155,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(()) }