nautilus_blockchain/contracts/
uniswap_v3_swap.rs1use std::sync::Arc;
17
18use alloy::{
19 primitives::{Address, aliases::U24},
20 sol,
21 sol_types::SolCall,
22};
23
24use super::base::BaseContract;
25use crate::rpc::{error::BlockchainRpcClientError, http::BlockchainHttpRpcClient};
26
27sol! {
30 #[sol(rpc)]
31 contract UniswapV3SwapRouter {
32 struct ExactInputSingleParams {
33 address tokenIn;
34 address tokenOut;
35 uint24 fee;
36 address recipient;
37 uint256 deadline;
38 uint256 amountIn;
39 uint256 amountOutMinimum;
40 uint160 sqrtPriceLimitX96;
41 }
42
43 function exactInputSingle(ExactInputSingleParams memory params) external payable returns (uint256 amountOut);
44 }
45}
46
47sol! {
48 #[sol(rpc)]
49 contract UniswapV3RouterState {
50 function factory() external view returns (address);
51 function WETH9() external view returns (address);
52 }
53
54 #[sol(rpc)]
55 contract UniswapV3Factory {
56 function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);
57 }
58}
59
60#[derive(Debug)]
62pub struct UniswapV3Deployment {
63 base: BaseContract,
64}
65
66impl UniswapV3Deployment {
67 #[must_use]
69 pub fn new(client: Arc<BlockchainHttpRpcClient>, rpc_timeout_secs: Option<u64>) -> Self {
70 Self {
71 base: BaseContract::new_with_multicall_limit_and_timeout(
72 client,
73 super::base::DEFAULT_MULTICALL_CALLS_PER_RPC_REQUEST,
74 rpc_timeout_secs,
75 ),
76 }
77 }
78
79 pub async fn router_factory(
85 &self,
86 router: &Address,
87 ) -> Result<Address, BlockchainRpcClientError> {
88 self.router_factory_with_block(router, None).await
89 }
90
91 async fn router_factory_with_block(
92 &self,
93 router: &Address,
94 block: Option<u64>,
95 ) -> Result<Address, BlockchainRpcClientError> {
96 let result = self
97 .base
98 .execute_call(
99 router,
100 &UniswapV3RouterState::factoryCall {}.abi_encode(),
101 block,
102 )
103 .await?;
104 UniswapV3RouterState::factoryCall::abi_decode_returns(&result)
105 .map_err(|e| BlockchainRpcClientError::AbiDecodingError(e.to_string()))
106 }
107
108 pub async fn router_weth9(
114 &self,
115 router: &Address,
116 ) -> Result<Address, BlockchainRpcClientError> {
117 self.router_weth9_with_block(router, None).await
118 }
119
120 async fn router_weth9_with_block(
121 &self,
122 router: &Address,
123 block: Option<u64>,
124 ) -> Result<Address, BlockchainRpcClientError> {
125 let result = self
126 .base
127 .execute_call(
128 router,
129 &UniswapV3RouterState::WETH9Call {}.abi_encode(),
130 block,
131 )
132 .await?;
133 UniswapV3RouterState::WETH9Call::abi_decode_returns(&result)
134 .map_err(|e| BlockchainRpcClientError::AbiDecodingError(e.to_string()))
135 }
136
137 pub async fn pool(
143 &self,
144 factory: &Address,
145 token_a: Address,
146 token_b: Address,
147 fee: U24,
148 ) -> Result<Address, BlockchainRpcClientError> {
149 self.pool_with_block(factory, token_a, token_b, fee, None)
150 .await
151 }
152
153 async fn pool_with_block(
154 &self,
155 factory: &Address,
156 token_a: Address,
157 token_b: Address,
158 fee: U24,
159 block: Option<u64>,
160 ) -> Result<Address, BlockchainRpcClientError> {
161 let call = UniswapV3Factory::getPoolCall {
162 tokenA: token_a,
163 tokenB: token_b,
164 fee,
165 };
166 let result = self
167 .base
168 .execute_call(factory, &call.abi_encode(), block)
169 .await?;
170 UniswapV3Factory::getPoolCall::abi_decode_returns(&result)
171 .map_err(|e| BlockchainRpcClientError::AbiDecodingError(e.to_string()))
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use alloy::{
178 primitives::{
179 U256, address,
180 aliases::{U24, U160},
181 },
182 sol_types::SolCall,
183 };
184 use nautilus_core::hex;
185 use rstest::rstest;
186
187 use super::*;
188
189 #[rstest]
190 fn exact_input_single_selector_matches_canonical_signature() {
191 let calldata = UniswapV3SwapRouter::exactInputSingleCall {
192 params: UniswapV3SwapRouter::ExactInputSingleParams {
193 tokenIn: address!("82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
194 tokenOut: address!("af88d065e77c8cC2239327C5EDb3A432268e5831"),
195 fee: U24::try_from(500u32).unwrap(),
196 recipient: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
197 deadline: U256::from(1_761_889_100u64),
198 amountIn: U256::from(1_000_000_000_000_000u64),
199 amountOutMinimum: U256::from(1_995_000u64),
200 sqrtPriceLimitX96: U160::ZERO,
201 },
202 }
203 .abi_encode();
204
205 assert_eq!(hex::encode(&calldata[..4]), "414bf389");
207 }
208
209 #[rstest]
210 fn exact_input_single_encodes_fields_in_order() {
211 let calldata = UniswapV3SwapRouter::exactInputSingleCall {
212 params: UniswapV3SwapRouter::ExactInputSingleParams {
213 tokenIn: address!("82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
214 tokenOut: address!("af88d065e77c8cC2239327C5EDb3A432268e5831"),
215 fee: U24::try_from(500u32).unwrap(),
216 recipient: address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"),
217 deadline: U256::from(1_761_889_100u64),
218 amountIn: U256::from(1_000_000_000_000_000u64),
219 amountOutMinimum: U256::from(1_995_000u64),
220 sqrtPriceLimitX96: U160::ZERO,
221 },
222 }
223 .abi_encode();
224
225 let expected = concat!(
226 "414bf389",
227 "00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1",
228 "000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831",
229 "00000000000000000000000000000000000000000000000000000000000001f4",
230 "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266",
231 "0000000000000000000000000000000000000000000000000000000069044b4c",
232 "00000000000000000000000000000000000000000000000000038d7ea4c68000",
233 "00000000000000000000000000000000000000000000000000000000001e70f8",
234 "0000000000000000000000000000000000000000000000000000000000000000",
235 );
236 assert_eq!(hex::encode(&calldata), expected);
237 }
238
239 #[rstest]
240 fn deployment_selectors_match_canonical_signatures() {
241 assert_eq!(
242 hex::encode(UniswapV3RouterState::factoryCall {}.abi_encode()),
243 "c45a0155"
244 );
245 assert_eq!(
246 hex::encode(UniswapV3RouterState::WETH9Call {}.abi_encode()),
247 "4aa4a4fc"
248 );
249 let calldata = UniswapV3Factory::getPoolCall {
250 tokenA: address!("82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
251 tokenB: address!("af88d065e77c8cC2239327C5EDb3A432268e5831"),
252 fee: U24::try_from(500u32).unwrap(),
253 }
254 .abi_encode();
255 assert_eq!(hex::encode(&calldata[..4]), "1698ee82");
256 }
257}