diff --git a/Cargo.lock b/Cargo.lock index 8b0f3de..16adb59 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,9 +29,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -117,9 +117,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anyhow" @@ -491,9 +491,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d" +checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" dependencies = [ "serde", ] @@ -536,9 +536,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "num-traits", ] @@ -582,18 +582,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstyle", "clap_lex", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "coins-bip32" @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" +checksum = "18d59688ad0945eaf6b84cb44fedbe93484c81b48970e98f09db8a22832d7961" dependencies = [ "cfg-if", "cpufeatures", @@ -715,9 +715,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] @@ -960,9 +960,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "elliptic-curve" @@ -1697,9 +1697,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "hex" @@ -1859,9 +1859,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown", @@ -1893,12 +1893,12 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", - "rustix", + "libc", "windows-sys 0.52.0", ] @@ -1928,18 +1928,18 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -2137,11 +2137,10 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] @@ -2159,9 +2158,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -2361,9 +2360,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.6" +version = "2.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" +checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" dependencies = [ "memchr", "thiserror", @@ -3531,13 +3530,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] @@ -3555,18 +3553,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", @@ -3720,14 +3718,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.1", + "toml_edit 0.22.5", ] [[package]] @@ -3747,7 +3745,7 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -3758,7 +3756,7 @@ checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -3766,12 +3764,23 @@ name = "toml_edit" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e68c159e8f5ba8a28c4eb7b0c0c190d77bb479047ca713270048145a9ad28a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.0", ] [[package]] @@ -3906,9 +3915,9 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "uniswap-sdk-core" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b36075329d71604f738f4e1564cd44f0cf46480eeea2fa5c740b7a270898599" +checksum = "fb8c6dc4b6dd4653700903fd87becde38f949d8183f9435606ed714a6a7d920e" dependencies = [ "alloy-primitives", "bigdecimal", @@ -3919,12 +3928,13 @@ dependencies = [ "num-rational", "num-traits", "regex", + "syn 2.0.48", "thiserror", ] [[package]] name = "uniswap-v3-sdk" -version = "0.21.0" +version = "0.22.0" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -4053,9 +4063,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4063,9 +4073,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", @@ -4078,9 +4088,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -4090,9 +4100,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4100,9 +4110,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", @@ -4113,15 +4123,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -4298,9 +4308,18 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.37" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" +checksum = "6b1dbce9e90e5404c5a52ed82b1d13fc8cfbdad85033b6f57546ffd1265f8451" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index f774299..b368732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uniswap-v3-sdk" -version = "0.21.0" +version = "0.22.0" edition = "2021" authors = ["Shuhui Luo "] description = "Uniswap V3 SDK for Rust" @@ -30,7 +30,7 @@ regex = { version = "1.10", optional = true } ruint = "1.11" serde_json = { version = "1.0", optional = true } thiserror = "1.0" -uniswap-sdk-core = "0.12.0" +uniswap-sdk-core = "0.14.0" uniswap_v3_math = "0.4.1" [features] diff --git a/src/abi.rs b/src/abi.rs index 7d0249c..7a1e3c2 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -150,4 +150,82 @@ sol! { uint256 amountRequested ) external returns (uint256 reward); } + + interface IQuoter { + function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut); + + function quoteExactInputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountIn, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountOut); + + function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn); + + function quoteExactOutputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountOut, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountIn); + } +} + +sol! { + interface IQuoterV2 { + function quoteExactInput(bytes memory path, uint256 amountIn) + external + returns ( + uint256 amountOut, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); + + struct QuoteExactInputSingleParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + function quoteExactInputSingle(QuoteExactInputSingleParams memory params) + external + returns ( + uint256 amountOut, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); + + function quoteExactOutput(bytes memory path, uint256 amountOut) + external + returns ( + uint256 amountIn, + uint160[] memory sqrtPriceX96AfterList, + uint32[] memory initializedTicksCrossedList, + uint256 gasEstimate + ); + + struct QuoteExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint256 amount; + uint24 fee; + uint160 sqrtPriceLimitX96; + } + + function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params) + external + returns ( + uint256 amountIn, + uint160 sqrtPriceX96After, + uint32 initializedTicksCrossed, + uint256 gasEstimate + ); + } } diff --git a/src/lib.rs b/src/lib.rs index 9d07522..4c154bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod entities; pub mod multicall; pub mod nonfungible_position_manager; pub mod payments; +pub mod quoter; pub mod self_permit; pub mod staker; pub mod utils; @@ -19,7 +20,8 @@ pub mod extensions; pub mod prelude { pub use crate::{ abi::*, constants::*, entities::*, multicall::encode_multicall, - nonfungible_position_manager::*, payments::*, self_permit::*, staker::*, utils::*, + nonfungible_position_manager::*, payments::*, quoter::*, self_permit::*, staker::*, + utils::*, }; #[cfg(feature = "extensions")] diff --git a/src/quoter.rs b/src/quoter.rs new file mode 100644 index 0000000..84e31f0 --- /dev/null +++ b/src/quoter.rs @@ -0,0 +1,335 @@ +use crate::prelude::*; +use alloy_primitives::U256; +use alloy_sol_types::SolCall; +use uniswap_sdk_core::{constants::TradeType, prelude::*}; + +/// Optional arguments to send to the quoter. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct QuoteOptions { + /// The price limit for the trade. + sqrt_price_limit_x96: U256, + /// The quoter interface to use + use_quoter_v2: bool, +} + +/// Produces the on-chain method name of the appropriate function within QuoterV2, +/// and the relevant hex encoded parameters. +/// +/// ## Arguments +/// +/// * `route`: The swap route, a list of pools through which a swap can occur +/// * `amount`: The amount of the quote, either an amount in, or an amount out +/// * `trade_type`: The trade type, either exact input or exact output +/// * `options`: The optional params including price limit and Quoter contract switch +/// +pub fn quote_call_parameters( + route: &Route, + amount: CurrencyAmount, + trade_type: TradeType, + options: Option, +) -> MethodParameters { + let options = options.unwrap_or_default(); + let quote_amount = big_int_to_u256(amount.quotient()); + + let calldata = if route.pools.len() == 1 { + match trade_type { + TradeType::ExactInput => { + if options.use_quoter_v2 { + IQuoterV2::quoteExactInputSingleCall { + params: IQuoterV2::QuoteExactInputSingleParams { + tokenIn: route.token_path[0].address(), + tokenOut: route.token_path[1].address(), + amountIn: quote_amount, + fee: route.pools[0].fee as u32, + sqrtPriceLimitX96: options.sqrt_price_limit_x96, + }, + } + .abi_encode() + } else { + IQuoter::quoteExactInputSingleCall { + tokenIn: route.token_path[0].address(), + tokenOut: route.token_path[1].address(), + amountIn: quote_amount, + fee: route.pools[0].fee as u32, + sqrtPriceLimitX96: options.sqrt_price_limit_x96, + } + .abi_encode() + } + } + TradeType::ExactOutput => { + if options.use_quoter_v2 { + IQuoterV2::quoteExactOutputSingleCall { + params: IQuoterV2::QuoteExactOutputSingleParams { + tokenIn: route.token_path[0].address(), + tokenOut: route.token_path[1].address(), + amount: quote_amount, + fee: route.pools[0].fee as u32, + sqrtPriceLimitX96: options.sqrt_price_limit_x96, + }, + } + .abi_encode() + } else { + IQuoter::quoteExactOutputSingleCall { + tokenIn: route.token_path[0].address(), + tokenOut: route.token_path[1].address(), + amountOut: quote_amount, + fee: route.pools[0].fee as u32, + sqrtPriceLimitX96: options.sqrt_price_limit_x96, + } + .abi_encode() + } + } + } + } else { + assert!( + options.sqrt_price_limit_x96.is_zero(), + "MULTIHOP_PRICE_LIMIT" + ); + let path = encode_route_to_path(route, trade_type == TradeType::ExactOutput); + match trade_type { + TradeType::ExactInput => IQuoter::quoteExactInputCall { + path, + amountIn: quote_amount, + } + .abi_encode(), + TradeType::ExactOutput => IQuoter::quoteExactOutputCall { + path, + amountOut: quote_amount, + } + .abi_encode(), + } + }; + MethodParameters { + calldata, + value: U256::ZERO, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use once_cell::sync::Lazy; + use uniswap_sdk_core::token; + + static TOKEN0: Lazy = Lazy::new(|| { + token!( + 1, + "0x0000000000000000000000000000000000000001", + 18, + "t0", + "token0" + ) + }); + static TOKEN1: Lazy = Lazy::new(|| { + token!( + 1, + "0x0000000000000000000000000000000000000002", + 18, + "t1", + "token1" + ) + }); + static WETH: Lazy = Lazy::new(|| Ether::on_chain(1).wrapped()); + const FEE_AMOUNT: FeeAmount = FeeAmount::MEDIUM; + const SQRT_RATIO_X96: U256 = Q96; + const LIQUIDITY: u128 = 1_000_000; + + fn make_pool(token0: Token, token1: Token) -> Pool { + Pool::new_with_tick_data_provider( + token0, + token1, + FEE_AMOUNT, + SQRT_RATIO_X96, + LIQUIDITY, + TickListDataProvider::new( + vec![ + Tick::new( + nearest_usable_tick(MIN_TICK, FEE_AMOUNT.tick_spacing()), + LIQUIDITY, + LIQUIDITY as i128, + ), + Tick::new( + nearest_usable_tick(MAX_TICK, FEE_AMOUNT.tick_spacing()), + LIQUIDITY, + -(LIQUIDITY as i128), + ), + ], + FEE_AMOUNT.tick_spacing(), + ), + ) + .unwrap() + } + + static POOL_0_1: Lazy> = + Lazy::new(|| make_pool(TOKEN0.clone(), TOKEN1.clone())); + static POOL_1_WETH: Lazy> = + Lazy::new(|| make_pool(TOKEN1.clone(), WETH.clone())); + + mod single_trade_input { + use super::*; + use alloy_primitives::hex; + + #[test] + fn single_hop_exact_input() { + let mut trade = Trade::from_route( + Route::new(vec![POOL_0_1.clone()], TOKEN0.clone(), TOKEN1.clone()), + CurrencyAmount::from_raw_amount(TOKEN0.clone(), 100).unwrap(), + TradeType::ExactInput, + ) + .unwrap(); + let input_amount = trade.input_amount().unwrap(); + let params = + quote_call_parameters(&trade.swaps[0].route, input_amount, trade.trade_type, None); + assert_eq!( + params.calldata, + hex!("f7729d43000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!(params.value, U256::ZERO); + } + + #[test] + fn single_hop_exact_output() { + let mut trade = Trade::from_route( + Route::new(vec![POOL_0_1.clone()], TOKEN0.clone(), TOKEN1.clone()), + CurrencyAmount::from_raw_amount(TOKEN1.clone(), 100).unwrap(), + TradeType::ExactOutput, + ) + .unwrap(); + let output_amount = trade.output_amount().unwrap(); + let params = + quote_call_parameters(&trade.swaps[0].route, output_amount, trade.trade_type, None); + assert_eq!( + params.calldata, + hex!("30d07f21000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!(params.value, U256::ZERO); + } + + #[test] + fn multi_hop_exact_input() { + let mut trade = Trade::from_route( + Route::new( + vec![POOL_0_1.clone(), POOL_1_WETH.clone()], + TOKEN0.clone(), + WETH.clone(), + ), + CurrencyAmount::from_raw_amount(TOKEN0.clone(), 100).unwrap(), + TradeType::ExactInput, + ) + .unwrap(); + let params = quote_call_parameters( + &trade.route(), + trade.input_amount().unwrap(), + trade.trade_type, + None, + ); + assert_eq!( + params.calldata, + hex!("cdca17530000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!(params.value, U256::ZERO); + } + + #[test] + fn multi_hop_exact_output() { + let mut trade = Trade::from_route( + Route::new( + vec![POOL_0_1.clone(), POOL_1_WETH.clone()], + TOKEN0.clone(), + WETH.clone(), + ), + CurrencyAmount::from_raw_amount(WETH.clone(), 100).unwrap(), + TradeType::ExactOutput, + ) + .unwrap(); + let params = quote_call_parameters( + &trade.route(), + trade.output_amount().unwrap(), + trade.trade_type, + None, + ); + assert_eq!( + params.calldata, + hex!("2f80bb1d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!(params.value, U256::ZERO); + } + + #[test] + fn sqrt_price_limit_x96() { + let mut trade = Trade::from_route( + Route::new(vec![POOL_0_1.clone()], TOKEN0.clone(), TOKEN1.clone()), + CurrencyAmount::from_raw_amount(TOKEN0.clone(), 100).unwrap(), + TradeType::ExactInput, + ) + .unwrap(); + let params = quote_call_parameters( + &trade.route(), + trade.input_amount().unwrap(), + trade.trade_type, + Some(QuoteOptions { + sqrt_price_limit_x96: U256::from_limbs([0, 0, 1, 0]), + use_quoter_v2: false, + }), + ); + assert_eq!( + params.calldata, + hex!("f7729d43000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000100000000000000000000000000000000") + ); + assert_eq!(params.value, U256::ZERO); + } + } + + mod single_trade_input_using_quoter_v2 { + use super::*; + use alloy_primitives::hex; + + #[test] + fn single_hop_exact_input() { + let mut trade = Trade::from_route( + Route::new(vec![POOL_0_1.clone()], TOKEN0.clone(), TOKEN1.clone()), + CurrencyAmount::from_raw_amount(TOKEN0.clone(), 100).unwrap(), + TradeType::ExactInput, + ) + .unwrap(); + let input_amount = trade.input_amount().unwrap(); + let params = quote_call_parameters( + &trade.swaps[0].route, + input_amount, + trade.trade_type, + Some(QuoteOptions { + sqrt_price_limit_x96: U256::ZERO, + use_quoter_v2: true, + }), + ); + assert_eq!( + params.calldata, + hex!("c6a5026a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000000"), + ); + } + + #[test] + fn single_hop_exact_output() { + let mut trade = Trade::from_route( + Route::new(vec![POOL_0_1.clone()], TOKEN0.clone(), TOKEN1.clone()), + CurrencyAmount::from_raw_amount(TOKEN1.clone(), 100).unwrap(), + TradeType::ExactOutput, + ) + .unwrap(); + let output_amount = trade.output_amount().unwrap(); + let params = quote_call_parameters( + &trade.swaps[0].route, + output_amount, + trade.trade_type, + Some(QuoteOptions { + sqrt_price_limit_x96: U256::ZERO, + use_quoter_v2: true, + }), + ); + assert_eq!( + params.calldata, + hex!("bd21704a0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000bb80000000000000000000000000000000000000000000000000000000000000000"), + ); + } + } +} diff --git a/src/utils/encode_route_to_path.rs b/src/utils/encode_route_to_path.rs new file mode 100644 index 0000000..246a220 --- /dev/null +++ b/src/utils/encode_route_to_path.rs @@ -0,0 +1,257 @@ +use crate::prelude::Route; +use alloy_sol_types::{sol, SolType, SolValue}; +use uniswap_sdk_core::prelude::*; + +type TokenFee = sol!((address, uint24)); + +/// Converts a route to a hex encoded path +/// +/// ## Arguments +/// +/// * `route`: the v3 path to convert to an encoded path +/// * `exact_output`: whether the route should be encoded in reverse, for making exact output swaps +/// +pub fn encode_route_to_path( + route: &Route, + exact_output: bool, +) -> Vec { + let mut path: Vec = Vec::with_capacity(23 * route.pools.len() + 20); + if exact_output { + let mut output_token = &route.output.wrapped(); + for pool in route.pools.iter().rev() { + let leg = if pool.token0.equals(output_token) { + output_token = &pool.token1; + (pool.token0.address(), pool.fee as u32) + } else { + output_token = &pool.token0; + (pool.token1.address(), pool.fee as u32) + }; + path.extend(TokenFee::abi_encode_packed(&leg)); + } + path.extend(route.input.address().abi_encode_packed()); + } else { + let mut input_token = &route.input.wrapped(); + for pool in route.pools.iter() { + let leg = if pool.token0.equals(input_token) { + input_token = &pool.token1; + (pool.token0.address(), pool.fee as u32) + } else { + input_token = &pool.token0; + (pool.token1.address(), pool.fee as u32) + }; + path.extend(TokenFee::abi_encode_packed(&leg)); + } + path.extend(route.output.address().abi_encode_packed()); + } + path +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::prelude::*; + use alloy_primitives::hex; + use once_cell::sync::Lazy; + use uniswap_sdk_core::token; + + static ETHER: Lazy = Lazy::new(|| Ether::on_chain(1)); + static TOKEN0: Lazy = Lazy::new(|| { + token!( + 1, + "0x0000000000000000000000000000000000000001", + 18, + "t0", + "token0" + ) + }); + static TOKEN1: Lazy = Lazy::new(|| { + token!( + 1, + "0x0000000000000000000000000000000000000002", + 18, + "t1", + "token1" + ) + }); + static TOKEN2: Lazy = Lazy::new(|| { + token!( + 1, + "0x0000000000000000000000000000000000000003", + 18, + "t2", + "token2" + ) + }); + static WETH: Lazy = Lazy::new(|| ETHER.wrapped()); + + static POOL_0_1_MEDIUM: Lazy> = Lazy::new(|| { + Pool::new( + TOKEN0.clone(), + TOKEN1.clone(), + FeeAmount::MEDIUM, + encode_sqrt_ratio_x96(1, 1), + 0, + ) + .unwrap() + }); + static POOL_1_2_LOW: Lazy> = Lazy::new(|| { + Pool::new( + TOKEN1.clone(), + TOKEN2.clone(), + FeeAmount::LOW, + encode_sqrt_ratio_x96(1, 1), + 0, + ) + .unwrap() + }); + static POOL_0_WETH: Lazy> = Lazy::new(|| { + Pool::new( + TOKEN0.clone(), + WETH.clone(), + FeeAmount::MEDIUM, + encode_sqrt_ratio_x96(1, 1), + 0, + ) + .unwrap() + }); + static POOL_1_WETH: Lazy> = Lazy::new(|| { + Pool::new( + TOKEN1.clone(), + WETH.clone(), + FeeAmount::MEDIUM, + encode_sqrt_ratio_x96(1, 1), + 0, + ) + .unwrap() + }); + + static ROUTE_0_1: Lazy> = Lazy::new(|| { + Route::new( + vec![POOL_0_1_MEDIUM.clone()], + TOKEN0.clone(), + TOKEN1.clone(), + ) + }); + static ROUTE_0_1_2: Lazy> = Lazy::new(|| { + Route::new( + vec![POOL_0_1_MEDIUM.clone(), POOL_1_2_LOW.clone()], + TOKEN0.clone(), + TOKEN2.clone(), + ) + }); + + static ROUTE_0_WETH: Lazy> = + Lazy::new(|| Route::new(vec![POOL_0_WETH.clone()], TOKEN0.clone(), ETHER.clone())); + static ROUTE_0_1_WETH: Lazy> = Lazy::new(|| { + Route::new( + vec![POOL_0_1_MEDIUM.clone(), POOL_1_WETH.clone()], + TOKEN0.clone(), + ETHER.clone(), + ) + }); + static ROUTE_WETH_0: Lazy> = + Lazy::new(|| Route::new(vec![POOL_0_WETH.clone()], ETHER.clone(), TOKEN0.clone())); + static ROUTE_WETH_0_1: Lazy> = Lazy::new(|| { + Route::new( + vec![POOL_0_WETH.clone(), POOL_0_1_MEDIUM.clone()], + ETHER.clone(), + TOKEN1.clone(), + ) + }); + + #[test] + fn pack_them_for_exact_input_single_hop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_1, false), + hex!("0000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002") + ); + } + + #[test] + fn pack_them_for_exact_output_single_hop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_1, true), + hex!("0000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001") + ); + } + + #[test] + fn pack_them_for_exact_input_multihop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_1_2, false), + hex!("0000000000000000000000000000000000000001000bb800000000000000000000000000000000000000020001f40000000000000000000000000000000000000003") + ); + } + + #[test] + fn pack_them_for_exact_output_multihop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_1_2, true), + hex!("00000000000000000000000000000000000000030001f40000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001") + ); + } + + #[test] + fn wrap_ether_input_for_exact_input_single_hop() { + assert_eq!( + encode_route_to_path(&ROUTE_WETH_0, false), + hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000001") + ); + } + + #[test] + fn wrap_ether_input_for_exact_output_single_hop() { + assert_eq!( + encode_route_to_path(&ROUTE_WETH_0, true), + hex!("0000000000000000000000000000000000000001000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") + ); + } + + #[test] + fn wrap_ether_input_for_exact_input_multihop() { + assert_eq!( + encode_route_to_path(&ROUTE_WETH_0_1, false), + hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002") + ); + } + + #[test] + fn wrap_ether_input_for_exact_output_multihop() { + assert_eq!( + encode_route_to_path(&ROUTE_WETH_0_1, true), + hex!("0000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") + ); + } + + #[test] + fn wrap_ether_output_for_exact_input_single_hop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_WETH, false), + hex!("0000000000000000000000000000000000000001000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") + ); + } + + #[test] + fn wrap_ether_output_for_exact_output_single_hop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_WETH, true), + hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000001") + ); + } + + #[test] + fn wrap_ether_output_for_exact_input_multihop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_1_WETH, false), + hex!("0000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") + ); + } + + #[test] + fn wrap_ether_output_for_exact_output_multihop() { + assert_eq!( + encode_route_to_path(&ROUTE_0_1_WETH, true), + hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001") + ); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f3d1467..52588a0 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,6 @@ mod bit_math; mod compute_pool_address; +mod encode_route_to_path; mod encode_sqrt_ratio_x96; mod full_math; mod get_fee_growth_inside; @@ -16,6 +17,7 @@ mod types; pub use bit_math::*; pub use compute_pool_address::compute_pool_address; +pub use encode_route_to_path::encode_route_to_path; pub use encode_sqrt_ratio_x96::encode_sqrt_ratio_x96; pub use full_math::*; pub use get_fee_growth_inside::*;