From 181d337d6ebf5a0ef879735e89df11c4df5a52fb Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 30 Sep 2021 09:47:11 +0200 Subject: [PATCH] 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