-
Notifications
You must be signed in to change notification settings - Fork 162
Expand file tree
/
Copy pathmod.rs
More file actions
265 lines (237 loc) · 7.7 KB
/
mod.rs
File metadata and controls
265 lines (237 loc) · 7.7 KB
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
pub mod colocation;
mod deploy;
pub mod onchain_components;
pub mod proxy;
mod services;
mod solver;
use {
crate::nodes::{NODE_HOST, Node},
::alloy::signers::local::{MnemonicBuilder, coins_bip39::English},
anyhow::{Result, anyhow},
ethrpc::Web3,
futures::FutureExt,
std::{
future::Future,
io::Write,
iter::empty,
panic::{self, AssertUnwindSafe},
sync::{Arc, Mutex},
time::Duration,
},
tempfile::TempPath,
};
pub use {deploy::*, onchain_components::*, services::*, solver::*};
/// Create a temporary file with the given content.
pub fn config_tmp_file<C: AsRef<[u8]>>(content: C) -> TempPath {
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(content.as_ref()).unwrap();
file.into_temp_path()
}
/// Reasonable default timeout for `wait_for_condition`.
///
/// The correct timeout depends on the condition and where the test is run. For
/// example, it can take a couple of seconds for a newly placed order to show up
/// in the auction. When running on Github CI, anything can take an unexpectedly
/// long time.
pub const TIMEOUT: Duration = Duration::from_secs(30);
/// Repeatedly evaluates condition until it returns a truthy value
/// (true, Some(true), Result(true)) or the timeout is reached.
/// If condition evaluates to truthy, Ok(()) is returned. If the timeout
/// is reached Err is returned.
pub async fn wait_for_condition<Fut>(
timeout: Duration,
mut condition: impl FnMut() -> Fut,
) -> Result<()>
where
Fut: Future<Output: AwaitableCondition>,
{
let start = std::time::Instant::now();
while !condition().await.was_successful() {
tokio::time::sleep(Duration::from_millis(200)).await;
if start.elapsed() > timeout {
return Err(anyhow!("timeout"));
}
}
Ok(())
}
pub trait AwaitableCondition {
fn was_successful(&self) -> bool;
}
impl AwaitableCondition for bool {
fn was_successful(&self) -> bool {
*self
}
}
impl AwaitableCondition for Option<bool> {
fn was_successful(&self) -> bool {
self.is_some_and(|inner| inner)
}
}
impl AwaitableCondition for Result<bool> {
fn was_successful(&self) -> bool {
self.as_ref().is_ok_and(|inner| *inner)
}
}
static NODE_MUTEX: Mutex<()> = Mutex::new(());
const DEFAULT_FILTERS: &[&str] = &[
"warn",
"autopilot=debug",
"cow_amm=debug",
"driver=debug",
"e2e=debug",
"orderbook=debug",
"shared=debug",
"solver=debug",
"solvers=debug",
"orderbook::api::request_summary=off",
"simulator=debug",
];
fn with_default_filters<T>(custom_filters: impl IntoIterator<Item = T>) -> Vec<String>
where
T: AsRef<str>,
{
let mut default_filters: Vec<_> = DEFAULT_FILTERS.iter().map(|s| s.to_string()).collect();
default_filters.extend(custom_filters.into_iter().map(|f| f.as_ref().to_owned()));
default_filters
}
/// *Testing* function that takes a closure and runs it on a local testing node
/// and database. Before each test, it creates a snapshot of the current state
/// of the chain. The saved state is restored at the end of the test.
/// The database is cleaned at the end of the test.
///
/// This function also initializes tracing and sets panic hook.
///
/// Note that tests calling with this function will not be run simultaneously.
pub async fn run_test<F, Fut>(f: F)
where
F: FnOnce(Web3) -> Fut,
Fut: Future<Output = ()>,
{
run(f, empty::<&str>(), None).await
}
pub async fn run_test_with_extra_filters<F, Fut, T>(
f: F,
extra_filters: impl IntoIterator<Item = T>,
) where
F: FnOnce(Web3) -> Fut,
Fut: Future<Output = ()>,
T: AsRef<str>,
{
run(f, extra_filters, None).await
}
pub async fn run_forked_test<F, Fut>(f: F, fork_url: String)
where
F: FnOnce(Web3) -> Fut,
Fut: Future<Output = ()>,
{
run(f, empty::<&str>(), Some((fork_url, None))).await
}
pub async fn run_forked_test_with_block_number<F, Fut>(f: F, fork_url: String, block_number: u64)
where
F: FnOnce(Web3) -> Fut,
Fut: Future<Output = ()>,
{
run(f, empty::<&str>(), Some((fork_url, Some(block_number)))).await
}
pub async fn run_forked_test_with_extra_filters<F, Fut, T>(
f: F,
fork_url: String,
extra_filters: impl IntoIterator<Item = T>,
) where
F: FnOnce(Web3) -> Fut,
Fut: Future<Output = ()>,
T: AsRef<str>,
{
run(f, extra_filters, Some((fork_url, None))).await
}
pub async fn run_forked_test_with_extra_filters_and_block_number<F, Fut, T>(
f: F,
fork_url: String,
block_number: u64,
extra_filters: impl IntoIterator<Item = T>,
) where
F: FnOnce(Web3) -> Fut,
Fut: Future<Output = ()>,
T: AsRef<str>,
{
run(f, extra_filters, Some((fork_url, Some(block_number)))).await
}
async fn run<F, Fut, T>(
f: F,
filters: impl IntoIterator<Item = T>,
fork: Option<(String, Option<u64>)>,
) where
F: FnOnce(Web3) -> Fut,
Fut: Future<Output = ()>,
T: AsRef<str>,
{
let obs_config = observe::Config::new(
&with_default_filters(filters).join(","),
Some(tracing::Level::ERROR),
false,
None,
);
observe::tracing::init::initialize_reentrant(&obs_config);
observe::panic_hook::install();
services::ensure_e2e_readonly_user().await;
// The mutex guarantees that no more than a test at a time is running on
// the testing node.
// Note that the mutex is expected to become poisoned if a test panics. This
// is not relevant for us as we are not interested in the data stored in
// it but rather in the locked state.
let _lock = NODE_MUTEX.lock();
let node = match fork {
Some((fork, block_number)) => Node::forked(fork, block_number).await,
None => Node::new().await,
};
let node = Arc::new(Mutex::new(Some(node)));
let node_panic_handle = node.clone();
observe::panic_hook::prepend_panic_handler(Box::new(move |_| {
// Drop node in panic handler because `.catch_unwind()` does not catch all
// panics
let _ = node_panic_handle.lock().unwrap().take();
}));
let web3 = Web3::new_from_url(NODE_HOST);
let phrase = "test test test test test test test test test test test junk";
let signers = (0..10).map(|i| {
MnemonicBuilder::<English>::default()
.phrase(phrase)
.index(i)
.unwrap()
.build()
.unwrap()
});
for signer in signers {
web3.wallet.register_signer(signer);
}
services::clear_database().await;
// Hack: the closure may actually be unwind unsafe; moreover, `catch_unwind`
// does not catch some types of panics. In this cases, the state of the node
// is not restored. This is not considered an issue since this function
// is supposed to be used in a test environment.
let result = AssertUnwindSafe(f(web3.clone())).catch_unwind().await;
let node = node.lock().unwrap().take();
if let Some(mut node) = node {
node.kill().await;
}
services::clear_database().await;
if let Err(err) = result {
panic::resume_unwind(err);
}
}
#[macro_export]
macro_rules! assert_approximately_eq {
($executed_value:expr_2021, $expected_value:expr_2021) => {{
let lower = $expected_value * ::alloy::primitives::U256::from(99999999999u128)
/ ::alloy::primitives::U256::from(100000000000u128);
let upper = ($expected_value * ::alloy::primitives::U256::from(100000000001u128)
/ ::alloy::primitives::U256::from(100000000000u128))
+ ::alloy::primitives::U256::ONE;
assert!(
$executed_value >= lower && $executed_value <= upper,
"Expected: ~{}, got: {}, ({lower}, {upper})",
$expected_value,
$executed_value
);
}};
}