From dfe9986d215a87c89dc0d00d63557968ce472e44 Mon Sep 17 00:00:00 2001 From: 0xOsiris Date: Wed, 28 Aug 2024 23:47:06 -0700 Subject: [PATCH 1/3] feat: simulate_swap simulate_swap_mut --- src/amm/balancer_v2/batch_request/mod.rs | 2 +- src/amm/balancer_v2/bmath.rs | 147 ++++++++++++++++++----- src/amm/balancer_v2/error.rs | 15 +++ src/amm/balancer_v2/mod.rs | 17 +-- src/amm/erc_4626/mod.rs | 2 +- src/amm/uniswap_v2/mod.rs | 2 +- src/errors.rs | 4 + src/filters/value.rs | 1 - 8 files changed, 151 insertions(+), 39 deletions(-) create mode 100644 src/amm/balancer_v2/error.rs diff --git a/src/amm/balancer_v2/batch_request/mod.rs b/src/amm/balancer_v2/batch_request/mod.rs index d0bb356..9ca7f41 100644 --- a/src/amm/balancer_v2/batch_request/mod.rs +++ b/src/amm/balancer_v2/batch_request/mod.rs @@ -118,7 +118,7 @@ where let return_data_tokens = constructor_return.abi_decode_sequence(&res)?; if let Some(tokens_arr) = return_data_tokens.as_array() { - for (i, token) in tokens_arr.into_iter().enumerate() { + for (i, token) in tokens_arr.iter().enumerate() { let pool_data = token .as_tuple() .ok_or(AMMError::BatchRequestError(amms[i].address()))?; diff --git a/src/amm/balancer_v2/bmath.rs b/src/amm/balancer_v2/bmath.rs index 187d2be..7bb8e68 100644 --- a/src/amm/balancer_v2/bmath.rs +++ b/src/amm/balancer_v2/bmath.rs @@ -3,28 +3,111 @@ use rug::Float; use crate::amm::consts::{BONE, DECIMAL_RADIX, MPFR_T_PRECISION, U256_2}; -pub fn badd(a: U256, b: U256) -> U256 { +use super::error::BMathError; + +pub fn btoi(a: U256) -> U256 { + a / BONE +} + +pub fn badd(a: U256, b: U256) -> Result { let c = a + b; - assert!(c >= a, "ERR_ADD_OVERFLOW"); - c + if c < a { + return Err(BMathError::AddOverflow); + } + Ok(c) +} + +pub fn bpowi(a: U256, n: U256) -> Result { + let mut z = if n % U256::from(2u64) != U256::ZERO { + a + } else { + BONE + }; + + let mut a = a; + let mut n = n / U256::from(2u64); + while n != U256::ZERO { + a = bmul(a, a)?; + if n % U256::from(2u64) != U256::ZERO { + z = bmul(z, a)?; + } + n /= U256::from(2u64); + } + Ok(z) +} + +pub fn bpow(base: U256, exp: U256) -> Result { + let whole = bfloor(exp); + let remain = bsub(exp, whole)?; + let whole_pow = bpowi(base, btoi(whole))?; + if remain == U256::ZERO { + return Ok(whole_pow); + } + let precision = BONE / U256::from(10_u64.pow(10)); + let partial_result = bpow_approx(base, remain, precision)?; + bmul(whole_pow, partial_result) } + +pub fn bpow_approx(base: U256, exp: U256, precision: U256) -> Result { + let a = exp; + let (x, xneg) = bsub_sign(base, BONE); + let mut term = BONE; + let mut sum = term; + let mut negative = false; + let mut i = 1; + while term >= precision { + let big_k = U256::from(i) * BONE; + let (c, cneg) = bsub_sign(a, bsub(big_k, BONE)?); + term = bmul(term, bmul(c, x)?)?; + term = bdiv(term, big_k)?; + if term == U256::ZERO { + break; + } + if xneg { + negative = !negative; + } + if cneg { + negative = !negative; + } + if negative { + sum = bsub(sum, term)?; + } else { + sum = badd(sum, term)?; + } + i += 1; + } + Ok(sum) +} + +pub fn bfloor(a: U256) -> U256 { + btoi(a) * BONE +} + // Reference: // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L75 -pub fn bdiv(a: U256, b: U256) -> U256 { - assert!(b != U256::ZERO, "ERR_DIV_ZERO"); +pub fn bdiv(a: U256, b: U256) -> Result { + if b == U256::ZERO { + return Err(BMathError::DivZero); + } let c0 = a * BONE; - assert!(a == U256::ZERO || c0 / a == BONE, "ERR_DIV_INTERNAL"); + if a != U256::ZERO && c0 / a != BONE { + return Err(BMathError::DivInternal); + } let c1 = c0 + (b / U256_2); - assert!(c1 >= c0, "ERR_DIV_INTERNAL"); - c1 / b + if c1 < c0 { + return Err(BMathError::DivInternal); + } + Ok(c1 / b) } // Reference: // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L43 -pub fn bsub(a: U256, b: U256) -> U256 { +pub fn bsub(a: U256, b: U256) -> Result { let (c, flag) = bsub_sign(a, b); - assert!(!flag, "ERR_SUB_UNDERFLOW"); - c + if flag { + return Err(BMathError::SubUnderflow); + } + Ok(c) } // Reference: @@ -39,12 +122,16 @@ pub fn bsub_sign(a: U256, b: U256) -> (U256, bool) { // Reference: // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L63C4-L73C6 -pub fn bmul(a: U256, b: U256) -> U256 { +pub fn bmul(a: U256, b: U256) -> Result { let c0 = a * b; - assert!(a == U256::ZERO || c0 / a == b, "ERR_MUL_OVERFLOW"); + if a != U256::ZERO && c0 / a != b { + return Err(BMathError::MulOverflow); + } let c1 = c0 + (BONE / U256_2); - assert!(c1 >= c0, "ERR_MUL_OVERFLOW"); - c1 / BONE + if c1 < c0 { + return Err(BMathError::MulOverflow); + } + Ok(c1 / BONE) } /********************************************************************************************** @@ -56,11 +143,17 @@ pub fn bmul(a: U256, b: U256) -> U256 { // wO = tokenWeightOut // // sF = swapFee // **********************************************************************************************/ -pub fn calculate_price(b_i: U256, w_i: U256, b_o: U256, w_o: U256, s_f: U256) -> U256 { - let numer = bdiv(b_i, w_i); - let denom = bdiv(b_o, w_o); - let ratio = bdiv(numer, denom); - let scale = bdiv(BONE, bsub(BONE, s_f)); +pub fn calculate_price( + b_i: U256, + w_i: U256, + b_o: U256, + w_o: U256, + s_f: U256, +) -> Result { + let numer = bdiv(b_i, w_i)?; + let denom = bdiv(b_o, w_o)?; + let ratio = bdiv(numer, denom)?; + let scale = bdiv(BONE, bsub(BONE, s_f)?)?; bmul(ratio, scale) } @@ -81,13 +174,13 @@ pub fn calculate_out_given_in( token_weight_out: U256, token_amount_in: U256, swap_fee: U256, -) -> U256 { - let weight_ratio = bdiv(token_weight_in, token_weight_out); - let adjusted_in = bsub(BONE, swap_fee); - let adjusted_in = bmul(token_amount_in, adjusted_in); - let y = bdiv(token_balance_in, badd(token_balance_in, adjusted_in)); - let foo = y.pow(weight_ratio); - let bar = bsub(BONE, foo); +) -> Result { + let weight_ratio = bdiv(token_weight_in, token_weight_out)?; + let adjusted_in = bsub(BONE, swap_fee)?; + let adjusted_in = bmul(token_amount_in, adjusted_in)?; + let y = bdiv(token_balance_in, badd(token_balance_in, adjusted_in)?)?; + let foo = bpow(y, weight_ratio)?; + let bar = bsub(BONE, foo)?; bmul(token_balance_out, bar) } diff --git a/src/amm/balancer_v2/error.rs b/src/amm/balancer_v2/error.rs new file mode 100644 index 0000000..26568fe --- /dev/null +++ b/src/amm/balancer_v2/error.rs @@ -0,0 +1,15 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum BMathError { + #[error("Division by zero")] + DivZero, + #[error("Error during division")] + DivInternal, + #[error("Addition overflow")] + AddOverflow, + #[error("Subtraction underflow")] + SubUnderflow, + #[error("Multiplication overflow")] + MulOverflow, +} diff --git a/src/amm/balancer_v2/mod.rs b/src/amm/balancer_v2/mod.rs index c27fe18..8d98045 100644 --- a/src/amm/balancer_v2/mod.rs +++ b/src/amm/balancer_v2/mod.rs @@ -1,12 +1,13 @@ pub mod batch_request; -mod bmath; +pub mod bmath; +pub mod error; pub mod factory; use std::sync::Arc; use alloy::{ network::Network, - primitives::{ruint::BaseConvertError, Address, B256, U256}, + primitives::{Address, B256, U256}, providers::Provider, rpc::types::Log, sol, @@ -147,7 +148,7 @@ impl AutomatedMarketMaker for BalancerV2Pool { let dividend = (balance_quote / norm_weight_quote) * bone.clone(); let divisor = (balance_base / norm_weight_base) - * (bone - Float::with_val(MPFR_T_PRECISION, self.fee as u32)); + * (bone - Float::with_val(MPFR_T_PRECISION, self.fee)); let ratio = dividend / divisor; Ok(ratio.to_f64_round(Round::Nearest)) } @@ -234,7 +235,7 @@ impl AutomatedMarketMaker for BalancerV2Pool { quote_token_weight, amount_in, swap_fee, - )) + )?) } /// Locally simulates a swap in the AMM. @@ -283,9 +284,9 @@ impl AutomatedMarketMaker for BalancerV2Pool { quote_token_weight, amount_in, swap_fee, - ); - self.liquidity[base_token_index] = bmath::badd(base_token_balance, amount_in); - self.liquidity[quote_token_index] = bmath::bsub(quote_token_balance, out); + )?; + self.liquidity[base_token_index] = bmath::badd(base_token_balance, amount_in)?; + self.liquidity[quote_token_index] = bmath::bsub(quote_token_balance, out)?; Ok(out) } } @@ -388,7 +389,7 @@ mod tests { .calcOutGivenIn( balancer_v2_pool.liquidity[0], balancer_v2_pool.weights[0], - balancer_v2_pool.liquidity[0], + balancer_v2_pool.liquidity[1], balancer_v2_pool.weights[1], amount_in, U256::from(balancer_v2_pool.fee), diff --git a/src/amm/erc_4626/mod.rs b/src/amm/erc_4626/mod.rs index c4d84e8..ee3adcd 100644 --- a/src/amm/erc_4626/mod.rs +++ b/src/amm/erc_4626/mod.rs @@ -306,7 +306,7 @@ impl ERC4626Vault { #[cfg(test)] mod tests { - use std::{ops::Add, sync::Arc}; + use std::sync::Arc; use alloy::{ primitives::{address, Address, U256}, diff --git a/src/amm/uniswap_v2/mod.rs b/src/amm/uniswap_v2/mod.rs index ec8e383..a6ed26e 100644 --- a/src/amm/uniswap_v2/mod.rs +++ b/src/amm/uniswap_v2/mod.rs @@ -553,7 +553,7 @@ pub fn q64_to_f64(x: u128) -> f64 { #[cfg(test)] mod tests { - use std::{ops::Add, sync::Arc}; + use std::sync::Arc; use alloy::{ primitives::{address, Address, U256}, diff --git a/src/errors.rs b/src/errors.rs index 401473e..21cdfe3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -6,6 +6,8 @@ use thiserror::Error; use tokio::task::JoinError; use uniswap_v3_math::error::UniswapV3MathError; +use crate::amm::balancer_v2::error::BMathError; + #[derive(Error, Debug)] pub enum AMMError { #[error(transparent)] @@ -100,6 +102,8 @@ pub enum SwapSimulationError { LiquidityUnderflow, #[error(transparent)] ArithmeticError(#[from] ArithmeticError), + #[error(transparent)] + BMathError(#[from] BMathError), } #[derive(Error, Debug)] diff --git a/src/filters/value.rs b/src/filters/value.rs index 7b965a8..12f7909 100644 --- a/src/filters/value.rs +++ b/src/filters/value.rs @@ -11,7 +11,6 @@ use alloy::{ use crate::{ amm::{ - balancer_v2::factory::BalancerV2Factory, factory::{AutomatedMarketMakerFactory, Factory}, AutomatedMarketMaker, AMM, }, From 74ab4cd6fad22cd6c2411d89f888ad93645cdb0f Mon Sep 17 00:00:00 2001 From: 0xOsiris Date: Thu, 29 Aug 2024 12:16:52 -0700 Subject: [PATCH 2/3] Update src/amm/balancer_v2/bmath.rs Co-authored-by: 0xKitsune <77890308+0xKitsune@users.noreply.github.com> --- src/amm/balancer_v2/bmath.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/amm/balancer_v2/bmath.rs b/src/amm/balancer_v2/bmath.rs index 7bb8e68..34273e2 100644 --- a/src/amm/balancer_v2/bmath.rs +++ b/src/amm/balancer_v2/bmath.rs @@ -63,12 +63,7 @@ pub fn bpow_approx(base: U256, exp: U256, precision: U256) -> Result Date: Thu, 29 Aug 2024 20:20:07 -0700 Subject: [PATCH 3/3] pr nits --- Cargo.toml | 1 - src/amm/balancer_v2/bmath.rs | 37 ++++++++++++++++++++---------------- src/amm/balancer_v2/mod.rs | 15 +++++++++------ src/amm/consts.rs | 1 + 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2874517..ecaf7ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ alloy = { version = "0.2", features = [ "rpc-types-eth", "signer-local", ] } -reqwest = "0.12" rug = "1.24.1" [features] diff --git a/src/amm/balancer_v2/bmath.rs b/src/amm/balancer_v2/bmath.rs index 34273e2..ad275bd 100644 --- a/src/amm/balancer_v2/bmath.rs +++ b/src/amm/balancer_v2/bmath.rs @@ -1,7 +1,7 @@ use alloy::primitives::U256; use rug::Float; -use crate::amm::consts::{BONE, DECIMAL_RADIX, MPFR_T_PRECISION, U256_2}; +use crate::amm::consts::{BONE, DECIMAL_RADIX, MPFR_T_PRECISION, U256_1, U256_10E_10, U256_2}; use super::error::BMathError; @@ -9,6 +9,7 @@ pub fn btoi(a: U256) -> U256 { a / BONE } +#[inline] pub fn badd(a: U256, b: U256) -> Result { let c = a + b; if c < a { @@ -17,25 +18,23 @@ pub fn badd(a: U256, b: U256) -> Result { Ok(c) } +#[inline] pub fn bpowi(a: U256, n: U256) -> Result { - let mut z = if n % U256::from(2u64) != U256::ZERO { - a - } else { - BONE - }; + let mut z = if n % U256_2 != U256::ZERO { a } else { BONE }; let mut a = a; - let mut n = n / U256::from(2u64); + let mut n = n / U256_2; while n != U256::ZERO { a = bmul(a, a)?; - if n % U256::from(2u64) != U256::ZERO { + if n % U256_2 != U256::ZERO { z = bmul(z, a)?; } - n /= U256::from(2u64); + n /= U256_2; } Ok(z) } +#[inline] pub fn bpow(base: U256, exp: U256) -> Result { let whole = bfloor(exp); let remain = bsub(exp, whole)?; @@ -43,18 +42,19 @@ pub fn bpow(base: U256, exp: U256) -> Result { if remain == U256::ZERO { return Ok(whole_pow); } - let precision = BONE / U256::from(10_u64.pow(10)); + let precision = BONE / U256_10E_10; let partial_result = bpow_approx(base, remain, precision)?; bmul(whole_pow, partial_result) } +#[inline] pub fn bpow_approx(base: U256, exp: U256, precision: U256) -> Result { let a = exp; let (x, xneg) = bsub_sign(base, BONE); let mut term = BONE; let mut sum = term; let mut negative = false; - let mut i = 1; + let mut i = U256_1; while term >= precision { let big_k = U256::from(i) * BONE; let (c, cneg) = bsub_sign(a, bsub(big_k, BONE)?); @@ -63,23 +63,25 @@ pub fn bpow_approx(base: U256, exp: U256, precision: U256) -> Result U256 { btoi(a) * BONE } // Reference: // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L75 +#[inline] pub fn bdiv(a: U256, b: U256) -> Result { if b == U256::ZERO { return Err(BMathError::DivZero); @@ -97,6 +99,7 @@ pub fn bdiv(a: U256, b: U256) -> Result { // Reference: // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L43 +#[inline] pub fn bsub(a: U256, b: U256) -> Result { let (c, flag) = bsub_sign(a, b); if flag { @@ -107,6 +110,7 @@ pub fn bsub(a: U256, b: U256) -> Result { // Reference: // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L52 +#[inline] pub fn bsub_sign(a: U256, b: U256) -> (U256, bool) { if a >= b { (a - b, false) @@ -117,6 +121,7 @@ pub fn bsub_sign(a: U256, b: U256) -> (U256, bool) { // Reference: // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L63C4-L73C6 +#[inline] pub fn bmul(a: U256, b: U256) -> Result { let c0 = a * b; if a != U256::ZERO && c0 / a != b { @@ -174,9 +179,9 @@ pub fn calculate_out_given_in( let adjusted_in = bsub(BONE, swap_fee)?; let adjusted_in = bmul(token_amount_in, adjusted_in)?; let y = bdiv(token_balance_in, badd(token_balance_in, adjusted_in)?)?; - let foo = bpow(y, weight_ratio)?; - let bar = bsub(BONE, foo)?; - bmul(token_balance_out, bar) + let x = bpow(y, weight_ratio)?; + let z = bsub(BONE, x)?; + bmul(token_balance_out, z) } /// Converts a `U256` into a `Float` with a high precision. diff --git a/src/amm/balancer_v2/mod.rs b/src/amm/balancer_v2/mod.rs index 8d98045..e4f8a9e 100644 --- a/src/amm/balancer_v2/mod.rs +++ b/src/amm/balancer_v2/mod.rs @@ -308,8 +308,9 @@ mod tests { address: address!("8a649274E4d777FFC6851F13d23A86BBFA2f2Fbf"), ..Default::default() }; - let provider = - Arc::new(ProviderBuilder::new().on_http(env!("ETHEREUM_PROVIDER").parse().unwrap())); + let provider = Arc::new( + ProviderBuilder::new().on_http(env!("ETHEREUM_RPC_ENDPOINT").parse().unwrap()), + ); balancer_v2_pool .populate_data(Some(20487793), provider.clone()) .await @@ -335,8 +336,9 @@ mod tests { #[tokio::test] pub async fn test_calculate_price() { - let provider = - Arc::new(ProviderBuilder::new().on_http(env!("ETHEREUM_PROVIDER").parse().unwrap())); + let provider = Arc::new( + ProviderBuilder::new().on_http(env!("ETHEREUM_RPC_ENDPOINT").parse().unwrap()), + ); let mut balancer_v2_pool = super::BalancerV2Pool { address: address!("8a649274E4d777FFC6851F13d23A86BBFA2f2Fbf"), ..Default::default() @@ -358,8 +360,9 @@ mod tests { #[tokio::test] pub async fn test_simulate_swap() { - let provider = - Arc::new(ProviderBuilder::new().on_http(env!("ETHEREUM_PROVIDER").parse().unwrap())); + let provider = Arc::new( + ProviderBuilder::new().on_http(env!("ETHEREUM_RPC_ENDPOINT").parse().unwrap()), + ); let mut balancer_v2_pool = super::BalancerV2Pool { address: address!("8a649274E4d777FFC6851F13d23A86BBFA2f2Fbf"), ..Default::default() diff --git a/src/amm/consts.rs b/src/amm/consts.rs index b63bcfe..5fff587 100644 --- a/src/amm/consts.rs +++ b/src/amm/consts.rs @@ -1,6 +1,7 @@ use alloy::primitives::U256; // commonly used U256s +pub const U256_10E_10: U256 = U256::from_limbs([10000000000, 0, 0, 0]); pub const U256_0X100000000: U256 = U256::from_limbs([4294967296, 0, 0, 0]); pub const U256_0X10000: U256 = U256::from_limbs([65536, 0, 0, 0]); pub const U256_0X100: U256 = U256::from_limbs([256, 0, 0, 0]);