Skip to content
Merged
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
36 changes: 36 additions & 0 deletions crates/hyperdrive-math/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,39 @@ pub fn get_effective_share_reserves(
}
effective_share_reserves.into()
}

/// Calculates the bond reserves assuming that the pool has a given
/// share reserves and fixed rate APR.
///
/// r = ((1 / p) - 1) / t = (1 - p) / (pt)
/// p = ((u * z) / y) ** t
///
/// Arguments:
///
/// * effective_share_reserves : The pool's effective share reserves. The
/// effective share reserves are a modified version of the share
/// reserves used when pricing trades.
/// * initial_share_price : The pool's initial share price.
/// * apr : The pool's APR.
/// * position_duration : The amount of time until maturity in seconds.
/// * time_stretch : The time stretch parameter.
///
/// Returns:
///
/// * bond_reserves : The bond reserves (without adjustment) that make
/// the pool have a specified APR.
pub fn calculate_bonds_given_shares_and_rate(
effective_share_reserves: FixedPoint,
initial_share_price: FixedPoint,
apr: FixedPoint,
position_duration: FixedPoint,
time_stretch: FixedPoint,
) -> FixedPoint {
let annualized_time = position_duration / FixedPoint::from(U256::from(60 * 60 * 24 * 365));
// mu * (z - zeta) * (1 + apr * t) ** (1 / tau)
return initial_share_price
.mul_down(effective_share_reserves)
.mul_down(
(fixed!(1e18) + apr.mul_down(annualized_time)).pow(fixed!(1e18).div_up(time_stretch)),
);
}
84 changes: 83 additions & 1 deletion crates/hyperdrive-math/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use ethers::types::U256;
use eyre::Result;
use fixed_point::FixedPoint;
use fixed_point_macros::{fixed, uint256};
use hyperdrive_wrappers::wrappers::i_hyperdrive::Checkpoint;
use hyperdrive_math::{calculate_bonds_given_shares_and_rate, get_effective_share_reserves};
use hyperdrive_wrappers::wrappers::{
erc4626_data_provider::GetPoolConfigCall, i_hyperdrive::Checkpoint,
};
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use test_utils::{
Expand Down Expand Up @@ -227,3 +230,82 @@ pub async fn test_integration_get_max_long() -> Result<()> {

Ok(())
}

#[tokio::test]
pub async fn test_integration_calculate_bonds_given_shares_and_rate() -> Result<()> {
// Set up a random number generator. We use ChaCha8Rng with a randomly
// generated seed, which makes it easy to reproduce test failures given
// the seed.
let mut rng = {
let mut rng = thread_rng();
let seed = rng.gen();
ChaCha8Rng::seed_from_u64(seed)
};
Comment thread
dpaiton marked this conversation as resolved.

// Initialize the test chain and agents.
let chain = TestChain::new(3).await?;
let mut alice = Agent::new(
chain.client(chain.accounts()[0].clone()).await?,
chain.addresses(),
None,
)
.await?;
let mut bob = Agent::new(
chain.client(chain.accounts()[1].clone()).await?,
chain.addresses(),
None,
)
.await?;
let mut celine = Agent::new(
chain.client(chain.accounts()[2].clone()).await?,
chain.addresses(),
None,
)
.await?;

for _ in 0..*FUZZ_RUNS {
// Snapshot the chain and run the preamble.
let id = chain.snapshot().await?;
let fixed_rate = fixed!(0.05e18);
preamble(&mut rng, &mut alice, &mut bob, &mut celine, fixed_rate).await?;

// Calculate the bond reserves that target the current rate with the current
// share reserves.
let state = alice.get_state().await?;
let effective_share_reserves = get_effective_share_reserves(
state.info.share_reserves.into(),
state.info.share_adjustment.into(),
);
let rust_reserves = calculate_bonds_given_shares_and_rate(
effective_share_reserves,
state.config.initial_share_price.into(),
state.get_spot_rate(),
state.config.position_duration.into(),
state.config.time_stretch.into(),
);

// Ensure that the calculated reserves are approximately equal
// to the starting reserves. These won't be exactly equal because
// compressing through "rate space" loses information.
let sol_reserves = state.info.bond_reserves.into();
let delta = if rust_reserves > sol_reserves {
rust_reserves - sol_reserves
} else {
sol_reserves - rust_reserves
};
assert!(
delta < fixed!(1e12), // Better than 1e-6 error.
"Invalid bond reserve calculation.rust_reserves={} != sol_reserves={} within 1e12",
rust_reserves,
sol_reserves
);

// Revert to the snapshot and reset the agent's wallets.
chain.revert(id).await?;
alice.reset(Default::default());
bob.reset(Default::default());
celine.reset(Default::default());
}

Ok(())
}