Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Simple pyO3 wrapper for hyperdrive_math #553

Merged
merged 12 commits into from
Aug 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ forge-cache/
.vscode/
broadcast
.env
.venv

node_modules/
yarn-error.log
Expand Down
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.3.0
branch = v1.6.0
[submodule "lib/aave-v3-core"]
path = lib/aave-v3-core
url = https://github.com/aave/aave-v3-core
Expand Down
93 changes: 93 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/fixed-point-macros",
"crates/hyperdrive-addresses",
"crates/hyperdrive-math",
"crates/hyperdrive-math-py",
"crates/hyperdrive-wrappers",
"crates/test-utils",
"test/rust",
Expand Down
28 changes: 28 additions & 0 deletions crates/hyperdrive-math-py/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[package]

name = "hyperdrive-math-py"
version = "0.1.0"
edition = "2021"

[lib]

name = "hyperdrive_math"
# "cdylib" is necessary to produce a shared library for Python to import from.
crate-type = ["cdylib"]

[dependencies]

ethers = "2.0.8"
eyre = "0.6.8"
rand = "0.8.5"
tokio = { version = "1", features = ["full"] }

hyperdrive-math = { version = "0.1.0", path = "../hyperdrive-math" }
fixed-point = { version = "0.1.0", path = "../fixed-point" }
fixed-point-macros = { version = "0.1.0", path = "../fixed-point-macros" }
hyperdrive-wrappers = { version = "0.1.0", path = "../hyperdrive-wrappers" }

[dependencies.pyo3]
version = "0.19.0"
# "abi3-py37" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.7
features = ["abi3-py37"]
16 changes: 16 additions & 0 deletions crates/hyperdrive-math-py/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[build-system]
requires = ["maturin>=1.2,<2.0"]
build-backend = "maturin"

[project]
name = "hyperdrive_math_lib"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]


[tool.maturin]
features = ["pyo3/extension-module"]
161 changes: 161 additions & 0 deletions crates/hyperdrive-math-py/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use ethers::core::types::{Address, U256};
use hyperdrive_wrappers::wrappers::i_hyperdrive::{Fees, PoolConfig, PoolInfo};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::PyErr;

use hyperdrive_math::hyperdrive_math::State;
sentilesdal marked this conversation as resolved.
Show resolved Hide resolved

#[pyclass(name = "HyperdriveState")]
pub struct PyState {
pub state: State,
}

impl PyState {
pub(crate) fn new(state: State) -> Self {
PyState { state }
}
}

impl From<State> for PyState {
fn from(state: State) -> Self {
PyState { state }
}
}
pub struct PyPoolConfig {
pub pool_config: PoolConfig,
}

// Helper function to extract U256 values from Python object attributes
fn extract_u256_from_attr(ob: &PyAny, attr: &str) -> PyResult<U256> {
let value_str: String = ob.getattr(attr)?.extract()?;
U256::from_dec_str(&value_str)
.map_err(|e| PyErr::new::<PyValueError, _>(format!("Invalid U256 for {}: {}", attr, e)))
}

// Helper function to extract Ethereum Address values from Python object attributes
fn extract_address_from_attr(ob: &PyAny, attr: &str) -> PyResult<Address> {
let address_str: String = ob.getattr(attr)?.extract()?;
address_str.parse::<Address>().map_err(|e| {
PyErr::new::<PyValueError, _>(format!("Invalid Ethereum address for {}: {}", attr, e))
})
}

fn extract_fees_from_attr(ob: &PyAny, attr: &str) -> PyResult<Fees> {
let fees_obj = ob.getattr(attr)?;

let curve = extract_u256_from_attr(&fees_obj, "curve")?;
let flat = extract_u256_from_attr(&fees_obj, "flat")?;
let governance = extract_u256_from_attr(&fees_obj, "governance")?;

Ok(Fees {
curve,
flat,
governance,
})
}

impl FromPyObject<'_> for PyPoolConfig {
fn extract(ob: &PyAny) -> PyResult<Self> {
let base_token = extract_address_from_attr(ob, "base_token")?;
let initial_share_price = extract_u256_from_attr(ob, "initial_share_price")?;
let minimum_share_reserves = extract_u256_from_attr(ob, "minimum_share_reserves")?;
let position_duration = extract_u256_from_attr(ob, "position_duration")?;
let checkpoint_duration = extract_u256_from_attr(ob, "checkpoint_duration")?;
let time_stretch = extract_u256_from_attr(ob, "time_stretch")?;
let governance = extract_address_from_attr(ob, "governance")?;
let fees = extract_fees_from_attr(ob, "fees")?;
let fee_collector = extract_address_from_attr(ob, "fee_collector")?;
let oracle_size = extract_u256_from_attr(ob, "oracle_size")?;
let update_gap = extract_u256_from_attr(ob, "update_gap")?;

return Ok(PyPoolConfig {
pool_config: PoolConfig {
base_token,
initial_share_price,
minimum_share_reserves,
position_duration,
checkpoint_duration,
time_stretch,
governance,
fees,
fee_collector,
oracle_size,
update_gap,
},
});
}
}

pub struct PyPoolInfo {
pub pool_info: PoolInfo,
}

impl PyPoolInfo {
pub(crate) fn new(pool_info: PoolInfo) -> Self {
PyPoolInfo { pool_info }
}
}

impl FromPyObject<'_> for PyPoolInfo {
fn extract(ob: &PyAny) -> PyResult<Self> {
let share_reserves = extract_u256_from_attr(ob, "share_reserves")?;
let bond_reserves = extract_u256_from_attr(ob, "bond_reserves")?;
let lp_total_supply = extract_u256_from_attr(ob, "lp_total_supply")?;
let share_price = extract_u256_from_attr(ob, "share_price")?;
let longs_outstanding = extract_u256_from_attr(ob, "longs_outstanding")?;
let long_average_maturity_time = extract_u256_from_attr(ob, "long_average_maturity_time")?;
let shorts_outstanding = extract_u256_from_attr(ob, "shorts_outstanding")?;
let short_average_maturity_time =
extract_u256_from_attr(ob, "short_average_maturity_time")?;
let short_base_volume = extract_u256_from_attr(ob, "short_base_volume")?;
let withdrawal_shares_ready_to_withdraw =
extract_u256_from_attr(ob, "withdrawal_shares_ready_to_withdraw")?;
let withdrawal_shares_proceeds = extract_u256_from_attr(ob, "withdrawal_shares_proceeds")?;
let lp_share_price = extract_u256_from_attr(ob, "lp_share_price")?;

let pool_info = PoolInfo {
share_reserves,
bond_reserves,
lp_total_supply,
share_price,
longs_outstanding,
long_average_maturity_time,
shorts_outstanding,
short_average_maturity_time,
short_base_volume,
withdrawal_shares_ready_to_withdraw,
withdrawal_shares_proceeds,
lp_share_price,
};

Ok(PyPoolInfo::new(pool_info))
}
}

#[pymethods]
impl PyState {
#[new]
pub fn __init__(pool_config: &PyAny, pool_info: &PyAny) -> PyResult<Self> {
let rust_pool_config = PyPoolConfig::extract(pool_config)?.pool_config;
let rust_pool_info = PyPoolInfo::extract(pool_info)?.pool_info;
let hyperdrive_state = State::new(rust_pool_config, rust_pool_info);
Ok(PyState::new(hyperdrive_state))
}

pub fn get_spot_price(&self) -> PyResult<String> {
sentilesdal marked this conversation as resolved.
Show resolved Hide resolved
let result_fp = self.state.get_spot_price();
let result = U256::from(result_fp).to_string();
return Ok(result);
}
}

/// A pyO3 wrapper for the hyperdrie_math crate. The State struct will be exposed with all methods
/// listed in #[pymethods] decorated impl. The name of this function must match the `lib.name`
/// setting in the `Cargo.toml`, else Python will not be able to import the module.
#[pymodule]
#[pyo3(name = "hyperdrive_math")]
fn hyperdrive_math_lib(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PyState>().unwrap();
Ok(())
}
14 changes: 11 additions & 3 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@ test = 'test'
# Whether to cache builds or not
cache = true
# The cache directory if enabled
cache_path = 'forge-cache'
cache_path = 'forge-cache'
# Enables or disables the optimizer
remappings = ['forge-std=lib/forge-std/src', 'openzeppelin-contracts/=lib/openzeppelin-contracts/', 'solmate=lib/solmate/src', '@aave/=lib/aave-v3-core/contracts']
remappings = [
'forge-std=lib/forge-std/src',
'openzeppelin-contracts/=lib/openzeppelin-contracts/',
'solmate=lib/solmate/src',
'@aave/=lib/aave-v3-core/contracts',
]
# gas limit - max u64
gas_limit = "18446744073709551615"
# Allows the scripts to write the addresses to a file.
fs_permissions = [{ access = "write", path = "./artifacts/script_addresses.json" }, { access = "write", path = "./test/Generated.t.sol" }]
fs_permissions = [
{ access = "write", path = "./artifacts/script_addresses.json" },
{ access = "write", path = "./test/Generated.t.sol" },
]
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

[profile.production]
Expand Down
2 changes: 1 addition & 1 deletion lib/forge-std
Loading
Loading