Skip to content
Draft
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
30 changes: 16 additions & 14 deletions crates/hyperdrive-math/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,33 +65,35 @@ impl Distribution<State> for Standard {
// We need the spot price to be less than or equal to 1, so we need to
// generate the bond reserves so that mu * z <= y
let share_reserves = rng.gen_range(fixed!(1_000e18)..=fixed!(100_000_000e18));
let share_adjustment = {
if rng.gen() {
-I256::try_from(rng.gen_range(fixed!(0)..=fixed!(100_000e18))).unwrap()
} else {
// We generate values that satisfy `z - zeta >= z_min`,
// so `z - z_min >= zeta`.
I256::try_from(rng.gen_range(
fixed!(0)..(share_reserves - FixedPoint::from(config.minimum_share_reserves)),
))
.unwrap()
}
};
let effective_share_reserves =
calculate_effective_share_reserves(share_reserves, share_adjustment).unwrap();
let info = PoolInfo {
share_reserves: share_reserves.into(),
zombie_base_proceeds: fixed!(0).into(),
zombie_share_reserves: fixed!(0).into(),
bond_reserves: rng
.gen_range(
share_reserves * FixedPoint::from(config.initial_vault_share_price)
effective_share_reserves * FixedPoint::from(config.initial_vault_share_price)
..=fixed!(1_000_000_000e18),
)
.into(),
vault_share_price: rng.gen_range(fixed!(0.5e18)..=fixed!(2.5e18)).into(),
longs_outstanding: rng.gen_range(fixed!(0)..=fixed!(100_000e18)).into(),
shorts_outstanding: rng.gen_range(fixed!(0)..=fixed!(100_000e18)).into(),
long_exposure: rng.gen_range(fixed!(0)..=fixed!(100_000e18)).into(),
share_adjustment: {
if rng.gen() {
-I256::try_from(rng.gen_range(fixed!(0)..=fixed!(100_000e18))).unwrap()
} else {
// We generate values that satisfy `z - zeta >= z_min`,
// so `z - z_min >= zeta`.
I256::try_from(rng.gen_range(
fixed!(0)
..(share_reserves - FixedPoint::from(config.minimum_share_reserves)),
))
.unwrap()
}
},
share_adjustment: share_adjustment.into(),
// If this range returns greater than position duration, then both rust and solidity will fail
// on calls that depend on this value.
long_average_maturity_time: rng
Expand Down
77 changes: 65 additions & 12 deletions crates/hyperdrive-math/src/short/max.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,11 +436,10 @@ impl State {
return Ok(None);
};
let curve_fee_base = self.open_short_curve_fee(bond_amount)?;
let share_reserves = self.share_reserves()
- (principal
- (curve_fee_base
- self.open_short_governance_fee(bond_amount, Some(curve_fee_base))?)
/ self.vault_share_price());
let share_reserves = self.share_reserves() - principal
+ (curve_fee_base
- self.open_short_governance_fee(bond_amount, Some(curve_fee_base))?)
/ self.vault_share_price();
let exposure = {
let checkpoint_exposure: FixedPoint =
checkpoint_exposure.max(I256::zero()).try_into()?;
Expand Down Expand Up @@ -520,11 +519,29 @@ mod tests {
#[tokio::test]
async fn fuzz_calculate_max_short_no_budget() -> Result<()> {
let chain = TestChain::new().await?;
let mut lowest_rate: Option<FixedPoint> = None;
let mut highest_rate: Option<FixedPoint> = None;
let mut lowest_rate_mismatch: Option<FixedPoint> = None;
let mut highest_rate_mismatch: Option<FixedPoint> = None;

let mut both_pass_tests = 0;
let mut both_fail_tests = 0;
let mut mismatched_tests = 0;

// Fuzz the rust and solidity implementations against each other.
let mut rng = thread_rng();
for _ in 0..*FAST_FUZZ_RUNS {
let state = rng.gen::<State>();
let fixed_rate = state
.calculate_spot_rate()
.expect("Failed to get fixed rate");
if lowest_rate.is_none() || fixed_rate < lowest_rate.unwrap() {
lowest_rate = Some(fixed_rate);
}
if highest_rate.is_none() || fixed_rate > highest_rate.unwrap() {
highest_rate = Some(fixed_rate);
}
// println!("Fixed rate: {}", fixed_rate);
let checkpoint_exposure = {
let value = rng.gen_range(fixed!(0)..=FixedPoint::try_from(I256::MAX)?);
if rng.gen() {
Expand Down Expand Up @@ -572,18 +589,56 @@ mod tests {
// TODO: remove this tolerance when calculate_open_short
// rust implementation matches solidity.
// Currently, only about 1 - 4 / 1000 tests aren't
// exact matchces. Related issue:
// exact matches. Related issue:
// https://github.com/delvtech/hyperdrive-rs/issues/45
assert_eq!(
U256::from(actual.unwrap().unwrap()) / uint256!(1e12),
expected / uint256!(1e12)
);
both_pass_tests += 1;
}
Err(_) => {
assert!(actual.is_err() || actual.unwrap().is_err());
}
Err(expected) => match actual {
Err(_) => {
both_fail_tests += 1;
// println!("Both failed: actual: {:?} expected: {:?}", actual, expected);
}
Ok(_) => {
mismatched_tests += 1;
println!("Fixed rate: {}", fixed_rate);
println!("MISMATCHED: actual: {:?} expected: {:?}", actual, expected);

let rust_result = actual.unwrap().unwrap();
let solidity_result = expected;
println!("Rust: {:?} Solidity: {:?}", rust_result, solidity_result);

if lowest_rate_mismatch.is_none()
|| fixed_rate < lowest_rate_mismatch.unwrap()
{
lowest_rate_mismatch = Some(fixed_rate);
}
if highest_rate_mismatch.is_none()
|| fixed_rate > highest_rate_mismatch.unwrap()
{
highest_rate_mismatch = Some(fixed_rate);
}
}
},
};
}
let total_tests = both_pass_tests + both_fail_tests + mismatched_tests;
let failure_rate = mismatched_tests as f64 / total_tests as f64;
println!(
"Total tests: {} Both pass: {} Both fail: {} Mismatched: {} Failure rate: {}",
total_tests, both_pass_tests, both_fail_tests, mismatched_tests, failure_rate
);
println!(
"Fuzzed over fixed rate from {:?} to {:?}",
lowest_rate, highest_rate
);
println!(
"Mismatched fixed rate from {:?} to {:?}",
lowest_rate_mismatch, highest_rate_mismatch
);
Ok(())
}

Expand All @@ -606,10 +661,8 @@ mod tests {
// Snapshot the chain.
let id = chain.snapshot().await?;

// TODO: We should fuzz over a range of fixed rates.
//
// Fund Alice and Bob.
let fixed_rate = fixed!(0.05e18);
let fixed_rate = rng.gen_range(fixed!(0.0001e18)..=fixed!(1e18)); // 0.01% to 100%
let contribution = rng.gen_range(fixed!(100_000e18)..=fixed!(100_000_000e18));
alice.fund(contribution).await?;

Expand Down
16 changes: 8 additions & 8 deletions crates/hyperdrive-test-utils/src/chain/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,16 @@ impl Default for TestChainConfig {
factory_max_position_duration: U256::from(60 * 60 * 24 * 365 * 10), // 10 years
factory_min_circuit_breaker_delta: uint256!(0.15e18),
factory_max_circuit_breaker_delta: uint256!(2e18),
factory_min_fixed_apr: uint256!(0.01e18),
factory_max_fixed_apr: uint256!(0.5e18),
factory_min_time_stretch_apr: uint256!(0.01e18),
factory_max_time_stretch_apr: uint256!(0.5e18),
factory_min_curve_fee: uint256!(0.0001e18),
factory_min_flat_fee: uint256!(0.0001e18),
factory_min_fixed_apr: uint256!(0), // 0%
factory_max_fixed_apr: uint256!(10e18), // 1000%
factory_min_time_stretch_apr: uint256!(0), // 0%
factory_max_time_stretch_apr: uint256!(10e18), // 1000%
factory_min_curve_fee: uint256!(0.0001e18), // 0.01%
factory_min_flat_fee: uint256!(0.0001e18), // 0.01%
factory_min_governance_lp_fee: uint256!(0.15e18),
factory_min_governance_zombie_fee: uint256!(0.03e18),
factory_max_curve_fee: uint256!(0.1e18),
factory_max_flat_fee: uint256!(0.001e18),
factory_max_curve_fee: uint256!(0.1e18), // 10%
factory_max_flat_fee: uint256!(0.001e18), // 0.1%
factory_max_governance_lp_fee: uint256!(0.15e18),
factory_max_governance_zombie_fee: uint256!(0.03e18),
// erc4626 hyperdrive configuration
Expand Down
2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "stable"
3 changes: 0 additions & 3 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
unstable_features = true
imports_granularity = "Crate"
group_imports = "StdExternalCrate"
reorder_imports = true