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

cleanup tests & test support code #211

Merged
merged 13 commits into from
Jan 22, 2025
Prev Previous commit
Next Next commit
fix check that short is possible to be more strict
  • Loading branch information
dpaiton authored and sentilesdal committed Jan 22, 2025
commit b37873a84f84273c15f39f4aefab2800918e179c
114 changes: 59 additions & 55 deletions crates/hyperdrive-math/src/short/max.rs
Original file line number Diff line number Diff line change
Expand Up @@ -890,17 +890,13 @@ mod tests {
chain::TestChain,
constants::{FAST_FUZZ_RUNS, FUZZ_RUNS, SLOW_FUZZ_RUNS},
};
use hyperdrive_wrappers::wrappers::{
ihyperdrive::{Checkpoint, Options},
mock_hyperdrive_math::MaxTradeParams,
};
use hyperdrive_wrappers::wrappers::ihyperdrive::Checkpoint;
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;

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

#[tokio::test]
Expand Down Expand Up @@ -960,6 +956,8 @@ mod tests {
let empirical_derivative_epsilon = fixed!(1e14);
let test_tolerance = fixed!(1e14);
let mut rng = thread_rng();

// Run the fuzz tests.
for _ in 0..*FAST_FUZZ_RUNS {
let state = rng.gen::<State>();
// Min trade amount should be at least 1,000x the derivative epsilon
Expand Down Expand Up @@ -1025,26 +1023,31 @@ mod tests {
async fn fuzz_calculate_absolute_max_short_guess() -> Result<()> {
let solvency_tolerance = fixed!(100_000_000e18);
let mut rng = thread_rng();

// Run the fuzz tests.
for _ in 0..*FAST_FUZZ_RUNS {
// Compute a random state and checkpoint exposure.
let state = rng.gen::<State>();
// Check that a short is possible.
if state
.effective_share_reserves()?
.min(state.share_reserves())
< state
.calculate_min_share_reserves_given_exposure()?
.change_type::<U256>()?
{
continue;
}
match state.solvency_after_short(state.minimum_transaction_amount()) {
Ok(_) => (),
Err(_) => continue,
}

// Compute the guess, check that it is solvent.
let max_short_guess = state.absolute_max_short_guess()?;
assert!(
state.solvency_after_short(max_short_guess).is_ok(),
"max_short_guess={:#?} is not solvent",
max_short_guess
);
assert!(
state
.calculate_open_short(max_short_guess, state.vault_share_price())
.is_ok(),
"cannot open short with max_short_guess={:#?}",
max_short_guess
);
let solvency = state.solvency_after_short(max_short_guess)?;

// Check that the remaining available shares in the pool are below a
Expand All @@ -1056,7 +1059,6 @@ mod tests {
solvency_tolerance
);
}

Ok(())
}

Expand All @@ -1067,20 +1069,12 @@ mod tests {
async fn fuzz_calculate_absolute_max_short() -> Result<()> {
let bonds_tolerance = fixed_u256!(1e9);
let max_iterations = 500;
// Run the fuzz tests

// Run the fuzz tests.
let mut rng = thread_rng();
for _ in 0..*FUZZ_RUNS {
let state = rng.gen::<State>();
// Make sure a short is possible.
if state
.effective_share_reserves()?
.min(state.share_reserves())
< state
.calculate_min_share_reserves_given_exposure()?
.change_type::<U256>()?
{
continue;
}
match state.solvency_after_short(state.minimum_transaction_amount()) {
Ok(_) => (),
Err(_) => continue,
Expand All @@ -1093,11 +1087,19 @@ mod tests {
// The short should be valid.
assert!(absolute_max_short >= state.minimum_transaction_amount());
assert!(state.solvency_after_short(absolute_max_short).is_ok());

assert!(state
.calculate_open_short(absolute_max_short, state.vault_share_price())
.is_ok());
// Adding tolerance more bonds should be insolvent.
assert!(state
.solvency_after_short(absolute_max_short + bonds_tolerance)
.is_err());
assert!(state
.calculate_open_short(
absolute_max_short + bonds_tolerance,
state.vault_share_price()
)
.is_err());
}
Ok(())
}
Expand All @@ -1109,20 +1111,12 @@ mod tests {
let abs_max_bonds_tolerance = fixed_u256!(1e9);
let budget_base_tolerance = fixed_u256!(1e9);
let max_iterations = 500;
// Run the fuzz tests
let mut rng = thread_rng();
for _ in 0..*FUZZ_RUNS {

// Run the fuzz tests.
for _ in 0..*SLOW_FUZZ_RUNS {
let state = rng.gen::<State>();
// Make sure a short is possible.
if state
.effective_share_reserves()?
.min(state.share_reserves())
< state
.calculate_min_share_reserves_given_exposure()?
.change_type::<U256>()?
{
continue;
}
match state.solvency_after_short(state.minimum_transaction_amount()) {
Ok(_) => (),
Err(_) => continue,
Expand Down Expand Up @@ -1213,7 +1207,7 @@ mod tests {
// generated seed, which makes it easy to reproduce test failures given
// the seed.
let mut rng = {
let mut rng = thread_rng();
let mut rng = thread_rng();
let seed = rng.gen();
ChaCha8Rng::seed_from_u64(seed)
};
Expand Down Expand Up @@ -1393,17 +1387,17 @@ mod tests {
initialize_pool_with_random_state(&mut rng, &mut alice, &mut bob, &mut celine).await?;
// Get the current state from solidity.
let mut state = alice.get_state().await?;
// Check that a short is possible.
// Check that a short is possible.
if state
.solvency_after_short(state.minimum_transaction_amount())
.is_err()
{
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
continue;
}
{
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
continue;
}

// Open the max short.
let max_short = state.calculate_absolute_max_short(None, None)?;
Expand Down Expand Up @@ -1449,24 +1443,24 @@ mod tests {
// Ensure solidity & rust solvency values are within tolerance.
let error = if solvency_after_short_guess > solvency_after_short_rs {
solvency_after_short_guess - solvency_after_short_rs
} else {
} else {
solvency_after_short_rs - solvency_after_short_guess
};
assert!(
};
assert!(
error <= solvency_tolerance,
"rust error={:#?} > tolerance={:#?}",
error,
error,
solvency_tolerance
);
let error = if solvency_after_short_guess > solvency_after_short_sol {
solvency_after_short_guess - solvency_after_short_sol
} else {
} else {
solvency_after_short_sol - solvency_after_short_guess
};
assert!(
};
assert!(
error <= solvency_tolerance,
"solidity error={:#?} > tolerance={:#?}",
error,
error,
solvency_tolerance
);

Expand All @@ -1491,8 +1485,18 @@ mod tests {
let inc = i_val * fixed!(1e9); // range is 1e9->1e19
increments.push(inc);
}

// Run the fuzz tests.
for _ in 0..*FUZZ_RUNS {
let state = rng.gen::<State>();
// Ensure a short is possible.
if state
.solvency_after_short(state.minimum_transaction_amount())
.is_err()
{
continue;
}

// Vary the baseline scale factor by adjusting the initial bond amount.
let bond_amount = rng.gen_range(fixed!(1e10)..=fixed!(100_000_000e18));
// Compute f_x at the baseline bond_amount.
Expand Down
96 changes: 44 additions & 52 deletions crates/hyperdrive-math/src/short/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ impl State {
));
}

// Verify final solvency. This ensures exposure is accounted for.
// In Hyperdrive Solidity this is done in `applyOpenShort`.
match self.solvency_after_short(bond_amount) {
Ok(_) => (),
Err(e) => return Err(e),
}

// If the open share price hasn't been set, we use the current share
// price, since this is what will be set as the checkpoint share price
// in this transaction.
Expand Down Expand Up @@ -96,15 +103,6 @@ impl State {
let curve_fee_shares = self
.open_short_curve_fee(bond_amount)?
.div_up(self.vault_share_price());
if share_reserves_delta < curve_fee_shares {
return Err(eyre!(format!(
"The transaction curve fee = {}, computed with coefficient = {},
is too high. It must be less than share reserves delta = {}",
curve_fee_shares,
self.curve_fee(),
share_reserves_delta
)));
}

// If negative interest has accrued during the current checkpoint, we
// set the close vault share price to equal the open vault share price.
Expand Down Expand Up @@ -498,8 +496,7 @@ mod tests {

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

#[tokio::test]
Expand Down Expand Up @@ -529,16 +526,15 @@ mod tests {
alice.advance_time(fixed!(0), fixed!(0)).await?;
let original_state = alice.get_state().await?;
// Check that a short is possible.
if original_state.effective_share_reserves()?
< original_state
.calculate_min_share_reserves_given_exposure()?
.change_type::<U256>()?
{
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
continue;
match original_state.solvency_after_short(original_state.minimum_transaction_amount()) {
Ok(_) => (),
Err(_) => {
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
continue;
}
}
// Get a random short amount.
let max_short_amount = original_state.calculate_max_short(
Expand Down Expand Up @@ -858,18 +854,16 @@ mod tests {
alice.initialize(fixed_rate, contribution, None).await?;
let mut state = alice.get_state().await?;

// Check that a short is possible.
if state.effective_share_reserves()?
< state
.calculate_min_share_reserves_given_exposure()?
.change_type::<U256>()?
{
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
continue;
// Check that a short is possible & generate a random short amount.
match state.solvency_after_short(state.minimum_transaction_amount()) {
Ok(_) => (),
Err(_) => {
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
continue;
}
}

let bond_amount = rng.gen_range(
state.minimum_transaction_amount()..=bob.calculate_max_short(None).await?,
);
Expand Down Expand Up @@ -992,15 +986,14 @@ mod tests {

// Check that a short is possible.
let state = alice.get_state().await?;
if state.effective_share_reserves()?
< state
.calculate_min_share_reserves_given_exposure()?
.change_type::<U256>()?
{
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
continue;
match state.solvency_after_short(state.minimum_transaction_amount()) {
Ok(_) => (),
Err(_) => {
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
continue;
}
}

// Bob opens a short with a random bond amount. Before opening the
Expand Down Expand Up @@ -1157,16 +1150,15 @@ mod tests {
let min_txn_amount = state.minimum_transaction_amount();

// Check that a short is possible.
if state.effective_share_reserves()?
< state
.calculate_min_share_reserves_given_exposure()?
.change_type::<U256>()?
{
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
continue;
match state.solvency_after_short(state.minimum_transaction_amount()) {
Ok(_) => (),
Err(_) => {
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
continue;
}
}

let max_short = celine.calculate_max_short(None).await?;
Expand Down Expand Up @@ -1275,7 +1267,7 @@ mod tests {
for _ in 0..*FAST_FUZZ_RUNS {
let state = rng.gen::<State>();
let open_vault_share_price = rng.gen_range(fixed!(1e5)..=state.vault_share_price());
match get_max_short(state.clone(), None) {
match state.calculate_absolute_max_short(None, None) {
Ok(max_short_bonds) => {
let bond_amount =
rng.gen_range(state.minimum_transaction_amount()..=max_short_bonds);
Expand Down