<repo_name>serge-medvedev/tonos-cli<file_sep>/src/debot/term_signing_box.rs
use crate::crypto::load_keypair;
use super::term_browser::input;
use ton_client::crypto::KeyPair;
use std::io::{self, BufRead, Write};

pub(super) struct TerminalSigningBox {
    pub keys: KeyPair
}

impl TerminalSigningBox {
    pub fn new() -> Result<Self, String> {
        let stdio = io::stdin();
        let mut reader = stdio.lock();    
        let mut writer = io::stdout();
        let keys = input_keys(&mut reader, &mut writer, 3)?;
        Ok(Self {
            keys
        })
    }
}

pub(super) fn input_keys<R, W>(reader: &mut R, writer: &mut W, tries: u8) -> Result<KeyPair, String>
where
    R: BufRead,
    W: Write,
{
    let enter_str = "enter seed phrase or path to keypair file";
    let mut pair = Err("no keypair".to_string());
    for _ in 0..tries {
        let value = input(enter_str, reader, writer);
        pair = load_keypair(&value).map_err(|e| {
            println!("Invalid keys: {}. Try again.", e);
            e.to_string()
        });
        if pair.is_ok() {
            break;
        }
    }
    pair
}


#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;

    const PUBLIC: &'static str = "<KEY>";
    const PRIVATE: &'static str = "<KEY>";
    const SEED: &'static str = "episode polar pistol excite essence van cover fox visual gown yellow minute";
    const KEYS_FILE: &'static str = "./keys.json";

    fn create_keypair_file(name: &str) {
        let mut file = File::create(name).unwrap();
        file.write_all(format!(r#"{{
            "public": "{}",
            "secret": "{}"
        }}"#, PUBLIC, PRIVATE).as_bytes()).unwrap();
    }

    #[test]
    fn load_key_from_file() {
        let mut in_data = KEYS_FILE.as_bytes();
        let mut out_data = vec![];

        create_keypair_file(KEYS_FILE);
        let keys = input_keys(&mut in_data, &mut out_data, 1).unwrap();
        
        assert_eq!(format!("{}", keys.public), PUBLIC);
        assert_eq!(format!("{}", keys.secret), PRIVATE);        
    }

    #[test]
    fn load_key_from_seed() {
        let mut in_data = SEED.as_bytes();
        let mut out_data = vec![];

        let keys = input_keys(&mut in_data, &mut out_data, 1).unwrap();
        
        assert_eq!(format!("{}", keys.public), PUBLIC);
        assert_eq!(format!("{}", keys.secret), PRIVATE);
    }
}<file_sep>/src/config.rs
/*
 * 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.
 */
use serde::{Deserialize, Serialize};

const TESTNET: &'static str = "https://net.ton.dev";
fn default_url() -> String {
    TESTNET.to_string()
}

fn default_wc() -> i32 {
    0
}

fn default_retries() -> u8 {
    5
}

fn default_depool_fee() -> f32 {
    0.5
}

fn default_timeout() -> u32 {
    60000
}

fn default_false() -> bool {
    false
}

#[derive(Serialize, Deserialize, Clone)]
pub struct Config {
    #[serde(default = "default_url")]
    pub url: String,
    #[serde(default = "default_wc")]
    pub wc: i32,
    pub addr: Option<String>,
    pub wallet: Option<String>,
    pub abi_path: Option<String>,
    pub keys_path: Option<String>,
    #[serde(default = "default_retries")]
    pub retries: u8,
    #[serde(default = "default_timeout")]
    pub timeout: u32,
    #[serde(default = "default_false")]
    pub is_json: bool,
    #[serde(default = "default_depool_fee")]
    pub depool_fee: f32,
}

impl Config {
    pub fn new() -> Self {
        Config {
            url: default_url(),
            wc: default_wc(),
            addr: None,
            wallet: None,
            abi_path: None,
            keys_path: None,
            retries: default_retries(),
            timeout: default_timeout(),
            is_json: default_false(),
            depool_fee: default_depool_fee(),
        }
    }

    pub fn from_file(path: &str) -> Option<Self> {
        let conf_str = std::fs::read_to_string(path).ok()?;
        let conf: Config = serde_json::from_str(&conf_str).ok()?;
        Some(conf)
    }
}

pub fn clear_config(
    mut conf: Config,
    path: &str,
    url: bool,
    addr: bool,
    wallet: bool,
    abi: bool,
    keys: bool,
    wc: bool,
    retries: bool,
    timeout: bool,
    depool_fee: bool,
) -> Result<(), String> {
    if url {
        conf.url = default_url();
    }
    if addr {
        conf.addr = None;
    }
    if wallet {
        conf.wallet = None;
    }
    if abi {
        conf.abi_path = None;
    }
    if keys {
        conf.keys_path = None;
    }
    if retries {
        conf.retries = default_retries();
    }
    if timeout {
        conf.timeout = default_timeout();
    }
    if wc {
        conf.wc = default_wc();
    }
    if depool_fee {
        conf.depool_fee = default_depool_fee();
    }
    if (url || addr || wallet || abi || keys || retries || timeout || wc || depool_fee) == false {
        conf = Config {
            url: default_url(),
            wc: default_wc(),
            addr: None,
            wallet: None,
            abi_path: None,
            keys_path: None,
            retries: default_retries(),
            timeout: default_timeout(),
            is_json: default_false(),
            depool_fee: default_depool_fee(),
        };
    }
    let conf_str = serde_json::to_string(&conf)
        .map_err(|_| "failed to serialize config object".to_string())?;

    std::fs::write(path, conf_str).map_err(|e| format!("failed to write config file: {}", e))?;
    println!("Succeeded.");
    Ok(())
}

pub fn set_config(
    mut conf: Config,
    path: &str,
    url: Option<&str>,
    addr: Option<&str>,
    wallet: Option<&str>,
    abi: Option<&str>,
    keys: Option<&str>,
    wc: Option<&str>,
    retries: Option<&str>,
    timeout: Option<&str>,
    depool_fee: Option<&str>,
) -> Result<(), String> {
        if let Some(s) = url {
            conf.url = s.to_string();
        }
        if let Some(s) = addr {
            conf.addr = Some(s.to_string());
        }
        if let Some(s) = wallet {
            conf.wallet = Some(s.to_string());
        }
        if let Some(s) = abi {
            conf.abi_path = Some(s.to_string());
        }
        if let Some(s) = keys {
            conf.keys_path = Some(s.to_string());
        }
        if let Some(retries) = retries {
            conf.retries = u8::from_str_radix(retries, 10)
                .map_err(|e| format!(r#"failed to parse "retries": {}"#, e))?;
        }
        if let Some(timeout) = timeout {
            conf.timeout = u32::from_str_radix(timeout, 10)
                .map_err(|e| format!(r#"failed to parse "timeout": {}"#, e))?;
        }
        if let Some(wc) = wc {
            conf.wc = i32::from_str_radix(wc, 10)
                .map_err(|e| format!(r#"failed to parse "workchain id": {}"#, e))?;
        }
        if let Some(depool_fee) = depool_fee {
            conf.depool_fee = depool_fee.parse::<f32>()
                .map_err(|e| format!(r#"failed to parse "depool_fee": {}"#, e))?;
        }
        if conf.depool_fee < 0.5 {
            return Err("Minimal value for depool fee is 0.5".to_string());
        }
        let conf_str = serde_json::to_string(&conf)
            .map_err(|_| "failed to serialize config object".to_string())?;

        std::fs::write(path, conf_str).map_err(|e| format!("failed to write config file: {}", e))?;
        if !conf.is_json {
            println!("Succeeded.");
        }
        Ok(())
    }<file_sep>/src/decode.rs
/*
 * 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.
 */
use crate::{print_args, VERBOSE_MODE};
use crate::config::Config;
use crate::helpers::{decode_msg_body, create_client_local};
use clap::{ArgMatches, SubCommand, Arg, App, AppSettings};
use ton_types::cells_serialization::serialize_tree_of_cells;
use ton_types::Cell;
use std::fmt::Write;

fn match_abi_path(matches: &ArgMatches, config: &Config) -> Option<String> {
    matches.value_of("ABI")
        .map(|s| s.to_string())
        .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::TrailingVarArg)
        .setting(AppSettings::DontCollapseArgsInUsage)
        .subcommand(SubCommand::with_name("body")
            .arg(Arg::with_name("BODY")
                .required(true)
                .help("Message body encoded as base64."))
            .arg(Arg::with_name("ABI")
                .long("--abi")
                .takes_value(true)
                .help("Path to ABI file.")))
        .subcommand(SubCommand::with_name("msg")
           .arg(Arg::with_name("MSG")
                    .required(true)
                    .help("Path to message boc file."))
            .arg(Arg::with_name("ABI")
                    .long("--abi")
                    .takes_value(true)
                    .help("Path to ABI file.")))
}

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).await;
    }
    if let Some(m) = m.subcommand_matches("msg") {
        return decode_message_command(m, config).await;
    }
    Err("unknown command".to_owned())
}

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)
            .ok_or("ABI file not defined. Supply it in config file or command line.".to_string())?
    );
    if !config.is_json {
        print_args!(m, body, abi);
    }
    println!("{}", decode_body(body.unwrap(), &abi.unwrap(), config.is_json).await?);
    Ok(())
}

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)
            .ok_or("ABI file not defined. Supply it in config file or command line.".to_string())?
    );
    if !config.is_json {
        print_args!(m, msg, abi);
    }
    let msg = msg.map(|f| std::fs::read(f))
        .transpose()
        .map_err(|e| format!(" failed to read msg boc file: {}", e))?
        .unwrap();
    println!("{}", decode_message(msg, abi, config.is_json).await?);
    Ok(())
}

async fn print_decoded_body(body_vec: Vec<u8>, abi: &str, is_json: bool) -> Result<String, String> {
    let ton = create_client_local()?;
    let mut empty_boc = vec![];
    serialize_tree_of_cells(&Cell::default(), &mut empty_boc).unwrap();
    if body_vec.cmp(&empty_boc) == std::cmp::Ordering::Equal {
        return Err(format!("body is empty"));
    }

    let body_base64 = base64::encode(&body_vec);
    let mut res = {
        match decode_msg_body(ton.clone(), abi, &body_base64, false).await {
            Ok(res) => 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)
    } else {
        format!("{}: {}", res.name, serde_json::to_string_pretty(&output).unwrap())
    })
}

async fn decode_body(body: &str, abi: &str, is_json: bool) -> Result<String, String> {
    let abi = std::fs::read_to_string(abi)
        .map_err(|e| format!("failed to read ABI file: {}", e))?;

    let body_vec  = base64::decode(body)
        .map_err(|e| format!("body is not a valid base64 string: {}", e))?;

    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).await?).unwrap();
    if is_json { writeln!(s, "}}").unwrap(); }
    Ok(result)
}

async fn decode_message(msg_boc: Vec<u8>, abi: Option<String>, is_json: bool) -> Result<String, String> {
    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;
    write!(s, "{}", printer.print(false)).unwrap();

    if abi.is_some() && tvm_msg.body().is_some() {
        let abi = abi.unwrap();
        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).await?).unwrap();
    }
    if is_json { writeln!(s, "}}").unwrap(); }
    Ok(result)
}


mod msg_printer {
    use ton_block::*;
    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,
        end: &'static str,
        msg: &'a Message,
        is_json: bool,
    }

    impl<'a> MsgPrinter<'a> {
        pub fn new(msg: &'a Message, is_json: bool) -> Self {
            MsgPrinter {off: " ", start: "\"", end: "\",", msg, is_json }
        }

        pub fn print(&mut self, close: bool) -> String {
            let mut result = String::new();
            let s = &mut result;
            if self.is_json {
                write!(s, "{{\n").unwrap();
            }
            self.json(s, "Type", &self.print_msg_type());
            let hdr = self.print_msg_header();
            self.start = "{\n";
            self.end = " },";
            self.off = " ";
            self.json(s, "Header", &hdr);
            self.state_init_printer(s);
            self.start = "\"";
            if close { self.end = "\""; } else { self.end = "\","; }
            self.off = " ";
            self.json(s, "Body", &tree_of_cells_into_base64(
                self.msg.body().map(|slice| slice.into_cell()).as_ref(),
            ));
            if self.is_json && close {
                write!(s, "}}\n").unwrap();
            }
            result
        }

        fn print_msg_type(&self) -> String {
            match self.msg.header() {
                CommonMsgInfo::IntMsgInfo(_) => "internal",
                CommonMsgInfo::ExtInMsgInfo(_) => "external inbound",
                CommonMsgInfo::ExtOutMsgInfo(_) => "external outbound",
            }.to_owned() + " message"
        }


        fn json<T: std::fmt::Display>(&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;
            self.start = "\"";
            self.end = "\",";
            self.off = "   ";
            match self.msg.header() {
                CommonMsgInfo::IntMsgInfo(header) => {
                    self.json(s, "ihr_disabled", &header.ihr_disabled);
                    self.json(s, "bounce", &header.bounce);
                    self.json(s, "bounced", &header.bounced);
                    self.json(s, "source", &header.src);
                    self.json(s, "destination", &header.dst);
                    self.json(s, "value", &print_cc(&header.value));
                    self.json(s, "ihr_fee", &print_grams(&header.ihr_fee));
                    self.json(s, "fwd_fee", &print_grams(&header.fwd_fee));
                    self.json(s, "created_lt", &header.created_lt);
                    self.end = "\"";
                    self.json(s, "created_at", &header.created_at);
                },
                CommonMsgInfo::ExtInMsgInfo(header) => {
                    self.json(s, "source", &header.src);
                    self.json(s, "destination", &header.dst);
                    self.end = "\"";
                    self.json(s, "import_fee", &print_grams(&header.import_fee));
                },
                CommonMsgInfo::ExtOutMsgInfo(header) => {
                    self.json(s, "source", &header.src);
                    self.json(s, "destination", &header.dst);
                    self.json(s, "created_lt", &header.created_lt);
                    self.end = "\"";
                    self.json(s, "created_at", &header.created_at);
                }
            };
            result
        }

        fn state_init_printer(&self, s: &mut String) {
            match self.msg.state_init().as_ref() {
                Some(x) => {
                    let init = format!(
                        "StateInit\n split_depth: {}\n special: {}\n data: {}\n code: {}\n lib:  {}\n",
                        x.split_depth.as_ref().map(|x| format!("{:?}", x)).unwrap_or("None".to_string()),
                        x.special.as_ref().map(|x| format!("{:?}", x)).unwrap_or("None".to_string()),
                        tree_of_cells_into_base64(x.data.as_ref()),
                        tree_of_cells_into_base64(x.code.as_ref()),
                        tree_of_cells_into_base64(x.library.root())
                    );
                    self.json(s, "Init", &init);
                },
                None => (),
            };
        }
    }

    pub fn tree_of_cells_into_base64(root_cell: Option<&Cell>) -> String {
        match root_cell {
            Some(cell) => {
                let mut bytes = Vec::new();
                serialize_tree_of_cells(cell, &mut bytes).unwrap();
                base64::encode(&bytes)
            }
            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() {
            result += " other: {";
            cc.other.iterate_with_keys(|key: u32, value| {
                result += &format!(" \"{}\": \"{}\",", key, value.0);
                Ok(true)
            }).ok();
            result.pop(); // remove extra comma
            result += " }";
        }
        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[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).await.unwrap();
        let _ : serde_json::Value = serde_json::from_str(&out).unwrap();
    }

    #[tokio::test]
    async fn test_decode_body_json() {
        let body = "te6ccgEBAQEARAAAgwAAALqUCTqWL8OX7JivfJrAAzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMQAAAAAAAAAAAAAAAEeGjADA==";
        let out = decode_body(body, "tests/samples/wallet.abi.json", true).await.unwrap();
        let _ : serde_json::Value = serde_json::from_str(&out).unwrap();
    }
}<file_sep>/src/deploy.rs
/*
 * 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.
 */
use crate::helpers::{create_client_verbose, load_abi, calc_acc_address};
use crate::config::Config;
use crate::crypto::load_keypair;
use ton_client::processing::{ParamsOfProcessMessage};
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)?;

    let keys = load_keypair(keys_file)?;

    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(
        &tvc_bytes,
        wc,
        keys.public.clone(),
        None,
        abi.clone()
    ).await?;

    println!("Deploying...");
    let dset = DeploySet {
        tvc: tvc_base64,
        workchain_id: Some(wc),
        ..Default::default()
    };
    let params = serde_json::from_str(params)
        .map_err(|e| format!("function arguments is not a json: {}", e))?;

    let callback = |_event| { async move { } };
    ton_client::processing::process_message(
        ton.clone(),
        ParamsOfProcessMessage {
            message_encode_params: ParamsOfEncodeMessage {
                abi,
                address: Some(addr.clone()),
                deploy_set: Some(dset),
                call_set: CallSet::some_with_function_and_input("constructor", params),
                signer: Signer::Keys{ keys },
                ..Default::default()
            },
            send_events: true,
            ..Default::default()
        },
        callback,
    ).await
    .map_err(|e| format!("deploy failed: {:#}", e))?;

    println!("Transaction succeeded.");
    println!("Contract deployed at address: {}", addr);
    Ok(())
}<file_sep>/CHANGELOG.md
# CHANGELOG

## v1.0.0

### Breaking changes
- `tonos-cli` now stores its configuration in `./tonos-cli.conf.json`. `tonlabs-cli.conf.json` is now obsolete and can be renamed or deleted.
- Commands `deploy`, `call`, `callex`, `run`, `message` and others now output errors in a different format, compatible with the corresponding changes in the SDK v1.

### Fixes

- Some fixes were made in SDK Debot module that affects running of debots in cli terminal debot browser. The following were fixed:
    - invoked debot terminated correctly after error occured during
    execution of one of its actions. Initial `prev_state` of invoked debot changed to STATE_EXIT;
    - fixed double jumping to current context in invoker debot after
    returning control to it from invoked debot;
    - fixed conversation of exception codes thrown by debots to their user-friendly description.
- Fixed bug in terminal debot browser. Error in invoked debot doesn't shutdown caller debot. 
    
This fixes affects all debots invoking other debots (e.g. depool debot, mludi debot, DoD debot).

### Miscellaneous
- `tonos-cli` switched to SDK v1. All code using sdk api was refactored.
- `tonos-cli` started to use Debot Engine from SDK Debot Module.
<file_sep>/src/depool.rs
/*
 * 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.
 */
use crate::{print_args, VERBOSE_MODE};
use crate::config::Config;
use crate::convert;
use crate::depool_abi::DEPOOL_ABI;
use crate::helpers::{create_client_local, create_client_verbose, load_abi, load_ton_address, now, TonClient};
use crate::multisig::send_with_body;
use clap::{App, ArgMatches, SubCommand, Arg, AppSettings};
use serde_json;
use ton_client::abi::{ParamsOfEncodeMessageBody, ParamsOfDecodeMessageBody, CallSet};
use ton_client::net::{OrderBy, ParamsOfQueryCollection, ParamsOfWaitForCollection, SortDirection};

pub fn create_depool_command<'a, 'b>() -> App<'a, 'b> {
    let wallet_arg = Arg::with_name("MSIG")
        .takes_value(true)
        .long("--wallet")
        .short("-w")
        .help("Multisig wallet address.");
    let value_arg = Arg::with_name("VALUE")
        .takes_value(true)
        .long("--value")
        .short("-v")
        .help("Value in tons.");
    let keys_arg = Arg::with_name("SIGN")
        .takes_value(true)
        .long("--sign")
        .short("-s")
        .help("Path to keypair file or seed phrase which must be used to sign message to multisig wallet.");
    let total_period_arg = Arg::with_name("TPERIOD")
        .takes_value(true)
        .long("--total")
        .short("-t")
        .help("Total period of vesting stake (days).");
    let withdrawal_period_arg = Arg::with_name("WPERIOD")
        .takes_value(true)
        .long("--withdrawal")
        .short("-i")
        .help("Payment period of vesting stake (days).");
    let beneficiary_arg = Arg::with_name("BENEFICIARY")
        .takes_value(true)
        .long("--beneficiary")
        .short("-b")
        .help("Smart contract address which will own lock stake rewards.");
    let donor_arg = Arg::with_name("DONOR")
        .takes_value(true)
        .long("--donor")
        .short("-d")
        .help("Donor smart contract address.");
    let dest_arg = Arg::with_name("DEST")
        .takes_value(true)
        .long("--dest")
        .short("-d")
        .help("Address of destination smart contract.");
    SubCommand::with_name("depool")
        .about("DePool commands.")
        .setting(AppSettings::AllowLeadingHyphen)
        .setting(AppSettings::DontCollapseArgsInUsage)
        .arg(Arg::with_name("ADDRESS")
            .takes_value(true)
            .long("--addr")
            .help("DePool contract address. if the parameter is omitted, then the value `addr` from the config is used"))
        .subcommand(SubCommand::with_name("donor")
            .about(r#"Top level command for specifying donor for exotic stakes in depool."#)
            .subcommand(SubCommand::with_name("vesting")
                .about("Set the address from which participant can receive a vesting stake.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(keys_arg.clone())
                .arg(donor_arg.clone()))
            .subcommand(SubCommand::with_name("lock")
                .about("Set the address from which participant can receive a lock stake.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(keys_arg.clone())
                .arg(donor_arg.clone())))
        .subcommand(SubCommand::with_name("stake")
            .about(r#"Top level command for managing stakes in depool. Uses a supplied multisignature wallet to send internal message with stake to depool."#)
            .subcommand(SubCommand::with_name("ordinary")
                .about("Deposits ordinary stake in depool from multisignature wallet.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(value_arg.clone())
                .arg(keys_arg.clone()))
            .subcommand(SubCommand::with_name("vesting")
                .about("Deposits vesting stake in depool from multisignature wallet.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(value_arg.clone())
                .arg(keys_arg.clone())
                .arg(total_period_arg.clone())
                .arg(withdrawal_period_arg.clone())
                .arg(beneficiary_arg.clone()))
            .subcommand(SubCommand::with_name("lock")
                .about("Deposits lock stake in depool from multisignature wallet.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(value_arg.clone())
                .arg(keys_arg.clone())
                .arg(total_period_arg.clone())
                .arg(withdrawal_period_arg.clone())
                .arg(beneficiary_arg.clone()))
            .subcommand(SubCommand::with_name("transfer")
                .about("Transfers ownership of wallet stake to another contract.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(value_arg.clone())
                .arg(keys_arg.clone())
                .arg(dest_arg.clone()))
            .subcommand(SubCommand::with_name("remove")
                .about("Withdraws ordinary stake from current pooling round of depool to the multisignature wallet.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(value_arg.clone())
                .arg(keys_arg.clone()))
            .subcommand(SubCommand::with_name("withdrawPart")
                .about("Withdraws part of the stake after round completion.")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(value_arg.clone())
                .arg(keys_arg.clone())))
        .subcommand(SubCommand::with_name("replenish")
            .about("Transfers funds from the multisignature wallet to the depool contract (NOT A STAKE).")
            .setting(AppSettings::AllowLeadingHyphen)
            .arg(wallet_arg.clone())
            .arg(value_arg.clone())
            .arg(keys_arg.clone()))
        .subcommand(SubCommand::with_name("ticktock")
            .about("Call DePool 'ticktock()' function to update its state. 1 ton is attached to this call (change will be returned).")
            .setting(AppSettings::AllowLeadingHyphen)
            .arg(wallet_arg.clone())
            .arg(keys_arg.clone()))
        .subcommand(SubCommand::with_name("withdraw")
            .about("Allows to disable auto investment of the stake into next round and withdraw all the stakes after round completion.")
            .setting(AppSettings::AllowLeadingHyphen)
            .subcommand(SubCommand::with_name("on")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(keys_arg.clone()))
            .subcommand(SubCommand::with_name("off")
                .setting(AppSettings::AllowLeadingHyphen)
                .arg(wallet_arg.clone())
                .arg(keys_arg.clone())))
        .subcommand(SubCommand::with_name("events")
            .about("Prints depool events.")
            .setting(AppSettings::AllowLeadingHyphen)
            .arg(Arg::with_name("SINCE")
                .takes_value(true)
                .long("--since")
                .short("-s")
                .help("Prints events since this unixtime."))
            .arg(Arg::with_name("WAITONE")
                .long("--wait-one")
                .short("-w")
                .help("Waits until new event will be emitted.")) )
}

struct CommandData<'a> {
    conf: Config,
    depool: String,
    wallet: String,
    keys: String,
    stake: &'a str,
    depool_fee: String,
}

impl<'a> CommandData<'a> {
    pub fn from_matches_and_conf(m: &'a ArgMatches, conf: Config, depool: String) -> Result<Self, String> {
        let (wallet, stake, keys) = parse_stake_data(m, &conf)?;
        let depool_fee = conf.depool_fee.clone().to_string();
        Ok(CommandData {conf, depool, wallet, stake, keys, depool_fee})
    }
}

fn parse_wallet_data(m: &ArgMatches, conf: &Config) -> Result<(String, String), String> {
    let wallet = m.value_of("MSIG")
        .map(|s| s.to_string())
        .or(conf.wallet.clone())
        .ok_or("multisig wallet address is not defined.".to_string())?;
    let wallet = load_ton_address(&wallet, conf)
        .map_err(|e| format!("invalid multisig address: {}", e))?;
    let keys = m.value_of("SIGN")
        .map(|s| s.to_string())
        .or(conf.keys_path.clone())
        .ok_or("keypair is not defined.".to_string())?;
    Ok((wallet, keys))
}

fn parse_stake_data<'a>(m: &'a ArgMatches, conf: &Config) -> Result<(String, &'a str, String), String> {
    let (wallet, keys) = parse_wallet_data(m, conf)?;
    let stake = m.value_of("VALUE")
        .ok_or("stake value is not defined.".to_string())?;
    Ok((wallet, stake, keys))
}

pub async fn depool_command(m: &ArgMatches<'_>, conf: Config) -> Result<(), String> {
    let depool = m.value_of("ADDRESS")
        .map(|s| s.to_string())
        .or(conf.addr.clone())
        .ok_or("depool address is not defined. Supply it in config file or in command line.".to_string())?;
    let depool = load_ton_address(&depool, &conf)
        .map_err(|e| format!("invalid depool address: {}", e))?;

    if let Some(m) = m.subcommand_matches("donor") {
        let matches = m.subcommand_matches("vesting").or(m.subcommand_matches("lock"));
        if let Some(matches) = matches {
            let is_vesting = m.subcommand_matches("vesting").is_some();
            let (wallet, keys) = parse_wallet_data(&matches, &conf)?;
            return set_donor_command(matches, conf, depool.as_str(), &wallet, &keys, is_vesting).await;
        }
    }

    if let Some(m) = m.subcommand_matches("stake") {
        if let Some(m) = m.subcommand_matches("ordinary") {
            return ordinary_stake_command(m,
                CommandData::from_matches_and_conf(m, conf, depool)?,
            ).await;
        }
        if let Some(m) = m.subcommand_matches("vesting") {
            return exotic_stake_command(m,
                CommandData::from_matches_and_conf(m, conf, depool)?,
                true,
            ).await;
        }
        if let Some(m) = m.subcommand_matches("lock") {
            return exotic_stake_command(m,
                CommandData::from_matches_and_conf(m, conf, depool)?,
                false,
            ).await;
        }
        if let Some(m) = m.subcommand_matches("remove") {
            return remove_stake_command(m,
                CommandData::from_matches_and_conf(m, conf, depool)?,
            ).await;
        }
        if let Some(m) = m.subcommand_matches("withdrawPart") {
            return withdraw_stake_command(m,
                CommandData::from_matches_and_conf(m, conf, depool)?,
            ).await;
        }
        if let Some(m) = m.subcommand_matches("transfer") {
            return transfer_stake_command(m,
                CommandData::from_matches_and_conf(m, conf, depool)?,
            ).await;
        }
    }
    if let Some(m) = m.subcommand_matches("withdraw") {
        let matches = m.subcommand_matches("on").or(m.subcommand_matches("off"));
        if let Some(matches) = matches {
            let (wallet, keys) = parse_wallet_data(&matches, &conf)?;
            let enable_withdraw = m.subcommand_matches("on").is_some();
            return set_withdraw_command(matches, conf, &depool, &wallet, &keys, enable_withdraw).await;
        }
    }
    if let Some(m) = m.subcommand_matches("events") {
        return events_command(m, conf, &depool).await
    }
    if let Some(m) = m.subcommand_matches("replenish") {
        return replenish_command(m,
            CommandData::from_matches_and_conf(m, conf, depool)?,
        ).await;
    }
    if let Some(m) = m.subcommand_matches("ticktock") {
        let (wallet, keys) = parse_wallet_data(&m, &conf)?;
        return ticktock_command(m, conf, &depool, &wallet, &keys).await;
    }
    Err("unknown depool command".to_owned())
}

/*
 * Events command
 */

async fn events_command(m: &ArgMatches<'_>, conf: Config, depool: &str) -> Result<(), String> {
    let since = m.value_of("SINCE");
    let wait_for = m.is_present("WAITONE");
    let depool = Some(depool);
    print_args!(m, depool, since);
    if !wait_for {
        let since = since.map(|s| {
                u32::from_str_radix(s, 10)
                    .map_err(|e| format!(r#"cannot parse "since" option: {}"#, e))
            })
            .transpose()?
            .unwrap_or(0);
        get_events(conf, depool.unwrap(), since).await
    } else {
        wait_for_event(conf, depool.unwrap()).await
    }
}

fn events_filter(addr: &str, since: u32) -> serde_json::Value {
    json!({
        "src": { "eq": addr },
        "msg_type": {"eq": 2 },
        "created_at": {"ge": since }
    })
}

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(),
        ParamsOfDecodeMessageBody {
            abi: load_abi(DEPOOL_ABI).unwrap(),
            body: body.to_owned(),
            is_internal: false,
            ..Default::default()
        },
    ).await;
    let (name, args) = if result.is_err() {
        ("unknown".to_owned(), "{}".to_owned())
    } else {
        let result = result.unwrap();
        (result.name, serde_json::to_string(&result.value).unwrap())
    };

    println!("{} {} ({})\n{}\n",
        name,
        event["created_at"].as_u64().unwrap(),
        event["created_at_string"].as_str().unwrap(),
        args
    );
}

async fn get_events(conf: Config, depool: &str, since: u32) -> Result<(), String> {
    let ton = create_client_verbose(&conf)?;
    let _addr = load_ton_address(depool, &conf)?;

    let events = ton_client::net::query_collection(
        ton.clone(),
        ParamsOfQueryCollection {
            collection: "messages".to_owned(),
            filter: Some(events_filter(depool, since)),
            result: "id body created_at created_at_string".to_owned(),
            order: Some(vec![OrderBy{ path: "created_at".to_owned(), direction: SortDirection::DESC }]),
            ..Default::default()
        },
    ).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).await;
    }
    println!("Done");
    Ok(())
}

async fn wait_for_event(conf: Config, depool: &str) -> Result<(), String> {
    let ton = create_client_verbose(&conf)?;
    let _addr = load_ton_address(depool, &conf)?;
    println!("Waiting for a new event...");
    let event = ton_client::net::wait_for_collection(
        ton.clone(),
        ParamsOfWaitForCollection {
            collection: "messages".to_owned(),
            filter: Some(events_filter(depool, now())),
            result: "id body created_at created_at_string".to_owned(),
            timeout: Some(conf.timeout),
            ..Default::default()
        },

    ).await.map_err(|e| println!("failed to query event: {}", e.to_string()));
    if event.is_ok() {
        print_event(ton.clone(), &event.unwrap().result).await;
    }
    Ok(())
}
/*
 * Stake commands
 */

async fn ordinary_stake_command(
    m: &ArgMatches<'_>,
    cmd: CommandData<'_>,
) -> Result<(), String> {
    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
}

async fn replenish_command(
    m: &ArgMatches<'_>,
    cmd: CommandData<'_>,
) -> Result<(), String> {
    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
}

async fn ticktock_command(
    m: &ArgMatches<'_>,
    conf: Config,
    depool: &str,
    wallet: &str,
    keys: &str,
) -> Result<(), String> {
    let (depool, wallet, keys) = (Some(depool), Some(wallet), Some(keys));
    print_args!(m, depool, wallet, keys);
    call_ticktock(conf, depool.unwrap(), wallet.unwrap(), keys.unwrap()).await
}

async fn transfer_stake_command(
    m: &ArgMatches<'_>,
    cmd: CommandData<'_>,
) -> Result<(), String> {
    let dest = Some(m.value_of("DEST")
        .ok_or("destination address is not defined.".to_string())?);
    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
}

async fn set_donor_command(
    m: &ArgMatches<'_>,
    conf: Config,
    depool: &str,
    wallet: &str,
    keys: &str,
    is_vesting: bool,
) -> Result<(), String> {
    let (depool, wallet, keys) = (Some(depool), Some(wallet), Some(keys));
    let donor = Some(m.value_of("DONOR")
        .ok_or("donor is not defined.".to_string())?);
    print_args!(m, depool, wallet, keys, donor);
    set_donor(conf, depool.unwrap(), wallet.unwrap(), keys.unwrap(), is_vesting, donor.unwrap()).await
}

async fn exotic_stake_command(
    m: &ArgMatches<'_>,
    cmd: CommandData<'_>,
    is_vesting: bool,
) -> Result<(), String> {
    let withdrawal_period = Some(m.value_of("WPERIOD")
        .ok_or("withdrawal period is not defined.".to_string())?);
    let total_period = Some(m.value_of("TPERIOD")
        .ok_or("total period is not defined.".to_string())?);
    let beneficiary = Some(m.value_of("BENEFICIARY")
        .ok_or("beneficiary is not defined.".to_string())?);
    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"))
        }
    };
    let wperiod = u32::from_str_radix(withdrawal_period.unwrap(), 10)
        .map_err(|e| format!("invalid withdrawal period: {}", e))
        .and_then(period_checker)?;
    let tperiod = u32::from_str_radix(total_period.unwrap(), 10)
        .map_err(|e| format!("invalid total period: {}", e))
        .and_then(period_checker)?;
    let wperiod = wperiod * 86400;
    let tperiod = tperiod * 86400;
    add_exotic_stake(cmd, beneficiary.unwrap(), wperiod, tperiod, is_vesting).await
}

async fn remove_stake_command(
    m: &ArgMatches<'_>,
    cmd: CommandData<'_>,
) -> Result<(), String> {
    let (depool, wallet, stake, keys) = (Some(&cmd.depool), Some(&cmd.wallet), Some(cmd.stake), Some(&cmd.keys));
    print_args!(m, depool, wallet, stake, keys);
   remove_stake(cmd).await
}

async fn withdraw_stake_command(
    m: &ArgMatches<'_>,
    cmd: CommandData<'_>,
) -> Result<(), String> {
    let (depool, wallet, stake, keys) = (Some(&cmd.depool), Some(&cmd.wallet), Some(cmd.stake), Some(&cmd.keys));
    print_args!(m, depool, wallet, stake, keys);
   withdraw_stake(cmd).await
}

async fn set_withdraw_command(
    m: &ArgMatches<'_>,
    conf: Config,
    depool: &str,
    wallet: &str,
    keys: &str,
    enable: bool,
) -> Result<(), String> {
    let (depool, wallet, keys) = (Some(depool), Some(wallet), Some(keys));
    let withdraw = Some(if enable { "true" } else { "false" });
    print_args!(m, depool, wallet, keys, withdraw);
    set_withdraw(conf, depool.unwrap(), wallet.unwrap(), keys.unwrap(), enable).await
}

async fn add_ordinary_stake(cmd: CommandData<'_>) -> Result<(), String> {
    let stake = u64::from_str_radix(&convert::convert_token(cmd.stake)?, 10)
        .map_err(|e| format!(r#"failed to parse stake value: {}"#, e))?;
    let body = encode_add_ordinary_stake(stake).await?;
    let fee = u64::from_str_radix(&convert::convert_token(&cmd.depool_fee)?, 10)
        .map_err(|e| format!(r#"failed to parse depool fee value: {}"#, e))?;
    let value = (fee + stake) as f64 * 1.0 / 1e9;

    send_with_body(cmd.conf, &cmd.wallet, &cmd.depool, &format!("{}", value), &cmd.keys, &body).await
}

async fn replenish_stake(cmd: CommandData<'_>) -> Result<(), String> {
    let body = encode_replenish_stake().await?;
    send_with_body(cmd.conf, &cmd.wallet, &cmd.depool, cmd.stake, &cmd.keys, &body).await
}

async fn call_ticktock(
    conf: Config,
    depool: &str,
    wallet: &str,
    keys: &str,
) -> Result<(), String> {
    let body = encode_ticktock().await?;
    send_with_body(conf, wallet, depool, "1", keys, &body).await
}

async fn add_exotic_stake(
    cmd: CommandData<'_>,
    beneficiary: &str,
    wp: u32,
    tp: u32,
    is_vesting: bool,
) -> Result<(), String> {
    let beneficiary = load_ton_address(beneficiary, &cmd.conf)?;
    let stake = u64::from_str_radix(&convert::convert_token(cmd.stake)?, 10)
        .map_err(|e| format!(r#"failed to parse stake value: {}"#, e))?;
    let body = if is_vesting {
        encode_add_vesting_stake(stake, beneficiary.as_str(), tp, wp).await?
    } else {
        encode_add_lock_stake(stake, beneficiary.as_str(), tp, wp).await?
    };
    let fee = u64::from_str_radix(&convert::convert_token(&cmd.depool_fee)?, 10)
        .map_err(|e| format!(r#"failed to parse depool fee value: {}"#, e))?;
    let value = (fee + stake) as f64 * 1.0 / 1e9;
    send_with_body(cmd.conf, &cmd.wallet, &cmd.depool, &format!("{}", value), &cmd.keys, &body).await
}

async fn remove_stake(
    cmd: CommandData<'_>,
) -> Result<(), String> {
    let stake = u64::from_str_radix(
        &convert::convert_token(cmd.stake)?, 10,
    ).unwrap();
    let body = encode_remove_stake(stake).await?;
    send_with_body(cmd.conf, &cmd.wallet, &cmd.depool, &cmd.depool_fee, &cmd.keys, &body).await
}

async fn withdraw_stake(
    cmd: CommandData<'_>,
) -> Result<(), String> {
    let stake = u64::from_str_radix(
        &convert::convert_token(cmd.stake)?, 10,
    ).unwrap();
    let body = encode_withdraw_stake(stake).await?;
    send_with_body(cmd.conf, &cmd.wallet, &cmd.depool, &cmd.depool_fee, &cmd.keys, &body).await
}

async fn transfer_stake(cmd: CommandData<'_>, dest: &str) -> Result<(), String> {
    let dest = load_ton_address(dest, &cmd.conf)?;
    let stake = u64::from_str_radix(
        &convert::convert_token(cmd.stake)?, 10,
    ).unwrap();
    let body = encode_transfer_stake(dest.as_str(), stake).await?;
    send_with_body(cmd.conf, &cmd.wallet, &cmd.depool, &cmd.depool_fee, &cmd.keys, &body).await
}

async fn set_withdraw(
    conf: Config,
    depool: &str,
    wallet: &str,
    keys: &str,
    enable: bool,
) -> Result<(), String> {
    let body = encode_set_withdraw(enable).await?;
    let value = conf.depool_fee.to_string();
    send_with_body(conf, wallet, depool, &value, keys, &body).await
}

async fn set_donor(
    conf: Config,
    depool: &str,
    wallet: &str,
    keys: &str,
    is_vesting: bool,
    donor: &str,
) -> Result<(), String> {
    let body = encode_set_donor(is_vesting, donor).await?;
    let value = conf.depool_fee.to_string();
    send_with_body(conf, wallet, depool, &value, keys, &body).await
}

async fn encode_body(func: &str, params: serde_json::Value) -> Result<String, String> {
    let client = create_client_local()?;
    ton_client::abi::encode_message_body(
        client.clone(),
        ParamsOfEncodeMessageBody {
            abi: load_abi(DEPOOL_ABI)?,
            call_set: CallSet::some_with_function_and_input(func, params).unwrap(),
            is_internal: true,
            ..Default::default()
        },
    ).await
    .map_err(|e| format!("failed to encode body: {}", e))
    .map(|r| r.body)
}

async fn encode_set_withdraw(flag: bool) -> Result<String, String> {
    if flag {
        encode_body("withdrawAll", json!({}))
    } else {
        encode_body("cancelWithdrawal", json!({}))
    }.await
}

async fn encode_add_ordinary_stake(stake: u64) -> Result<String, String> {
	encode_body("addOrdinaryStake", json!({
        "stake": stake
    })).await
}

async fn encode_replenish_stake() -> Result<String, String> {
	encode_body("receiveFunds", json!({})).await
}

async fn encode_ticktock() -> Result<String, String> {
	encode_body("ticktock", json!({})).await
}

async fn encode_add_vesting_stake(
    stake: u64,
    beneficiary: &str,
    tperiod: u32,
    wperiod: u32,
) -> Result<String, String> {
	encode_body("addVestingStake", json!({
        "stake": stake,
        "beneficiary": beneficiary,
        "withdrawalPeriod": wperiod,
        "totalPeriod": tperiod
    })).await
}

async fn encode_set_donor(is_vesting: bool, donor: &str) -> Result<String, String> {
    if is_vesting {
        encode_body("setVestingDonor", json!({
            "donor": donor
        }))
    } else {
        encode_body("setLockDonor", json!({
            "donor": donor
        }))
    }.await
}

async fn encode_add_lock_stake(
    stake: u64,
    beneficiary: &str,
    tperiod: u32,
    wperiod: u32,
) -> Result<String, String> {
	encode_body("addLockStake", json!({
        "stake": stake,
        "beneficiary": beneficiary,
        "withdrawalPeriod": wperiod,
        "totalPeriod": tperiod
    })).await
}

async fn encode_remove_stake(target_value: u64) -> Result<String, String> {
	encode_body("withdrawFromPoolingRound", json!({
        "withdrawValue": target_value
    })).await
}

async fn encode_withdraw_stake(target_value: u64) -> Result<String, String> {
	encode_body("withdrawPart", json!({
        "withdrawValue": target_value
    })).await
}

async fn encode_transfer_stake(dest: &str, amount: u64) -> Result<String, String> {
	encode_body("transferStake", json!({
        "dest": dest,
        "amount": amount
    })).await
}
<file_sep>/src/account.rs
/*
* 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.
*/
use crate::helpers::create_client_verbose;
use crate::config::Config;
use serde_json::json;
use ton_client::net::{ParamsOfQueryCollection, query_collection};

const ACCOUNT_FIELDS: &str = r#"
    acc_type_name
    balance(format: DEC)
    last_paid
    last_trans_lt
    data
"#;

pub async fn get_account(conf: Config, addr: &str) -> Result<(), String> {
    let ton = create_client_verbose(&conf)?;

    if !conf.is_json {
        println!("Processing...");
    }
    let query_result = query_collection(
        ton.clone(),
        ParamsOfQueryCollection {
            collection: "accounts".to_owned(),
            filter: Some(json!({ "id": { "eq": addr } })),
            result: ACCOUNT_FIELDS.to_string(),
            limit: Some(1),
            ..Default::default()
        },
    ).await.map_err(|e| format!("failed to query account info: {}", e))?;
    let accounts = query_result.result;
    if !conf.is_json {
        println!("Succeeded.");
    }

    if conf.is_json {
        println!("{{");
        if accounts.len() == 1 {
            let acc = &accounts[0];
            let acc_type = acc["acc_type_name"].as_str().unwrap();
            if acc_type != "NonExist" {
                println!("  \"acc_type\": \"{}\",", acc_type);
                let balance_str = &acc["balance"].as_str().unwrap();
                println!("  \"balance\": \"{}\",", u64::from_str_radix(balance_str, 10).unwrap());
                println!("  \"last_paid\": \"{}\",", acc["last_paid"].as_u64().unwrap());
                println!("  \"last_trans_lt\": \"{}\",", acc["last_trans_lt"].as_str().unwrap());
                let data_str = acc["data"].as_str();
                if data_str.is_some() {
                    let data_vec = base64::decode(data_str.unwrap()).unwrap();
                    println!("  \"data(boc)\": \"{}\"", hex::encode(&data_vec));
                } else {
                    println!("  \"data(boc)\": \"null\"");
                }
            } else {
                println!("  \"acc_type\": \"{}\"", acc_type);
            }
        }
        println!("}}");
    } else {
        if accounts.len() == 1 {
            let acc = &accounts[0];
            let acc_type = acc["acc_type_name"].as_str().unwrap();
            if acc_type != "NonExist" {
                println!("acc_type:      {}", acc_type);
                let balance_str = &acc["balance"].as_str().unwrap();
                println!("balance:       {}", u64::from_str_radix(balance_str, 10).unwrap());
                println!("last_paid:     {}", acc["last_paid"].as_u64().unwrap());
                println!("last_trans_lt: {}", acc["last_trans_lt"].as_str().unwrap());
                let data_str = acc["data"].as_str();
                if data_str.is_some() {
                    let data_vec = base64::decode(data_str.unwrap()).unwrap();
                    println!("data(boc): {}", hex::encode(&data_vec));
                } else {
                    println!("data(boc): null");
                }
            } else {
                println!("Account does not exist.");    
            }
        } else {
            println!("Account not found.");
        }
    }
    Ok(())
}<file_sep>/src/helpers.rs
/*
 * 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.
 */
use crate::config::Config;
use log;
use std::sync::Arc;
use std::time::SystemTime;
use ton_client::abi::{
    Abi, AbiConfig, AbiContract, DecodedMessageBody, DeploySet, ParamsOfDecodeMessageBody,
    ParamsOfEncodeMessage, Signer,
};
use ton_client::crypto::{CryptoConfig, KeyPair};
use ton_client::error::ClientError;
use ton_client::net::{query_collection, OrderBy, ParamsOfQueryCollection};
use ton_client::{ClientConfig, ClientContext};


const TEST_MAX_LEVEL: log::LevelFilter = log::LevelFilter::Debug;
const MAX_LEVEL: log::LevelFilter = log::LevelFilter::Warn;

pub const HD_PATH: &str = "m/44'/396'/0'/0/0";
pub const WORD_COUNT: u8 = 12;

struct SimpleLogger;

impl log::Log for SimpleLogger {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        metadata.level() < MAX_LEVEL
    }

    fn log(&self, record: &log::Record) {
        match record.level() {
            log::Level::Error | log::Level::Warn => {
                eprintln!("{}", record.args());
            }
            _ => {
                println!("{}", record.args());
            }
        }
    }

    fn flush(&self) {}
}

pub fn read_keys(filename: &str) -> Result<KeyPair, String> {
    let keys_str = std::fs::read_to_string(filename)
        .map_err(|e| format!("failed to read keypair file: {}", e.to_string()))?;
    let keys: KeyPair = serde_json::from_str(&keys_str).unwrap();
    Ok(keys)
}

pub fn load_ton_address(addr: &str, conf: &Config) -> Result<String, 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!("Address is specified in the wrong format. Error description: {}", e))?;
    Ok(addr)
}

pub fn now() -> u32 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap()
        .as_secs() as u32
}

pub type TonClient = Arc<ClientContext>;

pub fn create_client_local() -> Result<TonClient, String> {
    let cli = ClientContext::new(ClientConfig::default())
        .map_err(|e| format!("failed to create tonclient: {}", e))?;
    Ok(Arc::new(cli))
}

pub fn create_client(conf: &Config) -> Result<TonClient, String> {
    let cli_conf = ClientConfig {
        abi: AbiConfig {
            workchain: conf.wc,
            message_expiration_timeout: conf.timeout,
            message_expiration_timeout_grow_factor: 1.3,
        },
        crypto: CryptoConfig {
            mnemonic_dictionary: 1,
            mnemonic_word_count: WORD_COUNT,
            hdkey_derivation_path: HD_PATH.to_string(),
        },
        network: ton_client::net::NetworkConfig {
            server_address: Some(conf.url.to_owned()),
            network_retries_count: 3,
            message_retries_count: conf.retries as i8,
            message_processing_timeout: 30000,
            wait_for_timeout: 30000,
            out_of_sync_threshold: (conf.timeout / 2),
            max_reconnect_timeout: 1000,
            ..Default::default()
        },
        boc: Default::default(),
    };
    let cli =
        ClientContext::new(cli_conf).map_err(|e| format!("failed to create tonclient: {}", e))?;
    Ok(Arc::new(cli))
}

pub fn create_client_verbose(conf: &Config) -> Result<TonClient, String> {
    if !conf.is_json {
        println!("Connecting to {}", conf.url);
    }

    let level = if std::env::var("RUST_LOG")
        .unwrap_or_default()
        .eq_ignore_ascii_case("debug")
    {
        TEST_MAX_LEVEL
    } else {
        MAX_LEVEL
    };
    log::set_max_level(level);
    log::set_boxed_logger(Box::new(SimpleLogger))
        .map_err(|e| format!("failed to init logger: {}", e))?;

    create_client(conf)
}

pub async fn query(
    ton: TonClient,
    collection: &str,
    filter: serde_json::Value,
    result: &str,
    order: Option<Vec<OrderBy>>,
) -> Result<Vec<serde_json::Value>, ClientError> {
    query_collection(
        ton,
        ParamsOfQueryCollection {
            collection: collection.to_owned(),
            filter: Some(filter),
            result: result.to_owned(),
            order,
            ..Default::default()
        },
    )
    .await
    .map(|r| r.result)
}

pub async fn decode_msg_body(
    ton: TonClient,
    abi: &str,
    body: &str,
    is_internal: bool,
) -> Result<DecodedMessageBody, String> {
    let abi = load_abi(abi)?;
    ton_client::abi::decode_message_body(
        ton,
        ParamsOfDecodeMessageBody {
            abi,
            body: body.to_owned(),
            is_internal,
            ..Default::default()
        },
    )
    .await
    .map_err(|e| format!("failed to decode body: {}", e))
}

pub fn load_abi(abi: &str) -> Result<Abi, String> {
    Ok(Abi::Contract(
        serde_json::from_str::<AbiContract>(abi)
            .map_err(|e| format!("ABI is not a valid json: {}", e))?,
    ))
}

pub async fn calc_acc_address(
    tvc: &[u8],
    wc: i32,
    pubkey: String,
    init_data: Option<&str>,
    abi: Abi,
) -> Result<String, String> {
    let ton = create_client_local()?;

    let init_data_json = init_data
        .map(|d| serde_json::from_str(d))
        .transpose()
        .map_err(|e| format!("initial data is not in json: {}", e))?;

    let dset = DeploySet {
        tvc: base64::encode(tvc),
        workchain_id: Some(wc),
        initial_data: init_data_json,
        ..Default::default()
    };
    let result = ton_client::abi::encode_message(
        ton.clone(),
        ParamsOfEncodeMessage {
            abi,
            deploy_set: Some(dset),
            signer: Signer::External {
                public_key: pubkey,
            },
            ..Default::default()
        },
    )
    .await
    .map_err(|e| format!("cannot generate address: {}", e))?;
    Ok(result.address)
}
