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
use alloy_primitives::{Address, Log, U256};
use super::bytecode::{AdapterCodeSeed, BytecodeTemplateError};
use super::cold_start::AdapterColdStartPlanner;
use super::factory::{FactoryConfig, PoolFactory};
use super::sim::{SimConfig, SimError, SwapQuote};
use super::{
AdapterCache, AdapterEvent, AdapterEventResult, AdapterRegistry, ColdStartPolicy, EventSource,
PoolKey, PoolRegistration, PoolStateDependencies, ProtocolId, RepairAction, StateDiff,
StateView, UnsupportedReason,
};
/// Protocol adapter contract for AMM-specific routing, cold-start, and decoding.
pub trait AmmAdapter: Send + Sync {
/// The adapter's primary/canonical protocol id.
fn protocol(&self) -> ProtocolId;
/// Every protocol id this adapter serves.
///
/// Defaults to `[self.protocol()]`. Override to claim a whole storage-layout
/// family from a single adapter instance (e.g. the V3 family adapter serves
/// `UniswapV3`, `PancakeV3`, and `Slipstream`). The registry registers the
/// adapter `Arc` under every returned id; [`Self::protocol`] remains the
/// primary/canonical id.
fn protocols(&self) -> Vec<ProtocolId> {
vec![self.protocol()]
}
/// The log sources to subscribe/route for `pool`. Defaults to the pool's own
/// stored `event_sources`; override to derive them from adapter knowledge.
fn event_sources(&self, pool: &PoolRegistration) -> Vec<EventSource> {
pool.event_sources.clone()
}
/// Route a log to the pool key it belongs to. Defaults to the registry's
/// generic emitter/topic routing; override for adapter-defined routing.
fn route_log(&self, log: &Log, registry: &AdapterRegistry) -> Option<PoolKey> {
registry.route_log_generic(log).map(|pool| pool.key.clone())
}
/// Declare the pool's state-associated addresses and storage ownership.
///
/// The conservative default treats the pool key address and every explicit
/// `state_address` as whole-account storage. Shared-address adapters should
/// override this and declare exact slots so unrelated co-tenants are not
/// attributed every write at the shared contract.
fn state_dependencies(&self, pool: &PoolRegistration) -> PoolStateDependencies {
let mut addresses: Vec<Address> = pool.key.address().into_iter().collect();
addresses.extend(pool.state_addresses.iter().copied());
PoolStateDependencies::default().with_whole_accounts(addresses)
}
/// Build factory drivers backed by `config`, if this adapter supports
/// declarative pool discovery. Defaults to none so third-party adapters and
/// protocols without discovery support are unaffected.
fn pool_factories(&self, _config: &FactoryConfig) -> Vec<Box<dyn PoolFactory>> {
Vec::new()
}
/// Build a cold-start planner for `pool` under `policy`.
///
/// The returned [`AdapterColdStartPlanner`] declares the per-round slot work
/// (verify/probe) for [`AdapterRegistry::cold_start`] to drive through
/// [`EvmCache::run_cold_start`](evm_fork_cache::cache::EvmCache::run_cold_start),
/// then finalizes the pool's metadata/status from the run results. This
/// replaces the former imperative `cold_start`: the repair decision is now
/// sourced from the per-slot
/// [`SlotFetch`](evm_fork_cache::cold_start::SlotFetch) classification rather
/// than a `cached_storage(..).is_none()` proxy.
///
/// # Metadata contract: merge vs. preserve
///
/// Adapters fall into two camps depending on where a pool's *immutable*
/// metadata lives:
///
/// - **Merge** (metadata at known storage slots): if an adapter can read its
/// immutable identity from predictable slots — e.g. Uniswap V2
/// `token0`/`token1` — it MERGES the decoded values into the existing
/// config metadata, decoded fields filling in the on-chain truth while
/// config-only fields (e.g. `fee_bps`) are preserved untouched.
/// - **Preserve** (metadata not at predictable slots): if an adapter cannot
/// recover its identity from a fixed slot layout — e.g. V3
/// `token0`/`token1`/`fee`/`tick_spacing` — it PRESERVES the
/// config-supplied metadata unchanged and requires a resolvable storage
/// layout (returning [`UnsupportedReason::MissingMetadata`] when none can be
/// derived) rather than overwriting config with guesses.
fn cold_start_planner(
&self,
_pool: &PoolRegistration,
_policy: ColdStartPolicy,
) -> Result<Box<dyn AdapterColdStartPlanner>, UnsupportedReason> {
Err(UnsupportedReason::Protocol(self.protocol()))
}
/// Return canonical runtime bytecode seeds that should be written into
/// `EvmCache` before the cold-start run. The cache verifies these seeds
/// against on-chain code hashes during its `verify_code` phase.
///
/// A pool that is simply not seedable (wrong pool-key shape, missing or
/// incomplete metadata, wrong protocol) returns `Ok(vec![])`. Only a
/// genuine template render failure returns `Err(BytecodeTemplateError)`;
/// the facade treats that as a safe skip (no seeding for that pool).
fn code_seeds(
&self,
_pool: &PoolRegistration,
) -> Result<Vec<AdapterCodeSeed>, BytecodeTemplateError> {
Ok(Vec::new())
}
/// Runtime-code accounts that must be fetched and exact-hash verified for
/// snapshot-only quote execution, but whose bytecode cannot be derived from
/// an embedded template.
///
/// Unlike [`code_seeds`](Self::code_seeds), these addresses carry no
/// predeclared hash: the background worker fetches both `eth_getCode` and an
/// exact-block account proof, and the cache owner accepts the prepared bytes
/// only when they hash to the proof's `codeHash`. The default is empty.
fn verified_code_targets(&self, _pool: &PoolRegistration) -> Vec<Address> {
Vec::new()
}
/// Decode a routed log into a semantic event with its cache updates.
/// Defaults to [`AdapterEventResult::ignored`]; a malformed watched log
/// should return [`AdapterEventResult::error`] (it is isolated per-log, not
/// batch-fatal), never panic.
fn decode_event(
&self,
_pool: &PoolRegistration,
_log: &Log,
_view: &dyn StateView,
) -> AdapterEventResult {
AdapterEventResult::ignored()
}
/// Follow-up repair after `event`'s updates were applied, given the resulting
/// `diff` (e.g. re-verify slots that were skipped because they were cold).
/// Defaults to [`RepairAction::None`].
fn after_apply(
&self,
_pool: &PoolRegistration,
_event: &AdapterEvent,
_diff: &StateDiff,
) -> RepairAction {
RepairAction::None
}
/// Simulate `amount_in` of `token_in` swapped to `token_out` for `pool`,
/// returning the protocol's canonical `amount_out`.
///
/// The implementation builds the protocol's canonical *quote* calldata and
/// runs it via [`AdapterCache::call_raw`] with `from = config.from` (default
/// `ZERO`, see [`SimConfig::from`]),
/// `to = <quote target>`, `commit = false` against the cold-start snapshot,
/// then decodes `amount_out` from the [`ExecutionResult`] output. The
/// deployed contract bytecode does the AMM math — there is no `amm-math` /
/// `LocalAMM` / hand-rolled math here. A revert/halt maps to
/// [`SimError::Reverted`].
///
/// Quote targets are resolved from `config` (Uniswap V3 `QuoterV2`, Uniswap
/// V2 `Router02`) with the Balancer vault taken from the pool's metadata.
///
/// [`ExecutionResult`]: revm::context::result::ExecutionResult
///
/// Defaults to [`SimError::Unsupported`] for protocols without a quote impl.
fn simulate_swap(
&self,
_pool: &PoolRegistration,
_cache: &mut dyn AdapterCache,
_token_in: Address,
_token_out: Address,
_amount_in: U256,
_config: &SimConfig,
) -> Result<SwapQuote, SimError> {
Err(SimError::Unsupported(self.protocol()))
}
}