Skip to content

Commit

Permalink
Add error handling and conversion utils to hyperwasm (#1154)
Browse files Browse the repository at this point in the history
* Improve error handling

* Fix location tracking

* Finish utils

* Update type conversion

* Finish error management

* Rebuild

* typo

* Fix error name

* Change error name

* Rebuild

* Add changeset

* Decrease scope of `ToAddress` implementations

* Fix i256 parsing

* Remove console logs from build
  • Loading branch information
ryangoree authored Jun 7, 2024
1 parent 6682e87 commit a9aedb1
Show file tree
Hide file tree
Showing 19 changed files with 1,243 additions and 779 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-windows-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@delvtech/hyperdrive-wasm": patch
---

Added error handling and conversion utils
3 changes: 2 additions & 1 deletion Cargo.lock

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

3 changes: 2 additions & 1 deletion crates/hyperdrive-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "hyperdrive-wasm"
# This version will be overwritten by the build script to match the version in
# the crate's package.json which is managed by changesets.
version = "0.14.1"
version = "0.14.3"
edition = "2021"
authors = ["Ryan Goree <ryan@delv.tech>", "Danny Delott <dannyd@delv.tech>"]
license = "AGPL-3.0"
Expand All @@ -20,6 +20,7 @@ js-sys = "0.3.64"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-wasm-bindgen = "0.6.0"
thiserror = "1.0"
wasm-bindgen = { version = "0.2.86", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4.36"
web-sys = { version = "0.3.63", features = ["console"] }
Expand Down
115 changes: 115 additions & 0 deletions crates/hyperdrive-wasm/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::panic::Location;
use thiserror::Error;
use wasm_bindgen::JsValue;

/// A custom error type for the HyperdriveWasm library that can be converted to
/// a JsValue for passing to JavaScript.
#[derive(Error, Debug)]
pub enum HyperdriveWasmError {
#[error("TypeError: {0}\n Location: {1}")]
TypeError(String, String),
#[error("Error: {0}\n Location: {1}")]
Generic(String, String),
}

// Conversions //

// Convert a HyperdriveWasmError to a JsValue via `.into()` or `::from()`
impl From<HyperdriveWasmError> for JsValue {
fn from(error: HyperdriveWasmError) -> JsValue {
error.to_string().into()
}
}

/// Convert a value to a `HyperdriveWasmError` via `.to_error()`
pub trait ToHyperdriveWasmError {
/// Convert a value to a `HyperdriveWasmError`, capturing the current location
#[track_caller]
fn to_error(&self) -> HyperdriveWasmError {
self.to_error_at(&Location::caller().to_string())
}

fn to_error_at(&self, location: &str) -> HyperdriveWasmError;
}

// If a value can `.to_string()`, it can `.to_error()`
impl<T> ToHyperdriveWasmError for T
where
T: ToString,
{
fn to_error_at(&self, location: &str) -> HyperdriveWasmError {
HyperdriveWasmError::Generic(self.to_string(), location.to_string())
}
}

// If a value can `.into()` a String, it can `.into()` a HyperdriveWasmError
impl<T> From<T> for HyperdriveWasmError
where
T: Into<String>,
{
#[track_caller]
fn from(t: T) -> Self {
t.into().to_error_at(&Location::caller().to_string())
}
}

/// Convert a value to a `Result<T, JsValue>` via `.to_js_result()`
pub trait ToJsResult<T> {
/// Convert a value to a `Result<T, JsValue>`, capturing the current location
#[track_caller]
fn to_js_result(self) -> Result<T, JsValue>
where
Self: Sized,
{
self.to_js_result_at(&Location::caller().to_string())
}

fn to_js_result_at(self, location: &str) -> Result<T, JsValue>;
}

// If a Result's error type can `.to_error()`, the Result can be
// converted to a `Result<T, JsValue>`
impl<T, E> ToJsResult<T> for Result<T, E>
where
E: ToHyperdriveWasmError,
{
fn to_js_result_at(self, location: &str) -> Result<T, JsValue> {
self.map_err(|e| e.to_error_at(location).into())
}
}

// Macros //

#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
$crate::error::HyperdriveWasmError::Generic(format!($($arg)*), format!("{}", ::std::panic::Location::caller()))
};
}

#[macro_export]
macro_rules! type_error {
($($arg:tt)*) => {
$crate::error::HyperdriveWasmError::TypeError(format!($($arg)*), format!("{}", ::std::panic::Location::caller()))
};
}

#[cfg(test)]
mod tests {

#[test]
fn test_conversion_error() {
let error = type_error!("Bad value: {}", "Foo");
assert!(error
.to_string()
.starts_with("Conversion error: Bad value: Foo\n Location: "));
}

#[test]
fn test_generic_error() {
let error = error!("Bad value: {}", "Foo");
assert!(error
.to_string()
.starts_with("Error: Bad value: Foo\n Location: "));
}
}
88 changes: 49 additions & 39 deletions crates/hyperdrive-wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
#![allow(non_snake_case)]

mod error;
mod long;
mod lp;
mod short;
mod types;
mod utils;

use ethers::types::U256;
use fixed_point::FixedPoint;
use error::ToJsResult;
use hyperdrive_math::{calculate_hpr_given_apr, calculate_hpr_given_apy, State};
use types::{JsPoolConfig, JsPoolInfo};
use utils::set_panic_hook;
use wasm_bindgen::prelude::*;
use utils::{set_panic_hook, ToFixedPoint, ToU256};
use wasm_bindgen::{prelude::*, JsValue};

// Initialization function
#[wasm_bindgen(start)]
pub fn initialize() {
set_panic_hook();
}

/// Calculates the pool's spot price, i.e. the price to open a long of 1.
///
/// @param poolInfo - The current state of the pool
///
/// @param poolConfig - The pool's configuration
#[wasm_bindgen(skip_jsdoc)]
pub fn spotPrice(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig) -> String {
set_panic_hook();
pub fn spotPrice(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig) -> Result<String, JsValue> {
let state = State {
config: poolConfig.into(),
info: poolInfo.into(),
config: poolConfig.try_into()?,
info: poolInfo.try_into()?,
};
let result_fp = state.calculate_spot_price().unwrap();
let result_fp = state.calculate_spot_price().to_js_result()?;

U256::from(result_fp).to_string()
Ok(result_fp.to_u256()?.to_string())
}

/// Calculate the holding period return (HPR) given a non-compounding,
Expand All @@ -37,13 +42,12 @@ pub fn spotPrice(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig) -> String {
///
/// @param positionDuration - The position duration in seconds
#[wasm_bindgen(skip_jsdoc)]
pub fn calcHprGivenApr(apr: &str, positionDuration: &str) -> String {
set_panic_hook();
let apr_fp = FixedPoint::from(U256::from_dec_str(apr).unwrap());
let position_duration_fp = FixedPoint::from(U256::from_dec_str(positionDuration).unwrap());
pub fn calcHprGivenApr(apr: &str, positionDuration: &str) -> Result<String, JsValue> {
let apr_fp = apr.to_fixed_point()?;
let position_duration_fp = positionDuration.to_fixed_point()?;
let result_fp = calculate_hpr_given_apr(apr_fp, position_duration_fp);

U256::from(result_fp).to_string()
Ok(result_fp.to_u256()?.to_string())
}

/// Calculate the holding period return (HPR) given a compounding, annualized
Expand All @@ -53,13 +57,12 @@ pub fn calcHprGivenApr(apr: &str, positionDuration: &str) -> String {
///
/// @param positionDuration - The position duration in seconds
#[wasm_bindgen(skip_jsdoc)]
pub fn calcHprGivenApy(apy: &str, positionDuration: &str) -> String {
set_panic_hook();
let apy_fp = FixedPoint::from(U256::from_dec_str(apy).unwrap());
let position_duration_fp = FixedPoint::from(U256::from_dec_str(positionDuration).unwrap());
let result_fp = calculate_hpr_given_apy(apy_fp, position_duration_fp).unwrap();
pub fn calcHprGivenApy(apy: &str, positionDuration: &str) -> Result<String, JsValue> {
let apy_fp = apy.to_fixed_point()?;
let position_duration_fp = positionDuration.to_fixed_point()?;
let result_fp = calculate_hpr_given_apy(apy_fp, position_duration_fp).to_js_result()?;

U256::from(result_fp).to_string()
Ok(result_fp.to_u256()?.to_string())
}

/// Calculates the pool's idle liquidity in base
Expand All @@ -68,14 +71,17 @@ pub fn calcHprGivenApy(apy: &str, positionDuration: &str) -> String {
///
/// @param poolConfig - The pool's configuration
#[wasm_bindgen(skip_jsdoc)]
pub fn idleShareReservesInBase(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig) -> String {
set_panic_hook();
pub fn idleShareReservesInBase(
poolInfo: &JsPoolInfo,
poolConfig: &JsPoolConfig,
) -> Result<String, JsValue> {
let state = State {
config: poolConfig.into(),
info: poolInfo.into(),
config: poolConfig.try_into()?,
info: poolInfo.try_into()?,
};

let result_fp = state.calculate_idle_share_reserves_in_base();
U256::from(result_fp).to_string()
Ok(result_fp.to_u256()?.to_string())
}

/// Calculates the pool's present value in base
Expand All @@ -86,15 +92,19 @@ pub fn idleShareReservesInBase(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig)
///
/// @param currentTime - The time at which to grab the present value
#[wasm_bindgen(skip_jsdoc)]
pub fn presentValue(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig, currentTime: &str) -> String {
set_panic_hook();
pub fn presentValue(
poolInfo: &JsPoolInfo,
poolConfig: &JsPoolConfig,
currentTime: &str,
) -> Result<String, JsValue> {
let state = State {
config: poolConfig.into(),
info: poolInfo.into(),
config: poolConfig.try_into()?,
info: poolInfo.try_into()?,
};
let current_time = U256::from_dec_str(currentTime).unwrap();
let result_fp = state.calculate_present_value(current_time).unwrap();
U256::from(result_fp).to_string()
let currentTime = currentTime.to_u256()?;
let result_fp = state.calculate_present_value(currentTime).to_js_result()?;

Ok(result_fp.to_u256()?.to_string())
}

/// Calculates the pool's fixed APR, i.e. the fixed rate a user locks in when
Expand All @@ -104,12 +114,12 @@ pub fn presentValue(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig, currentTim
///
/// @param poolConfig - The pool's configuration
#[wasm_bindgen(skip_jsdoc)]
pub fn spotRate(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig) -> String {
set_panic_hook();
pub fn spotRate(poolInfo: &JsPoolInfo, poolConfig: &JsPoolConfig) -> Result<String, JsValue> {
let state = State {
info: poolInfo.into(),
config: poolConfig.into(),
info: poolInfo.try_into()?,
config: poolConfig.try_into()?,
};
let result_fp = state.calculate_spot_rate().unwrap();
U256::from(result_fp).to_string()
let result_fp = state.calculate_spot_rate().to_js_result()?;

Ok(result_fp.to_u256()?.to_string())
}
24 changes: 11 additions & 13 deletions crates/hyperdrive-wasm/src/long/close.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use ethers::types::U256;
use fixed_point::FixedPoint;
use hyperdrive_math::State;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};

use crate::{
error::ToJsResult,
types::{JsPoolConfig, JsPoolInfo},
utils::set_panic_hook,
utils::ToU256,
};

/// Calculates the amount of shares the trader will receive after fees for
Expand All @@ -27,19 +26,18 @@ pub fn calcCloseLong(
bondAmount: &str,
maturityTime: &str,
currentTime: &str,
) -> String {
set_panic_hook();
) -> Result<String, JsValue> {
let state = State {
info: poolInfo.into(),
config: poolConfig.into(),
info: poolInfo.try_into()?,
config: poolConfig.try_into()?,
};
let bond_amount = FixedPoint::from(U256::from_dec_str(bondAmount).unwrap());
let maturity_time = U256::from_dec_str(maturityTime).unwrap();
let current_time = U256::from_dec_str(currentTime).unwrap();
let bond_amount = bondAmount.to_u256()?;
let maturity_time = maturityTime.to_u256()?;
let current_time = currentTime.to_u256()?;

let result_fp = state
.calculate_close_long(bond_amount, maturity_time, current_time)
.unwrap();
.to_js_result()?;

U256::from(result_fp).to_string()
Ok(result_fp.to_u256()?.to_string())
}
Loading

0 comments on commit a9aedb1

Please sign in to comment.