Skip to content

Commit

Permalink
Add high level support for sending funds from multisig (#27)
Browse files Browse the repository at this point in the history
- Added top level command `multisig`
- Added subcommand `send` to tranfer funds from multisig to recepient
- Allowed to add string with purpose of payment
  • Loading branch information
Keshoid authored Jul 10, 2020
1 parent e8e2f83 commit ac54bfc
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 162 deletions.
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"]
edition = "2018"
version = "0.1.9"
version = "0.1.10"

[dependencies]
base64 = "0.10.1"
Expand Down
10 changes: 8 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ mod convert;
mod crypto;
mod deploy;
mod genaddr;
mod getconfig;
mod helpers;
mod multisig;
mod voting;
mod getconfig;

use account::get_account;
use call::{call_contract, call_contract_with_msg, generate_message, parse_params, run_get_method};
Expand All @@ -36,6 +37,7 @@ use crypto::{generate_mnemonic, extract_pubkey, generate_keypair};
use deploy::deploy_contract;
use genaddr::generate_address;
use getconfig::query_global_config;
use multisig::{create_multisig_command, multisig_command};
use std::{env, path::PathBuf};
use voting::{create_proposal, decode_proposal, vote};

Expand Down Expand Up @@ -267,6 +269,7 @@ fn main_internal() -> Result <(), String> {
(@arg ID: +required +takes_value "Proposal transaction id.")
)
)
(subcommand: create_multisig_command())
(@subcommand getconfig =>
(about: "Reads global configuration parameter with defined index.")
(@arg INDEX: +required +takes_value "Parameter index.")
Expand Down Expand Up @@ -353,6 +356,9 @@ fn main_internal() -> Result <(), String> {
return proposal_decode_command(m, conf);
}
}
if let Some(m) = matches.subcommand_matches("multisig") {
return multisig_command(m, conf);
}
if let Some(m) = matches.subcommand_matches("getconfig") {
return getconfig_command(m, conf);
}
Expand Down Expand Up @@ -651,4 +657,4 @@ fn nodeid_command(matches: &ArgMatches) -> Result<(), String> {
};
println!("{}", nodeid);
Ok(())
}
}
265 changes: 265 additions & 0 deletions src/multisig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/*
* 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::crypto::{SdkClient};
use crate::call;
use crate::config::Config;
use crate::convert;
use clap::{App, ArgMatches, SubCommand, Arg, AppSettings};
use serde_json;

pub const MSIG_ABI: &str = r#"{
"ABI version": 2,
"header": ["pubkey", "time", "expire"],
"functions": [
{
"name": "constructor",
"inputs": [
{"name":"owners","type":"uint256[]"},
{"name":"reqConfirms","type":"uint8"}
],
"outputs": [
]
},
{
"name": "acceptTransfer",
"inputs": [
{"name":"payload","type":"bytes"}
],
"outputs": [
]
},
{
"name": "sendTransaction",
"inputs": [
{"name":"dest","type":"address"},
{"name":"value","type":"uint128"},
{"name":"bounce","type":"bool"},
{"name":"flags","type":"uint8"},
{"name":"payload","type":"cell"}
],
"outputs": [
]
},
{
"name": "submitTransaction",
"inputs": [
{"name":"dest","type":"address"},
{"name":"value","type":"uint128"},
{"name":"bounce","type":"bool"},
{"name":"allBalance","type":"bool"},
{"name":"payload","type":"cell"}
],
"outputs": [
{"name":"transId","type":"uint64"}
]
},
{
"name": "confirmTransaction",
"inputs": [
{"name":"transactionId","type":"uint64"}
],
"outputs": [
]
},
{
"name": "isConfirmed",
"inputs": [
{"name":"mask","type":"uint32"},
{"name":"index","type":"uint8"}
],
"outputs": [
{"name":"confirmed","type":"bool"}
]
},
{
"name": "getParameters",
"inputs": [
],
"outputs": [
{"name":"maxQueuedTransactions","type":"uint8"},
{"name":"maxCustodianCount","type":"uint8"},
{"name":"expirationTime","type":"uint64"},
{"name":"minValue","type":"uint128"},
{"name":"requiredTxnConfirms","type":"uint8"}
]
},
{
"name": "getTransaction",
"inputs": [
{"name":"transactionId","type":"uint64"}
],
"outputs": [
{"components":[{"name":"id","type":"uint64"},{"name":"confirmationsMask","type":"uint32"},{"name":"signsRequired","type":"uint8"},{"name":"signsReceived","type":"uint8"},{"name":"creator","type":"uint256"},{"name":"index","type":"uint8"},{"name":"dest","type":"address"},{"name":"value","type":"uint128"},{"name":"sendFlags","type":"uint16"},{"name":"payload","type":"cell"},{"name":"bounce","type":"bool"}],"name":"trans","type":"tuple"}
]
},
{
"name": "getTransactions",
"inputs": [
],
"outputs": [
{"components":[{"name":"id","type":"uint64"},{"name":"confirmationsMask","type":"uint32"},{"name":"signsRequired","type":"uint8"},{"name":"signsReceived","type":"uint8"},{"name":"creator","type":"uint256"},{"name":"index","type":"uint8"},{"name":"dest","type":"address"},{"name":"value","type":"uint128"},{"name":"sendFlags","type":"uint16"},{"name":"payload","type":"cell"},{"name":"bounce","type":"bool"}],"name":"transactions","type":"tuple[]"}
]
},
{
"name": "getTransactionIds",
"inputs": [
],
"outputs": [
{"name":"ids","type":"uint64[]"}
]
},
{
"name": "getCustodians",
"inputs": [
],
"outputs": [
{"components":[{"name":"index","type":"uint8"},{"name":"pubkey","type":"uint256"}],"name":"custodians","type":"tuple[]"}
]
}
],
"data": [
],
"events": [
{
"name": "TransferAccepted",
"inputs": [
{"name":"payload","type":"bytes"}
],
"outputs": [
]
}
]
}"#;

pub const TRANSFER_WITH_COMMENT: &str = r#"{
"ABI version": 1,
"functions": [
{
"name": "transfer",
"id": "0x00000000",
"inputs": [{"name":"comment","type":"bytes"}],
"outputs": []
}
],
"events": [],
"data": []
}"#;

pub fn create_multisig_command<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("multisig")
.about("Multisignature wallet commands.")
.setting(AppSettings::AllowLeadingHyphen)
.setting(AppSettings::TrailingVarArg)
.setting(AppSettings::DontCollapseArgsInUsage)
.subcommand(SubCommand::with_name("send")
.about("Transfer funds from multisignature wallet to recepient.")
.arg(Arg::with_name("ADDRESS")
.long("--addr")
.takes_value(true)
.help("Wallet address."))
.arg(Arg::with_name("DEST")
.long("--dest")
.takes_value(true)
.help("Recepient address."))
.arg(Arg::with_name("VALUE")
.long("--value")
.takes_value(true)
.help("Amount of funds to transfer."))
.arg(Arg::with_name("PURPOSE")
.long("--purpose")
.takes_value(true)
.help("Purpose of payment."))
.arg(Arg::with_name("SIGN")
.long("--sign")
.takes_value(true)
.help("Path to keys or seed phrase.")))
}

pub fn multisig_command(m: &ArgMatches, config: Config) -> Result<(), String> {
if let Some(m) = m.subcommand_matches("send") {
return multisig_send_command(m, config);
}
Err("unknown multisig command".to_owned())
}

fn multisig_send_command(matches: &ArgMatches, config: Config) -> Result<(), String> {
let address = matches.value_of("ADDRESS")
.ok_or(format!("--addr parameter is not defined"))?;
let dest = matches.value_of("DEST")
.ok_or(format!("--dst parameter is not defined"))?;
let keys = matches.value_of("SIGN")
.ok_or(format!("--sign parameter is not defined"))?;
let value = matches.value_of("VALUE")
.ok_or(format!("--value parameter is not defined"))?;
let comment = matches.value_of("PURPOSE");

send(config, address, dest, value, keys, comment)
}

pub fn encode_transfer_body(text: &str) -> Result<String, String> {
let text = hex::encode(text.as_bytes());
let client = SdkClient::new();
let abi: serde_json::Value = serde_json::from_str(TRANSFER_WITH_COMMENT).unwrap();
client.request(
"contracts.run.body",
json!({
"abi": abi,
"function": "transfer",
"params": json!({
"comment": text
}),
"internal": true,
})
)
}

fn send(
conf: Config,
addr: &str,
dest: &str,
value: &str,
keys: &str,
comment: Option<&str>
) -> Result<(), String> {
let body = if let Some(text) = comment {
let msg_body: serde_json::Value =
serde_json::from_str(&encode_transfer_body(text)?)
.map_err(|e| format!("failed to encode comment: {}", e))?;

msg_body.get("bodyBase64")
.ok_or(format!(r#"internal error: "bodyBase64" not found in sdk call result"#))?
.as_str()
.ok_or(format!(r#"internal error: "bodyBase64" field is not a string"#))?
.to_owned()
} else {
"".to_owned()
};

let params = json!({
"dest": dest,
"value": convert::convert_token(value)?,
"bounce": true,
"allBalance": false,
"payload": body,
}).to_string();

call::call_contract(
conf,
addr,
MSIG_ABI.to_string(),
"submitTransaction",
&params,
Some(keys.to_owned()),
false
)
}
Loading

0 comments on commit ac54bfc

Please sign in to comment.