Skip to content

Commit 3067bd4

Browse files
authored
Remove CoW AMM indexer from driver (#3680)
# Description Currently, both autopilot and driver use the same CoW AMM indexer functionality in parallel to serve different purposes: autopilot needs to fill out surplus capturing jit order owners in the auction and driver uses it to fetch tradable tokens and then prepare CoW AMM template orders. Since autopilot already sends CoW AMM addresses within each auction, the only missing part is the CoW AMM helper SC address. It can be received by fetching the CoW AMM factory address and then using a config mapping to find the helper address. This should also reduce the RPC traffic. # Changes - [ ] Driver now contains only the mapping between AMM factory and helper SC. - [ ] When it receives a CoW AMM in the auction, it tries to fetch the AMM factory address by calling the `FACTORY` function. Since we can now completely deprecate legacy CoW AMMs, this logic is simplified. I also used a simple abi to create rust bindings for this interface. The fetched data is stored in the memory cache. - [ ] Using the factory address, it gets the helper SC instance based on the config. - [ ] Simplify the e2e tests config. - [ ] Adjusted a forked e2e test to start using the new factory address with some caveats(the test with its comments should be self-descriptive). ## How to test Adjusted forked e2e test.
1 parent 2bc45bc commit 3067bd4

File tree

17 files changed

+247
-163
lines changed

17 files changed

+247
-163
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"abi": [
3+
{
4+
"inputs": [],
5+
"name": "FACTORY",
6+
"outputs": [
7+
{
8+
"internalType": "address",
9+
"name": "",
10+
"type": "address"
11+
}
12+
],
13+
"stateMutability": "view",
14+
"type": "function"
15+
}
16+
]
17+
}

crates/contracts/src/alloy.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ crate::bindings!(
556556
}
557557
);
558558

559+
pub mod cow_amm {
560+
crate::bindings!(CowAmmFactoryGetter);
561+
}
562+
559563
pub mod support {
560564
// Support contracts used for trade and token simulations.
561565
crate::bindings!(AnyoneAuthenticator);

crates/cow-amm/src/amm.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ pub struct Amm {
2020
}
2121

2222
impl Amm {
23-
pub(crate) async fn new(
24-
address: Address,
25-
helper: &CowAmmLegacyHelper,
26-
) -> Result<Self, MethodError> {
23+
pub async fn new(address: Address, helper: &CowAmmLegacyHelper) -> Result<Self, MethodError> {
2724
let tradeable_tokens = helper.tokens(address).call().await?;
2825

2926
Ok(Self {

crates/driver/example.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ flashloan-router = "0x0000000000000000000000000000000000000000"
4444
factory = "0x86f3df416979136cb4fdea2c0886301b911c163b"
4545
# address of contract to help interfacing with the created CoW AMMs
4646
helper = "0x86f3df416979136cb4fdea2c0886301b911c163b"
47-
# at which block the driver should start indexing the factory (1 block before deployment)
48-
index-start = 20188649
4947

5048
[liquidity]
5149
base-tokens = [

crates/driver/src/domain/competition/pre_processing.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use {
33
crate::{
44
domain::{
55
competition::order::{SellTokenBalance, app_data::AppData},
6+
cow_amm,
67
eth,
78
liquidity,
89
},
@@ -58,6 +59,7 @@ pub struct Utilities {
5859
liquidity_fetcher: infra::liquidity::Fetcher,
5960
tokens: tokens::Fetcher,
6061
balance_fetcher: Arc<dyn BalanceFetching>,
62+
cow_amm_cache: cow_amm::Cache,
6163
}
6264

6365
impl std::fmt::Debug for Utilities {
@@ -130,6 +132,14 @@ impl DataAggregator {
130132
eth.balance_overrider(),
131133
);
132134

135+
let cow_amm_helper_by_factory = eth
136+
.contracts()
137+
.cow_amm_helper_by_factory()
138+
.iter()
139+
.map(|(factory, helper)| (factory.0.into(), helper.0.into()))
140+
.collect();
141+
let cow_amm_cache = cow_amm::Cache::new(eth.web3().clone(), cow_amm_helper_by_factory);
142+
133143
Self {
134144
utilities: Arc::new(Utilities {
135145
eth,
@@ -138,6 +148,7 @@ impl DataAggregator {
138148
liquidity_fetcher,
139149
tokens,
140150
balance_fetcher,
151+
cow_amm_cache,
141152
}),
142153
control: Mutex::new(ControlBlock {
143154
solve_request: Default::default(),
@@ -380,17 +391,19 @@ impl Utilities {
380391
let _timer = metrics::get().processing_stage_timer("cow_amm_orders");
381392
let _timer2 =
382393
observe::metrics::metrics().on_auction_overhead_start("driver", "cow_amm_orders");
383-
let cow_amms = self.eth.contracts().cow_amm_registry().amms().await;
394+
395+
let cow_amms = self
396+
.cow_amm_cache
397+
.get_or_create_amms(&auction.surplus_capturing_jit_order_owners)
398+
.await;
399+
384400
let domain_separator = self.eth.contracts().settlement_domain_separator();
385401
let domain_separator = model::DomainSeparator(domain_separator.0);
386402
let validator = self.signature_validator.as_ref();
403+
387404
let results: Vec<_> = futures::future::join_all(
388405
cow_amms
389406
.into_iter()
390-
// Only generate orders for cow amms the auction told us about.
391-
// Otherwise the solver would expect the order to get surplus but
392-
// the autopilot would actually not count it.
393-
.filter(|amm| auction.surplus_capturing_jit_order_owners.contains(&eth::Address(*amm.address())))
394407
// Only generate orders where the auction provided the required
395408
// reference prices. Otherwise there will be an error during the
396409
// surplus calculation which will also result in 0 surplus for
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use {
2+
crate::domain::eth,
3+
contracts::CowAmmLegacyHelper,
4+
cow_amm::Amm,
5+
ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy},
6+
itertools::{
7+
Either::{Left, Right},
8+
Itertools,
9+
},
10+
std::{
11+
collections::{HashMap, HashSet},
12+
sync::Arc,
13+
},
14+
tokio::sync::RwLock,
15+
};
16+
17+
/// Cache for CoW AMM data to avoid using the registry dependency.
18+
/// Maps AMM address to the corresponding Amm instance.
19+
pub struct Cache {
20+
inner: RwLock<HashMap<eth::Address, Arc<Amm>>>,
21+
web3: ethrpc::Web3,
22+
helper_by_factory: HashMap<eth::Address, CowAmmLegacyHelper>,
23+
}
24+
25+
impl Cache {
26+
pub fn new(web3: ethrpc::Web3, factory_mapping: HashMap<eth::Address, eth::Address>) -> Self {
27+
let helper_by_factory = factory_mapping
28+
.into_iter()
29+
.map(|(factory, helper)| (factory, CowAmmLegacyHelper::at(&web3, helper.0)))
30+
.collect();
31+
Self {
32+
inner: RwLock::new(HashMap::new()),
33+
web3,
34+
helper_by_factory,
35+
}
36+
}
37+
38+
/// Gets or creates AMM instances for the given surplus capturing JIT order
39+
/// owners.
40+
pub async fn get_or_create_amms(
41+
&self,
42+
surplus_capturing_jit_order_owners: &HashSet<eth::Address>,
43+
) -> Vec<Arc<Amm>> {
44+
let (mut cached_amms, missing_amms): (Vec<Arc<Amm>>, Vec<eth::Address>) = {
45+
let cache = self.inner.read().await;
46+
surplus_capturing_jit_order_owners
47+
.iter()
48+
.partition_map(|&address| match cache.get(&address) {
49+
Some(amm) => Left(amm.clone()),
50+
None => Right(address),
51+
})
52+
};
53+
54+
if missing_amms.is_empty() {
55+
return cached_amms;
56+
}
57+
58+
let fetch_futures = missing_amms.into_iter().map(|amm_address| async move {
59+
let factory_address = self
60+
.fetch_amm_factory_address(amm_address)
61+
.await
62+
.inspect_err(|err| {
63+
tracing::warn!(
64+
?err,
65+
amm_address = ?amm_address.0,
66+
"failed to fetch CoW AMM factory address"
67+
);
68+
})
69+
.ok()?;
70+
71+
let Some(helper) = self.helper_by_factory.get(&factory_address) else {
72+
tracing::warn!(
73+
factory_address = ?factory_address.0,
74+
amm_address = ?amm_address.0,
75+
"no helper contract configured for CoW AMM factory"
76+
);
77+
return None;
78+
};
79+
80+
match Amm::new(amm_address.0, helper).await {
81+
Ok(amm) => Some((amm_address, Arc::new(amm))),
82+
Err(err) => {
83+
let helper_address = helper.address().0;
84+
tracing::warn!(
85+
?err,
86+
amm_address = ?amm_address.0,
87+
?helper_address,
88+
"failed to create CoW AMM instance"
89+
);
90+
None
91+
}
92+
}
93+
});
94+
95+
let fetched_results = futures::future::join_all(fetch_futures).await;
96+
97+
// Update cache with newly fetched AMMs
98+
let newly_created_amms = {
99+
let mut cache = self.inner.write().await;
100+
fetched_results
101+
.into_iter()
102+
.flatten()
103+
.map(|(amm_address, amm)| {
104+
cache.insert(amm_address, amm.clone());
105+
amm
106+
})
107+
.collect::<Vec<_>>()
108+
};
109+
110+
// Combine cached and newly created AMMs
111+
cached_amms.extend(newly_created_amms);
112+
cached_amms
113+
}
114+
115+
/// Fetches the factory address for the given AMM by calling the
116+
/// `FACTORY` function.
117+
async fn fetch_amm_factory_address(
118+
&self,
119+
amm_address: eth::Address,
120+
) -> anyhow::Result<eth::Address> {
121+
let factory_getter =
122+
contracts::alloy::cow_amm::CowAmmFactoryGetter::CowAmmFactoryGetter::new(
123+
amm_address.0.into_alloy(),
124+
&self.web3.alloy,
125+
);
126+
Ok(factory_getter.FACTORY().call().await?.into_legacy().into())
127+
}
128+
}

crates/driver/src/domain/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod competition;
2+
pub mod cow_amm;
23
pub mod eth;
34
pub mod liquidity;
45
pub mod mempools;

crates/driver/src/infra/blockchain/contracts.rs

Lines changed: 12 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use {
2-
crate::{boundary, domain::eth, infra::blockchain::Ethereum},
2+
crate::{domain::eth, infra::blockchain::Ethereum},
33
chain::Chain,
44
contracts::alloy::{BalancerV2Vault, FlashLoanRouter, support::Balances},
55
ethrpc::{
66
Web3,
77
alloy::conversions::{IntoAlloy, IntoLegacy},
8-
block_stream::CurrentBlockWatcher,
98
},
9+
std::collections::HashMap,
1010
thiserror::Error,
1111
};
1212

@@ -20,14 +20,16 @@ pub struct Contracts {
2020

2121
/// The domain separator for settlement contract used for signing orders.
2222
settlement_domain_separator: eth::DomainSeparator,
23-
cow_amm_registry: cow_amm::Registry,
2423

2524
/// Single router that supports multiple flashloans in the
2625
/// same settlement.
2726
// TODO: make this non-optional when contracts are deployed
2827
// everywhere
2928
flashloan_router: Option<FlashLoanRouter::Instance>,
3029
balance_helper: Balances::Instance,
30+
/// Mapping from CoW AMM factory address to the corresponding CoW AMM
31+
/// helper.
32+
cow_amm_helper_by_factory: HashMap<eth::ContractAddress, eth::ContractAddress>,
3133
}
3234

3335
#[derive(Debug, Default, Clone)]
@@ -36,7 +38,7 @@ pub struct Addresses {
3638
pub signatures: Option<eth::ContractAddress>,
3739
pub weth: Option<eth::ContractAddress>,
3840
pub balances: Option<eth::ContractAddress>,
39-
pub cow_amms: Vec<CowAmmConfig>,
41+
pub cow_amm_helper_by_factory: HashMap<eth::ContractAddress, eth::ContractAddress>,
4042
pub flashloan_router: Option<eth::ContractAddress>,
4143
}
4244

@@ -45,8 +47,6 @@ impl Contracts {
4547
web3: &Web3,
4648
chain: Chain,
4749
addresses: Addresses,
48-
block_stream: CurrentBlockWatcher,
49-
archive_node: Option<super::RpcArgs>,
5050
) -> Result<Self, Error> {
5151
let address_for = |contract: &ethcontract::Contract,
5252
address: Option<eth::ContractAddress>| {
@@ -99,21 +99,6 @@ impl Contracts {
9999
.0,
100100
);
101101

102-
let archive_node_web3 = archive_node.as_ref().map_or(web3.clone(), |args| {
103-
boundary::buffered_web3_client(
104-
&args.url,
105-
args.max_batch_size,
106-
args.max_concurrent_requests,
107-
)
108-
});
109-
let mut cow_amm_registry = cow_amm::Registry::new(archive_node_web3);
110-
for config in addresses.cow_amms {
111-
cow_amm_registry
112-
.add_listener(config.index_start, config.factory, config.helper)
113-
.await;
114-
}
115-
cow_amm_registry.spawn_maintenance_task(block_stream);
116-
117102
// TODO: use `address_for()` once contracts are deployed
118103
let flashloan_router = addresses
119104
.flashloan_router
@@ -133,9 +118,9 @@ impl Contracts {
133118
signatures,
134119
weth,
135120
settlement_domain_separator,
136-
cow_amm_registry,
137121
flashloan_router,
138122
balance_helper,
123+
cow_amm_helper_by_factory: addresses.cow_amm_helper_by_factory,
139124
})
140125
}
141126

@@ -167,27 +152,19 @@ impl Contracts {
167152
&self.settlement_domain_separator
168153
}
169154

170-
pub fn cow_amm_registry(&self) -> &cow_amm::Registry {
171-
&self.cow_amm_registry
172-
}
173-
174155
pub fn flashloan_router(&self) -> Option<&FlashLoanRouter::Instance> {
175156
self.flashloan_router.as_ref()
176157
}
177158

178159
pub fn balance_helper(&self) -> &Balances::Instance {
179160
&self.balance_helper
180161
}
181-
}
182162

183-
#[derive(Debug, Clone)]
184-
pub struct CowAmmConfig {
185-
/// Which contract to index for CoW AMM deployment events.
186-
pub factory: eth::H160,
187-
/// Which helper contract to use for interfacing with the indexed CoW AMMs.
188-
pub helper: eth::H160,
189-
/// At which block indexing should start on the factory.
190-
pub index_start: u64,
163+
pub fn cow_amm_helper_by_factory(
164+
&self,
165+
) -> &HashMap<eth::ContractAddress, eth::ContractAddress> {
166+
&self.cow_amm_helper_by_factory
167+
}
191168
}
192169

193170
/// Returns the address of a contract for the specified network, or `None` if

crates/driver/src/infra/blockchain/mod.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ impl Ethereum {
9696
rpc: Rpc,
9797
addresses: contracts::Addresses,
9898
gas: Arc<GasPriceEstimator>,
99-
archive_node_url: Option<&Url>,
10099
tx_gas_limit: U256,
101100
) -> Self {
102101
let Rpc { web3, chain, args } = rpc;
@@ -108,19 +107,9 @@ impl Ethereum {
108107
.await
109108
.expect("couldn't initialize current block stream");
110109

111-
let contracts = Contracts::new(
112-
&web3,
113-
chain,
114-
addresses,
115-
current_block_stream.clone(),
116-
archive_node_url.map(|url| RpcArgs {
117-
url: url.clone(),
118-
max_batch_size: args.max_batch_size,
119-
max_concurrent_requests: args.max_concurrent_requests,
120-
}),
121-
)
122-
.await
123-
.expect("could not initialize important smart contracts");
110+
let contracts = Contracts::new(&web3, chain, addresses)
111+
.await
112+
.expect("could not initialize important smart contracts");
124113
let balance_overrider = Arc::new(BalanceOverrides::new(web3.clone()));
125114
let balance_simulator = BalanceSimulator::new(
126115
contracts.settlement().clone(),

0 commit comments

Comments
 (0)