1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#[cfg(test)]
mod tests {
use ethers::types::U256;
use eyre::Result;
use fixedpointmath::fixed;
use hyperdrive_test_utils::{chain::TestChain, constants::FUZZ_RUNS};
use hyperdrive_wrappers::wrappers::ihyperdrive::Checkpoint;
use rand::{thread_rng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
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.
#[ignore]
#[tokio::test]
pub async fn test_integration_calculate_max_short() -> 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)
};
// Initialize the test chain.
let chain = TestChain::new().await?;
let mut alice = chain.alice().await?;
let mut bob = chain.bob().await?;
let mut celine = chain.celine().await?;
for _ in 0..*FUZZ_RUNS {
// Snapshot the chain.
let id = chain.snapshot().await?;
// Run the preamble.
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
// of her budget.
let state = alice.get_state().await?;
let Checkpoint {
vault_share_price: open_vault_share_price,
weighted_spot_price: _,
last_weighted_spot_price_update_time: _,
} = alice
.get_checkpoint(state.to_checkpoint(alice.now().await?))
.await?;
let checkpoint_exposure = alice
.get_checkpoint_exposure(state.to_checkpoint(alice.now().await?))
.await?;
let global_max_short = state.calculate_max_short(
U256::MAX,
open_vault_share_price,
checkpoint_exposure,
None,
None,
)?;
let budget = bob.base();
let slippage_tolerance = fixed!(0.001e18);
let max_short = bob.calculate_max_short(Some(slippage_tolerance)).await?;
bob.open_short(max_short, None, None).await?;
if max_short != global_max_short {
// We currently allow up to a tolerance of 3%, which means
// that the max short is always consuming at least 97% of
// the budget.
let error_tolerance = fixed!(0.03e18);
assert!(
bob.base() < budget * (fixed!(1e18) - slippage_tolerance) * error_tolerance,
"expected (base={}) < (budget={}) * {} = {}",
bob.base(),
budget,
error_tolerance,
budget * error_tolerance
);
}
// Revert to the snapshot and reset the agent's wallets.
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
}
Ok(())
}
// TODO: Unignore after we add the logic to apply checkpoints prior to computing
// the max long.
#[ignore]
#[tokio::test]
pub async fn test_integration_calculate_max_long() -> 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)
};
// Initialize the test chain.
let chain = TestChain::new().await?;
let mut alice = chain.alice().await?;
let mut bob = chain.bob().await?;
let mut celine = chain.celine().await?;
for _ in 0..*FUZZ_RUNS {
// Snapshot the chain.
let id = chain.snapshot().await?;
// Run the preamble.
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:
//
// 1. The pool's spot price reached the max spot price prior to
// considering fees.
// 2. The pool's solvency is close to zero.
// 3. Bob's budget is consumed.
let max_spot_price = bob.get_state().await?.calculate_max_spot_price()?;
let max_long = bob.calculate_max_long(None).await?;
let spot_price_after_long = bob
.get_state()
.await?
.calculate_spot_price_after_long(max_long, None)?;
bob.open_long(max_long, None, None).await?;
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(state.calculate_spot_rate()?, fixed!(0.1e18));
state.calculate_solvency()? < error_tolerance
};
let is_budget_consumed = {
let error_tolerance = fixed!(1e18);
bob.base() < error_tolerance
};
assert!(
is_max_price || is_solvency_consumed || is_budget_consumed,
"Invalid max long."
);
// Revert to the snapshot and reset the agent's wallets.
chain.revert(id).await?;
alice.reset(Default::default()).await?;
bob.reset(Default::default()).await?;
celine.reset(Default::default()).await?;
}
Ok(())
}
}