diff --git a/Cargo.lock b/Cargo.lock index 2b55eb2..4bc0407 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -967,7 +967,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "mfm" -version = "0.1.19" +version = "0.1.20" dependencies = [ "anyhow", "bigdecimal", diff --git a/Cargo.toml b/Cargo.toml index d21b683..244a1b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mfm" -version = "0.1.19" +version = "0.1.20" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/asset/mod.rs b/src/asset/mod.rs index c0ca08b..886162c 100644 --- a/src/asset/mod.rs +++ b/src/asset/mod.rs @@ -9,7 +9,7 @@ use web3::{ types::{Address, Bytes, H160, U256}, }; -use crate::utils; +use crate::utils::{self, math}; use crate::config::{network::Network, wallet::Wallet, withdraw_wallet::WithdrawWallet, Config}; use crate::utils::resources::{exists_resource_file_fs_or_res, get_resource_file_fs_or_res}; @@ -33,6 +33,16 @@ pub struct Asset { impl Asset { pub fn new(asset_config: &AssetConfig, network: &Network) -> Result { let asset_network = asset_config.networks.get(network.get_name())?; + + // TODO: add a validator for the builders + + if !(asset_network.slippage > 0.0 && asset_network.slippage <= 100.0) { + return Err(anyhow::anyhow!( + "Asset::new(): asset_name: {}; slippage needs to be between 0 and 100", + asset_network.name + )); + } + Ok(Asset { name: asset_network.name.clone(), kind: asset_config.kind.clone(), @@ -44,10 +54,12 @@ impl Asset { }) } + pub fn slippage(&self) -> f64 { + self.slippage + } + pub fn slippage_u256(&self, asset_decimals: u8) -> U256 { - //TODO: review u128 - let qe = ((&self.slippage / 100.0) * 10_f64.powf(asset_decimals.into())) as u128; - U256::from(qe) + math::percent_to_u256(self.slippage, asset_decimals) } pub fn name(&self) -> &str { diff --git a/src/cmd/approve.rs b/src/cmd/approve.rs index 5e46639..745857f 100644 --- a/src/cmd/approve.rs +++ b/src/cmd/approve.rs @@ -13,7 +13,11 @@ pub fn generate_cmd() -> Command { ) .arg(clap::arg!(-w --"wallet" "Wallet id from config file").required(true)) .arg(clap::arg!(-a --"asset" "Asset to approve spender").required(true)) - .arg(clap::arg!(-v --"amount" "Amount to allow spending").required(true)) + .arg( + clap::arg!(-v --"amount" "Amount to allow spending") + .required(true) + .value_parser(clap::value_parser!(f64)), + ) } pub async fn call_sub_commands(args: &ArgMatches) -> Result<(), anyhow::Error> { diff --git a/src/cmd/helpers.rs b/src/cmd/helpers.rs index 2024dff..e92f829 100644 --- a/src/cmd/helpers.rs +++ b/src/cmd/helpers.rs @@ -5,6 +5,7 @@ use crate::{ Config, }, rebalancer::config::RebalancerConfig, + utils::math, }; use anyhow::Context; use clap::ArgMatches; @@ -89,30 +90,29 @@ pub fn get_txn_id(args: &ArgMatches) -> &str { #[tracing::instrument(name = "get amount from command args")] pub fn get_amount(args: &ArgMatches, asset_decimals: u8) -> Result { - //TODO: need to review usage from i128 - match args.get_one::("amount") { - Some(a) => { - //TODO: move it to a helper function - let q = a - .parse::() - .map_err(|e| anyhow::anyhow!("cant parse the amount value to f64, got {:?}", e))?; - let qe = (q * 10_f64.powf(asset_decimals.into())) as i128; - Ok(U256::from(qe)) - } - None => Err(anyhow::anyhow!("--amount is required")), + match args.get_one::("amount") { + Some(amount) => Ok(math::f64_to_u256(*amount, asset_decimals)), + None => Err(anyhow::anyhow!("--amount is not a number")), } } -#[tracing::instrument(name = "get slippage from command args")] -pub fn get_slippage(args: &ArgMatches, asset_decimals: u8) -> Result { - //TODO: review u128 - match args.get_one::("slippage") { - Some(a) => { - let q = a.parse::().unwrap(); - let qe = ((q / 100.0) * 10_f64.powf(asset_decimals.into())) as u128; - Ok(U256::from(qe)) - } - None => Err(anyhow::anyhow!("--slippage is required")), +#[tracing::instrument(name = "get amount in f64 from command args")] +pub fn get_amount_f64(args: &ArgMatches) -> Result { + match args.get_one::("amount") { + Some(amount) => Ok(*amount), + None => Err(anyhow::anyhow!("--amount is not a number")), + } +} + +#[tracing::instrument(name = "get slippage in f64 from command args")] +pub fn get_slippage(args: &ArgMatches) -> Result { + match args.get_one::("slippage") { + Some(f) if *f > 0.0 && *f <= 100.0 => Ok(*f), + Some(f) => Err(anyhow::anyhow!( + "--slippage needs to be between 0 and 100. f: {}", + f + )), + None => Err(anyhow::anyhow!("--slippage is not a number")), } } diff --git a/src/cmd/swap.rs b/src/cmd/swap.rs index 51f9dcc..b7591ec 100644 --- a/src/cmd/swap.rs +++ b/src/cmd/swap.rs @@ -1,4 +1,7 @@ -use crate::{cmd::helpers, utils}; +use crate::{ + cmd::helpers, + utils::{self, math}, +}; use clap::{ArgMatches, Command}; use prettytable::{row, Table}; use web3::types::U256; @@ -13,14 +16,16 @@ pub fn generate_cmd() -> Command { .arg(clap::arg!(-w --"wallet" "Wallet id from config file").required(true)) .arg( clap::arg!(-a --"amount" "Amount of TokenA to swap to TokenB") - .required(false), + .required(false) + .value_parser(clap::value_parser!(f64)), ) .arg(clap::arg!(-i --"token_input" "Asset of input token").required(false)) .arg(clap::arg!(-o --"token_output" "Asset of output token").required(false)) .arg( clap::arg!(-s --"slippage" "Slippage (default 0.5)") .required(false) - .default_value("0.5"), + .default_value("0.5") + .value_parser(clap::value_parser!(f64)), ) } @@ -37,10 +42,7 @@ pub async fn call_sub_commands(args: &ArgMatches) -> Result<(), anyhow::Error> { let input_asset_decimals = input_asset.decimals().await.unwrap(); let output_asset_decimals = output_asset.decimals().await.unwrap(); - let amount_in = helpers::get_amount(args, input_asset_decimals).unwrap_or_else(|e| { - tracing::error!(error = %e); - panic!() - }); + let amount_in = helpers::get_amount(args, input_asset_decimals)?; let exchange = match helpers::get_exchange(args) { Ok(e) => e, @@ -54,12 +56,12 @@ pub async fn call_sub_commands(args: &ArgMatches) -> Result<(), anyhow::Error> { } }; - let wallet = helpers::get_wallet(args).unwrap_or_else(|e| { + let from_wallet = helpers::get_wallet(args).unwrap_or_else(|e| { tracing::error!(error = %e); panic!() }); - let slippage = helpers::get_slippage(args, output_asset_decimals).unwrap(); + let slippage = helpers::get_slippage(args)?; let asset_path_in = exchange.build_route_for(&input_asset, &output_asset).await; @@ -71,17 +73,18 @@ pub async fn call_sub_commands(args: &ArgMatches) -> Result<(), anyhow::Error> { .into(); tracing::debug!("amount_mint_out: {:?}", amount_min_out); - let slippage_amount = (amount_min_out * slippage) / U256::exp10(output_asset_decimals.into()); + let slippage_amount = + math::get_slippage_amount(amount_min_out, slippage, output_asset_decimals); let amount_out_slippage = amount_min_out - slippage_amount; exchange .swap_tokens_for_tokens( - wallet, + from_wallet, amount_in, amount_out_slippage, input_asset.clone(), output_asset.clone(), - Some(slippage), + Some(math::f64_to_u256(slippage, output_asset_decimals)), ) .await; diff --git a/src/cmd/withdraw.rs b/src/cmd/withdraw.rs index 4df37fe..f63febe 100644 --- a/src/cmd/withdraw.rs +++ b/src/cmd/withdraw.rs @@ -22,6 +22,7 @@ pub fn generate_cmd() -> Command { .arg( clap::arg!(-v --"amount" "Amount to withdraw") .required(true) + .value_parser(clap::value_parser!(f64)) ) } diff --git a/src/config/exchange.rs b/src/config/exchange.rs index c248196..58067ec 100644 --- a/src/config/exchange.rs +++ b/src/config/exchange.rs @@ -1,7 +1,9 @@ use crate::asset::Asset; -use crate::utils; use crate::utils::resources::{exists_resource_file_fs_or_res, get_resource_file_fs_or_res}; +use crate::utils::scalar::BigDecimal; +use crate::utils::{self, math}; +use std::ops::Div; use std::str::FromStr; use std::time::UNIX_EPOCH; use std::{collections::HashMap, time::SystemTime}; @@ -214,8 +216,6 @@ impl Exchange { } let contract = self.router_contract(); - // let quantity = 1; - // let amount: U256 = (quantity * 10_i32.pow(decimals.into())).into(); let result = contract.query( "getAmountsOut", (amount, assets_path), @@ -273,22 +273,9 @@ impl Exchange { .collect::>(), ); - // let asset_path_out = self.build_route_for(&output_asset, &input_asset).await; - // let asset_path_in = self.build_route_for(&input_asset, &output_asset).await; - - //let input_asset_decimals = input_asset.decimals().await; let output_asset_decimals = output_asset.decimals().await.unwrap(); let amount_in = input_asset.balance_of(from_wallet.address()).await; - //TODO: review this model of use slippage - // review another usage to change to use always the output asset decimals - let slippage = { - let ais = input_asset.slippage_u256(output_asset_decimals); - let aos = output_asset.slippage_u256(output_asset_decimals); - - ais + aos - }; - let amount_out: U256 = self .get_amounts_out(amount_in, asset_path.clone()) .await @@ -296,11 +283,12 @@ impl Exchange { .unwrap() .into(); - //TODO: move this kind of logic to the U256 module - //FIXME: fix the arithmetic operation overflow - let slippage_amount = (amount_out * slippage) / U256::exp10(output_asset_decimals.into()); + // Sum the slippage of the both assets the input and output. + let slippage = input_asset.slippage() + output_asset.slippage(); + + let slippage_amount = + math::get_slippage_amount(amount_out, slippage, output_asset_decimals); let amount_min_out_slippage = amount_out - slippage_amount; - //let amount_min_out_slippage = amount_out; match swap_tokens_for_tokens::estimate_gas( self, @@ -349,14 +337,11 @@ impl Exchange { let input_asset_decimals = input_asset.decimals().await.unwrap(); let output_asset_decimals = output_asset.decimals().await.unwrap(); - //TODO: review this model of use slippage let slippage = match slippage_opt { Some(s) => s, None => { - let ais = input_asset.slippage_u256(output_asset_decimals); - let aos = output_asset.slippage_u256(output_asset_decimals); - - ais + aos + input_asset.slippage_u256(output_asset_decimals) + + output_asset.slippage_u256(output_asset_decimals) } }; @@ -428,12 +413,13 @@ impl Exchange { match limit_max_input { Some(limit) if amount_in > limit => { - // TODO: resolv this calc with U256 exp10 or numbigint let mut total = amount_in; - let amount_in_plus_two_decimals = amount_in * U256::exp10(2); - let number_hops = (((amount_in_plus_two_decimals / limit).as_u128() as f64) - / 100_f64) - .ceil() as u64; + + let amount_in_bd = + BigDecimal::from_unsigned_u256(&amount_in, input_asset_decimals.into()); + let limit_bd = BigDecimal::from_unsigned_u256(&limit, input_asset_decimals.into()); + + let number_hops = amount_in_bd.div(limit_bd).to_f64().unwrap().ceil() as u64; for _ in 0..number_hops { let ai: U256; @@ -471,6 +457,7 @@ impl Exchange { let slippage_amount = (ao * slippage) / U256::exp10(output_asset_decimals.into()); + let amount_min_out_slippage = ao - slippage_amount; tracing::debug!("slippage_amount {:?}", slippage_amount); diff --git a/src/config/network.rs b/src/config/network.rs index 7a92b8a..9a285b3 100644 --- a/src/config/network.rs +++ b/src/config/network.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use web3::{transports::Http, types::U256, Web3}; use super::{exchange::Exchange, Config}; -use crate::asset::Asset; +use crate::{asset::Asset, utils::scalar::BigDecimal}; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Network { @@ -43,9 +43,13 @@ impl Network { .assets .find_by_name_and_network(self.wrapped_asset.as_str(), self.name.as_str()) } + + //TODO: validate min_balance_coin in the build of the type pub fn get_min_balance_coin(&self, decimals: u8) -> U256 { - let qe = (self.min_balance_coin * 10_f64.powf(decimals.into())) as i64; - U256::from(qe) + BigDecimal::try_from(self.min_balance_coin) + .unwrap() + .with_scale(decimals.into()) + .to_unsigned_u256() } pub fn get_web3_client_http(&self) -> Web3 { @@ -61,7 +65,6 @@ impl Network { .collect() } - // TODO: add test to avoid the bugs of like in the choice of exchange pub async fn get_exchange_by_liquidity( &self, input_asset: &Asset, diff --git a/src/quote/cmd.rs b/src/quote/cmd.rs index 81651a8..b43f402 100644 --- a/src/quote/cmd.rs +++ b/src/quote/cmd.rs @@ -6,15 +6,17 @@ pub fn generate() -> Command { .arg(clap::arg!(-n --"network" "Network to use, ex (bsc, polygon)").required(true)) .arg(clap::arg!(-e --"exchange" "Exchange to use router").required(false)) .arg( - clap::arg!(-a --"amount" "Amount of TokenA to swap to TokenB") - .required(false), + clap::arg!(-a --"amount" "Amount of TokenA to swap to TokenB") + .required(false) + .value_parser(clap::value_parser!(f64)), ) .arg(clap::arg!(-i --"token_input" "Asset of input token").required(false)) .arg(clap::arg!(-o --"token_output" "Asset of output token").required(false)) .arg( clap::arg!(-s --"slippage" "Slippage (default 0.5)") .required(false) - .default_value("0.5"), + .default_value("0.5") + .value_parser(clap::value_parser!(f64)), ) } diff --git a/src/quote/mod.rs b/src/quote/mod.rs index 74e5827..b7ad980 100644 --- a/src/quote/mod.rs +++ b/src/quote/mod.rs @@ -1,4 +1,7 @@ -use crate::{cmd::helpers, utils}; +use crate::{ + cmd::helpers, + utils::{self, math}, +}; use clap::ArgMatches; use prettytable::{row, Table}; use web3::types::U256; @@ -28,7 +31,7 @@ async fn run(args: &ArgMatches) -> Result<(), anyhow::Error> { } }; - let slippage = helpers::get_slippage(args, output_asset_decimals)?; + let slippage = helpers::get_slippage(args)?; let asset_path = exchange.build_route_for(&input_asset, &output_asset).await; @@ -39,8 +42,8 @@ async fn run(args: &ArgMatches) -> Result<(), anyhow::Error> { .unwrap() .into(); - // TODO: move this calc for the new mod of U256 - let slippage_amount = (amount_min_out * slippage) / U256::exp10(output_asset_decimals.into()); + let slippage_amount = + math::get_slippage_amount(amount_min_out, slippage, output_asset_decimals); let amount_out_slippage: U256 = amount_min_out - slippage_amount; let mut table = Table::new(); diff --git a/src/rebalancer/config.rs b/src/rebalancer/config.rs index e9915a0..a3471be 100644 --- a/src/rebalancer/config.rs +++ b/src/rebalancer/config.rs @@ -1,5 +1,6 @@ use crate::asset::Asset; use crate::config::network::Network; +use crate::utils::math::{self, percent_to_u256}; use crate::{config::wallet::Wallet, config::Config}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -42,9 +43,7 @@ pub struct RebalancerConfig { impl RebalancerConfig { pub fn parking_asset_min_move_u256(&self, decimals: u8) -> U256 { - //TODO: review u128 - let qe = (self.parking_asset_min_move * 10_f64.powf(decimals.into())) as u128; - U256::from(qe) + math::f64_to_u256(self.parking_asset_min_move, decimals) } pub fn network_id(&self) -> &str { @@ -158,6 +157,7 @@ impl RebalancerConfig { threshold-percent_diff = -0,28 ``` */ + // TODO: add tests and refactor this function pub fn reach_min_threshold(&self, assets_balances: &[AssetBalances]) -> bool { // TODO: abstract this // abs for U256 @@ -170,11 +170,9 @@ impl RebalancerConfig { }; let quoted_asset_decimals = assets_balances.last().unwrap().quoted_asset_decimals(); - let max_percent_u256 = U256::from(100_i32) * U256::exp10(quoted_asset_decimals.into()); + let max_percent_u256 = percent_to_u256(100.0, quoted_asset_decimals); - let thresold_percent_u256 = U256::from( - (self.threshold_percent * 10_f64.powf(quoted_asset_decimals.into())) as u128, - ); + let thresold_percent_u256 = percent_to_u256(self.threshold_percent, quoted_asset_decimals); tracing::debug!( "reach_min_threshold(): thresold_percent_u256: {:?}", thresold_percent_u256 diff --git a/src/rebalancer/mod.rs b/src/rebalancer/mod.rs index f173051..ffa7967 100644 --- a/src/rebalancer/mod.rs +++ b/src/rebalancer/mod.rs @@ -1,12 +1,15 @@ pub mod cmd; pub mod config; +use std::ops::{Mul, Sub}; + use crate::{ - asset::Asset, config::wallet::Wallet, rebalancer::config::RebalancerConfig, - utils::blockchain::display_amount_to_float, + asset::Asset, + config::wallet::Wallet, + rebalancer::config::RebalancerConfig, + utils::{blockchain::display_amount_to_float, math::percent_to_u256, scalar::BigDecimal}, }; -use num_bigint::{BigInt, Sign}; use serde::{Deserialize, Serialize}; use web3::types::U256; @@ -156,16 +159,8 @@ impl AssetBalances { } } - pub fn desired_quoted_in_balance(&self, total_quoted_balance: U256) -> U256 { - //self.quoted_balance / total_quoted_balance - let p = ((self.percent / 100.0) * 10_f64.powf(self.quoted_asset_decimals.into())) as u128; - (total_quoted_balance * U256::from(p)) / U256::exp10(self.quoted_asset_decimals.into()) - } - - pub fn desired_parking_to_move(&self, total_parking_balance: U256, decimals: u8) -> U256 { - //self.quoted_balance / total_quoted_balance - let p = ((self.percent / 100.0) * 10_f64.powf(decimals.into())) as u128; - (total_parking_balance * U256::from(p)) / U256::exp10(decimals.into()) + pub fn get_amount_by_percent(&self, total_balance: U256, decimals: u8) -> U256 { + get_amount_to_trade(total_balance, U256::zero(), self.percent, decimals).to_unsigned_u256() } pub fn quoted_balance(&self) -> U256 { @@ -178,10 +173,26 @@ impl AssetBalances { self.percent } pub fn quoted_asset_percent_u256(&self) -> U256 { - U256::from((self.percent * 10_f64.powf(self.quoted_asset_decimals.into())) as u128) + percent_to_u256(self.percent, self.quoted_asset_decimals) } } +pub fn get_amount_to_trade( + total_balance: U256, + balance: U256, + percent: f64, + decimals: u8, +) -> BigDecimal { + let total_balance_bd = BigDecimal::from_unsigned_u256(&total_balance, decimals.into()); + let balance_bd = BigDecimal::from_unsigned_u256(&balance, decimals.into()); + let percent_bd = BigDecimal::try_from(percent / 100.0).unwrap(); + + total_balance_bd + .mul(percent_bd) + .sub(balance_bd) + .with_scale(decimals.into()) +} + pub async fn get_assets_balances( rebalancer_config: &RebalancerConfig, assets: Vec, @@ -253,11 +264,12 @@ pub async fn move_asset_with_slippage( } let asset_out_decimals = asset_out.decimals().await.unwrap(); - let amount_in_slippage = asset_in.slippage_u256(asset_out_decimals); - let amount_out_slippage = asset_out.slippage_u256(asset_out_decimals); - let slippage = amount_in_slippage + amount_out_slippage; + let slippage = + asset_in.slippage_u256(asset_out_decimals) + asset_out.slippage_u256(asset_out_decimals); + let slippage_amount = (amount_out * slippage) / U256::exp10(asset_out_decimals.into()); + let asset_out_amount_slip = amount_out - slippage_amount; tracing::debug!("asset_out_amount_slip: {:?}", asset_out_amount_slip); @@ -346,9 +358,9 @@ pub async fn move_parking_to_assets( continue; } - let parking_slip = parking_asset.slippage_u256(ab.asset_decimals); + let parking_slippage = parking_asset.slippage_u256(ab.asset_decimals); let parking_amount = - ab.desired_parking_to_move(total_parking_balance, parking_asset_decimals); + ab.get_amount_by_percent(total_parking_balance, parking_asset_decimals); tracing::debug!("desired_parking_to_move: {}", parking_amount); let exchange = ab.asset @@ -373,8 +385,8 @@ pub async fn move_parking_to_assets( .into(); tracing::debug!("asset_amount_out: {:?}", asset_amount_out); - let ab_slip = ab.asset.slippage_u256(ab.asset_decimals); - let slippage = ab_slip + parking_slip; + let ab_slippage = ab.asset.slippage_u256(ab.asset_decimals); + let slippage = ab_slippage + parking_slippage; tracing::debug!("slippage: {:?}", slippage); let slippage_amount = (asset_amount_out * slippage) / U256::exp10(ab.asset_decimals.into()); @@ -406,22 +418,6 @@ pub async fn move_parking_to_assets( } } -//TODO: create a mod to carry all the U256 ops -// https://github.com/graphprotocol/graph-node/blob/master/graph/src/data/store/scalar.rs -// U256 -> BigUint -// BigUint -> U256 -pub fn u256_to_bigint(u: U256) -> BigInt { - let mut bytes: [u8; 32] = [0; 32]; - u.to_little_endian(&mut bytes); - BigInt::from_bytes_le(Sign::Plus, &bytes) -} - -pub fn bigint_to_u256(b: BigInt) -> U256 { - let (_, unb) = b.into_parts(); - let bytes = unb.to_bytes_le(); - U256::from_little_endian(&bytes) -} - //TODO: break validation and threshold pub async fn validate(config: &RebalancerConfig) -> Result<(), anyhow::Error> { if !config.is_valid_portfolio_total_percentage() { @@ -527,12 +523,11 @@ pub async fn generate_asset_rebalances( //TODO: add thresould per position - let mut total = BigInt::from(0_i32); + let mut total = BigDecimal::zero(); let assets_balances = get_assets_balances(config, assets.clone()).await; let assets_balances_with_parking = add_parking_asset(config, assets_balances).await; - //TODO: add a sum_U256 in the module of U256 ops let total_quoted_balance = assets_balances_with_parking .iter() .fold(U256::from(0_i32), |acc, x| acc + x.quoted_balance()); @@ -544,36 +539,36 @@ pub async fn generate_asset_rebalances( let mut asset_rebalances = vec![]; - let tqb = u256_to_bigint(total_quoted_balance); - tracing::debug!("diff_parking: tqb: {}", tqb); - // TODO: break this for in functions to return rp_to_parking rp_from_parking for ab in assets_balances_with_parking.clone() { - let quoted_balance = u256_to_bigint(ab.quoted_balance()); - let diff = tqb.clone() - quoted_balance.clone(); - - let pow = 10_u32.pow(4); - // let percent_diff = (diff.clone() * pow) / quoted_balance.clone(); - let percent: BigInt = ((quoted_balance.clone() * pow) / tqb.clone()) * 100_i32; - let percent_to_buy = (ab.percent() * 10_f64.powf(4.0)) as u32 - percent.clone(); - // ((2730469751527576947)*((35,68/100)*1e18))/1e18 - // TODO: may add slippage in this calcs - let amount_to_trade: BigInt = (tqb.clone() - * (percent_to_buy.clone() * 10_u128.pow((ab.asset_decimals - 4 - 2).into()))) - / 10_u128.pow(ab.asset_decimals.into()); - - total += amount_to_trade.clone(); + tracing::debug!( + "generate_asset_rebalances(): loop assets_balances_with_parking; ab: {:?}", + ab + ); + let amount_to_trade = get_amount_to_trade( + total_quoted_balance, + ab.quoted_balance, + ab.percent, + ab.quoted_asset_decimals, + ); - let quoted_amount_to_trade = bigint_to_u256(amount_to_trade.clone()); + total = total + amount_to_trade.clone(); // if amount_to_trade is negative, move to parking - let kind = if amount_to_trade <= BigInt::from(0_i32) { + let kind = if amount_to_trade <= BigDecimal::zero() { Kind::ToParking } else { Kind::FromParking }; - match AssetRebalancer::new(kind, config.clone(), ab.clone(), quoted_amount_to_trade).await { + match AssetRebalancer::new( + kind, + config.clone(), + ab.clone(), + amount_to_trade.abs().to_unsigned_u256(), + ) + .await + { Some(ar) => asset_rebalances.push(ar), None => { tracing::debug!("diff_parking: rebalancer_parking cant be created, continue."); @@ -581,13 +576,10 @@ pub async fn generate_asset_rebalances( } }; - tracing::debug!("diff_parking: ab: {}, quoted_balance: {}, ab.percent(): {}, percent: {}, diff: {}, percent_to_buy: {}, amount_to_trade: {}, total: {}", + tracing::debug!("diff_parking: ab: {}, quoted_balance: {}, ab.percent(): {}, amount_to_trade: {}, total: {}", ab.asset.name(), - quoted_balance, - ab.percent(), - percent, - diff, - percent_to_buy, + ab.quoted_balance, + ab.percent, amount_to_trade, total, ); @@ -597,10 +589,68 @@ pub async fn generate_asset_rebalances( } pub async fn run_diff_parking(config: &RebalancerConfig) -> Result<(), anyhow::Error> { - let asset_balances = generate_asset_rebalances(config).await?; + let asset_rebalancers = generate_asset_rebalances(config).await?; - run_diff_parking_per_kind(config, Kind::ToParking, asset_balances.clone()).await; - run_diff_parking_per_kind(config, Kind::FromParking, asset_balances.clone()).await; + run_diff_parking_per_kind(config, Kind::ToParking, asset_rebalancers.clone()).await; + run_diff_parking_per_kind(config, Kind::FromParking, asset_rebalancers.clone()).await; Ok(()) } + +mod test { + #[test] + fn get_amount_to_trade_test() { + use crate::rebalancer::get_amount_to_trade; + use crate::utils::scalar::BigDecimal; + use web3::types::U256; + + struct TestCase { + total_balance: U256, + balance: U256, + percent: f64, + decimals: u8, + expected: BigDecimal, + } + + let test_cases = vec![ + TestCase { + total_balance: BigDecimal::from(20000_i32) + .with_scale(18) + .to_unsigned_u256(), + balance: BigDecimal::from(100_i32).with_scale(18).to_unsigned_u256(), + percent: 10.0, + decimals: 18_u8, + expected: BigDecimal::from(1900_i32), + }, + TestCase { + total_balance: BigDecimal::from(17333_i32) + .with_scale(18) + .to_unsigned_u256(), + balance: BigDecimal::from(97_i32).with_scale(18).to_unsigned_u256(), + percent: 2.5, + decimals: 18_u8, + expected: BigDecimal::try_from(336.325).unwrap(), + }, + TestCase { + total_balance: BigDecimal::from(20000_i32) + .with_scale(18) + .to_unsigned_u256(), + balance: BigDecimal::from(2000_i32).with_scale(18).to_unsigned_u256(), + percent: 2.0, + decimals: 18_u8, + expected: BigDecimal::from(-1600_i32), + }, + ]; + + for test_case in test_cases { + let amount_to_trade = get_amount_to_trade( + test_case.total_balance, + test_case.balance, + test_case.percent, + test_case.decimals, + ); + + assert_eq!(test_case.expected, amount_to_trade) + } + } +} diff --git a/src/unwrap/cmd.rs b/src/unwrap/cmd.rs index 26316e1..97b1761 100644 --- a/src/unwrap/cmd.rs +++ b/src/unwrap/cmd.rs @@ -5,7 +5,11 @@ pub fn generate() -> Command { .about("Unwrap a wrapped coin to coin") .arg(clap::arg!(-n --"network" "Network to unwrap token to coin").required(true)) .arg(clap::arg!(-w --"wallet" "Wallet id from config file").required(true)) - .arg(clap::arg!(-a --"amount" "Amount to unwrap token into coin").required(false)) + .arg( + clap::arg!(-a --"amount" "Amount to unwrap token into coin") + .required(false) + .value_parser(clap::value_parser!(f64)), + ) } #[tracing::instrument(name = "unwrap call command")] diff --git a/src/utils/blockchain.rs b/src/utils/blockchain.rs index 4a669e4..892ae66 100644 --- a/src/utils/blockchain.rs +++ b/src/utils/blockchain.rs @@ -13,6 +13,8 @@ use web3::{ use crate::{asset::Asset, config::wallet::Wallet}; +use super::scalar::BigDecimal; + pub async fn estimate_gas

( contract: &Contract, from_wallet: &Wallet, @@ -124,7 +126,9 @@ pub async fn wait_receipt( } pub fn display_amount_to_float(amount: U256, decimals: u8) -> f64 { - amount.low_u128() as f64 / 10_u64.pow(decimals.into()) as f64 + BigDecimal::from_unsigned_u256(&amount, decimals.into()) + .to_f64() + .unwrap() } pub async fn amount_in_quoted(asset_in: &Asset, asset_quoted: &Asset, amount_in: U256) -> U256 { diff --git a/src/utils/math.rs b/src/utils/math.rs new file mode 100644 index 0000000..394c573 --- /dev/null +++ b/src/utils/math.rs @@ -0,0 +1,116 @@ +use std::ops::Mul; +use web3::types::U256; + +use super::scalar::BigDecimal; + +//TODO: add test to all functions + +pub fn to_percent(value: f64) -> f64 { + value / 100.0 +} + +pub fn f64_to_u256(value: f64, decimals: u8) -> U256 { + f64_to_bigdecimal(value, decimals).to_unsigned_u256() +} + +pub fn f64_to_bigdecimal(value: f64, decimals: u8) -> BigDecimal { + BigDecimal::try_from(value) + .unwrap() + .with_scale(decimals.into()) +} + +pub fn percent_to_bigdecimal(percent: f64, decimals: u8) -> BigDecimal { + f64_to_bigdecimal(to_percent(percent), decimals) +} + +pub fn percent_to_u256(percent: f64, decimals: u8) -> U256 { + f64_to_u256(to_percent(percent), decimals) +} + +pub fn get_slippage_amount(amount: U256, slippage: f64, decimals: u8) -> U256 { + let amount_bd = BigDecimal::from_unsigned_u256(&amount, decimals.into()); + let slippage_bd = percent_to_bigdecimal(slippage, decimals); + + amount_bd + .mul(slippage_bd) + .with_scale(decimals.into()) + .to_unsigned_u256() +} + +mod test { + #[test] + fn get_slippage_amount_test() { + use crate::utils::{math::get_slippage_amount, scalar::BigDecimal}; + use web3::types::U256; + + struct TestCase { + amount: U256, + slippage: f64, + decimals: u8, + expected: U256, + } + + let test_cases = vec![ + TestCase { + amount: BigDecimal::from(12).with_scale(18).to_unsigned_u256(), + slippage: 2.0, + decimals: 18, + expected: BigDecimal::try_from(12.0 * 0.02) + .unwrap() + .with_scale(18) + .to_unsigned_u256(), + }, + TestCase { + amount: BigDecimal::from(12).with_scale(6).to_unsigned_u256(), + slippage: 1.5, + decimals: 6, + expected: U256::from(180000_u32), + }, + TestCase { + amount: U256::from(153987924_u32), + slippage: 4.0, + decimals: 6, + expected: U256::from(6159516_u32), + }, + TestCase { + amount: BigDecimal::from(12).with_scale(18).to_unsigned_u256(), + slippage: 0.5, + decimals: 18, + expected: BigDecimal::try_from(12.0 * 0.005) + .unwrap() + .with_scale(18) + .to_unsigned_u256(), + }, + TestCase { + amount: BigDecimal::try_from(13.33) + .unwrap() + .with_scale(18) + .to_unsigned_u256(), + slippage: 0.3, + decimals: 18, + expected: BigDecimal::try_from(13.33 * 0.003) + .unwrap() + .with_scale(18) + .to_unsigned_u256(), + }, + TestCase { + amount: BigDecimal::try_from(13.33) + .unwrap() + .with_scale(6) + .to_unsigned_u256(), + slippage: 0.3, + decimals: 6, + expected: BigDecimal::try_from(13.33 * 0.003) + .unwrap() + .with_scale(6) + .to_unsigned_u256(), + }, + ]; + + for test_case in test_cases { + let result = + get_slippage_amount(test_case.amount, test_case.slippage, test_case.decimals); + assert_eq!(test_case.expected, result); + } + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 5361843..9928f24 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,6 @@ pub mod blockchain; pub mod hidden; +pub mod math; pub mod password; pub mod resources; pub mod scalar; diff --git a/src/utils/scalar.rs b/src/utils/scalar.rs index 9bc060b..387cc8e 100644 --- a/src/utils/scalar.rs +++ b/src/utils/scalar.rs @@ -65,6 +65,10 @@ impl BigDecimal { self.0.as_bigint_and_exponent() } + pub fn abs(&self) -> BigDecimal { + BigDecimal::from(self.0.abs()) + } + pub fn to_unsigned_u256(&self) -> U256 { let (bigint, _) = self.as_bigint_and_exponent(); BigInt::from(bigint).to_unsigned_u256() @@ -305,6 +309,7 @@ impl BigInt { ); U256::from_little_endian(&bytes) } + pub fn pow(self, exponent: u8) -> Self { use num_traits::pow::Pow; @@ -340,6 +345,12 @@ impl From for BigInt { } } +impl From for BigInt { + fn from(i: u8) -> BigInt { + BigInt(i.into()) + } +} + impl From for BigInt { fn from(i: i64) -> BigInt { BigInt(i.into()) @@ -466,6 +477,7 @@ mod test { (11.0, BigDecimal::try_from(11).unwrap(), "11"), (0.0, BigDecimal::try_from(0).unwrap(), "0"), (0.33, BigDecimal::try_from(0.33).unwrap(), "0.33"), + (-0.33, BigDecimal::try_from(-0.33).unwrap(), "-0.33"), ]; for (fnumber, fbigdecimal, snumber) in test_cases { diff --git a/src/wrap/cmd.rs b/src/wrap/cmd.rs index dc1b6db..4719611 100644 --- a/src/wrap/cmd.rs +++ b/src/wrap/cmd.rs @@ -12,8 +12,9 @@ pub fn generate() -> Command { .required(true), ) .arg( - clap::arg!(-a --"amount" "Amount to wrap coin into token, default: (balance-min_balance_coin)") - .required(false), + clap::arg!(-a --"amount" "Amount to wrap coin into token, default: (balance-min_balance_coin)") + .required(false) + .value_parser(clap::value_parser!(f64)), ) } diff --git a/tests/common.rs b/tests/common.rs index be33c98..2ad8d7a 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -17,7 +17,7 @@ static TRACING: Lazy<()> = Lazy::new(|| { }); pub struct App { - command: Command, + _command: Command, config: Config, } @@ -27,16 +27,16 @@ impl App { Self::default() } - pub fn command(&self) -> Command { - self.command.clone() + pub fn _command(&self) -> Command { + self._command.clone() } pub fn config(&self) -> Config { self.config.clone() } - pub fn get_arg_matches(&self, argv: &'static str) -> ArgMatches { - get_arg_matches(self.command(), argv) + pub fn _get_arg_matches(&self, argv: &'static str) -> ArgMatches { + _get_arg_matches(self._command(), argv) } } @@ -45,13 +45,13 @@ impl Default for App { Lazy::force(&TRACING); App { - command: cmd::new(), + _command: cmd::new(), config: Config::from_file(DEFAULT_CONFIG_FILE).unwrap(), } } } -pub fn get_arg_matches(cmd: Command, argv: &'static str) -> ArgMatches { +pub fn _get_arg_matches(cmd: Command, argv: &'static str) -> ArgMatches { cmd.try_get_matches_from(argv.split(' ').collect::>()) .unwrap() }