Skip to content

Commit

Permalink
add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sentilesdal committed May 1, 2024
1 parent 6d06be3 commit 48e666b
Showing 1 changed file with 191 additions and 11 deletions.
202 changes: 191 additions & 11 deletions crates/hyperdrive-math/src/lp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{calculate_effective_share_reserves, State, YieldSpace};

static SHARE_PROCEEDS_MAX_ITERATIONS: u128 = 4;
static SHARE_PROCEEDS_MIN_TOLERANCE: u128 = 1_000_000_000; // 1e9
static SHARE_PROCEEDS_SHORT_CIRCUIT_TOLERANCE: u128 = 1_000_000_000; // 1e9;

impl State {
/// Calculates the lp_shares for a given contribution when adding liquidity.
Expand Down Expand Up @@ -588,16 +589,37 @@ impl State {
starting_present_value: FixedPoint,
lp_total_supply: FixedPoint, // total lp shares and withdrawal shares - w.s. ready to withraw
present_value: FixedPoint,
) -> bool {
self.lp_total_supply();
let new_lp_share_price = present_value.div_down(active_lp_total_supply);
let old_lp_share_price = starting_present_value.div_down(lp_total_supply);
let old_lp_share_price_with_tolerance = fixed!(1e18)
+ FixedPoint::from(SHARE_PROCEEDS_MIN_TOLERANCE)
.mul_div_down(starting_present_value, lp_total_supply);

new_lp_share_price >= old_lp_share_price
&& new_lp_share_price <= old_lp_share_price_with_tolerance
) -> Result<bool> {
if active_lp_total_supply == fixed!(0) {
return Err(eyre!("active_lp_total_supply is zero"));
}
if lp_total_supply == fixed!(0) {
return Err(eyre!("lp_total_supply is zero"));
}

let new_lp_share_price_down = present_value.div_down(active_lp_total_supply);
let new_lp_share_price_up = present_value.div_up(active_lp_total_supply);
let old_lp_share_price = starting_present_value.div_up(lp_total_supply);
let old_lp_share_price_with_tolerance = (fixed!(1e18)
+ FixedPoint::from(SHARE_PROCEEDS_SHORT_CIRCUIT_TOLERANCE))
.mul_div_down(starting_present_value, lp_total_supply);

Ok(
// Ensure that new LP share price is greater than or equal to the
// previous LP share price:
//
// PV_1 / l_1 >= PV_0 / l_0
//
// NOTE: Round the LHS down to make the check stricter.
new_lp_share_price_down >= old_lp_share_price
// Ensure that new LP share price is less than or equal to the
// previous LP share price plus the minimum tolerance:
//
// PV_1 / l_1 <= (PV_0 / l_0) * (1 + tolerance)
//
// NOTE: Round the LHS up to make the check stricter.
&& new_lp_share_price_up <= old_lp_share_price_with_tolerance,
)
}

/// One of the edge cases that occurs when using Newton's method for the
Expand Down Expand Up @@ -1015,7 +1037,7 @@ mod tests {
// this to zero to make the math simpler.
let current_block_timestamp = fixed!(0);

// Calcuulate the result from the Rust implementation.
// Calculate the result from the Rust implementation.
let actual = present_state.calculate_shares_delta_given_bonds_delta_derivative(
bond_amount,
original_state.share_reserves(),
Expand Down Expand Up @@ -1097,4 +1119,162 @@ mod tests {
}
Ok(())
}

#[tokio::test]
async fn fuzz_test_should_short_circuit_distribute_excess_idle_share_proceeds() -> Result<()> {
let chain = TestChain::new().await?;
let mock = chain.mock_lp_math();

// Fuzz the rust and solidity implementations against each other.
let mut rng = thread_rng();

for _ in 0..*FAST_FUZZ_RUNS {
// Generate random states.
let present_state = rng.gen::<State>();

let starting_present_value = rng.gen_range(fixed!(0)..=fixed!(1e24));
let present_value = rng.gen_range(fixed!(0)..=fixed!(1e24));
let active_lp_total_supply = rng.gen_range(fixed!(0)..=fixed!(1e24));
let lp_total_supply = rng.gen_range(fixed!(0)..=fixed!(1e24));

// Calculate the result from the Rust implementation.
let actual = present_state.should_short_circuit_distribute_excess_idle_share_proceeds(
active_lp_total_supply,
starting_present_value,
lp_total_supply,
present_value,
);

// Gather the parameters for the solidity call.
let mut params = DistributeExcessIdleParams::default();
params.active_lp_total_supply = active_lp_total_supply.into();
params.starting_present_value = starting_present_value.into();

// Make the solidity call and compare to the Rust implementation.
match mock
.should_short_circuit_distribute_excess_idle_share_proceeds(
params,
lp_total_supply.into(),
present_value.into(),
)
.call()
.await
{
Ok(expected) => {
if expected == true {
assert!(actual? == true);
} else {
assert!(actual? == false);
}
}
Err(_) => {
assert!(actual.is_err())
}
}
}
Ok(())
}

#[tokio::test]
async fn fuzz_test_calculate_distribute_excess_idle_share_proceeds_net_long_edge_case(
) -> Result<()> {
let chain = TestChain::new().await?;
let mock = chain.mock_lp_math();

// Fuzz the rust and solidity implementations against each other.
let mut rng = thread_rng();

for _ in 0..*FAST_FUZZ_RUNS {
// Generate random states.
let original_state = rng.gen::<State>();
let present_state = rng.gen::<State>();

//**************Get Variables**************** */
// Generate random variables.
let net_curve_trade = I256::try_from(rng.gen_range(fixed!(0)..=fixed!(1e24)))?; // 1 million
let withdraw_shares_total_supply = rng.gen_range(fixed!(0)..=fixed!(1e24)); // 1 million

// Maturity time goes from 0 to position duration, so we'll just set
// this to zero to make the math simpler.
let current_block_timestamp = fixed!(0);

// This errors out a lot so we need to catch that here.
let starting_present_value_result =
original_state.calculate_present_value_safe(U256::from(current_block_timestamp));
if starting_present_value_result.is_err() {
continue;
}
let starting_present_value = starting_present_value_result?;
let idle = present_state.calculate_idle_share_reserves();
//******************************************* */
// Calculate the result from the Rust implementation.
let actual = present_state
.calculate_distribute_excess_idle_share_proceeds_net_long_edge_case(
original_state.share_adjustment(),
original_state.share_reserves(),
starting_present_value,
present_state.lp_total_supply(),
withdraw_shares_total_supply,
);

// Gather the parameters for the solidity call. There are a lot
// that aren't actually used, but the solidity call needs them.
let params = DistributeExcessIdleParams {
present_value_params: PresentValueParams {
share_reserves: present_state.info.share_reserves,
bond_reserves: present_state.info.bond_reserves,
longs_outstanding: present_state.info.longs_outstanding,
share_adjustment: present_state.info.share_adjustment,
time_stretch: present_state.config.time_stretch,
vault_share_price: present_state.info.vault_share_price,
initial_vault_share_price: present_state.config.initial_vault_share_price,
minimum_share_reserves: present_state.config.minimum_share_reserves,
minimum_transaction_amount: present_state.config.minimum_transaction_amount,
long_average_time_remaining: present_state
.calculate_normalized_time_remaining(
present_state.long_average_maturity_time().into(),
current_block_timestamp.into(),
)
.into(),
short_average_time_remaining: present_state
.calculate_normalized_time_remaining(
present_state.short_average_maturity_time().into(),
current_block_timestamp.into(),
)
.into(),
shorts_outstanding: present_state.shorts_outstanding().into(),
},
starting_present_value: starting_present_value.into(),
active_lp_total_supply: original_state.lp_total_supply().into(),
withdrawal_shares_total_supply: uint256!(0),
idle: idle.into(),
net_curve_trade: net_curve_trade,
original_share_reserves: original_state.share_reserves().into(),
original_share_adjustment: original_state.share_adjustment(),
original_bond_reserves: original_state.bond_reserves().into(),
};

// Make the solidity call and compare to the Rust implementation.
match mock
.calculate_distribute_excess_idle_share_proceeds_net_long_edge_case_safe(params)
.call()
.await
{
Ok(expected) => {
let (result, success) = expected;
if !success && result == uint256!(0) {
assert!(actual.is_err());
} else if success && result == uint256!(0) {
assert_eq!(actual?, fixed!(0));
} else {
assert_eq!(actual?, FixedPoint::from(expected.0));
}
}
Err(_) => {
assert!(actual.is_err())
}
}
}
Ok(())
}
}

0 comments on commit 48e666b

Please sign in to comment.