From 181d337d6ebf5a0ef879735e89df11c4df5a52fb Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 09:47:11 +0200 Subject: [PATCH 01/16] util: refactored util.rs into util directory --- src/bin/darkfid.rs | 14 ++-- src/util/mod.rs | 9 +++ src/util/net_name.rs | 52 ++++++++++++++ src/util/parse.rs | 144 +++++++++++++++++++++++++++++++++++++ src/util/path.rs | 32 +++++++++ src/util/token_list.rs | 45 ++++++++++++ src/{util.rs => util/util} | 0 7 files changed, 290 insertions(+), 6 deletions(-) create mode 100644 src/util/mod.rs create mode 100644 src/util/net_name.rs create mode 100644 src/util/parse.rs create mode 100644 src/util/path.rs create mode 100644 src/util/token_list.rs rename src/{util.rs => util/util} (100%) diff --git a/src/bin/darkfid.rs b/src/bin/darkfid.rs index 7a9b1cb21..363965c42 100644 --- a/src/bin/darkfid.rs +++ b/src/bin/darkfid.rs @@ -17,9 +17,7 @@ use drk::{ rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig}, }, serial::{deserialize, serialize}, - util::{ - expand_path, join_config_path, parse_network, parse_params, parse_wrapped_token, search_id, - }, + util::{expand_path, join_config_path, parse_network, parse_wrapped_token, TokenList}, wallet::WalletDb, Result, }; @@ -29,6 +27,7 @@ struct Darkfid { config: DarkfidConfig, wallet: Arc, client: Arc>, + tokenlist: TokenList, } #[async_trait] @@ -81,10 +80,13 @@ impl Darkfid { )?; let client = Arc::new(Mutex::new(client)); + let tokenlist = TokenList::new()?; + Ok(Self { config, wallet, client, + tokenlist, }) } @@ -149,7 +151,7 @@ impl Darkfid { let symbol = symbol.unwrap(); let result: Result = async { - let token_id = search_id(symbol)?; + let token_id = self.tokenlist.clone().search_id(symbol)?; Ok(json!(token_id)) } .await; @@ -211,7 +213,7 @@ impl Darkfid { let network = network.as_str().unwrap(); - let token_id = match parse_network(&network, &token) { + let token_id = match parse_network(&network, &token, self.tokenlist.clone()) { Ok(t) => t, Err(_e) => { debug!(target: "DARKFID", "TOKEN ID IS ERR"); @@ -330,7 +332,7 @@ impl Darkfid { let amount = amount.as_f64().unwrap(); let result: Result<()> = async { - let token_id = parse_wrapped_token(token)?; + let token_id = parse_wrapped_token(token, self.tokenlist.clone())?; let address = bs58::decode(&address).into_vec()?; let address: jubjub::SubgroupPoint = deserialize(&address)?; self.client diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 000000000..292ae523a --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,9 @@ +pub mod net_name; +pub mod parse; +pub mod path; +pub mod token_list; + +pub use net_name::NetworkName; +pub use parse::{generate_id, parse_network, parse_params, parse_wrapped_token}; +pub use path::{expand_path, join_config_path}; +pub use token_list::TokenList; diff --git a/src/util/net_name.rs b/src/util/net_name.rs new file mode 100644 index 000000000..136b1e4f5 --- /dev/null +++ b/src/util/net_name.rs @@ -0,0 +1,52 @@ +use crate::{ + serial::{Decodable, Encodable}, + Result, +}; +use std::str::FromStr; + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum NetworkName { + Solana, + Bitcoin, +} + +impl std::fmt::Display for NetworkName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Solana => { + write!(f, "Solana") + } + Self::Bitcoin => { + write!(f, "Bitcoin") + } + } + } +} + +impl FromStr for NetworkName { + type Err = crate::Error; + + fn from_str(s: &str) -> std::result::Result { + match s.to_lowercase().as_str() { + "sol" | "solana" => Ok(NetworkName::Solana), + "btc" | "bitcoin" => Ok(NetworkName::Bitcoin), + _ => Err(crate::Error::NotSupportedNetwork), + } + } +} + +impl Encodable for NetworkName { + fn encode(&self, s: S) -> Result { + let name = self.to_string(); + let len = name.encode(s)?; + Ok(len) + } +} + +impl Decodable for NetworkName { + fn decode(mut d: D) -> Result { + let name: String = Decodable::decode(&mut d)?; + let name = NetworkName::from_str(&name)?; + Ok(name) + } +} diff --git a/src/util/parse.rs b/src/util/parse.rs new file mode 100644 index 000000000..9bc6c702f --- /dev/null +++ b/src/util/parse.rs @@ -0,0 +1,144 @@ +use crate::{ + serial::{deserialize, serialize}, + util::{NetworkName, TokenList}, + Error, Result, +}; + +use log::debug; +use sha2::{Digest, Sha256}; +use std::str::FromStr; + +//1. deposit(network, asset) +//internal ID = hash(externalID, NetworkName) +//deposit(internalID) + +//2. withdraw(network, asset, amount) +//internal ID = hash(externalID, NetworkName) +//amountu64 = amount.to_u64() +//withdraw(internalID, amount) + +//3. transfer(asset, amount, address) +//asset = { match [SOL, BTC, TOKEN] +// return token-id from wallet.db } +//amountu64 = amount.to_u64() +//address = jubjub::SubgroupPoint +//transfer(token_id, amountu64, address) +// +// + +// here we hash the alphanumeric token ID. if it fails, we change the last 4 bytes and hash it +// again, and keep repeating until it works. +pub fn generate_id(tkn_str: &str) -> Result { + if bs58::decode(tkn_str).into_vec().is_err() { + // TODO: make this an error + debug!(target: "PARSE ID", "COULD NOT DECODE STR"); + } + let mut data = bs58::decode(tkn_str).into_vec().unwrap(); + + let token_id = match deserialize::(&data) { + Ok(v) => v, + Err(_) => { + let mut counter = 0; + loop { + data.truncate(28); + let serialized_counter = serialize(&counter); + data.extend(serialized_counter.iter()); + let mut hasher = Sha256::new(); + hasher.update(&data); + let hash = hasher.finalize(); + let token_id = deserialize::(&hash); + if token_id.is_err() { + counter += 1; + continue; + } + debug!(target: "CASHIER", "DESERIALIZATION SUCCESSFUL"); + return Ok(token_id.unwrap()); + } + } + }; + + Ok(token_id) +} + +pub fn parse_wrapped_token(token: &str, tokenlist: TokenList) -> Result { + match token.to_lowercase().as_str() { + "sol" => { + let id = "So11111111111111111111111111111111111111112"; + let token_id = generate_id(id)?; + Ok(token_id) + } + "btc" => Err(Error::TokenParseError), + tkn => { + // (== 44) can represent a Solana base58 token mint address + let id = if token.len() == 44 { + token.to_string() + } else { + symbol_to_id(tkn, tokenlist)? + }; + + let token_id = generate_id(&id)?; + Ok(token_id) + } + } +} + +pub fn parse_network(network: &str, token: &str, tokenlist: TokenList) -> Result { + match NetworkName::from_str(network)? { + NetworkName::Solana => match token.to_lowercase().as_str() { + "solana" | "sol" => { + let token_id = "So11111111111111111111111111111111111111112"; + Ok(token_id.to_string()) + } + tkn => { + // (== 44) can represent a Solana base58 token mint address + let id = if token.len() == 44 { + token.to_string() + } else { + symbol_to_id(tkn, tokenlist)? + }; + Ok(id) + } + }, + NetworkName::Bitcoin => Err(Error::NetworkParseError), + } +} + +pub fn parse_params( + network: &str, + token: &str, + amount: u64, + tokenlist: TokenList, +) -> Result<(String, u64)> { + match NetworkName::from_str(network)? { + NetworkName::Solana => match token { + "solana" | "sol" => { + let token_id = "So11111111111111111111111111111111111111112"; + let decimals = 9; + let amount_in_apo = amount * u64::pow(10, decimals as u32); + Ok((token_id.to_string(), amount_in_apo)) + } + tkn => { + let token_id = symbol_to_id(tkn, tokenlist.clone())?; + let decimals = tokenlist.search_decimal(tkn)?; + let amount_in_apo = amount * u64::pow(10, decimals as u32); + Ok((token_id, amount_in_apo)) + } + }, + NetworkName::Bitcoin => Err(Error::NetworkParseError), + } +} + +pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { + let vec: Vec = token.chars().collect(); + let mut counter = 0; + for c in vec { + if c.is_alphabetic() { + counter += 1; + } + } + if counter == token.len() { + tokenlist.search_id(token) + } else { + Ok(token.to_string()) + } +} diff --git a/src/util/path.rs b/src/util/path.rs new file mode 100644 index 000000000..8efd5d8dc --- /dev/null +++ b/src/util/path.rs @@ -0,0 +1,32 @@ +use crate::Result; +use std::path::{Path, PathBuf}; + +pub fn expand_path(path: &str) -> Result { + let ret: PathBuf; + + if path.starts_with("~/") { + let homedir = dirs::home_dir().unwrap(); + let remains = PathBuf::from(path.strip_prefix("~/").unwrap()); + ret = [homedir, remains].iter().collect(); + } else if path.starts_with('~') { + ret = dirs::home_dir().unwrap(); + } else { + ret = PathBuf::from(path); + } + + Ok(ret) +} + +pub fn join_config_path(file: &Path) -> Result { + let mut path = PathBuf::new(); + let dfi_path = Path::new("darkfi"); + + if let Some(v) = dirs::config_dir() { + path.push(v); + } + + path.push(dfi_path); + path.push(file); + + Ok(path) +} diff --git a/src/util/token_list.rs b/src/util/token_list.rs new file mode 100644 index 000000000..c0ae0e9c1 --- /dev/null +++ b/src/util/token_list.rs @@ -0,0 +1,45 @@ +use crate::{Error, Result}; +use serde_json::Value; + +#[derive(Debug, Clone)] +pub struct TokenList { + tokenlist: Value, +} + +impl TokenList { + pub fn new() -> Result { + // TODO: FIXME + let file_contents = std::fs::read_to_string("token/solanatokenlist.json")?; + let tokenlist: Value = serde_json::from_str(&file_contents)?; + + Ok(Self { tokenlist }) + } + + pub fn search_id(self, symbol: &str) -> Result { + let tokens = self.tokenlist["tokens"] + .as_array() + .ok_or(Error::TokenParseError)?; + for item in tokens { + if item["symbol"] == symbol.to_uppercase() { + let address = item["address"].clone(); + let address = address.as_str().ok_or(Error::TokenParseError)?; + return Ok(address.to_string()); + } + } + unreachable!(); + } + + pub fn search_decimal(self, symbol: &str) -> Result { + let tokens = self.tokenlist["tokens"] + .as_array() + .ok_or(Error::TokenParseError)?; + for item in tokens { + if item["symbol"] == symbol.to_uppercase() { + let decimals = item["decimals"].clone(); + let decimals = decimals.as_u64().ok_or(Error::TokenParseError)?; + return Ok(decimals); + } + } + unreachable!(); + } +} diff --git a/src/util.rs b/src/util/util similarity index 100% rename from src/util.rs rename to src/util/util From a5f0e629ceeaf357da4f2b6fa4cf356c8fda9273 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 11:22:34 +0200 Subject: [PATCH 02/16] renamed parse_network to assign_id --- src/bin/darkfid.rs | 4 ++-- src/util/parse.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/bin/darkfid.rs b/src/bin/darkfid.rs index 363965c42..ed1f19709 100644 --- a/src/bin/darkfid.rs +++ b/src/bin/darkfid.rs @@ -17,7 +17,7 @@ use drk::{ rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig}, }, serial::{deserialize, serialize}, - util::{expand_path, join_config_path, parse_network, parse_wrapped_token, TokenList}, + util::{assign_id, expand_path, join_config_path, parse_wrapped_token, TokenList}, wallet::WalletDb, Result, }; @@ -213,7 +213,7 @@ impl Darkfid { let network = network.as_str().unwrap(); - let token_id = match parse_network(&network, &token, self.tokenlist.clone()) { + let token_id = match assign_id(&network, &token, self.tokenlist.clone()) { Ok(t) => t, Err(_e) => { debug!(target: "DARKFID", "TOKEN ID IS ERR"); diff --git a/src/util/parse.rs b/src/util/parse.rs index 9bc6c702f..6dd01f2f5 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -9,6 +9,7 @@ use sha2::{Digest, Sha256}; use std::str::FromStr; //1. deposit(network, asset) +// //internal ID = hash(externalID, NetworkName) //deposit(internalID) @@ -25,6 +26,32 @@ use std::str::FromStr; //transfer(token_id, amountu64, address) // // +// +// +// +// extern_tokenID +// tokenID +// + +//pub fn create_id(extern_tokenID, NetworkName) -> Result { +//} + +// DEPOSIT +// parse_network +// parse_id + +// generate_id +// +// WITHDRAW +// parse_network +// parse_id +// +// generate_id +// amount.to_u64() +// +// TRANSFER +// get_id +// amount.to_u64() // here we hash the alphanumeric token ID. if it fails, we change the last 4 bytes and hash it // again, and keep repeating until it works. @@ -82,7 +109,7 @@ pub fn parse_wrapped_token(token: &str, tokenlist: TokenList) -> Result Result { +pub fn assign_id(network: &str, token: &str, tokenlist: TokenList) -> Result { match NetworkName::from_str(network)? { NetworkName::Solana => match token.to_lowercase().as_str() { "solana" | "sol" => { From 6a7e458c82861863c25a5a08ce39c313fee9055a Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 11:24:36 +0200 Subject: [PATCH 03/16] bin/cashierd: renamed check_token_id to validate_token_id --- src/bin/cashierd.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/bin/cashierd.rs b/src/bin/cashierd.rs index 2b4a98eb0..b338c428f 100644 --- a/src/bin/cashierd.rs +++ b/src/bin/cashierd.rs @@ -204,7 +204,7 @@ impl Cashierd { } let result: Result = async { - Self::check_token_id(&network, token_id)?; + Self::validate_token_id(&network, token_id)?; let asset_id = generate_id(token_id)?; @@ -298,7 +298,7 @@ impl Cashierd { } let result: Result = async { - Self::check_token_id(&network, token)?; + Self::validate_token_id(&network, token)?; let asset_id = generate_id(&token)?; let address = serialize(&address.to_string()); @@ -346,7 +346,7 @@ impl Cashierd { )) } - fn check_token_id(network: &NetworkName, token_id: &str) -> Result<()> { + fn validate_token_id(network: &NetworkName, token_id: &str) -> Result<()> { match network { #[cfg(feature = "sol")] NetworkName::Solana => { @@ -390,10 +390,7 @@ impl Cashierd { NetworkName::Solana => { debug!(target: "CASHIER DAEMON", "Add sol network"); use drk::service::SolClient; - use solana_sdk::{ - signature::Signer, - signer::keypair::Keypair, - }; + use solana_sdk::{signature::Signer, signer::keypair::Keypair}; let main_keypair: Keypair; From c43dbb53647eb423afa99364644969f0213561be Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 11:59:38 +0200 Subject: [PATCH 04/16] generate_id: added NetworkName. disabled parse_wrapped_token --- src/bin/cashierd.rs | 4 ++-- src/bin/darkfid.rs | 39 ++++++++++++++++++++--------------- src/util/mod.rs | 2 +- src/util/parse.rs | 50 +++++++++++++++++++++++---------------------- 4 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/bin/cashierd.rs b/src/bin/cashierd.rs index b338c428f..735c8b969 100644 --- a/src/bin/cashierd.rs +++ b/src/bin/cashierd.rs @@ -206,7 +206,7 @@ impl Cashierd { let result: Result = async { Self::validate_token_id(&network, token_id)?; - let asset_id = generate_id(token_id)?; + let asset_id = generate_id(token_id, &network)?; let drk_pub_key = bs58::decode(&drk_pub_key).into_vec()?; let drk_pub_key: jubjub::SubgroupPoint = deserialize(&drk_pub_key)?; @@ -300,7 +300,7 @@ impl Cashierd { let result: Result = async { Self::validate_token_id(&network, token)?; - let asset_id = generate_id(&token)?; + let asset_id = generate_id(&token, &network)?; let address = serialize(&address.to_string()); let cashier_public: jubjub::SubgroupPoint; diff --git a/src/bin/darkfid.rs b/src/bin/darkfid.rs index ed1f19709..7451fdddd 100644 --- a/src/bin/darkfid.rs +++ b/src/bin/darkfid.rs @@ -17,7 +17,7 @@ use drk::{ rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig}, }, serial::{deserialize, serialize}, - util::{assign_id, expand_path, join_config_path, parse_wrapped_token, TokenList}, + util::{assign_id, expand_path, join_config_path, TokenList}, wallet::WalletDb, Result, }; @@ -331,23 +331,28 @@ impl Darkfid { let amount = amount.as_f64().unwrap(); - let result: Result<()> = async { - let token_id = parse_wrapped_token(token, self.tokenlist.clone())?; - let address = bs58::decode(&address).into_vec()?; - let address: jubjub::SubgroupPoint = deserialize(&address)?; - self.client - .lock() - .await - .transfer(token_id, address, amount) - .await?; - Ok(()) - } - .await; + //let result: Result<()> = async { + // let token_id = parse_wrapped_token(token, self.tokenlist.clone())?; + // let address = bs58::decode(&address).into_vec()?; + // let address: jubjub::SubgroupPoint = deserialize(&address)?; + // self.client + // .lock() + // .await + // .transfer(token_id, address, amount) + // .await?; + // Ok(()) + //} + //.await; - match result { - Ok(res) => JsonResult::Resp(jsonresp(json!(res), json!(id))), - Err(err) => JsonResult::Err(jsonerr(InternalError, Some(err.to_string()), json!(id))), - } + //match result { + // Ok(res) => JsonResult::Resp(jsonresp(json!(res), json!(id))), + // Err(err) => JsonResult::Err(jsonerr(InternalError, Some(err.to_string()), json!(id))), + //} + return JsonResult::Err(jsonerr( + ServerError(-32005), + Some("failed to withdraw".to_string()), + id, + )); } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 292ae523a..65ba7a14e 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -4,6 +4,6 @@ pub mod path; pub mod token_list; pub use net_name::NetworkName; -pub use parse::{generate_id, parse_network, parse_params, parse_wrapped_token}; +pub use parse::{assign_id, generate_id, parse_params}; pub use path::{expand_path, join_config_path}; pub use token_list::TokenList; diff --git a/src/util/parse.rs b/src/util/parse.rs index 6dd01f2f5..d04930fc6 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -55,12 +55,14 @@ use std::str::FromStr; // here we hash the alphanumeric token ID. if it fails, we change the last 4 bytes and hash it // again, and keep repeating until it works. -pub fn generate_id(tkn_str: &str) -> Result { - if bs58::decode(tkn_str).into_vec().is_err() { +pub fn generate_id(tkn_str: &str, network: &NetworkName) -> Result { + let mut id_string = network.to_string(); + id_string.push_str(tkn_str); + if bs58::decode(id_string.clone()).into_vec().is_err() { // TODO: make this an error debug!(target: "PARSE ID", "COULD NOT DECODE STR"); } - let mut data = bs58::decode(tkn_str).into_vec().unwrap(); + let mut data = bs58::decode(id_string).into_vec().unwrap(); let token_id = match deserialize::(&data) { Ok(v) => v, @@ -87,27 +89,27 @@ pub fn generate_id(tkn_str: &str) -> Result { Ok(token_id) } -pub fn parse_wrapped_token(token: &str, tokenlist: TokenList) -> Result { - match token.to_lowercase().as_str() { - "sol" => { - let id = "So11111111111111111111111111111111111111112"; - let token_id = generate_id(id)?; - Ok(token_id) - } - "btc" => Err(Error::TokenParseError), - tkn => { - // (== 44) can represent a Solana base58 token mint address - let id = if token.len() == 44 { - token.to_string() - } else { - symbol_to_id(tkn, tokenlist)? - }; - - let token_id = generate_id(&id)?; - Ok(token_id) - } - } -} +//pub fn parse_wrapped_token(token: &str, tokenlist: TokenList) -> Result { +// match token.to_lowercase().as_str() { +// "sol" => { +// let id = "So11111111111111111111111111111111111111112"; +// let token_id = generate_id(id)?; +// Ok(token_id) +// } +// "btc" => Err(Error::TokenParseError), +// tkn => { +// // (== 44) can represent a Solana base58 token mint address +// let id = if token.len() == 44 { +// token.to_string() +// } else { +// symbol_to_id(tkn, tokenlist)? +// }; +// +// let token_id = generate_id(&id)?; +// Ok(token_id) +// } +// } +//} pub fn assign_id(network: &str, token: &str, tokenlist: TokenList) -> Result { match NetworkName::from_str(network)? { From 3190f622d209edd272bb568cbdb9b96f3073fee6 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 12:15:48 +0200 Subject: [PATCH 05/16] disabled parse_params. replaced with to_apo() and decimals() --- src/util/mod.rs | 2 +- src/util/parse.rs | 45 +++++++++++++++++++++++++++++++++------------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/util/mod.rs b/src/util/mod.rs index 65ba7a14e..e882341e9 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -4,6 +4,6 @@ pub mod path; pub mod token_list; pub use net_name::NetworkName; -pub use parse::{assign_id, generate_id, parse_params}; +pub use parse::{assign_id, decimals, generate_id, to_apo}; pub use path::{expand_path, join_config_path}; pub use token_list::TokenList; diff --git a/src/util/parse.rs b/src/util/parse.rs index d04930fc6..4abde0747 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -132,31 +132,52 @@ pub fn assign_id(network: &str, token: &str, tokenlist: TokenList) -> Result Result<(String, u64)> { +//pub fn parse_params( +// network: &str, +// token: &str, +// amount: u64, +// tokenlist: TokenList, +//) -> Result<(String, u64)> { +// match NetworkName::from_str(network)? { +// NetworkName::Solana => match token { +// "solana" | "sol" => { +// let token_id = "So11111111111111111111111111111111111111112"; +// let decimals = 9; +// let amount_in_apo = amount * u64::pow(10, decimals as u32); +// Ok((token_id.to_string(), amount_in_apo)) +// } +// tkn => { +// let token_id = symbol_to_id(tkn, tokenlist.clone())?; +// let decimals = tokenlist.search_decimal(tkn)?; +// let amount_in_apo = amount * u64::pow(10, decimals as u32); +// Ok((token_id, amount_in_apo)) +// } +// }, +// NetworkName::Bitcoin => Err(Error::NetworkParseError), +// } +//} +// +pub fn decimals(network: &str, token: &str, tokenlist: TokenList) -> Result { match NetworkName::from_str(network)? { NetworkName::Solana => match token { "solana" | "sol" => { - let token_id = "So11111111111111111111111111111111111111112"; let decimals = 9; - let amount_in_apo = amount * u64::pow(10, decimals as u32); - Ok((token_id.to_string(), amount_in_apo)) + Ok(decimals) } tkn => { - let token_id = symbol_to_id(tkn, tokenlist.clone())?; let decimals = tokenlist.search_decimal(tkn)?; - let amount_in_apo = amount * u64::pow(10, decimals as u32); - Ok((token_id, amount_in_apo)) + Ok(decimals) } }, NetworkName::Bitcoin => Err(Error::NetworkParseError), } } +pub fn to_apo(amount: f64, decimals: u32) -> Result { + let apo = amount as u64 * u64::pow(10, decimals as u32); + Ok(apo) +} + pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { let vec: Vec = token.chars().collect(); let mut counter = 0; From 676b341f136117dba137336ba7c05542b9a58d66 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 12:19:33 +0200 Subject: [PATCH 06/16] util: clean up --- src/util/parse.rs | 96 +---------------------------------------------- 1 file changed, 2 insertions(+), 94 deletions(-) diff --git a/src/util/parse.rs b/src/util/parse.rs index 4abde0747..492a16a59 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -8,53 +8,8 @@ use log::debug; use sha2::{Digest, Sha256}; use std::str::FromStr; -//1. deposit(network, asset) -// -//internal ID = hash(externalID, NetworkName) -//deposit(internalID) - -//2. withdraw(network, asset, amount) -//internal ID = hash(externalID, NetworkName) -//amountu64 = amount.to_u64() -//withdraw(internalID, amount) - -//3. transfer(asset, amount, address) -//asset = { match [SOL, BTC, TOKEN] -// return token-id from wallet.db } -//amountu64 = amount.to_u64() -//address = jubjub::SubgroupPoint -//transfer(token_id, amountu64, address) -// -// -// -// -// -// extern_tokenID -// tokenID -// - -//pub fn create_id(extern_tokenID, NetworkName) -> Result { -//} - -// DEPOSIT -// parse_network -// parse_id - -// generate_id -// -// WITHDRAW -// parse_network -// parse_id -// -// generate_id -// amount.to_u64() -// -// TRANSFER -// get_id -// amount.to_u64() - -// here we hash the alphanumeric token ID. if it fails, we change the last 4 bytes and hash it -// again, and keep repeating until it works. +// hash the external token ID and NetworkName param. +// if fails, change the last 4 bytes and hash it again. keep repeating until it works. pub fn generate_id(tkn_str: &str, network: &NetworkName) -> Result { let mut id_string = network.to_string(); id_string.push_str(tkn_str); @@ -89,28 +44,6 @@ pub fn generate_id(tkn_str: &str, network: &NetworkName) -> Result { Ok(token_id) } -//pub fn parse_wrapped_token(token: &str, tokenlist: TokenList) -> Result { -// match token.to_lowercase().as_str() { -// "sol" => { -// let id = "So11111111111111111111111111111111111111112"; -// let token_id = generate_id(id)?; -// Ok(token_id) -// } -// "btc" => Err(Error::TokenParseError), -// tkn => { -// // (== 44) can represent a Solana base58 token mint address -// let id = if token.len() == 44 { -// token.to_string() -// } else { -// symbol_to_id(tkn, tokenlist)? -// }; -// -// let token_id = generate_id(&id)?; -// Ok(token_id) -// } -// } -//} - pub fn assign_id(network: &str, token: &str, tokenlist: TokenList) -> Result { match NetworkName::from_str(network)? { NetworkName::Solana => match token.to_lowercase().as_str() { @@ -132,31 +65,6 @@ pub fn assign_id(network: &str, token: &str, tokenlist: TokenList) -> Result Result<(String, u64)> { -// match NetworkName::from_str(network)? { -// NetworkName::Solana => match token { -// "solana" | "sol" => { -// let token_id = "So11111111111111111111111111111111111111112"; -// let decimals = 9; -// let amount_in_apo = amount * u64::pow(10, decimals as u32); -// Ok((token_id.to_string(), amount_in_apo)) -// } -// tkn => { -// let token_id = symbol_to_id(tkn, tokenlist.clone())?; -// let decimals = tokenlist.search_decimal(tkn)?; -// let amount_in_apo = amount * u64::pow(10, decimals as u32); -// Ok((token_id, amount_in_apo)) -// } -// }, -// NetworkName::Bitcoin => Err(Error::NetworkParseError), -// } -//} -// pub fn decimals(network: &str, token: &str, tokenlist: TokenList) -> Result { match NetworkName::from_str(network)? { NetworkName::Solana => match token { From 0aa135c5b5fb35dca286caef2ffc7877e076c6f0 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 12:56:33 +0200 Subject: [PATCH 07/16] bin/darkfid: parse transfer params --- src/bin/darkfid.rs | 69 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/src/bin/darkfid.rs b/src/bin/darkfid.rs index 7451fdddd..510f859e7 100644 --- a/src/bin/darkfid.rs +++ b/src/bin/darkfid.rs @@ -17,7 +17,7 @@ use drk::{ rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig}, }, serial::{deserialize, serialize}, - util::{assign_id, expand_path, join_config_path, TokenList}, + util::{assign_id, decimals, expand_path, join_config_path, to_apo, TokenList}, wallet::WalletDb, Result, }; @@ -277,21 +277,61 @@ impl Darkfid { return JsonResult::Err(jsonerr(InvalidParams, None, id)); } - let _network = &args[0]; - let _token = &args[1]; - let _address = &args[2]; - let _amount = &args[3]; + let network = &args[0]; + let token = &args[1]; + let address = &args[2]; + let amount = &args[3]; - // 1. Send request to cashier. - // 2. Cashier checks if they support the network, and if so, - // return adeposit address. - // 3. We issue a transfer of $amount to the given address. + if token.as_str().is_none() { + return JsonResult::Err(jsonerr(InvalidParams, None, id)); + } - return JsonResult::Err(jsonerr( - ServerError(-32005), - Some("failed to withdraw".to_string()), - id, - )); + let token = token.as_str().unwrap(); + + if network.as_str().is_none() { + return JsonResult::Err(jsonerr(InvalidParams, None, id)); + } + + let network = network.as_str().unwrap(); + + if amount.as_f64().is_none() { + return JsonResult::Err(jsonerr(InvalidParams, None, id)); + } + + let amount = amount.as_f64().unwrap(); + + // TODO: get rid of these unwraps + let decimals = decimals(network, token, self.tokenlist.clone()).unwrap(); + let amount_in_apo = to_apo(amount, decimals as u32).unwrap(); + + let token_id = match assign_id(&network, &token, self.tokenlist.clone()) { + Ok(t) => t, + Err(_e) => { + debug!(target: "DARKFID", "TOKEN ID IS ERR"); + // TODO: this should return the relevant drk error + // right now it just flattens it into ParseError + return JsonResult::Err(jsonerr(ParseError, None, id)); + } + }; + + let req = jsonreq( + json!("withdraw"), + json!([network, token_id, address, amount_in_apo]), + ); + let rep: JsonResult; + match send_request(&self.config.cashier_rpc_url, json!(req)).await { + Ok(v) => rep = v, + Err(e) => { + debug!(target: "DARKFID", "REQUEST IS ERR"); + return JsonResult::Err(jsonerr(ServerError(-32004), Some(e.to_string()), id)); + } + } + + match rep { + JsonResult::Resp(r) => return JsonResult::Resp(r), + JsonResult::Err(e) => return JsonResult::Err(e), + JsonResult::Notif(_n) => return JsonResult::Err(jsonerr(InternalError, None, id)), + } } // --> {"method": "transfer", [dToken, address, amount]} @@ -331,6 +371,7 @@ impl Darkfid { let amount = amount.as_f64().unwrap(); + // TODO: get tokenID from walletdb //let result: Result<()> = async { // let token_id = parse_wrapped_token(token, self.tokenlist.clone())?; // let address = bs58::decode(&address).into_vec()?; From bbba70b04b533e7156f0f0694119919a778b7be3 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 16:00:42 +0200 Subject: [PATCH 08/16] util: decode_base10 and encode_base10 test cases --- src/util/parse.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/util/parse.rs b/src/util/parse.rs index 492a16a59..ab570ecfa 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -100,3 +100,79 @@ pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { Ok(token.to_string()) } } + +mod tests { + #[test] + fn decode_base10() { + const RADIX: u32 = 10; + // TODO: this number varies per token + let decimal_places = 10; + + let input = "0.1111"; + println!("Initial input: {}", input); + let mut input_str = input.to_string(); + + // remove the decimal point + let mut amount: String = match input_str.find(".") { + Some(v) => { + input_str.remove(v); + input_str + } + None => { + print!("Number isn't a float"); + input_str + } + }; + + println!("Removed decimal point: {}", amount); + // only digits should remain: + for c in amount.chars() { + if c.is_digit(RADIX) == false { + println!("Amount is not valid digits!") + } + } + + // add digits to the end if there are too few + if amount.len() < decimal_places { + loop { + amount.push('0'); + + if amount.len() == decimal_places { + break; + } + continue; + } + } + + // remove digits from the end if there are too many + if amount.len() > decimal_places { + loop { + amount.pop(); + + if amount.len() == decimal_places { + break; + } + continue; + } + } + + println!("Resized amount: {}", amount); + + // convert to an integer + for i in amount.chars() { + let digit = i.to_digit(RADIX).unwrap(); + println!("Converted to integer: {}", digit); + } + } + + #[test] + fn encode_base10() { + let input = 1000000000; + println!("Original input: {}", input); + let mut input_str = input.to_string(); + + input_str.insert(1, '.'); + let amount = input_str.trim(); + println!("Encoded output: {}", amount); + } +} From 675b7efd7724aee1866d97547af4d1b44debcecb Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 16:37:50 +0200 Subject: [PATCH 09/16] util: replaced trim with trim_end_matches --- src/util/parse.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/parse.rs b/src/util/parse.rs index ab570ecfa..2d4aee4fd 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -108,7 +108,7 @@ mod tests { // TODO: this number varies per token let decimal_places = 10; - let input = "0.1111"; + let input = "2.5"; println!("Initial input: {}", input); let mut input_str = input.to_string(); @@ -167,12 +167,12 @@ mod tests { #[test] fn encode_base10() { - let input = 1000000000; + let input = 1500000000; println!("Original input: {}", input); let mut input_str = input.to_string(); input_str.insert(1, '.'); - let amount = input_str.trim(); + let amount = input_str.trim_end_matches('0'); println!("Encoded output: {}", amount); } } From 1884a0d6d19f8017b4d2cebe4c21fe515d403e52 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 16:49:36 +0200 Subject: [PATCH 10/16] util: remove decimal point if round number --- src/util/parse.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/util/parse.rs b/src/util/parse.rs index 2d4aee4fd..28b0b59ac 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -167,12 +167,21 @@ mod tests { #[test] fn encode_base10() { - let input = 1500000000; + let input = 100000000; println!("Original input: {}", input); let mut input_str = input.to_string(); input_str.insert(1, '.'); + let amount = input_str.trim_end_matches('0'); + + let amount = if amount.ends_with('.') == true { + let amount = amount.trim_end_matches('.'); + amount + } else { + amount + }; + println!("Encoded output: {}", amount); } } From 29d3040a29c8422fd83047559ba691e25e1d01b9 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 17:14:10 +0200 Subject: [PATCH 11/16] util: decimal() returns usize --- src/util/parse.rs | 62 +++++++++++++++++++++++++++++++++++++++++- src/util/token_list.rs | 2 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/util/parse.rs b/src/util/parse.rs index 28b0b59ac..30850d38f 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -65,7 +65,7 @@ pub fn assign_id(network: &str, token: &str, tokenlist: TokenList) -> Result Result { +pub fn decimals(network: &str, token: &str, tokenlist: TokenList) -> Result { match NetworkName::from_str(network)? { NetworkName::Solana => match token { "solana" | "sol" => { @@ -101,6 +101,65 @@ pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { } } +//pub fn decode_base10(amount: &str, decimals: usize) -> Result { +// const RADIX: u32 = 10; +// +// let mut input_str = amount.to_string(); +// +// // remove the decimal point +// let mut amount: String = match input_str.find(".") { +// Some(v) => { +// input_str.remove(v); +// input_str +// } +// None => input_str, +// }; +// +// // only digits should remain: +// for c in amount.chars() { +// if c.is_digit(RADIX) == false { +// // TODO: Make this an error +// println!("Amount is not valid digits!") +// } +// } +// +// // add digits to the end if there are too few +// if amount.len() < decimals { +// loop { +// amount.push('0'); +// +// if amount.len() == decimals { +// break; +// } +// continue; +// } +// } +// +// // remove digits from the end if there are too many +// if amount.len() > decimals { +// loop { +// amount.pop(); +// +// if amount.len() == decimals { +// break; +// } +// continue; +// } +// } +// +// println!("Resized amount: {}", amount); +// +// let amount_vec: Vec = vec![0; decimals]; +// +// // convert to an integer +// for i in amount.chars() { +// let digit = i.to_digit(RADIX).unwrap(); +// amount_vec.push(digit); +// } +// +// Ok(amount_vec.drain()) +//} + mod tests { #[test] fn decode_base10() { @@ -110,6 +169,7 @@ mod tests { let input = "2.5"; println!("Initial input: {}", input); + let mut input_str = input.to_string(); // remove the decimal point diff --git a/src/util/token_list.rs b/src/util/token_list.rs index c0ae0e9c1..1bd902b67 100644 --- a/src/util/token_list.rs +++ b/src/util/token_list.rs @@ -29,7 +29,7 @@ impl TokenList { unreachable!(); } - pub fn search_decimal(self, symbol: &str) -> Result { + pub fn search_decimal(self, symbol: &str) -> Result { let tokens = self.tokenlist["tokens"] .as_array() .ok_or(Error::TokenParseError)?; From c784d0396cef218977d7580addcb7ef4909bdb26 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 17:33:10 +0200 Subject: [PATCH 12/16] util: convert u32 to u64 --- src/util/parse.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/util/parse.rs b/src/util/parse.rs index 30850d38f..6d7347fe7 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -157,7 +157,8 @@ pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { // amount_vec.push(digit); // } // -// Ok(amount_vec.drain()) +// let amount: u64 = amount_vec.drain(); +// Ok(amount) //} mod tests { @@ -185,6 +186,7 @@ mod tests { }; println!("Removed decimal point: {}", amount); + // only digits should remain: for c in amount.chars() { if c.is_digit(RADIX) == false { @@ -222,6 +224,7 @@ mod tests { for i in amount.chars() { let digit = i.to_digit(RADIX).unwrap(); println!("Converted to integer: {}", digit); + let u = u64::from(digit); } } From 9b71f28f26ca888d352a70a65aefbb3551a57162 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 17:46:10 +0200 Subject: [PATCH 13/16] token_list: fixed usize conversion error on search_decimal --- src/util/token_list.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/token_list.rs b/src/util/token_list.rs index 1bd902b67..83fb4806d 100644 --- a/src/util/token_list.rs +++ b/src/util/token_list.rs @@ -37,6 +37,7 @@ impl TokenList { if item["symbol"] == symbol.to_uppercase() { let decimals = item["decimals"].clone(); let decimals = decimals.as_u64().ok_or(Error::TokenParseError)?; + let decimals = decimals as usize; return Ok(decimals); } } From a3173f3089f33a2705dfdda83864d3464332cf5f Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 18:37:35 +0200 Subject: [PATCH 14/16] util: simplified u64 conversion with str::parse() --- src/util/parse.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/util/parse.rs b/src/util/parse.rs index 6d7347fe7..b6fe85509 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -221,11 +221,9 @@ mod tests { println!("Resized amount: {}", amount); // convert to an integer - for i in amount.chars() { - let digit = i.to_digit(RADIX).unwrap(); - println!("Converted to integer: {}", digit); - let u = u64::from(digit); - } + let number = amount.parse::().unwrap(); + + println!("The final number: {:?}", number); } #[test] From e2a21d25455b9b77cbd6b5d0278455e637c00f78 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 18:46:03 +0200 Subject: [PATCH 15/16] replaced to_apo with decode_base10 --- src/bin/darkfid.rs | 6 +-- src/util/mod.rs | 2 +- src/util/parse.rs | 131 +++++++++++++++++++++++++-------------------- 3 files changed, 77 insertions(+), 62 deletions(-) diff --git a/src/bin/darkfid.rs b/src/bin/darkfid.rs index 510f859e7..cb562afd0 100644 --- a/src/bin/darkfid.rs +++ b/src/bin/darkfid.rs @@ -17,7 +17,7 @@ use drk::{ rpcserver::{listen_and_serve, RequestHandler, RpcServerConfig}, }, serial::{deserialize, serialize}, - util::{assign_id, decimals, expand_path, join_config_path, to_apo, TokenList}, + util::{assign_id, decimals, decode_base10, expand_path, join_config_path, TokenList}, wallet::WalletDb, Result, }; @@ -298,11 +298,11 @@ impl Darkfid { return JsonResult::Err(jsonerr(InvalidParams, None, id)); } - let amount = amount.as_f64().unwrap(); + let amount = amount.as_str().unwrap(); // TODO: get rid of these unwraps let decimals = decimals(network, token, self.tokenlist.clone()).unwrap(); - let amount_in_apo = to_apo(amount, decimals as u32).unwrap(); + let amount_in_apo = decode_base10(amount, decimals).unwrap(); let token_id = match assign_id(&network, &token, self.tokenlist.clone()) { Ok(t) => t, diff --git a/src/util/mod.rs b/src/util/mod.rs index e882341e9..d07b6d7ca 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -4,6 +4,6 @@ pub mod path; pub mod token_list; pub use net_name::NetworkName; -pub use parse::{assign_id, decimals, generate_id, to_apo}; +pub use parse::{assign_id, decimals, decode_base10, generate_id}; pub use path::{expand_path, join_config_path}; pub use token_list::TokenList; diff --git a/src/util/parse.rs b/src/util/parse.rs index b6fe85509..fff8800b2 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -81,10 +81,10 @@ pub fn decimals(network: &str, token: &str, tokenlist: TokenList) -> Result Result { - let apo = amount as u64 * u64::pow(10, decimals as u32); - Ok(apo) -} +//pub fn to_apo(amount: f64, decimals: u32) -> Result { +// let apo = amount as u64 * u64::pow(10, decimals as u32); +// Ok(apo) +//} pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { let vec: Vec = token.chars().collect(); @@ -101,64 +101,79 @@ pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { } } -//pub fn decode_base10(amount: &str, decimals: usize) -> Result { -// const RADIX: u32 = 10; +pub fn decode_base10(amount: &str, decimals: usize) -> Result { + const RADIX: u32 = 10; + + let mut input_str = amount.to_string(); + + // remove the decimal point + let mut amount: String = match input_str.find(".") { + Some(v) => { + input_str.remove(v); + input_str + } + None => input_str, + }; + + // only digits should remain: + for c in amount.chars() { + if c.is_digit(RADIX) == false { + // TODO: Make this an error + println!("Amount is not valid digits!") + } + } + + // add digits to the end if there are too few + if amount.len() < decimals { + loop { + amount.push('0'); + + if amount.len() == decimals { + break; + } + continue; + } + } + + // remove digits from the end if there are too many + if amount.len() > decimals { + loop { + amount.pop(); + + if amount.len() == decimals { + break; + } + continue; + } + } + + println!("Resized amount: {}", amount); + + // convert to an integer + let number = amount.parse::().unwrap(); + + Ok(number) +} + +// TODO: implement this + +//fn encode_base10() { +// let input = 100000000; +// println!("Original input: {}", input); +// let mut input_str = input.to_string(); // -// let mut input_str = amount.to_string(); +// input_str.insert(1, '.'); // -// // remove the decimal point -// let mut amount: String = match input_str.find(".") { -// Some(v) => { -// input_str.remove(v); -// input_str -// } -// None => input_str, +// let amount = input_str.trim_end_matches('0'); +// +// let amount = if amount.ends_with('.') == true { +// let amount = amount.trim_end_matches('.'); +// amount +// } else { +// amount // }; // -// // only digits should remain: -// for c in amount.chars() { -// if c.is_digit(RADIX) == false { -// // TODO: Make this an error -// println!("Amount is not valid digits!") -// } -// } -// -// // add digits to the end if there are too few -// if amount.len() < decimals { -// loop { -// amount.push('0'); -// -// if amount.len() == decimals { -// break; -// } -// continue; -// } -// } -// -// // remove digits from the end if there are too many -// if amount.len() > decimals { -// loop { -// amount.pop(); -// -// if amount.len() == decimals { -// break; -// } -// continue; -// } -// } -// -// println!("Resized amount: {}", amount); -// -// let amount_vec: Vec = vec![0; decimals]; -// -// // convert to an integer -// for i in amount.chars() { -// let digit = i.to_digit(RADIX).unwrap(); -// amount_vec.push(digit); -// } -// -// let amount: u64 = amount_vec.drain(); -// Ok(amount) +// println!("Encoded output: {}", amount); //} mod tests { From ce2e5d60a21688a8f6f639b6fee13a37db0b1763 Mon Sep 17 00:00:00 2001 From: parazyd Date: Thu, 30 Sep 2021 22:33:37 +0200 Subject: [PATCH 16/16] util/parse: Implement base10 operations. This should be equivalent to the code in libbitcoin: https://github.com/libbitcoin/libbitcoin-system/blob/master/src/formats/base_10.cpp --- src/bin/darkfid.rs | 2 +- src/util/mod.rs | 2 +- src/util/parse.rs | 242 +++++++++++++++++---------------------------- 3 files changed, 91 insertions(+), 155 deletions(-) diff --git a/src/bin/darkfid.rs b/src/bin/darkfid.rs index cb562afd0..25055a3b1 100644 --- a/src/bin/darkfid.rs +++ b/src/bin/darkfid.rs @@ -302,7 +302,7 @@ impl Darkfid { // TODO: get rid of these unwraps let decimals = decimals(network, token, self.tokenlist.clone()).unwrap(); - let amount_in_apo = decode_base10(amount, decimals).unwrap(); + let amount_in_apo = decode_base10(amount, decimals, true).unwrap(); let token_id = match assign_id(&network, &token, self.tokenlist.clone()) { Ok(t) => t, diff --git a/src/util/mod.rs b/src/util/mod.rs index d07b6d7ca..589367870 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -4,6 +4,6 @@ pub mod path; pub mod token_list; pub use net_name::NetworkName; -pub use parse::{assign_id, decimals, decode_base10, generate_id}; +pub use parse::{assign_id, decimals, decode_base10, encode_base10, generate_id}; pub use path::{expand_path, join_config_path}; pub use token_list::TokenList; diff --git a/src/util/parse.rs b/src/util/parse.rs index fff8800b2..aa161df6a 100644 --- a/src/util/parse.rs +++ b/src/util/parse.rs @@ -1,13 +1,14 @@ +use log::debug; +use sha2::{Digest, Sha256}; +use std::iter::FromIterator; +use std::str::FromStr; + use crate::{ serial::{deserialize, serialize}, util::{NetworkName, TokenList}, Error, Result, }; -use log::debug; -use sha2::{Digest, Sha256}; -use std::str::FromStr; - // hash the external token ID and NetworkName param. // if fails, change the last 4 bytes and hash it again. keep repeating until it works. pub fn generate_id(tkn_str: &str, network: &NetworkName) -> Result { @@ -101,163 +102,98 @@ pub fn symbol_to_id(token: &str, tokenlist: TokenList) -> Result { } } -pub fn decode_base10(amount: &str, decimals: usize) -> Result { - const RADIX: u32 = 10; - - let mut input_str = amount.to_string(); - - // remove the decimal point - let mut amount: String = match input_str.find(".") { - Some(v) => { - input_str.remove(v); - input_str - } - None => input_str, - }; - - // only digits should remain: - for c in amount.chars() { - if c.is_digit(RADIX) == false { - // TODO: Make this an error - println!("Amount is not valid digits!") - } - } - - // add digits to the end if there are too few - if amount.len() < decimals { - loop { - amount.push('0'); - - if amount.len() == decimals { - break; - } - continue; - } - } - - // remove digits from the end if there are too many - if amount.len() > decimals { - loop { - amount.pop(); - - if amount.len() == decimals { - break; - } - continue; - } - } - - println!("Resized amount: {}", amount); - - // convert to an integer - let number = amount.parse::().unwrap(); - - Ok(number) +fn is_digit(c: char) -> bool { + ('0'..='9').contains(&c) } -// TODO: implement this +fn char_eq(a: char, b: char) -> bool { + a == b +} -//fn encode_base10() { -// let input = 100000000; -// println!("Original input: {}", input); -// let mut input_str = input.to_string(); -// -// input_str.insert(1, '.'); -// -// let amount = input_str.trim_end_matches('0'); -// -// let amount = if amount.ends_with('.') == true { -// let amount = amount.trim_end_matches('.'); -// amount -// } else { -// amount -// }; -// -// println!("Encoded output: {}", amount); -//} +pub fn decode_base10(amount: &str, decimal_places: usize, strict: bool) -> Result { + let mut s: Vec = amount.to_string().chars().collect(); + // Get rid of the decimal point: + let point: usize; + if let Some(p) = amount.find('.') { + s.remove(p); + point = p; + } else { + point = s.len(); + } + + // Only digits should remain + for i in &s { + if !is_digit(*i) { + return Err(Error::ParseFailed("Found non-digits")); + } + } + + // Add digits to the end if there are too few: + let actual_places = s.len() - point; + if actual_places < decimal_places { + s.extend(vec!['0'; decimal_places - actual_places]) + } + + // Remove digits from the end if there are too many: + let mut round = false; + if actual_places > decimal_places { + let end = point + decimal_places; + for i in &s[end..s.len()] { + if !char_eq(*i, '0') { + round = true; + break; + } + } + s.truncate(end); + } + + if strict && round { + return Err(Error::ParseFailed("Would end up rounding while strict")); + } + + // Convert to an integer + let number = u64::from_str(&String::from_iter(&s))?; + + // Round and return + if round && number == u64::MAX { + return Err(Error::ParseFailed("u64 overflow")); + } + + Ok(number + round as u64) +} + +pub fn encode_base10(amount: u64, decimal_places: usize) -> String { + let mut s: Vec = format!("{:0width$}", amount, width = 1 + decimal_places) + .chars() + .collect(); + s.insert(s.len() - decimal_places, '.'); + + String::from_iter(&s) + .trim_end_matches('0') + .trim_end_matches('.') + .to_string() +} + +#[allow(unused_imports)] mod tests { + use crate::util::decode_base10; + use crate::util::encode_base10; #[test] - fn decode_base10() { - const RADIX: u32 = 10; - // TODO: this number varies per token - let decimal_places = 10; - - let input = "2.5"; - println!("Initial input: {}", input); - - let mut input_str = input.to_string(); - - // remove the decimal point - let mut amount: String = match input_str.find(".") { - Some(v) => { - input_str.remove(v); - input_str - } - None => { - print!("Number isn't a float"); - input_str - } - }; - - println!("Removed decimal point: {}", amount); - - // only digits should remain: - for c in amount.chars() { - if c.is_digit(RADIX) == false { - println!("Amount is not valid digits!") - } - } - - // add digits to the end if there are too few - if amount.len() < decimal_places { - loop { - amount.push('0'); - - if amount.len() == decimal_places { - break; - } - continue; - } - } - - // remove digits from the end if there are too many - if amount.len() > decimal_places { - loop { - amount.pop(); - - if amount.len() == decimal_places { - break; - } - continue; - } - } - - println!("Resized amount: {}", amount); - - // convert to an integer - let number = amount.parse::().unwrap(); - - println!("The final number: {:?}", number); + fn test_decode_base10() { + assert_eq!(124, decode_base10("12.33", 1, false).unwrap()); + assert_eq!(1233000, decode_base10("12.33", 5, false).unwrap()); + assert_eq!(1200000, decode_base10("12.", 5, false).unwrap()); + assert_eq!(1200000, decode_base10("12", 5, false).unwrap()); + assert!(decode_base10("12.33", 1, true).is_err()); } #[test] - fn encode_base10() { - let input = 100000000; - println!("Original input: {}", input); - let mut input_str = input.to_string(); - - input_str.insert(1, '.'); - - let amount = input_str.trim_end_matches('0'); - - let amount = if amount.ends_with('.') == true { - let amount = amount.trim_end_matches('.'); - amount - } else { - amount - }; - - println!("Encoded output: {}", amount); + fn test_encode_base10() { + assert_eq!("23.4321111", &encode_base10(234321111, 7)); + assert_eq!("23432111.1", &encode_base10(234321111, 1)); + assert_eq!("234321.1", &encode_base10(2343211, 1)); + assert_eq!("2343211", &encode_base10(2343211, 0)); + assert_eq!("0.00002343", &encode_base10(2343, 8)); } }