From b70f84a13ef1897da395a312eae2abb2084d36a4 Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Fri, 6 Sep 2024 16:17:11 +0200 Subject: [PATCH 01/11] WIP --- core/lib/external_price_api/src/coingecko_api.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/lib/external_price_api/src/coingecko_api.rs b/core/lib/external_price_api/src/coingecko_api.rs index 8fa7514b3684..a710a610497d 100644 --- a/core/lib/external_price_api/src/coingecko_api.rs +++ b/core/lib/external_price_api/src/coingecko_api.rs @@ -110,3 +110,8 @@ impl CoinGeckoPriceResponse { .and_then(|price| price.get(currency)) } } + +#[cfg(test)] +mod test { + fn setup() {} +} From 7950d15841db59c09280a9fa244aed70ff3603cb Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Wed, 25 Sep 2024 17:35:09 +0200 Subject: [PATCH 02/11] get_fraction test --- core/lib/external_price_api/src/utils.rs | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/core/lib/external_price_api/src/utils.rs b/core/lib/external_price_api/src/utils.rs index 879be44e1737..f6d00b080fee 100644 --- a/core/lib/external_price_api/src/utils.rs +++ b/core/lib/external_price_api/src/utils.rs @@ -13,3 +13,32 @@ pub fn get_fraction(ratio_f64: f64) -> (NonZeroU64, NonZeroU64) { (numerator, denominator) } + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + fn assert_get_fraction_value(f: f64, num: u64, denum: u64) { + assert_eq!( + get_fraction(f), + ( + NonZeroU64::try_from(num).unwrap(), + NonZeroU64::try_from(denum).unwrap() + ) + ); + } + + #[test] + fn test_float_to_fraction_conversion_as_expected() { + assert_get_fraction_value(1.0, 1, 1); + assert_get_fraction_value(1337.0, 1337, 1); + assert_get_fraction_value(0.1, 1, 10); + assert_get_fraction_value(3.141, 3141, 1000); + assert_get_fraction_value(1_000_000.0, 1_000_000, 1); + assert_get_fraction_value(3123.47, 312347, 100); + // below tests assume some not necessarily required behaviour of get_fraction + assert_get_fraction_value(0.2, 1, 5); + assert_get_fraction_value(0.5, 1, 2); + assert_get_fraction_value(3.1415, 6283, 2000); + } +} From 46935a071c5a281cb8fc60568b963bed931e7bcb Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Wed, 25 Sep 2024 17:35:35 +0200 Subject: [PATCH 03/11] WIP --- Cargo.lock | 355 +++++++++++++++++- Cargo.toml | 1 + core/lib/external_price_api/Cargo.toml | 1 + .../external_price_api/src/coingecko_api.rs | 19 +- core/lib/external_price_api/src/lib.rs | 1 + core/lib/external_price_api/src/tests.rs | 132 +++++++ 6 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 core/lib/external_price_api/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 8164d412af55..13beced2519e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,12 +233,52 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "assert_matches" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-channel" version = "2.3.1" @@ -275,6 +315,21 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + [[package]] name = "async-io" version = "2.3.4" @@ -316,13 +371,22 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "async-object-pool" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" +dependencies = [ + "async-std", +] + [[package]] name = "async-process" version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-io", "async-lock", "async-signal", @@ -365,6 +429,34 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers 0.3.0", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -594,6 +686,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "basic-toml" version = "0.1.4" @@ -689,6 +792,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + [[package]] name = "bit-vec" version = "0.6.3" @@ -862,7 +974,7 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-task", "futures-io", "futures-lite", @@ -1980,6 +2092,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -2127,6 +2260,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -2262,6 +2404,12 @@ dependencies = [ "uint", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "event-listener" version = "4.0.3" @@ -2622,7 +2770,7 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" dependencies = [ - "gloo-timers", + "gloo-timers 0.2.6", "send_wrapper", ] @@ -2758,6 +2906,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -3147,6 +3307,34 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.5", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.29", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + [[package]] name = "hyper" version = "0.14.29" @@ -3467,6 +3655,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.0" @@ -3835,6 +4032,46 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.8.2", + "string_cache", + "term", + "tiny-keccak 2.0.2", + "unicode-xid 0.2.4", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.7", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3856,6 +4093,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" version = "0.2.155" @@ -3878,6 +4121,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + [[package]] name = "librocksdb-sys" version = "0.11.0+8.1.1" @@ -4023,6 +4276,9 @@ name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +dependencies = [ + "value-bag", +] [[package]] name = "logos" @@ -4276,6 +4532,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.27.1" @@ -4884,6 +5146,21 @@ dependencies = [ "indexmap 2.1.0", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.3" @@ -5042,6 +5319,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "pretty_assertions" version = "1.4.0" @@ -5504,6 +5787,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.6" @@ -6481,6 +6775,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6689,6 +6993,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "siphasher" version = "1.0.1" @@ -6744,7 +7054,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-executor", "async-fs", "async-io", @@ -6800,7 +7110,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sha3 0.10.8", - "siphasher", + "siphasher 1.0.1", "slab", "smallvec", "soketto 0.7.1", @@ -6816,7 +7126,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-lock", "base64 0.21.5", "blake2-rfc", @@ -6839,7 +7149,7 @@ dependencies = [ "rand_chacha", "serde", "serde_json", - "siphasher", + "siphasher 1.0.1", "slab", "smol", "smoldot", @@ -7204,6 +7514,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "stringprep" version = "0.1.4" @@ -7524,6 +7847,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -8276,6 +8610,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + [[package]] name = "vcpkg" version = "0.2.15" @@ -9918,6 +10258,7 @@ dependencies = [ "bigdecimal", "chrono", "fraction", + "httpmock", "rand 0.8.5", "reqwest 0.12.5", "serde", diff --git a/Cargo.toml b/Cargo.toml index 5a8a507b0340..0c83564cbf37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,6 +128,7 @@ google-cloud-storage = "0.20.0" governor = "0.4.2" hex = "0.4" http = "1.1" +httpmock = "0.7.0" hyper = "1.3" iai = "0.1" insta = "1.29.0" diff --git a/core/lib/external_price_api/Cargo.toml b/core/lib/external_price_api/Cargo.toml index 9539aa3fdc3c..3eee675b4e65 100644 --- a/core/lib/external_price_api/Cargo.toml +++ b/core/lib/external_price_api/Cargo.toml @@ -24,3 +24,4 @@ rand.workspace = true zksync_config.workspace = true zksync_types.workspace = true tokio.workspace = true +httpmock.workspace = true diff --git a/core/lib/external_price_api/src/coingecko_api.rs b/core/lib/external_price_api/src/coingecko_api.rs index a710a610497d..258065c3fd3f 100644 --- a/core/lib/external_price_api/src/coingecko_api.rs +++ b/core/lib/external_price_api/src/coingecko_api.rs @@ -113,5 +113,22 @@ impl CoinGeckoPriceResponse { #[cfg(test)] mod test { - fn setup() {} + use httpmock::MockServer; + + fn auth_check_create( + auth_header: String, + api_key: Option<String>, + ) -> impl (FnOnce(httpmock::When) -> httpmock::When) { + |when: httpmock::When| -> httpmock::When { + if let Some(x) = api_key { + when.header(auth_header, x) + } else { + when + } + } + } + + fn setup() { + let server = MockServer::start(); + } } diff --git a/core/lib/external_price_api/src/lib.rs b/core/lib/external_price_api/src/lib.rs index e86279dbe850..1f0dad0f7e4e 100644 --- a/core/lib/external_price_api/src/lib.rs +++ b/core/lib/external_price_api/src/lib.rs @@ -1,5 +1,6 @@ pub mod coingecko_api; pub mod forced_price_client; +mod tests; mod utils; use std::fmt; diff --git a/core/lib/external_price_api/src/tests.rs b/core/lib/external_price_api/src/tests.rs new file mode 100644 index 000000000000..7b1193aa3996 --- /dev/null +++ b/core/lib/external_price_api/src/tests.rs @@ -0,0 +1,132 @@ +#[cfg(test)] +pub(crate) mod tests { + use std::{collections::HashMap, str::FromStr}; + + use bigdecimal::BigDecimal; + use chrono::Utc; + use httpmock::{Mock, MockServer}; + use url::Url; + use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; + + use crate::{utils::get_fraction, PriceAPIClient}; + + const TIME_TOLERANCE_MS: i64 = 100; + + pub(crate) type SetupFn = fn( + server: &MockServer, + api_key: Option<String>, + address: Address, + base_token_price: f64, + ) -> Box<dyn PriceAPIClient>; + + pub(crate) fn server_url(server: &MockServer) -> Url { + Url::from_str(server.url("").as_str()).unwrap() + } + + pub(crate) fn add_mock( + server: &MockServer, + method: httpmock::Method, + path: String, + query_params: HashMap<String, String>, + response_status: u16, + response_body: String, + auth_check: fn(when: httpmock::When) -> httpmock::When, + ) -> Mock { + server.mock(|mut when, then| { + when = when.method(method).path(path); + + for (k, v) in &query_params { + when = when.query_param(k, v); + } + when = auth_check(when); + then.status(response_status).body(response_body); + }) + } + + pub(crate) async fn happy_day_test(api_key: Option<String>, setup: SetupFn) { + let server = MockServer::start(); + let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; //Uniswap (UNI) + let address = Address::from_str(address_str).unwrap(); + let base_token_price = 198.9; + + let mut client = setup(&server, api_key, address, base_token_price); + let api_price = client.fetch_ratio(address).await.unwrap(); + + let (numerator, denominator) = get_fraction(base_token_price); + + assert_eq!( + BaseTokenAPIRatio { + numerator, + denominator, + ratio_timestamp: api_price.ratio_timestamp, + }, + api_price + ); + assert!((Utc::now() - api_price.ratio_timestamp).num_milliseconds() <= TIME_TOLERANCE_MS); + } + + pub(crate) async fn no_eth_price_404_test(api_key: Option<String>, setup: SetupFn) { + let server = MockServer::start(); + let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; + let address = Address::from_str(address_str).unwrap(); + let mut client = setup(&server, api_key, address, 1.0); + let api_price = client.fetch_ratio(address).await; + + assert!(api_price.is_err()); + assert!(api_price + .err() + .unwrap() + .to_string() + .starts_with("Http error while fetching token price. Status: 404 Not Found")) + } + + pub(crate) async fn eth_price_not_found_test(api_key: Option<String>, setup: SetupFn) { + let server = MockServer::start(); + let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; + let address = Address::from_str("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984").unwrap(); + + let mut client = setup(&server, api_key, address, 1.0); + let api_price = client + .fetch_ratio(Address::from_str(address_str).unwrap()) + .await; + + assert!(api_price.is_err()); + assert!(api_price + .err() + .unwrap() + .to_string() + .starts_with("Price not found for token")) + } + + pub(crate) async fn no_base_token_price_404_test(api_key: Option<String>, setup: SetupFn) { + let server = MockServer::start(); + let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; + let address = Address::from_str(address_str).unwrap(); + + let mut client = setup(&server, api_key, address, 1.0); + let api_price = client.fetch_ratio(address).await; + + assert!(api_price.is_err()); + assert!(api_price + .err() + .unwrap() + .to_string() + .starts_with("Http error while fetching token price. Status: 404 Not Found")) + } + + pub(crate) async fn base_token_price_not_found_test(api_key: Option<String>, setup: SetupFn) { + let server = MockServer::start(); + let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; + let address = Address::from_str(address_str).unwrap(); + + let mut client = setup(&server, api_key, address, 1.0); + let api_price = client.fetch_ratio(address).await; + + assert!(api_price.is_err()); + assert!(api_price + .err() + .unwrap() + .to_string() + .starts_with("Price not found for token")) + } +} From ed35b68616006a74f8a6c1e44122903806693585 Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Thu, 26 Sep 2024 14:33:42 +0200 Subject: [PATCH 04/11] feat: finished coingecko_api.rs tests --- .../external_price_api/src/coingecko_api.rs | 169 ++++++++++++++++-- core/lib/external_price_api/src/lib.rs | 1 + core/lib/external_price_api/src/tests.rs | 164 +++++------------ core/lib/external_price_api/src/utils.rs | 1 + 4 files changed, 202 insertions(+), 133 deletions(-) diff --git a/core/lib/external_price_api/src/coingecko_api.rs b/core/lib/external_price_api/src/coingecko_api.rs index 258065c3fd3f..3c4181f3ee1b 100644 --- a/core/lib/external_price_api/src/coingecko_api.rs +++ b/core/lib/external_price_api/src/coingecko_api.rs @@ -114,21 +114,170 @@ impl CoinGeckoPriceResponse { #[cfg(test)] mod test { use httpmock::MockServer; + use zksync_config::configs::external_price_api_client::DEFAULT_TIMEOUT_MS; - fn auth_check_create( - auth_header: String, + use super::*; + use crate::tests::*; + + fn auth_check(when: httpmock::When, api_key: Option<String>) -> httpmock::When { + if let Some(key) = api_key { + when.header(COINGECKO_AUTH_HEADER, key) + } else { + when + } + } + + fn get_mock_response(address: &str, price: f64) -> String { + format!("{{\"{}\":{{\"eth\":{}}}}}", address, price) + } + + #[test] + fn test_mock_response() { + // curl "https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0x1f9840a85d5af5bf1d1762f925bdaddc4201f984&vs_currencies=eth" + // {"0x1f9840a85d5af5bf1d1762f925bdaddc4201f984":{"eth":0.00269512}} + assert_eq!( + get_mock_response("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", 0.00269512), + r#"{"0x1f9840a85d5af5bf1d1762f925bdaddc4201f984":{"eth":0.00269512}}"# + ) + } + + fn add_mock_by_address( + server: &MockServer, + // use string explicitly to verify that conversion of the address to string works as expected + address: String, + price: Option<f64>, api_key: Option<String>, - ) -> impl (FnOnce(httpmock::When) -> httpmock::When) { - |when: httpmock::When| -> httpmock::When { - if let Some(x) = api_key { - when.header(auth_header, x) + ) { + server.mock(|mut when, then| { + when = when + .method(httpmock::Method::GET) + .path("/api/v3/simple/token_price/ethereum"); + + when = when.query_param("contract_addresses", address.clone()); + when = when.query_param("vs_currencies", ETH_ID); + auth_check(when, api_key); + + if let Some(p) = price { + then.status(200).body(get_mock_response(&address, p)); } else { - when - } + // requesting with invalid/unknown address results in empty json + // example: + // $ curl "https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0x000000000000000000000000000000000000dead&vs_currencies=eth" + // {} + then.status(200).body("{}"); + }; + }); + } + + fn get_config(base_url: String, api_key: Option<String>) -> ExternalPriceApiClientConfig { + ExternalPriceApiClientConfig { + base_url: Some(base_url), + api_key, + source: "FILLER".to_string(), + client_timeout_ms: DEFAULT_TIMEOUT_MS, + forced: None, + } + } + + fn happy_day_setup( + api_key: Option<String>, + server: &MockServer, + address: Address, + base_token_price: f64, + ) -> SetupResult { + add_mock_by_address( + server, + address_to_string(&address), + Some(base_token_price), + api_key.clone(), + ); + SetupResult { + client: Box::new(CoinGeckoPriceAPIClient::new(get_config( + server.url(""), + api_key, + ))), + } + } + + fn happy_day_setup_with_key( + server: &MockServer, + address: Address, + base_token_price: f64, + ) -> SetupResult { + happy_day_setup( + Some("test-key".to_string()), + server, + address, + base_token_price, + ) + } + + #[tokio::test] + async fn test_happy_day_with_api_key() { + happy_day_test(happy_day_setup_with_key).await + } + + fn happy_day_setup_no_key( + server: &MockServer, + address: Address, + base_token_price: f64, + ) -> SetupResult { + happy_day_setup(None, server, address, base_token_price) + } + + #[tokio::test] + async fn test_happy_day_with_no_api_key() { + happy_day_test(happy_day_setup_no_key).await + } + + fn error_404_setup( + server: &MockServer, + _address: Address, + _base_token_price: f64, + ) -> SetupResult { + // just don't add mock + SetupResult { + client: Box::new(CoinGeckoPriceAPIClient::new(get_config( + server.url(""), + Some("FILLER".to_string()), + ))), + } + } + + #[tokio::test] + async fn test_error_404() { + let error_string = error_test(error_404_setup).await.to_string(); + assert!( + error_string + .starts_with("Http error while fetching token price. Status: 404 Not Found"), + "Error was: {}", + &error_string + ) + } + + fn error_missing_setup( + server: &MockServer, + address: Address, + _base_token_price: f64, + ) -> SetupResult { + let api_key = Some("FILLER".to_string()); + + add_mock_by_address(server, address_to_string(&address), None, api_key.clone()); + SetupResult { + client: Box::new(CoinGeckoPriceAPIClient::new(get_config( + server.url(""), + api_key, + ))), } } - fn setup() { - let server = MockServer::start(); + #[tokio::test] + async fn test_error_missing() { + let error_string = error_test(error_missing_setup).await.to_string(); + assert!( + error_string.starts_with("Price not found for token"), + "Error was: {}", + error_string + ) } } diff --git a/core/lib/external_price_api/src/lib.rs b/core/lib/external_price_api/src/lib.rs index 1f0dad0f7e4e..9bc54c771d30 100644 --- a/core/lib/external_price_api/src/lib.rs +++ b/core/lib/external_price_api/src/lib.rs @@ -1,5 +1,6 @@ pub mod coingecko_api; pub mod forced_price_client; +#[cfg(test)] mod tests; mod utils; diff --git a/core/lib/external_price_api/src/tests.rs b/core/lib/external_price_api/src/tests.rs index 7b1193aa3996..b06d90be9b40 100644 --- a/core/lib/external_price_api/src/tests.rs +++ b/core/lib/external_price_api/src/tests.rs @@ -1,132 +1,50 @@ -#[cfg(test)] -pub(crate) mod tests { - use std::{collections::HashMap, str::FromStr}; +use std::str::FromStr; - use bigdecimal::BigDecimal; - use chrono::Utc; - use httpmock::{Mock, MockServer}; - use url::Url; - use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; +use chrono::Utc; +use httpmock::MockServer; +use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; - use crate::{utils::get_fraction, PriceAPIClient}; +use crate::{utils::get_fraction, PriceAPIClient}; - const TIME_TOLERANCE_MS: i64 = 100; +const TIME_TOLERANCE_MS: i64 = 100; - pub(crate) type SetupFn = fn( - server: &MockServer, - api_key: Option<String>, - address: Address, - base_token_price: f64, - ) -> Box<dyn PriceAPIClient>; - - pub(crate) fn server_url(server: &MockServer) -> Url { - Url::from_str(server.url("").as_str()).unwrap() - } - - pub(crate) fn add_mock( - server: &MockServer, - method: httpmock::Method, - path: String, - query_params: HashMap<String, String>, - response_status: u16, - response_body: String, - auth_check: fn(when: httpmock::When) -> httpmock::When, - ) -> Mock { - server.mock(|mut when, then| { - when = when.method(method).path(path); - - for (k, v) in &query_params { - when = when.query_param(k, v); - } - when = auth_check(when); - then.status(response_status).body(response_body); - }) - } - - pub(crate) async fn happy_day_test(api_key: Option<String>, setup: SetupFn) { - let server = MockServer::start(); - let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; //Uniswap (UNI) - let address = Address::from_str(address_str).unwrap(); - let base_token_price = 198.9; - - let mut client = setup(&server, api_key, address, base_token_price); - let api_price = client.fetch_ratio(address).await.unwrap(); - - let (numerator, denominator) = get_fraction(base_token_price); - - assert_eq!( - BaseTokenAPIRatio { - numerator, - denominator, - ratio_timestamp: api_price.ratio_timestamp, - }, - api_price - ); - assert!((Utc::now() - api_price.ratio_timestamp).num_milliseconds() <= TIME_TOLERANCE_MS); - } - - pub(crate) async fn no_eth_price_404_test(api_key: Option<String>, setup: SetupFn) { - let server = MockServer::start(); - let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; - let address = Address::from_str(address_str).unwrap(); - let mut client = setup(&server, api_key, address, 1.0); - let api_price = client.fetch_ratio(address).await; - - assert!(api_price.is_err()); - assert!(api_price - .err() - .unwrap() - .to_string() - .starts_with("Http error while fetching token price. Status: 404 Not Found")) - } - - pub(crate) async fn eth_price_not_found_test(api_key: Option<String>, setup: SetupFn) { - let server = MockServer::start(); - let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; - let address = Address::from_str("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984").unwrap(); - - let mut client = setup(&server, api_key, address, 1.0); - let api_price = client - .fetch_ratio(Address::from_str(address_str).unwrap()) - .await; - - assert!(api_price.is_err()); - assert!(api_price - .err() - .unwrap() - .to_string() - .starts_with("Price not found for token")) - } - - pub(crate) async fn no_base_token_price_404_test(api_key: Option<String>, setup: SetupFn) { - let server = MockServer::start(); - let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; - let address = Address::from_str(address_str).unwrap(); - - let mut client = setup(&server, api_key, address, 1.0); - let api_price = client.fetch_ratio(address).await; +pub(crate) struct SetupResult { + pub(crate) client: Box<dyn PriceAPIClient>, +} - assert!(api_price.is_err()); - assert!(api_price - .err() - .unwrap() - .to_string() - .starts_with("Http error while fetching token price. Status: 404 Not Found")) - } +pub(crate) type SetupFn = + fn(server: &MockServer, address: Address, base_token_price: f64) -> SetupResult; + +pub(crate) async fn happy_day_test(setup: SetupFn) { + let server = MockServer::start(); + let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; //Uniswap (UNI) + let address = Address::from_str(address_str).unwrap(); + let base_token_price = 198.9; + + let SetupResult { client } = setup(&server, address, base_token_price); + let api_price = client.fetch_ratio(address).await.unwrap(); + + let (numerator, denominator) = get_fraction(base_token_price); + + assert_eq!( + BaseTokenAPIRatio { + numerator, + denominator, + ratio_timestamp: api_price.ratio_timestamp, + }, + api_price + ); + assert!((Utc::now() - api_price.ratio_timestamp).num_milliseconds() <= TIME_TOLERANCE_MS); +} - pub(crate) async fn base_token_price_not_found_test(api_key: Option<String>, setup: SetupFn) { - let server = MockServer::start(); - let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; - let address = Address::from_str(address_str).unwrap(); +pub(crate) async fn error_test(setup: SetupFn) -> anyhow::Error { + let server = MockServer::start(); + let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; + let address = Address::from_str(address_str).unwrap(); - let mut client = setup(&server, api_key, address, 1.0); - let api_price = client.fetch_ratio(address).await; + let SetupResult { client } = setup(&server, address, 1.0); + let api_price = client.fetch_ratio(address).await; - assert!(api_price.is_err()); - assert!(api_price - .err() - .unwrap() - .to_string() - .starts_with("Price not found for token")) - } + assert!(api_price.is_err()); + api_price.err().unwrap() } diff --git a/core/lib/external_price_api/src/utils.rs b/core/lib/external_price_api/src/utils.rs index f6d00b080fee..c285a9095c68 100644 --- a/core/lib/external_price_api/src/utils.rs +++ b/core/lib/external_price_api/src/utils.rs @@ -28,6 +28,7 @@ pub(crate) mod tests { ); } + #[allow(clippy::approx_constant)] #[test] fn test_float_to_fraction_conversion_as_expected() { assert_get_fraction_value(1.0, 1, 1); From c65450645b07278944b18dd367aeec4ea8b38c14 Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Thu, 26 Sep 2024 16:06:19 +0200 Subject: [PATCH 05/11] fix: ignore atty advisory --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index c2775fc057c8..559076a575bc 100644 --- a/deny.toml +++ b/deny.toml @@ -8,6 +8,7 @@ feature-depth = 1 [advisories] ignore = [ + "RUSTSEC-2024-0375", # atty dependency being unmaintained, dependency of clap and criterion, we would need to update to newer major of dependencies "RUSTSEC-2024-0320", # yaml_rust dependency being unmaintained, dependency in core, we should consider moving to yaml_rust2 fork "RUSTSEC-2020-0168", # mach dependency being unmaintained, dependency in consensus, we should consider moving to mach2 fork "RUSTSEC-2024-0370", # `cs_derive` needs to be updated to not rely on `proc-macro-error` From 3b219d08f594efd1b9eac77786808a94a301d486 Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Thu, 26 Sep 2024 16:36:46 +0200 Subject: [PATCH 06/11] fix: conversion from price to ratio reciprocal error --- core/lib/external_price_api/src/coingecko_api.rs | 9 ++++++--- core/lib/external_price_api/src/forced_price_client.rs | 2 +- core/lib/external_price_api/src/lib.rs | 1 + core/lib/external_price_api/src/tests.rs | 10 ++++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/core/lib/external_price_api/src/coingecko_api.rs b/core/lib/external_price_api/src/coingecko_api.rs index 3c4181f3ee1b..18ac9f175c51 100644 --- a/core/lib/external_price_api/src/coingecko_api.rs +++ b/core/lib/external_price_api/src/coingecko_api.rs @@ -49,6 +49,7 @@ impl CoinGeckoPriceAPIClient { } } + /// returns ETH/BaseToken price of a token by address async fn get_token_price_by_address(&self, address: Address) -> anyhow::Result<f64> { let address_str = address_to_string(&address); let price_url = self @@ -87,11 +88,13 @@ impl CoinGeckoPriceAPIClient { impl PriceAPIClient for CoinGeckoPriceAPIClient { async fn fetch_ratio(&self, token_address: Address) -> anyhow::Result<BaseTokenAPIRatio> { let base_token_in_eth = self.get_token_price_by_address(token_address).await?; - let (numerator, denominator) = get_fraction(base_token_in_eth); + let (num_in_eth, denom_in_eth) = get_fraction(base_token_in_eth); + // take reciprocal of price as returned price is ETH/BaseToken and BaseToken/ETH is needed + let (num_in_base, denom_in_base) = (denom_in_eth, num_in_eth); return Ok(BaseTokenAPIRatio { - numerator, - denominator, + numerator: num_in_base, + denominator: denom_in_base, ratio_timestamp: Utc::now(), }); } diff --git a/core/lib/external_price_api/src/forced_price_client.rs b/core/lib/external_price_api/src/forced_price_client.rs index fd166cdfd2da..a18c03fd8cab 100644 --- a/core/lib/external_price_api/src/forced_price_client.rs +++ b/core/lib/external_price_api/src/forced_price_client.rs @@ -7,7 +7,7 @@ use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; use crate::PriceAPIClient; -// Struct for a a forced price "client" (conversion ratio is always a configured "forced" ratio). +// Struct for a forced price "client" (conversion ratio is always a configured "forced" ratio). #[derive(Debug, Clone)] pub struct ForcedPriceClient { ratio: BaseTokenAPIRatio, diff --git a/core/lib/external_price_api/src/lib.rs b/core/lib/external_price_api/src/lib.rs index 9bc54c771d30..ff92c2128fe2 100644 --- a/core/lib/external_price_api/src/lib.rs +++ b/core/lib/external_price_api/src/lib.rs @@ -13,6 +13,7 @@ use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; #[async_trait] pub trait PriceAPIClient: Sync + Send + fmt::Debug + 'static { /// Returns the BaseToken<->ETH ratio for the input token address. + /// The returned unit is BaseToken/ETH. Example if 1 BaseToken = 0.002 ETH, then ratio is 500/1 async fn fetch_ratio(&self, token_address: Address) -> anyhow::Result<BaseTokenAPIRatio>; } diff --git a/core/lib/external_price_api/src/tests.rs b/core/lib/external_price_api/src/tests.rs index b06d90be9b40..896d2566e840 100644 --- a/core/lib/external_price_api/src/tests.rs +++ b/core/lib/external_price_api/src/tests.rs @@ -19,17 +19,19 @@ pub(crate) async fn happy_day_test(setup: SetupFn) { let server = MockServer::start(); let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; //Uniswap (UNI) let address = Address::from_str(address_str).unwrap(); - let base_token_price = 198.9; + let base_token_price = 0.00269; //ETH costs one token let SetupResult { client } = setup(&server, address, base_token_price); let api_price = client.fetch_ratio(address).await.unwrap(); - let (numerator, denominator) = get_fraction(base_token_price); + let (num_in_eth, denom_in_eth) = get_fraction(base_token_price); + let (ratio_num, ratio_denom) = (denom_in_eth, num_in_eth); + assert!(((ratio_num.get() as f64) / (ratio_denom.get() as f64) - 371.74).abs() < 0.1); assert_eq!( BaseTokenAPIRatio { - numerator, - denominator, + numerator: ratio_num, + denominator: ratio_denom, ratio_timestamp: api_price.ratio_timestamp, }, api_price From c6ddc1aa50908fb8399ba26fbda8a1bf746bae59 Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Fri, 27 Sep 2024 14:07:24 +0200 Subject: [PATCH 07/11] fix: review feedback --- .../external_price_api/src/coingecko_api.rs | 52 +++++++------------ core/lib/external_price_api/src/tests.rs | 38 ++++++++------ core/lib/external_price_api/src/utils.rs | 31 ++++++++++- 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/core/lib/external_price_api/src/coingecko_api.rs b/core/lib/external_price_api/src/coingecko_api.rs index 18ac9f175c51..41ae88209c91 100644 --- a/core/lib/external_price_api/src/coingecko_api.rs +++ b/core/lib/external_price_api/src/coingecko_api.rs @@ -122,14 +122,6 @@ mod test { use super::*; use crate::tests::*; - fn auth_check(when: httpmock::When, api_key: Option<String>) -> httpmock::When { - if let Some(key) = api_key { - when.header(COINGECKO_AUTH_HEADER, key) - } else { - when - } - } - fn get_mock_response(address: &str, price: f64) -> String { format!("{{\"{}\":{{\"eth\":{}}}}}", address, price) } @@ -158,7 +150,7 @@ mod test { when = when.query_param("contract_addresses", address.clone()); when = when.query_param("vs_currencies", ETH_ID); - auth_check(when, api_key); + api_key.map(|key| when.header(COINGECKO_AUTH_HEADER, key)); if let Some(p) = price { then.status(200).body(get_mock_response(&address, p)); @@ -176,7 +168,7 @@ mod test { ExternalPriceApiClientConfig { base_url: Some(base_url), api_key, - source: "FILLER".to_string(), + source: "coingecko".to_string(), client_timeout_ms: DEFAULT_TIMEOUT_MS, forced: None, } @@ -202,35 +194,29 @@ mod test { } } - fn happy_day_setup_with_key( - server: &MockServer, - address: Address, - base_token_price: f64, - ) -> SetupResult { - happy_day_setup( - Some("test-key".to_string()), - server, - address, - base_token_price, - ) - } - #[tokio::test] async fn test_happy_day_with_api_key() { - happy_day_test(happy_day_setup_with_key).await - } - - fn happy_day_setup_no_key( - server: &MockServer, - address: Address, - base_token_price: f64, - ) -> SetupResult { - happy_day_setup(None, server, address, base_token_price) + happy_day_test( + |server: &MockServer, address: Address, base_token_price: f64| { + happy_day_setup( + Some("test-key".to_string()), + server, + address, + base_token_price, + ) + }, + ) + .await } #[tokio::test] async fn test_happy_day_with_no_api_key() { - happy_day_test(happy_day_setup_no_key).await + happy_day_test( + |server: &MockServer, address: Address, base_token_price: f64| { + happy_day_setup(None, server, address, base_token_price) + }, + ) + .await } fn error_404_setup( diff --git a/core/lib/external_price_api/src/tests.rs b/core/lib/external_price_api/src/tests.rs index 896d2566e840..bb2af866cf5f 100644 --- a/core/lib/external_price_api/src/tests.rs +++ b/core/lib/external_price_api/src/tests.rs @@ -2,11 +2,19 @@ use std::str::FromStr; use chrono::Utc; use httpmock::MockServer; -use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; +use zksync_types::Address; -use crate::{utils::get_fraction, PriceAPIClient}; +use crate::PriceAPIClient; const TIME_TOLERANCE_MS: i64 = 100; +/// Uniswap (UNI) +const TEST_TOKEN_ADDRESS: &str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; +/// 1UNI = 0.00269ETH +const TEST_TOKEN_PRICE_ETH: f64 = 0.00269; +/// 1ETH = 371.74UNI; When converting gas price from ETH to UNI +/// you need to multiply by this value. Thus, this should be equal to the ratio. +const TEST_BASE_PRICE: f64 = 371.74; +const PRICE_FLOAT_COMPARE_TOLERANCE: f64 = 0.1; pub(crate) struct SetupResult { pub(crate) client: Box<dyn PriceAPIClient>, @@ -17,31 +25,27 @@ pub(crate) type SetupFn = pub(crate) async fn happy_day_test(setup: SetupFn) { let server = MockServer::start(); - let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; //Uniswap (UNI) + let address_str = TEST_TOKEN_ADDRESS; let address = Address::from_str(address_str).unwrap(); - let base_token_price = 0.00269; //ETH costs one token - let SetupResult { client } = setup(&server, address, base_token_price); + // APIs return token price in ETH (ETH per 1 token) + let SetupResult { client } = setup(&server, address, TEST_TOKEN_PRICE_ETH); let api_price = client.fetch_ratio(address).await.unwrap(); - let (num_in_eth, denom_in_eth) = get_fraction(base_token_price); - let (ratio_num, ratio_denom) = (denom_in_eth, num_in_eth); - assert!(((ratio_num.get() as f64) / (ratio_denom.get() as f64) - 371.74).abs() < 0.1); - - assert_eq!( - BaseTokenAPIRatio { - numerator: ratio_num, - denominator: ratio_denom, - ratio_timestamp: api_price.ratio_timestamp, - }, - api_price + // we expect the returned ratio to be such that when multiplying gas price in ETH you get gas + // price in base token. So we expect such ratio X that X Base = 1ETH + assert!( + ((api_price.numerator.get() as f64) / (api_price.denominator.get() as f64) + - TEST_BASE_PRICE) + .abs() + < PRICE_FLOAT_COMPARE_TOLERANCE ); assert!((Utc::now() - api_price.ratio_timestamp).num_milliseconds() <= TIME_TOLERANCE_MS); } pub(crate) async fn error_test(setup: SetupFn) -> anyhow::Error { let server = MockServer::start(); - let address_str = "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"; + let address_str = TEST_TOKEN_ADDRESS; let address = Address::from_str(address_str).unwrap(); let SetupResult { client } = setup(&server, address, 1.0); diff --git a/core/lib/external_price_api/src/utils.rs b/core/lib/external_price_api/src/utils.rs index c285a9095c68..9c5e8f18aa15 100644 --- a/core/lib/external_price_api/src/utils.rs +++ b/core/lib/external_price_api/src/utils.rs @@ -5,10 +5,13 @@ use fraction::Fraction; /// Using the base token price and eth price, calculate the fraction of the base token to eth. pub fn get_fraction(ratio_f64: f64) -> (NonZeroU64, NonZeroU64) { let rate_fraction = Fraction::from(ratio_f64); + if rate_fraction.sign() == Some(fraction::Sign::Minus) { + panic!("number is negative"); + } - let numerator = NonZeroU64::new(*rate_fraction.numer().expect("numerator is empty")) + let numerator = NonZeroU64::new(*rate_fraction.numer().expect("number is not rational")) .expect("numerator is zero"); - let denominator = NonZeroU64::new(*rate_fraction.denom().expect("denominator is empty")) + let denominator = NonZeroU64::new(*rate_fraction.denom().expect("number is not rational")) .expect("denominator is zero"); (numerator, denominator) @@ -42,4 +45,28 @@ pub(crate) mod tests { assert_get_fraction_value(0.5, 1, 2); assert_get_fraction_value(3.1415, 6283, 2000); } + + #[should_panic(expected = "numerator is zero")] + #[test] + fn test_zero_panics() { + get_fraction(0.0); + } + + #[should_panic(expected = "number is negative")] + #[test] + fn test_negative() { + get_fraction(-1.0); + } + + #[should_panic(expected = "number is not rational")] + #[test] + fn test_nan() { + get_fraction(f64::NAN); + } + + #[should_panic(expected = "number is not rational")] + #[test] + fn test_infinity() { + get_fraction(f64::INFINITY); + } } From e005f6c1a72afe7065cd7b80af721a4ce339f1db Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Fri, 27 Sep 2024 14:22:04 +0200 Subject: [PATCH 08/11] docs: better explanation of ratio value --- core/lib/external_price_api/src/coingecko_api.rs | 2 +- core/lib/external_price_api/src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/lib/external_price_api/src/coingecko_api.rs b/core/lib/external_price_api/src/coingecko_api.rs index 41ae88209c91..f383af35766f 100644 --- a/core/lib/external_price_api/src/coingecko_api.rs +++ b/core/lib/external_price_api/src/coingecko_api.rs @@ -49,7 +49,7 @@ impl CoinGeckoPriceAPIClient { } } - /// returns ETH/BaseToken price of a token by address + /// returns token price in ETH by token address. Returned value is X such that 1 TOKEN = X ETH. async fn get_token_price_by_address(&self, address: Address) -> anyhow::Result<f64> { let address_str = address_to_string(&address); let price_url = self diff --git a/core/lib/external_price_api/src/lib.rs b/core/lib/external_price_api/src/lib.rs index ff92c2128fe2..7a068f9b1cb5 100644 --- a/core/lib/external_price_api/src/lib.rs +++ b/core/lib/external_price_api/src/lib.rs @@ -13,7 +13,8 @@ use zksync_types::{base_token_ratio::BaseTokenAPIRatio, Address}; #[async_trait] pub trait PriceAPIClient: Sync + Send + fmt::Debug + 'static { /// Returns the BaseToken<->ETH ratio for the input token address. - /// The returned unit is BaseToken/ETH. Example if 1 BaseToken = 0.002 ETH, then ratio is 500/1 + /// The returned value is rational number X such that X BaseToken = 1 ETH. + /// Example if 1 BaseToken = 0.002 ETH, then ratio is 500/1 (500 BaseToken = 1ETH) async fn fetch_ratio(&self, token_address: Address) -> anyhow::Result<BaseTokenAPIRatio>; } From 408fba4f0581deaef83cd7bebc237434d830555a Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Fri, 27 Sep 2024 17:23:48 +0200 Subject: [PATCH 09/11] fix: error instead of panic on bad price --- .../external_price_api/src/coingecko_api.rs | 2 +- core/lib/external_price_api/src/utils.rs | 52 +++++++++++++------ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/core/lib/external_price_api/src/coingecko_api.rs b/core/lib/external_price_api/src/coingecko_api.rs index f383af35766f..cc882db95c36 100644 --- a/core/lib/external_price_api/src/coingecko_api.rs +++ b/core/lib/external_price_api/src/coingecko_api.rs @@ -88,7 +88,7 @@ impl CoinGeckoPriceAPIClient { impl PriceAPIClient for CoinGeckoPriceAPIClient { async fn fetch_ratio(&self, token_address: Address) -> anyhow::Result<BaseTokenAPIRatio> { let base_token_in_eth = self.get_token_price_by_address(token_address).await?; - let (num_in_eth, denom_in_eth) = get_fraction(base_token_in_eth); + let (num_in_eth, denom_in_eth) = get_fraction(base_token_in_eth)?; // take reciprocal of price as returned price is ETH/BaseToken and BaseToken/ETH is needed let (num_in_base, denom_in_base) = (denom_in_eth, num_in_eth); diff --git a/core/lib/external_price_api/src/utils.rs b/core/lib/external_price_api/src/utils.rs index 9c5e8f18aa15..90a0533ac629 100644 --- a/core/lib/external_price_api/src/utils.rs +++ b/core/lib/external_price_api/src/utils.rs @@ -3,18 +3,26 @@ use std::num::NonZeroU64; use fraction::Fraction; /// Using the base token price and eth price, calculate the fraction of the base token to eth. -pub fn get_fraction(ratio_f64: f64) -> (NonZeroU64, NonZeroU64) { +pub fn get_fraction(ratio_f64: f64) -> anyhow::Result<(NonZeroU64, NonZeroU64)> { let rate_fraction = Fraction::from(ratio_f64); if rate_fraction.sign() == Some(fraction::Sign::Minus) { - panic!("number is negative"); + return Err(anyhow::anyhow!("number is negative")); } - let numerator = NonZeroU64::new(*rate_fraction.numer().expect("number is not rational")) - .expect("numerator is zero"); - let denominator = NonZeroU64::new(*rate_fraction.denom().expect("number is not rational")) - .expect("denominator is zero"); + let numerator = NonZeroU64::new( + *rate_fraction + .numer() + .ok_or(anyhow::anyhow!("number is not rational"))?, + ) + .ok_or(anyhow::anyhow!("numerator is zero"))?; + let denominator = NonZeroU64::new( + *rate_fraction + .denom() + .ok_or(anyhow::anyhow!("number is not rational"))?, + ) + .ok_or(anyhow::anyhow!("denominator is zero"))?; - (numerator, denominator) + Ok((numerator, denominator)) } #[cfg(test)] @@ -23,7 +31,7 @@ pub(crate) mod tests { fn assert_get_fraction_value(f: f64, num: u64, denum: u64) { assert_eq!( - get_fraction(f), + get_fraction(f).unwrap(), ( NonZeroU64::try_from(num).unwrap(), NonZeroU64::try_from(denum).unwrap() @@ -46,27 +54,39 @@ pub(crate) mod tests { assert_get_fraction_value(3.1415, 6283, 2000); } - #[should_panic(expected = "numerator is zero")] #[test] fn test_zero_panics() { - get_fraction(0.0); + assert_eq!( + get_fraction(0.0).expect_err("did not error").to_string(), + "numerator is zero" + ); } - #[should_panic(expected = "number is negative")] #[test] fn test_negative() { - get_fraction(-1.0); + assert_eq!( + get_fraction(-1.0).expect_err("did not error").to_string(), + "number is negative" + ); } - #[should_panic(expected = "number is not rational")] #[test] fn test_nan() { - get_fraction(f64::NAN); + assert_eq!( + get_fraction(f64::NAN) + .expect_err("did not error") + .to_string(), + "number is not rational" + ); } - #[should_panic(expected = "number is not rational")] #[test] fn test_infinity() { - get_fraction(f64::INFINITY); + assert_eq!( + get_fraction(f64::INFINITY) + .expect_err("did not error") + .to_string(), + "number is not rational" + ); } } From 68e8b2ddf4996d080493624f05a2c098177fe1e6 Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Fri, 27 Sep 2024 17:25:33 +0200 Subject: [PATCH 10/11] better formatting --- core/lib/external_price_api/src/utils.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/core/lib/external_price_api/src/utils.rs b/core/lib/external_price_api/src/utils.rs index 90a0533ac629..4b8abc39dff2 100644 --- a/core/lib/external_price_api/src/utils.rs +++ b/core/lib/external_price_api/src/utils.rs @@ -55,33 +55,21 @@ pub(crate) mod tests { } #[test] - fn test_zero_panics() { + fn test_to_fraction_bad_inputs() { assert_eq!( get_fraction(0.0).expect_err("did not error").to_string(), "numerator is zero" ); - } - - #[test] - fn test_negative() { assert_eq!( get_fraction(-1.0).expect_err("did not error").to_string(), "number is negative" ); - } - - #[test] - fn test_nan() { assert_eq!( get_fraction(f64::NAN) .expect_err("did not error") .to_string(), "number is not rational" ); - } - - #[test] - fn test_infinity() { assert_eq!( get_fraction(f64::INFINITY) .expect_err("did not error") From 71cd367e7ac8cf0b9f573cde52397b4c7cb01b3a Mon Sep 17 00:00:00 2001 From: Artur Puzio <contact@puzio.waw.pl> Date: Tue, 1 Oct 2024 15:09:49 +0200 Subject: [PATCH 11/11] Update Cargo.lock --- Cargo.lock | 355 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 348 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9425ad8e238f..646732a19759 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,12 +234,52 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "assert_matches" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + [[package]] name = "async-channel" version = "2.3.1" @@ -276,6 +316,21 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + [[package]] name = "async-io" version = "2.3.4" @@ -317,13 +372,22 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "async-object-pool" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" +dependencies = [ + "async-std", +] + [[package]] name = "async-process" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-io", "async-lock", "async-signal", @@ -365,6 +429,34 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers 0.3.0", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -594,6 +686,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "beef" version = "0.5.2" @@ -680,6 +783,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + [[package]] name = "bit-vec" version = "0.6.3" @@ -853,7 +965,7 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-task", "futures-io", "futures-lite", @@ -1959,6 +2071,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -2107,6 +2240,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -2244,6 +2386,12 @@ dependencies = [ "uint", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "event-listener" version = "4.0.3" @@ -2586,7 +2734,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ - "gloo-timers", + "gloo-timers 0.2.6", "send_wrapper", ] @@ -2722,6 +2870,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -3115,6 +3275,34 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.30", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + [[package]] name = "hyper" version = "0.14.30" @@ -3439,6 +3627,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -3816,6 +4013,46 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools 0.11.0", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.8.4", + "string_cache", + "term", + "tiny-keccak 2.0.2", + "unicode-xid 0.2.6", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.7", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3837,6 +4074,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" version = "0.2.159" @@ -3859,6 +4102,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + [[package]] name = "librocksdb-sys" version = "0.11.0+8.1.1" @@ -4004,6 +4257,9 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "value-bag", +] [[package]] name = "logos" @@ -4256,6 +4512,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.29.0" @@ -4865,6 +5127,21 @@ dependencies = [ "indexmap 2.5.0", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.5" @@ -5026,6 +5303,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "pretty_assertions" version = "1.4.1" @@ -5478,6 +5761,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.10.6" @@ -6458,6 +6752,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -6675,6 +6979,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "siphasher" version = "1.0.1" @@ -6730,7 +7040,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-executor", "async-fs", "async-io", @@ -6786,7 +7096,7 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sha3 0.10.8", - "siphasher", + "siphasher 1.0.1", "slab", "smallvec", "soketto 0.7.1", @@ -6802,7 +7112,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5496f2d116b7019a526b1039ec2247dd172b8670633b1a64a614c9ea12c9d8c7" dependencies = [ - "async-channel", + "async-channel 2.3.1", "async-lock", "base64 0.21.7", "blake2-rfc", @@ -6825,7 +7135,7 @@ dependencies = [ "rand_chacha", "serde", "serde_json", - "siphasher", + "siphasher 1.0.1", "slab", "smol", "smoldot", @@ -7189,6 +7499,19 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -7534,6 +7857,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -8303,6 +8637,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + [[package]] name = "vcpkg" version = "0.2.15" @@ -9901,6 +10241,7 @@ dependencies = [ "bigdecimal", "chrono", "fraction", + "httpmock", "rand 0.8.5", "reqwest 0.12.7", "serde",