Skip to content

Commit

Permalink
Debot interfaces (#94)
Browse files Browse the repository at this point in the history
* wip: support for DInterfaces

* support echo and stdout ifaces

* Switch cli to debot dev sdk branch

* Refactor supported interfaces

* Add AddressInput iface

* Add Terminal interface

* Switch to SDK branch debot-getters

* Update Terminal iface

* Support for multiline input

* Add Menu iface

* FIx after rebase

* remove printf from Terminal

* Fix multiline input: Add new line char

* Update ver

* Fix cli after switching to master sdk
  • Loading branch information
Keshoid authored Feb 9, 2021
1 parent c978665 commit 67ac6c0
Show file tree
Hide file tree
Showing 19 changed files with 818 additions and 109 deletions.
3 changes: 1 addition & 2 deletions 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.4.0"
version = "0.5.0"

[dependencies]
async-trait = "0.1.42"
Expand All @@ -35,7 +35,6 @@ ton_sdk = { git = 'https://github.com/tonlabs/TON-SDK.git' }
ton_types = { git = "https://github.com/tonlabs/ton-labs-types.git" }
ton_block = { git = "https://github.com/tonlabs/ton-labs-block.git" }


[dev-dependencies]
assert_cmd = "0.11"
predicates = "1"
Expand Down
30 changes: 17 additions & 13 deletions src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,22 @@ async fn prepare_message(
let params = serde_json::from_str(&params)
.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 {
abi,
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
},
Expand All @@ -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),
Expand Down Expand Up @@ -121,7 +121,7 @@ fn pack_message(msg: &EncodedMessage, method: &str, is_raw: bool) -> Vec<u8> {
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))?;

Expand All @@ -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,
Expand All @@ -178,7 +180,7 @@ fn parse_integer_param(value: &str) -> Result<String, String> {
fn build_json_from_params(params_vec: Vec<&str>, abi: &str, method: &str) -> Result<String, String> {
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();

Expand Down Expand Up @@ -257,14 +259,16 @@ 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))?;
Ok(result.decoded.and_then(|d| d.output).unwrap_or(json!({})))
} else {
println!("Processing... ");
let callback = |_| {
async move {}
async move {}
};

let result = send_message(
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(())
Expand Down
6 changes: 3 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down
73 changes: 73 additions & 0 deletions src/debot/interfaces/address_input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use crate::debot::term_browser::terminal_input;
use crate::helpers::load_ton_address;
use serde_json::Value;
use ton_client::abi::Abi;
use ton_client::debot::{DebotInterface, InterfaceResult};
use super::dinterface::decode_answer_id;
use crate::config::Config;

const ID: &'static str = "d7ed1bd8e6230871116f4522e58df0a93c5520c56f4ade23ef3d8919a984653b";

pub const ABI: &str = r#"
{
"ABI version": 2,
"header": ["time"],
"functions": [
{
"name": "select",
"inputs": [
{"name":"answerId","type":"uint32"}
],
"outputs": [
{"name":"value","type":"address"}
]
},
{
"name": "constructor",
"inputs": [
],
"outputs": [
]
}
],
"data": [
],
"events": [
]
}
"#;

pub struct AddressInput {
conf: Config
}
impl AddressInput {
pub fn new(conf: Config) -> Self {
Self {conf}
}
fn select(&self, args: &Value) -> InterfaceResult {
let answer_id = decode_answer_id(args)?;
let value = terminal_input("", |val| {
let _ = load_ton_address(val, &self.conf).map_err(|e| format!("Invalid address: {}", e))?;
Ok(())
});
Ok((answer_id, json!({ "value": value })))
}
}

#[async_trait::async_trait]
impl DebotInterface for AddressInput {
fn get_id(&self) -> String {
ID.to_string()
}

fn get_abi(&self) -> Abi {
Abi::Json(ABI.to_owned())
}

async fn call(&self, func: &str, args: &Value) -> InterfaceResult {
match func {
"select" => self.select(args),
_ => Err(format!("function \"{}\" is not implemented", func)),
}
}
}
84 changes: 84 additions & 0 deletions src/debot/interfaces/dinterface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use super::address_input::AddressInput;
use super::echo::Echo;
use super::stdout::Stdout;
use super::terminal::Terminal;
use super::menu::Menu;
use crate::config::Config;
use crate::helpers::TonClient;
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use ton_client::debot::{DebotInterface, DebotInterfaceExecutor};

pub struct SupportedInterfaces {
client: TonClient,
interfaces: HashMap<String, Arc<dyn DebotInterface + Send + Sync>>,
}

#[async_trait::async_trait]
impl DebotInterfaceExecutor for SupportedInterfaces {
fn get_interfaces<'a>(&'a self) -> &'a HashMap<String, Arc<dyn DebotInterface + Send + Sync>> {
&self.interfaces
}
fn get_client(&self) -> TonClient {
self.client.clone()
}
}

impl SupportedInterfaces {
pub fn new(client: TonClient, conf: &Config) -> Self {
let mut interfaces = HashMap::new();

let iface: Arc<dyn DebotInterface + Send + Sync> = Arc::new(AddressInput::new(conf.clone()));
interfaces.insert(iface.get_id(), iface);

let iface: Arc<dyn DebotInterface + Send + Sync> = Arc::new(Stdout::new());
interfaces.insert(iface.get_id(), iface);

let iface: Arc<dyn DebotInterface + Send + Sync> = Arc::new(Echo::new());
interfaces.insert(iface.get_id(), iface);

let iface: Arc<dyn DebotInterface + Send + Sync> = Arc::new(Terminal::new());
interfaces.insert(iface.get_id(), iface);

let iface: Arc<dyn DebotInterface + Send + Sync> = Arc::new(Menu::new());
interfaces.insert(iface.get_id(), iface);

Self { client, interfaces }
}
}

pub fn decode_answer_id(args: &Value) -> Result<u32, String> {
u32::from_str_radix(
args["answerId"]
.as_str()
.ok_or(format!("answer id not found in argument list"))?,
10,
)
.map_err(|e| format!("{}", e))
}

pub fn decode_arg(args: &Value, name: &str) -> Result<String, String> {
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<bool, String> {
args[name]
.as_bool()
.ok_or(format!("\"{}\" not found", name))
}

pub fn decode_string_arg(args: &Value, name: &str) -> Result<String, String> {
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<String, String> {
decode_string_arg(args, "prompt")
}
68 changes: 68 additions & 0 deletions src/debot/interfaces/echo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use serde_json::Value;
use ton_client::debot::{DebotInterface, InterfaceResult};
use ton_client::abi::Abi;

const ECHO_ID: &'static str = "f6927c0d4bdb69e1b52d27f018d156ff04152f00558042ff674f0fec32e4369d";

pub const ECHO_ABI: &str = r#"
{
"ABI version": 2,
"header": ["time"],
"functions": [
{
"name": "echo",
"inputs": [
{"name":"answerId","type":"uint32"},
{"name":"request","type":"bytes"}
],
"outputs": [
{"name":"response","type":"bytes"}
]
},
{
"name": "constructor",
"inputs": [
],
"outputs": [
]
}
],
"data": [
],
"events": [
]
}
"#;

pub struct Echo {}
impl Echo {

pub fn new() -> Self {
Self{}
}

fn echo(&self, args: &Value) -> InterfaceResult {
let answer_id = u32::from_str_radix(args["answerId"].as_str().unwrap(), 10).unwrap();
let request_vec = hex::decode(args["request"].as_str().unwrap()).unwrap();
let request = std::str::from_utf8(&request_vec).unwrap();
Ok(( answer_id, json!({ "response": hex::encode(request.as_bytes()) }) ))
}
}

#[async_trait::async_trait]
impl DebotInterface for Echo {
fn get_id(&self) -> String {
ECHO_ID.to_string()
}

fn get_abi(&self) -> Abi {
Abi::Json(ECHO_ABI.to_owned())
}

async fn call(&self, func: &str, args: &Value) -> InterfaceResult {
match func {
"echo" => self.echo(args),
_ => Err(format!("function \"{}\" is not implemented", func)),
}
}
}
Loading

0 comments on commit 67ac6c0

Please sign in to comment.