Skip to main content

wp_evm_v3_core/
data.rs

1//! Pure data records for the v3 family.
2//!
3//! These are dumb structs with no methods. All operations on them live
4//! in `quote`, `plan`, or `hydrate` modules.
5
6use alloy_primitives::{Address, B256, U256};
7
8/// Snapshot of a v3 pool's on-chain state at some block.
9///
10/// Hydrated by `hydrate::pool_state`. Consumed by `quote::*` and `plan::*`.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct PoolState {
13    pub token0: Address,
14    pub token1: Address,
15    pub fee: u32,
16    pub tick_spacing: i32,
17    pub sqrt_price_x96: U256,
18    pub liquidity: u128,
19    /// Current tick as reported by the pool's `slot0()` at hydration time.
20    ///
21    /// **Informational only.** Quote functions derive `tick_current` from
22    /// `sqrt_price_x96` via the SDK's tick math; this field is not consumed
23    /// by `quote::*` or `plan::*`. Stored for display and debugging.
24    pub tick: i32,
25    /// Initialized ticks within a window around the current tick. The
26    /// window size is determined at hydrate time.
27    ///
28    /// **Sorted ascending by `tick`.** Callers must populate via
29    /// `wp-evm-v3-provider::populate_ticks` (or equivalent) before passing
30    /// the `PoolState` to `quote::*`. The native swap loop
31    /// (`crate::swap::swap`) relies on this ordering for O(log n) tick
32    /// search.
33    pub ticks: Vec<TickInfo>,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct TickInfo {
38    pub tick: i32,
39    pub liquidity_net: i128,
40    pub liquidity_gross: u128,
41}
42
43/// Snapshot of a v3 LP position.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct PositionState {
46    pub token_id: U256,
47    pub owner: Address,
48    pub token0: Address,
49    pub token1: Address,
50    pub fee: u32,
51    pub tick_lower: i32,
52    pub tick_upper: i32,
53    pub liquidity: u128,
54    pub fees_owed_0: U256,
55    pub fees_owed_1: U256,
56}
57
58/// Per-protocol configuration baked into protocol facade crates.
59///
60/// Adding a new v3-fork DEX = creating a new protocol facade with a
61/// `pub const CONFIG: V3ProtocolConfig = ...`. No family code changes.
62///
63/// Uniswap V3 SwapRouter ABI version. Determines whether the
64/// `exactInputSingle` / `exactOutputSingle` params struct carries a
65/// `deadline: uint256` field.
66///
67/// - `V1`: original SwapRouter (with deadline). Selector for
68///   `exactInputSingle`: `0x414bf389`. Runtime size ~12,070 bytes.
69/// - `V02`: SwapRouter02 (no `deadline`; checks tx-level deadline
70///   externally via `Multicall` deadline wrapper if needed). Selector
71///   for `exactInputSingle`: `0x04e45aaf`. Runtime size ~24,497 bytes.
72///
73/// All 8 V3-family routers in waterpump-evm scanned 2026-05-12 — see
74/// R26-future-1 spec PR #200 §4 Slice 2 Task 6 amendment table for
75/// per-facade-chain mapping. Base + Avalanche Uniswap V3 = V02; all
76/// other 6 facade-chain combos = V1.
77#[derive(Copy, Clone, Debug, PartialEq, Eq)]
78pub enum SwapRouterKind {
79    /// Canonical Uniswap V3 SwapRouter (with `deadline` in params).
80    V1,
81    /// SwapRouter02 (no `deadline` in params).
82    V02,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct V3ProtocolConfig {
87    pub factory: Address,
88    /// CREATE2 caller for pool deployment. `None` means the factory
89    /// itself is the deployer (Uniswap V3 pattern). `Some(addr)` routes
90    /// CREATE2 derivation through a separate `PoolDeployer` contract
91    /// (PancakeSwap V3 pattern; analogous to Algebra's `AlgebraPoolDeployer`).
92    pub pool_deployer: Option<Address>,
93    pub router: Address,
94    /// SwapRouter ABI version — determines `exactInputSingle` /
95    /// `exactOutputSingle` encoding shape (V1 includes `deadline`,
96    /// V02 doesn't). See `SwapRouterKind` doc-comment for selector
97    /// + runtime-size scan provenance.
98    pub swap_router_kind: SwapRouterKind,
99    pub position_mgr: Address,
100    pub init_code_hash: B256,
101    pub fee_tiers: &'static [u32],
102    pub multicall: Address,
103    /// Canonical QuoterV2 deployment for this protocol, when one exists.
104    /// `None` means the facade has no on-chain parity oracle registered.
105    /// `wp-evm-v3-provider::quote_online` returns an error if called with
106    /// `None`.
107    pub quoter: Option<Address>,
108}
109
110/// Parameters for an `exact_in` swap quote/plan.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct ExactInParams {
113    pub token_in: Address,
114    pub token_out: Address,
115    pub amount_in: U256,
116    pub recipient: Address,
117}
118
119/// Parameters for an `exact_out` swap quote/plan.
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct ExactOutParams {
122    pub token_in: Address,
123    pub token_out: Address,
124    pub amount_out: U256,
125    pub recipient: Address,
126}
127
128/// Output of a quote computation. Pure — no chain interaction needed
129/// to produce one given a `PoolState`.
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct Quote {
132    pub amount_in: U256,
133    pub amount_out: U256,
134    pub sqrt_price_x96_after: U256,
135    pub price_impact_bps: u16,
136}
137
138/// Parameters for an `add_liquidity` plan.
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct AddLiquidityParams {
141    pub token0: Address,
142    pub token1: Address,
143    pub fee: u32,
144    pub tick_lower: i32,
145    pub tick_upper: i32,
146    pub amount0_desired: U256,
147    pub amount1_desired: U256,
148    pub recipient: Address,
149}
150
151/// Parameters for a `remove_liquidity` plan.
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct RemoveLiquidityParams {
154    pub token_id: U256,
155    pub liquidity: u128,
156    /// Minimum `token0` amount the caller expects back, computed off-chain
157    /// from a prior quote against the current pool state. `None` means
158    /// no slippage protection (zero) — only safe for private mempools or
159    /// when the caller has other guarantees.
160    pub amount0_min: Option<U256>,
161    /// Same for `token1`.
162    pub amount1_min: Option<U256>,
163}
164
165/// Parameters for a `collect_fees` plan.
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub struct CollectFeesParams {
168    pub token_id: U256,
169    pub recipient: Address,
170    /// Pool's `token0` address — caller-supplied (typically fetched
171    /// via NFPM.positions(tokenId) before constructing this struct).
172    /// Used by facade-layer native unwrap to compose `sweepToken` for
173    /// the non-native side when `recipient == Address::ZERO`. Ignored
174    /// for non-native recipient cases.
175    pub token0: Address,
176    /// Same for token1.
177    pub token1: Address,
178    /// Caller's address (msg.sender). When `recipient == ZERO` (native
179    /// unwrap sentinel), used as the final `unwrapWETH9.recipient` and
180    /// `sweepToken.recipient`. Ignored for non-native recipient cases.
181    pub caller: Address,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct RemoveAndCollectParams {
186    /// NFPM position id — single source of truth (no risk of mismatch
187    /// between separate remove + collect param structs).
188    pub token_id: U256,
189    // -- decrease side --
190    pub liquidity: u128,
191    pub amount0_min: Option<U256>,
192    pub amount1_min: Option<U256>,
193    // -- collect side --
194    /// Recipient for the collected tokens. Pass `Address::ZERO` to
195    /// trigger native unwrap (wrapped-native side → unwrapped to caller;
196    /// non-native side → swept to caller as ERC20).
197    pub recipient: Address,
198    /// Pool's `token0` — caller fetches via NFPM.positions(tokenId)
199    /// before constructing. Used by facade-layer native unwrap to
200    /// determine which side is wrapped-native and which side to sweep.
201    pub token0: Address,
202    /// Same for token1.
203    pub token1: Address,
204    /// Caller's `msg.sender`. When `recipient == ZERO`, used as final
205    /// `unwrapWETH9.recipient` and `sweepToken.recipient`. Ignored for
206    /// non-native cases.
207    pub caller: Address,
208}
209
210/// Re-export of `wp_evm_base::types::PlanFragment`.
211///
212/// Canonical single definition lives in `waterpump-base`. All family crates
213/// re-export from there so the Phase 5 composer can aggregate fragments from
214/// multiple families into one `Vec<PlanFragment>`.
215pub use wp_evm_base::types::PlanFragment;
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use alloy_primitives::{address, U256};
221
222    #[test]
223    fn pool_state_constructs() {
224        let s = PoolState {
225            token0: address!("0x0000000000000000000000000000000000000001"),
226            token1: address!("0x0000000000000000000000000000000000000002"),
227            fee: 3000,
228            tick_spacing: 60,
229            sqrt_price_x96: U256::from(1u64) << 96,
230            liquidity: 0u128,
231            tick: 0i32,
232            ticks: vec![],
233        };
234        assert_eq!(s.fee, 3000);
235    }
236
237    #[test]
238    fn plan_fragment_default_is_empty() {
239        let f = PlanFragment::default();
240        assert!(f.calls.is_empty());
241        assert!(f.approvals.is_empty());
242        assert_eq!(f.value, U256::ZERO);
243    }
244}