Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit eac4e73

Browse files
committed
Improve cetus swaps
1 parent f90e365 commit eac4e73

5 files changed

Lines changed: 419 additions & 118 deletions

File tree

crates/gem_sui/src/tx_builder/prefetch.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,21 @@ impl PrefetchedTransactionData {
1616
client: &SuiClient<C>,
1717
sender: &str,
1818
input_coin_type: &str,
19-
output_coin_type: &str,
19+
output_coin_type: Option<&str>,
2020
object_ids: Vec<String>,
2121
pinned: &HashMap<String, u64>,
2222
gas_budget: u64,
2323
) -> Result<Self, SuiError> {
24+
let output_coins_fut = async {
25+
match output_coin_type {
26+
Some(coin_type) => get_user_coins(client, sender, coin_type).await,
27+
None => Ok(Vec::new()),
28+
}
29+
};
2430
let (transaction, input_coins, output_coins, resolver) = try_join!(
2531
TransactionBuilderInput::prefetch(client, sender, gas_budget),
2632
get_user_coins(client, sender, input_coin_type),
27-
get_user_coins(client, sender, output_coin_type),
33+
output_coins_fut,
2834
ObjectResolver::prefetch(client, object_ids, pinned),
2935
)?;
3036

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,130 @@
11
use super::model::DiscoveredPool;
22
use std::{
3-
collections::HashMap,
3+
collections::{HashMap, HashSet},
44
sync::{Arc, Mutex},
55
};
66

7+
#[derive(Debug, Clone, Default)]
8+
struct PoolDiscovery {
9+
pools: Vec<DiscoveredPool>,
10+
explored_ticks: Vec<u32>,
11+
}
12+
713
#[derive(Debug, Clone, Default)]
814
pub(super) struct PoolCache {
9-
inner: Arc<Mutex<HashMap<(String, String), Vec<DiscoveredPool>>>>,
15+
pools: Arc<Mutex<HashMap<(String, String), PoolDiscovery>>>,
16+
routes: Arc<Mutex<HashMap<(String, String), Vec<DiscoveredPool>>>>,
1017
}
1118

1219
impl PoolCache {
13-
pub fn get(&self, from: &str, to: &str) -> Option<Vec<DiscoveredPool>> {
14-
let cache = self.inner.lock().ok()?;
15-
cache.get(&Self::key(from, to)).cloned()
20+
pub fn get(&self, from: &str, to: &str) -> Option<(Vec<DiscoveredPool>, Vec<u32>)> {
21+
let cache = self.pools.lock().ok()?;
22+
cache.get(&Self::pool_key(from, to)).map(|d| (d.pools.clone(), d.explored_ticks.clone()))
23+
}
24+
25+
pub fn put(&self, from: &str, to: &str, pools: &[DiscoveredPool], ticks: &[u32]) {
26+
if pools.is_empty() && ticks.is_empty() {
27+
return;
28+
}
29+
if let Ok(mut cache) = self.pools.lock() {
30+
let entry = cache.entry(Self::pool_key(from, to)).or_default();
31+
let mut seen: HashSet<String> = entry.pools.iter().map(|p| p.pool_id.clone()).collect();
32+
for pool in pools {
33+
if seen.insert(pool.pool_id.clone()) {
34+
entry.pools.push(pool.clone());
35+
}
36+
}
37+
for tick in ticks {
38+
if !entry.explored_ticks.contains(tick) {
39+
entry.explored_ticks.push(*tick);
40+
}
41+
}
42+
}
1643
}
1744

18-
pub fn put(&self, from: &str, to: &str, pools: &[DiscoveredPool]) {
19-
if pools.is_empty() {
45+
pub fn get_route(&self, from: &str, to: &str) -> Option<Vec<DiscoveredPool>> {
46+
let cache = self.routes.lock().ok()?;
47+
cache.get(&Self::route_key(from, to)).cloned()
48+
}
49+
50+
pub fn put_route(&self, from: &str, to: &str, route: &[DiscoveredPool]) {
51+
if route.is_empty() {
2052
return;
2153
}
22-
if let Ok(mut cache) = self.inner.lock() {
23-
cache.insert(Self::key(from, to), pools.to_vec());
54+
if let Ok(mut cache) = self.routes.lock() {
55+
cache.insert(Self::route_key(from, to), route.to_vec());
2456
}
2557
}
2658

27-
fn key(from: &str, to: &str) -> (String, String) {
59+
fn pool_key(from: &str, to: &str) -> (String, String) {
2860
let (a, b) = if from <= to { (from, to) } else { (to, from) };
2961
(a.to_string(), b.to_string())
3062
}
63+
64+
fn route_key(from: &str, to: &str) -> (String, String) {
65+
(from.to_string(), to.to_string())
66+
}
3167
}
3268

3369
#[cfg(test)]
3470
mod tests {
3571
use super::*;
3672

73+
fn pool(id: &str) -> DiscoveredPool {
74+
DiscoveredPool {
75+
pool_id: id.into(),
76+
pool_init_version: 1,
77+
coin_a: "0xa".into(),
78+
coin_b: "0xb".into(),
79+
}
80+
}
81+
3782
#[test]
38-
fn test_key_is_direction_insensitive() {
39-
assert_eq!(PoolCache::key("0xa", "0xb"), PoolCache::key("0xb", "0xa"));
83+
fn test_pool_key_is_direction_insensitive() {
84+
assert_eq!(PoolCache::pool_key("0xa", "0xb"), PoolCache::pool_key("0xb", "0xa"));
85+
}
86+
87+
#[test]
88+
fn test_route_key_is_direction_sensitive() {
89+
assert_ne!(PoolCache::route_key("0xa", "0xb"), PoolCache::route_key("0xb", "0xa"));
90+
}
91+
92+
#[test]
93+
fn test_pool_cache_merges_pools_and_ticks_across_passes() {
94+
let cache = PoolCache::default();
95+
cache.put("0xa", "0xb", &[pool("0x1")], &[60, 200]);
96+
cache.put("0xa", "0xb", &[pool("0x2"), pool("0x1")], &[10, 2]);
97+
98+
let (pools, ticks) = cache.get("0xa", "0xb").unwrap();
99+
assert_eq!(pools.len(), 2);
100+
assert_eq!(pools[0].pool_id, "0x1");
101+
assert_eq!(pools[1].pool_id, "0x2");
102+
assert_eq!(ticks, vec![60, 200, 10, 2]);
103+
}
104+
105+
#[test]
106+
fn test_pool_cache_tracks_explored_ticks_when_no_pools_found() {
107+
let cache = PoolCache::default();
108+
cache.put("0xa", "0xb", &[], &[60, 200]);
109+
let (pools, ticks) = cache.get("0xa", "0xb").unwrap();
110+
assert!(pools.is_empty());
111+
assert_eq!(ticks, vec![60, 200]);
112+
}
113+
114+
#[test]
115+
fn test_route_roundtrip() {
116+
let cache = PoolCache::default();
117+
let route = vec![pool("0x1"), pool("0x2")];
118+
cache.put_route("USDC", "WAL", &route);
119+
let fetched = cache.get_route("USDC", "WAL").expect("hit");
120+
assert_eq!(fetched.len(), 2);
121+
assert!(cache.get_route("WAL", "USDC").is_none(), "direction-sensitive");
40122
}
41123

42124
#[test]
43-
fn test_put_skips_empty_to_avoid_false_negative_cache() {
125+
fn test_put_route_skips_empty() {
44126
let cache = PoolCache::default();
45-
cache.put("0xa", "0xb", &[]);
46-
assert!(cache.get("0xa", "0xb").is_none());
127+
cache.put_route("USDC", "WAL", &[]);
128+
assert!(cache.get_route("USDC", "WAL").is_none());
47129
}
48130
}

0 commit comments

Comments
 (0)