Skip to content

Commit

Permalink
added some tests & sleep bugfix
Browse files Browse the repository at this point in the history
  • Loading branch information
dpaiton committed Jun 21, 2024
1 parent d4fd972 commit f925806
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 87 deletions.
6 changes: 6 additions & 0 deletions crates/fixed-point/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,12 @@ mod tests {
);
}

#[test]
fn test_sub_failure() {
// Ensure that subtraction producing negative numbers fails.
assert!(panic::catch_unwind(|| fixed!(1e18) - fixed!(2e18)).is_err());
}

#[test]
fn test_mul_div_down_failure() {
// Ensure that division by zero fails.
Expand Down
1 change: 1 addition & 0 deletions crates/hyperdrive-math/src/long/max.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ impl State {
checkpoint_exposure,
)
.is_ok()
&& absolute_max_base_amount >= self.minimum_transaction_amount()
{
return Ok(absolute_max_base_amount.min(budget));
}
Expand Down
89 changes: 74 additions & 15 deletions crates/hyperdrive-math/src/long/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl State {
pub fn calculate_open_long<F: Into<FixedPoint>>(&self, base_amount: F) -> Result<FixedPoint> {
let base_amount = base_amount.into();

if base_amount < self.config.minimum_transaction_amount.into() {
if base_amount < self.minimum_transaction_amount() {
return Err(eyre!("MinimumTransactionAmount: Input amount too low",));
}

Expand Down Expand Up @@ -120,14 +120,19 @@ impl State {

#[cfg(test)]
mod tests {
use std::panic;

use ethers::types::{I256, U256};
use fixed_point::fixed;
use hyperdrive_test_utils::{chain::TestChain, constants::FUZZ_RUNS};
use hyperdrive_wrappers::wrappers::ihyperdrive::Options;
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;

use super::*;
use crate::test_utils::{agent::HyperdriveMathAgent, preamble::preamble};
use crate::test_utils::{
agent::HyperdriveMathAgent, preamble::initialize_pool_with_random_state,
};

#[tokio::test]
async fn fuzz_calculate_spot_price_after_long() -> Result<()> {
Expand Down Expand Up @@ -263,14 +268,68 @@ mod tests {
Ok(())
}

// TODO ideally we would test calculate open long with an amount larger than the maximum size.
// However, `calculate_max_long` requires a `checkpoint_exposure` argument, which requires
// implementing checkpointing in the rust sdk.
// https://github.com/delvtech/hyperdrive/issues/862
// Tests open long with an amount larger than the maximum.
#[tokio::test]
async fn fuzz_error_open_long_max_txn_amount() -> Result<()> {
// This amount gets added to the max trade to cause a failure.
// TODO: You should be able to add a small amount (e.g. 1e18) to max to fail.
// calc_open_long or calc_max_long must be incorrect for the additional
// amount to have to be so large.
let max_base_delta = fixed!(1_000_000_000e18);

let mut rng = thread_rng();
for _ in 0..*FUZZ_RUNS {
let state = rng.gen::<State>();
let checkpoint_exposure = {
let value = rng.gen_range(fixed!(0)..=FixedPoint::try_from(I256::MAX)?);
if rng.gen() {
-I256::try_from(value)?
} else {
I256::try_from(value)?
}
};
let max_iterations = 7;
let open_vault_share_price = rng.gen_range(fixed!(0)..=state.vault_share_price());
// TODO: We should use calculate_absolute_max_short here because that is what we are testing.
// We need to catch panics because of FixedPoint overflows & underflows.
let max_trade = panic::catch_unwind(|| {
state.calculate_max_long(U256::MAX, checkpoint_exposure, Some(max_iterations))
});
// Since we're fuzzing it's possible that the max can fail.
// We're only going to use it in this test if it succeeded.
match max_trade {
Ok(max_trade) => match max_trade {
Ok(max_trade) => {
let base_amount = max_trade + max_base_delta;
let bond_amount =
panic::catch_unwind(|| state.calculate_open_long(base_amount));
match bond_amount {
Ok(result) => match result {
Ok(_) => {
return Err(eyre!(
format!(
"calculate_open_long for {} base should have failed but succeeded.",
base_amount,
)
));
}
Err(_) => continue, // Open threw an Err.
},
Err(_) => continue, // Open threw a panic, likely due to FixedPoint under/over flow.
}
}
Err(_) => continue, // Max threw an Err.
},
Err(_) => continue, // Max thew an panic, likely due to FixedPoint under/over flow.
}
}

Ok(())
}

#[tokio::test]
pub async fn fuzz_calc_open_long() -> Result<()> {
let tolerance = fixed!(1e10);
pub async fn fuzz_sol_calculate_open_long() -> Result<()> {
let tolerance = fixed!(1e11);

// Set up a random number generator. We use ChaCha8Rng with a randomly
// generated seed, which makes it easy to reproduce test failures given
Expand All @@ -292,8 +351,7 @@ mod tests {
let id = chain.snapshot().await?;

// Run the preamble.
let fixed_rate = fixed!(0.05e18);
preamble(&mut rng, &mut alice, &mut bob, &mut celine, fixed_rate).await?;
initialize_pool_with_random_state(&mut rng, &mut alice, &mut bob, &mut celine).await?;

// Get state and trade details.
let state = alice.get_state().await?;
Expand All @@ -304,7 +362,8 @@ mod tests {
// Compare the open short call output against calculate_open_long.
let rust_bonds_purchased = state.calculate_open_long(long_amount);

bob.fund(long_amount).await?;
// Fund a little extra bc of slippage.
bob.fund(long_amount + fixed!(10e18)).await?;
match bob
.hyperdrive()
.open_long(
Expand All @@ -321,11 +380,11 @@ mod tests {
.await
{
Ok((_, sol_bonds_purchased)) => {
let actual = rust_bonds_purchased.unwrap();
let error = if actual >= sol_bonds_purchased.into() {
actual - FixedPoint::from(sol_bonds_purchased)
let rust_bonds_purchased_unwrapped = rust_bonds_purchased.unwrap();
let error = if rust_bonds_purchased_unwrapped >= sol_bonds_purchased.into() {
rust_bonds_purchased_unwrapped - FixedPoint::from(sol_bonds_purchased)
} else {
FixedPoint::from(sol_bonds_purchased) - actual
FixedPoint::from(sol_bonds_purchased) - rust_bonds_purchased_unwrapped
};
assert!(
error <= tolerance,
Expand Down
38 changes: 24 additions & 14 deletions crates/hyperdrive-math/src/short/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,9 @@ mod tests {
use rand_chacha::ChaCha8Rng;

use super::*;
use crate::test_utils::{agent::HyperdriveMathAgent, preamble::preamble};
use crate::test_utils::{
agent::HyperdriveMathAgent, preamble::initialize_pool_with_random_state,
};

#[tokio::test]
async fn test_calculate_pool_deltas_after_open_short() -> Result<()> {
Expand Down Expand Up @@ -824,7 +826,7 @@ mod tests {
}

#[tokio::test]
pub async fn fuzz_calc_open_short() -> Result<()> {
pub async fn fuzz_sol_calculate_open_short() -> Result<()> {
let tolerance = fixed!(1e9);

// Set up a random number generator. We use ChaCha8Rng with a randomly
Expand All @@ -847,8 +849,7 @@ mod tests {
let id = chain.snapshot().await?;

// Run the preamble.
let fixed_rate = fixed!(0.05e18);
preamble(&mut rng, &mut alice, &mut bob, &mut celine, fixed_rate).await?;
initialize_pool_with_random_state(&mut rng, &mut alice, &mut bob, &mut celine).await?;

// Get state and trade details.
let state = alice.get_state().await?;
Expand All @@ -860,36 +861,38 @@ mod tests {
.get_checkpoint(state.to_checkpoint(alice.now().await?))
.await?;
let slippage_tolerance = fixed!(0.001e18);
let max_short = bob.calculate_max_short(Some(slippage_tolerance)).await?;
let max_short = celine.calculate_max_short(Some(slippage_tolerance)).await?;
let min_bond_amount = FixedPoint::from(state.config.minimum_transaction_amount)
* FixedPoint::from(state.info.vault_share_price);
let short_amount = rng.gen_range(min_bond_amount..=max_short);

// Compare the open short call output against calculate_open_short.
let actual_base_amount =
let rust_base_amount =
state.calculate_open_short(short_amount, open_vault_share_price.into());

match bob
// Deposit should always be less than the bond amount.
celine.fund(short_amount).await?;
match celine
.hyperdrive()
.open_short(
short_amount.into(),
FixedPoint::from(U256::MAX).into(),
fixed!(0).into(),
Options {
destination: bob.address(),
destination: celine.address(),
as_base: true,
extra_data: [].into(),
},
)
.call()
.await
{
Ok((_, expected_base_amount)) => {
let actual = actual_base_amount.unwrap();
let error = if actual >= expected_base_amount.into() {
actual - FixedPoint::from(expected_base_amount)
Ok((_, sol_base_amount)) => {
let rust_base_amount_unwrapped = rust_base_amount.unwrap();
let error = if rust_base_amount_unwrapped >= sol_base_amount.into() {
rust_base_amount_unwrapped - FixedPoint::from(sol_base_amount)
} else {
FixedPoint::from(expected_base_amount) - actual
FixedPoint::from(sol_base_amount) - rust_base_amount_unwrapped
};
assert!(
error <= tolerance,
Expand All @@ -898,7 +901,14 @@ mod tests {
tolerance
);
}
Err(_) => assert!(actual_base_amount.is_err()),
Err(sol_err) => {
assert!(
rust_base_amount.is_err(),
"sol_err={:#?}, but rust_base_amount={:#?} did not error",
sol_err,
rust_base_amount,
);
}
}

// Revert to the snapshot and reset the agent's wallets.
Expand Down
13 changes: 7 additions & 6 deletions crates/hyperdrive-math/src/test_utils/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ mod tests {
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;

use crate::test_utils::{agent::HyperdriveMathAgent, preamble::preamble};
use crate::test_utils::{
agent::HyperdriveMathAgent, preamble::initialize_pool_with_random_state,
};

// TODO: Unignore after we add the logic to apply checkpoints prior to computing
// the max long.
Expand All @@ -36,8 +38,7 @@ mod tests {
let id = chain.snapshot().await?;

// Run the preamble.
let fixed_rate = fixed!(0.05e18);
preamble(&mut rng, &mut alice, &mut bob, &mut celine, fixed_rate).await?;
initialize_pool_with_random_state(&mut rng, &mut alice, &mut bob, &mut celine).await?;

// Celine opens a max short. Despite the trading that happened before this,
// we expect Celine to open the max short on the pool or consume almost all
Expand Down Expand Up @@ -115,8 +116,7 @@ mod tests {
let id = chain.snapshot().await?;

// Run the preamble.
let fixed_rate = fixed!(0.05e18);
preamble(&mut rng, &mut alice, &mut bob, &mut celine, fixed_rate).await?;
initialize_pool_with_random_state(&mut rng, &mut alice, &mut bob, &mut celine).await?;

// One of three things should be true after opening the long:
//
Expand All @@ -134,7 +134,8 @@ mod tests {
let is_max_price = max_spot_price - spot_price_after_long < fixed!(1e15);
let is_solvency_consumed = {
let state = bob.get_state().await?;
let error_tolerance = fixed!(1_000e18).mul_div_down(fixed_rate, fixed!(0.1e18));
let error_tolerance =
fixed!(1_000e18).mul_div_down(state.calculate_spot_rate()?, fixed!(0.1e18));
state.calculate_solvency() < error_tolerance
};
let is_budget_consumed = {
Expand Down
Loading

0 comments on commit f925806

Please sign in to comment.