Skip to content

Commit 9de851c

Browse files
committed
Refactor Route and sell=buy validation control
1 parent 157f647 commit 9de851c

File tree

7 files changed

+435
-84
lines changed

7 files changed

+435
-84
lines changed

crates/driver/src/domain/quote.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ use {
1717
},
1818
chrono::Utc,
1919
ethrpc::alloy::conversions::IntoLegacy,
20-
std::{
21-
collections::{HashMap, HashSet},
22-
iter,
23-
},
20+
std::collections::{HashMap, HashSet},
2421
};
2522

2623
/// A quote describing the expected outcome of an order.
@@ -92,7 +89,7 @@ impl Order {
9289
tokens: &infra::tokens::Fetcher,
9390
) -> Result<Quote, Error> {
9491
let liquidity = match (solver.liquidity(), self.token_liquidity()) {
95-
(solver::Liquidity::Fetch, Some(pairs)) => {
92+
(solver::Liquidity::Fetch, pairs) if !pairs.is_empty() => {
9693
liquidity
9794
.fetch(&pairs, infra::liquidity::AtBlock::Recent)
9895
.await
@@ -226,10 +223,11 @@ impl Order {
226223
}
227224

228225
/// Returns the token pairs to fetch liquidity for.
229-
fn token_liquidity(&self) -> Option<HashSet<liquidity::TokenPair>> {
226+
fn token_liquidity(&self) -> HashSet<liquidity::TokenPair> {
230227
liquidity::TokenPair::try_new(self.tokens.sell(), self.tokens.buy())
231-
.map(|pair| iter::once(pair).collect())
232228
.ok()
229+
.into_iter()
230+
.collect()
233231
}
234232
}
235233

crates/e2e/tests/e2e/place_order_with_quote.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ async fn place_order_with_quote_same_token_pair_error(web3: Web3) {
168168
},
169169
..Default::default()
170170
};
171-
assert!(services.submit_quote(&quote_request).await.is_err());
171+
assert!(
172+
matches!(services.submit_quote(&quote_request).await, Err((reqwest::StatusCode::BAD_REQUEST, response)) if response.contains("SameBuyAndSellToken"))
173+
);
172174
}
173175

174176
async fn place_order_with_quote_same_token_pair(web3: Web3) {

crates/e2e/tests/e2e/submission.rs

Lines changed: 235 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,28 @@ use {
1919

2020
#[tokio::test]
2121
#[ignore]
22-
async fn local_node_test() {
22+
async fn local_node_on_expiry() {
2323
run_test(test_cancel_on_expiry).await;
2424
}
2525

26+
#[tokio::test]
27+
#[ignore]
28+
async fn local_node_execute_same_sell_and_buy_token() {
29+
run_test(test_execute_same_sell_and_buy_token).await;
30+
}
31+
32+
#[tokio::test]
33+
#[ignore]
34+
async fn local_node_submit_same_sell_and_buy_token_order_without_quote() {
35+
run_test(test_submit_same_sell_and_buy_token_order_without_quote).await;
36+
}
37+
38+
#[tokio::test]
39+
#[ignore]
40+
async fn local_node_submit_same_sell_and_buy_token_order_without_quote_fail() {
41+
run_test(test_submit_same_sell_and_buy_token_order_without_quote_fail).await;
42+
}
43+
2644
async fn test_cancel_on_expiry(web3: Web3) {
2745
let mut onchain = OnchainComponents::deploy(web3.clone()).await;
2846

@@ -123,6 +141,222 @@ async fn test_cancel_on_expiry(web3: Web3) {
123141
assert_eq!(tx.to, Some(solver.account().address()))
124142
}
125143

144+
async fn test_submit_same_sell_and_buy_token_order_without_quote(web3: Web3) {
145+
let mut onchain = OnchainComponents::deploy(web3.clone()).await;
146+
147+
let [solver] = onchain.make_solvers(eth(10)).await;
148+
let [trader] = onchain.make_accounts(eth(10)).await;
149+
let [token] = onchain
150+
.deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000))
151+
.await;
152+
153+
token.mint(trader.address(), eth(10)).await;
154+
155+
token
156+
.approve(onchain.contracts().allowance.into_alloy(), eth(10))
157+
.from(trader.address())
158+
.send_and_watch()
159+
.await
160+
.unwrap();
161+
162+
tracing::info!("Starting services.");
163+
let services = Services::new(&onchain).await;
164+
services
165+
.start_protocol_with_args(
166+
ExtraServiceArgs {
167+
api: vec!["--allow-same-sell-and-buy-token=true".to_string()],
168+
..Default::default()
169+
},
170+
solver.clone(),
171+
)
172+
.await;
173+
174+
// Disable auto-mine so we don't accidentally mine a settlement
175+
web3.api::<TestNodeApi<_>>()
176+
.set_automine_enabled(false)
177+
.await
178+
.expect("Must be able to disable automine");
179+
180+
tracing::info!("Placing order");
181+
let order = OrderCreation {
182+
sell_token: token.address().into_legacy(),
183+
sell_amount: to_wei(1),
184+
buy_token: token.address().into_legacy(),
185+
buy_amount: to_wei(1),
186+
valid_to: model::time::now_in_epoch_seconds() + 300,
187+
kind: OrderKind::Sell,
188+
..Default::default()
189+
}
190+
.sign(
191+
EcdsaSigningScheme::Eip712,
192+
&onchain.contracts().domain_separator,
193+
SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()),
194+
);
195+
services.create_order(&order).await.unwrap();
196+
}
197+
198+
async fn test_submit_same_sell_and_buy_token_order_without_quote_fail(web3: Web3) {
199+
let mut onchain = OnchainComponents::deploy(web3.clone()).await;
200+
201+
let [solver] = onchain.make_solvers(eth(10)).await;
202+
let [trader] = onchain.make_accounts(eth(10)).await;
203+
let [token] = onchain
204+
.deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000))
205+
.await;
206+
207+
token.mint(trader.address(), eth(10)).await;
208+
209+
token
210+
.approve(onchain.contracts().allowance.into_alloy(), eth(10))
211+
.from(trader.address())
212+
.send_and_watch()
213+
.await
214+
.unwrap();
215+
216+
tracing::info!("Starting services.");
217+
let services = Services::new(&onchain).await;
218+
services.start_protocol(solver.clone()).await;
219+
220+
// Disable auto-mine so we don't accidentally mine a settlement
221+
web3.api::<TestNodeApi<_>>()
222+
.set_automine_enabled(false)
223+
.await
224+
.expect("Must be able to disable automine");
225+
226+
tracing::info!("Placing order");
227+
let order = OrderCreation {
228+
sell_token: token.address().into_legacy(),
229+
sell_amount: to_wei(1),
230+
buy_token: token.address().into_legacy(),
231+
buy_amount: to_wei(1),
232+
valid_to: model::time::now_in_epoch_seconds() + 300,
233+
kind: OrderKind::Sell,
234+
..Default::default()
235+
}
236+
.sign(
237+
EcdsaSigningScheme::Eip712,
238+
&onchain.contracts().domain_separator,
239+
SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()),
240+
);
241+
// services.create_order(&order).await.unwrap();
242+
assert!(
243+
matches!(services.create_order(&order).await, Err((reqwest::StatusCode::BAD_REQUEST, response)) if response.contains("SameBuyAndSellToken"))
244+
);
245+
}
246+
247+
async fn test_execute_same_sell_and_buy_token(web3: Web3) {
248+
let mut onchain = OnchainComponents::deploy(web3.clone()).await;
249+
250+
let [solver] = onchain.make_solvers(eth(10)).await;
251+
let [trader] = onchain.make_accounts(eth(10)).await;
252+
let [token] = onchain
253+
.deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000))
254+
.await;
255+
256+
token.mint(trader.address(), eth(10)).await;
257+
258+
token
259+
.approve(onchain.contracts().allowance.into_alloy(), eth(10))
260+
.from(trader.address())
261+
.send_and_watch()
262+
.await
263+
.unwrap();
264+
265+
tracing::info!("Starting services.");
266+
let services = Services::new(&onchain).await;
267+
services
268+
.start_protocol_with_args(
269+
ExtraServiceArgs {
270+
api: vec!["--allow-same-sell-and-buy-token=true".to_string()],
271+
..Default::default()
272+
},
273+
solver.clone(),
274+
)
275+
.await;
276+
277+
// Disable auto-mine so we don't accidentally mine a settlement
278+
web3.api::<TestNodeApi<_>>()
279+
.set_automine_enabled(false)
280+
.await
281+
.expect("Must be able to disable automine");
282+
283+
tracing::info!("Placing order");
284+
let initial_balance = token.balanceOf(trader.address()).call().await.unwrap();
285+
assert_eq!(initial_balance, eth(10));
286+
287+
let order_sell_amount = to_wei(2);
288+
let order_buy_amount = to_wei(1);
289+
let order = OrderCreation {
290+
sell_token: token.address().into_legacy(),
291+
sell_amount: order_sell_amount,
292+
buy_token: token.address().into_legacy(),
293+
buy_amount: order_buy_amount,
294+
valid_to: model::time::now_in_epoch_seconds() + 300,
295+
kind: OrderKind::Sell,
296+
..Default::default()
297+
}
298+
.sign(
299+
EcdsaSigningScheme::Eip712,
300+
&onchain.contracts().domain_separator,
301+
SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()),
302+
);
303+
assert!(services.create_order(&order).await.is_ok());
304+
305+
// Start tracking confirmed blocks so we can find the transaction later
306+
let block_stream = web3
307+
.eth_filter()
308+
.create_blocks_filter()
309+
.await
310+
.expect("must be able to create blocks filter")
311+
.stream(Duration::from_millis(50));
312+
313+
tracing::info!("Waiting for trade.");
314+
onchain.mint_block().await;
315+
316+
// Wait for settlement tx to appear in txpool
317+
wait_for_condition(Duration::from_secs(2), || async {
318+
get_pending_tx(solver.account().address(), &web3)
319+
.await
320+
.is_some()
321+
})
322+
.await
323+
.unwrap();
324+
325+
// Continue mining to confirm the settlement
326+
web3.api::<TestNodeApi<_>>()
327+
.set_automine_enabled(true)
328+
.await
329+
.expect("Must be able to enable automine");
330+
331+
// Wait for the settlement to be confirmed on chain
332+
let tx = tokio::time::timeout(
333+
Duration::from_secs(5),
334+
get_confirmed_transaction(solver.account().address(), &web3, block_stream),
335+
)
336+
.await
337+
.unwrap();
338+
339+
// Verify the transaction is to the settlement contract (not a cancellation)
340+
assert_eq!(
341+
tx.to,
342+
Some(onchain.contracts().gp_settlement.address().into_legacy())
343+
);
344+
345+
// Verify that the balance changed (settlement happened on chain)
346+
let trade_happened = || async {
347+
let balance = token.balanceOf(trader.address()).call().await.unwrap();
348+
// Balance should change due to fees even if sell token == buy token
349+
balance != initial_balance
350+
};
351+
wait_for_condition(TIMEOUT, trade_happened).await.unwrap();
352+
353+
let final_balance = token.balanceOf(trader.address()).call().await.unwrap();
354+
tracing::info!(?initial_balance, ?final_balance, "Trade completed");
355+
356+
// Verify that the balance changed (settlement happened on chain)
357+
assert_ne!(final_balance, initial_balance);
358+
}
359+
126360
async fn get_pending_tx(account: H160, web3: &Web3) -> Option<web3::types::Transaction> {
127361
let txpool = web3
128362
.txpool()

crates/orderbook/src/run.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ pub async fn run(args: Arguments) {
403403
.ok();
404404
let order_validator = Arc::new(
405405
OrderValidator::new(
406+
native_token,
406407
Arc::new(order_validation::banned::Users::new(
407408
chainalysis_oracle,
408409
args.banned_users,
@@ -421,9 +422,7 @@ pub async fn run(args: Arguments) {
421422
app_data_validator.clone(),
422423
args.max_gas_per_order,
423424
)
424-
.with_sell_and_buy_validation(
425-
(!args.allow_same_sell_and_buy_token).then(|| native_token.clone()),
426-
),
425+
.with_allow_same_sell_and_buy_token(args.allow_same_sell_and_buy_token),
427426
);
428427
let ipfs = args
429428
.ipfs_gateway

0 commit comments

Comments
 (0)