diff --git a/Cargo.lock b/Cargo.lock index 326d7392..5db1a4f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1091,7 +1091,7 @@ dependencies = [ [[package]] name = "fixed-point" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ethers", "eyre", @@ -1104,7 +1104,7 @@ dependencies = [ [[package]] name = "fixed-point-macros" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ethers", "fixed-point", @@ -1506,7 +1506,7 @@ dependencies = [ [[package]] name = "hyperdrive-addresses" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ethers", "serde", @@ -1514,7 +1514,7 @@ dependencies = [ [[package]] name = "hyperdrive-math" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ethers", "eyre", @@ -1531,7 +1531,7 @@ dependencies = [ [[package]] name = "hyperdrive-wrappers" -version = "0.15.4" +version = "0.15.5" dependencies = [ "dotenv", "ethers", @@ -1548,7 +1548,7 @@ dependencies = [ [[package]] name = "hyperdrivepy" -version = "0.15.4" +version = "0.15.5" dependencies = [ "ethers", "eyre", @@ -1672,6 +1672,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + [[package]] name = "ipnet" version = "2.9.0" @@ -2408,6 +2414,7 @@ checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" dependencies = [ "cfg-if", "indoc", + "inventory", "libc", "memoffset", "parking_lot", @@ -3270,7 +3277,7 @@ dependencies = [ [[package]] name = "test-utils" -version = "0.15.4" +version = "0.15.5" dependencies = [ "async-trait", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index c9c2ebb0..2ab8fa52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ [workspace.package] name="hyperdrive-rs" -version="0.15.4" +version="0.15.5" authors = [ "Alex Towle ", "Dylan Paiton ", diff --git a/bindings/hyperdrivepy/Cargo.toml b/bindings/hyperdrivepy/Cargo.toml index 1c86b423..519e9ba6 100644 --- a/bindings/hyperdrivepy/Cargo.toml +++ b/bindings/hyperdrivepy/Cargo.toml @@ -26,4 +26,4 @@ fixed-point-macros = { path = "../../crates/fixed-point-macros" } hyperdrive-math = { path = "../../crates/hyperdrive-math" } hyperdrive-wrappers = { path = "../../crates/hyperdrive-wrappers" } -pyo3 = { version = "0.19.0", features = ["abi3-py37"] } +pyo3 = { version = "0.19.0", features = ["abi3-py37", "multiple-pymethods"] } diff --git a/bindings/hyperdrivepy/pyproject.toml b/bindings/hyperdrivepy/pyproject.toml index d063ba60..ffb9aa86 100644 --- a/bindings/hyperdrivepy/pyproject.toml +++ b/bindings/hyperdrivepy/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hyperdrivepy" -version = "0.15.4" +version = "0.15.5" requires-python = ">=3.7" classifiers = [ "Programming Language :: Rust", diff --git a/bindings/hyperdrivepy/python/hyperdrivepy/hyperdrive_state.py b/bindings/hyperdrivepy/python/hyperdrivepy/hyperdrive_state.py index 10567258..625c4e1e 100644 --- a/bindings/hyperdrivepy/python/hyperdrivepy/hyperdrive_state.py +++ b/bindings/hyperdrivepy/python/hyperdrivepy/hyperdrive_state.py @@ -96,7 +96,7 @@ def calculate_solvency( pool_config: types.PoolConfigType, pool_info: types.PoolInfoType, ) -> str: - """Get the pool's solvency. + """Calculate the pool's solvency. Arguments --------- @@ -217,6 +217,32 @@ def calculate_open_long( return _get_interface(pool_config, pool_info).calculate_open_long(base_amount) +def calculate_pool_deltas_after_open_long( + pool_config: types.PoolConfigType, + pool_info: types.PoolInfoType, + base_amount: str, +) -> str: + """Calculate the bond deltas to be applied to the pool after opening a long. + + Arguments + --------- + pool_config: PoolConfig + Static configuration for the hyperdrive contract. + Set at deploy time. + pool_info: PoolInfo + Current state information of the hyperdrive contract. + Includes attributes like reserve levels and share prices. + base_amount: str (FixedPoint) + The amount of base used to open a long. + + Returns + ------- + str (FixedPoint) + The amount of bonds to remove from the pool reserves. + """ + return _get_interface(pool_config, pool_info).calculate_pool_deltas_after_open_long(base_amount) + + def calculate_close_long( pool_config: types.PoolConfigType, pool_info: types.PoolInfoType, @@ -657,3 +683,76 @@ def calculate_idle_share_reserves_in_base( The idle share reserves in base of the pool. """ return _get_interface(pool_config, pool_info).calculate_idle_share_reserves_in_base() + + +def calculate_add_liquidity( + pool_config: types.PoolConfigType, + pool_info: types.PoolInfoType, + contribution: str, + min_lp_share_price: str, + min_apr: str, + max_apr: str, + as_base: str, +) -> str: + """Calculates the lp_shares for a given contribution when adding liquidity. + + Arguments + --------- + pool_config: PoolConfig + Static configuration for the hyperdrive contract. + Set at deploy time. + pool_info: PoolInfo + Current state information of the hyperdrive contract. + Includes attributes like reserve levels and share prices. + contribution: str (FixedPoint) + The amount of liquidity, in base or shares, to add to the pool. + min_lp_share_price: str (FixedPoint) + The minimum allowable LP share price. + The call will return an error if this condition is not met. + min_apr: str (FixedPoint) + The minimum apr after contribution is added. + The call will return an error if this condition is not met. + max_apr: str (FixedPoint) + The maximum apr after contribution is added. + The call will return an error if this condition is not met. + as_base: str (bool) + The unit of currency for the contribution. + If true, then the contribution is in base. Otherwise, it is shares. + + Returns + ------- + str (FixedPoint) + The amount of LP shares provided by the pool for the given contribution. + """ + return _get_interface(pool_config, pool_info).calculate_add_liquidity( + contribution, min_lp_share_price, min_apr, max_apr, as_base.lower() + ) + + +def calculate_pool_deltas_after_add_liquidity( + pool_config: types.PoolConfigType, pool_info: types.PoolInfoType, contribution: str, as_base: str +) -> str: + """Calculate the deltas to be applied to the pool after adding liquidity. + + Arguments + --------- + pool_config: PoolConfig + Static configuration for the hyperdrive contract. + Set at deploy time. + pool_info: PoolInfo + Current state information of the hyperdrive contract. + Includes attributes like reserve levels and share prices. + contribution: str (FixedPoint) + The amount of liquidity, in base or shares, to add to the pool. + as_base: str (bool) + The unit of currency for the contribution. + If true, then the contribution is in base. Otherwise, it is shares. + + Returns + ------- + Tuple(str, str, str) (FixedPoint, FixedPoint, FixedPoint) + The deltas for share reserves, share adjustment, and bond reserves, respectively. + """ + return _get_interface(pool_config, pool_info).calculate_pool_deltas_after_add_liquidity( + contribution, as_base.lower() + ) diff --git a/bindings/hyperdrivepy/setup.py b/bindings/hyperdrivepy/setup.py index 5a19ee54..b67875f1 100644 --- a/bindings/hyperdrivepy/setup.py +++ b/bindings/hyperdrivepy/setup.py @@ -5,7 +5,7 @@ setup( name="hyperdrivepy", - version="0.15.4", + version="0.15.5", packages=["hyperdrivepy"], package_dir={"": "python"}, rust_extensions=[ diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods.rs index 86637899..62457be9 100644 --- a/bindings/hyperdrivepy/src/hyperdrive_state_methods.rs +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods.rs @@ -1,6 +1,10 @@ -use ethers::core::types::{I256, U256}; -use fixed_point::FixedPoint; -use hyperdrive_math::{State, YieldSpace}; +mod long; +mod lp; +mod short; +mod yield_space; + +use ethers::core::types::U256; +use hyperdrive_math::State; use pyo3::{exceptions::PyValueError, prelude::*}; use crate::HyperdriveState; @@ -16,425 +20,41 @@ impl HyperdriveState { Ok(HyperdriveState::new(state)) } - pub fn calculate_solvency(&self) -> PyResult { - let result_fp = self.state.calculate_solvency(); - let result = U256::from(result_fp).to_string(); + pub fn to_checkpoint(&self, time: &str) -> PyResult { + let time_int = U256::from_dec_str(time) + .map_err(|_| PyErr::new::("Failed to convert time string to U256"))?; + let result_int = self.state.to_checkpoint(time_int); + let result = result_int.to_string(); Ok(result) } - pub fn calculate_spot_price_after_long( - &self, - base_amount: &str, - maybe_bond_amount: Option<&str>, - ) -> PyResult { - let base_amount_fp = FixedPoint::from(U256::from_dec_str(base_amount).map_err(|_| { - PyErr::new::("Failed to convert base_amount string to U256") - })?); - let maybe_bond_amount_fp = if let Some(bond_amount) = maybe_bond_amount { - Some(FixedPoint::from(U256::from_dec_str(bond_amount).map_err( - |_| { - PyErr::new::( - "Failed to convert maybe_bond_amount string to U256", - ) - }, - )?)) - } else { - None - }; - let result_fp = self - .state - .calculate_spot_price_after_long(base_amount_fp, maybe_bond_amount_fp) - .unwrap(); + pub fn calculate_solvency(&self) -> PyResult { + let result_fp = self.state.calculate_solvency(); let result = U256::from(result_fp).to_string(); Ok(result) } - pub fn calculate_spot_price_after_short( - &self, - bond_amount: &str, - maybe_base_amount: Option<&str>, - ) -> PyResult { - let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { - PyErr::new::("Failed to convert bond_amount string to U256") - })?); - let maybe_base_amount_fp = if let Some(base_amount) = maybe_base_amount { - Some(FixedPoint::from(U256::from_dec_str(base_amount).map_err( - |_| { - PyErr::new::( - "Failed to convert maybe_base_amount string to U256", - ) - }, - )?)) - } else { - None - }; - let result_fp = self - .state - .calculate_spot_price_after_short(bond_amount_fp, maybe_base_amount_fp) - .map_err(|_| { - PyErr::new::("Failed to calculate spot price after short.") - })?; - Ok(U256::from(result_fp).to_string()) - } - pub fn calculate_spot_price(&self) -> PyResult { let result_fp = self.state.calculate_spot_price(); let result = U256::from(result_fp).to_string(); Ok(result) } - pub fn calculate_spot_rate_after_long( - &self, - base_amount: &str, - maybe_bond_amount: Option<&str>, - ) -> PyResult { - let base_amount_fp = FixedPoint::from(U256::from_dec_str(base_amount).map_err(|_| { - PyErr::new::("Failed to convert base_amount string to U256") - })?); - let maybe_bond_amount_fp = if let Some(bond_amount) = maybe_bond_amount { - Some(FixedPoint::from(U256::from_dec_str(bond_amount).map_err( - |_| { - PyErr::new::( - "Failed to convert maybe_bond_amount string to U256", - ) - }, - )?)) - } else { - None - }; - let result_fp = self - .state - .calculate_spot_rate_after_long(base_amount_fp, maybe_bond_amount_fp) - .unwrap(); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - pub fn calculate_spot_rate(&self) -> PyResult { let result_fp = self.state.calculate_spot_rate(); let result = U256::from(result_fp).to_string(); Ok(result) } - pub fn calculate_pool_deltas_after_add_liquidity( - &self, - contribution: &str, - ) -> PyResult<(String, String, String)> { - let contribution_fp = FixedPoint::from(U256::from_dec_str(contribution).map_err(|_| { - PyErr::new::("Failed to convert contribution string to U256") - })?); - let (result_fp1, result_fp2, result_fp3) = self - .state - .calculate_pool_deltas_after_add_liquidity(contribution_fp) - .map_err(|err| { - PyErr::new::(format!( - "calculate_pool_deltas_after_add_liquidity returned the error: {:?}", - err - )) - })?; - let result1 = U256::from(result_fp1).to_string(); - let result2 = result_fp2.to_string(); - let result3 = U256::from(result_fp3).to_string(); - Ok((result1, result2, result3)) - } - - pub fn calculate_open_long(&self, base_amount: &str) -> PyResult { - let base_amount_fp = FixedPoint::from(U256::from_dec_str(base_amount).map_err(|_| { - PyErr::new::("Failed to convert base_amount string to U256") - })?); - let result_fp = self.state.calculate_open_long(base_amount_fp).unwrap(); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_close_long( - &self, - bond_amount: &str, - maturity_time: &str, - current_time: &str, - ) -> PyResult { - let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { - PyErr::new::("Failed to convert bond_amount string to U256") - })?); - let maturity_time = U256::from_dec_str(maturity_time).map_err(|_| { - PyErr::new::("Failed to convert maturity_time string to U256") - })?; - let current_time = U256::from_dec_str(current_time).map_err(|_| { - PyErr::new::("Failed to convert current_time string to U256") - })?; - - let result_fp = - self.state - .calculate_close_long(bond_amount_fp, maturity_time, current_time); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_open_short( - &self, - bond_amount: &str, - open_vault_share_price: &str, - ) -> PyResult { - let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { - PyErr::new::("Failed to convert bond_amount string to U256") - })?); - let open_vault_share_price_fp = - FixedPoint::from(U256::from_dec_str(open_vault_share_price).map_err(|_| { - PyErr::new::( - "Failed to convert open_vault_share_price string to U256", - ) - })?); - let result_fp = self - .state - .calculate_open_short(bond_amount_fp, open_vault_share_price_fp) - .unwrap(); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_pool_deltas_after_open_short(&self, bond_amount: &str) -> PyResult { - let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { - PyErr::new::("Failed to convert bond_amount string to U256") - })?); - let result_fp = self - .state - .calculate_pool_deltas_after_open_short(bond_amount_fp) - .unwrap(); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_close_short( - &self, - bond_amount: &str, - open_vault_share_price: &str, - close_vault_share_price: &str, - maturity_time: &str, - current_time: &str, - ) -> PyResult { - let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { - PyErr::new::("Failed to convert bond_amount string to U256") - })?); - let open_vault_share_price_fp = - FixedPoint::from(U256::from_dec_str(open_vault_share_price).map_err(|_| { - PyErr::new::( - "Failed to convert open_vault_share_price string to U256", - ) - })?); - let close_vault_share_price_fp = - FixedPoint::from(U256::from_dec_str(close_vault_share_price).map_err(|_| { - PyErr::new::( - "Failed to convert close_vault_share_price string to U256", - ) - })?); - let maturity_time = U256::from_dec_str(maturity_time).map_err(|_| { - PyErr::new::("Failed to convert maturity_time string to U256") - })?; - let current_time = U256::from_dec_str(current_time).map_err(|_| { - PyErr::new::("Failed to convert current_time string to U256") - })?; - let result_fp = self.state.calculate_close_short( - bond_amount_fp, - open_vault_share_price_fp, - close_vault_share_price_fp, - maturity_time, - current_time, - ); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - pub fn calculate_max_spot_price(&self) -> PyResult { let result_fp = self.state.calculate_max_spot_price(); let result = U256::from(result_fp).to_string(); Ok(result) } - pub fn calculate_targeted_long_with_budget( - &self, - budget: &str, - target_rate: &str, - checkpoint_exposure: &str, - maybe_max_iterations: Option, - maybe_allowable_error: Option<&str>, - ) -> PyResult { - let budget_fp = FixedPoint::from(U256::from_dec_str(budget).map_err(|_| { - PyErr::new::("Failed to convert budget string to U256") - })?); - let target_rate_fp = FixedPoint::from(U256::from_dec_str(target_rate).map_err(|_| { - PyErr::new::("Failed to convert target_rate string to U256") - })?); - let checkpoint_exposure_i = I256::from_dec_str(checkpoint_exposure).map_err(|_| { - PyErr::new::("Failed to convert checkpoint_exposure string to I256") - })?; - let maybe_allowable_error_fp = if let Some(allowable_error) = maybe_allowable_error { - Some(FixedPoint::from( - U256::from_dec_str(allowable_error).map_err(|_| { - PyErr::new::( - "Failed to convert maybe_allowable_error string to U256", - ) - })?, - )) - } else { - None - }; - let result_fp = self - .state - .calculate_targeted_long_with_budget( - budget_fp, - target_rate_fp, - checkpoint_exposure_i, - maybe_max_iterations, - maybe_allowable_error_fp, - ) - .map_err(|err| { - PyErr::new::(format!( - "Calculate_targeted_long_with_budget returned the error: {:?}", - err - )) - })?; - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_max_long( - &self, - budget: &str, - checkpoint_exposure: &str, - maybe_max_iterations: Option, - ) -> PyResult { - let budget_fp = FixedPoint::from(U256::from_dec_str(budget).map_err(|_| { - PyErr::new::("Failed to convert budget string to U256") - })?); - let checkpoint_exposure_i = I256::from_dec_str(checkpoint_exposure).map_err(|_| { - PyErr::new::("Failed to convert checkpoint_exposure string to I256") - })?; - let result_fp = self - .state - .calculate_max_long(budget_fp, checkpoint_exposure_i, maybe_max_iterations) - .map_err(|e| { - PyErr::new::(format!("Failed to calculate max long: {}", e)) - })?; - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_max_short( - &self, - budget: &str, - open_vault_share_price: &str, - checkpoint_exposure: &str, - maybe_conservative_price: Option<&str>, - maybe_max_iterations: Option, - ) -> PyResult { - let budget_fp = FixedPoint::from(U256::from_dec_str(budget).map_err(|_| { - PyErr::new::("Failed to convert budget string to U256") - })?); - let open_vault_share_price_fp = - FixedPoint::from(U256::from_dec_str(open_vault_share_price).map_err(|_| { - PyErr::new::( - "Failed to convert open_vault_share_price string to U256", - ) - })?); - let checkpoint_exposure_i = I256::from_dec_str(checkpoint_exposure).map_err(|_| { - PyErr::new::("Failed to convert checkpoint_exposure string to I256") - })?; - let maybe_conservative_price_fp = if let Some(conservative_price) = maybe_conservative_price - { - Some(FixedPoint::from( - U256::from_dec_str(conservative_price).map_err(|_| { - PyErr::new::( - "Failed to convert maybe_conservative_price string to U256", - ) - })?, - )) - } else { - None - }; - let result_fp = self.state.calculate_max_short( - budget_fp, - open_vault_share_price_fp, - checkpoint_exposure_i, - maybe_conservative_price_fp, - maybe_max_iterations, - ); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_present_value(&self, current_block_timestamp: &str) -> PyResult { - let current_block_timestamp_int = - U256::from_dec_str(current_block_timestamp).map_err(|_| { - PyErr::new::( - "Failed to convert current_block_timestamp string to U256", - ) - })?; - match self - .state - .calculate_present_value(current_block_timestamp_int) - { - Ok(result) => Ok(U256::from(result).to_string()), - Err(err) => Err(PyErr::new::(format!("{:?}", err))), - } - } - pub fn calculate_idle_share_reserves_in_base(&self) -> PyResult { let result_fp = self.state.calculate_idle_share_reserves_in_base(); let result = U256::from(result_fp).to_string(); Ok(result) } - - pub fn calculate_bonds_out_given_shares_in_down(&self, amount_in: &str) -> PyResult { - let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { - PyErr::new::("Failed to convert amount_in string to U256") - })?); - let result_fp = self - .state - .calculate_bonds_out_given_shares_in_down(amount_in_fp); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_shares_in_given_bonds_out_up(&self, amount_in: &str) -> PyResult { - let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { - PyErr::new::("Failed to convert amount_in string to U256") - })?); - // We unwrap the error here to throw panic error if this fails - let result_fp = self - .state - .calculate_shares_in_given_bonds_out_up_safe(amount_in_fp) - .unwrap(); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_shares_in_given_bonds_out_down(&self, amount_in: &str) -> PyResult { - let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { - PyErr::new::("Failed to convert amount_in string to U256") - })?); - let result_fp = self - .state - .calculate_shares_in_given_bonds_out_down(amount_in_fp); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn calculate_shares_out_given_bonds_in_down(&self, amount_in: &str) -> PyResult { - let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { - PyErr::new::("Failed to convert amount_in string to U256") - })?); - let result_fp = self - .state - .calculate_shares_out_given_bonds_in_down(amount_in_fp); - let result = U256::from(result_fp).to_string(); - Ok(result) - } - - pub fn to_checkpoint(&self, time: &str) -> PyResult { - let time_int = U256::from_dec_str(time) - .map_err(|_| PyErr::new::("Failed to convert time string to U256"))?; - let result_int = self.state.to_checkpoint(time_int); - let result = result_int.to_string(); - Ok(result) - } } diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/long.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long.rs new file mode 100644 index 00000000..880e8f7b --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long.rs @@ -0,0 +1,4 @@ +mod close; +mod max; +mod open; +mod targeted; diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/close.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/close.rs new file mode 100644 index 00000000..cfa0c9b7 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/close.rs @@ -0,0 +1,31 @@ +use ethers::core::types::U256; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_close_long( + &self, + bond_amount: &str, + maturity_time: &str, + current_time: &str, + ) -> PyResult { + let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { + PyErr::new::("Failed to convert bond_amount string to U256") + })?); + let maturity_time = U256::from_dec_str(maturity_time).map_err(|_| { + PyErr::new::("Failed to convert maturity_time string to U256") + })?; + let current_time = U256::from_dec_str(current_time).map_err(|_| { + PyErr::new::("Failed to convert current_time string to U256") + })?; + + let result_fp = + self.state + .calculate_close_long(bond_amount_fp, maturity_time, current_time); + let result = U256::from(result_fp).to_string(); + Ok(result) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/max.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/max.rs new file mode 100644 index 00000000..5b523372 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/max.rs @@ -0,0 +1,30 @@ +use ethers::core::types::{I256, U256}; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_max_long( + &self, + budget: &str, + checkpoint_exposure: &str, + maybe_max_iterations: Option, + ) -> PyResult { + let budget_fp = FixedPoint::from(U256::from_dec_str(budget).map_err(|_| { + PyErr::new::("Failed to convert budget string to U256") + })?); + let checkpoint_exposure_i = I256::from_dec_str(checkpoint_exposure).map_err(|_| { + PyErr::new::("Failed to convert checkpoint_exposure string to I256") + })?; + let result_fp = self + .state + .calculate_max_long(budget_fp, checkpoint_exposure_i, maybe_max_iterations) + .map_err(|e| { + PyErr::new::(format!("Failed to calculate max long: {}", e)) + })?; + let result = U256::from(result_fp).to_string(); + Ok(result) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/open.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/open.rs new file mode 100644 index 00000000..9b5d0549 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/open.rs @@ -0,0 +1,88 @@ +use ethers::core::types::U256; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_open_long(&self, base_amount: &str) -> PyResult { + let base_amount_fp = FixedPoint::from(U256::from_dec_str(base_amount).map_err(|_| { + PyErr::new::("Failed to convert base_amount string to U256") + })?); + let result_fp = self.state.calculate_open_long(base_amount_fp).unwrap(); + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_pool_deltas_after_open_long(&self, base_amount: &str) -> PyResult { + let base_amount_fp = FixedPoint::from(U256::from_dec_str(base_amount).map_err(|_| { + PyErr::new::("Failed to convert base_amount string to U256") + })?); + let result_fp = self + .state + .calculate_pool_deltas_after_open_long(base_amount_fp) + .map_err(|err| { + PyErr::new::(format!( + "calculate_pool_deltas_after_open_long returned the error: {:?}", + err + )) + })?; + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_spot_price_after_long( + &self, + base_amount: &str, + maybe_bond_amount: Option<&str>, + ) -> PyResult { + let base_amount_fp = FixedPoint::from(U256::from_dec_str(base_amount).map_err(|_| { + PyErr::new::("Failed to convert base_amount string to U256") + })?); + let maybe_bond_amount_fp = if let Some(bond_amount) = maybe_bond_amount { + Some(FixedPoint::from(U256::from_dec_str(bond_amount).map_err( + |_| { + PyErr::new::( + "Failed to convert maybe_bond_amount string to U256", + ) + }, + )?)) + } else { + None + }; + let result_fp = self + .state + .calculate_spot_price_after_long(base_amount_fp, maybe_bond_amount_fp) + .unwrap(); + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_spot_rate_after_long( + &self, + base_amount: &str, + maybe_bond_amount: Option<&str>, + ) -> PyResult { + let base_amount_fp = FixedPoint::from(U256::from_dec_str(base_amount).map_err(|_| { + PyErr::new::("Failed to convert base_amount string to U256") + })?); + let maybe_bond_amount_fp = if let Some(bond_amount) = maybe_bond_amount { + Some(FixedPoint::from(U256::from_dec_str(bond_amount).map_err( + |_| { + PyErr::new::( + "Failed to convert maybe_bond_amount string to U256", + ) + }, + )?)) + } else { + None + }; + let result_fp = self + .state + .calculate_spot_rate_after_long(base_amount_fp, maybe_bond_amount_fp) + .unwrap(); + let result = U256::from(result_fp).to_string(); + Ok(result) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/targeted.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/targeted.rs new file mode 100644 index 00000000..556ba1e5 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/long/targeted.rs @@ -0,0 +1,55 @@ +use ethers::core::types::{I256, U256}; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_targeted_long_with_budget( + &self, + budget: &str, + target_rate: &str, + checkpoint_exposure: &str, + maybe_max_iterations: Option, + maybe_allowable_error: Option<&str>, + ) -> PyResult { + let budget_fp = FixedPoint::from(U256::from_dec_str(budget).map_err(|_| { + PyErr::new::("Failed to convert budget string to U256") + })?); + let target_rate_fp = FixedPoint::from(U256::from_dec_str(target_rate).map_err(|_| { + PyErr::new::("Failed to convert target_rate string to U256") + })?); + let checkpoint_exposure_i = I256::from_dec_str(checkpoint_exposure).map_err(|_| { + PyErr::new::("Failed to convert checkpoint_exposure string to I256") + })?; + let maybe_allowable_error_fp = if let Some(allowable_error) = maybe_allowable_error { + Some(FixedPoint::from( + U256::from_dec_str(allowable_error).map_err(|_| { + PyErr::new::( + "Failed to convert maybe_allowable_error string to U256", + ) + })?, + )) + } else { + None + }; + let result_fp = self + .state + .calculate_targeted_long_with_budget( + budget_fp, + target_rate_fp, + checkpoint_exposure_i, + maybe_max_iterations, + maybe_allowable_error_fp, + ) + .map_err(|err| { + PyErr::new::(format!( + "Calculate_targeted_long_with_budget returned the error: {:?}", + err + )) + })?; + let result = U256::from(result_fp).to_string(); + Ok(result) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp.rs new file mode 100644 index 00000000..1f7383a2 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp.rs @@ -0,0 +1,2 @@ +mod add; +mod math; diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp/add.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp/add.rs new file mode 100644 index 00000000..df38d141 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp/add.rs @@ -0,0 +1,102 @@ +use ethers::core::types::U256; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_add_liquidity( + &self, + current_block_timestamp: &str, + contribution: &str, + min_lp_share_price: &str, + min_apr: &str, + max_apr: &str, + as_base: &str, + ) -> PyResult { + let current_block_timestamp = + U256::from_dec_str(current_block_timestamp).map_err(|err| { + PyErr::new::(format!( + "Failed to convert current_block_timestamp string to U256: {}", + err + )) + })?; + let contribution_fp = + FixedPoint::from(U256::from_dec_str(contribution).map_err(|err| { + PyErr::new::(format!( + "Failed to convert contribution string to U256: {}", + err + )) + })?); + let min_lp_share_price_fp = + FixedPoint::from(U256::from_dec_str(min_lp_share_price).map_err(|err| { + PyErr::new::(format!( + "Failed to convert min_lp_share_price string to U256: {}", + err + )) + })?); + let min_apr_fp = FixedPoint::from(U256::from_dec_str(min_apr).map_err(|err| { + PyErr::new::(format!( + "Failed to convert min_apr string to U256: {}", + err + )) + })?); + let max_apr_fp = FixedPoint::from(U256::from_dec_str(max_apr).map_err(|err| { + PyErr::new::(format!( + "Failed to convert max_apr string to U256: {}", + err + )) + })?); + let as_base = as_base.parse::().map_err(|err| { + PyErr::new::(format!( + "Failed to convert as_base string to bool: {}", + err + )) + })?; + let result_fp = self + .state + .calculate_add_liquidity( + current_block_timestamp, + contribution_fp, + min_lp_share_price_fp, + min_apr_fp, + max_apr_fp, + as_base, + ) + .map_err(|err| { + PyErr::new::(format!( + "Failed to run calculate_add_liquidity: {}", + err + )) + })?; + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_pool_deltas_after_add_liquidity( + &self, + contribution: &str, + as_base: &str, + ) -> PyResult<(String, String, String)> { + let contribution_fp = FixedPoint::from(U256::from_dec_str(contribution).map_err(|_| { + PyErr::new::("Failed to convert contribution string to U256") + })?); + let as_base = as_base.parse::().map_err(|_| { + PyErr::new::("Failed to convert as_base string to bool") + })?; + let (result_fp1, result_i256, result_fp2) = self + .state + .calculate_pool_deltas_after_add_liquidity(contribution_fp, as_base) + .map_err(|err| { + PyErr::new::(format!( + "calculate_pool_deltas_after_add_liquidity returned the error: {:?}", + err + )) + })?; + let result1 = U256::from(result_fp1).to_string(); + let result2 = result_i256.to_string(); + let result3 = U256::from(result_fp2).to_string(); + Ok((result1, result2, result3)) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp/math.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp/math.rs new file mode 100644 index 00000000..7ad16f20 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/lp/math.rs @@ -0,0 +1,23 @@ +use ethers::core::types::U256; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_present_value(&self, current_block_timestamp: &str) -> PyResult { + let current_block_timestamp_int = + U256::from_dec_str(current_block_timestamp).map_err(|_| { + PyErr::new::( + "Failed to convert current_block_timestamp string to U256", + ) + })?; + match self + .state + .calculate_present_value(current_block_timestamp_int) + { + Ok(result) => Ok(U256::from(result).to_string()), + Err(err) => Err(PyErr::new::(format!("{:?}", err))), + } + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/short.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short.rs new file mode 100644 index 00000000..6c64efd4 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short.rs @@ -0,0 +1,3 @@ +mod close; +mod max; +mod open; diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/close.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/close.rs new file mode 100644 index 00000000..2a4873e9 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/close.rs @@ -0,0 +1,48 @@ +use ethers::core::types::U256; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_close_short( + &self, + bond_amount: &str, + open_vault_share_price: &str, + close_vault_share_price: &str, + maturity_time: &str, + current_time: &str, + ) -> PyResult { + let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { + PyErr::new::("Failed to convert bond_amount string to U256") + })?); + let open_vault_share_price_fp = + FixedPoint::from(U256::from_dec_str(open_vault_share_price).map_err(|_| { + PyErr::new::( + "Failed to convert open_vault_share_price string to U256", + ) + })?); + let close_vault_share_price_fp = + FixedPoint::from(U256::from_dec_str(close_vault_share_price).map_err(|_| { + PyErr::new::( + "Failed to convert close_vault_share_price string to U256", + ) + })?); + let maturity_time = U256::from_dec_str(maturity_time).map_err(|_| { + PyErr::new::("Failed to convert maturity_time string to U256") + })?; + let current_time = U256::from_dec_str(current_time).map_err(|_| { + PyErr::new::("Failed to convert current_time string to U256") + })?; + let result_fp = self.state.calculate_close_short( + bond_amount_fp, + open_vault_share_price_fp, + close_vault_share_price_fp, + maturity_time, + current_time, + ); + let result = U256::from(result_fp).to_string(); + Ok(result) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/max.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/max.rs new file mode 100644 index 00000000..654bb3ba --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/max.rs @@ -0,0 +1,51 @@ +use ethers::core::types::{I256, U256}; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_max_short( + &self, + budget: &str, + open_vault_share_price: &str, + checkpoint_exposure: &str, + maybe_conservative_price: Option<&str>, + maybe_max_iterations: Option, + ) -> PyResult { + let budget_fp = FixedPoint::from(U256::from_dec_str(budget).map_err(|_| { + PyErr::new::("Failed to convert budget string to U256") + })?); + let open_vault_share_price_fp = + FixedPoint::from(U256::from_dec_str(open_vault_share_price).map_err(|_| { + PyErr::new::( + "Failed to convert open_vault_share_price string to U256", + ) + })?); + let checkpoint_exposure_i = I256::from_dec_str(checkpoint_exposure).map_err(|_| { + PyErr::new::("Failed to convert checkpoint_exposure string to I256") + })?; + let maybe_conservative_price_fp = if let Some(conservative_price) = maybe_conservative_price + { + Some(FixedPoint::from( + U256::from_dec_str(conservative_price).map_err(|_| { + PyErr::new::( + "Failed to convert maybe_conservative_price string to U256", + ) + })?, + )) + } else { + None + }; + let result_fp = self.state.calculate_max_short( + budget_fp, + open_vault_share_price_fp, + checkpoint_exposure_i, + maybe_conservative_price_fp, + maybe_max_iterations, + ); + let result = U256::from(result_fp).to_string(); + Ok(result) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/open.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/open.rs new file mode 100644 index 00000000..8df5b34b --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/short/open.rs @@ -0,0 +1,70 @@ +use ethers::core::types::U256; +use fixed_point::FixedPoint; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_open_short( + &self, + bond_amount: &str, + open_vault_share_price: &str, + ) -> PyResult { + let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { + PyErr::new::("Failed to convert bond_amount string to U256") + })?); + let open_vault_share_price_fp = + FixedPoint::from(U256::from_dec_str(open_vault_share_price).map_err(|_| { + PyErr::new::( + "Failed to convert open_vault_share_price string to U256", + ) + })?); + let result_fp = self + .state + .calculate_open_short(bond_amount_fp, open_vault_share_price_fp) + .unwrap(); + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_pool_deltas_after_open_short(&self, bond_amount: &str) -> PyResult { + let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { + PyErr::new::("Failed to convert bond_amount string to U256") + })?); + let result_fp = self + .state + .calculate_pool_deltas_after_open_short(bond_amount_fp) + .unwrap(); + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_spot_price_after_short( + &self, + bond_amount: &str, + maybe_base_amount: Option<&str>, + ) -> PyResult { + let bond_amount_fp = FixedPoint::from(U256::from_dec_str(bond_amount).map_err(|_| { + PyErr::new::("Failed to convert bond_amount string to U256") + })?); + let maybe_base_amount_fp = if let Some(base_amount) = maybe_base_amount { + Some(FixedPoint::from(U256::from_dec_str(base_amount).map_err( + |_| { + PyErr::new::( + "Failed to convert maybe_base_amount string to U256", + ) + }, + )?)) + } else { + None + }; + let result_fp = self + .state + .calculate_spot_price_after_short(bond_amount_fp, maybe_base_amount_fp) + .map_err(|_| { + PyErr::new::("Failed to calculate spot price after short.") + })?; + Ok(U256::from(result_fp).to_string()) + } +} diff --git a/bindings/hyperdrivepy/src/hyperdrive_state_methods/yield_space.rs b/bindings/hyperdrivepy/src/hyperdrive_state_methods/yield_space.rs new file mode 100644 index 00000000..6cf5ef25 --- /dev/null +++ b/bindings/hyperdrivepy/src/hyperdrive_state_methods/yield_space.rs @@ -0,0 +1,55 @@ +use ethers::core::types::U256; +use fixed_point::FixedPoint; +use hyperdrive_math::YieldSpace; +use pyo3::{exceptions::PyValueError, prelude::*}; + +use crate::HyperdriveState; + +#[pymethods] +impl HyperdriveState { + pub fn calculate_bonds_out_given_shares_in_down(&self, amount_in: &str) -> PyResult { + let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { + PyErr::new::("Failed to convert amount_in string to U256") + })?); + let result_fp = self + .state + .calculate_bonds_out_given_shares_in_down(amount_in_fp); + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_shares_in_given_bonds_out_up(&self, amount_in: &str) -> PyResult { + let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { + PyErr::new::("Failed to convert amount_in string to U256") + })?); + // We unwrap the error here to throw panic error if this fails + let result_fp = self + .state + .calculate_shares_in_given_bonds_out_up_safe(amount_in_fp) + .unwrap(); + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_shares_in_given_bonds_out_down(&self, amount_in: &str) -> PyResult { + let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { + PyErr::new::("Failed to convert amount_in string to U256") + })?); + let result_fp = self + .state + .calculate_shares_in_given_bonds_out_down(amount_in_fp); + let result = U256::from(result_fp).to_string(); + Ok(result) + } + + pub fn calculate_shares_out_given_bonds_in_down(&self, amount_in: &str) -> PyResult { + let amount_in_fp = FixedPoint::from(U256::from_dec_str(amount_in).map_err(|_| { + PyErr::new::("Failed to convert amount_in string to U256") + })?); + let result_fp = self + .state + .calculate_shares_out_given_bonds_in_down(amount_in_fp); + let result = U256::from(result_fp).to_string(); + Ok(result) + } +} diff --git a/crates/hyperdrive-math/src/long/max.rs b/crates/hyperdrive-math/src/long/max.rs index 1e85a93c..9e89d127 100644 --- a/crates/hyperdrive-math/src/long/max.rs +++ b/crates/hyperdrive-math/src/long/max.rs @@ -561,7 +561,6 @@ mod tests { #[tokio::test] async fn fuzz_calculate_max_long() -> Result<()> { let chain = TestChain::new().await?; - let alice = chain.alice().await?; // Fuzz the rust and solidity implementations against each other. let mut rng = thread_rng(); @@ -570,7 +569,7 @@ mod tests { let id = chain.snapshot().await?; // Gen a random state. - let mut state = rng.gen::(); + let state = rng.gen::(); // Generate a random checkpoint exposure. let checkpoint_exposure = { diff --git a/crates/hyperdrive-math/src/lp.rs b/crates/hyperdrive-math/src/lp.rs index 9808a5ae..aa81589f 100644 --- a/crates/hyperdrive-math/src/lp.rs +++ b/crates/hyperdrive-math/src/lp.rs @@ -140,7 +140,12 @@ impl State { pub fn calculate_pool_deltas_after_add_liquidity( &self, contribution: FixedPoint, + as_base: bool, ) -> Result<(FixedPoint, I256, FixedPoint)> { + let share_contribution = match as_base { + true => contribution / self.vault_share_price(), + false => contribution, + }; let (share_reserves, share_adjustment, bond_reserves) = self.calculate_update_liquidity( self.share_reserves(), self.share_adjustment(), @@ -154,7 +159,7 @@ impl State { self.share_adjustment(), self.bond_reserves(), self.minimum_share_reserves(), - I256::try_from(contribution)?, + I256::try_from(share_contribution)?, )?; Ok(( new_share_reserves - share_reserves, diff --git a/crates/hyperdrive-wrappers/build.rs b/crates/hyperdrive-wrappers/build.rs index 4872432b..1d2f2183 100644 --- a/crates/hyperdrive-wrappers/build.rs +++ b/crates/hyperdrive-wrappers/build.rs @@ -351,7 +351,7 @@ fn checkout_branch(git_ref: &str, repo_path: &Path) -> Result<()> { status = Command::new("git") .current_dir(repo_path) - .args(["pull", "origin", &git_ref]) + .args(["pull", "origin", git_ref]) .status()?; if !status.success() { diff --git a/pyproject.toml b/pyproject.toml index 6cf2610b..3bcd2a44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hyperdrivepy" -version = "0.15.4" +version = "0.15.5" authors = [ { name = "Dylan Paiton", email = "dylan@delv.tech" }, { name = "Matthew Brown", email = "matt@delv.tech" },