From b577e7d6b2764c7b7c30b349b775239d1477da30 Mon Sep 17 00:00:00 2001 From: dianacarvalho1 Date: Thu, 25 Sep 2025 17:27:05 +0100 Subject: [PATCH] refactor: Misc improvements to code (#277) * refactor: Misc improvements to code - Decouple validating logic from TychoRunner - Move all data fetching and decoding the tycho message into the same method - Split validate_state into validate_state, validate_token_balances and simulate_and_execute - Make rpc_provider and runtime attributes of TestRunner - Add references where possible to avoid clones - Remove unnecessary code - Make clippy happy #time 2h 36m #time 0m #time 3m * chore: Use tycho deps and foundry from tycho_simulation This is to try to decrease the risk of using conflicting versions in the different repositories #time 32m #time 0m * chore: Read RPC_URL in main.rs #time 10m * fix: Support eth trades (skip balance and allowance overwrites) and set balance overwrite to amount in For tokens like USDC setting the balance super high was making us getting blacklisted #time 1h 12m * fix: Fix curve tests and filter components_by_id with the expected_component_ids #time 1h 30m #time 0m * fix: Don't use all the possible executor addresses. Hardcode just one for the test Refactor overwrites logic: - renamed functions - moved logic around that fits together - don't use StateOverrides and then convert to alloy overrides. Use alloy's directly #time 1h 21m * fix: Assume that the executors mapping starts at storage value 1 Move setup_router_overwrites away from the rpc and into the execution file Delete unnecessary get_storage_at #time 33m --- protocol-testing/Cargo.lock | 460 ++++---- protocol-testing/Cargo.toml | 12 +- protocol-testing/src/config.rs | 12 +- protocol-testing/src/encoding.rs | 51 +- protocol-testing/src/execution.rs | 371 +++--- protocol-testing/src/main.rs | 13 +- protocol-testing/src/rpc.rs | 143 +-- protocol-testing/src/test_runner.rs | 1045 +++++++++-------- protocol-testing/src/traces.rs | 54 +- protocol-testing/src/tycho_rpc.rs | 22 +- protocol-testing/src/tycho_runner.rs | 36 +- protocol-testing/test_executor_addresses.json | 14 - .../integration_test.tycho.yaml | 15 + 13 files changed, 1161 insertions(+), 1087 deletions(-) delete mode 100644 protocol-testing/test_executor_addresses.json diff --git a/protocol-testing/Cargo.lock b/protocol-testing/Cargo.lock index cc8e164..b37fc15 100644 --- a/protocol-testing/Cargo.lock +++ b/protocol-testing/Cargo.lock @@ -68,9 +68,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f63701831729cb154cf0b6945256af46c426074646c98b9d123148ba1d8bde" +checksum = "67031be093311a96afdd146fb5de209ceaf0f347f2284ed71902368cd15a77ff" dependencies = [ "alloy-consensus", "alloy-contract", @@ -96,9 +96,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8ff73a143281cb77c32006b04af9c047a6b8fe5860e85a88ad325328965355" +checksum = "f3008b4f680adca5a81fad5f6cdbb561cca0cee7e97050756c2c1f3e41d2103c" dependencies = [ "alloy-primitives", "num_enum", @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a3bd0305a44fb457cae77de1e82856eadd42ea3cdf0dae29df32eb3b592979" +checksum = "0cd9d29a6a0bb8d4832ff7685dcbb430011b832f2ccec1af9571a0e75c1f7e9c" dependencies = [ "alloy-eips", "alloy-primitives", @@ -128,15 +128,16 @@ dependencies = [ "rand 0.8.5", "secp256k1 0.30.0", "serde", + "serde_json", "serde_with", "thiserror 2.0.16", ] [[package]] name = "alloy-consensus-any" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a842b4023f571835e62ac39fb8d523d19fcdbacfa70bf796ff96e7e19586f50" +checksum = "ce038cb325f9a85a10fb026fb1b70cb8c62a004d85d22f8516e5d173e3eec612" dependencies = [ "alloy-consensus", "alloy-eips", @@ -149,9 +150,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "591104333286b52b03ec4e8162983e31122b318d21ae2b0900d1e8b51727ad40" +checksum = "a376305e5c3b3285e84a553fa3f9aee4f5f0e1b0aad4944191b843cd8228788d" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -246,9 +247,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd749c57f38f8cbf433e651179fc5a676255e6b95044f467d49255d2b81725a" +checksum = "4bfec530782b30151e2564edf3c900f1fa6852128b7a993e458e8e3815d8b915" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -269,9 +270,9 @@ dependencies = [ [[package]] name = "alloy-ens" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a7feb13ccb13b784cdd5c1bb8756f74dd5b322c10ccfb737c8979ff26f9808" +checksum = "3c15d47b8b4e3f7f330cee5e279040ed94fc534dee7ea4d6ca7dcf28df1b0e2b" dependencies = [ "alloy-primitives", ] @@ -298,9 +299,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d32cbf6c26d7d87e8a4e5925bbce41456e0bbeed95601add3443af277cd604e" +checksum = "956e6a23eb880dd93123e8ebea028584325b9af22f991eec2c499c54c277c073" dependencies = [ "alloy-eips", "alloy-primitives", @@ -337,9 +338,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f614019a029c8fec14ae661aa7d4302e6e66bdbfb869dab40e78dcfba935fc97" +checksum = "be436893c0d1f7a57d1d8f1b6b9af9db04174468410b7e6e1d1893e78110a3bc" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -352,9 +353,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8b6d58e98803017bbfea01dde96c4d270a29e7aed3beb65c8d28b5ab464e0e" +checksum = "f18959e1a1b40e05578e7a705f65ff4e6b354e38335da4b33ccbee876bde7c26" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -378,9 +379,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db489617bffe14847bf89f175b1c183e5dd7563ef84713936e2c34255cfbd845" +checksum = "1da0037ac546c0cae2eb776bed53687b7bbf776f4e7aa2fea0b8b89e734c319b" dependencies = [ "alloy-consensus", "alloy-eips", @@ -391,9 +392,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50536212c8b686907f66dbac78799b8d39e18558648963594544ac1a05f4306d" +checksum = "fd89c9e72e62d95b51be0b92468282526b37d3d1015f5e31069a35c2e020872f" dependencies = [ "alloy-genesis", "alloy-hardforks", @@ -453,7 +454,7 @@ dependencies = [ "foldhash", "getrandom 0.3.3", "hashbrown 0.15.5", - "indexmap 2.11.3", + "indexmap 2.11.4", "itoa", "k256", "keccak-asm", @@ -470,9 +471,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed90278374435e076a04dbddbb6d714bdd518eb274a64dbd70f65701429dd747" +checksum = "4ca97e31bc05bd6d4780254fbb60b16d33b3548d1c657a879fffb0e7ebb642e9" dependencies = [ "alloy-chains", "alloy-consensus", @@ -512,9 +513,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f539a4caaa5496ad54af38f5615adb54cc7b3ec1a42e530e706291cce074f4a" +checksum = "f7bb37096e97de25133cf904e08df2aa72168af64f429e3c43a112649e131930" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -556,9 +557,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33732242ca63f107f5f8284190244038905fb233280f4b7c41f641d4f584d40d" +checksum = "dbeeeffa0bb7e95cb79f2b4b46b591763afeccfa9a797183c1b192377ffb6fac" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -579,9 +580,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2683049c5f3037d64722902e2c1081f3d45de68696aca0511bbea834905746" +checksum = "a21fe4c370b9e733d884ffd953eb6d654d053b1b22e26ffd591ef597a9e2bc49" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -593,9 +594,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b757081f2a68e683de3731108494fa058036d5651bf10141ec2430bc1315c362" +checksum = "eb44412ed075c19d37698f33213b83f0bf8ccc2d4e928527f2622555a31723de" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -605,9 +606,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f27c0c41a16cd0af4f5dbf791f7be2a60502ca8b0e840e0ad29803fac2d587" +checksum = "65423baf6af0ff356e254d7824b3824aa34d8ca9bd857a4e298f74795cc4b69d" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -616,9 +617,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec35a39206f0e04e8544d763c9fe324cc01f74de8821ef4b61e25ac329682f9" +checksum = "d5dc8a9ba66f1a654d935584200fcd0b7fd34dac0ca19df024911899066b0583" dependencies = [ "alloy-consensus", "alloy-eips", @@ -635,9 +636,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f5812f81c3131abc2cd8953dc03c41999e180cff7252abbccaba68676e15027" +checksum = "848f8ea4063bed834443081d77f840f31075f68d0d49723027f5a209615150bf" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -657,9 +658,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1070e7e92dae6a9c48885980f4f9ca9faa70f945fcd62fbb94472182ca08854f" +checksum = "6c632e12fb9bbde97eb2a0f5145f0fe6e0ed1b3927de29d8463ab468905d9843" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -671,9 +672,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dfe41a47805a34b848c83448946ca96f3d36842e8c074bcf8fa0870e337d12" +checksum = "19c3835bdc128f2f3418f5d6c76aec63a245d72973e0eaacc9720aa0787225c5" dependencies = [ "alloy-primitives", "arbitrary", @@ -683,9 +684,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79237b4c1b0934d5869deea4a54e6f0a7425a8cd943a739d6293afdf893d847" +checksum = "42084a7b455ef0b94ed201b7494392a759c3e20faac2d00ded5d5762fcf71dee" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -700,9 +701,9 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5cbc38711b7e5ee7b53a7489e8e718a54c6b138d382cfb86e293a2ad5ac894d" +checksum = "d190ee456bba27fc4c3bf7aa65001dd3c71cd431591f98ca3b07960d530d2336" dependencies = [ "alloy-consensus", "alloy-network", @@ -719,9 +720,9 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc77e474f2384192f1f27a07a2dd13dd7530064ae96c5941aa1b6ddf33fc7540" +checksum = "1ba0de990b20e15be741248c68cdff6c296dcd4ed658f82d0ccff2c036dacca3" dependencies = [ "alloy-consensus", "alloy-network", @@ -737,9 +738,9 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b13d57b88e25281272533cd55bbf086db9b867be001e02954aa3346ea9fa8e" +checksum = "3f2dc315b34d1c77e97a67b57aebf9b244dda6428f5df555b133f7e9e588cac9" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -757,9 +758,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e90a3858da59d1941f496c17db8d505f643260f7e97cdcdd33823ddca48fc1" +checksum = "6312ccc048a4a88aed7311fc448a2e23da55c60c2b3b6dcdb794f759d02e49d7" dependencies = [ "alloy-consensus", "alloy-network", @@ -777,9 +778,9 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58930e48ab90c91978aaaccb5c042858c5982cb3a6f9432742fbf939b80f58a" +checksum = "ee1c2920daf194a9b8052549de309beb2e4e65673475aa6b2c7760b8ca538fba" dependencies = [ "alloy-consensus", "alloy-network", @@ -816,7 +817,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck 0.5.0", - "indexmap 2.11.3", + "indexmap 2.11.4", "proc-macro-error2", "proc-macro2", "quote", @@ -867,9 +868,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb43750e137fe3a69a325cd89a8f8e2bbf4f83e70c0f60fbe49f22511ca075e8" +checksum = "68f77fa71f6dad3aa9b97ab6f6e90f257089fb9eaa959892d153a1011618e2d6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -891,9 +892,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b42c7d8b666eed975739201f407afc3320d3cd2e4d807639c2918fc736ea67" +checksum = "0ab1a5d0f5dd5e07187a4170bdcb7ceaff18b1133cd6b8585bc316ab442cd78a" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -906,9 +907,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07b920e2d4ec9b08cb12a32fa8e5e95dfcf706fe1d7f46453e24ee7089e29f0" +checksum = "62764e672967d7f8a890c3d28c9c9a9fc781fba59e5d869898b08073c9deae3a" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -926,15 +927,15 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83db1cc29cce5f692844d6cf1b6b270ae308219c5d90a7246a74f3479b9201c2" +checksum = "6a21442472bad4494cfb1f11d975ae83059882a11cdda6a3aa8c0d2eb444beb6" dependencies = [ "alloy-pubsub", "alloy-transport", "futures 0.3.31", "http 1.3.1", - "rustls 0.23.31", + "rustls 0.23.32", "serde_json", "tokio", "tokio-tungstenite 0.26.2", @@ -964,9 +965,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e434e0917dce890f755ea774f59d6f12557bc8c7dd9fa06456af80cfe0f0181e" +checksum = "cc79013f9ac3a8ddeb60234d43da09e6d6abfc1c9dd29d3fe97adfbece3f4a08" dependencies = [ "alloy-primitives", "darling 0.21.3", @@ -1528,9 +1529,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b8ff6c09cd57b16da53641caa860168b88c172a5ee163b0288d3d6eea12786" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" dependencies = [ "aws-lc-sys", "zeroize", @@ -1538,15 +1539,16 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e44d16778acaf6a9ec9899b92cebd65580b83f685446bf2e1f5d3d732f99dcd" +checksum = "ee74396bee4da70c2e27cf94762714c911725efe69d9e2672f998512a67a4ce4" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", + "libloading", ] [[package]] @@ -1575,9 +1577,9 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.87.0" +version = "1.87.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865b1f9214c318bc3d4baaf0aa918f7d648b993fcb83601f7b3ede06ba6ce08f" +checksum = "ef56853ddcce20bb4883f5db9d8631d7223ff37b039d033a14cb0b4e87fd2c21" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1619,9 +1621,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.85.0" +version = "1.86.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e05f33b6c9026fecfe9b3b6740f34d41bc6ff641a6a32dabaab60209245b75" +checksum = "9d1cc7fb324aa12eb4404210e6381195c5b5e9d52c2682384f295f38716dd3c7" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1736,7 +1738,7 @@ dependencies = [ "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.31", + "rustls 0.23.32", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -1959,6 +1961,20 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.16", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.75" @@ -2044,7 +2060,7 @@ dependencies = [ "bitflags 2.9.4", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -2149,9 +2165,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" dependencies = [ "cc", "glob", @@ -2281,9 +2297,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.1" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +checksum = "137a2a2878ed823ef1bd73e5441e245602aae5360022113b8ad259ca4b5b8727" dependencies = [ "arbitrary", "blst", @@ -2342,9 +2358,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.37" +version = "1.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9" dependencies = [ "find-msvc-tools", "jobserver", @@ -2437,9 +2453,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", @@ -2447,9 +2463,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -2625,7 +2641,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3078,12 +3094,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -4027,9 +4043,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" [[package]] name = "fixed-hash" @@ -4177,7 +4193,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.16", - "toml 0.9.6", + "toml 0.9.7", "tracing", "walkdir", ] @@ -4399,7 +4415,7 @@ dependencies = [ "solar-parse", "soldeer-core", "thiserror 2.0.16", - "toml 0.9.6", + "toml 0.9.7", "toml_edit 0.22.27", "tracing", "walkdir", @@ -4927,7 +4943,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.11.3", + "indexmap 2.11.4", "slab", "tokio", "tokio-util", @@ -4946,7 +4962,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.11.3", + "indexmap 2.11.4", "slab", "tokio", "tokio-util", @@ -5263,7 +5279,7 @@ dependencies = [ "http 1.3.1", "hyper 1.7.0", "hyper-util", - "rustls 0.23.31", + "rustls 0.23.32", "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", @@ -5588,13 +5604,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.3" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", "serde", "serde_core", ] @@ -5827,9 +5843,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -5962,9 +5978,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libloading" @@ -5973,7 +5989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -6965,7 +6981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.11.3", + "indexmap 2.11.4", ] [[package]] @@ -6985,7 +7001,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.11.3", + "serde", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", "serde", ] @@ -6995,7 +7021,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.5", ] @@ -7006,7 +7032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", "syn 2.0.106", @@ -7021,6 +7047,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "0.4.30" @@ -7106,9 +7141,9 @@ dependencies = [ [[package]] name = "postgres" -version = "0.19.10" +version = "0.19.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363e6dfbdd780d3aa3597b6eb430db76bb315fa9bad7fae595bb8def808b8470" +checksum = "8bc19a61586a93e0663e8663dcc37c361f11e44ab0309d23eba1aa5a1965116d" dependencies = [ "bytes", "fallible-iterator", @@ -7120,9 +7155,9 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" dependencies = [ "base64 0.22.1", "byteorder", @@ -7138,9 +7173,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" +checksum = "77a120daaabfcb0e324d5bf6e411e9222994cb3795c79943a0ef28ed27ea76e4" dependencies = [ "bytes", "fallible-iterator", @@ -7242,7 +7277,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.5", + "toml_edit 0.23.6", ] [[package]] @@ -7352,9 +7387,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" dependencies = [ "bit-set 0.8.0", "bit-vec 0.8.0", @@ -7513,8 +7548,6 @@ dependencies = [ "colored", "dotenv", "figment", - "foundry-config", - "foundry-evm", "glob", "hex", "itertools 0.14.0", @@ -7524,7 +7557,6 @@ dependencies = [ "num-traits", "postgres", "reqwest 0.12.23", - "revm", "serde", "serde_json", "serde_yaml", @@ -7533,9 +7565,6 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber 0.3.20", - "tycho-client", - "tycho-common", - "tycho-ethereum", "tycho-simulation", ] @@ -7584,7 +7613,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.31", + "rustls 0.23.32", "socket2 0.6.0", "thiserror 2.0.16", "tokio", @@ -7604,7 +7633,7 @@ dependencies = [ "rand 0.9.2", "ring 0.17.14", "rustc-hash", - "rustls 0.23.31", + "rustls 0.23.32", "rustls-pki-types", "slab", "thiserror 2.0.16", @@ -7906,7 +7935,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.31", + "rustls 0.23.32", "rustls-native-certs 0.8.1", "rustls-pki-types", "serde", @@ -7954,7 +7983,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66c52031b73cae95d84cd1b07725808b5fd1500da3e5e24574a3b2dc13d9f16d" dependencies = [ "bitvec", - "phf", + "phf 0.11.3", "revm-primitives", "serde", ] @@ -8245,14 +8274,15 @@ dependencies = [ [[package]] name = "ruint" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -8266,7 +8296,7 @@ dependencies = [ "rand 0.9.2", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -8353,9 +8383,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40" dependencies = [ "aws-lc-rs", "log", @@ -8388,7 +8418,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.4.0", + "security-framework 3.5.0", ] [[package]] @@ -8676,9 +8706,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b369d18893388b345804dc0007963c99b7d665ae71d275812d828c6f089640" +checksum = "cc198e42d9b7510827939c9a15f5062a0c913f3371d765977e586d2fe6c16f4a" dependencies = [ "bitflags 2.9.4", "core-foundation 0.10.1", @@ -8739,9 +8769,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.225" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" dependencies = [ "serde_core", "serde_derive", @@ -8749,18 +8779,18 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.226" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", @@ -8782,7 +8812,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.11.3", + "indexmap 2.11.4", "itoa", "memchr", "ryu", @@ -8801,9 +8831,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" dependencies = [ "serde_core", ] @@ -8822,15 +8852,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.3", + "indexmap 2.11.4", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -8842,11 +8872,11 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" dependencies = [ - "darling 0.20.11", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.106", @@ -8858,7 +8888,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.11.3", + "indexmap 2.11.4", "itoa", "ryu", "serde", @@ -9060,7 +9090,7 @@ dependencies = [ "itertools 0.11.0", "lalrpop", "lalrpop-util", - "phf", + "phf 0.11.3", "thiserror 1.0.69", "unicode-xid", ] @@ -9101,7 +9131,7 @@ checksum = "3cc21b4df6061e1c825c16faf8e1f16c2341f4c46a2b2a60e03069c4453fc5ac" dependencies = [ "bumpalo", "index_vec", - "indexmap 2.11.3", + "indexmap 2.11.4", "parking_lot", "rayon", "rustc-hash", @@ -9262,7 +9292,7 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", "precomputed-hash", ] @@ -9617,9 +9647,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom 0.3.3", @@ -9735,11 +9765,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.43" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", + "itoa", "libc", "num-conv", "num_threads", @@ -9853,9 +9884,9 @@ dependencies = [ [[package]] name = "tokio-postgres" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" +checksum = "a156efe7fff213168257853e1dfde202eed5f487522cbbbf7d219941d753d853" dependencies = [ "async-trait", "byteorder", @@ -9866,12 +9897,12 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf", + "phf 0.13.1", "pin-project-lite", "postgres-protocol", "postgres-types", "rand 0.9.2", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tokio-util", "whoami", @@ -9893,7 +9924,7 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd" dependencies = [ - "rustls 0.23.31", + "rustls 0.23.32", "tokio", ] @@ -9934,7 +9965,7 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", - "rustls 0.23.31", + "rustls 0.23.32", "rustls-pki-types", "tokio", "tokio-rustls 0.26.3", @@ -9969,14 +10000,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.6" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" dependencies = [ - "indexmap 2.11.3", + "indexmap 2.11.4", "serde_core", - "serde_spanned 1.0.1", - "toml_datetime 0.7.1", + "serde_spanned 1.0.2", + "toml_datetime 0.7.2", "toml_parser", "toml_writer", "winnow", @@ -9993,9 +10024,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ "serde_core", ] @@ -10006,7 +10037,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.3", + "indexmap 2.11.4", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -10016,21 +10047,21 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" +checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ - "indexmap 2.11.3", - "toml_datetime 0.7.1", + "indexmap 2.11.4", + "toml_datetime 0.7.2", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] @@ -10043,9 +10074,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" [[package]] name = "tonic" @@ -10133,7 +10164,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap 2.11.3", + "indexmap 2.11.4", "pin-project-lite", "slab", "sync_wrapper 1.0.2", @@ -10391,7 +10422,7 @@ dependencies = [ "httparse", "log", "rand 0.9.2", - "rustls 0.23.31", + "rustls 0.23.32", "rustls-pki-types", "sha1", "thiserror 2.0.16", @@ -10400,11 +10431,12 @@ dependencies = [ [[package]] name = "tycho-client" -version = "0.85.0" -source = "git+https://github.com/propeller-heads/tycho-indexer.git?rev=28d013a906c497d95e27f01f48fc887fb22dbbbc#28d013a906c497d95e27f01f48fc887fb22dbbbc" +version = "0.88.0" +source = "git+https://github.com/propeller-heads/tycho-indexer.git?tag=0.88.0#47ed2e3db5e6b038866c682964ca0112c27d9ae1" dependencies = [ "anyhow", "async-trait", + "backoff", "chrono", "clap", "futures 0.3.31", @@ -10415,6 +10447,7 @@ dependencies = [ "serde", "serde_json", "thiserror 1.0.69", + "time", "tokio", "tokio-tungstenite 0.20.1", "tracing", @@ -10426,8 +10459,8 @@ dependencies = [ [[package]] name = "tycho-common" -version = "0.85.0" -source = "git+https://github.com/propeller-heads/tycho-indexer.git?rev=28d013a906c497d95e27f01f48fc887fb22dbbbc#28d013a906c497d95e27f01f48fc887fb22dbbbc" +version = "0.88.0" +source = "git+https://github.com/propeller-heads/tycho-indexer.git?tag=0.88.0#47ed2e3db5e6b038866c682964ca0112c27d9ae1" dependencies = [ "anyhow", "async-trait", @@ -10450,8 +10483,8 @@ dependencies = [ [[package]] name = "tycho-ethereum" -version = "0.85.0" -source = "git+https://github.com/propeller-heads/tycho-indexer.git?rev=28d013a906c497d95e27f01f48fc887fb22dbbbc#28d013a906c497d95e27f01f48fc887fb22dbbbc" +version = "0.88.0" +source = "git+https://github.com/propeller-heads/tycho-indexer.git?tag=0.88.0#47ed2e3db5e6b038866c682964ca0112c27d9ae1" dependencies = [ "alloy", "alloy-rpc-types-trace", @@ -10481,8 +10514,8 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.124.0" -source = "git+https://github.com/propeller-heads/tycho-execution.git?rev=14a1e497999999c5058dfaf9eee51bec0c025d12#14a1e497999999c5058dfaf9eee51bec0c025d12" +version = "0.127.0" +source = "git+https://github.com/propeller-heads/tycho-execution.git?tag=0.127.0#ae5c3778eae88a9c14878dc5cc6853df62b4ceac" dependencies = [ "alloy", "chrono", @@ -10502,8 +10535,8 @@ dependencies = [ [[package]] name = "tycho-simulation" -version = "0.160.1" -source = "git+https://github.com/propeller-heads/tycho-simulation.git?rev=6585823a859a29bd64600cc1d2fa7d502d48d3e6#6585823a859a29bd64600cc1d2fa7d502d48d3e6" +version = "0.164.0" +source = "git+https://github.com/propeller-heads/tycho-simulation.git?rev=f73c2ef28328abdde791edf1fb21748f78dbee6a#f73c2ef28328abdde791edf1fb21748f78dbee6a" dependencies = [ "alloy", "async-stream", @@ -10540,6 +10573,7 @@ dependencies = [ "tracing", "tycho-client", "tycho-common", + "tycho-ethereum", "tycho-execution", "uuid 1.18.1", ] @@ -10754,7 +10788,7 @@ version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" dependencies = [ - "indexmap 2.11.3", + "indexmap 2.11.4", "serde", "serde_json", "utoipa-gen", @@ -10965,9 +10999,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", @@ -10978,9 +11012,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -10992,9 +11026,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.53" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -11005,9 +11039,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11015,9 +11049,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -11028,9 +11062,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -11064,9 +11098,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.80" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -11171,7 +11205,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.0", ] [[package]] @@ -11738,7 +11772,7 @@ dependencies = [ "arbitrary", "crc32fast", "flate2", - "indexmap 2.11.3", + "indexmap 2.11.4", "memchr", "zopfli", ] diff --git a/protocol-testing/Cargo.toml b/protocol-testing/Cargo.toml index 41ba453..a9dd142 100644 --- a/protocol-testing/Cargo.toml +++ b/protocol-testing/Cargo.toml @@ -8,13 +8,7 @@ glob = "0.3.0" miette = { version = "7.6.0", features = ["fancy"] } # Logging & Tracing tracing = "0.1.37" -# Tycho dependencies -tycho-common = { git = "https://github.com/propeller-heads/tycho-indexer.git", rev = "28d013a906c497d95e27f01f48fc887fb22dbbbc" } -tycho-client = { git = "https://github.com/propeller-heads/tycho-indexer.git", rev = "28d013a906c497d95e27f01f48fc887fb22dbbbc" } -tycho-simulation = { git = "https://github.com/propeller-heads/tycho-simulation.git", rev = "6585823a859a29bd64600cc1d2fa7d502d48d3e6" } -## TODO: for local development -#tycho-simulation = { path = "../../tycho-simulation", features = ["evm"] } -tycho-ethereum = { git = "https://github.com/propeller-heads/tycho-indexer.git", rev = "28d013a906c497d95e27f01f48fc887fb22dbbbc", features = ["onchain_data"] } +tycho-simulation = { git = "https://github.com/propeller-heads/tycho-simulation.git", rev = "f73c2ef28328abdde791edf1fb21748f78dbee6a", features = ["evm"] } num-bigint = "0.4" num-traits = "0.2" num-rational = "0.4.2" @@ -36,7 +30,3 @@ colored = "3.0.0" similar = "2.7.0" termsize = "0.1.9" itertools = "0.14.0" -# Foundry dependencies (same versions as tycho-simulation) -foundry-config = { git = "https://github.com/foundry-rs/foundry", rev = "5a552bb0de7126fa35170fd84532bbd3d40cd348" } -foundry-evm = { git = "https://github.com/foundry-rs/foundry", rev = "5a552bb0de7126fa35170fd84532bbd3d40cd348" } -revm = { version = "27.0.3", features = ["alloydb", "serde"] } diff --git a/protocol-testing/src/config.rs b/protocol-testing/src/config.rs index ca512af..1bd7fa8 100644 --- a/protocol-testing/src/config.rs +++ b/protocol-testing/src/config.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use colored::Colorize; use serde::{Deserialize, Serialize}; use similar::{ChangeTag, TextDiff}; -use tycho_common::{dto::ProtocolComponent, Bytes}; +use tycho_simulation::{protocol::models::ProtocolComponent, tycho_common::Bytes}; /// Represents a ProtocolComponent with its main attributes #[derive(Debug, Clone, Deserialize, Serialize)] @@ -31,14 +31,18 @@ impl ProtocolComponentExpectation { let mut diffs = Vec::new(); // Compare id (case-insensitive) - if self.id.to_lowercase() != other.id.to_lowercase() { - let diff = self.format_diff("id", &self.id, &other.id, colorize_output); + if self.id.to_lowercase() != other.id.to_string().to_lowercase() { + let diff = self.format_diff("id", &self.id, &other.id.to_string(), colorize_output); diffs.push(format!("Field 'id' mismatch for {}:\n{}", self.id, diff)); } // Compare tokens (order-independent) let self_tokens_set: HashSet<_> = self.tokens.iter().collect(); - let other_tokens_set: HashSet<_> = other.tokens.iter().collect(); + let other_tokens_set: HashSet<_> = other + .tokens + .iter() + .map(|token| &token.address) + .collect(); if self_tokens_set != other_tokens_set { let self_tokens = format!("{:?}", self.tokens); let other_tokens = format!("{:?}", other.tokens); diff --git a/protocol-testing/src/encoding.rs b/protocol-testing/src/encoding.rs index 587d60f..7541bc7 100644 --- a/protocol-testing/src/encoding.rs +++ b/protocol-testing/src/encoding.rs @@ -9,10 +9,11 @@ use std::str::FromStr; use alloy::{primitives::Keccak256, sol_types::SolValue}; use miette::{IntoDiagnostic, WrapErr}; use num_bigint::BigUint; -use tycho_common::{dto::Chain, Bytes}; +use serde_json::json; use tycho_simulation::{ evm::protocol::u256_num::biguint_to_u256, protocol::models::ProtocolComponent, + tycho_common::{dto::Chain, Bytes}, tycho_execution::encoding::{ errors::EncodingError, evm::{encoder_builders::TychoRouterEncoderBuilder, utils::bytes_to_address}, @@ -22,7 +23,7 @@ use tycho_simulation::{ }, }; -use crate::execution::EXECUTORS_JSON; +use crate::execution::EXECUTOR_ADDRESS; /// Creates a Solution for the given swap parameters. /// @@ -36,17 +37,17 @@ use crate::execution::EXECUTORS_JSON; /// # Returns /// A `Result` containing the solution, or an error if creation fails. pub fn get_solution( - component: ProtocolComponent, - token_in: Bytes, - token_out: Bytes, - amount_in: BigUint, - amount_out: BigUint, + component: &ProtocolComponent, + token_in: &Bytes, + token_out: &Bytes, + amount_in: &BigUint, + amount_out: &BigUint, ) -> miette::Result { - let alice_address = Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") + let user_address = Bytes::from_str("0xf847a638E44186F3287ee9F8cAF73FF4d4B80784") .into_diagnostic() .wrap_err("Failed to parse Alice's address for Tycho router encoding")?; - let swap = SwapBuilder::new(component, token_in.clone(), token_out.clone()).build(); + let swap = SwapBuilder::new(component.clone(), token_in.clone(), token_out.clone()).build(); let slippage = 0.0025; // 0.25% slippage let bps = BigUint::from(10_000u32); @@ -55,11 +56,11 @@ pub fn get_solution( let min_amount_out = (amount_out * &multiplier) / &bps; Ok(Solution { - sender: alice_address.clone(), - receiver: alice_address.clone(), - given_token: token_in, - given_amount: amount_in, - checked_token: token_out, + sender: user_address.clone(), + receiver: user_address.clone(), + given_token: token_in.clone(), + given_amount: amount_in.clone(), + checked_token: token_out.clone(), exact_out: false, checked_amount: min_amount_out, swaps: vec![swap], @@ -83,18 +84,26 @@ pub fn get_solution( /// A `Result` containing the encoded transaction data for the Tycho /// router, or an error if encoding fails. pub fn encode_swap( - component: ProtocolComponent, - token_in: Bytes, - token_out: Bytes, - amount_in: BigUint, - amount_out: BigUint, + component: &ProtocolComponent, + token_in: &Bytes, + token_out: &Bytes, + amount_in: &BigUint, + amount_out: &BigUint, ) -> miette::Result<(Transaction, Solution)> { - let chain: tycho_common::models::Chain = Chain::Ethereum.into(); + let protocol_system = component.protocol_system.clone(); + let executors_json = json!({ + "ethereum": { + (protocol_system):EXECUTOR_ADDRESS + } + }) + .to_string(); + + let chain: tycho_simulation::tycho_common::models::Chain = Chain::Ethereum.into(); let encoder = TychoRouterEncoderBuilder::new() .chain(chain) .user_transfer_type(UserTransferType::TransferFrom) - .executors_addresses(EXECUTORS_JSON.to_string()) + .executors_addresses(executors_json) .historical_trade() .build() .into_diagnostic() diff --git a/protocol-testing/src/execution.rs b/protocol-testing/src/execution.rs index 588cbff..571ad8a 100644 --- a/protocol-testing/src/execution.rs +++ b/protocol-testing/src/execution.rs @@ -7,25 +7,29 @@ use std::{collections::HashMap, str::FromStr, sync::LazyLock}; use alloy::{ - primitives::{Address, U256}, - rpc::types::{Block, TransactionRequest}, + primitives::{keccak256, map::AddressHashMap, Address, FixedBytes, U256}, + rpc::types::{state::AccountOverride, Block, TransactionRequest}, }; use miette::{miette, IntoDiagnostic, WrapErr}; use num_bigint::BigUint; -use serde_json::Value; use tracing::info; -use tycho_common::traits::{AllowanceSlotDetector, BalanceSlotDetector}; -use tycho_ethereum::entrypoint_tracer::{ - allowance_slot_detector::{AllowanceSlotDetectorConfig, EVMAllowanceSlotDetector}, - balance_slot_detector::{BalanceSlotDetectorConfig, EVMBalanceSlotDetector}, -}; use tycho_simulation::{ - evm::protocol::u256_num::u256_to_biguint, tycho_execution::encoding::models::Solution, + evm::protocol::u256_num::{biguint_to_u256, u256_to_biguint}, + tycho_common::{ + traits::{AllowanceSlotDetector, BalanceSlotDetector}, + Bytes, + }, + tycho_ethereum::entrypoint_tracer::{ + allowance_slot_detector::{AllowanceSlotDetectorConfig, EVMAllowanceSlotDetector}, + balance_slot_detector::{BalanceSlotDetectorConfig, EVMBalanceSlotDetector}, + }, + tycho_execution::encoding::models::Solution, }; use crate::rpc::RPCProvider; -const ROUTER_BYTECODE_JSON: &str = include_str!("../../evm/test/router/TychoRouter.runtime.json"); -pub const EXECUTORS_JSON: &str = include_str!("../test_executor_addresses.json"); +pub const ROUTER_BYTECODE_JSON: &str = + include_str!("../../evm/test/router/TychoRouter.runtime.json"); +pub const EXECUTOR_ADDRESS: &str = "0xaE04CA7E9Ed79cBD988f6c536CE11C621166f41B"; // Include all executor bytecode files at compile time const UNISWAP_V2_BYTECODE_JSON: &str = @@ -60,62 +64,6 @@ static EXECUTOR_MAPPING: LazyLock> = LazyLoc map }); -/// Executor addresses loaded from test_executor_addresses.json at startup -pub static EXECUTOR_ADDRESSES: LazyLock> = LazyLock::new(|| { - let json_value: Value = serde_json::from_str(&EXECUTORS_JSON) - .expect("Failed to parse test_executor_addresses.json"); - - let ethereum_addresses = json_value["ethereum"] - .as_object() - .expect("Missing 'ethereum' key in test_executor_addresses.json"); - - let mut addresses = HashMap::new(); - for (protocol_name, address_value) in ethereum_addresses { - let address_str = address_value - .as_str() - .unwrap_or_else(|| panic!("Invalid address format for protocol '{protocol_name}'")); - - let address = Address::from_str(address_str).unwrap_or_else(|_| { - panic!("Invalid address '{address_str}' for protocol '{protocol_name}'") - }); - - addresses.insert(protocol_name.clone(), address); - } - addresses -}); - -#[derive(Debug, Clone)] -pub struct StateOverride { - pub code: Option>, - pub balance: Option, - pub state_diff: HashMap, -} - -impl StateOverride { - pub fn new() -> Self { - Self { code: None, balance: None, state_diff: HashMap::new() } - } - - pub fn with_code(mut self, code: Vec) -> Self { - self.code = Some(code); - self - } - - pub fn with_balance(mut self, balance: U256) -> Self { - self.balance = Some(balance); - self - } - - pub fn with_state_diff( - mut self, - slot: alloy::primitives::Bytes, - value: alloy::primitives::Bytes, - ) -> Self { - self.state_diff.insert(slot, value); - self - } -} - /// Get executor bytecode JSON based on component ID fn get_executor_bytecode_json(component_id: &str) -> miette::Result<&'static str> { for (pattern, executor_json) in EXECUTOR_MAPPING.iter() { @@ -126,20 +74,9 @@ fn get_executor_bytecode_json(component_id: &str) -> miette::Result<&'static str Err(miette!("Unknown component type '{}' - no matching executor found", component_id)) } -/// Get executor address for a given component ID -fn get_executor_address(component_id: &str) -> miette::Result
{ - if let Some(&address) = EXECUTOR_ADDRESSES.get(component_id) { - return Ok(address); - } - Err(miette!("No executor address found for component type '{}'", component_id)) -} - -/// Load executor bytecode from embedded constants based on solution component -fn load_executor_bytecode(solution: &Solution) -> miette::Result> { - let first_swap = solution.swaps.first().unwrap(); - let component_id = &first_swap.component; - - let executor_json = get_executor_bytecode_json(&component_id.protocol_system)?; +/// Load executor bytecode from embedded constants based on the protocol system +pub fn load_executor_bytecode(protocol_system: &str) -> miette::Result> { + let executor_json = get_executor_bytecode_json(protocol_system)?; let json_value: serde_json::Value = serde_json::from_str(executor_json) .into_diagnostic() @@ -177,93 +114,206 @@ fn calculate_gas_fees(block_header: &Block) -> miette::Result<(U256, U256)> { Ok((max_fee_per_gas, max_priority_fee_per_gas)) } -/// Set up all state overrides needed for simulation -async fn setup_state_overrides( +/// Calculate storage slot for Solidity mapping. +/// +/// The solidity code: +/// keccak256(abi.encodePacked(bytes32(key), bytes32(slot))) +pub fn calculate_executor_storage_slot(key: Address) -> FixedBytes<32> { + // Convert key (20 bytes) to 32-byte left-padded array (uint256) + let mut key_bytes = [0u8; 32]; + key_bytes[12..].copy_from_slice(key.as_slice()); + + // The base of the executor storage slot is 1, since there is only one + // variable that is initialized before it (which is _roles in AccessControl.sol). + // In this case, _roles gets slot 0. + // The slots are given in order to the parent contracts' variables first and foremost. + let slot = U256::from(1); + + // Convert U256 slot to 32-byte big-endian array + let slot_bytes = slot.to_be_bytes::<32>(); + + // Concatenate key_bytes + slot_bytes, then keccak hash + let mut buf = [0u8; 64]; + buf[..32].copy_from_slice(&key_bytes); + buf[32..].copy_from_slice(&slot_bytes); + keccak256(buf) +} + +/// Sets up state overwrites for the Tycho router and its associated executor. +/// +/// This method prepares the router for simulation by: +/// 1. Overriding the router's bytecode with the embedded runtime bytecode +/// 2. Copying executor approval storage from the current block to maintain permissions +/// 3. Overriding the executor's bytecode based on the protocol system +/// +/// # Arguments +/// * `router_address` - The address of the Tycho router contract +/// * `protocol_system` - The protocol system identifier (e.g., "uniswap_v2", "vm:balancer_v2") +/// +/// # Returns +/// A HashMap containing account overwrites for both the router and executor addresses. +/// The router override includes bytecode and executor approval storage. +/// The executor override includes the appropriate bytecode for the protocol. +/// +/// # Errors +/// Returns an error if: +/// - Router bytecode JSON parsing fails +/// - Executor address parsing fails +/// - Storage slot fetching fails +/// - Executor bytecode loading fails +pub async fn setup_router_overwrites( + router_address: Address, + protocol_system: &str, +) -> miette::Result> { + let json_value: serde_json::Value = serde_json::from_str(ROUTER_BYTECODE_JSON) + .into_diagnostic() + .wrap_err("Failed to parse router JSON")?; + + let bytecode_str = json_value["runtimeBytecode"] + .as_str() + .ok_or_else(|| miette::miette!("No runtimeBytecode field found in router JSON"))?; + + // Remove 0x prefix if present + let bytecode_hex = + if let Some(stripped) = bytecode_str.strip_prefix("0x") { stripped } else { bytecode_str }; + + let router_bytecode = hex::decode(bytecode_hex) + .into_diagnostic() + .wrap_err("Failed to decode router bytecode from hex")?; + + // Start with the router bytecode override + let mut state_overwrites = AddressHashMap::default(); + let mut tycho_router_override = AccountOverride::default().with_code(router_bytecode); + + // Find executor address approval storage slot + let executor_address = Address::from_str(EXECUTOR_ADDRESS).into_diagnostic()?; + let storage_slot = calculate_executor_storage_slot(executor_address); + + // The executors mapping starts at storage value 1 + let storage_value = FixedBytes::<32>::from(U256::ONE); + + tycho_router_override = + tycho_router_override.with_state_diff(vec![(storage_slot, storage_value)]); + + state_overwrites.insert(router_address, tycho_router_override); + + // Add bytecode overwrite for the executor + let executor_bytecode = load_executor_bytecode(protocol_system)?; + state_overwrites + .insert(executor_address, AccountOverride::default().with_code(executor_bytecode.to_vec())); + Ok(state_overwrites) +} + +/// Sets up state overwrites for user accounts and tokens required for swap simulation. +/// +/// This method prepares the user environment for historical block simulation by: +/// 1. Providing the user with sufficient ETH balance (100 ETH) for gas payments +/// 2. For ETH swaps: Adding the swap amount to the user's ETH balance +/// 3. For ERC20 swaps: Overriding token balance and allowance storage slots to ensure: +/// - User has sufficient tokens for the swap +/// - Router has unlimited allowance to spend user's tokens +/// +/// The function uses EVM storage slot detection to find the correct storage locations +/// for token balances and allowances, then applies state overwrites to simulate the +/// required pre-conditions without executing actual token transfers. +/// +/// # Arguments +/// * `solution` - The encoded swap solution containing token and amount information +/// * `transaction` - The transaction details for determining router address +/// * `user_address` - The address of the user performing the swap +/// * `rpc_url` - RPC endpoint URL for storage slot detection +/// * `block` - The historical block context for storage queries +/// +/// # Returns +/// A HashMap containing account overwrites for: +/// - User account: ETH balance override +/// - Token contract: Balance and allowance storage slot overwrites (for ERC20 swaps) +/// +/// # Errors +/// Returns an error if: +/// - Storage slot detection fails for balance or allowance +/// - Token address parsing fails +/// - RPC queries for storage detection fail +async fn setup_user_overwrites( solution: &Solution, transaction: &tycho_simulation::tycho_execution::encoding::models::Transaction, user_address: Address, - executor_bytecode: &[u8], rpc_url: String, block: &Block, -) -> miette::Result> { - let mut state_overwrites = HashMap::new(); - let token_address = Address::from_slice(&solution.given_token[..20]); - - // Extract executor address from the encoded solution's swaps data. - // The solution should only have one swap for the test, so this should be safe. - let executor_address = if let Some(first_swap) = solution.swaps.first() { - get_executor_address(&first_swap.component.protocol_system)? - } else { - return Err(miette!("No swaps in solution - cannot determine executor address")); - }; - - // Add bytecode overwrite for the executor - state_overwrites - .insert(executor_address, StateOverride::new().with_code(executor_bytecode.to_vec())); - +) -> miette::Result> { + let mut overwrites = AddressHashMap::default(); // Add ETH balance override for the user to ensure they have enough gas funds - state_overwrites.insert( - user_address, - StateOverride::new().with_balance(U256::from_str("100000000000000000000").unwrap()), // 100 ETH - ); + let mut eth_balance = U256::from_str("100000000000000000000").unwrap(); // 100 ETH - let detector = EVMBalanceSlotDetector::new(BalanceSlotDetectorConfig { - rpc_url: rpc_url.clone(), - ..Default::default() - }) - .into_diagnostic()?; + let token_address = Address::from_slice(&solution.given_token[..20]); + // If given token is ETH, add the given amount to the balance + if solution.given_token == Bytes::zero(20) { + eth_balance += biguint_to_u256(&solution.given_amount); + // if the given token is not ETH, do balance and allowance slots overwrites + } else { + let detector = EVMBalanceSlotDetector::new(BalanceSlotDetectorConfig { + rpc_url: rpc_url.clone(), + ..Default::default() + }) + .into_diagnostic()?; - let results = detector - .detect_balance_slots( - &[solution.given_token.clone()], - (**user_address).into(), - (*block.header.hash).into(), - ) - .await; + let results = detector + .detect_balance_slots( + std::slice::from_ref(&solution.given_token), + (**user_address).into(), + (*block.header.hash).into(), + ) + .await; - let balance_slot = - if let Some(Ok((_storage_addr, slot))) = results.get(&solution.given_token.clone()) { - slot - } else { - return Err(miette!("Couldn't find balance storage slot for token {token_address}")); - }; + let balance_slot = + if let Some(Ok((_storage_addr, slot))) = results.get(&solution.given_token.clone()) { + slot + } else { + return Err(miette!("Couldn't find balance storage slot for token {token_address}")); + }; - let detector = EVMAllowanceSlotDetector::new(AllowanceSlotDetectorConfig { - rpc_url, - ..Default::default() - }) - .into_diagnostic()?; + let detector = EVMAllowanceSlotDetector::new(AllowanceSlotDetectorConfig { + rpc_url, + ..Default::default() + }) + .into_diagnostic()?; - let results = detector - .detect_allowance_slots( - &[solution.given_token.clone()], - (**user_address).into(), - transaction.to.clone(), // tycho router - (*block.header.hash).into(), - ) - .await; + let results = detector + .detect_allowance_slots( + std::slice::from_ref(&solution.given_token), + (**user_address).into(), + transaction.to.clone(), // tycho router + (*block.header.hash).into(), + ) + .await; - let allowance_slot = - if let Some(Ok((_storage_addr, slot))) = results.get(&solution.given_token.clone()) { + let allowance_slot = if let Some(Ok((_storage_addr, slot))) = + results.get(&solution.given_token.clone()) + { slot } else { return Err(miette!("Couldn't find allowance storage slot for token {token_address}")); }; - state_overwrites.insert( - token_address, - StateOverride::new() - .with_state_diff( - alloy::primitives::Bytes::from(allowance_slot.to_vec()), - alloy::primitives::Bytes::from(U256::MAX.to_be_bytes::<32>()), - ) - .with_state_diff( - alloy::primitives::Bytes::from(balance_slot.to_vec()), - alloy::primitives::Bytes::from(U256::MAX.to_be_bytes::<32>()), - ), - ); + overwrites.insert( + token_address, + AccountOverride::default().with_state_diff(vec![ + ( + alloy::primitives::B256::from_slice(allowance_slot), + alloy::primitives::B256::from_slice(&U256::MAX.to_be_bytes::<32>()), + ), + ( + alloy::primitives::B256::from_slice(balance_slot), + alloy::primitives::B256::from_slice( + &biguint_to_u256(&solution.given_amount).to_be_bytes::<32>(), + ), + ), + ]), + ); + } + overwrites.insert(user_address, AccountOverride::default().with_balance(eth_balance)); - Ok(state_overwrites) + Ok(overwrites) } /// Simulate a trade using eth_call for historical blocks @@ -271,10 +321,11 @@ pub async fn simulate_trade_with_eth_call( rpc_provider: &RPCProvider, transaction: &tycho_simulation::tycho_execution::encoding::models::Transaction, solution: &Solution, - block_number: u64, block: &Block, ) -> miette::Result { - let executor_bytecode = load_executor_bytecode(solution)?; + let first_swap = solution.swaps.first().unwrap(); + let protocol_system = &first_swap.component.protocol_system; + let user_address = Address::from_slice(&solution.sender[..20]); let (max_fee_per_gas, max_priority_fee_per_gas) = calculate_gas_fees(block)?; // Convert main transaction to alloy TransactionRequest @@ -295,28 +346,24 @@ pub async fn simulate_trade_with_eth_call( ); let tycho_router_address = Address::from_slice(&transaction.to[..20]); - // Copy router storage and code from current block to historical block - let router_override = rpc_provider - .copy_contract_storage_and_code(tycho_router_address, ROUTER_BYTECODE_JSON) + let router_overwrites = setup_router_overwrites(tycho_router_address, protocol_system) .await .wrap_err("Failed to create router override")?; - // Set up state overrides including router override - let mut state_overwrites = setup_state_overrides( + let mut user_overwrites = setup_user_overwrites( solution, transaction, user_address, - &executor_bytecode, rpc_provider.url.to_string(), block, ) - .await?; // Include executor override for historical blocks + .await?; - // Add the router override - state_overwrites.insert(tycho_router_address, router_override); + // Merge router overwrites with user overwrites + user_overwrites.extend(router_overwrites); let execution_amount_out = rpc_provider - .simulate_transactions_with_tracing(execution_tx, block_number, state_overwrites) + .simulate_transactions_with_tracing(execution_tx, block.number(), user_overwrites) .await .map_err(|e| { info!("Execution transaction failed with error: {}", e); diff --git a/protocol-testing/src/main.rs b/protocol-testing/src/main.rs index 58f07b9..c01625a 100644 --- a/protocol-testing/src/main.rs +++ b/protocol-testing/src/main.rs @@ -9,9 +9,10 @@ mod tycho_rpc; mod tycho_runner; mod utils; -use std::{fmt::Display, path::PathBuf}; +use std::{env, fmt::Display, path::PathBuf}; use clap::Parser; +use dotenv::dotenv; use miette::{miette, IntoDiagnostic, WrapErr}; use tracing::info; use tracing_subscriber::EnvFilter; @@ -78,6 +79,9 @@ impl Args { } fn main() -> miette::Result<()> { + // Load .env file before setting up logging + dotenv().ok(); + tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env()) .with_target(false) @@ -90,6 +94,10 @@ fn main() -> miette::Result<()> { } info!("{version}"); + let rpc_url = env::var("RPC_URL") + .into_diagnostic() + .wrap_err("Missing RPC_URL in environment")?; + let args = Args::parse(); let test_runner = TestRunner::new( @@ -99,7 +107,8 @@ fn main() -> miette::Result<()> { args.db_url, args.vm_simulation_traces, args.execution_traces, - ); + rpc_url, + )?; test_runner.run_tests() } diff --git a/protocol-testing/src/rpc.rs b/protocol-testing/src/rpc.rs index 43dd0b4..e387b43 100644 --- a/protocol-testing/src/rpc.rs +++ b/protocol-testing/src/rpc.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, str::FromStr}; +use std::str::FromStr; use alloy::{ contract::{ContractInstance, Interface}, dyn_abi::DynSolValue, eips::eip1898::BlockId, - primitives::{address, keccak256, map::AddressHashMap, Address, FixedBytes, U256}, + primitives::{address, map::AddressHashMap, Address, U256}, providers::{Provider, ProviderBuilder}, rpc::types::{ state::AccountOverride, @@ -20,12 +20,9 @@ use alloy::{ use miette::{IntoDiagnostic, WrapErr}; use serde_json::Value; use tracing::info; -use tycho_common::Bytes; +use tycho_simulation::tycho_common::Bytes; -use crate::{ - execution::{StateOverride, EXECUTOR_ADDRESSES}, - traces::print_call_trace, -}; +use crate::traces::print_call_trace; const NATIVE_ALIASES: &[Address] = &[ address!("0x0000000000000000000000000000000000000000"), @@ -101,139 +98,13 @@ impl RPCProvider { .and_then(|block_opt| block_opt.ok_or_else(|| miette::miette!("Block not found"))) } - /// Helper function to get the contract's storage at the given slot at the latest block. - pub async fn get_storage_at( - &self, - contract_address: Address, - slot: FixedBytes<32>, - ) -> miette::Result> { - let provider = ProviderBuilder::new().connect_http(self.url.clone()); - let storage_value = provider - .get_storage_at(contract_address, slot.into()) - .await - .into_diagnostic() - .wrap_err("Failed to fetch storage slot")?; - - Ok(storage_value.into()) - } - - pub async fn copy_contract_storage_and_code( - &self, - contract_address: Address, - router_bytecode_json: &str, - ) -> miette::Result { - let json_value: serde_json::Value = serde_json::from_str(router_bytecode_json) - .into_diagnostic() - .wrap_err("Failed to parse router JSON")?; - - let bytecode_str = json_value["runtimeBytecode"] - .as_str() - .ok_or_else(|| miette::miette!("No runtimeBytecode field found in router JSON"))?; - - // Remove 0x prefix if present - let bytecode_hex = if let Some(stripped) = bytecode_str.strip_prefix("0x") { - stripped - } else { - bytecode_str - }; - - let router_bytecode = hex::decode(bytecode_hex) - .into_diagnostic() - .wrap_err("Failed to decode router bytecode from hex")?; - - // Start with the router bytecode override - let mut state_override = StateOverride::new().with_code(router_bytecode); - - for (protocol_name, &executor_address) in EXECUTOR_ADDRESSES.iter() { - let storage_slot = self.calculate_executor_storage_slot(executor_address); - - match self - .get_storage_at(contract_address, storage_slot) - .await - { - Ok(value) => { - state_override = state_override.with_state_diff( - alloy::primitives::Bytes::from(storage_slot.to_vec()), - alloy::primitives::Bytes::from(value.to_vec()), - ); - } - Err(e) => { - info!( - "Failed to fetch executor approval for {} ({:?}): {}", - protocol_name, executor_address, e - ); - } - } - } - Ok(state_override) - } - - /// Calculate storage slot for Solidity mapping. - /// - /// The solidity code: - /// keccak256(abi.encodePacked(bytes32(key), bytes32(slot))) - pub fn calculate_executor_storage_slot(&self, key: Address) -> FixedBytes<32> { - // Convert key (20 bytes) to 32-byte left-padded array (uint256) - let mut key_bytes = [0u8; 32]; - key_bytes[12..].copy_from_slice(key.as_slice()); - - // The base of the executor storage slot is 1, since there is only one - // variable that is initialized before it (which is _roles in AccessControl.sol). - // In this case, _roles gets slot 0. - // The slots are given in order to the parent contracts' variables first and foremost. - let slot = U256::from(1); - - // Convert U256 slot to 32-byte big-endian array - let slot_bytes = slot.to_be_bytes::<32>(); - - // Concatenate key_bytes + slot_bytes, then keccak hash - let mut buf = [0u8; 64]; - buf[..32].copy_from_slice(&key_bytes); - buf[32..].copy_from_slice(&slot_bytes); - keccak256(buf) - } - - fn bytes_to_fixed_32(bytes: &[u8]) -> [u8; 32] { - let mut arr = [0u8; 32]; - let len = bytes.len().min(32); - // Right-pad by copying to the end of the array - arr[32 - len..].copy_from_slice(&bytes[bytes.len() - len..]); - arr - } - pub async fn simulate_transactions_with_tracing( &self, transaction: TransactionRequest, block_number: u64, - state_overwrites: HashMap, + state_overwrites: AddressHashMap, ) -> miette::Result { let provider = ProviderBuilder::new().connect_http(self.url.clone()); - // Convert our StateOverride to alloy's state override format - let mut alloy_state_overrides = AddressHashMap::default(); - for (address, override_data) in state_overwrites { - let mut account_override = AccountOverride::default(); - - if let Some(code) = override_data.code { - account_override.code = Some(alloy::primitives::Bytes::from(code)); - } - - if let Some(balance) = override_data.balance { - account_override.balance = Some(balance); - } - - if !override_data.state_diff.is_empty() { - // Convert Bytes to FixedBytes<32> for storage slots - let mut state_diff = HashMap::default(); - for (slot, value) in override_data.state_diff { - let slot_bytes = Self::bytes_to_fixed_32(&slot); - let value_bytes = Self::bytes_to_fixed_32(&value); - state_diff.insert(FixedBytes(slot_bytes), FixedBytes(value_bytes)); - } - account_override.state_diff = Some(state_diff); - } - - alloy_state_overrides.insert(address, account_override); - } // Configure tracing options - use callTracer for better formatted results let tracing_options = GethDebugTracingOptions { @@ -247,10 +118,10 @@ impl RPCProvider { let trace_options = GethDebugTracingCallOptions { tracing_options, - state_overrides: if alloy_state_overrides.is_empty() { + state_overrides: if state_overwrites.is_empty() { None } else { - Some(alloy_state_overrides) + Some(state_overwrites) }, block_overrides: None, }; diff --git a/protocol-testing/src/test_runner.rs b/protocol-testing/src/test_runner.rs index b32c6fd..12a09b1 100644 --- a/protocol-testing/src/test_runner.rs +++ b/protocol-testing/src/test_runner.rs @@ -1,6 +1,14 @@ -use std::{collections::HashMap, env, path::PathBuf, str::FromStr, sync::LazyLock}; +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, + str::FromStr, + sync::LazyLock, +}; -use alloy::primitives::{Address, U256}; +use alloy::{ + primitives::{Address, U256}, + rpc::types::Block, +}; use figment::{ providers::{Format, Yaml}, Figment, @@ -13,27 +21,27 @@ use num_traits::{Signed, ToPrimitive, Zero}; use postgres::{Client, Error, NoTls}; use tokio::runtime::Runtime; use tracing::{debug, error, info, warn}; -use tycho_client::feed::BlockHeader; -use tycho_common::{ - dto::{Chain, ProtocolComponent, ResponseAccount, ResponseProtocolState}, - models::token::Token, - Bytes, -}; use tycho_simulation::{ evm::{ decoder::TychoStreamDecoder, engine_db::tycho_db::PreCachedDB, protocol::{ - pancakeswap_v2::state::PancakeswapV2State, u256_num::bytes_to_u256, - uniswap_v2::state::UniswapV2State, uniswap_v3::state::UniswapV3State, - vm::state::EVMPoolState, + ekubo::state::EkuboState, pancakeswap_v2::state::PancakeswapV2State, + u256_num::bytes_to_u256, uniswap_v2::state::UniswapV2State, + uniswap_v3::state::UniswapV3State, vm::state::EVMPoolState, }, }, - protocol::models::DecoderContext, + protocol::models::{DecoderContext, Update}, tycho_client::feed::{ synchronizer::{ComponentWithState, Snapshot, StateSyncMessage}, - FeedMessage, + BlockHeader, FeedMessage, }, + tycho_common::{ + dto::{Chain, ProtocolComponent, ResponseAccount, ResponseProtocolState}, + models::token::Token, + Bytes, + }, + tycho_execution::encoding::evm::utils::bytes_to_address, }; use crate::{ @@ -57,11 +65,12 @@ static CLONE_TO_BASE_PROTOCOL: LazyLock> = LazyLock::new(|| pub struct TestRunner { db_url: String, vm_simulation_traces: bool, - execution_traces: bool, substreams_path: PathBuf, adapter_contract_builder: AdapterContractBuilder, match_test: Option, config_file_path: PathBuf, + runtime: Runtime, + rpc_provider: RPCProvider, } impl TestRunner { @@ -72,7 +81,8 @@ impl TestRunner { db_url: String, vm_traces: bool, execution_traces: bool, - ) -> Self { + rpc_url: String, + ) -> miette::Result { let base_protocol = CLONE_TO_BASE_PROTOCOL .get(protocol.as_str()) .unwrap_or(&protocol.as_str()) @@ -98,15 +108,19 @@ impl TestRunner { }; let config_file_path = substreams_path.join(&config_file_name); - Self { + let rpc_provider = RPCProvider::new(rpc_url, execution_traces); + let runtime = Runtime::new().into_diagnostic()?; + + Ok(Self { db_url, vm_simulation_traces: vm_traces, - execution_traces, + runtime, + rpc_provider, substreams_path, adapter_contract_builder, match_test, config_file_path, - } + }) } pub fn run_tests(&self) -> miette::Result<()> { @@ -231,22 +245,51 @@ impl TestRunner { ) .wrap_err("Failed to run Tycho")?; - tycho_runner.run_with_rpc_server( - |expected_components, start_block, stop_block| { - validate_state( - expected_components, - start_block, - stop_block, - config, - &self.adapter_contract_builder, - self.vm_simulation_traces, - self.execution_traces, - ) - }, - &test.expected_components, - test.start_block, - test.stop_block, - )? + let rpc_server = tycho_runner.start_rpc_server()?; + + let expected_ids = test + .expected_components + .iter() + .map(|c| c.base.id.to_lowercase()) + .collect::>(); + + let (update, component_tokens, response_protocol_states_by_id, block) = self + .fetch_from_tycho_rpc( + &config.protocol_system, + expected_ids, + &config.adapter_contract, + &config.adapter_build_signature, + &config.adapter_build_args, + self.vm_simulation_traces, + test.stop_block, + )?; + + // Step 1: Validate that all expected components are present on Tycho after indexing + self.validate_state(&test.expected_components, update.clone())?; + + // Step 2: Validate Token Balances + match config.skip_balance_check { + true => info!("Skipping balance check"), + false => { + self.validate_token_balances( + &component_tokens, + &response_protocol_states_by_id, + test.start_block, + )?; + info!("All token balances match the values found onchain") + } + } + // Step 3: Run Tycho Simulation and Execution + self.simulate_and_execute( + update, + &component_tokens, + block, + Some(test.expected_components.clone()), + )?; + + tycho_runner.stop_rpc_server(rpc_server)?; + + Ok(()) } fn empty_database(&self) -> Result<(), Error> { @@ -263,343 +306,428 @@ impl TestRunner { Ok(()) } -} -fn validate_state( - expected_components: &Vec, - start_block: u64, - stop_block: u64, - config: &IntegrationTestsConfig, - adapter_contract_builder: &AdapterContractBuilder, - vm_simulation_traces: bool, - execution_traces: bool, -) -> miette::Result<()> { - let rt = Runtime::new().unwrap(); + /// Fetches protocol data from the Tycho RPC server and prepares it for validation and + /// simulation. + /// + /// This method connects to the running Tycho RPC server to retrieve protocol components, + /// states, and contract storage. It then sets up the Tycho Decoder and creates an update + /// message that can be used for validation and simulation testing. + /// + /// # Arguments + /// * `protocol_system` - The protocol system identifier (e.g., "uniswap_v2", "balancer_v2") + /// * `expected_component_ids` - List of component IDs to fetch from Tycho + /// * `adapter_contract` - Optional adapter contract name for VM-based protocols + /// * `adapter_build_signature` - Optional build signature for the adapter contract + /// * `adapter_build_args` - Optional build arguments for the adapter contract + /// * `vm_simulation_traces` - Whether to enable VM simulation traces + /// * `stop_block` - The block number to fetch data for + /// + /// # Returns + /// A tuple containing: + /// - `Update` - Decoded protocol state update for simulation + /// - `HashMap>` - Token mappings for each component + /// - `HashMap` - Protocol states by component ID + /// - `Block` - The block header for the specified block + #[allow(clippy::type_complexity, clippy::too_many_arguments)] + fn fetch_from_tycho_rpc( + &self, + protocol_system: &str, + expected_component_ids: Vec, + adapter_contract: &Option, + adapter_build_signature: &Option, + adapter_build_args: &Option, + vm_simulation_traces: bool, + stop_block: u64, + ) -> miette::Result<( + Update, + HashMap>, + HashMap, + Block, + )> { + // Create Tycho client for the RPC server + let tycho_client = TychoClient::new("http://localhost:4242") + .into_diagnostic() + .wrap_err("Failed to create Tycho client")?; - // Create Tycho client for the RPC server - let tycho_client = TychoClient::new("http://localhost:4242") - .into_diagnostic() - .wrap_err("Failed to create Tycho client")?; + let chain = Chain::Ethereum; - let chain = Chain::Ethereum; - let protocol_system = &config.protocol_system; + // Fetch data from Tycho RPC. We use block_on to avoid using async functions on the testing + // module, in order to simplify debugging + let protocol_components = self + .runtime + .block_on(tycho_client.get_protocol_components(protocol_system, chain)) + .into_diagnostic() + .wrap_err("Failed to get protocol components")?; - // Fetch data from Tycho RPC. We use block_on to avoid using async functions on the testing - // module, in order to simplify debugging - let protocol_components = rt - .block_on(tycho_client.get_protocol_components(protocol_system, chain)) - .into_diagnostic() - .wrap_err("Failed to get protocol components")?; + let protocol_states = self + .runtime + .block_on(tycho_client.get_protocol_state( + protocol_system, + expected_component_ids.clone(), + chain, + )) + .into_diagnostic() + .wrap_err("Failed to get protocol state")?; - let expected_ids = expected_components - .iter() - .map(|c| c.base.id.to_lowercase()) - .collect::>(); + let vm_storages = self + .runtime + .block_on(tycho_client.get_contract_state(protocol_system, chain)) + .into_diagnostic() + .wrap_err("Failed to get contract state")?; - let protocol_states = rt - .block_on(tycho_client.get_protocol_state(protocol_system, expected_ids, chain)) - .into_diagnostic() - .wrap_err("Failed to get protocol state")?; - - let vm_storages = rt - .block_on(tycho_client.get_contract_state(protocol_system, chain)) - .into_diagnostic() - .wrap_err("Failed to get contract state")?; - - // Create a map of component IDs to components for easy lookup - let components_by_id: HashMap = protocol_components - .clone() - .into_iter() - .map(|c| (c.id.to_lowercase(), c)) - .collect(); - - let protocol_states_by_id: HashMap = protocol_states - .into_iter() - .map(|s| (s.component_id.to_lowercase(), s)) - .collect(); - - debug!("Found {} protocol components", components_by_id.len()); - debug!("Found {} protocol states", protocol_states_by_id.len()); - - // Step 1: Validate that all expected components are present on Tycho after indexing - debug!("Validating {:?} expected components", expected_components.len()); - for expected_component in expected_components { - let component_id = expected_component - .base - .id - .to_lowercase(); - - let component = components_by_id - .get(&component_id) - .ok_or_else(|| miette!("Component {:?} was not found on Tycho", component_id))?; - - let diff = expected_component - .base - .compare(component, true); - match diff { - Some(diff) => { - return Err(miette!( - "Component {} does not match the expected state:\n{}", - component_id, - diff - )); - } - None => { - info!("Component {} matches the expected state", component_id); - } - } - } - info!("All expected components were successfully found on Tycho and match the expected state"); - - // Step 2: Validate Token Balances - let rpc_url = env::var("RPC_URL") - .into_diagnostic() - .wrap_err("Missing RPC_URL in environment")?; - let rpc_provider = RPCProvider::new(rpc_url, execution_traces); - - match config.skip_balance_check { - true => info!("Skipping balance check"), - false => { - validate_token_balances( - &components_by_id, - &protocol_states_by_id, - start_block, - &rt, - &rpc_provider, - )?; - info!("All token balances match the values found onchain") - } - } - - // Step 3: Run Tycho Simulation - // Filter out components that have skip_simulation = true - let simulation_component_ids: std::collections::HashSet = expected_components - .iter() - .filter(|c| !c.skip_simulation) - .map(|c| c.base.id.to_lowercase()) - .collect(); - - info!("Components to simulate: {}", simulation_component_ids.len()); - for id in &simulation_component_ids { - info!("Simulating component: {}", id); - } - - if simulation_component_ids.is_empty() { - info!("No components to simulate, skipping simulation validation"); - return Ok(()); - } - - // Filter out components that have skip_execution = true - let execution_component_ids: std::collections::HashSet = expected_components - .iter() - .filter(|c| !c.skip_execution) - .map(|c| c.base.id.clone().to_lowercase()) - .collect(); - - let adapter_contract_path; - let mut adapter_contract_path_str: Option<&str> = None; - - // Adapter contract will only be configured for VM protocols, not natively implemented - // protocols. - if let Some(adapter_contract_name) = &config.adapter_contract { - // Build/find the adapter contract - adapter_contract_path = match adapter_contract_builder.find_contract(adapter_contract_name) - { - Ok(path) => { - debug!("Found adapter contract at: {}", path.display()); - path - } - Err(_) => { - info!("Adapter contract not found, building it..."); - adapter_contract_builder - .build_target( - adapter_contract_name, - config - .adapter_build_signature - .as_deref(), - config.adapter_build_args.as_deref(), - ) - .wrap_err("Failed to build adapter contract")? - } + // Create a map of component IDs to components for easy lookup + let mut components_by_id: HashMap = protocol_components + .clone() + .into_iter() + .map(|c| (c.id.to_lowercase(), c)) + .collect(); + if !expected_component_ids.is_empty() { + components_by_id.retain(|id, _| expected_component_ids.contains(id)) }; - debug!("Using adapter contract: {}", adapter_contract_path.display()); - adapter_contract_path_str = Some(adapter_contract_path.to_str().unwrap()); - } + let protocol_states_by_id: HashMap = protocol_states + .into_iter() + .map(|s| (s.component_id.to_lowercase(), s)) + .collect(); - // Clear the shared database state to ensure test isolation - // This prevents state from previous tests from affecting the current test - tycho_simulation::evm::engine_db::SHARED_TYCHO_DB.clear(); + debug!("Found {} protocol components", components_by_id.len()); + debug!("Found {} protocol states", protocol_states_by_id.len()); - let mut decoder = TychoStreamDecoder::new(); - let mut decoder_context = DecoderContext::new().vm_traces(vm_simulation_traces); + let adapter_contract_path; + let mut adapter_contract_path_str: Option<&str> = None; - if let Some(vm_adapter_path) = adapter_contract_path_str { - decoder_context = decoder_context.vm_adapter_path(vm_adapter_path); - } - match protocol_system.as_str() { - "uniswap_v2" | "sushiswap_v2" => { - decoder - .register_decoder_with_context::(protocol_system, decoder_context); - } - "pancakeswap_v2" => { - decoder.register_decoder_with_context::( - protocol_system, - decoder_context, - ); - } - "uniswap_v3" | "pancakeswap_v3" => { - decoder - .register_decoder_with_context::(protocol_system, decoder_context); - } - _ => { - decoder.register_decoder_with_context::>( - protocol_system, - decoder_context, - ); - } - } + // Adapter contract will only be configured for VM protocols, not natively implemented + // protocols. + if let Some(adapter_contract_name) = &adapter_contract { + // Build/find the adapter contract + adapter_contract_path = match self + .adapter_contract_builder + .find_contract(adapter_contract_name) + { + Ok(path) => { + debug!("Found adapter contract at: {}", path.display()); + path + } + Err(_) => { + info!("Adapter contract not found, building it..."); + self.adapter_contract_builder + .build_target( + adapter_contract_name, + adapter_build_signature.as_deref(), + adapter_build_args.as_deref(), + ) + .wrap_err("Failed to build adapter contract")? + } + }; - // Mock a stream message, with only a Snapshot and no deltas - let mut states: HashMap = HashMap::new(); - for (id, component) in &components_by_id { - let component_id = id; - - // Only include components that should be simulated - if !simulation_component_ids.contains(component_id) { - continue; + debug!("Using adapter contract: {}", adapter_contract_path.display()); + adapter_contract_path_str = Some(adapter_contract_path.to_str().unwrap()); } - let state = protocol_states_by_id - .get(component_id) - .wrap_err(format!( - "Component {id} does not exist in protocol_states_by_id {protocol_states_by_id:?}" - ))? - .clone(); + // Clear the shared database state to ensure test isolation + // This prevents state from previous tests from affecting the current test + tycho_simulation::evm::engine_db::SHARED_TYCHO_DB.clear(); - let component_with_state = ComponentWithState { - state, - component: component.clone(), - component_tvl: None, - // Neither UniswapV4 with hooks not certain balancer pools are currently supported for - // SDK testing - entrypoints: vec![], - }; - states.insert(component_id.clone(), component_with_state); - } - // Convert vm_storages to a HashMap - match Python behavior exactly - let vm_storage: HashMap = vm_storages - .into_iter() - .map(|x| (x.address.clone(), x)) - .collect(); + let mut decoder = TychoStreamDecoder::new(); + decoder.skip_state_decode_failures(true); + let mut decoder_context = DecoderContext::new().vm_traces(vm_simulation_traces); - let snapshot = Snapshot { states, vm_storage }; + if let Some(vm_adapter_path) = adapter_contract_path_str { + decoder_context = decoder_context.vm_adapter_path(vm_adapter_path); + } + match protocol_system { + "uniswap_v2" | "sushiswap_v2" => { + decoder.register_decoder_with_context::( + protocol_system, + decoder_context, + ); + } + "pancakeswap_v2" => { + decoder.register_decoder_with_context::( + protocol_system, + decoder_context, + ); + } + "uniswap_v3" | "pancakeswap_v3" => { + decoder.register_decoder_with_context::( + protocol_system, + decoder_context, + ); + } + "ekubo_v2" => { + decoder + .register_decoder_with_context::(protocol_system, decoder_context); + } + _ => { + decoder.register_decoder_with_context::>( + protocol_system, + decoder_context, + ); + } + } - let bytes = [0u8; 32]; + // Mock a stream message, with only a Snapshot and no deltas + let mut states: HashMap = HashMap::new(); + for (id, component) in &components_by_id { + let component_id = id; - // Get block header to extract the timestamp - let block_header = rt - .block_on(rpc_provider.get_block_header(stop_block)) - .wrap_err("Failed to get block header")?; + let state = protocol_states_by_id + .get(component_id) + .wrap_err(format!( + "Component {id} does not exist in protocol_states_by_id {protocol_states_by_id:?}" + ))? + .clone(); - let state_msgs: HashMap> = HashMap::from([( - String::from(protocol_system), - StateSyncMessage { - header: BlockHeader { - hash: Bytes::from(bytes), - number: stop_block, - parent_hash: Bytes::from(bytes), - revert: false, - timestamp: block_header.header.timestamp, + let component_with_state = ComponentWithState { + state, + component: component.clone(), + component_tvl: None, + // Neither UniswapV4 with hooks not certain balancer pools are currently supported + // for SDK testing + entrypoints: vec![], + }; + states.insert(component_id.clone(), component_with_state); + } + + // Convert vm_storages to a HashMap + let vm_storage: HashMap = vm_storages + .into_iter() + .map(|x| (x.address.clone(), x)) + .collect(); + + let snapshot = Snapshot { states, vm_storage }; + + // Get block header to extract the timestamp + let block_header = self + .runtime + .block_on( + self.rpc_provider + .get_block_header(stop_block), + ) + .wrap_err("Failed to get block header")?; + + let state_msgs: HashMap> = HashMap::from([( + String::from(protocol_system), + StateSyncMessage { + header: BlockHeader { + hash: (*block_header.hash()).into(), + number: stop_block, + parent_hash: Bytes::default(), + revert: false, + timestamp: block_header.header.timestamp, + }, + snapshots: snapshot, + deltas: None, + removed_components: HashMap::new(), }, - snapshots: snapshot, - deltas: None, - removed_components: HashMap::new(), - }, - )]); + )]); - let all_tokens = rt - .block_on(tycho_client.get_tokens(Chain::Ethereum, None, None)) - .into_diagnostic() - .wrap_err("Failed to get tokens")?; - debug!("Loaded {} tokens", all_tokens.len()); + let all_tokens = self + .runtime + .block_on(tycho_client.get_tokens(Chain::Ethereum, None, None)) + .into_diagnostic() + .wrap_err("Failed to get tokens")?; + debug!("Loaded {} tokens", all_tokens.len()); - rt.block_on(decoder.set_tokens(all_tokens)); + self.runtime + .block_on(decoder.set_tokens(all_tokens)); - let mut pairs: HashMap> = HashMap::new(); + let message: FeedMessage = FeedMessage { state_msgs, sync_states: Default::default() }; - let message: FeedMessage = FeedMessage { state_msgs, sync_states: Default::default() }; + let block_msg = self + .runtime + .block_on(decoder.decode(&message)) + .into_diagnostic() + .wrap_err("Failed to decode message")?; - let block_msg = rt - .block_on(decoder.decode(&message)) - .into_diagnostic() - .wrap_err("Failed to decode message")?; + let mut component_tokens: HashMap> = HashMap::new(); - for (id, comp) in block_msg.new_pairs.iter() { - pairs - .entry(id.clone()) - .or_insert_with(|| comp.tokens.clone()); + for (id, comp) in block_msg.new_pairs.iter() { + component_tokens + .entry(id.clone()) + .or_insert_with(|| comp.tokens.clone()); + } + + Ok((block_msg, component_tokens, protocol_states_by_id, block_header)) } - for (id, state) in block_msg.states.iter() { - if let Some(tokens) = pairs.get(id) { - let formatted_token_str = format!("{:}/{:}", &tokens[0].symbol, &tokens[1].symbol); - state - .spot_price(&tokens[0], &tokens[1]) - .map(|price| info!("Spot price {:?}: {:?}", formatted_token_str, price)) - .into_diagnostic() - .wrap_err(format!("Error calculating spot price for Pool {id:?}."))?; + /// Validates that the protocol components retrieved from Tycho match the expected + /// configuration. + /// + /// This method compares each expected component from the test configuration against + /// the actual components found in the protocol state update. It ensures that all + /// expected components are present and their properties (tokens, addresses, fees, etc.) + /// match the expected values. + /// + /// # Arguments + /// * `expected_components` - Vector of expected protocol components with their test + /// configuration + /// * `block_msg` - The decoded protocol state update containing the actual component data + /// + /// # Returns + /// Returns `Ok(())` if all expected components are found and match their expected state. + /// + /// # Errors + /// Returns an error if: + /// - Any expected component is missing from the Tycho state + /// - Any component's properties don't match the expected values (shows detailed diff) + fn validate_state( + &self, + expected_components: &Vec, + block_msg: Update, + ) -> miette::Result<()> { + debug!("Validating {:?} expected components", expected_components.len()); + for expected_component in expected_components { + let component_id = expected_component + .base + .id + .to_lowercase(); - // Test get_amount_out with different percentages of limits. The reserves or limits are - // relevant because we need to know how much to test with. We don't know if a pool is - // going to revert with 10 or 10 million USDC, for example, so by using the limits we - // can use "safe values" where the sim shouldn't break. - // We then retrieve the amount out for 0.1%, 1% and 10%. - let percentages = [0.001, 0.01, 0.1]; + let component = block_msg + .new_pairs + .get(&component_id) + .ok_or_else(|| miette!("Component {:?} was not found on Tycho", component_id))?; - // Test all permutations of swap directions - let swap_directions: Vec<_> = tokens + let diff = expected_component + .base + .compare(component, true); + match diff { + Some(diff) => { + return Err(miette!( + "Component {} does not match the expected state:\n{}", + component_id, + diff + )); + } + None => { + info!("Component {} matches the expected state", component_id); + } + } + } + info!( + "All expected components were successfully found on Tycho and match the expected state" + ); + Ok(()) + } + + /// Performs comprehensive simulation and execution testing on protocol components. + /// + /// This method tests each protocol component by: + /// 1. Computing spot prices for all token pairs + /// 2. Simulating swaps with different input amounts (0.1%, 1%, 10% of limits) + /// 3. Testing all possible swap directions between tokens + /// 4. Simulating actual execution using historical block state + /// 5. Comparing simulation results with execution results for accuracy + /// + /// The simulation uses the Tycho SDK to calculate expected outputs, while execution + /// uses `debug_traceCall` with state overwrites to simulate actual on-chain behavior + /// at historical blocks. + /// + /// # Arguments + /// * `update` - The decoded protocol state containing all component data + /// * `component_tokens` - Mapping of component IDs to their associated tokens + /// * `block` - The historical block to use for execution testing + /// * `expected_components` - Optional test configuration to determine which components to skip + /// + /// # Returns + /// Returns `Ok(())` if all simulations and executions complete successfully within tolerance. + /// + /// # Errors + /// Returns an error if: + /// - Spot price calculation fails for any component + /// - Simulation fails to calculate amount out + /// - Execution simulation fails or reverts + /// - Difference between simulation and execution exceeds 5% slippage tolerance + /// + /// Components can be skipped using `skip_simulation` or `skip_execution` flags + /// in the test configuration. + fn simulate_and_execute( + &self, + update: Update, + component_tokens: &HashMap>, + block: Block, + expected_components: Option>, + ) -> miette::Result<()> { + let mut skip_simulation = HashSet::new(); + let mut skip_execution = HashSet::new(); + if let Some(components) = expected_components { + skip_simulation = components .iter() - .permutations(2) - .map(|perm| (perm[0], perm[1])) + .filter(|c| c.skip_simulation) + .map(|c| c.base.id.to_lowercase()) .collect(); + skip_execution = components + .iter() + .filter(|c| c.skip_execution) + .map(|c| c.base.id.to_lowercase()) + .collect(); + } - for (token_in, token_out) in &swap_directions { - let (max_input, max_output) = state - .get_limits(token_in.address.clone(), token_out.address.clone()) + for (id, state) in update.states.iter() { + if skip_simulation.contains(id) { + info!("Skipping simulation for component {id}"); + continue + } + if let Some(tokens) = component_tokens.get(id) { + let formatted_token_str = format!("{:}/{:}", &tokens[0].symbol, &tokens[1].symbol); + state + .spot_price(&tokens[0], &tokens[1]) + .map(|price| info!("Spot price {:?}: {:?}", formatted_token_str, price)) .into_diagnostic() - .wrap_err(format!( - "Error getting limits for Pool {id:?} for in token: {}, and out token: {}", - token_in.address, token_out.address - ))?; + .wrap_err(format!("Error calculating spot price for Pool {id:?}."))?; - info!( + // Test get_amount_out with different percentages of limits. The reserves or limits + // are relevant because we need to know how much to test with. We + // don't know if a pool is going to revert with 10 or 10 million + // USDC, for example, so by using the limits we can use "safe + // values" where the sim shouldn't break. We then retrieve the + // amount out for 0.1%, 1% and 10%. + let percentages = [0.001, 0.01, 0.1]; + + // Test all permutations of swap directions + let swap_directions: Vec<_> = tokens + .iter() + .permutations(2) + .map(|perm| (perm[0], perm[1])) + .collect(); + + for (token_in, token_out) in &swap_directions { + let (max_input, max_output) = state + .get_limits(token_in.address.clone(), token_out.address.clone()) + .into_diagnostic() + .wrap_err(format!( + "Error getting limits for Pool {id:?} for in token: {}, and out token: {}", + token_in.address, token_out.address + ))?; + + info!( "Retrieved limits. | Max input: {max_input} {} | Max output: {max_output} {}", token_in.symbol, token_out.symbol ); - for percentage in &percentages { - // For precision, multiply by 1000 then divide by 1000 - let percentage_biguint = BigUint::from((percentage * 1000.0) as u32); - let thousand = BigUint::from(1000u32); - let amount_in = (&max_input * &percentage_biguint) / &thousand; + for percentage in &percentages { + // For precision, multiply by 1000 then divide by 1000 + let percentage_biguint = BigUint::from((percentage * 1000.0) as u32); + let thousand = BigUint::from(1000u32); + let amount_in = (&max_input * &percentage_biguint) / &thousand; - // Skip if amount is zero - if amount_in.is_zero() { - info!("Amount in multiplied by percentage {percentage} is zero. Skipping pool {id}."); - continue; - } + // Skip if amount is zero + if amount_in.is_zero() { + info!("Amount in multiplied by percentage {percentage} is zero. Skipping pool {id}."); + continue; + } - let amount_out_result = state - .get_amount_out(amount_in.clone(), token_in, token_out) - .into_diagnostic() - .wrap_err(format!( - "Error calculating amount out for Pool {id:?} at {:.1}% with input of {amount_in} {}.", - percentage * 100.0, - token_in.symbol, - ))?; + let amount_out_result = state + .get_amount_out(amount_in.clone(), token_in, token_out) + .into_diagnostic() + .wrap_err(format!( + "Error calculating amount out for Pool {id:?} at {:.1}% with input of {amount_in} {}.", + percentage * 100.0, + token_in.symbol, + ))?; - info!( + info!( "Simulated amount out for trading {:.1}% of max: ({} {} -> {} {}) (gas: {})", percentage * 100.0, amount_in, @@ -609,129 +737,131 @@ fn validate_state( amount_out_result.gas ); - // Only execute for components that should have execution - if !execution_component_ids.contains(id) { - info!("Skipping execution for component {id}"); - continue; - } + // Only execute for components that should have execution + if skip_execution.contains(id) { + info!("Skipping execution for component {id}"); + continue; + } - let protocol_component = block_msg.new_pairs.get(id).unwrap(); + let protocol_component = update.new_pairs.get(id).unwrap(); - let (calldata, solution) = encode_swap( - protocol_component.clone(), - token_in.address.clone(), - token_out.address.clone(), - amount_in, - amount_out_result.amount.clone(), - )?; + let (calldata, solution) = encode_swap( + protocol_component, + &token_in.address, + &token_out.address, + &amount_in, + &amount_out_result.amount, + )?; - info!("Simulating swap at historical block {}", block_header.header.number); - // Simulate the trade using debug_traceCall with overwrites - let execution_amount_out = - rt.block_on(execution::simulate_trade_with_eth_call( - &rpc_provider, - &calldata, - &solution, - stop_block, - &block_header, - )); + info!("Simulating swap at historical block {}", block.number()); + // Simulate the trade using debug_traceCall with overwrites + let execution_amount_out = + self.runtime + .block_on(execution::simulate_trade_with_eth_call( + &self.rpc_provider, + &calldata, + &solution, + &block, + )); - match execution_amount_out { - Ok(amount_out) => { - info!( - "Simulating execution passed with {} {} -> {} {}", - solution.given_amount, - token_in.symbol, - amount_out, - token_out.symbol - ); + match execution_amount_out { + Ok(amount_out) => { + info!( + "Simulating execution passed with {} {} -> {} {}", + solution.given_amount, + token_in.symbol, + amount_out, + token_out.symbol + ); - // Compare execution amount out with simulation amount out - let diff = BigInt::from(amount_out_result.amount) - - BigInt::from(amount_out.clone()); + // Compare execution amount out with simulation amount out + let diff = BigInt::from(amount_out_result.amount) - + BigInt::from(amount_out.clone()); - let slippage: BigRational = - BigRational::new(diff.abs(), BigInt::from(amount_out)); - if slippage.to_f64() > Some(0.05) { - return Err(miette!( + let slippage: BigRational = + BigRational::new(diff.abs(), BigInt::from(amount_out)); + if slippage.to_f64() > Some(0.05) { + return Err(miette!( "Execution amount and simulation amount differ more than 5%!" )); + } + } + Err(e) => { + return Err(miette!( + "Simulating execution failed for {} -> {}: {}", + token_in.symbol, + token_out.symbol, + e + )); } } - Err(e) => { - return Err(miette!( - "Simulating execution failed for {} -> {}: {}", - token_in.symbol, - token_out.symbol, - e - )); - } } } } } + Ok(()) } - Ok(()) -} -/// Validate that the token balances of the components match the values -/// on-chain, extracted by querying the token balances using a node. -fn validate_token_balances( - components_by_id: &HashMap, - protocol_states_by_id: &HashMap, - start_block: u64, - rt: &Runtime, - rpc_provider: &RPCProvider, -) -> miette::Result<()> { - for (id, component) in components_by_id.iter() { - let component_state = protocol_states_by_id.get(id); + /// Validate that the token balances of the components match the values + /// on-chain, extracted by querying the token balances using a node. + fn validate_token_balances( + &self, + component_tokens: &HashMap>, + protocol_states_by_id: &HashMap, + start_block: u64, + ) -> miette::Result<()> { + for (id, component) in protocol_states_by_id.iter() { + let tokens = component_tokens.get(id); + if let Some(tokens) = tokens { + for token in tokens { + let mut balance: U256 = U256::from(0); + let bal = component.balances.get(&token.address); + if let Some(bal) = bal { + let bal = bal.clone().into(); + balance = bytes_to_u256(bal); + } - for token in &component.tokens { - let mut balance: U256 = U256::from(0); - - if let Some(state) = component_state { - let bal = state.balances.get(token); - if let Some(bal) = bal { - let bal = bal.clone().into(); - balance = bytes_to_u256(bal); + info!( + "Validating token balance for component {} and token {}", + id, token.symbol + ); + let token_address = bytes_to_address(&token.address).into_diagnostic()?; + let component_address = + Address::from_str(id.as_str()).expect("Failed to parse component address"); + let node_balance = + self.runtime + .block_on(self.rpc_provider.get_token_balance( + token_address, + component_address, + start_block, + ))?; + if balance != node_balance { + return Err(miette!( + "Token balance mismatch for component {} and token {}", + id, + token.symbol + )); + } + info!( + "Token balance for component {} and token {} matches the expected value", + id, token.symbol + ); } + } else { + return Err(miette!("Couldn't find tokens for component {}", id,)); } - - info!("Validating token balance for component {} and token {}", component.id, token); - let token_address = Address::from_slice(&token[..20]); - let component_address = Address::from_str(component.id.as_str()) - .expect("Failed to parse component address"); - let node_balance = rt.block_on(rpc_provider.get_token_balance( - token_address, - component_address, - start_block, - ))?; - if balance != node_balance { - return Err(miette!( - "Token balance mismatch for component {} and token {}", - component.id, - token - )); - } - info!( - "Token balance for component {} and token {} matches the expected value", - component.id, token - ); } + Ok(()) } - Ok(()) } #[cfg(test)] mod tests { - use std::{collections::HashMap, str::FromStr}; + use std::{collections::HashMap, env, str::FromStr}; use dotenv::dotenv; use glob::glob; - use tycho_common::{ - dto::{ProtocolComponent, ResponseProtocolState}, - Bytes, - }; + use tycho_simulation::tycho_common::{dto::ResponseProtocolState, Bytes}; use super::*; @@ -781,21 +911,29 @@ mod tests { } } + fn get_mocked_runner() -> TestRunner { + dotenv().ok(); + let rpc_url = env::var("RPC_URL").unwrap(); + let current_dir = std::env::current_dir().unwrap(); + TestRunner::new( + current_dir, + "test-protocol".to_string(), + None, + "".to_string(), + false, + false, + rpc_url, + ) + .unwrap() + } #[test] fn test_token_balance_validation() { - dotenv().ok(); - let eth_rpc_url = env::var("RPC_URL").expect("Missing RPC_URL in environment"); - let rpc_provider = RPCProvider::new(eth_rpc_url, false); + let runner = get_mocked_runner(); // Setup mock data let block_number = 21998530; let token_bytes = Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(); let component_id = "0x787B8840100d9BaAdD7463f4a73b5BA73B00C6cA".to_string(); - - let component = ProtocolComponent { - id: component_id.clone(), - tokens: vec![token_bytes.clone()], - ..Default::default() - }; + let token = Token::new(&token_bytes, "FAKE", 18, 0, &[], Chain::Ethereum.into(), 100); let mut balances = HashMap::new(); let balance_bytes = Bytes::from( @@ -810,38 +948,25 @@ mod tests { ..Default::default() }; - let mut components_by_id = HashMap::new(); - components_by_id.insert(component_id.clone(), component.clone()); + let mut component_tokens = HashMap::new(); + component_tokens.insert(component_id.clone(), vec![token]); let mut protocol_states_by_id = HashMap::new(); protocol_states_by_id.insert(component_id.clone(), protocol_state.clone()); - let rt = Runtime::new().unwrap(); - dotenv().ok(); - let result = validate_token_balances( - &components_by_id, - &protocol_states_by_id, - block_number, - &rt, - &rpc_provider, - ); + let result = + runner.validate_token_balances(&component_tokens, &protocol_states_by_id, block_number); assert!(result.is_ok(), "Should pass when balance check is performed and balances match"); } #[test] fn test_token_balance_validation_fails_on_mismatch() { - dotenv().ok(); - let eth_rpc_url = env::var("RPC_URL").expect("Missing RPC_URL in environment"); - let rpc_provider = RPCProvider::new(eth_rpc_url, false); + let runner = get_mocked_runner(); + // Setup mock data let block_number = 21998530; let token_bytes = Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap(); let component_id = "0x787B8840100d9BaAdD7463f4a73b5BA73B00C6cA".to_string(); - - let component = ProtocolComponent { - id: component_id.clone(), - tokens: vec![token_bytes.clone()], - ..Default::default() - }; + let token = Token::new(&token_bytes, "FAKE", 18, 0, &[], Chain::Ethereum.into(), 100); // Set expected balance to zero let mut balances = HashMap::new(); @@ -853,20 +978,14 @@ mod tests { ..Default::default() }; - let mut components_by_id = HashMap::new(); - components_by_id.insert(component_id.clone(), component.clone()); + let mut component_tokens = HashMap::new(); + component_tokens.insert(component_id.clone(), vec![token]); let mut protocol_states_by_id = HashMap::new(); protocol_states_by_id.insert(component_id.clone(), protocol_state.clone()); - let rt = Runtime::new().unwrap(); dotenv().ok(); - let result = validate_token_balances( - &components_by_id, - &protocol_states_by_id, - block_number, - &rt, - &rpc_provider, - ); + let result = + runner.validate_token_balances(&component_tokens, &protocol_states_by_id, block_number); assert!( result.is_err(), "Should fail when balance check is performed and balances do not match" diff --git a/protocol-testing/src/traces.rs b/protocol-testing/src/traces.rs index 935519b..9172a53 100644 --- a/protocol-testing/src/traces.rs +++ b/protocol-testing/src/traces.rs @@ -5,8 +5,8 @@ use alloy::dyn_abi::{DynSolType, DynSolValue}; use colored::Colorize; -use foundry_evm::traces::identifier::SignaturesIdentifier; use serde_json::Value; +use tycho_simulation::foundry_evm::traces::identifier::SignaturesIdentifier; /// Decode method selectors and return function info pub async fn decode_method_selector_with_info(input: &str) -> Option<(String, Vec)> { @@ -84,18 +84,16 @@ pub async fn decode_function_with_params(input: &str) -> Option { if input.len() > 10 { let calldata_hex = &input[10..]; // Skip the 4-byte selector if let Ok(calldata) = hex::decode(calldata_hex) { - if let Ok(decoded_values) = + if let Ok(DynSolValue::Tuple(values)) = DynSolType::Tuple(param_types.clone()).abi_decode(&calldata) { - if let DynSolValue::Tuple(values) = decoded_values { - let formatted_params: Vec = values - .iter() - .zip(param_types.iter()) - .map(|(value, ty)| format_parameter_value(value, ty)) - .collect(); + let formatted_params: Vec = values + .iter() + .zip(param_types.iter()) + .map(|(value, ty)| format_parameter_value(value, ty)) + .collect(); - return Some(format!("{}({})", name, formatted_params.join(", "))); - } + return Some(format!("{}({})", name, formatted_params.join(", "))); } } } @@ -255,11 +253,6 @@ pub async fn print_call_trace(call: &Value, depth: usize) { .any(|field| call_obj.get(*field).is_some()); let call_failed = has_error || has_revert || has_other_error; - // Debug: if there's any failure, print all fields to help identify the error structure - if call_failed && depth <= 2 { - eprintln!("DEBUG: Failed call at depth {}: {:#?}", depth, call_obj); - } - // Create tree structure prefix let tree_prefix = if depth == 0 { "".to_string() } else { " ".repeat(depth) + "├─ " }; @@ -295,19 +288,19 @@ pub async fn print_call_trace(call: &Value, depth: usize) { let mut found_error = false; if let Some(error) = call_obj.get("error") { - println!("{}{}", result_indent, format!("[Error] {}", error)); + println!("{result_indent} [Error] {error}"); found_error = true; } if let Some(revert_reason) = call_obj.get("revertReason") { - println!("{}{}", result_indent, format!("[Revert] {}", revert_reason)); + println!("{}[Revert] {}", result_indent, revert_reason); found_error = true; } // Check for other possible error fields for error_field in ["revert", "reverted", "message", "errorMessage", "reason"] { if let Some(error_val) = call_obj.get(error_field) { - println!("{}{}", result_indent, format!("[{}] {}", error_field, error_val)); + println!("{}[{}] {}", result_indent, error_field, error_val); found_error = true; } } @@ -319,28 +312,27 @@ pub async fn print_call_trace(call: &Value, depth: usize) { { if !output.is_empty() && output != "0x" { // Try to decode revert reason from output if it looks like revert data - if output.starts_with("0x08c379a0") { + if let Some(stripped) = output.strip_prefix("0x08c379a0") { // Error(string) selector - if let Ok(decoded) = hex::decode(&output[10..]) { - if let Ok(reason) = alloy::dyn_abi::DynSolType::String.abi_decode(&decoded) + if let Ok(decoded) = hex::decode(stripped) { + if let Ok(alloy::dyn_abi::DynSolValue::String(reason_str)) = + alloy::dyn_abi::DynSolType::String.abi_decode(&decoded) { - if let alloy::dyn_abi::DynSolValue::String(reason_str) = reason { - println!( - "{}{}", - result_indent, - format!("[Revert] {}", reason_str).red() - ); - found_error = true; - } + println!( + "{}{}", + result_indent, + format!("[Revert] {}", reason_str).red() + ); + found_error = true; } } } if !found_error { - println!("{}{}", result_indent, format!("[Return] {}", output)); + println!("{}[Return] {}", result_indent, output); } } else if !found_error { - println!("{}{}", result_indent, "[Return]"); + println!("{}[Return]", result_indent); } } diff --git a/protocol-testing/src/tycho_rpc.rs b/protocol-testing/src/tycho_rpc.rs index 491d699..a1cce71 100644 --- a/protocol-testing/src/tycho_rpc.rs +++ b/protocol-testing/src/tycho_rpc.rs @@ -1,14 +1,16 @@ use std::{collections::HashMap, error::Error as StdError, fmt}; use tracing::debug; -use tycho_client::{rpc::RPCClient, HttpRPCClient}; -use tycho_common::{ - dto::{ - Chain, PaginationParams, ProtocolComponent, ProtocolComponentsRequestBody, ResponseAccount, - ResponseProtocolState, ResponseToken, StateRequestBody, VersionParam, +use tycho_simulation::{ + tycho_client::{rpc::RPCClient, HttpRPCClient}, + tycho_common::{ + dto::{ + Chain, PaginationParams, ProtocolComponent, ProtocolComponentsRequestBody, + ResponseAccount, ResponseProtocolState, ResponseToken, StateRequestBody, VersionParam, + }, + models::token::Token, + Bytes, }, - models::token::Token, - Bytes, }; /// Custom error type for RPC operations @@ -33,8 +35,8 @@ impl From> for RpcError { } } -impl From for RpcError { - fn from(error: tycho_client::RPCError) -> Self { +impl From for RpcError { + fn from(error: tycho_simulation::tycho_client::RPCError) -> Self { RpcError::ClientError(error.to_string()) } } @@ -79,7 +81,7 @@ impl TychoClient { ) -> Result, RpcError> { let chunk_size = 100; let concurrency = 1; - let version: tycho_common::dto::VersionParam = VersionParam::default(); + let version: tycho_simulation::tycho_common::dto::VersionParam = VersionParam::default(); let protocol_states = self .http_client diff --git a/protocol-testing/src/tycho_runner.rs b/protocol-testing/src/tycho_runner.rs index 0c80755..887e947 100644 --- a/protocol-testing/src/tycho_runner.rs +++ b/protocol-testing/src/tycho_runner.rs @@ -9,15 +9,16 @@ use std::{ use miette::{IntoDiagnostic, WrapErr}; use tracing::{debug, info}; -use crate::config::ProtocolComponentWithTestConfig; - pub struct TychoRunner { db_url: String, initialized_accounts: Vec, } -// TODO: Currently Tycho-Indexer cannot be run as a lib. We need to expose the entrypoints to allow -// running it as a lib +pub struct TychoRpcServer { + sender: Sender, + thread_handle: thread::JoinHandle<()>, +} + impl TychoRunner { pub fn new(db_url: String, initialized_accounts: Vec) -> Self { Self { db_url, initialized_accounts } @@ -93,16 +94,7 @@ impl TychoRunner { Ok(()) } - pub fn run_with_rpc_server( - &self, - func: F, - expected_components: &Vec, - start_block: u64, - stop_block: u64, - ) -> miette::Result - where - F: FnOnce(&Vec, u64, u64) -> R, - { + pub fn start_rpc_server(&self) -> miette::Result { let (tx, rx): (Sender, Receiver) = mpsc::channel(); let db_url = self.db_url.clone(); @@ -140,18 +132,22 @@ impl TychoRunner { // Give the RPC server time to start thread::sleep(Duration::from_secs(3)); - // Run the provided function - let result = func(expected_components, start_block, stop_block); + Ok(TychoRpcServer { sender: tx, thread_handle: rpc_thread }) + } - tx.send(true) - .expect("Failed to send termination message"); + pub fn stop_rpc_server(&self, server: TychoRpcServer) -> miette::Result<()> { + server + .sender + .send(true) + .into_diagnostic() + .wrap_err("Failed to send termination message")?; // Wait for the RPC thread to finish - if rpc_thread.join().is_err() { + if server.thread_handle.join().is_err() { eprintln!("Failed to join RPC thread"); } - Ok(result) + Ok(()) } // Helper method to handle process output in separate threads diff --git a/protocol-testing/test_executor_addresses.json b/protocol-testing/test_executor_addresses.json deleted file mode 100644 index 8b3bc73..0000000 --- a/protocol-testing/test_executor_addresses.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "ethereum": { - "uniswap_v2": "0xaE04CA7E9Ed79cBD988f6c536CE11C621166f41B", - "uniswap_v3": "0xbab7124C9662B15C6b9AF0b1f329907dD55a24FC", - "uniswap_v4": "0x2C2EaB81Cf983602153E67b1890164BC4CABC6ed", - "vm:balancer_v2": "0xB5b8dc3F0a1Be99685a0DEd015Af93bFBB55C411", - "vm:balancer_v3": "0xec5cE4bF6FbcB7bB0148652c92a4AEC8c1d474Ec", - "sushiswap_v2": "0x2017ad7035D781C14699C8E44ed62d3083723A18", - "pancakeswap_v2": "0xC9db3FEB380E4fd9af239e2595ECdEcE3b5c34A4", - "pancakeswap_v3": "0x9D32e9F569B22Ae8d8C6f788037C1CD53632A059", - "vm:curve": "0x879F3008D96EBea0fc584aD684c7Df31777F3165", - "vm:maverick_v2": "0xF35e3F5F205769B41508A18787b62A21bC80200B" - } -} \ No newline at end of file diff --git a/substreams/ethereum-curve/integration_test.tycho.yaml b/substreams/ethereum-curve/integration_test.tycho.yaml index 8c3be9a..c11d332 100644 --- a/substreams/ethereum-curve/integration_test.tycho.yaml +++ b/substreams/ethereum-curve/integration_test.tycho.yaml @@ -24,6 +24,7 @@ tests: coins: "0x5b22307836623137353437346538393039346334346461393862393534656564656163343935323731643066222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307864616331376639353864326565353233613232303632303639393435393763313364383331656337225d" # ["0x6b175474e89094c44da98b954eedeac495271d0f","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","0xdac17f958d2ee523a2206206994597c13d831ec7"] creation_tx: "0x20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6" skip_simulation: false + skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it # Unique pool (no factory) steth - 0xdc24316b9ae028f1497c275eb9192a3ea0f67022 - name: test_steth @@ -41,6 +42,7 @@ tests: coins: "0x5b22307865656565656565656565656565656565656565656565656565656565656565656565656565656565222c22307861653761623936353230646533613138653565313131623565616162303935333132643766653834225d" # ["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","0xae7ab96520de3a18e5e111b5eaab095312d7fe84"] creation_tx: "0xfac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa" skip_simulation: false + skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it # Unique pool (no factory) tricrypto2 - 0xd51a44d3fae010294c616388b506acda1bfaae46 - name: test_tricrypto2 @@ -59,6 +61,7 @@ tests: coins: "0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307863303261616133396232323366653864306130653563346632376561643930383363373536636332225d" # ["0xdac17f958d2ee523a2206206994597c13d831ec7","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"] creation_tx: "0xdafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138" skip_simulation: false + skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it # Unique pool (no factory) susd - 0xa5407eae9ba41422680e2e00537571bcc53efbfd - name: test_susd @@ -78,6 +81,7 @@ tests: coins: "0x5b22307836623137353437346538393039346334346461393862393534656564656163343935323731643066222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307835376162316563323864313239373037303532646634646634313864353861326434366435663531225d" # ["0x6b175474e89094c44da98b954eedeac495271d0f","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","0xdac17f958d2ee523a2206206994597c13d831ec7","0x57ab1ec28d129707052df4df418d58a2d46d5f51"] creation_tx: "0x51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2" skip_simulation: false + skip_execution: true # the block is before EIP-1559 where base fee was added. The current testing logic doesn't work without it # Unique pool (no factory) fraxusdc - 0xdcef968d416a41cdac0ed8702fac8128a64241a2 - name: test_fraxusdc @@ -95,6 +99,7 @@ tests: coins: "0x5b22307838353364393535616365663832326462303538656238353035393131656437376631373562393965222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438225d" # ["0x853d955acef822db058eb8505911ed77f175b99e","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"] creation_tx: "0x1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775" skip_simulation: false + skip_execution: true # the block is before the Shanghai upgrade and our router needs functionality introduced there # CryptoSwapNG factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - PlainPool - name: test_crypto_swap_ng_factory_plain_pool @@ -120,6 +125,7 @@ tests: coins: "0x5b22307834633965646435383532636439303566303836633735396538333833653039626666316536386233222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438225d" # ["0x4c9edd5852cd905f086c759e8383e09bff1e68b3","0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"] creation_tx: "0x6f4438aa1785589e2170599053a0cdc740d8987746a4b5ad9614b6ab7bb4e550" skip_simulation: false + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) # CryptoSwapNG factory 0x6A8cbed756804B16E05E741eDaBd5cB544AE21bf - MetaPool - name: test_crypto_swap_ng_factory_metapool @@ -143,6 +149,7 @@ tests: coins: "0x5b22307838363533373733363730353435313665313730313463636465643165376438313465646339636534222c22307861353538386637636466353630383131373130613264383264336339633939373639646231646362225d" # ["0x865377367054516e17014ccded1e7d814edc9ce4","0xa5588f7cdf560811710a2d82d3c9c99769db1dcb"] creation_tx: "0x3cfeecae1b43086ee5705f89b803e21eb0492d7d5db06c229586db8fc72f5665" skip_simulation: false + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) # # Metapool factory 0xB9fC157394Af804a3578134A6585C0dc9cc990d4 - MetaPool # - name: test_metapool_factory_metapool @@ -184,6 +191,8 @@ tests: coins: "0x5b22307865393633336335326634633862376264656230386334613766653861356331623834616663663637222c22307837376530366339656363663265373937666434363261393262366437363432656638356230613434225d" # ["0xe9633c52f4c8b7bdeb08c4a7fe8a5c1b84afcf67","0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44"] creation_tx: "0xeb34c90d352f18ffcfe78b7e393e155f0314acf06c54d1ac9996e4ee5a9b4742" skip_simulation: false + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) + - id: "0x3f67dc2AdBA4B1beB6A48c30AB3AFb1c1440d35B" tokens: - "0xe9633C52f4c8B7BDeb08c4A7fE8a5c1B84AFCf67" @@ -196,6 +205,7 @@ tests: coins: "0x5b22307865393633336335326634633862376264656230386334613766653861356331623834616663663637222c22307837376530366339656363663265373937666434363261393262366437363432656638356230613434225d" # ["0xe9633c52f4c8b7bdeb08c4a7fe8a5c1b84afcf67","0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44"] creation_tx: "0x455559b43afaf429c15c1d807fd7f5dd47be30f6411a854499f719b944f4c024" skip_simulation: true # Reason: this pool has no liquidity at stop_block + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) # CryptoPool factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - name: test_cryptopool_factory @@ -217,6 +227,7 @@ tests: coins: "0x5b22307830346331353462363663623334306633616532343131316363373637653031383465643030636336222c22307834353931646266663632363536653738353961666535653435663666343764333636396662623238225d" # ["0x04c154b66cb340f3ae24111cc767e0184ed00cc6","0x4591dbff62656e7859afe5e45f6f47d3669fbb28"] creation_tx: "0xa89c09a7e0dfd84f3a294b8df4f33cc4a623e6d52deee357457afe2591ea596f" skip_simulation: false + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) - id: "0x6c9Fe53cC13b125d6476E5Ce2b76983bd5b7A112" tokens: - "0x35fA164735182de50811E8e2E824cFb9B6118ac2" @@ -230,6 +241,7 @@ tests: coins: "0x5b22307833356661313634373335313832646535303831316538653265383234636662396236313138616332222c22307866393531653333356166623238393335336463323439653832393236313738656163376465643738225d" # ["0x35fa164735182de50811e8e2e824cfb9b6118ac2","0xf951e335afb289353dc249e82926178eac7ded78"] creation_tx: "0xa5b13d50c56242f7994b8e1339032bb4c6f9ac3af3054d4eae3ce9e32e3c1a50" skip_simulation: true # Reason: this pool has no liquidity at stop_block + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) # CryptoPool factory 0xF18056Bbd320E96A48e3Fbf8bC061322531aac99 - with ETH - name: test_cryptopool_factory_with_eth @@ -251,6 +263,7 @@ tests: coins: "0x5b22307865656565656565656565656565656565656565656565656565656565656565656565656565656565222c22307835353239366636396634306561366432306534373835333363313561366230386236353465373538225d" # ["0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","0x55296f69f40ea6d20e478533c15a6b08b654e758"] creation_tx: "0x52f0f76d97e77579eebd32876de99f656930a99131dc4c4f1dec005786c8782b" skip_simulation: false + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) # Tricrypto factory 0x0c0e5f2fF0ff18a3be9b835635039256dC4B4963 - name: test_tricrypto_factory @@ -273,6 +286,7 @@ tests: coins: "0x5b22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307865656565656565656565656565656565656565656565656565656565656565656565656565656565225d" # ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"] creation_tx: "0x2bd59c19f993b83729fb23498f897a58567c6f0b3ee2f00613ba515a7b19fe23" skip_simulation: false + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) # Twocrypto factory 0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f - name: test_twocrypto_factory @@ -313,6 +327,7 @@ tests: coins: "0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307866393339653061303366623037663539613733333134653733373934626530653537616331623465225d" # ["0xdac17f958d2ee523a2206206994597c13d831ec7","0xf939e0a03fb07f59a73314e73794be0e57ac1b4e"] creation_tx: "0x40b25773bf8ea673434277d279af40a85b09072072e7004e9048a2ec0f0dd5a0" skip_simulation: false + skip_execution: true # the block is before the Cancun upgrade and our router needs functionality introduced there (transient storage) # StableSwap factory 0x4F8846Ae9380B90d2E71D5e3D042dff3E7ebb40d - Metapool # - name: test_stableswap_factory_meta_pool