Skip to content

Commit

Permalink
Merge pull request #147 from tonlabs/run_local
Browse files Browse the repository at this point in the history
Added preliminary local run before call and deploy
  • Loading branch information
SilkovAlexander authored May 12, 2021
2 parents 4062942 + c2d2b9d commit c79043f
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 28 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ This fixes affects all debots invoking other debots (e.g. depool debot, mludi de
## v0.11.5

### Improvements
- `tonos-cli` now provides several different endpoints for main.ton.dev and net.ton.dev in order to improve reliability.
- `tonos-cli` now provides several different endpoints for main.ton.dev and net.ton.dev in order to improve reliability.

## v0.12.0

### New feature
- tonos-cli now can execute the transaction locally for deploy and call commands before executing
it onchain. If local execution fails, onchain execution is not performed. Local run can be
enabled by setting the flag `local_run` in the tonos-cli config.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ readme = "README.md"
license = "Apache-2.0"
keywords = ["TON", "SDK", "smart contract", "tonlabs", "solidity"]
edition = "2018"
version = "0.11.6"
version = "0.12.0"

[dependencies]
async-trait = "0.1.42"
Expand Down
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,22 @@ in the most part of subcommands.

List of possible parameters:

--abi <ABI> File with contract ABI.
--addr <ADDR> Contract address.
--depool_fee <DEPOOL_FEE> Value added to message sent to depool to cover it's fees (change will be returned).
--keys <KEYS> File with keypair.
--lifetime <LIFETIME> Period of time in seconds while message is valid.
--no-answer <NO_ANSWER> FLag whether to wait for depool answer when calling a depool function.
--retries <RETRIES> Number of attempts to call smart contract function if previous attempt was
unsuccessful.
--timeout <TIMEOUT> Contract call timeout in ms.
--url <URL> Url to connect.
--wallet <WALLET> Multisig wallet address. Used in commands which send internal messages through
multisig wallets.
--wc <WC> Workchain id.
--abi <ABI> File with contract ABI.
--addr <ADDR> Contract address.
--depool_fee <DEPOOL_FEE> Value added to message sent to depool to cover it's fees (change will be
returned).
--keys <KEYS> File with keypair.
--lifetime <LIFETIME> Period of time in seconds while message is valid.
--local_run <LOCAL_RUN> Enable preliminary local run before deploy and call commands.
--no-answer <NO_ANSWER> FLag whether to wait for depool answer when calling a depool function.
--retries <RETRIES> Number of attempts to call smart contract function if previous attempt was
unsuccessful.
--timeout <TIMEOUT> Contract call timeout in ms.
--url <URL> Url to connect.
--delimiters <USE_DELIMITERS> Use delimiters while printing account balance
--wallet <WALLET> Multisig wallet address. Used in commands which send internal messages through
multisig wallets.
--wc <WC> Workchain id.
Also user can clear one or all parameters from the config file (it will set the default value for
them if it is possible.
Expand Down
5 changes: 4 additions & 1 deletion src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const ACCOUNT_FIELDS: &str = r#"
last_paid
last_trans_lt
data
code_hash
"#;

pub async fn get_account(conf: Config, addr: &str) -> Result<(), String> {
Expand Down Expand Up @@ -62,6 +63,7 @@ pub async fn get_account(conf: Config, addr: &str) -> Result<(), String> {
} else {
println!(" \"data(boc)\": \"null\"");
}
println!(" code_hash: {}", acc["code_hash"].as_str().unwrap_or("null"));
} else {
println!(" \"acc_type\": \"{}\"", acc_type);
}
Expand Down Expand Up @@ -100,6 +102,7 @@ pub async fn get_account(conf: Config, addr: &str) -> Result<(), String> {
} else {
println!("data(boc): null");
}
println!("code_hash: {}", acc["code_hash"].as_str().unwrap_or("null"));
} else {
println!("Account does not exist.");
}
Expand All @@ -108,4 +111,4 @@ pub async fn get_account(conf: Config, addr: &str) -> Result<(), String> {
}
}
Ok(())
}
}
47 changes: 39 additions & 8 deletions src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use ton_client::processing::{
wait_for_transaction,
send_message,
};
use ton_client::tvm::{run_tvm, run_get, ParamsOfRunTvm, ParamsOfRunGet};
use ton_client::tvm::{run_tvm, run_get, ParamsOfRunTvm, ParamsOfRunGet, run_executor, ParamsOfRunExecutor, AccountForExecutor};
use ton_client::error::ClientError;

pub struct EncodedMessage {
Expand Down Expand Up @@ -245,6 +245,32 @@ async fn query_account_boc(ton: TonClient, addr: &str) -> Result<String, String>
Ok(boc.unwrap().to_owned())
}

pub async fn emulate_localy(
ton: TonClient,
addr: &str,
msg: String,
) -> Result<(), String> {
let state_boc = query_account_boc(ton.clone(), addr).await?;

let res = run_executor(
ton.clone(),
ParamsOfRunExecutor {
message: msg.clone(),
account: AccountForExecutor::Account {
boc: state_boc,
unlimited_balance: None,
},
..Default::default()
},
)
.await;

if res.is_err() {
return Err(format!("{:#}", res.err().unwrap()));
}
Ok(())
}

async fn send_message_and_wait(
ton: TonClient,
addr: &str,
Expand Down Expand Up @@ -353,33 +379,38 @@ pub async fn call_contract_with_result(
if !conf.is_json {
print_encoded_message(&msg);
}

let mut retry: bool = true;
let error_handler = |err: ClientError| {
// obtaining error code
let code = err.code.clone();
// but if it was simulated locally and local exit code is not zero,
// we ignore previous exit code because it means we shouldn't make a retry.
if !err.data["exit_code"].is_null() {
if err.data["exit_code"].as_i64().unwrap() != 0 {
if err.data["exit_code"].as_i64().unwrap_or(-1) != 0 {
retry = false;
}
}
// There is also another way how SDK can print local run results.
let local_error = err.data["local_error"]["data"]["exit_code"].clone();
if !local_error.is_null() {
if local_error.as_i64().unwrap() != 0 {
if local_error.as_i64().unwrap_or(-1) != 0 {
retry = false;
}
}
// if error code was 4XX then don't perform a retry.
let code = (code / 100) as u32 % 10;
if code == 4 {
// if error code was 4XX then don't perform a retry. Also if error was 508,
// it means that message could have been delivered after timeout and for not
// to cause double call we shouldn't perform a retry.
if (((code / 100) as u32 % 10) == 4) || (code == 508) {
retry = false;
}
};

if !local && conf.local_run {
emulate_localy(ton.clone(), addr, msg.message.clone()).await?;
}
let result = send_message_and_wait(ton.clone(), addr, abi.clone(), msg.message, local, conf.clone(), error_handler).await;

if result.is_ok() {
return result;
}
Expand Down
14 changes: 13 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub struct Config {
pub no_answer: bool,
#[serde(default = "default_false")]
pub use_delimiters: bool,
#[serde(default = "default_false")]
pub local_run: bool,
}

impl Config {
Expand All @@ -85,6 +87,7 @@ impl Config {
lifetime: default_lifetime(),
no_answer: default_true(),
use_delimiters: default_false(),
local_run: default_false(),
}
}

Expand All @@ -110,6 +113,7 @@ pub fn clear_config(
lifetime: bool,
no_answer: bool,
use_delimiters: bool,
local_run: bool,
) -> Result<(), String> {
if url {
conf.url = default_url();
Expand Down Expand Up @@ -147,9 +151,12 @@ pub fn clear_config(
if use_delimiters {
conf.use_delimiters = default_false();
}
if local_run {
conf.local_run = default_false();
}

if (url || addr || wallet || abi || keys || retries || timeout || wc || depool_fee || lifetime
|| no_answer || use_delimiters) == false {
|| no_answer || use_delimiters || local_run) == false {
conf = Config::new();
}
let conf_str = serde_json::to_string(&conf)
Expand All @@ -175,6 +182,7 @@ pub fn set_config(
lifetime: Option<&str>,
no_answer: Option<&str>,
use_delimiters: Option<&str>,
local_run: Option<&str>,
) -> Result<(), String> {
if let Some(s) = url {
conf.url = s.to_string();
Expand Down Expand Up @@ -222,6 +230,10 @@ pub fn set_config(
conf.use_delimiters = use_delimiters.parse::<bool>()
.map_err(|e| format!(r#"failed to parse "use_delimiters": {}"#, e))?;
}
if let Some(local_run) = local_run {
conf.local_run = local_run.parse::<bool>()
.map_err(|e| format!(r#"failed to parse "local_run": {}"#, e))?;
}

let conf_str = serde_json::to_string(&conf)
.map_err(|_| "failed to serialize config object".to_string())?;
Expand Down
9 changes: 8 additions & 1 deletion src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use crate::helpers::{create_client_verbose, create_client_local, load_abi, calc_acc_address};
use crate::config::Config;
use crate::crypto::load_keypair;
use crate::call::{EncodedMessage, display_generated_message};
use crate::call::{EncodedMessage, display_generated_message, emulate_localy};
use ton_client::processing::{ParamsOfProcessMessage};
use ton_client::abi::{
encode_message, Signer, CallSet, DeploySet, ParamsOfEncodeMessage
Expand All @@ -33,6 +33,13 @@ pub async fn deploy_contract(

let (msg, addr) = prepare_deploy_message(tvc, abi, params, keys_file, wc).await?;

let enc_msg = encode_message(ton.clone(), msg.clone()).await
.map_err(|e| format!("failed to create inbound message: {}", e))?;

if conf.local_run {
emulate_localy(ton.clone(), addr.as_str(), enc_msg.message).await?;
}

let callback = |_event| { async move { } };
ton_client::processing::process_message(
ton.clone(),
Expand Down
8 changes: 6 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ async fn main_internal() -> Result <(), String> {
(@arg LIFETIME: --lifetime +takes_value "Period of time in seconds while message is valid.")
(arg: no_answer_with_value)
(@arg USE_DELIMITERS: --delimiters +takes_value "Use delimiters while printing account balance")
(@arg LOCAL_RUN: --local_run +takes_value "Enable preliminary local run before deploy and call commands.")
(@subcommand clear =>
(@setting AllowLeadingHyphen)
(about: "Resets certain default values for options in the config file. Resets all values if used without options.")
Expand All @@ -307,6 +308,7 @@ async fn main_internal() -> Result <(), String> {
(@arg LIFETIME: --lifetime "Period of time in seconds while message is valid.")
(arg: no_answer)
(@arg USE_DELIMITERS: --delimiters "Use delimiters while printing account balance")
(@arg LOCAL_RUN: --local_run "Enable preliminary local run before deploy and call commands.")
)
)
(@subcommand account =>
Expand Down Expand Up @@ -734,7 +736,8 @@ fn config_command(matches: &ArgMatches, config: Config, config_file: String) ->
let lifetime = clear_matches.is_present("LIFETIME");
let no_answer = clear_matches.is_present("NO_ANSWER");
let use_delimiters = clear_matches.is_present("USE_DELIMITERS");
result = clear_config(config, config_file.as_str(), url, address, wallet, abi, keys, wc, retries, timeout, depool_fee, lifetime, no_answer, use_delimiters);
let local_run = clear_matches.is_present("LOCAL_RUN");
result = clear_config(config, config_file.as_str(), url, address, wallet, abi, keys, wc, retries, timeout, depool_fee, lifetime, no_answer, use_delimiters, local_run);
} else {
let url = matches.value_of("URL");
let address = matches.value_of("ADDR");
Expand All @@ -748,7 +751,8 @@ fn config_command(matches: &ArgMatches, config: Config, config_file: String) ->
let lifetime = matches.value_of("LIFETIME");
let no_answer = matches.value_of("NO_ANSWER");
let use_delimiters = matches.value_of("USE_DELIMITERS");
result = set_config(config, config_file.as_str(), url, address, wallet, abi, keys, wc, retries, timeout, depool_fee, lifetime, no_answer, use_delimiters);
let local_run = matches.value_of("LOCAL_RUN");
result = set_config(config, config_file.as_str(), url, address, wallet, abi, keys, wc, retries, timeout, depool_fee, lifetime, no_answer, use_delimiters, local_run);
}
}
let config = match Config::from_file(config_file.as_str()) {
Expand Down
Binary file modified tests/data.tvc
Binary file not shown.

0 comments on commit c79043f

Please sign in to comment.