1use crate::rpc::{TempoDexApiServer, dex::orders::OrdersResponse};
2use alloy_eips::{BlockId, BlockNumberOrTag};
3use alloy_primitives::{Address, B256, Sealable};
4use jsonrpsee::core::RpcResult;
5use reth_ethereum::evm::revm::database::StateProviderDatabase;
6use reth_evm::{EvmInternals, revm::database::CacheDB};
7use reth_node_api::{ConfigureEvm, NodePrimitives};
8use reth_provider::{BlockReaderIdExt, StateProviderFactory};
9use reth_rpc_eth_api::{RpcNodeCore, helpers::SpawnBlocking};
10use reth_rpc_eth_types::{EthApiError, error::FromEthApiError};
11use tempo_alloy::rpc::pagination::PaginationParams;
12use tempo_evm::TempoEvmConfig;
13use tempo_precompiles::{
14 stablecoin_exchange::{
15 Order as PrecompileOrder, Orderbook as PrecompileOrderbook, StablecoinExchange, TickLevel,
16 orderbook::{OrderbookHandler, compute_book_key},
17 },
18 storage::{ContractStorage, Handler, StorageCtx, evm::EvmPrecompileStorageProvider},
19};
20use tempo_primitives::TempoHeader;
21
22pub mod api;
23
24pub mod orders;
25pub use orders::{Order, OrdersFilters, Tick};
26
27mod books;
28pub use books::{Orderbook, OrderbooksFilter, OrderbooksResponse};
29
30mod error;
31pub use error::DexApiError;
32
33const DEFAULT_LIMIT: usize = 10;
35
36const MAX_LIMIT: usize = 100;
38
39#[derive(Debug, Clone, Default)]
41pub struct TempoDex<EthApi> {
42 eth_api: EthApi,
43}
44
45impl<EthApi> TempoDex<EthApi> {
46 pub const fn new(eth_api: EthApi) -> Self {
48 Self { eth_api }
49 }
50}
51
52impl<
53 EthApi: RpcNodeCore<Evm = TempoEvmConfig, Primitives: NodePrimitives<BlockHeader = TempoHeader>>,
54> TempoDex<EthApi>
55{
56 fn orders(
58 &self,
59 params: PaginationParams<OrdersFilters>,
60 ) -> Result<OrdersResponse, DexApiError> {
61 let response = self.with_storage_at_block(BlockNumberOrTag::Latest.into(), || {
62 let exchange = StablecoinExchange::new();
63 let exchange_address = exchange.address();
64
65 let base_token = params.filters.as_ref().and_then(|f| f.base_token);
67 let quote_token = params.filters.as_ref().and_then(|f| f.quote_token);
68 let book_keys = get_book_keys_for_iteration(&exchange, base_token, quote_token)?;
69
70 let is_bid = params
71 .filters
72 .as_ref()
73 .is_none_or(|f| f.is_bid.unwrap_or(false));
74
75 let cursor = params
76 .cursor
77 .map(|cursor| parse_order_cursor(&cursor))
78 .transpose()?;
79
80 let limit = params
81 .limit
82 .map(|l| l.min(MAX_LIMIT))
83 .unwrap_or(DEFAULT_LIMIT);
84
85 let mut all_orders: Vec<Order> = Vec::new();
86 let mut next_cursor = None;
87
88 for book_key in book_keys {
90 let orderbook = exchange.books(book_key)?;
91
92 if !orderbook.matches_tokens(base_token, quote_token) {
94 continue;
95 }
96
97 let starting_order = if all_orders.is_empty() {
98 cursor } else {
100 None
101 };
102
103 let book_iterator = BookIterator::new(
104 &orderbook,
105 exchange_address,
106 is_bid,
107 starting_order,
108 params.filters.clone(),
109 );
110
111 for order_result in book_iterator {
113 let order = order_result?;
114 let rpc_order = self.to_rpc_order(order, &orderbook);
115 all_orders.push(rpc_order);
116
117 if all_orders.len() > limit {
120 let last = &all_orders[limit];
122 next_cursor = Some(format!("0x{:x}", last.order_id));
123 break;
124 }
125 }
126
127 if all_orders.len() > limit {
129 break;
130 }
131 }
132
133 all_orders.truncate(limit);
135 let orders = all_orders;
136
137 let response = OrdersResponse {
138 next_cursor,
139 orders,
140 };
141 Ok(response)
142 })?;
143 Ok(response)
144 }
145
146 fn orderbooks(
148 &self,
149 params: PaginationParams<OrderbooksFilter>,
150 ) -> Result<OrderbooksResponse, DexApiError> {
151 let (items, next_cursor) = self.apply_pagination_to_orderbooks(params)?;
153
154 let orderbooks = items
156 .into_iter()
157 .map(|book| self.to_rpc_orderbook(&book))
158 .collect();
159
160 Ok(OrderbooksResponse {
162 next_cursor,
163 orderbooks,
164 })
165 }
166
167 fn with_storage_at_block<F, R>(&self, at: BlockId, f: F) -> Result<R, DexApiError>
170 where
171 F: FnOnce() -> Result<R, DexApiError>,
172 {
173 let provider = self.eth_api.provider();
175 let header = provider
176 .header_by_id(at)
177 .map_err(|e| DexApiError::Provider(Box::new(e)))?
178 .ok_or(DexApiError::HeaderNotFound(at))?;
179
180 let block_hash = header.hash_slow();
181 let state_provider = provider
182 .state_by_block_hash(block_hash)
183 .map_err(|e| DexApiError::Provider(Box::new(e)))?;
184
185 let db = CacheDB::new(StateProviderDatabase::new(state_provider));
187 let mut evm = self
188 .eth_api
189 .evm_config()
190 .evm_for_block(db, &header)
191 .map_err(|e| DexApiError::CreateEvm(Box::new(e)))?;
192
193 let ctx = evm.ctx_mut();
194 let internals = EvmInternals::new(&mut ctx.journaled_state, &ctx.block);
195 let mut storage = EvmPrecompileStorageProvider::new_max_gas(internals, &ctx.cfg);
196
197 StorageCtx::enter(&mut storage, f)
198 }
199
200 fn with_exchange_at_block<F, R>(&self, at: BlockId, f: F) -> Result<R, DexApiError>
203 where
204 F: FnOnce(&mut StablecoinExchange) -> Result<R, DexApiError>,
205 {
206 self.with_storage_at_block(at, || {
207 let mut exchange = StablecoinExchange::new();
208 f(&mut exchange)
209 })
210 }
211
212 pub fn apply_pagination_to_orderbooks(
216 &self,
217 params: PaginationParams<OrderbooksFilter>,
218 ) -> Result<(Vec<PrecompileOrderbook>, Option<String>), DexApiError> {
219 self.with_exchange_at_block(BlockNumberOrTag::Latest.into(), |exchange| {
220 let base_token = params.filters.as_ref().and_then(|f| f.base_token);
221 let quote_token = params.filters.as_ref().and_then(|f| f.quote_token);
222 let keys = get_book_keys_for_iteration(exchange, base_token, quote_token)?;
223
224 let start_idx = if let Some(ref cursor_str) = params.cursor {
226 let cursor_key = parse_orderbook_cursor(cursor_str)?;
227
228 keys.iter()
229 .position(|k| *k == cursor_key)
230 .ok_or(DexApiError::OrderbookCursorNotFound(cursor_key))?
231 } else {
232 0
233 };
234
235 let mut orderbooks = Vec::new();
237 let limit = params
238 .limit
239 .map(|l| l.min(MAX_LIMIT))
240 .unwrap_or(DEFAULT_LIMIT);
241
242 let mut iter = keys.into_iter().skip(start_idx);
243
244 for key in iter.by_ref() {
246 let book = exchange.books(key).map_err(DexApiError::Precompile)?;
247
248 if let Some(ref filter) = params.filters
250 && !orderbook_matches_filter(&book, filter)
251 {
252 continue;
253 }
254
255 orderbooks.push(book);
256
257 if orderbooks.len() >= limit {
259 break;
260 }
261 }
262
263 let next_cursor = iter.next().map(|next_book| format!("0x{next_book}"));
264
265 Ok((orderbooks, next_cursor))
266 })
267 }
268
269 fn to_rpc_order(&self, order: PrecompileOrder, book: &PrecompileOrderbook) -> Order {
273 let PrecompileOrder {
274 order_id,
275 maker,
276 book_key: _,
277 is_bid,
278 tick,
279 amount,
280 remaining,
281 prev,
282 next,
283 is_flip,
284 flip_tick,
285 } = order;
286
287 Order {
288 amount,
289 base_token: book.base,
290 flip_tick,
291 is_bid,
292 is_flip,
293 maker,
294 next,
295 order_id,
296 quote_token: book.quote,
297 prev,
298 remaining,
299 tick,
300 }
301 }
302
303 fn to_rpc_orderbook(&self, book: &PrecompileOrderbook) -> Orderbook {
309 let book_key = compute_book_key(book.base, book.quote);
310 let spread = if book.best_ask_tick != i16::MAX && book.best_bid_tick != i16::MIN {
311 book.best_ask_tick - book.best_bid_tick
312 } else {
313 0
314 };
315
316 Orderbook {
317 base_token: book.base,
318 quote_token: book.quote,
319 book_key,
320 best_ask_tick: book.best_ask_tick,
321 best_bid_tick: book.best_bid_tick,
322 spread,
323 }
324 }
325
326 pub fn pick_orderbooks(
328 &self,
329 filter: OrderbooksFilter,
330 ) -> Result<Vec<PrecompileOrderbook>, DexApiError> {
331 if let (Some(base), Some(quote)) = (filter.base_token, filter.quote_token) {
333 return Ok(vec![self.get_orderbook(base, quote)?]);
334 }
335
336 let all_books = self.get_all_books()?;
338
339 Ok(all_books
340 .into_iter()
341 .filter(|book| orderbook_matches_filter(book, &filter))
342 .collect())
343 }
344
345 pub fn get_all_books(&self) -> Result<Vec<PrecompileOrderbook>, DexApiError> {
347 self.with_exchange_at_block(BlockNumberOrTag::Latest.into(), |exchange| {
348 let mut books = Vec::new();
349 for book_key in exchange.get_book_keys()? {
350 let book = exchange.books(book_key)?;
351 books.push(book);
352 }
353 Ok(books)
354 })
355 }
356
357 pub fn get_orderbook(
363 &self,
364 base: Address,
365 quote: Address,
366 ) -> Result<PrecompileOrderbook, DexApiError> {
367 self.with_exchange_at_block(BlockNumberOrTag::Latest.into(), |exchange| {
368 let book_key = compute_book_key(base, quote);
369 exchange.books(book_key).map_err(DexApiError::Precompile)
370 })
371 }
372}
373
374#[async_trait::async_trait]
375impl<
376 EthApi: RpcNodeCore<Evm = TempoEvmConfig, Primitives: NodePrimitives<BlockHeader = TempoHeader>>
377 + SpawnBlocking,
378> TempoDexApiServer for TempoDex<EthApi>
379{
380 async fn orders(&self, params: PaginationParams<OrdersFilters>) -> RpcResult<OrdersResponse> {
387 let this = self.clone();
388 self.eth_api
389 .spawn_blocking_io(move |_| {
390 Self::orders(&this, params)
391 .map_err(EthApiError::from)
392 .map_err(EthApi::Error::from_eth_err)
393 })
394 .await
395 .map_err(Into::into)
396 }
397
398 async fn orderbooks(
405 &self,
406 params: PaginationParams<OrderbooksFilter>,
407 ) -> RpcResult<OrderbooksResponse> {
408 let this = self.clone();
409 self.eth_api
410 .spawn_blocking_io(move |_| {
411 Self::orderbooks(&this, params)
412 .map_err(EthApiError::from)
413 .map_err(EthApi::Error::from_eth_err)
414 })
415 .await
416 .map_err(Into::into)
417 }
418}
419
420pub struct BookIterator<'b> {
422 filter: Option<OrdersFilters>,
424 bids: bool,
426 exchange_address: Address,
428 starting_order: Option<u128>,
430 order: Option<u128>,
432 orderbook: &'b PrecompileOrderbook,
434 handler: OrderbookHandler,
436 storage: StorageCtx,
438}
439
440impl<'b> ContractStorage for BookIterator<'b> {
441 fn address(&self) -> Address {
442 self.exchange_address
443 }
444 fn storage(&mut self) -> &mut StorageCtx {
445 &mut self.storage
446 }
447}
448
449impl<'b> BookIterator<'b> {
450 fn new(
452 orderbook: &'b PrecompileOrderbook,
453 exchange_address: Address,
454 bids: bool,
455 starting_order: Option<u128>,
456 filter: Option<OrdersFilters>,
457 ) -> Self {
458 let book_key = compute_book_key(orderbook.base, orderbook.quote);
459 Self {
460 filter,
461 bids,
462 exchange_address,
463 order: None,
464 starting_order,
465 orderbook,
466 handler: StablecoinExchange::new().books.at(book_key),
467 storage: StorageCtx::default(),
468 }
469 }
470
471 pub fn try_next(&mut self) -> Result<Option<PrecompileOrder>, DexApiError> {
474 match self.next() {
475 None => Ok(None),
476 Some(Ok(order)) => Ok(Some(order)),
477 Some(Err(e)) => Err(e),
478 }
479 }
480
481 pub fn get_order(&self, order_id: u128) -> Result<PrecompileOrder, DexApiError> {
483 StablecoinExchange::new()
484 .get_order(order_id)
485 .map_err(DexApiError::Precompile)
486 }
487
488 pub fn get_price_level(&self, tick: i16) -> Result<TickLevel, DexApiError> {
490 self.handler
491 .get_tick_level_handler(tick, self.bids)
492 .read()
493 .map_err(DexApiError::Precompile)
494 }
495
496 pub fn get_next_tick(&mut self, tick: i16) -> Option<i16> {
499 let (next_tick, more_ticks) = self.handler.next_initialized_tick(tick, self.bids);
500
501 if more_ticks { Some(next_tick) } else { None }
502 }
503
504 fn find_next_order(&mut self) -> Result<Option<u128>, DexApiError> {
507 if let Some(starting_order) = self.starting_order.take() {
509 return Ok(Some(starting_order));
510 }
511
512 let Some(current_id) = self.order else {
514 let tick = if self.bids {
515 self.orderbook.best_bid_tick
516 } else {
517 self.orderbook.best_ask_tick
518 };
519
520 let price_level = self.get_price_level(tick)?;
521
522 if price_level.is_empty() {
525 return Ok(None);
526 }
527
528 return Ok(Some(price_level.head));
529 };
530
531 let current_order = self.get_order(current_id)?;
532
533 if current_order.next() != 0 {
535 Ok(Some(current_order.next()))
536 } else {
537 let tick = current_order.tick();
538
539 let Some(next_tick) = self.get_next_tick(tick) else {
541 return Ok(None);
542 };
543
544 let price_level = self.get_price_level(next_tick)?;
546 if price_level.is_empty() {
547 return Ok(None);
548 }
549
550 Ok(Some(price_level.head))
552 }
553 }
554}
555
556impl<'b> Iterator for BookIterator<'b> {
557 type Item = Result<PrecompileOrder, DexApiError>;
558
559 fn next(&mut self) -> Option<Self::Item> {
560 loop {
562 let order_id = match self.find_next_order() {
563 Ok(Some(id)) => id,
564 Ok(None) => return None,
565 Err(e) => return Some(Err(e)),
566 };
567
568 let order = match self.get_order(order_id) {
569 Ok(o) => o,
570 Err(e) => return Some(Err(e)),
571 };
572
573 self.order = Some(order_id);
575
576 if let Some(ref filter) = self.filter {
578 if order_matches_filter(&order, filter) {
579 return Some(Ok(order));
580 }
581 } else {
582 return Some(Ok(order));
584 }
585 }
586 }
587}
588
589fn orderbook_matches_filter(book: &PrecompileOrderbook, filter: &OrderbooksFilter) -> bool {
591 if !book.matches_tokens(filter.base_token, filter.quote_token) {
593 return false;
594 }
595
596 if let Some(ref ask_range) = filter.best_ask_tick {
598 if book.best_ask_tick != i16::MAX && !ask_range.in_range(book.best_ask_tick) {
600 return false;
601 }
602 }
603
604 if let Some(ref bid_range) = filter.best_bid_tick {
606 if book.best_bid_tick != i16::MIN && !bid_range.in_range(book.best_bid_tick) {
608 return false;
609 }
610 }
611
612 if let Some(ref spread_range) = filter.spread {
614 if book.best_ask_tick != i16::MAX && book.best_bid_tick != i16::MIN {
616 let spread = book.best_ask_tick - book.best_bid_tick;
617 if !spread_range.in_range(spread) {
618 return false;
619 }
620 }
621 }
622
623 true
624}
625
626fn order_matches_filter(order: &PrecompileOrder, filter: &OrdersFilters) -> bool {
628 if filter.is_bid.is_some_and(|is_bid| is_bid != order.is_bid) {
633 return false;
634 }
635
636 if filter
638 .is_flip
639 .is_some_and(|is_flip| is_flip != order.is_flip)
640 {
641 return false;
642 }
643
644 if filter.maker.is_some_and(|maker| maker != order.maker) {
646 return false;
647 }
648
649 if filter
651 .remaining
652 .as_ref()
653 .is_some_and(|remaining_range| !remaining_range.in_range(order.remaining))
654 {
655 return false;
656 }
657
658 if filter
660 .tick
661 .as_ref()
662 .is_some_and(|tick_range| !tick_range.in_range(order.tick))
663 {
664 return false;
665 }
666
667 true
668}
669
670fn parse_order_cursor(cursor: &str) -> Result<u128, DexApiError> {
672 if let Some(hex_val) = cursor.strip_prefix("0x") {
673 u128::from_str_radix(hex_val, 16).map_err(Into::into)
674 } else {
675 Err(DexApiError::InvalidOrderCursor(cursor.to_string()))
676 }
677}
678
679fn parse_orderbook_cursor(cursor: &str) -> Result<B256, DexApiError> {
681 cursor
682 .parse::<B256>()
683 .map_err(|_| DexApiError::InvalidOrderbookCursor(cursor.to_string()))
684}
685
686fn get_book_keys_for_iteration(
689 exchange: &StablecoinExchange,
690 base_token: Option<Address>,
691 quote_token: Option<Address>,
692) -> Result<Vec<B256>, DexApiError> {
693 match (base_token, quote_token) {
694 (Some(base), Some(quote)) => Ok(vec![compute_book_key(base, quote)]),
695 _ => exchange.get_book_keys().map_err(DexApiError::Precompile),
696 }
697}