From 94efd848082037a03fb8ffb68bc60cdde62fd129 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 4 Sep 2025 11:49:31 +0300 Subject: [PATCH 001/112] Alloy account converter (#3628) Replicates #3599 after reverting it in #3627, which caused some memory issues after updating the lockfile. This version uses downgraded aws lib versions to avoid updating the lockfile. Depends on https://github.com/cowprotocol/ethcontract-rs/pull/982 Tested on mainnet-shadow, looks fine. --- .github/workflows/pull-request.yaml | 2 +- Cargo.lock | 625 ++++++------------------- Cargo.toml | 6 +- crates/ethrpc/Cargo.toml | 2 +- crates/ethrpc/src/alloy/conversions.rs | 46 ++ 5 files changed, 193 insertions(+), 488 deletions(-) diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 7bec4b8113..0529f2eb6f 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -206,7 +206,7 @@ jobs: # Build the driver's tests. - run: cargo build -p driver --tests # Don't spawn any docker containers. The driver's tests will spawn anvil itself. - - run: cargo nextest run -p driver --test-threads 1 --run-ignored ignored-only + - run: RUST_MIN_STACK=3145728 cargo nextest run -p driver --test-threads 1 --run-ignored ignored-only openapi: timeout-minutes: 60 diff --git a/Cargo.lock b/Cargo.lock index 4821c005be..180d7008b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,9 @@ dependencies = [ "alloy-network", "alloy-provider", "alloy-rpc-client", + "alloy-signer", + "alloy-signer-aws", + "alloy-signer-local", "alloy-transport", "alloy-transport-http", ] @@ -500,6 +503,40 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "alloy-signer-aws" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd40e8f6dc5e19a04314714be91b582736964d776a1ef11d665c097a5429a14" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "aws-sdk-kms", + "k256", + "spki", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "alloy-signer-local" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ae7d854db5b7cdd5b9ed7ad13d1e5e034cdd8be85ffef081f61dc6c9e18351" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror 2.0.12", +] + [[package]] name = "alloy-sol-macro" version = "1.2.1" @@ -1022,60 +1059,30 @@ dependencies = [ "web3", ] -[[package]] -name = "aws-config" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdcf0d683fe9c23d32cf5b53c9918ea0a500375a9fb20109802552658e576c9" -dependencies = [ - "aws-credential-types 0.55.3", - "aws-http", - "aws-sdk-sso 0.28.0", - "aws-sdk-sts 0.28.0", - "aws-smithy-async 0.55.3", - "aws-smithy-client", - "aws-smithy-http 0.55.3", - "aws-smithy-http-tower", - "aws-smithy-json 0.55.3", - "aws-smithy-types 0.55.3", - "aws-types 0.55.3", - "bytes", - "fastrand 1.9.0", - "hex", - "http 0.2.12", - "hyper 0.14.29", - "ring 0.16.20", - "time", - "tokio", - "tower 0.4.13", - "tracing", - "zeroize", -] - [[package]] name = "aws-config" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ac9889352d632214df943e26740c46a0f3da6e329fbd28164fe7ae1b061da7b" dependencies = [ - "aws-credential-types 1.2.0", + "aws-credential-types", "aws-runtime", - "aws-sdk-sso 1.29.0", + "aws-sdk-sso", "aws-sdk-ssooidc", - "aws-sdk-sts 1.29.0", - "aws-smithy-async 1.2.1", - "aws-smithy-http 0.60.8", - "aws-smithy-json 0.60.7", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", - "aws-types 1.3.1", + "aws-smithy-types", + "aws-types", "bytes", - "fastrand 2.1.0", + "fastrand", "hex", "http 0.2.12", "hyper 0.14.29", - "ring 0.17.14", + "ring", "time", "tokio", "tracing", @@ -1083,81 +1090,34 @@ dependencies = [ "zeroize", ] -[[package]] -name = "aws-credential-types" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcdb2f7acbc076ff5ad05e7864bdb191ca70a6fd07668dc3a1a8bcd051de5ae" -dependencies = [ - "aws-smithy-async 0.55.3", - "aws-smithy-types 0.55.3", - "fastrand 1.9.0", - "tokio", - "tracing", - "zeroize", -] - [[package]] name = "aws-credential-types" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9" dependencies = [ - "aws-smithy-async 1.2.1", + "aws-smithy-async", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", + "aws-smithy-types", "zeroize", ] -[[package]] -name = "aws-endpoint" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cce1c41a6cfaa726adee9ebb9a56fcd2bbfd8be49fd8a04c5e20fd968330b04" -dependencies = [ - "aws-smithy-http 0.55.3", - "aws-smithy-types 0.55.3", - "aws-types 0.55.3", - "http 0.2.12", - "regex", - "tracing", -] - -[[package]] -name = "aws-http" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aadbc44e7a8f3e71c8b374e03ecd972869eb91dd2bc89ed018954a52ba84bc44" -dependencies = [ - "aws-credential-types 0.55.3", - "aws-smithy-http 0.55.3", - "aws-smithy-types 0.55.3", - "aws-types 0.55.3", - "bytes", - "http 0.2.12", - "http-body 0.4.6", - "lazy_static", - "percent-encoding", - "pin-project-lite", - "tracing", -] - [[package]] name = "aws-runtime" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75588e7ee5e8496eed939adac2035a6dbab9f7eb2acdd9ab2d31856dab6f3955" dependencies = [ - "aws-credential-types 1.2.0", - "aws-sigv4 1.2.1", - "aws-smithy-async 1.2.1", + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", "aws-smithy-eventstream", - "aws-smithy-http 0.60.8", + "aws-smithy-http", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", - "aws-types 1.3.1", + "aws-smithy-types", + "aws-types", "bytes", - "fastrand 2.1.0", + "fastrand", "http 0.2.12", "http-body 0.4.6", "percent-encoding", @@ -1168,26 +1128,23 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545335abd7c6ef7285d2972a67b9f8279ff5fec8bbb3ffc637fa436ba1e6e434" -dependencies = [ - "aws-credential-types 0.55.3", - "aws-endpoint", - "aws-http", - "aws-sig-auth", - "aws-smithy-async 0.55.3", - "aws-smithy-client", - "aws-smithy-http 0.55.3", - "aws-smithy-http-tower", - "aws-smithy-json 0.55.3", - "aws-smithy-types 0.55.3", - "aws-types 0.55.3", +version = "1.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da951fb0dd1a02728a91c58af8464098766f1a0600af932dd3f8361b23e1bfc9" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", "bytes", "http 0.2.12", - "regex", - "tokio-stream", - "tower 0.4.13", + "once_cell", + "regex-lite", "tracing", ] @@ -1198,21 +1155,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "724119d8fd2d2638b9979673f3b5c2979fa388c9ca27815e3cb5ad6234fac3f5" dependencies = [ "ahash 0.8.11", - "aws-credential-types 1.2.0", + "aws-credential-types", "aws-runtime", - "aws-sigv4 1.2.1", - "aws-smithy-async 1.2.1", + "aws-sigv4", + "aws-smithy-async", "aws-smithy-checksums", "aws-smithy-eventstream", - "aws-smithy-http 0.60.8", - "aws-smithy-json 0.60.7", + "aws-smithy-http", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", - "aws-smithy-xml 0.60.8", - "aws-types 1.3.1", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", "bytes", - "fastrand 2.1.0", + "fastrand", "hex", "hmac", "http 0.2.12", @@ -1226,46 +1183,21 @@ dependencies = [ "url", ] -[[package]] -name = "aws-sdk-sso" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8b812340d86d4a766b2ca73f740dfd47a97c2dff0c06c8517a16d88241957e4" -dependencies = [ - "aws-credential-types 0.55.3", - "aws-endpoint", - "aws-http", - "aws-sig-auth", - "aws-smithy-async 0.55.3", - "aws-smithy-client", - "aws-smithy-http 0.55.3", - "aws-smithy-http-tower", - "aws-smithy-json 0.55.3", - "aws-smithy-types 0.55.3", - "aws-types 0.55.3", - "bytes", - "http 0.2.12", - "regex", - "tokio-stream", - "tower 0.4.13", - "tracing", -] - [[package]] name = "aws-sdk-sso" version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da75cf91cbb46686a27436d639a720a3a198b148efa76dc2467b7e5374a67fc0" dependencies = [ - "aws-credential-types 1.2.0", + "aws-credential-types", "aws-runtime", - "aws-smithy-async 1.2.1", - "aws-smithy-http 0.60.8", - "aws-smithy-json 0.60.7", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", - "aws-types 1.3.1", + "aws-smithy-types", + "aws-types", "bytes", "http 0.2.12", "once_cell", @@ -1279,15 +1211,15 @@ version = "1.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf2ec8a6687299685ed0a4a3137c129cdb132b5235bc3aa3443f6cffe468b9ff" dependencies = [ - "aws-credential-types 1.2.0", + "aws-credential-types", "aws-runtime", - "aws-smithy-async 1.2.1", - "aws-smithy-http 0.60.8", - "aws-smithy-json 0.60.7", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", - "aws-types 1.3.1", + "aws-smithy-types", + "aws-types", "bytes", "http 0.2.12", "once_cell", @@ -1295,99 +1227,40 @@ dependencies = [ "tracing", ] -[[package]] -name = "aws-sdk-sts" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265fac131fbfc188e5c3d96652ea90ecc676a934e3174eaaee523c6cec040b3b" -dependencies = [ - "aws-credential-types 0.55.3", - "aws-endpoint", - "aws-http", - "aws-sig-auth", - "aws-smithy-async 0.55.3", - "aws-smithy-client", - "aws-smithy-http 0.55.3", - "aws-smithy-http-tower", - "aws-smithy-json 0.55.3", - "aws-smithy-query 0.55.3", - "aws-smithy-types 0.55.3", - "aws-smithy-xml 0.55.3", - "aws-types 0.55.3", - "bytes", - "http 0.2.12", - "regex", - "tower 0.4.13", - "tracing", -] - [[package]] name = "aws-sdk-sts" version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "458f1031e094b1411b59b49b19e4118f069e1fe13a9c5b8888e933daaf7ffdd6" dependencies = [ - "aws-credential-types 1.2.0", + "aws-credential-types", "aws-runtime", - "aws-smithy-async 1.2.1", - "aws-smithy-http 0.60.8", - "aws-smithy-json 0.60.7", - "aws-smithy-query 0.60.7", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", - "aws-smithy-xml 0.60.8", - "aws-types 1.3.1", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", "http 0.2.12", "once_cell", "regex-lite", "tracing", ] -[[package]] -name = "aws-sig-auth" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b94acb10af0c879ecd5c7bdf51cda6679a0a4f4643ce630905a77673bfa3c61" -dependencies = [ - "aws-credential-types 0.55.3", - "aws-sigv4 0.55.3", - "aws-smithy-http 0.55.3", - "aws-types 0.55.3", - "http 0.2.12", - "tracing", -] - -[[package]] -name = "aws-sigv4" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2ce6f507be68e968a33485ced670111d1cbad161ddbbab1e313c03d37d8f4c" -dependencies = [ - "aws-smithy-http 0.55.3", - "form_urlencoded", - "hex", - "hmac", - "http 0.2.12", - "once_cell", - "percent-encoding", - "regex", - "sha2", - "time", - "tracing", -] - [[package]] name = "aws-sigv4" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58b56f1cbe6fd4d0c2573df72868f20ab1c125ca9c9dbce17927a463433a2e57" dependencies = [ - "aws-credential-types 1.2.0", + "aws-credential-types", "aws-smithy-eventstream", - "aws-smithy-http 0.60.8", + "aws-smithy-http", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", + "aws-smithy-types", "bytes", "form_urlencoded", "hex", @@ -1401,18 +1274,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "aws-smithy-async" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bda3996044c202d75b91afeb11a9afae9db9a721c6a7a427410018e286b880" -dependencies = [ - "futures-util", - "pin-project-lite", - "tokio", - "tokio-stream", -] - [[package]] name = "aws-smithy-async" version = "1.2.1" @@ -1430,8 +1291,8 @@ version = "0.60.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6242d6a54d3b4b83458f4abd7057ba93c4419dc71e8217e9acd3a748d656d99e" dependencies = [ - "aws-smithy-http 0.60.8", - "aws-smithy-types 1.1.10", + "aws-smithy-http", + "aws-smithy-types", "bytes", "crc32c", "crc32fast", @@ -1445,63 +1306,17 @@ dependencies = [ "tracing", ] -[[package]] -name = "aws-smithy-client" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a86aa6e21e86c4252ad6a0e3e74da9617295d8d6e374d552be7d3059c41cedd" -dependencies = [ - "aws-smithy-async 0.55.3", - "aws-smithy-http 0.55.3", - "aws-smithy-http-tower", - "aws-smithy-types 0.55.3", - "bytes", - "fastrand 1.9.0", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "hyper-rustls 0.23.2", - "lazy_static", - "pin-project-lite", - "rustls 0.20.9", - "tokio", - "tower 0.4.13", - "tracing", -] - [[package]] name = "aws-smithy-eventstream" version = "0.60.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6363078f927f612b970edf9d1903ef5cef9a64d1e8423525ebb1f0a1633c858" dependencies = [ - "aws-smithy-types 1.1.10", + "aws-smithy-types", "bytes", "crc32fast", ] -[[package]] -name = "aws-smithy-http" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b3b693869133551f135e1f2c77cb0b8277d9e3e17feaf2213f735857c4f0d28" -dependencies = [ - "aws-smithy-types 0.55.3", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.29", - "once_cell", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "aws-smithy-http" version = "0.60.8" @@ -1510,7 +1325,7 @@ checksum = "4a7de001a1b9a25601016d8057ea16e31a45fdca3751304c8edf4ad72e706c08" dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", + "aws-smithy-types", "bytes", "bytes-utils", "futures-core", @@ -1523,48 +1338,13 @@ dependencies = [ "tracing", ] -[[package]] -name = "aws-smithy-http-tower" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae4f6c5798a247fac98a867698197d9ac22643596dc3777f0c76b91917616b9" -dependencies = [ - "aws-smithy-http 0.55.3", - "aws-smithy-types 0.55.3", - "bytes", - "http 0.2.12", - "http-body 0.4.6", - "pin-project-lite", - "tower 0.4.13", - "tracing", -] - -[[package]] -name = "aws-smithy-json" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f9f42fbfa96d095194a632fbac19f60077748eba536eb0b9fecc28659807f8" -dependencies = [ - "aws-smithy-types 0.55.3", -] - [[package]] name = "aws-smithy-json" version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" dependencies = [ - "aws-smithy-types 1.1.10", -] - -[[package]] -name = "aws-smithy-query" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98819eb0b04020a1c791903533b638534ae6c12e2aceda3e6e6fba015608d51d" -dependencies = [ - "aws-smithy-types 0.55.3", - "urlencoding", + "aws-smithy-types", ] [[package]] @@ -1573,7 +1353,7 @@ version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" dependencies = [ - "aws-smithy-types 1.1.10", + "aws-smithy-types", "urlencoding", ] @@ -1583,22 +1363,22 @@ version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0d3965f6417a92a6d1009c5958a67042f57e46342afb37ca58f9ad26744ec73" dependencies = [ - "aws-smithy-async 1.2.1", - "aws-smithy-http 0.60.8", + "aws-smithy-async", + "aws-smithy-http", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", + "aws-smithy-types", "bytes", - "fastrand 2.1.0", + "fastrand", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.0", "hyper 0.14.29", - "hyper-rustls 0.24.2", + "hyper-rustls", "once_cell", "pin-project-lite", "pin-utils", - "rustls 0.21.12", + "rustls", "tokio", "tracing", ] @@ -1609,8 +1389,8 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4179bd8a1c943e1aceb46c5b9fc014a561bd6c35a2153e816ba29076ee49d245" dependencies = [ - "aws-smithy-async 1.2.1", - "aws-smithy-types 1.1.10", + "aws-smithy-async", + "aws-smithy-types", "bytes", "http 0.2.12", "http 1.1.0", @@ -1620,19 +1400,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "aws-smithy-types" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a3d0bf4f324f4ef9793b86a1701d9700fbcdbd12a846da45eed104c634c6e8" -dependencies = [ - "base64-simd", - "itoa", - "num-integer", - "ryu", - "time", -] - [[package]] name = "aws-smithy-types" version = "1.1.10" @@ -1659,15 +1426,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "aws-smithy-xml" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b9d12875731bd07e767be7baad95700c3137b56730ec9ddeedb52a5e5ca63b" -dependencies = [ - "xmlparser", -] - [[package]] name = "aws-smithy-xml" version = "0.60.8" @@ -1677,32 +1435,16 @@ dependencies = [ "xmlparser", ] -[[package]] -name = "aws-types" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd209616cc8d7bfb82f87811a5c655dc97537f592689b18743bddf5dc5c4829" -dependencies = [ - "aws-credential-types 0.55.3", - "aws-smithy-async 0.55.3", - "aws-smithy-client", - "aws-smithy-http 0.55.3", - "aws-smithy-types 0.55.3", - "http 0.2.12", - "rustc_version 0.4.0", - "tracing", -] - [[package]] name = "aws-types" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f734808d43702a67e57d478a12e227d4d038d0b90c9005a78c87890d3805922" dependencies = [ - "aws-credential-types 1.2.0", - "aws-smithy-async 1.2.1", + "aws-credential-types", + "aws-smithy-async", "aws-smithy-runtime-api", - "aws-smithy-types 1.1.10", + "aws-smithy-types", "http 0.2.12", "rustc_version 0.4.0", "tracing", @@ -2869,12 +2611,11 @@ dependencies = [ [[package]] name = "ethcontract" -version = "0.25.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c83ea43ad75c3c78468966184a489c195822185df81d5fcaba4b1ba87f382c5" +version = "0.25.9" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" dependencies = [ "arrayvec", - "aws-config 0.55.3", + "aws-config", "aws-sdk-kms", "ethcontract-common", "ethcontract-derive", @@ -2896,9 +2637,8 @@ dependencies = [ [[package]] name = "ethcontract-common" -version = "0.25.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51c3253a19d093d249024012ed2f4117d819958f79339fd24d8158fb05306747" +version = "0.25.9" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" dependencies = [ "ethabi", "hex", @@ -2912,9 +2652,8 @@ dependencies = [ [[package]] name = "ethcontract-derive" -version = "0.25.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f2a75ebcd8fe443014a9f1157fab5bb8950cff7c3a62079b748201ca32570c" +version = "0.25.9" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" dependencies = [ "anyhow", "ethcontract-common", @@ -2926,9 +2665,8 @@ dependencies = [ [[package]] name = "ethcontract-generate" -version = "0.25.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c0c474f16da0667eec38c2fc4c1ee7ad7cdfd879ffabfd57713444d851c6c7" +version = "0.25.9" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" dependencies = [ "Inflector", "anyhow", @@ -2942,9 +2680,8 @@ dependencies = [ [[package]] name = "ethcontract-mock" -version = "0.25.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff5cf1c4d38e002747e5ecc69fe50cd8d00d4080f1de32acb4cb6373093b8a1e" +version = "0.25.9" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" dependencies = [ "ethcontract", "hex", @@ -3026,15 +2763,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.1.0" @@ -3694,21 +3422,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -dependencies = [ - "http 0.2.12", - "hyper 0.14.29", - "log", - "rustls 0.20.9", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.23.4", -] - [[package]] name = "hyper-rustls" version = "0.24.2" @@ -3719,10 +3432,10 @@ dependencies = [ "http 0.2.12", "hyper 0.14.29", "log", - "rustls 0.21.12", + "rustls", "rustls-native-certs", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", ] [[package]] @@ -5549,21 +5262,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.14" @@ -5574,7 +5272,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.15", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -5735,18 +5433,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.21.12" @@ -5754,7 +5440,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.14", + "ring", "rustls-webpki", "sct", ] @@ -5804,8 +5490,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -5837,7 +5523,7 @@ name = "s3" version = "0.1.0" dependencies = [ "anyhow", - "aws-config 1.5.1", + "aws-config", "aws-sdk-s3", "chrono", "flate2", @@ -5873,8 +5559,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -6772,7 +6458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.1.0", + "fastrand", "rustix", "windows-sys 0.52.0", ] @@ -6962,24 +6648,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", -] - [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.12", + "rustls", "tokio", ] @@ -7360,12 +7035,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -7637,16 +7306,6 @@ dependencies = [ "url", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", -] - [[package]] name = "whoami" version = "1.5.1" diff --git a/Cargo.toml b/Cargo.toml index 4973695824..10c4c40e2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,10 @@ clap = { version = "4.5.6", features = ["derive", "env"] } dashmap = "6.1.0" derivative = "2.2.0" derive_more = { version = "1.0.0", features = ["full"] } -ethcontract = { version = "0.25.8", default-features = false, features = ["aws-kms"] } +ethcontract = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "8817f4ba868e686f21cda886d6c8b75834eb51f2", default-features = false, features = ["aws-kms"] } mimalloc = "0.1.43" -ethcontract-generate = { version = "0.25.8", default-features = false } -ethcontract-mock = { version = "0.25.8", default-features = false } +ethcontract-generate = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "8817f4ba868e686f21cda886d6c8b75834eb51f2", default-features = false } +ethcontract-mock = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "8817f4ba868e686f21cda886d6c8b75834eb51f2", default-features = false } ethereum-types = "0.14.1" flate2 = "1.0.30" futures = "0.3.30" diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index 6654e3ad78..d4c4f6a771 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" doctest = false [dependencies] -alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest"] } +alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signer-aws", "signer-local"] } anyhow = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } diff --git a/crates/ethrpc/src/alloy/conversions.rs b/crates/ethrpc/src/alloy/conversions.rs index c8079c8eab..bf17d42a57 100644 --- a/crates/ethrpc/src/alloy/conversions.rs +++ b/crates/ethrpc/src/alloy/conversions.rs @@ -1,3 +1,8 @@ +use { + alloy::{network::TxSigner, signers::Signature}, + anyhow::Context, +}; + ///////////////////////////////// // Conversions to the alloy types ///////////////////////////////// @@ -46,6 +51,47 @@ impl IntoAlloy for primitive_types::H256 { } } +pub enum Account { + Address(alloy::primitives::Address), + Signer(Box + Send + Sync + 'static>), +} + +#[async_trait::async_trait] +pub trait TryIntoAlloyAsync { + type Into; + + async fn try_into_alloy(self) -> anyhow::Result; +} + +#[async_trait::async_trait] +impl TryIntoAlloyAsync for ethcontract::Account { + type Into = Account; + + async fn try_into_alloy(self) -> anyhow::Result { + match self { + ethcontract::Account::Offline(pk, _) => { + let signer = + alloy::signers::local::PrivateKeySigner::from_slice(&pk.secret_bytes()) + .context("invalid private key bytes")?; + Ok(Account::Signer(Box::new(signer))) + } + ethcontract::Account::Kms(account, chain_id) => { + let signer = alloy::signers::aws::AwsSigner::new( + account.client().clone(), + account.key_id().to_string(), + chain_id, + ) + .await?; + Ok(Account::Signer(Box::new(signer))) + } + ethcontract::Account::Local(address, _) => Ok(Account::Address(address.into_alloy())), + ethcontract::Account::Locked(_, _, _) => { + anyhow::bail!("Locked accounts are not currently supported") + } + } + } +} + ////////////////////////////////// // Conversions to the legacy types ////////////////////////////////// From 52669e56d3e3377059953ef7973f9f7d5b432b82 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Thu, 4 Sep 2025 12:28:17 +0200 Subject: [PATCH 002/112] [TRIVIAL] Fix Dockerfile lints (#3629) # Description Fixes the lints generated [here](https://github.com/cowprotocol/services/pull/3592/files#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557). # Changes - `as` -> `AS` - remove `CMD` line that only `echo`'s something ## How to test Run CI and check for these lints --- Dockerfile | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 427487cc5b..e5a8a0d470 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM docker.io/flyway/flyway:10.7.1 as migrations +FROM docker.io/flyway/flyway:10.7.1 AS migrations COPY database/ /flyway/ CMD ["migrate"] -FROM docker.io/rust:1-slim-bookworm as cargo-build +FROM docker.io/rust:1-slim-bookworm AS cargo-build WORKDIR /src/ # Install dependencies @@ -23,32 +23,32 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry --mount=type=cache,targe cp target/release/solvers / # Create an intermediate image to extract the binaries -FROM docker.io/debian:bookworm-slim as intermediate +FROM docker.io/debian:bookworm-slim AS intermediate RUN --mount=type=cache,target=/var/cache/apt,sharing=locked apt-get update && \ apt-get install -y ca-certificates tini gettext-base && \ apt-get clean -FROM intermediate as alerter +FROM intermediate AS alerter COPY --from=cargo-build /alerter /usr/local/bin/alerter ENTRYPOINT [ "alerter" ] -FROM intermediate as autopilot +FROM intermediate AS autopilot COPY --from=cargo-build /autopilot /usr/local/bin/autopilot ENTRYPOINT [ "autopilot" ] -FROM intermediate as driver +FROM intermediate AS driver COPY --from=cargo-build /driver /usr/local/bin/driver ENTRYPOINT [ "driver" ] -FROM intermediate as orderbook +FROM intermediate AS orderbook COPY --from=cargo-build /orderbook /usr/local/bin/orderbook ENTRYPOINT [ "orderbook" ] -FROM intermediate as refunder +FROM intermediate AS refunder COPY --from=cargo-build /refunder /usr/local/bin/refunder ENTRYPOINT [ "refunder" ] -FROM intermediate as solvers +FROM intermediate AS solvers COPY --from=cargo-build /solvers /usr/local/bin/solvers ENTRYPOINT [ "solvers" ] @@ -71,6 +71,5 @@ COPY --from=cargo-build /solvers /usr/local/bin/solvers COPY ./entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -CMD echo "Specify binary..." ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["/entrypoint.sh"] From 2cedefc04056f74c395c8f328b265868bec4dbca Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 4 Sep 2025 13:47:15 +0300 Subject: [PATCH 003/112] 0x alloy bindings (#3592) # Description Migrates 0x SC to `alloy-rs` bindings. # Changes - [ ] Binds `IZeroEx` SC using `alloy`. - [ ] Removes legacy bindings from the `build.rs` file. - [ ] For e2e tests, a different from the original alloy provider's signer needs to be used. Currently, alloy doesn't provide a concise way to achieve that. An issue was opened: https://github.com/alloy-rs/alloy/issues/2829. This PR contains a workaround that uses a new provider with the desired wallet for 0x bindings. - [ ] Drops GOERLI from the bindings tests. - [ ] Updates alloy to 1.0.30 since CI fails without it(e.g. https://github.com/cowprotocol/services/actions/runs/17409474760/job/49422717398) ## How to test Existing tests --------- Co-authored-by: MartinquaXD --- Cargo.lock | 134 ++++++++++------ Cargo.toml | 2 +- .../artifacts/{IZeroEx.json => IZeroex.json} | 0 crates/contracts/build.rs | 35 ----- crates/contracts/src/alloy.rs | 17 +- crates/contracts/src/lib.rs | 44 ++---- .../driver/src/boundary/liquidity/zeroex.rs | 3 +- .../competition/solution/interaction.rs | 13 +- crates/driver/src/domain/liquidity/zeroex.rs | 55 +++---- crates/driver/src/infra/solver/dto/auction.rs | 3 +- crates/e2e/Cargo.toml | 1 + crates/e2e/tests/e2e/liquidity.rs | 145 ++++++++++-------- crates/ethrpc/Cargo.toml | 5 +- crates/ethrpc/src/alloy/mod.rs | 39 ++++- crates/solver/Cargo.toml | 1 + crates/solver/src/interactions/zeroex.rs | 56 ++++--- crates/solver/src/liquidity/zeroex.rs | 40 +++-- 17 files changed, 338 insertions(+), 255 deletions(-) rename crates/contracts/artifacts/{IZeroEx.json => IZeroex.json} (100%) diff --git a/Cargo.lock b/Cargo.lock index 180d7008b2..175a5f2a93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,9 +91,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7413bbf62c40b5db916ad5a1c382df1affe42080e148d69932bb7f0a12f32e" +checksum = "2e2a5d689ccd182f1d138a61f081841b905034e0089f5278f6c200f2bcdab00a" dependencies = [ "alloy-consensus", "alloy-contract", @@ -123,9 +123,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7345077623aaa080fc06735ac13b8fa335125c8550f9c4f64135a5bf6f79967" +checksum = "d213580c17d239ae83c0d897ac3315db7cda83d2d4936a9823cc3517552f2e24" dependencies = [ "alloy-eips", "alloy-primitives", @@ -148,9 +148,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501f83565d28bdb9d6457dd3b5d646e19db37709d0f27608a26a1839052ddade" +checksum = "81443e3b8dccfeac7cd511aced15928c97ff253f4177acbb97de97178e543f6c" dependencies = [ "alloy-consensus", "alloy-eips", @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c36bb4173892aeeba1c6b9e4eff923fa3fe8583f6d3e07afe1cbc5a96a853a" +checksum = "de217ab604f1bcfa2e3b0aff86d50812d5931d47522f9f0a949cc263ec2d108e" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -248,9 +248,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c219a87fb386a75780ddbdbbced242477321887e426b0f946c05815ceabe5e09" +checksum = "2a15b4b0f6bab47aae017d52bb5a739bda381553c09fb9918b7172721ef5f5de" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334555c323fa2bb98f1d4c242b62da9de8c715557a2ed680a76cefbcac19fefd" +checksum = "f8882ec8e4542cfd02aadc6dccbe90caa73038f60016d936734eb6ced53d2167" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -297,9 +297,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7ea377c9650203d7a7da9e8dee7f04906b49a9253f554b110edd7972e75ef34" +checksum = "51d6d87d588bda509881a7a66ae77c86514bd1193ac30fbff0e0f24db95eb5a5" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -323,9 +323,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f9ab9a9e92c49a357edaee2d35deea0a32ac8f313cfa37448f04e7e029c9d9" +checksum = "5b14fa9ba5774e0b30ae6a04176d998211d516c8af69c9c530af7c6c42a8c508" dependencies = [ "alloy-consensus", "alloy-eips", @@ -363,9 +363,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a85361c88c16116defbd98053e3d267054d6b82729cdbef0236f7881590f924" +checksum = "475a5141313c3665b75d818be97d5fa3eb5e0abb7e832e9767edd94746db28e3" dependencies = [ "alloy-chains", "alloy-consensus", @@ -424,9 +424,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fc964abb0106e454e9e8683fb0809fb32940270ef586a58e913531360b302" +checksum = "25289674cd8c58fcca2568b5350423cb0dd7bca8c596c5e2869bfe4c5c57ed14" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -447,9 +447,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97372c51a14a804fb9c17010e3dd6c117f7866620b264e24b64d2259be44bcdf" +checksum = "01bac57c987c93773787619e20f89167db74d460a2d1d40f591d94fb7c22c379" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -458,9 +458,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "672286c19528007df058bafd82c67e23247b4b3ebbc538cbddc705a82d8a930f" +checksum = "1cd1e1b4dcdf13eaa96343e5c0dafc2d2e8ce5d20b90347169d46a1df0dec210" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -479,9 +479,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aae653f049267ae7e040eab6c9b9a417064ca1a6cb21e3dd59b9f1131ef048f" +checksum = "f1b3b1078b8775077525bc9fe9f6577e815ceaecd6c412a4f3b4d8aa2836e8f6" dependencies = [ "alloy-primitives", "serde", @@ -490,9 +490,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97cedce202f848592b96f7e891503d3adb33739c4e76904da73574290141b93" +checksum = "10ab1b8d4649bf7d0db8ab04e31658a6cc20364d920795484d886c35bed3bab4" dependencies = [ "alloy-primitives", "async-trait", @@ -505,15 +505,16 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dd40e8f6dc5e19a04314714be91b582736964d776a1ef11d665c097a5429a14" +checksum = "a46118173eb381b2911202a83dc4f39267027b0fe7d3533449f5e4ebc0eadcab" dependencies = [ "alloy-consensus", "alloy-network", "alloy-primitives", "alloy-signer", "async-trait", + "aws-config", "aws-sdk-kms", "k256", "spki", @@ -523,9 +524,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ae7d854db5b7cdd5b9ed7ad13d1e5e034cdd8be85ffef081f61dc6c9e18351" +checksum = "7bdeec36c8d9823102b571b3eab8b323e053dc19c12da14a9687bd474129bf2a" dependencies = [ "alloy-consensus", "alloy-network", @@ -612,9 +613,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08b383bc903c927635e39e1dae7df2180877d93352d1abd389883665a598afc" +checksum = "dce5129146a76ca6139a19832c75ad408857a56bcd18cd2c684183b8eacd78d8" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -636,9 +637,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e58dee1f7763ef302074b645fc4f25440637c09a60e8de234b62993f06c0ae3" +checksum = "e2379d998f46d422ec8ef2b61603bc28cda931e5e267aea1ebe71f62da61d101" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -667,12 +668,12 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.27" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14809f908822dbff0dc472c77ca4aa129ab12e22fd9bff2dd1ef54603e68e3d" +checksum = "3b5becb9c269a7d05a2f28d549f86df5a5dbc923e2667eff84fdecac8cda534c" dependencies = [ "alloy-primitives", - "darling", + "darling 0.21.3", "proc-macro2", "quote", "syn 2.0.104", @@ -2174,8 +2175,18 @@ version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.9", + "darling_macro 0.20.9", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -2192,13 +2203,39 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.104", +] + [[package]] name = "darling_macro" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ - "darling_core", + "darling_core 0.20.9", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn 2.0.104", ] @@ -2463,6 +2500,7 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" name = "e2e" version = "1.0.0" dependencies = [ + "alloy", "anyhow", "app-data", "autopilot", @@ -4891,7 +4929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.104", @@ -5672,18 +5710,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -5756,7 +5794,7 @@ version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ - "darling", + "darling 0.20.9", "proc-macro2", "quote", "syn 2.0.104", diff --git a/Cargo.toml b/Cargo.toml index 10c4c40e2f..144cb6cfef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "3" members = ["crates/*"] [workspace.dependencies] -alloy = { version = "1.0.27", default-features = false } +alloy = { version = "1.0.30", default-features = false } anyhow = "=1.0.76" async-trait = "0.1.80" axum = "0.6" diff --git a/crates/contracts/artifacts/IZeroEx.json b/crates/contracts/artifacts/IZeroex.json similarity index 100% rename from crates/contracts/artifacts/IZeroEx.json rename to crates/contracts/artifacts/IZeroex.json diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 829df0c6b1..ed7b1aea59 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -1447,41 +1447,6 @@ fn main() { .add_network_str(LENS, "0xc3A5b857Ba82a2586A45a8B59ECc3AA50Bc3D0e3") // Not available on Gnosis Chain }); - generate_contract_with_config("IZeroEx", |builder| { - // - // - builder - .add_network_str(MAINNET, "0xdef1c0ded9bec7f1a1670819833240f027b25eff") - .add_network_str(SEPOLIA, "0xdef1c0ded9bec7f1a1670819833240f027b25eff") - .add_network_str(ARBITRUM_ONE, "0xdef1c0ded9bec7f1a1670819833240f027b25eff") - .add_network_str(BASE, "0xdef1c0ded9bec7f1a1670819833240f027b25eff") - .add_network_str(AVALANCHE, "0xdef1c0ded9bec7f1a1670819833240f027b25eff") - .add_network_str(BNB, "0xdef1c0ded9bec7f1a1670819833240f027b25eff") - .add_network_str(OPTIMISM, "0xdef1abe32c034e558cdd535791643c58a13acc10") - .add_network_str(POLYGON, "0xdef1c0ded9bec7f1a1670819833240f027b25eff") - // Not available on Lens - .add_method_alias( - "_transformERC20((address,address,address,uint256,uint256,(uint32,bytes)[],bool,\ - address))", - "_transform_erc_20", - ) - .add_method_alias( - "_fillRfqOrder((address,address,uint128,uint128,address,address,address,bytes32,\ - uint64,uint256),(uint8,uint8,bytes32,bytes32),uint128,address,bool,address)", - "_fill_rfq_order", - ) - .add_method_alias( - "_fillLimitOrder((address,address,uint128,uint128,uint128,address,address,address,\ - address,bytes32,uint64,uint256),(uint8,uint8,bytes32,bytes32),uint128,address,\ - address)", - "_fill_limit_order", - ) - .add_method_alias( - "_fillOtcOrder((address,address,uint128,uint128,address,address,address,uint256),\ - (uint8,uint8,bytes32,bytes32),uint128,address,bool,address)", - "_fill_otc_order", - ) - }); generate_contract_with_config("CowProtocolToken", |builder| { builder .add_network_str(MAINNET, "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB") diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index e7b8395b6d..6d4a64ee45 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -24,6 +24,21 @@ crate::bindings!( } ); +crate::bindings!( + IZeroex, + maplit::hashmap! { + MAINNET => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), + SEPOLIA => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), + ARBITRUM_ONE => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), + BASE => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), + AVALANCHE => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), + BNB => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), + OPTIMISM => address!("0xdef1abe32c034e558cdd535791643c58a13acc10"), + POLYGON => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), + // Not available on Lens + } +); + pub use alloy::providers::DynProvider as Provider; /// Extension trait to attach some useful functions to the contract instance. @@ -45,7 +60,7 @@ macro_rules! bindings { #[allow(non_snake_case)] mod [<$contract Private>] { alloy::sol!( - #[allow(missing_docs)] + #[allow(missing_docs, clippy::too_many_arguments)] #[sol(rpc)] $contract, concat!("./artifacts/", stringify!($contract), ".json"), diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 3f224caf4e..01235123c1 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -98,7 +98,6 @@ include_contracts! { IUniswapLikePair; IUniswapLikeRouter; IUniswapV3Factory; - IZeroEx; PancakeRouter; Permit2; SushiSwapRouter; @@ -134,18 +133,9 @@ pub mod test { #[cfg(test)] mod tests { - const MAINNET: u64 = 1; - const GOERLI: u64 = 5; - const GNOSIS: u64 = 100; - const SEPOLIA: u64 = 11155111; - const ARBITRUM_ONE: u64 = 42161; - const BASE: u64 = 8453; - const POLYGON: u64 = 137; - const AVALANCHE: u64 = 43114; - const BNB: u64 = 56; - const OPTIMISM: u64 = 10; - const LENS: u64 = 232; - + use crate::alloy::networks::{ + ARBITRUM_ONE, AVALANCHE, BASE, BNB, GNOSIS, LENS, MAINNET, OPTIMISM, POLYGON, SEPOLIA, + }; use { super::*, ethcontract::{ @@ -207,17 +197,17 @@ mod tests { }}; } - for network in &[MAINNET, GOERLI, GNOSIS, SEPOLIA, ARBITRUM_ONE] { + for network in &[MAINNET, GNOSIS, SEPOLIA, ARBITRUM_ONE] { assert_has_deployment_address!(GPv2Settlement for *network); assert_has_deployment_address!(WETH9 for *network); assert_has_deployment_address!(HooksTrampoline for *network); assert_has_deployment_address!(BalancerV2Vault for *network); assert_has_deployment_address!(BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory for *network); } - for network in &[MAINNET, GOERLI, GNOSIS, SEPOLIA] { + for network in &[MAINNET, GNOSIS, SEPOLIA] { assert_has_deployment_address!(CowProtocolToken for *network); } - for network in &[MAINNET, GOERLI, GNOSIS, ARBITRUM_ONE] { + for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { assert_has_deployment_address!(SushiSwapRouter for *network); assert_has_deployment_address!(UniswapV2Factory for *network); assert_has_deployment_address!(UniswapV2Router02 for *network); @@ -235,7 +225,7 @@ mod tests { assert_has_deployment_address!(UniswapV3SwapRouterV2 for *network); assert_has_deployment_address!(IUniswapV3Factory for *network); } - for network in &[MAINNET, GOERLI, ARBITRUM_ONE] { + for network in &[MAINNET, ARBITRUM_ONE] { assert_has_deployment_address!(BalancerV2WeightedPool2TokensFactory for *network); assert_has_deployment_address!(BalancerV2LiquidityBootstrappingPoolFactory for *network); } @@ -244,15 +234,9 @@ mod tests { assert_has_deployment_address!(PancakeRouter for *network); } - for network in &[MAINNET, GOERLI] { - assert_has_deployment_address!(BalancerV2WeightedPoolFactory for *network); - } + assert_has_deployment_address!(BalancerV2WeightedPoolFactory for MAINNET); - for network in &[MAINNET, SEPOLIA, ARBITRUM_ONE] { - assert_has_deployment_address!(IZeroEx for *network); - } - - for network in &[MAINNET, GOERLI, GNOSIS, ARBITRUM_ONE] { + for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { assert_has_deployment_address!(BalancerV2StablePoolFactoryV2 for *network); } @@ -281,17 +265,15 @@ mod tests { }}; } - for network in &[MAINNET, GOERLI, GNOSIS, SEPOLIA, ARBITRUM_ONE] { + for network in &[MAINNET, GNOSIS, SEPOLIA, ARBITRUM_ONE] { assert_has_deployment_information!(GPv2Settlement for *network); assert_has_deployment_information!(BalancerV2Vault for *network); } - for network in &[MAINNET, GOERLI] { - assert_has_deployment_information!(BalancerV2WeightedPoolFactory for *network); - } - for network in &[MAINNET, GOERLI, ARBITRUM_ONE] { + assert_has_deployment_information!(BalancerV2WeightedPoolFactory for MAINNET); + for network in &[MAINNET, ARBITRUM_ONE] { assert_has_deployment_information!(BalancerV2WeightedPool2TokensFactory for *network); } - for network in &[MAINNET, GOERLI, GNOSIS, ARBITRUM_ONE] { + for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { assert_has_deployment_information!(BalancerV2StablePoolFactoryV2 for *network); } } diff --git a/crates/driver/src/boundary/liquidity/zeroex.rs b/crates/driver/src/boundary/liquidity/zeroex.rs index 3500fb1f3a..34367f3643 100644 --- a/crates/driver/src/boundary/liquidity/zeroex.rs +++ b/crates/driver/src/boundary/liquidity/zeroex.rs @@ -7,6 +7,7 @@ use { infra::{self, Ethereum}, }, anyhow::anyhow, + contracts::alloy::InstanceExt, ethrpc::block_stream::CurrentBlockWatcher, shared::{ http_client::HttpClientFactory, @@ -85,7 +86,7 @@ pub async fn collector( let eth = eth.with_metric_label("zeroex".into()); let settlement = eth.contracts().settlement().clone(); let web3 = eth.web3().clone(); - let contract = contracts::IZeroEx::deployed(&web3).await?; + let contract = contracts::alloy::IZeroex::Instance::deployed(&web3.alloy).await?; let http_client_factory = &HttpClientFactory::new(&shared::http_client::Arguments { http_timeout: config.http_timeout, }); diff --git a/crates/driver/src/domain/competition/solution/interaction.rs b/crates/driver/src/domain/competition/solution/interaction.rs index 92bde6226a..b51d310f55 100644 --- a/crates/driver/src/domain/competition/solution/interaction.rs +++ b/crates/driver/src/domain/competition/solution/interaction.rs @@ -1,6 +1,9 @@ -use crate::{ - domain::{self, eth, liquidity}, - util::Bytes, +use { + crate::{ + domain::{self, eth, liquidity}, + util::Bytes, + }, + ethrpc::alloy::conversions::IntoLegacy, }; /// Interaction with a smart contract which is needed to execute this solution @@ -42,7 +45,7 @@ impl Interaction { liquidity::Kind::BalancerV2Stable(pool) => pool.vault.into(), liquidity::Kind::BalancerV2Weighted(pool) => pool.vault.into(), liquidity::Kind::Swapr(pool) => pool.base.router.into(), - liquidity::Kind::ZeroEx(pool) => pool.zeroex.address().into(), + liquidity::Kind::ZeroEx(pool) => pool.zeroex.address().into_legacy(), }; // As a gas optimization, we always approve the max amount possible. This // minimizes the number of approvals necessary, and therefore @@ -52,7 +55,7 @@ impl Interaction { vec![ eth::Allowance { token: interaction.input.token, - spender: address, + spender: address.into(), amount: eth::U256::max_value(), } .into(), diff --git a/crates/driver/src/domain/liquidity/zeroex.rs b/crates/driver/src/domain/liquidity/zeroex.rs index 3dd7e56f12..93d8ac31ca 100644 --- a/crates/driver/src/domain/liquidity/zeroex.rs +++ b/crates/driver/src/domain/liquidity/zeroex.rs @@ -1,8 +1,8 @@ use { crate::domain::{eth, liquidity}, anyhow::anyhow, - contracts::IZeroEx, - ethcontract::Bytes, + contracts::alloy::IZeroex, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, primitive_types::{H160, H256, U256}, std::sync::Arc, }; @@ -19,7 +19,8 @@ pub struct LimitOrder { /// Scaled amounts according to how much of the partially fillable amounts /// were already used in the order. pub fillable: Amounts, - pub zeroex: Arc, + // todo: remove Arc + pub zeroex: Arc, } #[derive(Clone, Debug)] @@ -48,27 +49,27 @@ pub struct ZeroExSignature { impl LimitOrder { pub fn to_interaction(&self, input: &liquidity::MaxInput) -> anyhow::Result { - let method = self.zeroex.fill_or_kill_limit_order( - ( - self.order.maker_token, - self.order.taker_token, - self.order.amounts.maker, - self.order.amounts.taker, - self.order.taker_token_fee_amount, - self.order.maker, - self.order.taker, - self.order.sender, - self.order.fee_recipient, - Bytes(self.order.pool.0), - self.order.expiry, - self.order.salt, - ), - ( - self.order.signature.signature_type, - self.order.signature.v, - Bytes(self.order.signature.r.0), - Bytes(self.order.signature.s.0), - ), + let method = self.zeroex.fillOrKillLimitOrder( + IZeroex::LibNativeOrder::LimitOrder { + makerToken: self.order.maker_token.into_alloy(), + takerToken: self.order.taker_token.into_alloy(), + makerAmount: self.order.amounts.maker, + takerAmount: self.order.amounts.taker, + takerTokenFeeAmount: self.order.taker_token_fee_amount, + maker: self.order.maker.into_alloy(), + taker: self.order.taker.into_alloy(), + sender: self.order.sender.into_alloy(), + feeRecipient: self.order.fee_recipient.into_alloy(), + pool: self.order.pool.into_alloy(), + expiry: self.order.expiry, + salt: self.order.salt.into_alloy(), + }, + IZeroex::LibSignature::Signature { + signatureType: self.order.signature.signature_type, + v: self.order.signature.v, + r: self.order.signature.r.into_alloy(), + s: self.order.signature.s.into_alloy(), + }, input .0 .amount @@ -76,12 +77,12 @@ impl LimitOrder { .try_into() .map_err(|_| anyhow!("executed amount does not fit into u128"))?, ); - let calldata = method.tx.data.ok_or(anyhow!("no calldata"))?; + let calldata = method.calldata(); Ok(eth::Interaction { - target: self.zeroex.address().into(), + target: self.zeroex.address().into_legacy().into(), value: 0.into(), - call_data: calldata.0.into(), + call_data: calldata.to_vec().into(), }) } } diff --git a/crates/driver/src/infra/solver/dto/auction.rs b/crates/driver/src/infra/solver/dto/auction.rs index cc19111bab..9698a5da95 100644 --- a/crates/driver/src/infra/solver/dto/auction.rs +++ b/crates/driver/src/infra/solver/dto/auction.rs @@ -12,6 +12,7 @@ use { util::conv::{rational_to_big_decimal, u256::U256Ext}, }, app_data::AppDataHash, + ethrpc::alloy::conversions::IntoLegacy, model::order::{BuyTokenDestination, SellTokenSource}, std::collections::HashMap, }; @@ -294,7 +295,7 @@ pub fn new( solvers_dto::auction::Liquidity::LimitOrder( solvers_dto::auction::ForeignLimitOrder { id: liquidity.id.0.to_string(), - address: limit_order.zeroex.address(), + address: limit_order.zeroex.address().into_legacy(), gas_estimate: liquidity.gas.into(), hash: Default::default(), maker_token: limit_order.order.maker_token, diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 6772a8395a..51751dfbc2 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" license = "MIT OR Apache-2.0" [dependencies] +alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signers", "signer-local"] } app-data = { workspace = true } anyhow = { workspace = true } autopilot = { workspace = true } diff --git a/crates/e2e/tests/e2e/liquidity.rs b/crates/e2e/tests/e2e/liquidity.rs index f94123a968..2c1468a576 100644 --- a/crates/e2e/tests/e2e/liquidity.rs +++ b/crates/e2e/tests/e2e/liquidity.rs @@ -1,6 +1,9 @@ use { chrono::{NaiveDateTime, Utc}, - contracts::{ERC20, IZeroEx, i_zero_ex::Contract}, + contracts::{ + ERC20, + alloy::{IZeroex, InstanceExt}, + }, driver::domain::eth::H160, e2e::{ api::zeroex::{Eip712TypedZeroExOrder, ZeroExApi}, @@ -19,15 +22,14 @@ use { }, tx, }, - ethcontract::{ - Account, - Bytes, - H256, - errors::MethodError, - prelude::U256, - transaction::TransactionResult, + ethcontract::{Account, H256, prelude::U256}, + ethrpc::{ + Web3, + alloy::{ + ProviderSignerExt, + conversions::{IntoAlloy, IntoLegacy, TryIntoAlloyAsync}, + }, }, - ethrpc::Web3, hex_literal::hex, model::{ order::{OrderCreation, OrderKind}, @@ -75,7 +77,11 @@ async fn zero_ex_liquidity(web3: Web3) { .unwrap(), ); - let zeroex = IZeroEx::deployed(&web3).await.unwrap(); + let zeroex_provider = { + let signer = solver.account().clone().try_into_alloy().await.unwrap(); + web3.alloy.with_signer(signer) + }; + let zeroex = IZeroex::Instance::deployed(&zeroex_provider).await.unwrap(); let amount = to_wei_with_exp(5, 8); @@ -100,11 +106,11 @@ async fn zero_ex_liquidity(web3: Web3) { tx!( zeroex_maker.account(), // With a lower amount 0x contract shows much lower fillable amount - token_usdt.approve(zeroex.address(), amount * 4) + token_usdt.approve(zeroex.address().into_legacy(), amount * 4) ); tx!( solver.account(), - token_usdc.approve(zeroex.address(), amount) + token_usdc.approve(zeroex.address().into_legacy(), amount) ); let order = OrderCreation { @@ -126,7 +132,7 @@ async fn zero_ex_liquidity(web3: Web3) { let zeroex_liquidity_orders = create_zeroex_liquidity_orders( order.clone(), zeroex_maker.clone(), - zeroex.address(), + zeroex.address().into_legacy(), chain_id, onchain.contracts().weth.address(), ); @@ -235,12 +241,12 @@ async fn zero_ex_liquidity(web3: Web3) { maker: zeroex_maker.address(), taker: Default::default(), sender: Default::default(), - fee_recipient: zeroex.address(), + fee_recipient: zeroex.address().into_legacy(), pool: H256::default(), expiry: NaiveDateTime::MAX.and_utc().timestamp() as u64, salt: U256::from(Utc::now().timestamp()), } - .to_order_record(chain_id, zeroex.address(), zeroex_maker); + .to_order_record(chain_id, zeroex.address().into_legacy(), zeroex_maker); fill_or_kill_zeroex_limit_order(&zeroex, &zeroex_order, solver.account().clone()) .await .unwrap(); @@ -325,67 +331,74 @@ struct ZeroExOrderAmounts { } async fn get_zeroex_order_amounts( - zeroex: &Contract, + zeroex: &IZeroex::Instance, zeroex_order: &shared::zeroex_api::OrderRecord, -) -> Result { - zeroex - .get_limit_order_relevant_state( - ( - zeroex_order.order().maker_token, - zeroex_order.order().taker_token, - zeroex_order.order().maker_amount, - zeroex_order.order().taker_amount, - zeroex_order.order().taker_token_fee_amount, - zeroex_order.order().maker, - zeroex_order.order().taker, - zeroex_order.order().sender, - zeroex_order.order().fee_recipient, - Bytes(zeroex_order.order().pool.0), - zeroex_order.order().expiry, - zeroex_order.order().salt, - ), - ( - zeroex_order.order().signature.signature_type, - zeroex_order.order().signature.v, - Bytes(zeroex_order.order().signature.r.0), - Bytes(zeroex_order.order().signature.s.0), - ), +) -> anyhow::Result { + Ok(zeroex + .getLimitOrderRelevantState( + IZeroex::LibNativeOrder::LimitOrder { + makerToken: zeroex_order.order().maker_token.into_alloy(), + takerToken: zeroex_order.order().taker_token.into_alloy(), + makerAmount: zeroex_order.order().maker_amount, + takerAmount: zeroex_order.order().taker_amount, + takerTokenFeeAmount: zeroex_order.order().taker_token_fee_amount, + maker: zeroex_order.order().maker.into_alloy(), + taker: zeroex_order.order().taker.into_alloy(), + sender: zeroex_order.order().sender.into_alloy(), + feeRecipient: zeroex_order.order().fee_recipient.into_alloy(), + pool: zeroex_order.order().pool.into_alloy(), + expiry: zeroex_order.order().expiry, + salt: zeroex_order.order().salt.into_alloy(), + }, + IZeroex::LibSignature::Signature { + signatureType: zeroex_order.order().signature.signature_type, + v: zeroex_order.order().signature.v, + r: zeroex_order.order().signature.r.into_alloy(), + s: zeroex_order.order().signature.s.into_alloy(), + }, ) .call() .await - .map(|((_, _, filled), fillable, _)| ZeroExOrderAmounts { filled, fillable }) + .map(|response| ZeroExOrderAmounts { + filled: response.orderInfo.takerTokenFilledAmount, + fillable: response.actualFillableTakerTokenAmount, + })?) } async fn fill_or_kill_zeroex_limit_order( - zeroex: &Contract, + zeroex: &IZeroex::Instance, zeroex_order: &shared::zeroex_api::OrderRecord, from_account: Account, -) -> Result { - zeroex - .fill_or_kill_limit_order( - ( - zeroex_order.order().maker_token, - zeroex_order.order().taker_token, - zeroex_order.order().maker_amount, - zeroex_order.order().taker_amount, - zeroex_order.order().taker_token_fee_amount, - zeroex_order.order().maker, - zeroex_order.order().taker, - zeroex_order.order().sender, - zeroex_order.order().fee_recipient, - Bytes(zeroex_order.order().pool.0), - zeroex_order.order().expiry, - zeroex_order.order().salt, - ), - ( - zeroex_order.order().signature.signature_type, - zeroex_order.order().signature.v, - Bytes(zeroex_order.order().signature.r.0), - Bytes(zeroex_order.order().signature.s.0), - ), +) -> anyhow::Result { + let tx_hash = zeroex + .fillOrKillLimitOrder( + IZeroex::LibNativeOrder::LimitOrder { + makerToken: zeroex_order.order().maker_token.into_alloy(), + takerToken: zeroex_order.order().taker_token.into_alloy(), + makerAmount: zeroex_order.order().maker_amount, + takerAmount: zeroex_order.order().taker_amount, + takerTokenFeeAmount: zeroex_order.order().taker_token_fee_amount, + maker: zeroex_order.order().maker.into_alloy(), + taker: zeroex_order.order().taker.into_alloy(), + sender: zeroex_order.order().sender.into_alloy(), + feeRecipient: zeroex_order.order().fee_recipient.into_alloy(), + pool: zeroex_order.order().pool.into_alloy(), + expiry: zeroex_order.order().expiry, + salt: zeroex_order.order().salt.into_alloy(), + }, + IZeroex::LibSignature::Signature { + signatureType: zeroex_order.order().signature.signature_type, + v: zeroex_order.order().signature.v, + r: zeroex_order.order().signature.r.into_alloy(), + s: zeroex_order.order().signature.s.into_alloy(), + }, zeroex_order.order().taker_amount, ) - .from(from_account) + .from(from_account.address().into_alloy()) .send() - .await + .await? + .watch() + .await?; + + Ok(tx_hash.into_alloy()) } diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index d4c4f6a771..eb1472ed66 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" doctest = false [dependencies] -alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signer-aws", "signer-local"] } +alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signers", "signer-aws", "signer-local"] } anyhow = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } @@ -42,3 +42,6 @@ testlib = { workspace = true } [lints] workspace = true + +[features] +test-util = [] diff --git a/crates/ethrpc/src/alloy/mod.rs b/crates/ethrpc/src/alloy/mod.rs index af760aa3b0..d3db19bf39 100644 --- a/crates/ethrpc/src/alloy/mod.rs +++ b/crates/ethrpc/src/alloy/mod.rs @@ -2,16 +2,19 @@ mod buffering; pub mod conversions; mod instrumentation; -pub use instrumentation::ProviderLabelingExt; +#[cfg(any(test, feature = "test-util"))] +use alloy::providers::mock; use { crate::AlloyProvider, alloy::{ + network::EthereumWallet, providers::{Provider, ProviderBuilder}, - rpc::client::ClientBuilder, + rpc::client::{ClientBuilder, RpcClient}, }, buffering::BatchCallLayer, instrumentation::{InstrumentationLayer, LabelingLayer}, }; +pub use {conversions::Account, instrumentation::ProviderLabelingExt}; pub fn provider(url: &str) -> AlloyProvider { let rpc = ClientBuilder::default() @@ -23,3 +26,35 @@ pub fn provider(url: &str) -> AlloyProvider { .http(url.parse().unwrap()); ProviderBuilder::new().connect_client(rpc).erased() } + +#[cfg(any(test, feature = "test-util"))] +pub fn dummy_provider() -> AlloyProvider { + let asserter = mock::Asserter::new(); + ProviderBuilder::new() + .connect_mocked_client(asserter) + .erased() +} + +pub trait ProviderSignerExt { + /// Creates a new provider with the given signer. + fn with_signer(&self, signer: Account) -> Self; +} + +impl ProviderSignerExt for AlloyProvider { + fn with_signer(&self, signer: Account) -> Self { + let Account::Signer(signer) = signer else { + // Otherwise an unlocked account is used, not need to change anything. + return self.clone(); + }; + + let is_local = self.client().is_local(); + let transport = self.client().transport().clone(); + let wallet = EthereumWallet::new(signer); + let client = RpcClient::new(transport, is_local); + + ProviderBuilder::new() + .wallet(wallet) + .connect_client(client) + .erased() + } +} diff --git a/crates/solver/Cargo.toml b/crates/solver/Cargo.toml index deeec8329c..dfbeaa950f 100644 --- a/crates/solver/Cargo.toml +++ b/crates/solver/Cargo.toml @@ -40,6 +40,7 @@ web3 = { workspace = true } [dev-dependencies] derivative = { workspace = true } +ethrpc = { workspace = true, features = ["test-util"] } tokio = { workspace = true, features = ["test-util"] } testlib = { workspace = true } diff --git a/crates/solver/src/interactions/zeroex.rs b/crates/solver/src/interactions/zeroex.rs index 37925542de..a93dcb7c46 100644 --- a/crates/solver/src/interactions/zeroex.rs +++ b/crates/solver/src/interactions/zeroex.rs @@ -1,6 +1,7 @@ use { - contracts::IZeroEx, + contracts::alloy::IZeroex, ethcontract::Bytes, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, shared::{ interaction::{EncodedInteraction, Interaction}, zeroex_api::Order, @@ -12,35 +13,40 @@ use { pub struct ZeroExInteraction { pub order: Order, pub taker_token_fill_amount: u128, - pub zeroex: Arc, + // todo: remove Arc + pub zeroex: Arc, } impl Interaction for ZeroExInteraction { fn encode(&self) -> EncodedInteraction { - let method = self.zeroex.fill_or_kill_limit_order( - ( - self.order.maker_token, - self.order.taker_token, - self.order.maker_amount, - self.order.taker_amount, - self.order.taker_token_fee_amount, - self.order.maker, - self.order.taker, - self.order.sender, - self.order.fee_recipient, - Bytes(self.order.pool.0), - self.order.expiry, - self.order.salt, - ), - ( - self.order.signature.signature_type, - self.order.signature.v, - Bytes(self.order.signature.r.0), - Bytes(self.order.signature.s.0), - ), + let method = self.zeroex.fillOrKillLimitOrder( + IZeroex::LibNativeOrder::LimitOrder { + makerToken: self.order.maker_token.into_alloy(), + takerToken: self.order.taker_token.into_alloy(), + makerAmount: self.order.maker_amount, + takerAmount: self.order.taker_amount, + takerTokenFeeAmount: self.order.taker_token_fee_amount, + maker: self.order.maker.into_alloy(), + taker: self.order.taker.into_alloy(), + sender: self.order.sender.into_alloy(), + feeRecipient: self.order.fee_recipient.into_alloy(), + pool: self.order.pool.into_alloy(), + expiry: self.order.expiry, + salt: self.order.salt.into_alloy(), + }, + IZeroex::LibSignature::Signature { + signatureType: self.order.signature.signature_type, + v: self.order.signature.v, + r: self.order.signature.r.into_alloy(), + s: self.order.signature.s.into_alloy(), + }, self.taker_token_fill_amount, ); - let calldata = method.tx.data.expect("no calldata").0; - (self.zeroex.address(), 0.into(), Bytes(calldata)) + let calldata = method.calldata(); + ( + self.zeroex.address().into_legacy(), + 0.into(), + Bytes(calldata.to_vec()), + ) } } diff --git a/crates/solver/src/liquidity/zeroex.rs b/crates/solver/src/liquidity/zeroex.rs index 58d41afa3f..9f6f592b4d 100644 --- a/crates/solver/src/liquidity/zeroex.rs +++ b/crates/solver/src/liquidity/zeroex.rs @@ -11,8 +11,11 @@ use { }, anyhow::Result, arc_swap::ArcSwap, - contracts::{GPv2Settlement, IZeroEx}, - ethrpc::block_stream::{CurrentBlockWatcher, into_stream}, + contracts::{GPv2Settlement, alloy::IZeroex}, + ethrpc::{ + alloy::conversions::IntoLegacy, + block_stream::{CurrentBlockWatcher, into_stream}, + }, futures::StreamExt, itertools::Itertools, model::{TokenPair, order::OrderKind}, @@ -34,7 +37,8 @@ type OrderBuckets = HashMap<(H160, H160), Vec>; type OrderbookCache = ArcSwap; pub struct ZeroExLiquidity { - pub zeroex: Arc, + // todo: remove Arc + pub zeroex: Arc, pub allowance_manager: Box, pub orderbook_cache: Arc, } @@ -43,7 +47,7 @@ impl ZeroExLiquidity { pub async fn new( web3: Web3, api: Arc, - zeroex: IZeroEx, + zeroex: IZeroex::Instance, gpv2: GPv2Settlement, blocks_stream: CurrentBlockWatcher, ) -> Self { @@ -144,7 +148,7 @@ impl LiquidityCollecting for ZeroExLiquidity { let allowances = Arc::new( self.allowance_manager - .get_allowances(tokens, self.zeroex.address()) + .get_allowances(tokens, self.zeroex.address().into_legacy()) .await?, ); @@ -211,7 +215,8 @@ fn get_useful_orders( #[derive(Clone)] pub struct OrderSettlementHandler { pub order_record: OrderRecord, - pub zeroex: Arc, + // todo: remove Arc + pub zeroex: Arc, allowances: Arc, } @@ -249,6 +254,7 @@ pub mod tests { use { super::*, crate::interactions::allowances::Approval, + ethrpc::{alloy, alloy::conversions::IntoAlloy}, maplit::hashmap, shared::{ baseline_solver::BaseTokens, @@ -389,8 +395,14 @@ pub mod tests { #[tokio::test] async fn interaction_encodes_approval_when_insufficient() { let sell_token = H160::from_low_u64_be(1); - let zeroex = Arc::new(contracts::dummy_contract!(IZeroEx, H160::default())); - let allowances = Allowances::new(zeroex.address(), hashmap! { sell_token => 99.into() }); + let zeroex = Arc::new(IZeroex::Instance::new( + H160::default().into_alloy(), + alloy::dummy_provider(), + )); + let allowances = Allowances::new( + zeroex.address().into_legacy(), + hashmap! { sell_token => 99.into() }, + ); let order_record = OrderRecord::new( zeroex_api::Order { taker_amount: 100, @@ -415,7 +427,7 @@ pub mod tests { [ Approval { token: sell_token, - spender: zeroex.address(), + spender: zeroex.address().into_legacy(), } .encode(), ZeroExInteraction { @@ -431,8 +443,14 @@ pub mod tests { #[tokio::test] async fn interaction_encodes_no_approval_when_sufficient() { let sell_token = H160::from_low_u64_be(1); - let zeroex = Arc::new(contracts::dummy_contract!(IZeroEx, H160::default())); - let allowances = Allowances::new(zeroex.address(), hashmap! { sell_token => 100.into() }); + let zeroex = Arc::new(IZeroex::Instance::new( + H160::default().into_alloy(), + alloy::dummy_provider(), + )); + let allowances = Allowances::new( + zeroex.address().into_legacy(), + hashmap! { sell_token => 100.into() }, + ); let order_record = OrderRecord::new( zeroex_api::Order { taker_amount: 100, From 7ace07f9c24442fe3441bccd11c9db5fc86e54ba Mon Sep 17 00:00:00 2001 From: floor-licker Date: Fri, 5 Sep 2025 03:23:09 -0400 Subject: [PATCH 004/112] Optimize protocol fees collection in autopilot post-processing, quadratic to linear time complexity by using a HashMap. (#3621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This function is building a vector of fee policies (`fee_policies`) by looking up orders in an auction. Currently this is doing a linear search for each order ID. The change I'm proposing is to use a HashMap lookup for better performance and also I think slightly cleaner logic. The scale of the performance benefit is a bit context dependent, i.e, average auction size, but I'll share the theoretical perspective. ## The Theoretical Advantage The theoretical advantage here is from an algorithmic perspective, namely O(n²) -> O(n). The current implementation is ``` for order_id in ranking.ranked().flat_map(...).unique() { // m iterations match auction.orders.iter().find(|order| &order.uid == order_id) { // O(n) search // ... } } ``` m = number of unique order IDs from solutions n = number of orders in auction Total complexity: O(m × n) = O(n²) in the worst case (when m ≈ n) Whereas in this case, we'd be doing ``` let order_lookup: HashMap<_, _> = auction.orders.iter() // O(n) to build .map(|order| (order.uid, order)) .collect(); let fee_policies: Vec<_> = ranking.ranked() // m iterations .flat_map(...) .unique() .filter_map(|order_id| { match order_lookup.get(order_id) { // O(1) lookup // ... } }) .collect(); ``` HashMap creation: O(n) Lookups: O(m × 1) = O(m) Total complexity: O(n + m) = O(n) when m ≤ n --------- Co-authored-by: Martin Magnus --- crates/autopilot/src/run_loop.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index 01828d0632..85d9820153 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -398,25 +398,26 @@ impl RunLoop { .all() .map(|participant| participant.solution().solver().into()) .collect::>(); - let mut fee_policies = Vec::new(); - for order_id in ranking + let order_lookup: std::collections::HashMap<_, _> = auction + .orders + .iter() + .map(|order| (&order.uid, order)) + .collect(); + + let fee_policies: Vec<_> = ranking .ranked() .flat_map(|participant| participant.solution().order_ids()) .unique() - { - match auction - .orders - .iter() - .find(|auction_order| &auction_order.uid == order_id) - { + .filter_map(|order_id| match order_lookup.get(order_id) { Some(auction_order) => { - fee_policies.push((auction_order.uid, auction_order.protocol_fees.clone())); + Some((auction_order.uid, auction_order.protocol_fees.clone())) } None => { tracing::debug!(?order_id, "order not found in auction"); + None } - } - } + }) + .collect(); let mut solutions: Vec<_> = ranking .enumerated() From 0c49be3ac914b5ba4a5e58b078cf8cbaf789ef44 Mon Sep 17 00:00:00 2001 From: "Jan [Yann]" <4518474+fafk@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:54:15 +0200 Subject: [PATCH 005/112] Limit pool ids in `where` clause when querying univ3 subgraph for Ticks (#3632) # Description Some slow nodes time out when there are too many pool IDs when we query the uniswap v3 subgraph for `Tick`s. This PR adds the option to limit the number of such items in the query which means running multiple queries with fewer pool IDs in `where` to achieve the same effect. This can't be done in parallel as it overloads these slow nodes. In the end I found a [fast graph](https://thegraph.com/explorer/subgraphs/FiJDXMFCBv88GP17g2TtPh8BcA8jZozn5WRW7hCN7cUT?view=About&chain=arbitrum-one) so this change is not needed, but if this one ever stops working we might be happy to have the option. # Changes - [x] Add a new driver param that limits how many pool ids are added to a single query ## How to test Start driver with graph-url against a slow subgraph (e.g. `F85MNzUGYqgSHSHRGgeVMNsdnW1KtZSVgFULumXRZTw2`) and watch it fail. Then set max pool ids to 10 and see it succeed. --------- Co-authored-by: Martin Magnus --- .../src/boundary/liquidity/uniswap/v3.rs | 1 + crates/driver/src/infra/config/file/load.rs | 9 ++++- crates/driver/src/infra/config/file/mod.rs | 16 ++++++++ crates/driver/src/infra/liquidity/config.rs | 12 +++++- .../src/sources/balancer_v2/graph_api.rs | 6 ++- .../src/sources/uniswap_v3/graph_api.rs | 39 +++++++++++++------ .../src/sources/uniswap_v3/pool_fetching.rs | 14 +++++-- crates/shared/src/subgraph.rs | 12 +++++- 8 files changed, 90 insertions(+), 19 deletions(-) diff --git a/crates/driver/src/boundary/liquidity/uniswap/v3.rs b/crates/driver/src/boundary/liquidity/uniswap/v3.rs index 21a412c248..2686e3904f 100644 --- a/crates/driver/src/boundary/liquidity/uniswap/v3.rs +++ b/crates/driver/src/boundary/liquidity/uniswap/v3.rs @@ -138,6 +138,7 @@ async fn init_liquidity( boundary::liquidity::http_client(), block_retriever, config.max_pools_to_initialize, + config.max_pools_per_tick_query, ) .await .context("failed to initialise UniswapV3 liquidity")?, diff --git a/crates/driver/src/infra/config/file/load.rs b/crates/driver/src/infra/config/file/load.rs index 5183c38df3..47feef96e5 100644 --- a/crates/driver/src/infra/config/file/load.rs +++ b/crates/driver/src/infra/config/file/load.rs @@ -222,12 +222,17 @@ pub async fn load(chain: Chain, path: &Path) -> infra::Config { max_pools_to_initialize, graph_url, reinit_interval, + max_pools_per_tick_query, } => liquidity::config::UniswapV3 { max_pools_to_initialize, reinit_interval, ..match preset { file::UniswapV3Preset::UniswapV3 => { - liquidity::config::UniswapV3::uniswap_v3(&graph_url, chain) + liquidity::config::UniswapV3::uniswap_v3( + &graph_url, + chain, + max_pools_per_tick_query, + ) } } .expect("no Uniswap V3 preset for current network") @@ -237,11 +242,13 @@ pub async fn load(chain: Chain, path: &Path) -> infra::Config { max_pools_to_initialize, graph_url, reinit_interval, + max_pools_per_tick_query, } => liquidity::config::UniswapV3 { router: router.into(), max_pools_to_initialize, graph_url, reinit_interval, + max_pools_per_tick_query, }, }) .collect(), diff --git a/crates/driver/src/infra/config/file/mod.rs b/crates/driver/src/infra/config/file/mod.rs index 8873acdf6e..7c2ad98424 100644 --- a/crates/driver/src/infra/config/file/mod.rs +++ b/crates/driver/src/infra/config/file/mod.rs @@ -556,6 +556,12 @@ enum UniswapV3Config { graph_url: Url, + /// How many pool IDs can be present in a where clause of a Tick query + /// at once. Some subgraphs are overloaded and throw errors when + /// there are too many. + #[serde(default = "uniswap_v3::default_max_pools_per_tick_query")] + max_pools_per_tick_query: usize, + /// How often the liquidity source should be reinitialized to get /// access to new pools. #[serde(with = "humantime_serde", default = "default_reinit_interval")] @@ -571,6 +577,12 @@ enum UniswapV3Config { #[serde(default = "uniswap_v3::default_max_pools_to_initialize")] max_pools_to_initialize: usize, + /// How many pool IDs can be present in a where clause of a Tick query + /// at once. Some subgraphs are overloaded and throw errors when + /// there are too many. + #[serde(default = "uniswap_v3::default_max_pools_per_tick_query")] + max_pools_per_tick_query: usize, + /// The URL used to connect to uniswap v3 subgraph client. graph_url: Url, @@ -591,6 +603,10 @@ mod uniswap_v3 { pub fn default_max_pools_to_initialize() -> usize { 100 } + + pub fn default_max_pools_per_tick_query() -> usize { + usize::MAX + } } #[derive(Clone, Debug, Deserialize)] diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index fd4f052cc2..48aa4e687e 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -156,17 +156,27 @@ pub struct UniswapV3 { /// How often the liquidity source should be reinitialized to /// become aware of new pools. pub reinit_interval: Option, + + /// How many pool IDs can be present in a where clause of a Tick query at + /// once. Some subgraphs are overloaded and throw errors when there are + /// too many. + pub max_pools_per_tick_query: usize, } impl UniswapV3 { /// Returns the liquidity configuration for Uniswap V3. #[allow(clippy::self_named_constructors)] - pub fn uniswap_v3(graph_url: &Url, chain: Chain) -> Option { + pub fn uniswap_v3( + graph_url: &Url, + chain: Chain, + max_pools_per_tick_query: usize, + ) -> Option { Some(Self { router: deployment_address(contracts::UniswapV3SwapRouterV2::raw_contract(), chain)?, max_pools_to_initialize: 100, graph_url: graph_url.clone(), reinit_interval: None, + max_pools_per_tick_query, }) } } diff --git a/crates/shared/src/sources/balancer_v2/graph_api.rs b/crates/shared/src/sources/balancer_v2/graph_api.rs index cd82fa5cce..ae64119fea 100644 --- a/crates/shared/src/sources/balancer_v2/graph_api.rs +++ b/crates/shared/src/sources/balancer_v2/graph_api.rs @@ -35,7 +35,11 @@ pub struct BalancerSubgraphClient(SubgraphClient); impl BalancerSubgraphClient { /// Creates a new Balancer subgraph client with full subgraph URL. pub fn from_subgraph_url(subgraph_url: &Url, client: Client) -> Result { - Ok(Self(SubgraphClient::try_new(subgraph_url.clone(), client)?)) + Ok(Self(SubgraphClient::try_new( + subgraph_url.clone(), + client, + usize::MAX, + )?)) } /// Retrieves the list of registered pools from the subgraph. diff --git a/crates/shared/src/sources/uniswap_v3/graph_api.rs b/crates/shared/src/sources/uniswap_v3/graph_api.rs index 839da1d5cb..52dc383f76 100644 --- a/crates/shared/src/sources/uniswap_v3/graph_api.rs +++ b/crates/shared/src/sources/uniswap_v3/graph_api.rs @@ -107,8 +107,16 @@ pub struct UniV3SubgraphClient(SubgraphClient); impl UniV3SubgraphClient { /// Creates a new Uniswap V3 subgraph client from the specified URL. - pub fn from_subgraph_url(subgraph_url: &Url, client: Client) -> Result { - Ok(Self(SubgraphClient::try_new(subgraph_url.clone(), client)?)) + pub fn from_subgraph_url( + subgraph_url: &Url, + client: Client, + max_pools_per_tick_query: usize, + ) -> Result { + Ok(Self(SubgraphClient::try_new( + subgraph_url.clone(), + client, + max_pools_per_tick_query, + )?)) } async fn get_pools(&self, query: &str, variables: Map) -> Result> { @@ -134,7 +142,6 @@ impl UniV3SubgraphClient { }) } - /// Retrieves the pool data for pools with given pool ids async fn get_pools_by_pool_ids( &self, pool_ids: &[H160], @@ -154,15 +161,23 @@ impl UniV3SubgraphClient { pool_ids: &[H160], block_number: u64, ) -> Result> { - let variables = json_map! { - "block" => block_number, - "pool_ids" => json!(pool_ids) - }; - let result = self - .0 - .paginated_query(TICKS_BY_POOL_IDS_QUERY, variables) - .await?; - Ok(result) + let mut all = Vec::new(); + + // Default chunk size is usize::MAX - all pool ids in one `where`. We want to + // run requests sequentially to avoid overwhelming the node. + for chunk in pool_ids.chunks(self.0.max_pools_per_tick_query()) { + let variables = json_map! { + "block" => block_number, + "pool_ids" => json!(chunk) + }; + let mut batch = self + .0 + .paginated_query(TICKS_BY_POOL_IDS_QUERY, variables) + .await?; + all.append(&mut batch); + } + + Ok(all) } /// Retrieves the pool data and ticks data for pools with given pool ids diff --git a/crates/shared/src/sources/uniswap_v3/pool_fetching.rs b/crates/shared/src/sources/uniswap_v3/pool_fetching.rs index 3ca9ff690c..c0f110e557 100644 --- a/crates/shared/src/sources/uniswap_v3/pool_fetching.rs +++ b/crates/shared/src/sources/uniswap_v3/pool_fetching.rs @@ -135,8 +135,10 @@ impl PoolsCheckpointHandler { subgraph_url: &Url, client: Client, max_pools_to_initialize_cache: usize, + max_pools_per_tick_query: usize, ) -> Result { - let graph_api = UniV3SubgraphClient::from_subgraph_url(subgraph_url, client)?; + let graph_api = + UniV3SubgraphClient::from_subgraph_url(subgraph_url, client, max_pools_per_tick_query)?; let mut registered_pools = graph_api.get_registered_pools().await?; tracing::debug!( block = %registered_pools.fetched_block_number, pools = %registered_pools.pools.len(), @@ -274,10 +276,16 @@ impl UniswapV3PoolFetcher { client: Client, block_retriever: Arc, max_pools_to_initialize: usize, + max_pools_per_tick_query: usize, ) -> Result { let web3 = ethrpc::instrumented::instrument_with_label(&web3, "uniswapV3".into()); - let checkpoint = - PoolsCheckpointHandler::new(subgraph_url, client, max_pools_to_initialize).await?; + let checkpoint = PoolsCheckpointHandler::new( + subgraph_url, + client, + max_pools_to_initialize, + max_pools_per_tick_query, + ) + .await?; let init_block = checkpoint.pools_checkpoint.lock().unwrap().block_number; let init_block = block_retriever.block(init_block).await?; diff --git a/crates/shared/src/subgraph.rs b/crates/shared/src/subgraph.rs index 3b9f47c9f5..aa7fd838d7 100644 --- a/crates/shared/src/subgraph.rs +++ b/crates/shared/src/subgraph.rs @@ -15,6 +15,7 @@ const MAX_NUMBER_OF_RETRIES: usize = 10; pub struct SubgraphClient { client: Client, subgraph_url: Url, + max_pools_per_tick_query: usize, } pub trait ContainsId { @@ -29,10 +30,15 @@ pub struct Data { impl SubgraphClient { /// Creates a new subgraph client from the specified organization and name. - pub fn try_new(subgraph_url: Url, client: Client) -> Result { + pub fn try_new( + subgraph_url: Url, + client: Client, + max_pools_per_tick_query: usize, + ) -> Result { Ok(Self { client, subgraph_url, + max_pools_per_tick_query, }) } @@ -104,6 +110,10 @@ impl SubgraphClient { Ok(result) } + + pub fn max_pools_per_tick_query(&self) -> usize { + self.max_pools_per_tick_query + } } /// A GraphQL query. From 0b8db63ea6ce1c0679d0b70441a2257aaa702095 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 8 Sep 2025 14:08:13 +0300 Subject: [PATCH 006/112] [TRIVIAL] Remove dummy alloy provider (#3637) Removes the dummy alloy provider since it was overseen that a similar function already exists in `ethrpc::mock::web()` funciton. --- crates/ethrpc/Cargo.toml | 3 --- crates/ethrpc/src/alloy/mod.rs | 10 ---------- crates/solver/Cargo.toml | 1 - crates/solver/src/liquidity/zeroex.rs | 6 +++--- 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index eb1472ed66..47935e3795 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -42,6 +42,3 @@ testlib = { workspace = true } [lints] workspace = true - -[features] -test-util = [] diff --git a/crates/ethrpc/src/alloy/mod.rs b/crates/ethrpc/src/alloy/mod.rs index d3db19bf39..cfefe9002b 100644 --- a/crates/ethrpc/src/alloy/mod.rs +++ b/crates/ethrpc/src/alloy/mod.rs @@ -2,8 +2,6 @@ mod buffering; pub mod conversions; mod instrumentation; -#[cfg(any(test, feature = "test-util"))] -use alloy::providers::mock; use { crate::AlloyProvider, alloy::{ @@ -27,14 +25,6 @@ pub fn provider(url: &str) -> AlloyProvider { ProviderBuilder::new().connect_client(rpc).erased() } -#[cfg(any(test, feature = "test-util"))] -pub fn dummy_provider() -> AlloyProvider { - let asserter = mock::Asserter::new(); - ProviderBuilder::new() - .connect_mocked_client(asserter) - .erased() -} - pub trait ProviderSignerExt { /// Creates a new provider with the given signer. fn with_signer(&self, signer: Account) -> Self; diff --git a/crates/solver/Cargo.toml b/crates/solver/Cargo.toml index dfbeaa950f..deeec8329c 100644 --- a/crates/solver/Cargo.toml +++ b/crates/solver/Cargo.toml @@ -40,7 +40,6 @@ web3 = { workspace = true } [dev-dependencies] derivative = { workspace = true } -ethrpc = { workspace = true, features = ["test-util"] } tokio = { workspace = true, features = ["test-util"] } testlib = { workspace = true } diff --git a/crates/solver/src/liquidity/zeroex.rs b/crates/solver/src/liquidity/zeroex.rs index 9f6f592b4d..8b8f74c8e0 100644 --- a/crates/solver/src/liquidity/zeroex.rs +++ b/crates/solver/src/liquidity/zeroex.rs @@ -254,7 +254,7 @@ pub mod tests { use { super::*, crate::interactions::allowances::Approval, - ethrpc::{alloy, alloy::conversions::IntoAlloy}, + ethrpc::alloy::conversions::IntoAlloy, maplit::hashmap, shared::{ baseline_solver::BaseTokens, @@ -397,7 +397,7 @@ pub mod tests { let sell_token = H160::from_low_u64_be(1); let zeroex = Arc::new(IZeroex::Instance::new( H160::default().into_alloy(), - alloy::dummy_provider(), + ethrpc::mock::web3().alloy, )); let allowances = Allowances::new( zeroex.address().into_legacy(), @@ -445,7 +445,7 @@ pub mod tests { let sell_token = H160::from_low_u64_be(1); let zeroex = Arc::new(IZeroex::Instance::new( H160::default().into_alloy(), - alloy::dummy_provider(), + ethrpc::mock::web3().alloy, )); let allowances = Allowances::new( zeroex.address().into_legacy(), From 8737eca245f351792c0ec1411b06522c63578f2f Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 8 Sep 2025 14:18:17 +0300 Subject: [PATCH 007/112] Add deployment block info to alloy bindings (#3630) # Description To continue with the alloy migration, a deployed block info is required for some of the SC, list GPV2Settlement, BalancerV2.x, others. # Changes Extend the bindings macro and InstanceExt interface with that data. Now, in the bindings! macro, added a way to provide either only address or a tuple with (address, deployment block). --- crates/contracts/src/alloy.rs | 68 +++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 6d4a64ee45..e08171ee8d 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -13,7 +13,7 @@ pub mod networks { crate::bindings!( ChainalysisOracle, - maplit::hashmap! { + crate::deployments! { MAINNET => address!("0x40C57923924B5c5c5455c48D93317139ADDaC8fb"), ARBITRUM_ONE => address!("0x40C57923924B5c5c5455c48D93317139ADDaC8fb"), BASE => address!("0x3A91A31cB3dC49b4db9Ce721F50a9D076c8D739B"), @@ -26,7 +26,7 @@ crate::bindings!( crate::bindings!( IZeroex, - maplit::hashmap! { + crate::deployments! { MAINNET => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), SEPOLIA => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), ARBITRUM_ONE => address!("0xdef1c0ded9bec7f1a1670819833240f027b25eff"), @@ -48,11 +48,52 @@ pub trait InstanceExt: Sized { fn deployed( provider: &Provider, ) -> impl std::future::Future> + Send; + + /// Returns the block number at which the contract was deployed, if known. + fn deployed_block( + &self, + ) -> impl std::future::Future>> + Send; +} + +/// Build a `HashMap)>` from entries like: +/// `CHAIN_ID => address!("0x…")` // block = None +/// `CHAIN_ID => (address!("0x…"), 12_345_678)` // block = Some(…) +#[macro_export] +macro_rules! deployments { + (@acc $m:ident; ) => {}; + + // Tuple form with trailing comma: CHAIN => (addr, block), + (@acc $m:ident; $chain:expr => ( $addr:expr, $block:expr ), $($rest:tt)* ) => { + $m.insert($chain, ($addr, Some($block))); + $crate::deployments!(@acc $m; $($rest)*); + }; + + // Address-only form with trailing comma: CHAIN => addr, + (@acc $m:ident; $chain:expr => $addr:expr, $($rest:tt)* ) => { + $m.insert($chain, ($addr, None::)); + $crate::deployments!(@acc $m; $($rest)*); + }; + + // Tuple form without trailing comma (last entry). + (@acc $m:ident; $chain:expr => ( $addr:expr, $block:expr ) ) => { + $m.insert($chain, ($addr, Some($block))); + }; + + // Address-only form without trailing comma (last entry). + (@acc $m:ident; $chain:expr => $addr:expr ) => { + $m.insert($chain, ($addr, None::)); + }; + + ( $($rest:tt)* ) => {{ + let mut m = ::std::collections::HashMap::new(); + $crate::deployments!(@acc m; $($rest)*); + m + }}; } #[macro_export] macro_rules! bindings { - ($contract:ident, $($deployment_info:expr)?) => { + ($contract:ident $(, $deployment_info:expr)?) => { paste::paste! { // Generate the main bindings in a private module. That allows // us to re-export all items in our own module while also adding @@ -85,7 +126,7 @@ macro_rules! bindings { $crate::alloy::networks::*, }; - pub static DEPLOYMENT_INFO: LazyLock> = LazyLock::new(|| { + pub static DEPLOYMENT_INFO: LazyLock)>> = LazyLock::new(|| { $deployment_info }); @@ -96,16 +137,31 @@ macro_rules! bindings { .get_chain_id() .await .context("could not fetch current chain id")?; - let address = DEPLOYMENT_INFO + + let (address, _deployed_block) = *DEPLOYMENT_INFO .get(&chain_id) .with_context(|| format!("no deployment info for chain {chain_id:?}"))?; Ok(Instance::new( - address.clone(), + address, provider.clone(), )) } } + + fn deployed_block(&self) -> impl Future>> + Send { + async move { + let chain_id = self + .provider() + .get_chain_id() + .await + .context("could not fetch current chain id")?; + if let Some((_address, deployed_block)) = DEPLOYMENT_INFO.get(&chain_id) { + return Ok(*deployed_block); + } + Ok(None) + } + } } )* } From 2b4b557992c06e5e1c8e580f7103e4916246af73 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 8 Sep 2025 14:32:10 +0300 Subject: [PATCH 008/112] Alloy BlockId conversion (#3633) Adds alloy's BlockId and BlockNumber conversions, which are required for further migration. --- crates/ethrpc/Cargo.toml | 2 +- crates/ethrpc/src/alloy/conversions.rs | 32 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index 47935e3795..03de4f5c3f 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -11,7 +11,7 @@ path = "src/lib.rs" doctest = false [dependencies] -alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signers", "signer-aws", "signer-local"] } +alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signers", "signer-aws", "signer-local", "eips"] } anyhow = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } diff --git a/crates/ethrpc/src/alloy/conversions.rs b/crates/ethrpc/src/alloy/conversions.rs index bf17d42a57..5ac5850342 100644 --- a/crates/ethrpc/src/alloy/conversions.rs +++ b/crates/ethrpc/src/alloy/conversions.rs @@ -51,6 +51,38 @@ impl IntoAlloy for primitive_types::H256 { } } +impl IntoAlloy for web3::types::BlockNumber { + type To = alloy::eips::BlockNumberOrTag; + + fn into_alloy(self) -> Self::To { + match self { + web3::types::BlockNumber::Finalized => alloy::eips::BlockNumberOrTag::Finalized, + web3::types::BlockNumber::Safe => alloy::eips::BlockNumberOrTag::Safe, + web3::types::BlockNumber::Latest => alloy::eips::BlockNumberOrTag::Latest, + web3::types::BlockNumber::Earliest => alloy::eips::BlockNumberOrTag::Earliest, + web3::types::BlockNumber::Pending => alloy::eips::BlockNumberOrTag::Pending, + web3::types::BlockNumber::Number(number) => { + alloy::eips::BlockNumberOrTag::Number(number.as_u64()) + } + } + } +} + +impl IntoAlloy for web3::types::BlockId { + type To = alloy::eips::BlockId; + + fn into_alloy(self) -> Self::To { + match self { + web3::types::BlockId::Hash(hash) => { + alloy::eips::BlockId::Hash(alloy::eips::RpcBlockHash::from(hash.into_alloy())) + } + web3::types::BlockId::Number(number) => { + alloy::eips::BlockId::Number(number.into_alloy()) + } + } + } +} + pub enum Account { Address(alloy::primitives::Address), Signer(Box + Send + Sync + 'static>), From 538f5acdca4cfab4eb520152abb7dba3f9f33acb Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 8 Sep 2025 14:52:10 +0300 Subject: [PATCH 009/112] Alloy function selectors (#3631) # Description Adds an ability to retrieve SC function selectors required across the code base: https://github.com/cowprotocol/services/blob/e8fc0ac7e32d9046523c7d051f430e8d9cf2d5be/crates/autopilot/src/domain/settlement/transaction/mod.rs#L202 https://github.com/cowprotocol/services/blob/97ddf0c55b1ab7a41bd026ed5bfe7a95fd86f9ed/crates/contracts/src/vault.rs#L21 # Changes Parse the same ABI JSON used for the SC bindings and get function selectors by its name. --- crates/contracts/Cargo.toml | 5 +- crates/contracts/src/alloy.rs | 90 +++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index facce3a849..5b981ca22e 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -16,22 +16,21 @@ required-features = ["bin"] default = [] bin = [ "ethcontract-generate", - "serde_json", "tracing", "tracing-subscriber", ] [dependencies] -alloy = { workspace = true, features = ["sol-types", "json", "contract"] } +alloy = { workspace = true, features = ["sol-types", "json", "contract", "json-abi"] } paste = { workspace = true } maplit = { workspace = true } ethcontract = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } # [bin-dependencies] anyhow = { workspace = true } ethcontract-generate = { workspace = true, optional = true, features = ["http"] } -serde_json = { workspace = true, optional = true } tracing = { workspace = true, optional = true } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"], optional = true } diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index e08171ee8d..ab8bd4b68a 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -110,19 +110,44 @@ macro_rules! bindings { #[allow(non_snake_case)] pub mod $contract { - use alloy::providers::DynProvider; + use { + std::sync::LazyLock, + anyhow::{anyhow, Result}, + alloy::{ + json_abi::{ContractObject, JsonAbi}, + primitives::Selector, + providers::DynProvider, + }, + }; pub use super::[<$contract Private>]::*; pub type Instance = $contract::[<$contract Instance>]; + /// The contract's ABI parsed from the bundled artifact. + pub static ABI: LazyLock = LazyLock::new(|| { + let obj: ContractObject = serde_json::from_str(include_str!(concat!( + "../artifacts/", stringify!($contract), ".json" + ))) + .expect(concat!("failed to parse artifact JSON for ", stringify!($contract))); + obj.abi.expect(&format!("artifact for {} missing `abi` field", stringify!($contract))) + }); + + /// Return all function overloads 4-byte selectors by *name*. + pub fn selector_by_name(name: &str) -> Result> { + let Some(funcs) = ABI.functions.get(name) else { + return Err(anyhow!("no function named `{name}` in ABI")); + }; + Ok(funcs.iter().map(|f| f.selector()).collect()) + } + $( use { - std::{sync::LazyLock, collections::HashMap}, + std::collections::HashMap, alloy::{ providers::Provider, primitives::{address, Address}, }, - anyhow::{Context, Result}, + anyhow::Context, $crate::alloy::networks::*, }; @@ -168,3 +193,62 @@ macro_rules! bindings { } }; } + +#[cfg(test)] +mod tests { + use super::*; + use alloy::primitives::Selector; + + #[test] + fn test_selector_by_name_valid_function() { + let result = ChainalysisOracle::selector_by_name("isSanctioned"); + assert!(result.is_ok()); + + let selectors = result.unwrap(); + assert_eq!(selectors.len(), 1); + + let selector = &selectors[0]; + assert_eq!(selector, &Selector::from([0xdf, 0x59, 0x2f, 0x7d])); + } + + #[test] + fn test_selector_by_name_multiple_overloads() { + // Test with a contract that might have function overloads + // Using IZeroex which likely has multiple swap functions + let result = IZeroex::selector_by_name("transformERC20"); + + let selectors = result.unwrap(); + + assert!(!selectors.is_empty()); + + for selector in &selectors { + assert_eq!(selector.as_slice().len(), 4); + } + } + + #[test] + fn test_selector_by_name_invalid_function() { + let result = ChainalysisOracle::selector_by_name("nonExistentFunction"); + assert!(result.is_err()); + + let error_msg = result.unwrap_err().to_string(); + assert!(error_msg.contains("no function named `nonExistentFunction` in ABI")); + } + + #[test] + fn test_selector_by_name_empty_string() { + let result = ChainalysisOracle::selector_by_name(""); + assert!(result.is_err()); + } + + #[test] + fn test_selector_by_name_case_sensitive() { + let result1 = ChainalysisOracle::selector_by_name("isSanctioned"); + let result2 = ChainalysisOracle::selector_by_name("IsSanctioned"); + let result3 = ChainalysisOracle::selector_by_name("ISSANCTIONED"); + + assert!(result1.is_ok()); + assert!(result2.is_err()); + assert!(result3.is_err()); + } +} From 441beac9340de6836cc3f30996d038c0099e0154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Mon, 8 Sep 2025 13:37:17 +0100 Subject: [PATCH 010/112] Migrate GnosisSafe* contracts to alloy-rs (#3601) --- crates/contracts/Cargo.toml | 1 + crates/contracts/build.rs | 6 - crates/contracts/src/alloy.rs | 43 +++ crates/contracts/src/lib.rs | 4 - crates/e2e/Cargo.toml | 7 +- crates/e2e/src/setup/mod.rs | 2 +- .../e2e/src/setup/onchain_components/alloy.rs | 26 ++ .../e2e/src/setup/onchain_components/mod.rs | 3 +- .../e2e/src/setup/onchain_components/safe.rs | 251 +++++++++--------- crates/e2e/tests/e2e/app_data.rs | 1 - crates/e2e/tests/e2e/app_data_signer.rs | 18 +- crates/e2e/tests/e2e/banned_users.rs | 2 +- crates/e2e/tests/e2e/buffers.rs | 1 - crates/e2e/tests/e2e/eth_integration.rs | 2 +- crates/e2e/tests/e2e/eth_safe.rs | 30 ++- crates/e2e/tests/e2e/flashloans.rs | 50 ++-- crates/e2e/tests/e2e/hooks.rs | 175 +++++++----- crates/e2e/tests/e2e/order_cancellation.rs | 1 - .../e2e/tests/e2e/place_order_with_quote.rs | 1 - crates/e2e/tests/e2e/quoting.rs | 1 - crates/e2e/tests/e2e/smart_contract_orders.rs | 15 +- crates/e2e/tests/e2e/submission.rs | 2 +- .../tests/e2e/tracking_insufficient_funds.rs | 1 - crates/e2e/tests/e2e/uncovered_order.rs | 1 - crates/e2e/tests/e2e/vault_balances.rs | 1 - crates/ethrpc/Cargo.toml | 15 +- 26 files changed, 395 insertions(+), 265 deletions(-) create mode 100644 crates/e2e/src/setup/onchain_components/alloy.rs diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index 5b981ca22e..eba5e4f952 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -19,6 +19,7 @@ bin = [ "tracing", "tracing-subscriber", ] +test-util = [] [dependencies] alloy = { workspace = true, features = ["sol-types", "json", "contract", "json-abi"] } diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index ed7b1aea59..4ad0abc2c4 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -1297,12 +1297,6 @@ fn main() { }, ) }); - generate_contract("GnosisSafe"); - generate_contract_with_config("GnosisSafeCompatibilityFallbackHandler", |builder| { - builder.add_method_alias("isValidSignature(bytes,bytes)", "is_valid_signature_legacy") - }); - generate_contract("GnosisSafeProxy"); - generate_contract("GnosisSafeProxyFactory"); generate_contract_with_config("HoneyswapRouter", |builder| { builder.add_network_str(GNOSIS, "0x1C232F01118CB8B424793ae03F870aa7D0ac7f77") }); diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index ab8bd4b68a..6e11b85bfd 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "test-util")] +pub use crate::{tx, tx_value}; + pub mod networks { pub const MAINNET: u64 = 1; pub const GNOSIS: u64 = 100; @@ -39,6 +42,11 @@ crate::bindings!( } ); +crate::bindings!(GnosisSafe); +crate::bindings!(GnosisSafeCompatibilityFallbackHandler); +crate::bindings!(GnosisSafeProxy); +crate::bindings!(GnosisSafeProxyFactory); + pub use alloy::providers::DynProvider as Provider; /// Extension trait to attach some useful functions to the contract instance. @@ -194,6 +202,41 @@ macro_rules! bindings { }; } +#[cfg(feature = "test-util")] +#[macro_export] +macro_rules! tx_value { + ($call:expr, $value:expr) => {{ + const NAME: &str = stringify!($call); + $call + .value($value) + .send() + .await + .expect(&format!("failed to send: {}", NAME)) + .watch() + .await + .expect(&format!("failed to get confirmations for: {}", NAME)) + }}; + ($call:expr, $value:expr, $acc:expr) => {{ + const NAME: &str = stringify!($call); + $call + .from($acc) + .value($value) + .send() + .await + .expect(&format!("failed to send: {}", NAME)) + .watch() + .await + .expect(&format!("failed to get confirmations for: {}", NAME)) + }}; +} + +#[cfg(feature = "test-util")] +#[macro_export] +macro_rules! tx { + ($call:expr) => {{ $crate::tx_value!($call, ::alloy::primitives::U256::ZERO) }}; + ($call:expr, $acc:expr) => {{ $crate::tx_value!($call, ::alloy::primitives::U256::ZERO, $acc) }}; +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 01235123c1..31cf0eba14 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -86,10 +86,6 @@ include_contracts! { FlashLoanRouter; GPv2AllowListAuthentication; GPv2Settlement; - GnosisSafe; - GnosisSafeCompatibilityFallbackHandler; - GnosisSafeProxy; - GnosisSafeProxyFactory; HoneyswapRouter; HooksTrampoline; IAavePool; diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 51751dfbc2..fa7e446822 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "e2e" version = "1.0.0" -authors = ["Gnosis Developers ", "Cow Protocol Developers "] +authors = [ + "Gnosis Developers ", + "Cow Protocol Developers ", +] edition = "2024" license = "MIT OR Apache-2.0" @@ -14,7 +17,7 @@ axum = { workspace = true } bigdecimal = { workspace = true } chrono = { workspace = true } clap = { workspace = true } -contracts = { workspace = true } +contracts = { workspace = true, features = ["test-util"] } database = { workspace = true } driver = { workspace = true } ethcontract = { workspace = true } diff --git a/crates/e2e/src/setup/mod.rs b/crates/e2e/src/setup/mod.rs index 59e2ff2eea..508eeb4528 100644 --- a/crates/e2e/src/setup/mod.rs +++ b/crates/e2e/src/setup/mod.rs @@ -111,7 +111,7 @@ where /// of the chain. The saved state is restored at the end of the test. /// The database is cleaned at the end of the test. /// -/// This function also intializes tracing and sets panic hook. +/// This function also initializes tracing and sets panic hook. /// /// Note that tests calling with this function will not be run simultaneously. pub async fn run_test(f: F) diff --git a/crates/e2e/src/setup/onchain_components/alloy.rs b/crates/e2e/src/setup/onchain_components/alloy.rs new file mode 100644 index 0000000000..8bde749194 --- /dev/null +++ b/crates/e2e/src/setup/onchain_components/alloy.rs @@ -0,0 +1,26 @@ +use { + alloy::contract::{CallBuilder, CallDecoder}, + app_data::Hook, + ethrpc::{AlloyProvider, alloy::conversions::IntoLegacy}, +}; + +pub async fn hook_for_transaction(tx: CallBuilder<&AlloyProvider, D>) -> anyhow::Result +where + D: CallDecoder, +{ + let gas_limit = tx.estimate_gas().await?; + let call_data = tx.calldata().to_vec(); + let target = tx + .into_transaction_request() + .to + .unwrap() + .into_to() + .unwrap() + .into_legacy(); + + Ok(Hook { + target, + call_data, + gas_limit, + }) +} diff --git a/crates/e2e/src/setup/onchain_components/mod.rs b/crates/e2e/src/setup/onchain_components/mod.rs index a648d916f7..24c9f03373 100644 --- a/crates/e2e/src/setup/onchain_components/mod.rs +++ b/crates/e2e/src/setup/onchain_components/mod.rs @@ -28,6 +28,7 @@ use { }, }; +pub mod alloy; pub mod safe; #[macro_export] @@ -46,7 +47,7 @@ macro_rules! tx_value { #[macro_export] macro_rules! tx { ($acc:expr_2021, $call:expr_2021) => { - $crate::tx_value!($acc, U256::zero(), $call) + $crate::tx_value!($acc, ethcontract::U256::zero(), $call) }; } diff --git a/crates/e2e/src/setup/onchain_components/safe.rs b/crates/e2e/src/setup/onchain_components/safe.rs index 95ce981315..e9e545ee39 100644 --- a/crates/e2e/src/setup/onchain_components/safe.rs +++ b/crates/e2e/src/setup/onchain_components/safe.rs @@ -1,91 +1,114 @@ use { super::{OnchainComponents, TestAccount}, - contracts::{ - GnosisSafe, + alloy::{ + primitives::{Address, Bytes, U256}, + providers::Provider, + }, + contracts::alloy::{ + GnosisSafe::{self, GnosisSafe::execTransactionCall}, GnosisSafeCompatibilityFallbackHandler, GnosisSafeProxy, GnosisSafeProxyFactory, }, - ethcontract::{Bytes, H160, H256, U256}, + ethcontract::transaction::TransactionBuilder, + ethrpc::{ + AlloyProvider, + alloy::{ + ProviderSignerExt, + conversions::{IntoAlloy, TryIntoAlloyAsync}, + }, + }, hex_literal::hex, model::{ DomainSeparator, order::OrderCreation, signature::{Signature, hashed_eip712_message}, }, - shared::ethrpc::Web3, - web3::{ - signing, - signing::{Key, SecretKeyRef}, - }, + std::marker::PhantomData, + web3::signing::{self}, }; -macro_rules! tx_safe { - ($acc:expr_2021, $safe:ident, $call:expr_2021) => {{ - let call = $call; - $crate::tx!( - $acc, - $safe.exec_transaction( - call.tx.to.unwrap(), - call.tx.value.unwrap_or_default(), - ::ethcontract::Bytes(call.tx.data.unwrap_or_default().0), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - $crate::setup::safe::gnosis_safe_prevalidated_signature($acc.address()), - ) - ); - }}; -} - pub struct Infrastructure { - pub factory: GnosisSafeProxyFactory, - pub fallback: GnosisSafeCompatibilityFallbackHandler, - pub singleton: GnosisSafe, - web3: Web3, + pub factory: GnosisSafeProxyFactory::Instance, + pub fallback: GnosisSafeCompatibilityFallbackHandler::Instance, + pub singleton: GnosisSafe::Instance, + + pub provider: AlloyProvider, } impl Infrastructure { - pub async fn new(web3: &Web3) -> Self { - let singleton = GnosisSafe::builder(web3).deploy().await.unwrap(); - let fallback = GnosisSafeCompatibilityFallbackHandler::builder(web3) - .deploy() - .await - .unwrap(); - let factory = GnosisSafeProxyFactory::builder(web3) - .deploy() - .await - .unwrap(); + pub async fn new(provider: AlloyProvider) -> Self { + let first_account = *provider.get_accounts().await.unwrap().first().unwrap(); + + let singleton = { + let deployed_address = GnosisSafe::Instance::deploy_builder(provider.clone()) + .from(first_account) + .deploy() + .await + .unwrap(); + GnosisSafe::Instance::new(deployed_address, provider.clone()) + }; + let fallback = { + let deployed_address = + GnosisSafeCompatibilityFallbackHandler::Instance::deploy_builder(provider.clone()) + .from(first_account) + .deploy() + .await + .unwrap(); + GnosisSafeCompatibilityFallbackHandler::Instance::new( + deployed_address, + provider.clone(), + ) + }; + let factory = { + let deployed_address = + GnosisSafeProxyFactory::Instance::deploy_builder(provider.clone()) + .from(first_account) + .deploy() + .await + .unwrap(); + GnosisSafeProxyFactory::Instance::new(deployed_address, provider.clone()) + }; + Self { - web3: web3.clone(), singleton, fallback, factory, + provider, } } - pub async fn deploy_safe(&self, owners: Vec, threshold: usize) -> GnosisSafe { - let safe_proxy = GnosisSafeProxy::builder(&self.web3, self.singleton.address()) - .deploy() - .await - .unwrap(); - let safe = GnosisSafe::at(&self.web3, safe_proxy.address()); - safe.setup( - owners, - threshold.into(), - H160::default(), // delegate call - Bytes::default(), // delegate call bytes - self.fallback.address(), - H160::default(), // relayer payment token - 0.into(), // relayer payment amount - H160::default(), // relayer address - ) - .send() - .await - .unwrap(); + pub async fn deploy_safe( + &self, + owners: Vec, + threshold: usize, + ) -> GnosisSafe::Instance { + let provider = self + .provider + .with_signer(owners[0].account().clone().try_into_alloy().await.unwrap()); + let safe_proxy = + GnosisSafeProxy::Instance::deploy_builder(provider.clone(), *self.singleton.address()) + .deploy() + .await + .unwrap(); + let safe = GnosisSafe::Instance::new(safe_proxy, provider.clone()); + + contracts::alloy::tx!( + safe.setup( + owners + .into_iter() + .map(|owner| owner.address().into_alloy()) + .collect(), + U256::from(threshold), + Address::default(), // delegate call + Bytes::default(), // delegate call bytes + *self.fallback.address(), + Address::default(), // relayer payment token + U256::ZERO, // relayer payment amount + Address::default(), // relayer address + ) + ); + safe } } @@ -93,13 +116,13 @@ impl Infrastructure { /// Wrapper over a deployed Safe. pub struct Safe { chain_id: U256, - contract: GnosisSafe, + contract: GnosisSafe::Instance, owner: TestAccount, } impl Safe { /// Return a wrapper at the deployed address. - pub fn deployed(chain_id: U256, contract: GnosisSafe, owner: TestAccount) -> Self { + pub fn deployed(chain_id: U256, contract: GnosisSafe::Instance, owner: TestAccount) -> Self { Self { chain_id, contract, @@ -108,14 +131,14 @@ impl Safe { } /// Deploy a Safe with a single owner. - pub async fn deploy(owner: TestAccount, web3: &Web3) -> Self { + pub async fn deploy(owner: TestAccount, alloy: AlloyProvider) -> Self { // Infrastructure contracts can in principle be reused for any new deployments, // but it leads to boilerplate code that we don't need. Redeploying the // infrastructure contracts every time should have no appreciable impact in the // tests. - let infra = Infrastructure::new(web3).await; - let chain_id = web3.eth().chain_id().await.unwrap(); - let contract = infra.deploy_safe(vec![owner.address()], 1).await; + let chain_id = U256::from(alloy.get_chain_id().await.unwrap()); + let infra = Infrastructure::new(alloy).await; + let contract = infra.deploy_safe(vec![owner.clone()], 1).await; Self { chain_id, contract, @@ -128,26 +151,47 @@ impl Safe { tx: ethcontract::dyns::DynMethodBuilder, ) { let contract = &self.contract; - tx_safe!(self.owner.account(), contract, tx); + let TransactionBuilder { + data, value, to, .. + } = tx.tx; + + contracts::alloy::tx!( + contract.execTransaction( + to.unwrap().into_alloy(), + value.unwrap_or_default().into_alloy(), + alloy::primitives::Bytes::from(data.unwrap_or_default().0), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + Default::default(), + crate::setup::safe::gnosis_safe_prevalidated_signature( + self.owner.address().into_alloy(), + ), + ), + self.owner.address().into_alloy() + ); } /// Returns the address of the Safe. - pub fn address(&self) -> H160 { - self.contract.address() + pub fn address(&self) -> alloy::primitives::Address { + *self.contract.address() } /// Returns the next nonce to be used. - pub async fn nonce(&self) -> U256 { + pub async fn nonce(&self) -> alloy::primitives::U256 { self.contract.nonce().call().await.unwrap() } /// Returns a signed transaction ready for execution. pub fn sign_transaction( &self, - to: H160, + to: alloy::primitives::Address, data: Vec, - nonce: U256, - ) -> ethcontract::dyns::DynMethodBuilder { + nonce: alloy::primitives::U256, + ) -> alloy::contract::CallBuilder<&contracts::alloy::Provider, PhantomData> + { let signature = self.sign({ // `SafeTx` struct hash computation ported from the Safe Solidity code: // @@ -158,9 +202,9 @@ impl Safe { // "bb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8" )); - buffer[44..64].copy_from_slice(to.as_bytes()); + buffer[44..64].copy_from_slice(to.as_slice()); buffer[96..128].copy_from_slice(&signing::keccak256(&data)); - nonce.to_big_endian(&mut buffer[320..352]); + nonce.copy_be_bytes_to(&mut buffer[320..352]); // Since the [`sign_transaction`] transaction method only accepts // a limited number of parameters and defaults to 0 for the others, @@ -170,17 +214,17 @@ impl Safe { signing::keccak256(&buffer) }); - self.contract.exec_transaction( + self.contract.execTransaction( to, Default::default(), // value - Bytes(data), + alloy::primitives::Bytes::from(data), Default::default(), // operation (= CALL) Default::default(), // safe tx gas Default::default(), // base gas Default::default(), // gas price Default::default(), // gas token Default::default(), // refund receiver - Bytes(signature), + alloy::primitives::Bytes::from(signature), ) } @@ -228,8 +272,8 @@ impl Safe { // "47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218" )); - self.chain_id.to_big_endian(&mut buffer[32..64]); - buffer[76..96].copy_from_slice(self.contract.address().as_bytes()); + self.chain_id.copy_be_bytes_to(&mut buffer[32..64]); + buffer[76..96].copy_from_slice(self.contract.address().as_slice()); DomainSeparator(signing::keccak256(&buffer)) } @@ -260,42 +304,11 @@ impl Safe { /// See: /// - Documentation: /// - Code: -pub fn gnosis_safe_prevalidated_signature(owner: H160) -> Bytes> { +pub fn gnosis_safe_prevalidated_signature( + owner: alloy::primitives::Address, +) -> alloy::primitives::Bytes { let mut signature = vec![0; 65]; - signature[12..32].copy_from_slice(owner.as_bytes()); + signature[12..32].copy_from_slice(owner.as_slice()); signature[64] = 1; - Bytes(signature) -} - -/// Generate an owner signature for EIP-1271. -/// -/// The Gnosis Safe uses off-chain ECDSA signatures from its owners as the -/// signature bytes when validating EIP-1271 signatures. Specifically, it -/// expects a signed EIP-712 `SafeMessage(bytes message)` (where `message` is -/// the 32-byte hash of the data being verified). -/// -/// See: -/// - Code: -pub async fn gnosis_safe_eip1271_signature( - key: SecretKeyRef<'_>, - safe: &GnosisSafe, - message_hash: H256, -) -> Vec { - let handler = - GnosisSafeCompatibilityFallbackHandler::at(&safe.raw_instance().web3(), safe.address()); - - let signing_hash = handler - .get_message_hash(Bytes(message_hash.as_bytes().to_vec())) - .call() - .await - .unwrap(); - - let signature = key.sign(&signing_hash.0, None).unwrap(); - - let mut bytes = vec![0u8; 65]; - bytes[0..32].copy_from_slice(signature.r.as_bytes()); - bytes[32..64].copy_from_slice(signature.s.as_bytes()); - bytes[64] = signature.v as _; - - bytes + alloy::primitives::Bytes::from(signature) } diff --git a/crates/e2e/tests/e2e/app_data.rs b/crates/e2e/tests/e2e/app_data.rs index ca2c3518d8..39002f47d8 100644 --- a/crates/e2e/tests/e2e/app_data.rs +++ b/crates/e2e/tests/e2e/app_data.rs @@ -1,7 +1,6 @@ use { app_data::{AppDataHash, hash_full_app_data}, e2e::{setup::*, tx}, - ethcontract::prelude::U256, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, diff --git a/crates/e2e/tests/e2e/app_data_signer.rs b/crates/e2e/tests/e2e/app_data_signer.rs index ab8bcce8fa..18bdd6ea86 100644 --- a/crates/e2e/tests/e2e/app_data_signer.rs +++ b/crates/e2e/tests/e2e/app_data_signer.rs @@ -1,9 +1,9 @@ use { e2e::{ - setup::{safe::Safe, *}, + setup::{OnchainComponents, Services, TestAccount, run_test, safe::Safe, to_wei}, tx, }, - ethcontract::{H160, prelude::U256}, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, signature::EcdsaSigningScheme, @@ -66,13 +66,13 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { services.start_protocol(solver).await; // Rejected: app data with different signer. - let full_app_data = full_app_data_with_signer(adversary.address()); + let full_app_data = full_app_data_with_signer(adversary.address().into_alloy()); let order1 = sign(create_order(full_app_data), &trader); let err = services.create_order(&order1).await.unwrap_err(); assert!(dbg!(err).1.contains("WrongOwner")); // Accepted: app data with correct signer. - let full_app_data = full_app_data_with_signer(trader.address()); + let full_app_data = full_app_data_with_signer(trader.address().into_alloy()); let order2 = sign(create_order(full_app_data.clone()), &trader); let uid = services.create_order(&order2).await.unwrap(); assert!(matches!(services.get_order(&uid).await, Ok(..))); @@ -91,8 +91,8 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { // EIP-1271 - let safe = Safe::deploy(safe_owner.clone(), &web3).await; - token_a.mint(safe.address(), to_wei(10)).await; + let safe = Safe::deploy(safe_owner.clone(), web3.alloy.clone()).await; + token_a.mint(safe.address().into_legacy(), to_wei(10)).await; safe.exec_call(token_a.approve(onchain.contracts().allowance, to_wei(10))) .await; @@ -103,15 +103,15 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { assert!(matches!(services.create_order(&order4).await, Ok(..))); // Rejected: from and signer are inconsistent. - let full_app_data = full_app_data_with_signer(adversary.address()); + let full_app_data = full_app_data_with_signer(adversary.address().into_alloy()); let mut order5 = create_order(full_app_data); - order5.from = Some(safe.address()); + order5.from = Some(safe.address().into_legacy()); safe.sign_order(&mut order5, &onchain); let err = services.create_order(&order5).await.unwrap_err(); assert!(err.1.contains("AppdataFromMismatch")); } -fn full_app_data_with_signer(signer: H160) -> OrderCreationAppData { +fn full_app_data_with_signer(signer: alloy::primitives::Address) -> OrderCreationAppData { let app_data = format!("{{\"metadata\": {{\"signer\": \"{signer:?}\"}}}}"); OrderCreationAppData::Full { full: app_data.to_string(), diff --git a/crates/e2e/tests/e2e/banned_users.rs b/crates/e2e/tests/e2e/banned_users.rs index cac9fb04ee..909b20a2c6 100644 --- a/crates/e2e/tests/e2e/banned_users.rs +++ b/crates/e2e/tests/e2e/banned_users.rs @@ -11,7 +11,7 @@ use { }, tx, }, - ethcontract::{H160, prelude::U256}, + ethcontract::H160, ethrpc::Web3, model::quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, reqwest::StatusCode, diff --git a/crates/e2e/tests/e2e/buffers.rs b/crates/e2e/tests/e2e/buffers.rs index f33b31c614..44471d9c5a 100644 --- a/crates/e2e/tests/e2e/buffers.rs +++ b/crates/e2e/tests/e2e/buffers.rs @@ -1,6 +1,5 @@ use { e2e::{setup::*, tx}, - ethcontract::prelude::U256, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, diff --git a/crates/e2e/tests/e2e/eth_integration.rs b/crates/e2e/tests/e2e/eth_integration.rs index 1c02580760..8ada5978b2 100644 --- a/crates/e2e/tests/e2e/eth_integration.rs +++ b/crates/e2e/tests/e2e/eth_integration.rs @@ -1,6 +1,6 @@ use { e2e::{setup::*, tx}, - ethcontract::prelude::{Address, U256}, + ethcontract::prelude::Address, model::{ order::{BUY_ETH_ADDRESS, OrderCreation, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, diff --git a/crates/e2e/tests/e2e/eth_safe.rs b/crates/e2e/tests/e2e/eth_safe.rs index 67fe96d87b..b54f756ce3 100644 --- a/crates/e2e/tests/e2e/eth_safe.rs +++ b/crates/e2e/tests/e2e/eth_safe.rs @@ -1,9 +1,18 @@ use { + ::alloy::{primitives::U256, providers::Provider}, e2e::{ - setup::{safe::Safe, *}, + setup::{ + OnchainComponents, + Services, + TIMEOUT, + run_test, + safe::Safe, + to_wei, + wait_for_condition, + }, tx, }, - ethcontract::U256, + ethrpc::alloy::conversions::IntoLegacy, model::{ order::{BUY_ETH_ADDRESS, OrderCreation, OrderKind}, signature::{Signature, hashed_eip712_message}, @@ -23,7 +32,7 @@ async fn test(web3: Web3) { let [solver] = onchain.make_solvers(to_wei(10)).await; let [trader] = onchain.make_accounts(to_wei(10)).await; - let safe = Safe::deploy(trader.clone(), &web3).await; + let safe = Safe::deploy(trader.clone(), web3.alloy.clone()).await; let [token] = onchain .deploy_tokens_with_weth_uni_v2_pools(to_wei(1000), to_wei(1000)) .await; @@ -31,7 +40,7 @@ async fn test(web3: Web3) { token.mint(trader.address(), to_wei(4)).await; safe.exec_call(token.approve(onchain.contracts().allowance, to_wei(4))) .await; - token.mint(safe.address(), to_wei(4)).await; + token.mint(safe.address().into_legacy(), to_wei(4)).await; tx!( trader.account(), token.approve(onchain.contracts().allowance, to_wei(4)) @@ -45,13 +54,13 @@ async fn test(web3: Web3) { let balance = onchain .contracts() .weth - .balance_of(safe.address()) + .balance_of(safe.address().into_legacy()) .call() .await .unwrap(); assert_eq!(balance, 0.into()); let mut order = OrderCreation { - from: Some(safe.address()), + from: Some(safe.address().into_legacy()), sell_token: token.address(), sell_amount: to_wei(4), buy_token: BUY_ETH_ADDRESS, @@ -59,7 +68,7 @@ async fn test(web3: Web3) { valid_to: model::time::now_in_epoch_seconds() + 300, partially_fillable: true, kind: OrderKind::Sell, - receiver: Some(safe.address()), + receiver: Some(safe.address().into_legacy()), ..Default::default() }; order.signature = Signature::Eip1271(safe.sign_message(&hashed_eip712_message( @@ -71,10 +80,11 @@ async fn test(web3: Web3) { tracing::info!("Waiting for trade."); let trade_happened = || async { - let safe_balance = web3.eth().balance(safe.address(), None).await.unwrap(); + let safe_balance = web3.alloy.get_balance(safe.address()).await.unwrap(); // the balance is slightly less because of the fee - (3_899_000_000_000_000_000_u128..4_000_000_000_000_000_000_u128) - .contains(&safe_balance.as_u128()) + U256::from(3_899_000_000_000_000_000_u128) <= safe_balance + && safe_balance <= U256::from(4_000_000_000_000_000_000_u128) }; + wait_for_condition(TIMEOUT, trade_happened).await.unwrap(); } diff --git a/crates/e2e/tests/e2e/flashloans.rs b/crates/e2e/tests/e2e/flashloans.rs index 81265901f8..a7247081ea 100644 --- a/crates/e2e/tests/e2e/flashloans.rs +++ b/crates/e2e/tests/e2e/flashloans.rs @@ -17,7 +17,10 @@ use { tx, }, ethcontract::{H160, U256}, - ethrpc::Web3, + ethrpc::{ + Web3, + alloy::conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, signature::{Signature, hashed_eip712_message}, @@ -47,7 +50,7 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { let [solver] = onchain.make_solvers_forked(to_wei(1)).await; let [trader] = onchain.make_accounts(to_wei(1)).await; - let trader = Safe::deploy(trader.clone(), &web3).await; + let trader = Safe::deploy(trader.clone(), web3.alloy.clone()).await; // AAVE token tracking how much USDC is deposited by a user let ausdc = addr!("98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c"); @@ -67,7 +70,10 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { let collateral_amount = to_wei_with_exp(50_000, 6); tx!( usdc_whale, - usdc.transfer(trader.address(), collateral_amount + U256::from(1)) + usdc.transfer( + trader.address().into_legacy(), + collateral_amount + U256::from(1) + ) ); // approve vault relayer to take safe's sell tokens trader @@ -83,15 +89,17 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { // Deposit collateral trader .exec_call(aave_pool.supply( - usdc.address(), // token - collateral_amount, // amount - trader.address(), // on_behalf - 0, // referral code + usdc.address(), // token + collateral_amount, // amount + trader.address().into_legacy(), // on_behalf + 0, // referral code )) .await; // Exchange rate between USDC and aUSDC can differ from block to block. let slippage = 2; - assert!(balance(&web3, trader.address(), ausdc).await >= collateral_amount - slippage); + assert!( + balance(&web3, trader.address().into_legacy(), ausdc).await >= collateral_amount - slippage + ); tracing::info!("wait a bit to make `borrow()` call work"); tokio::time::sleep(Duration::from_secs(1)).await; @@ -104,7 +112,7 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { flashloan_amount, // borrowed amount 2.into(), // variable interest rate mode 0, // referral code - trader.address(), // on_behalf + trader.address().into_legacy(), // on_behalf )) .await; @@ -128,13 +136,13 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { // 3. withdraw the collateral it can be sold for WETH (2nd pre-hook) let app_data = { let repay_tx = trader.sign_transaction( - aave_pool.address(), + aave_pool.address().into_alloy(), aave_pool .repay( onchain.contracts().weth.address(), to_wei(1), 2.into(), - trader.address(), + trader.address().into_legacy(), ) .tx .data @@ -144,15 +152,19 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { current_safe_nonce, ); let withdraw_tx = trader.sign_transaction( - aave_pool.address(), + aave_pool.address().into_alloy(), aave_pool - .withdraw(usdc.address(), collateral_amount, trader.address()) + .withdraw( + usdc.address(), + collateral_amount, + trader.address().into_legacy(), + ) .tx .data .unwrap() .0 .clone(), - current_safe_nonce + U256::from(1), + current_safe_nonce + alloy::primitives::U256::ONE, ); let app_data = format!( r#"{{ @@ -188,11 +200,11 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { flashloan_amount, // 1st pre-hook trader.address(), - hex::encode(&repay_tx.tx.data.unwrap().0), + hex::encode(repay_tx.calldata()), // 2nd pre-hook // ~200K gas trader.address(), - hex::encode(&withdraw_tx.tx.data.unwrap().0), + hex::encode(withdraw_tx.calldata()), // signer trader.address(), ); @@ -245,7 +257,7 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { services.start_protocol(solver.clone()).await; assert_eq!( - balance(&web3, trader.address(), usdc.address()).await, + balance(&web3, trader.address().into_legacy(), usdc.address()).await, 1.into() ); tracing::info!("trader just has 1 atom of USDC before placing order"); @@ -271,7 +283,7 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { // Because the trader sold some of their collateral to repay their debt // (~4900 USDC for ~1 WETH) they have that much less `USDC` compared to // the original collateral. - let trader_usdc = balance(&web3, trader.address(), usdc.address()).await; + let trader_usdc = balance(&web3, trader.address().into_legacy(), usdc.address()).await; assert!(trader_usdc > to_wei_with_exp(45_000, 6)); tracing::info!("trader got majority of collateral back"); @@ -279,7 +291,7 @@ async fn forked_mainnet_repay_debt_with_collateral_of_safe(web3: Web3) { assert!(settlement_weth < 300_000_000u128.into()); tracing::info!("settlement contract only has dust amounts of WETH"); - assert!(balance(&web3, trader.address(), ausdc).await < 10_000.into()); + assert!(balance(&web3, trader.address().into_legacy(), ausdc).await < 10_000.into()); tracing::info!("trader only has dust of aUSDC"); // Check that the solver address is stored in the settlement table diff --git a/crates/e2e/tests/e2e/hooks.rs b/crates/e2e/tests/e2e/hooks.rs index 905a555ff0..5dfd68d9cd 100644 --- a/crates/e2e/tests/e2e/hooks.rs +++ b/crates/e2e/tests/e2e/hooks.rs @@ -1,12 +1,23 @@ use { + alloy::providers::Provider, app_data::Hook, - contracts::GnosisSafe, e2e::{ - setup::{safe::Safe, *}, + setup::{ + OnchainComponents, + Services, + TIMEOUT, + hook_for_transaction, + onchain_components, + run_test, + safe::Safe, + to_wei, + wait_for_condition, + }, tx, tx_value, }, - ethcontract::{Bytes, H160, U256}, + ethcontract::U256, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, @@ -239,67 +250,74 @@ async fn allowance(web3: Web3) { async fn signature(web3: Web3) { let mut onchain = OnchainComponents::deploy(web3.clone()).await; - let chain_id = web3.eth().chain_id().await.unwrap(); + let chain_id = alloy::primitives::U256::from(web3.alloy.get_chain_id().await.unwrap()); let [solver] = onchain.make_solvers(to_wei(1)).await; let [trader] = onchain.make_accounts(to_wei(1)).await; - let safe_infra = safe::Infrastructure::new(&web3).await; + let safe_infra = onchain_components::safe::Infrastructure::new(web3.alloy.clone()).await; // Prepare the Safe creation transaction, but don't execute it! This will // be executed as a pre-hook. - let safe_creation_builder = safe_infra.factory.create_proxy( - safe_infra.singleton.address(), - ethcontract::Bytes( - safe_infra - .singleton - .setup( - vec![trader.address()], // owners - 1.into(), // threshold - H160::default(), // delegate call - Bytes::default(), // delegate call bytes - safe_infra.fallback.address(), - H160::default(), // relayer payment token - 0.into(), // relayer payment amount - H160::default(), // relayer address - ) - .tx - .data - .unwrap() - .0, - ), + let safe_creation_builder = safe_infra.factory.createProxy( + *safe_infra.singleton.address(), + safe_infra + .singleton + .setup( + vec![trader.address().into_alloy()], // owners + alloy::primitives::U256::ONE, // threshold + alloy::primitives::Address::default(), // delegate call + alloy::primitives::Bytes::default(), // delegate call bytes + *safe_infra.fallback.address(), + alloy::primitives::Address::default(), // relayer payment token + alloy::primitives::U256::ZERO, // relayer payment amount + alloy::primitives::Address::default(), // relayer address + ) + .calldata() + .clone(), ); - let safe_creation = hook_for_transaction(safe_creation_builder.tx.clone()).await; + let safe_creation = + onchain_components::alloy::hook_for_transaction(safe_creation_builder.clone()) + .await + .unwrap(); // Create a contract instance at the would-be address of the Safe we are // creating with the pre-hook. - let safe_address = safe_creation_builder.clone().view().call().await.unwrap(); + let safe_address = safe_creation_builder.clone().call().await.unwrap(); let safe = Safe::deployed( chain_id, - GnosisSafe::at(&web3, safe_address), + contracts::alloy::GnosisSafe::Instance::new(safe_address, web3.alloy.clone()), trader.clone(), ); let [token] = onchain .deploy_tokens_with_weth_uni_v2_pools(to_wei(100_000), to_wei(100_000)) .await; - token.mint(safe.address(), to_wei(5)).await; + token.mint(safe.address().into_legacy(), to_wei(5)).await; // Sign an approval transaction for trading. This will be at nonce 0 because // it is the first transaction evah! let approval_builder = safe.sign_transaction( - token.address(), + token.address().into_alloy(), token .approve(onchain.contracts().allowance, to_wei(5)) .tx .data .unwrap() .0, - 0.into(), + alloy::primitives::U256::ZERO, ); + let call_data = approval_builder.calldata().to_vec(); + let target = approval_builder + .into_transaction_request() + .to + .unwrap() + .into_to() + .unwrap() + .into_legacy(); let approval = Hook { - target: approval_builder.tx.to.unwrap(), - call_data: approval_builder.tx.data.unwrap().0, + target, + call_data, // The contract isn't deployed, so we can't estimate this. Instead, we // just guess an amount that should be high enough. gas_limit: 100_000, @@ -311,7 +329,7 @@ async fn signature(web3: Web3) { // Place Orders let mut order = OrderCreation { - from: Some(safe.address()), + from: Some(safe.address().into_legacy()), // Quotes for trades where the pre-interactions deploy a contract // at the `from` address currently can't be verified. // To not throw an error because we can't get a verifiable quote @@ -344,17 +362,25 @@ async fn signature(web3: Web3) { services.create_order(&order).await.unwrap(); onchain.mint_block().await; - let balance = token.balance_of(safe.address()).call().await.unwrap(); + let balance = token + .balance_of(safe.address().into_legacy()) + .call() + .await + .unwrap(); assert_eq!(balance, to_wei(5)); // Check that the Safe really hasn't been deployed yet. - let code = web3.eth().code(safe.address(), None).await.unwrap(); + let code = web3 + .eth() + .code(safe.address().into_legacy(), None) + .await + .unwrap(); assert_eq!(code.0.len(), 0); tracing::info!("Waiting for trade."); let trade_happened = || async { token - .balance_of(safe.address()) + .balance_of(safe.address().into_legacy()) .call() .await .unwrap() @@ -367,14 +393,18 @@ async fn signature(web3: Web3) { let balance = onchain .contracts() .weth - .balance_of(safe.address()) + .balance_of(safe.address().into_legacy()) .call() .await .unwrap(); assert!(balance >= order.buy_amount); // Check Safe was deployed - let code = web3.eth().code(safe.address(), None).await.unwrap(); + let code = web3 + .eth() + .code(safe.address().into_legacy(), None) + .await + .unwrap(); assert_ne!(code.0.len(), 0); tracing::info!("Waiting for auction to be cleared."); @@ -489,69 +519,74 @@ async fn partial_fills(web3: Web3) { async fn quote_verification(web3: Web3) { let mut onchain = OnchainComponents::deploy(web3.clone()).await; - let chain_id = web3.eth().chain_id().await.unwrap(); + let chain_id = alloy::primitives::U256::from(web3.alloy.get_chain_id().await.unwrap()); let [trader] = onchain.make_accounts(to_wei(1)).await; let [solver] = onchain.make_solvers(to_wei(1)).await; - let safe_infra = safe::Infrastructure::new(&web3).await; + let safe_infra = onchain_components::safe::Infrastructure::new(web3.alloy.clone()).await; // Prepare the Safe creation transaction, but don't execute it! This will // be executed as a pre-hook. - let safe_creation_builder = safe_infra.factory.create_proxy( - safe_infra.singleton.address(), - ethcontract::Bytes( - safe_infra - .singleton - .setup( - vec![trader.address()], // owners - 1.into(), // threshold - H160::default(), // delegate call - Bytes::default(), // delegate call bytes - safe_infra.fallback.address(), - H160::default(), // relayer payment token - 0.into(), // relayer payment amount - H160::default(), // relayer address - ) - .tx - .data - .unwrap() - .0, - ), + let safe_creation_builder = safe_infra.factory.createProxy( + *safe_infra.singleton.address(), + safe_infra + .singleton + .setup( + vec![trader.address().into_alloy()], // owners + alloy::primitives::U256::ONE, // threshold + alloy::primitives::Address::default(), // delegate call + alloy::primitives::Bytes::default(), // delegate call bytes + *safe_infra.fallback.address(), + alloy::primitives::Address::default(), // relayer payment token + alloy::primitives::U256::ZERO, // relayer payment amount + alloy::primitives::Address::default(), // relayer address + ) + .calldata() + .clone(), ); - let safe_address = safe_creation_builder.clone().view().call().await.unwrap(); - safe_creation_builder.clone().send().await.unwrap(); + let safe_address = safe_creation_builder.clone().call().await.unwrap(); + let first_account = *web3.alloy.get_accounts().await.unwrap().first().unwrap(); + contracts::alloy::tx!(safe_creation_builder, first_account); let safe = Safe::deployed( chain_id, - GnosisSafe::at(&web3, safe_address), + contracts::alloy::GnosisSafe::Instance::new(safe_address, web3.alloy.clone()), trader.clone(), ); let [token] = onchain .deploy_tokens_with_weth_uni_v2_pools(to_wei(100_000), to_wei(100_000)) .await; - token.mint(safe.address(), to_wei(5)).await; + token.mint(safe.address().into_legacy(), to_wei(5)).await; tx!( trader.account(), token.approve(onchain.contracts().allowance, to_wei(5)) ); - // Sign transaction transfering 5 token from the safe to the trader + // Sign transaction transferring 5 token from the safe to the trader // to fund the trade in a pre-hook. let transfer_builder = safe.sign_transaction( - token.address(), + token.address().into_alloy(), token .transfer(trader.address(), to_wei(5)) .tx .data .unwrap() .0, - 0.into(), + alloy::primitives::U256::ZERO, ); + let call_data = transfer_builder.calldata().to_vec(); + let target = transfer_builder + .into_transaction_request() + .to + .unwrap() + .into_to() + .unwrap() + .into_legacy(); let transfer = Hook { - target: transfer_builder.tx.to.unwrap(), - call_data: transfer_builder.tx.data.unwrap().0, + target, + call_data, gas_limit: 100_000, }; diff --git a/crates/e2e/tests/e2e/order_cancellation.rs b/crates/e2e/tests/e2e/order_cancellation.rs index d2168a1312..2a7d385a02 100644 --- a/crates/e2e/tests/e2e/order_cancellation.rs +++ b/crates/e2e/tests/e2e/order_cancellation.rs @@ -1,7 +1,6 @@ use { database::order_events::OrderEventLabel, e2e::{setup::*, tx}, - ethcontract::prelude::U256, model::{ order::{ CancellationPayload, diff --git a/crates/e2e/tests/e2e/place_order_with_quote.rs b/crates/e2e/tests/e2e/place_order_with_quote.rs index 6311c98ab0..efc259e782 100644 --- a/crates/e2e/tests/e2e/place_order_with_quote.rs +++ b/crates/e2e/tests/e2e/place_order_with_quote.rs @@ -1,7 +1,6 @@ use { driver::domain::eth::NonZeroU256, e2e::{nodes::local_node::TestNodeApi, setup::*, tx, tx_value}, - ethcontract::U256, model::{ order::{OrderCreation, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, diff --git a/crates/e2e/tests/e2e/quoting.rs b/crates/e2e/tests/e2e/quoting.rs index 74a78c607a..34ee38ee82 100644 --- a/crates/e2e/tests/e2e/quoting.rs +++ b/crates/e2e/tests/e2e/quoting.rs @@ -4,7 +4,6 @@ use { tx, tx_value, }, - ethcontract::prelude::U256, futures::FutureExt, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, diff --git a/crates/e2e/tests/e2e/smart_contract_orders.rs b/crates/e2e/tests/e2e/smart_contract_orders.rs index d2d8b622fb..f56aa8fa67 100644 --- a/crates/e2e/tests/e2e/smart_contract_orders.rs +++ b/crates/e2e/tests/e2e/smart_contract_orders.rs @@ -4,6 +4,7 @@ use { tx, }, ethcontract::{Bytes, H160, U256}, + ethrpc::alloy::conversions::IntoLegacy, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind, OrderStatus, OrderUid}, signature::Signature, @@ -30,12 +31,12 @@ async fn smart_contract_orders(web3: Web3) { let [solver] = onchain.make_solvers(to_wei(1)).await; let [trader] = onchain.make_accounts(to_wei(1)).await; - let safe = Safe::deploy(trader, &web3).await; + let safe = Safe::deploy(trader, web3.alloy.clone()).await; let [token] = onchain .deploy_tokens_with_weth_uni_v2_pools(to_wei(100_000), to_wei(100_000)) .await; - token.mint(safe.address(), to_wei(10)).await; + token.mint(safe.address().into_legacy(), to_wei(10)).await; // Approve GPv2 for trading safe.exec_call(token.approve(onchain.contracts().allowance, to_wei(10))) @@ -58,7 +59,7 @@ async fn smart_contract_orders(web3: Web3) { // Check that we can't place invalid orders. let orders = [ OrderCreation { - from: Some(safe.address()), + from: Some(safe.address().into_legacy()), signature: Signature::Eip1271(b"invalid signature".to_vec()), ..order_template.clone() }, @@ -76,7 +77,7 @@ async fn smart_contract_orders(web3: Web3) { // Place orders let orders = [ OrderCreation { - from: Some(safe.address()), + from: Some(safe.address().into_legacy()), signature: Signature::Eip1271(signature1271), ..order_template.clone() }, @@ -84,7 +85,7 @@ async fn smart_contract_orders(web3: Web3) { app_data: OrderCreationAppData::Full { full: "{\"salt\": \"second\"}".to_string(), }, - from: Some(safe.address()), + from: Some(safe.address().into_legacy()), signature: Signature::PreSign, ..order_template.clone() }, @@ -129,14 +130,14 @@ async fn smart_contract_orders(web3: Web3) { tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { let token_balance = token - .balance_of(safe.address()) + .balance_of(safe.address().into_legacy()) .call() .await .expect("Couldn't fetch token balance"); let weth_balance = onchain .contracts() .weth - .balance_of(safe.address()) + .balance_of(safe.address().into_legacy()) .call() .await .expect("Couldn't fetch native token balance"); diff --git a/crates/e2e/tests/e2e/submission.rs b/crates/e2e/tests/e2e/submission.rs index 32578911db..9507a645e5 100644 --- a/crates/e2e/tests/e2e/submission.rs +++ b/crates/e2e/tests/e2e/submission.rs @@ -1,6 +1,6 @@ use { e2e::{nodes::local_node::TestNodeApi, setup::*, tx, tx_value}, - ethcontract::{BlockId, H160, H256, U256}, + ethcontract::{BlockId, H160, H256}, futures::{Stream, StreamExt}, model::{ order::{OrderCreation, OrderKind}, diff --git a/crates/e2e/tests/e2e/tracking_insufficient_funds.rs b/crates/e2e/tests/e2e/tracking_insufficient_funds.rs index ef46087336..3996a2498b 100644 --- a/crates/e2e/tests/e2e/tracking_insufficient_funds.rs +++ b/crates/e2e/tests/e2e/tracking_insufficient_funds.rs @@ -1,7 +1,6 @@ use { database::order_events::{OrderEvent, OrderEventLabel}, e2e::{setup::*, tx, tx_value}, - ethcontract::U256, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, diff --git a/crates/e2e/tests/e2e/uncovered_order.rs b/crates/e2e/tests/e2e/uncovered_order.rs index 3d17c86beb..2ae1102c61 100644 --- a/crates/e2e/tests/e2e/uncovered_order.rs +++ b/crates/e2e/tests/e2e/uncovered_order.rs @@ -1,6 +1,5 @@ use { e2e::{setup::*, tx, tx_value}, - ethcontract::U256, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, diff --git a/crates/e2e/tests/e2e/vault_balances.rs b/crates/e2e/tests/e2e/vault_balances.rs index aa62b50e85..06a7e6d09a 100644 --- a/crates/e2e/tests/e2e/vault_balances.rs +++ b/crates/e2e/tests/e2e/vault_balances.rs @@ -1,6 +1,5 @@ use { e2e::{setup::*, tx}, - ethcontract::prelude::U256, model::{ order::{OrderCreation, OrderKind, SellTokenSource}, signature::EcdsaSigningScheme, diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index 03de4f5c3f..09f226e8b0 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -6,17 +6,20 @@ edition = "2024" license = "GPL-3.0-or-later" [lib] +doctest = false name = "ethrpc" path = "src/lib.rs" -doctest = false [dependencies] alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signers", "signer-aws", "signer-local", "eips"] } anyhow = { workspace = true } async-trait = { workspace = true } +contracts = { workspace = true } +ethcontract = { workspace = true } futures = { workspace = true } hex = { workspace = true } hex-literal = { workspace = true } +itertools = { workspace = true } mockall = { workspace = true } observe = { workspace = true } primitive-types = { workspace = true } @@ -28,17 +31,17 @@ serde = { workspace = true } serde_json = { workspace = true } tokio = { workspace = true, features = [] } tokio-stream = { workspace = true } -web3 = { workspace = true } -contracts = { workspace = true } -ethcontract = { workspace = true } +tower = { workspace = true } tracing = { workspace = true } url = { workspace = true } -itertools = { workspace = true } -tower = { workspace = true } +web3 = { workspace = true } [dev-dependencies] maplit = { workspace = true } testlib = { workspace = true } +[features] +test-util = [] + [lints] workspace = true From 37e7465cf2bc2159fcea6cc9c102713c43691a99 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 8 Sep 2025 15:47:21 +0300 Subject: [PATCH 011/112] Get SC ABI functions (#3638) Adds the ability to retrieve SC ABI functions that are required in some cases to encode some inputs/outputs. Will be used in some tests in follow-up PRs. --- crates/contracts/src/alloy.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 6e11b85bfd..582c1a588d 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -122,7 +122,7 @@ macro_rules! bindings { std::sync::LazyLock, anyhow::{anyhow, Result}, alloy::{ - json_abi::{ContractObject, JsonAbi}, + json_abi::{ContractObject, Function, JsonAbi}, primitives::Selector, providers::DynProvider, }, @@ -148,6 +148,14 @@ macro_rules! bindings { Ok(funcs.iter().map(|f| f.selector()).collect()) } + /// Return all abi function overloads by *name*. + pub fn abi_functions_by_name(name: &str) -> Result> { + let Some(funcs) = ABI.functions.get(name) else { + return Err(anyhow!("no function named `{name}` in ABI")); + }; + Ok(funcs.clone()) + } + $( use { std::collections::HashMap, @@ -294,4 +302,13 @@ mod tests { assert!(result2.is_err()); assert!(result3.is_err()); } + + #[test] + fn test_get_function_valid() { + let functions = ChainalysisOracle::abi_functions_by_name("isSanctioned").unwrap(); + assert_eq!(functions.len(), 1); + + let function = &functions[0]; + assert_eq!(function.name, "isSanctioned"); + } } From a67e30e17d25f56c1e91666fca014a588c1f48b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 9 Sep 2025 08:42:38 +0100 Subject: [PATCH 012/112] Use alloy's SimpleNonceManager (#3639) --- crates/ethrpc/src/alloy/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/ethrpc/src/alloy/mod.rs b/crates/ethrpc/src/alloy/mod.rs index cfefe9002b..0524f467c9 100644 --- a/crates/ethrpc/src/alloy/mod.rs +++ b/crates/ethrpc/src/alloy/mod.rs @@ -22,7 +22,12 @@ pub fn provider(url: &str) -> AlloyProvider { .layer(InstrumentationLayer) .layer(BatchCallLayer::new(Default::default())) .http(url.parse().unwrap()); - ProviderBuilder::new().connect_client(rpc).erased() + ProviderBuilder::new() + // will query the node for the nonce every time that it is needed + // adds overhead but makes working with alloy/ethcontract at the same time much simpler + .with_simple_nonce_management() + .connect_client(rpc) + .erased() } pub trait ProviderSignerExt { @@ -44,6 +49,7 @@ impl ProviderSignerExt for AlloyProvider { ProviderBuilder::new() .wallet(wallet) + .with_simple_nonce_management() .connect_client(client) .erased() } From e04b5c43496742c1976558800506a414628a5e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 9 Sep 2025 09:39:23 +0100 Subject: [PATCH 013/112] Add `exec_alloy_call` to Safe (#3641) --- Cargo.lock | 15 +++++++ crates/e2e/Cargo.toml | 2 +- .../e2e/src/setup/onchain_components/safe.rs | 41 +++++++++++++------ 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 175a5f2a93..02d65ce03c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,11 +103,14 @@ dependencies = [ "alloy-network", "alloy-provider", "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", "alloy-signer", "alloy-signer-aws", "alloy-signer-local", "alloy-transport", "alloy-transport-http", + "alloy-trie", ] [[package]] @@ -445,6 +448,18 @@ dependencies = [ "wasmtimer", ] +[[package]] +name = "alloy-rpc-types" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39676beaa50db545cf15447fc94ec5513b64e85a48357a0625b9a04aef08a910" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-rpc-types-any" version = "1.0.30" diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index fa7e446822..4ee896c7cd 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -9,7 +9,7 @@ edition = "2024" license = "MIT OR Apache-2.0" [dependencies] -alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signers", "signer-local"] } +alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "rpc-types", "transports", "reqwest", "signers", "signer-local"] } app-data = { workspace = true } anyhow = { workspace = true } autopilot = { workspace = true } diff --git a/crates/e2e/src/setup/onchain_components/safe.rs b/crates/e2e/src/setup/onchain_components/safe.rs index e9e545ee39..a6c8b7bed7 100644 --- a/crates/e2e/src/setup/onchain_components/safe.rs +++ b/crates/e2e/src/setup/onchain_components/safe.rs @@ -3,6 +3,7 @@ use { alloy::{ primitives::{Address, Bytes, U256}, providers::Provider, + rpc::types::TransactionRequest, }, contracts::alloy::{ GnosisSafe::{self, GnosisSafe::execTransactionCall}, @@ -146,20 +147,12 @@ impl Safe { } } - pub async fn exec_call( - &self, - tx: ethcontract::dyns::DynMethodBuilder, - ) { - let contract = &self.contract; - let TransactionBuilder { - data, value, to, .. - } = tx.tx; - + async fn exec_alloy_tx(&self, to: Address, value: U256, calldata: Bytes) { contracts::alloy::tx!( - contract.execTransaction( - to.unwrap().into_alloy(), - value.unwrap_or_default().into_alloy(), - alloy::primitives::Bytes::from(data.unwrap_or_default().0), + self.contract.execTransaction( + to, + value, + calldata, Default::default(), Default::default(), Default::default(), @@ -174,6 +167,28 @@ impl Safe { ); } + pub async fn exec_call( + &self, + tx: ethcontract::dyns::DynMethodBuilder, + ) { + let TransactionBuilder { + data, value, to, .. + } = tx.tx; + self.exec_alloy_tx( + to.unwrap().into_alloy(), + value.unwrap_or_default().into_alloy(), + alloy::primitives::Bytes::from(data.unwrap_or_default().0), + ) + .await; + } + + pub async fn exec_alloy_call(&self, tx: TransactionRequest) { + let to = tx.to.unwrap().into_to().unwrap(); + let value = tx.value.unwrap_or_default(); + let data = tx.input.input().unwrap_or_default().to_owned(); + self.exec_alloy_tx(to, value, data).await; + } + /// Returns the address of the Safe. pub fn address(&self) -> alloy::primitives::Address { *self.contract.address() From 9c3c7a8c54b65705c23546dfa9d46336418a0096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 9 Sep 2025 09:54:46 +0100 Subject: [PATCH 014/112] [TRIVIAL] Add `send_and_watch` extensions (#3640) Co-authored-by: ilya --- crates/ethrpc/Cargo.toml | 2 +- crates/ethrpc/src/alloy/mod.rs | 49 ++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index 09f226e8b0..7fb65696bf 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -11,7 +11,7 @@ name = "ethrpc" path = "src/lib.rs" [dependencies] -alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "transports", "reqwest", "signers", "signer-aws", "signer-local", "eips"] } +alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "rpc-types", "transports", "reqwest", "signers", "signer-aws", "signer-local", "eips"] } anyhow = { workspace = true } async-trait = { workspace = true } contracts = { workspace = true } diff --git a/crates/ethrpc/src/alloy/mod.rs b/crates/ethrpc/src/alloy/mod.rs index 0524f467c9..8b08fda6cc 100644 --- a/crates/ethrpc/src/alloy/mod.rs +++ b/crates/ethrpc/src/alloy/mod.rs @@ -5,12 +5,19 @@ mod instrumentation; use { crate::AlloyProvider, alloy::{ - network::EthereumWallet, + contract::{CallBuilder, CallDecoder}, + network::{EthereumWallet, Network}, + primitives::{FixedBytes, TxHash}, providers::{Provider, ProviderBuilder}, - rpc::client::{ClientBuilder, RpcClient}, + rpc::{ + client::{ClientBuilder, RpcClient}, + types::TransactionRequest, + }, }, buffering::BatchCallLayer, instrumentation::{InstrumentationLayer, LabelingLayer}, + std::time::Duration, + tokio::time::timeout, }; pub use {conversions::Account, instrumentation::ProviderLabelingExt}; @@ -30,6 +37,8 @@ pub fn provider(url: &str) -> AlloyProvider { .erased() } +const DEFAULT_WATCH_TIMEOUT: Duration = Duration::from_secs(2); + pub trait ProviderSignerExt { /// Creates a new provider with the given signer. fn with_signer(&self, signer: Account) -> Self; @@ -54,3 +63,39 @@ impl ProviderSignerExt for AlloyProvider { .erased() } } + +pub trait ProviderExt { + /// Sends the transaction to the node and waits for confirmations. + /// + /// If confirmation takes longer than 25 seconds, the operation will + /// timeout. + fn send_and_watch( + &self, + tx: TransactionRequest, + ) -> impl Future>; +} + +impl ProviderExt for AlloyProvider { + async fn send_and_watch(&self, tx: TransactionRequest) -> anyhow::Result { + let pending = self.send_transaction(tx).await?; + let result = timeout(DEFAULT_WATCH_TIMEOUT, pending.watch()).await??; + Ok(result) + } +} + +pub trait CallBuilderExt { + /// Converts the current call into a [`TransactionRequest`], sends it to the + /// node and waits for confirmations. + /// + /// If confirmation takes longer than 25 seconds, the operation will + /// timeout. + fn send_and_watch(&self) -> impl Future>>; +} + +impl, D: CallDecoder, N: Network> CallBuilderExt for CallBuilder { + async fn send_and_watch(&self) -> anyhow::Result { + let pending = self.send().await?; + let result = timeout(DEFAULT_WATCH_TIMEOUT, pending.watch()).await??; + Ok(result) + } +} From 3d37a58b53ad228cff2d62dbc4004d129b92e79d Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Wed, 10 Sep 2025 10:34:43 +0200 Subject: [PATCH 015/112] Delay solve request parsing as much as possible (#3616) # Description Currently our axum `/solve` handler immediately parses the JSON request body into the DTO object. This is very convenient but also pretty suboptimal. Because the driver was built such that it can support multiple connected solvers the auction pre processing logic was written such that it only happens once per auction and the results get shared by all the solvers. That means all but 1 `/solve` requests per auction get fully parsed and then discarded. Also since that happens on the main tokio runtime (instead of a blocking task) every incoming request blocks the runtime for a considerable amount of time. # Changes - request handler now takes a raw `String` - parsing into the `dto` representation was moved into the auction pre-processor - also parsing happens in a task that is optimized for blocking - to detect whether a request is duplicated we simply to a string comparison on the requests (the autopilot sends identical requests to all drivers). It was determined via a benchmark that this is considerably faster than just parsing out the auction id from the request. - due to not having the `auction_id` available immediately we now init the tracing info span with `auction_id = field::Empty` and fill in the value immediately after parsing the request. There should be no important logs without the auction id present. - the `deadline` type of the `domain::Auction` needed to be changed because each solver may have different timeout settings which does not lend itself nicely to sharing the same auction struct. In fact sharing the same deadlines for all solvers was actually a bug that also gets resolved by this PR. Now we just store the auction deadline in the domain object and each solver can compute their own unique deadlines individually. ## How to test I ran a test on shadow mainnet which reduced the CPU time spent deserializing from `~40%` to `4%`. Before Screenshot 2025-09-01 at 14 49 11 After Screenshot 2025-09-01 at 14 49 24 Also the metrics show a ~50% reduction in CPU usage of the shadow mainnet driver Screenshot 2025-09-10 at 07 21 03 --- .../driver/src/domain/competition/auction.rs | 10 +- crates/driver/src/domain/competition/mod.rs | 44 +++++--- .../src/domain/competition/pre_processing.rs | 104 +++++++++++++----- crates/driver/src/domain/quote.rs | 2 +- crates/driver/src/infra/api/error.rs | 3 + crates/driver/src/infra/api/mod.rs | 9 +- crates/driver/src/infra/api/routes/mod.rs | 2 +- .../src/infra/api/routes/quote/dto/order.rs | 7 +- .../driver/src/infra/api/routes/quote/mod.rs | 2 +- .../src/infra/api/routes/solve/dto/mod.rs | 2 +- .../api/routes/solve/dto/solve_request.rs | 6 +- .../driver/src/infra/api/routes/solve/mod.rs | 36 ++---- crates/driver/src/infra/observe/mod.rs | 1 + crates/driver/src/infra/solver/dto/auction.rs | 3 +- crates/driver/src/infra/solver/mod.rs | 3 +- 15 files changed, 140 insertions(+), 94 deletions(-) diff --git a/crates/driver/src/domain/competition/auction.rs b/crates/driver/src/domain/competition/auction.rs index d98f740d35..f29fcbe4ad 100644 --- a/crates/driver/src/domain/competition/auction.rs +++ b/crates/driver/src/domain/competition/auction.rs @@ -6,7 +6,7 @@ use { liquidity, time, }, - infra::{Ethereum, blockchain}, + infra::{Ethereum, blockchain, solver::Timeouts}, }, std::collections::{HashMap, HashSet}, thiserror::Error, @@ -24,7 +24,7 @@ pub struct Auction { /// The tokens that are used in the orders of this auction. pub(crate) tokens: Tokens, pub(crate) gas_price: eth::GasPrice, - pub(crate) deadline: time::Deadline, + pub(crate) deadline: chrono::DateTime, pub(crate) surplus_capturing_jit_order_owners: HashSet, } @@ -33,7 +33,7 @@ impl Auction { id: Option, mut orders: Vec, tokens: impl Iterator, - deadline: time::Deadline, + deadline: chrono::DateTime, eth: &Ethereum, surplus_capturing_jit_order_owners: HashSet, ) -> Result { @@ -98,8 +98,8 @@ impl Auction { } /// The deadline for the driver to start sending solution to autopilot. - pub fn deadline(&self) -> time::Deadline { - self.deadline + pub fn deadline(&self, timeouts: Timeouts) -> time::Deadline { + time::Deadline::new(self.deadline, timeouts) } /// Prices used to convert token amounts to an equivalent amount of the diff --git a/crates/driver/src/domain/competition/mod.rs b/crates/driver/src/domain/competition/mod.rs index 2e1d5b522a..b105a983fa 100644 --- a/crates/driver/src/domain/competition/mod.rs +++ b/crates/driver/src/domain/competition/mod.rs @@ -28,7 +28,7 @@ use { cmp::Reverse, collections::{HashMap, HashSet, VecDeque}, sync::{Arc, Mutex}, - time::Duration, + time::{Duration, Instant}, }, tap::TapFallible, tokio::{ @@ -110,11 +110,18 @@ impl Competition { } /// Solve an auction as part of this competition. - #[instrument(skip_all)] - pub async fn solve(&self, auction: Auction) -> Result, Error> { - let auction = Arc::new(auction); - let tasks = self.fetcher.start_or_get_tasks_for_auction(auction.clone()); - let mut auction = Arc::unwrap_or_clone(auction); + pub async fn solve(&self, auction: Arc) -> Result, Error> { + let start = Instant::now(); + + let tasks = self + .fetcher + .start_or_get_tasks_for_auction(auction) + .await + .map_err(|err| { + tracing::error!(?err, "pre-processing auction failed"); + Error::MalformedRequest + })?; + let mut auction = Arc::unwrap_or_clone(tasks.auction.await); let settlement_contract = self.eth.contracts().settlement().address(); let solver_address = self.solver.account().address(); @@ -153,7 +160,7 @@ impl Competition { }) .await; - // We can tun bad token filtering and liquidity fetching in parallel + // We can run bad token filtering and liquidity fetching in parallel let (liquidity, auction) = tokio::join!( async { match self.solver.liquidity() { @@ -164,6 +171,13 @@ impl Competition { self.without_unsupported_orders(auction) ); + let elapsed = start.elapsed(); + metrics::get() + .auction_preprocessing + .with_label_values(&["total"]) + .observe(elapsed.as_secs_f64()); + tracing::debug!(?elapsed, "auction task execution time"); + let auction = &auction; // Fetch the solutions from the solver. @@ -177,7 +191,8 @@ impl Competition { } })?; - observe::postprocessing(&solutions, auction.deadline().driver()); + let deadline = auction.deadline(self.solver.timeouts()).driver(); + observe::postprocessing(&solutions, deadline); // Discard solutions that don't have unique ID. let mut ids = HashSet::new(); @@ -253,12 +268,9 @@ impl Competition { settlements.push(settlement); } }; - if tokio::time::timeout( - auction.deadline().driver().remaining().unwrap_or_default(), - future, - ) - .await - .is_err() + if tokio::time::timeout(deadline.remaining().unwrap_or_default(), future) + .await + .is_err() { observe::postprocessing_timed_out(&settlements); notify::postprocessing_timed_out(&self.solver, auction.id()) @@ -339,7 +351,7 @@ impl Competition { // Re-simulate the solution on every new block until the deadline ends to make // sure we actually submit a working solution close to when the winner // gets picked by the procotol. - if let Ok(remaining) = auction.deadline().driver().remaining() { + if let Ok(remaining) = deadline.remaining() { let score_ref = &mut score; let simulate_on_new_blocks = async move { let mut stream = @@ -870,4 +882,6 @@ pub enum Error { TooManyPendingSettlements, #[error("no valid orders found in the auction")] NoValidOrdersFound, + #[error("could not parse the request")] + MalformedRequest, } diff --git a/crates/driver/src/domain/competition/pre_processing.rs b/crates/driver/src/domain/competition/pre_processing.rs index 86c7f55aa2..58ccfcfe80 100644 --- a/crates/driver/src/domain/competition/pre_processing.rs +++ b/crates/driver/src/domain/competition/pre_processing.rs @@ -1,10 +1,11 @@ use { - super::{Auction, Order, auction, order}, + super::{Auction, Order, order}, crate::{ domain::{eth, liquidity}, - infra::{self, observe::metrics}, + infra::{self, api::routes::solve::dto::SolveRequest, observe::metrics, tokens}, util::Bytes, }, + anyhow::{Context, Result}, chrono::Utc, futures::{ FutureExt, @@ -13,12 +14,10 @@ use { itertools::Itertools, model::{order::OrderKind, signature::Signature}, shared::signature_validator::SignatureValidating, - std::{ - collections::HashMap, - future::Future, - sync::{Arc, Mutex}, - }, + std::{collections::HashMap, future::Future, sync::Arc}, tap::TapFallible, + tokio::sync::Mutex, + tracing::Instrument, }; type Shared = futures::future::Shared>; @@ -34,6 +33,7 @@ type Balances = HashMap; /// while the third task can continue in the background. #[derive(Clone, Debug)] pub struct DataFetchingTasks { + pub auction: Shared>, pub balances: Shared>, pub app_data: Shared>>>, @@ -47,6 +47,7 @@ pub struct Utilities { signature_validator: Arc, app_data_retriever: Option, liquidity_fetcher: infra::liquidity::Fetcher, + tokens: tokens::Fetcher, disable_access_list_simulation: bool, } @@ -61,7 +62,7 @@ impl std::fmt::Debug for Utilities { #[derive(Debug)] struct ControlBlock { /// Auction for which the data aggregation task was spawned. - auction: auction::Id, + solve_request: Arc, /// Data aggregation task. tasks: DataFetchingTasks, } @@ -76,28 +77,31 @@ impl DataAggregator { /// Aggregates all the data that is needed to pre-process the given auction. /// Uses a shared futures internally to make sure that the works happens /// only once for all connected solvers to share. - pub fn start_or_get_tasks_for_auction(&self, auction: Arc) -> DataFetchingTasks { - let new_id = auction - .id() - .expect("auctions used for quoting do not have to be prioritized"); - - let mut lock = self.control.lock().unwrap(); - let current_id = lock.auction; - if new_id.0 < current_id.0 { - tracing::error!(?current_id, ?new_id, "received an outdated auction"); - } - if current_id.0 == new_id.0 { + pub async fn start_or_get_tasks_for_auction( + &self, + request: Arc, + ) -> Result { + let mut lock = self.control.lock().await; + let current_auction = &lock.solve_request; + + // The autopilot ensures that all drivers receive identical + // requests per auction. That means we can use the significantly + // cheaper string comparison instead of parsing the JSON to compare + // the auction ids. + if &request == current_auction { + let id = lock.tasks.auction.clone().await.id; + init_auction_id_in_span(id.map(|i| i.0)); tracing::debug!("await running data aggregation task"); - return lock.tasks.clone(); + return Ok(lock.tasks.clone()); } - let tasks = self.assemble_tasks(auction); + let tasks = self.assemble_tasks(request.clone()).await?; tracing::debug!("started new data aggregation task"); - lock.auction = new_id; + lock.solve_request = request; lock.tasks = tasks.clone(); - tasks + Ok(tasks) } pub fn new( @@ -105,6 +109,7 @@ impl DataAggregator { app_data_retriever: Option, liquidity_fetcher: infra::liquidity::Fetcher, disable_access_list_simulation: bool, + tokens: tokens::Fetcher, ) -> Self { let signature_validator = shared::signature_validator::validator( eth.web3(), @@ -122,10 +127,12 @@ impl DataAggregator { app_data_retriever, liquidity_fetcher, disable_access_list_simulation, + tokens, }), control: Mutex::new(ControlBlock { - auction: auction::Id(0), + solve_request: Default::default(), tasks: DataFetchingTasks { + auction: futures::future::pending().boxed().shared(), balances: futures::future::pending().boxed().shared(), app_data: futures::future::pending().boxed().shared(), cow_amm_orders: futures::future::pending().boxed().shared(), @@ -135,7 +142,9 @@ impl DataAggregator { } } - fn assemble_tasks(&self, auction: Arc) -> DataFetchingTasks { + async fn assemble_tasks(&self, request: Arc) -> Result { + let auction = self.utilities.parse_request(request).await?; + let balances = Self::spawn_shared(Arc::clone(&self.utilities).fetch_balances(Arc::clone(&auction))); @@ -149,12 +158,13 @@ impl DataAggregator { let liquidity = Self::spawn_shared(Arc::clone(&self.utilities).fetch_liquidity(Arc::clone(&auction))); - DataFetchingTasks { + Ok(DataFetchingTasks { + auction: futures::future::ready(auction).boxed().shared(), balances, app_data, cow_amm_orders, liquidity, - } + }) } /// Spawns an async task and returns a `Shared` handle to its result. @@ -166,7 +176,7 @@ impl DataAggregator { where T: Send + Sync + Clone + 'static, { - let shared = fut.boxed().shared(); + let shared = fut.instrument(tracing::Span::current()).boxed().shared(); // Start the computation in the background tokio::spawn(shared.clone()); @@ -176,6 +186,35 @@ impl DataAggregator { } impl Utilities { + /// Parses the JSON body of the `/solve` request during the unified + /// auction pre-processing since eagerly deserializing these requests + /// is surprisingly costly because their are so big. + async fn parse_request(&self, solve_request: Arc) -> Result> { + let auction_dto: SolveRequest = { + let _timer = metrics::get().processing_stage_timer("parse_dto"); + // deserialization takes tens of milliseconds so run it on a blocking task + tokio::task::spawn_blocking(move || { + serde_json::from_str(&solve_request).context("could not parse solve request") + }) + .await + .context("failed to await blocking task")?? + }; + + // now that we finally know the auction id we can set it in the span + init_auction_id_in_span(Some(auction_dto.id())); + + let auction_domain = { + let _timer = metrics::get().processing_stage_timer("convert_to_domain"); + let auction = auction_dto + .into_domain(&self.eth, &self.tokens) + .await + .context("could not convert auction DTO to domain type")?; + Arc::new(auction) + }; + + Ok(auction_domain) + } + /// Fetches the tradable balance for every order owner. async fn fetch_balances(self: Arc, auction: Arc) -> Arc { let _timer = metrics::get().processing_stage_timer("fetch_balances"); @@ -405,3 +444,12 @@ impl Utilities { ) } } + +fn init_auction_id_in_span(id: Option) { + let Some(id) = id else { + return; + }; + let current_span = tracing::Span::current(); + debug_assert!(current_span.has_field("auction_id")); + current_span.record("auction_id", id); +} diff --git a/crates/driver/src/domain/quote.rs b/crates/driver/src/domain/quote.rs index 6f1956e1bf..3e6eb7acc5 100644 --- a/crates/driver/src/domain/quote.rs +++ b/crates/driver/src/domain/quote.rs @@ -73,7 +73,7 @@ pub struct Order { pub tokens: Tokens, pub amount: order::TargetAmount, pub side: order::Side, - pub deadline: time::Deadline, + pub deadline: chrono::DateTime, } impl Order { diff --git a/crates/driver/src/infra/api/error.rs b/crates/driver/src/infra/api/error.rs index 476fcbcfe0..52629bdf3f 100644 --- a/crates/driver/src/infra/api/error.rs +++ b/crates/driver/src/infra/api/error.rs @@ -22,6 +22,7 @@ enum Kind { QuoteSameTokens, FailedToSubmit, NoValidOrders, + MalformedRequest, } #[derive(Debug, Serialize)] @@ -55,6 +56,7 @@ impl From for (hyper::StatusCode, axum::Json) { Kind::FailedToSubmit => "Could not submit the solution to the blockchain", Kind::TooManyPendingSettlements => "Settlement queue is full", Kind::NoValidOrders => "No valid orders found in the auction", + Kind::MalformedRequest => "Could not parse the request", }; ( hyper::StatusCode::BAD_REQUEST, @@ -89,6 +91,7 @@ impl From for (hyper::StatusCode, axum::Json) { competition::Error::SubmissionError => Kind::FailedToSubmit, competition::Error::TooManyPendingSettlements => Kind::TooManyPendingSettlements, competition::Error::NoValidOrdersFound => Kind::NoValidOrders, + competition::Error::MalformedRequest => Kind::MalformedRequest, }; error.into() } diff --git a/crates/driver/src/infra/api/mod.rs b/crates/driver/src/infra/api/mod.rs index 382b3d9c95..489a977ee5 100644 --- a/crates/driver/src/infra/api/mod.rs +++ b/crates/driver/src/infra/api/mod.rs @@ -11,7 +11,7 @@ use { Simulator, config::file::OrderPriorityStrategy, liquidity, - solver::{Solver, Timeouts}, + solver::Solver, tokens, }, }, @@ -23,7 +23,7 @@ use { }; mod error; -mod routes; +pub mod routes; const REQUEST_BODY_LIMIT: usize = 10 * 1024 * 1024; @@ -59,6 +59,7 @@ impl Api { app_data_retriever.clone(), self.liquidity.clone(), disable_access_list_simulation, + tokens.clone(), )); let order_sorting_strategies = @@ -191,10 +192,6 @@ impl State { fn tokens(&self) -> &tokens::Fetcher { &self.0.tokens } - - fn timeouts(&self) -> Timeouts { - self.0.solver.timeouts() - } } struct Inner { diff --git a/crates/driver/src/infra/api/routes/mod.rs b/crates/driver/src/infra/api/routes/mod.rs index 8e86966a78..dcd4afffc8 100644 --- a/crates/driver/src/infra/api/routes/mod.rs +++ b/crates/driver/src/infra/api/routes/mod.rs @@ -6,7 +6,7 @@ mod notify; mod quote; mod reveal; mod settle; -mod solve; +pub mod solve; pub(super) use { gasprice::gasprice, diff --git a/crates/driver/src/infra/api/routes/quote/dto/order.rs b/crates/driver/src/infra/api/routes/quote/dto/order.rs index fbc27ae160..9d7e4a4f95 100644 --- a/crates/driver/src/infra/api/routes/quote/dto/order.rs +++ b/crates/driver/src/infra/api/routes/quote/dto/order.rs @@ -1,7 +1,6 @@ use { crate::{ - domain::{competition, eth, quote, time}, - infra::solver::Timeouts, + domain::{competition, eth, quote}, util::serialize, }, serde::Deserialize, @@ -9,7 +8,7 @@ use { }; impl Order { - pub fn into_domain(self, timeouts: Timeouts) -> Result { + pub fn into_domain(self) -> Result { Ok(quote::Order { tokens: quote::Tokens::try_new(self.sell_token.into(), self.buy_token.into()) .map_err(|quote::SameTokens| Error::SameTokens)?, @@ -18,7 +17,7 @@ impl Order { Kind::Sell => competition::order::Side::Sell, Kind::Buy => competition::order::Side::Buy, }, - deadline: time::Deadline::new(self.deadline, timeouts), + deadline: self.deadline, }) } } diff --git a/crates/driver/src/infra/api/routes/quote/mod.rs b/crates/driver/src/infra/api/routes/quote/mod.rs index c2fe76d824..ad179da746 100644 --- a/crates/driver/src/infra/api/routes/quote/mod.rs +++ b/crates/driver/src/infra/api/routes/quote/mod.rs @@ -20,7 +20,7 @@ async fn route( order: axum::extract::Query, ) -> Result, (hyper::StatusCode, axum::Json)> { let handle_request = async { - let order = order.0.into_domain(state.timeouts()).tap_err(|err| { + let order = order.0.into_domain().tap_err(|err| { observe::invalid_dto(err, "order"); })?; observe::quoting(&order); diff --git a/crates/driver/src/infra/api/routes/solve/dto/mod.rs b/crates/driver/src/infra/api/routes/solve/dto/mod.rs index 1fb6e69db5..458d9c47a9 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/mod.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/mod.rs @@ -1,4 +1,4 @@ -mod solve_request; +pub mod solve_request; mod solve_response; pub use { diff --git a/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs index eef89fee4c..013621ddca 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs @@ -3,9 +3,8 @@ use { domain::{ competition::{self, auction, order}, eth, - time, }, - infra::{Ethereum, solver::Timeouts, tokens}, + infra::{Ethereum, tokens}, util::serialize, }, serde::Deserialize, @@ -20,7 +19,6 @@ impl SolveRequest { self, eth: &Ethereum, tokens: &tokens::Fetcher, - timeouts: Timeouts, ) -> Result { let token_addresses: Vec<_> = self .tokens @@ -155,7 +153,7 @@ impl SolveRequest { trusted: token.trusted, } }), - time::Deadline::new(self.deadline, timeouts), + self.deadline, eth, self.surplus_capturing_jit_order_owners .into_iter() diff --git a/crates/driver/src/infra/api/routes/solve/mod.rs b/crates/driver/src/infra/api/routes/solve/mod.rs index 37e842ed36..3750b7ef1f 100644 --- a/crates/driver/src/infra/api/routes/solve/mod.rs +++ b/crates/driver/src/infra/api/routes/solve/mod.rs @@ -1,16 +1,12 @@ -mod dto; +pub mod dto; pub use dto::AuctionError; use { - crate::{ - domain, - infra::{ - api::{Error, State}, - observe, - }, + crate::infra::{ + api::{Error, State}, + observe, }, - std::time::Instant, - tap::TapFallible, + std::sync::Arc, tracing::Instrument, }; @@ -20,25 +16,13 @@ pub(in crate::infra::api) fn solve(router: axum::Router) -> axum::Router< async fn route( state: axum::extract::State, - req: axum::Json, + // take the request body as a raw string to delay parsing as much + // as possible because many requests don't have to be parsed at all + req: String, ) -> Result, (hyper::StatusCode, axum::Json)> { - let auction_id = req.id(); let handle_request = async { - observe::auction(auction_id); - let start = Instant::now(); - let auction = req - .0 - .into_domain(state.eth(), state.tokens(), state.timeouts()) - .await - .tap_err(|err| { - observe::invalid_dto(err, "auction"); - })?; - tracing::debug!(elapsed = ?start.elapsed(), "auction task execution time"); let competition = state.competition(); - if auction.orders.is_empty() { - return Err(domain::competition::Error::NoValidOrdersFound.into()); - } - let result = competition.solve(auction).await; + let result = competition.solve(Arc::new(req)).await; // Solving takes some time, so there is a chance for the settlement queue to // have capacity again. competition.ensure_settle_queue_capacity()?; @@ -50,6 +34,6 @@ async fn route( }; handle_request - .instrument(tracing::info_span!("/solve", solver = %state.solver().name(), auction_id)) + .instrument(tracing::info_span!("/solve", solver = %state.solver().name(), auction_id = tracing::field::Empty)) .await } diff --git a/crates/driver/src/infra/observe/mod.rs b/crates/driver/src/infra/observe/mod.rs index 409eb26751..f6dbab6fec 100644 --- a/crates/driver/src/infra/observe/mod.rs +++ b/crates/driver/src/infra/observe/mod.rs @@ -416,6 +416,7 @@ fn competition_error(err: &competition::Error) -> &'static str { competition::Error::SubmissionError => "SubmissionError", competition::Error::TooManyPendingSettlements => "TooManyPendingSettlements", competition::Error::NoValidOrdersFound => "NoValidOrdersFound", + competition::Error::MalformedRequest => "MalformedRequest", } } diff --git a/crates/driver/src/infra/solver/dto/auction.rs b/crates/driver/src/infra/solver/dto/auction.rs index 9698a5da95..3e6887f336 100644 --- a/crates/driver/src/infra/solver/dto/auction.rs +++ b/crates/driver/src/infra/solver/dto/auction.rs @@ -24,6 +24,7 @@ pub fn new( fee_handler: FeeHandler, solver_native_token: ManageNativeToken, flashloan_hints: &HashMap, + deadline: chrono::DateTime, ) -> solvers_dto::auction::Auction { let mut tokens: HashMap = auction .tokens() @@ -310,7 +311,7 @@ pub fn new( .collect(), tokens, effective_gas_price: auction.gas_price().effective().into(), - deadline: auction.deadline().solvers(), + deadline, surplus_capturing_jit_order_owners: auction .surplus_capturing_jit_order_owners() .iter() diff --git a/crates/driver/src/infra/solver/mod.rs b/crates/driver/src/infra/solver/mod.rs index 2e0d5b0de0..c54b32b1f9 100644 --- a/crates/driver/src/infra/solver/mod.rs +++ b/crates/driver/src/infra/solver/mod.rs @@ -243,6 +243,7 @@ impl Solver { self.config.fee_handler, self.config.solver_native_token, &flashloan_hints, + auction.deadline(self.timeouts()).solvers(), ); if let Some(id) = auction.id() { @@ -262,7 +263,7 @@ impl Solver { let url = shared::url::join(&self.config.endpoint, "solve"); super::observe::solver_request(&url, &body); - let timeout = match auction.deadline().solvers().remaining() { + let timeout = match auction.deadline(self.timeouts()).solvers().remaining() { Ok(timeout) => timeout, Err(_) => { tracing::warn!("auction deadline exceeded before sending request to solver"); From a18ec521b593f5316fb423beec264ab387791150 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Wed, 10 Sep 2025 11:22:35 +0200 Subject: [PATCH 016/112] Optimize invalid signature filtering (#3647) # Description Checking signatures is currently one of the slower auction building steps. This PR implements a couple optimizations to speed things up. # Changes - avoid `Itertools::partition` as that moves around every item even if we only need to move a few (e.g. in this case we don't need to split ALL orders into pre-signed and "unknown" - instead we just work with the orders that are actually relevant for this processing step) - changed `validate_signatures()` to only handle a single check instead of multiple - that made it a lot less awkward to figure out for which order the check failed. - updated internal helper functions to take an owned `SignatureCheck` so we don't have to clone the `signature` and `interaction` bytes twice - used `FuturesUnordered` instead of `join_all` to avoid allocating a vector for all the results - instead we just await each future concurrently and push the invalid orders into the result vector ## How to test updated the existing unit test unfortunate the performance improvement of this PR can only really be measured by deploying to mainnet prod as all the staging environments don't have enough data to matter and the shadow environment does not hit this code path. --- crates/autopilot/src/solvable_orders.rs | 121 +++++++++--------- crates/shared/src/signature_validator/mod.rs | 6 +- .../src/signature_validator/simulation.rs | 34 ++--- 3 files changed, 74 insertions(+), 87 deletions(-) diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index 08bc2a0e15..dae4b4fe07 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -7,9 +7,9 @@ use { anyhow::{Context, Result}, bigdecimal::BigDecimal, database::order_events::OrderEventLabel, - futures::{FutureExt, future::join_all}, + futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered}, indexmap::IndexSet, - itertools::{Either, Itertools}, + itertools::Itertools, model::{ order::{Order, OrderClass, OrderUid}, signature::Signature, @@ -461,51 +461,40 @@ async fn find_invalid_signature_orders( orders: &[Order], signature_validator: &dyn SignatureValidating, ) -> Vec { - let (mut invalid_orders, orders): (Vec<_>, Vec<_>) = orders.iter().partition_map(|order| { + let mut invalid_orders = vec![]; + let mut signature_check_futures = FuturesUnordered::new(); + + for order in orders { if matches!( order.metadata.status, model::order::OrderStatus::PresignaturePending ) { - Either::Left(order.metadata.uid) - } else { - Either::Right(order) + invalid_orders.push(order.metadata.uid); + continue; } - }); - let checks = orders - .iter() - .filter_map(|order| match &order.signature { - Signature::Eip1271(signature) => { + if let Signature::Eip1271(signature) = &order.signature { + signature_check_futures.push(async { let (H256(hash), signer, _) = order.metadata.uid.parts(); - Some(SignatureCheck { - signer, - hash, - signature: signature.clone(), - interactions: order.interactions.pre.clone(), - }) - } - _ => None, - }) - .collect::>(); - - if checks.is_empty() { - return invalid_orders; + match signature_validator + .validate_signature(SignatureCheck { + signer, + hash, + signature: signature.clone(), + interactions: order.interactions.pre.clone(), + }) + .await + { + Ok(_) => None, + Err(_) => Some(order.metadata.uid), + } + }); + } } - let mut validations = signature_validator - .validate_signatures(checks) - .await - .into_iter(); - for order in orders { - if !matches!(&order.signature, Signature::Eip1271(_)) { - continue; - } - if let Err(err) = validations.next().unwrap() { - tracing::warn!( - order =% order.metadata.uid, ?err, - "invalid EIP-1271 signature" - ); - invalid_orders.push(order.metadata.uid) + while let Some(res) = signature_check_futures.next().await { + if let Some(invalid_order_uid) = res { + invalid_orders.push(invalid_order_uid); } } @@ -1252,32 +1241,36 @@ mod tests { let mut signature_validator = MockSignatureValidating::new(); signature_validator - .expect_validate_signatures() - .with(eq(vec![ - SignatureCheck { - signer: H160([22; 20]), - hash: [2; 32], - signature: vec![2, 2], - interactions: vec![InteractionData { - target: H160([0xe3; 20]), - value: U256::zero(), - call_data: vec![5, 6], - }], - }, - SignatureCheck { - signer: H160([44; 20]), - hash: [4; 32], - signature: vec![4, 4, 4, 4], - interactions: vec![], - }, - SignatureCheck { - signer: H160([55; 20]), - hash: [5; 32], - signature: vec![5, 5, 5, 5, 5], - interactions: vec![], - }, - ])) - .returning(|_| vec![Ok(()), Err(SignatureValidationError::Invalid), Ok(())]); + .expect_validate_signature() + .with(eq(SignatureCheck { + signer: H160([22; 20]), + hash: [2; 32], + signature: vec![2, 2], + interactions: vec![InteractionData { + target: H160([0xe3; 20]), + value: U256::zero(), + call_data: vec![5, 6], + }], + })) + .returning(|_| Ok(())); + signature_validator + .expect_validate_signature() + .with(eq(SignatureCheck { + signer: H160([44; 20]), + hash: [4; 32], + signature: vec![4, 4, 4, 4], + interactions: vec![], + })) + .returning(|_| Err(SignatureValidationError::Invalid)); + signature_validator + .expect_validate_signature() + .with(eq(SignatureCheck { + signer: H160([55; 20]), + hash: [5; 32], + signature: vec![5, 5, 5, 5, 5], + interactions: vec![], + })) + .returning(|_| Ok(())); let invalid_signature_orders = find_invalid_signature_orders(&orders, &signature_validator).await; diff --git a/crates/shared/src/signature_validator/mod.rs b/crates/shared/src/signature_validator/mod.rs index f2efa120b3..6a74ff254a 100644 --- a/crates/shared/src/signature_validator/mod.rs +++ b/crates/shared/src/signature_validator/mod.rs @@ -48,10 +48,10 @@ pub enum SignatureValidationError { #[async_trait::async_trait] /// pub trait SignatureValidating: Send + Sync { - async fn validate_signatures( + async fn validate_signature( &self, - checks: Vec, - ) -> Vec>; + check: SignatureCheck, + ) -> Result<(), SignatureValidationError>; /// Validates the signature and returns the `eth_estimateGas` of the /// isValidSignature call minus the tx initation gas amount of 21k. diff --git a/crates/shared/src/signature_validator/simulation.rs b/crates/shared/src/signature_validator/simulation.rs index f80d7e47eb..0158639661 100644 --- a/crates/shared/src/signature_validator/simulation.rs +++ b/crates/shared/src/signature_validator/simulation.rs @@ -9,7 +9,6 @@ use { contracts::{ERC1271SignatureValidator, errors::EthcontractErrorType}, ethcontract::Bytes, ethrpc::Web3, - futures::future, primitive_types::{H160, U256}, tracing::instrument, }; @@ -44,7 +43,7 @@ impl Validator { /// pre-interactions async fn simulate_without_pre_interactions( &self, - check: &SignatureCheck, + check: SignatureCheck, ) -> Result<(), SignatureValidationError> { // Since there are no interactions (no dynamic conditions / complex pre-state // change), the order's validity can be directly determined by whether @@ -68,7 +67,7 @@ impl Validator { #[instrument(skip_all, fields(interactions_len = check.interactions.len()))] async fn simulate( &self, - check: &SignatureCheck, + check: SignatureCheck, ) -> Result { // We simulate the signature verification from the Settlement contract's // context. This allows us to check: @@ -79,11 +78,11 @@ impl Validator { (self.settlement.address(), self.vault_relayer), check.signer, Bytes(check.hash), - Bytes(check.signature.clone()), + Bytes(check.signature), check .interactions - .iter() - .map(|i| (i.target, i.value, Bytes(i.call_data.clone()))) + .into_iter() + .map(|i| (i.target, i.value, Bytes(i.call_data))) .collect(), ); let gas_cost_call = self @@ -99,7 +98,6 @@ impl Validator { .await .map_err(|_| SignatureValidationError::Invalid); - tracing::trace!(?check, ?result, "simulated signature"); Ok(Simulation { gas_used: result? }) } } @@ -107,19 +105,15 @@ impl Validator { #[async_trait::async_trait] impl SignatureValidating for Validator { #[instrument(skip_all)] - async fn validate_signatures( + async fn validate_signature( &self, - checks: Vec, - ) -> Vec> { - future::join_all(checks.into_iter().map(|check| async move { - if check.interactions.is_empty() { - self.simulate_without_pre_interactions(&check).await?; - } else { - self.simulate(&check).await?; - } - Ok(()) - })) - .await + check: SignatureCheck, + ) -> Result<(), SignatureValidationError> { + if check.interactions.is_empty() { + self.simulate_without_pre_interactions(check).await + } else { + self.simulate(check).await.map(|_| ()) + } } async fn validate_signature_and_get_additional_gas( @@ -127,7 +121,7 @@ impl SignatureValidating for Validator { check: SignatureCheck, ) -> Result { Ok(self - .simulate(&check) + .simulate(check) .await? .gas_used .try_into() From 45d5b64ad1b346bdbdfa9d3532cd920be5e7dbd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 11 Sep 2025 11:41:40 +0100 Subject: [PATCH 017/112] Hide ethrpc test extensions (#3649) --- crates/e2e/Cargo.toml | 2 +- crates/ethrpc/src/alloy/mod.rs | 91 +++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 4ee896c7cd..d2052c6bfc 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -21,7 +21,7 @@ contracts = { workspace = true, features = ["test-util"] } database = { workspace = true } driver = { workspace = true } ethcontract = { workspace = true } -ethrpc = { workspace = true } +ethrpc = { workspace = true, features = ["test-util"] } futures = { workspace = true } hex = { workspace = true } hex-literal = { workspace = true } diff --git a/crates/ethrpc/src/alloy/mod.rs b/crates/ethrpc/src/alloy/mod.rs index 8b08fda6cc..167ccc6396 100644 --- a/crates/ethrpc/src/alloy/mod.rs +++ b/crates/ethrpc/src/alloy/mod.rs @@ -5,19 +5,12 @@ mod instrumentation; use { crate::AlloyProvider, alloy::{ - contract::{CallBuilder, CallDecoder}, - network::{EthereumWallet, Network}, - primitives::{FixedBytes, TxHash}, + network::EthereumWallet, providers::{Provider, ProviderBuilder}, - rpc::{ - client::{ClientBuilder, RpcClient}, - types::TransactionRequest, - }, + rpc::client::{ClientBuilder, RpcClient}, }, buffering::BatchCallLayer, instrumentation::{InstrumentationLayer, LabelingLayer}, - std::time::Duration, - tokio::time::timeout, }; pub use {conversions::Account, instrumentation::ProviderLabelingExt}; @@ -37,8 +30,6 @@ pub fn provider(url: &str) -> AlloyProvider { .erased() } -const DEFAULT_WATCH_TIMEOUT: Duration = Duration::from_secs(2); - pub trait ProviderSignerExt { /// Creates a new provider with the given signer. fn with_signer(&self, signer: Account) -> Self; @@ -64,38 +55,58 @@ impl ProviderSignerExt for AlloyProvider { } } -pub trait ProviderExt { - /// Sends the transaction to the node and waits for confirmations. - /// - /// If confirmation takes longer than 25 seconds, the operation will - /// timeout. - fn send_and_watch( - &self, - tx: TransactionRequest, - ) -> impl Future>; -} +#[cfg(feature = "test-util")] +mod test_util { + use { + super::*, + alloy::{ + contract::{CallBuilder, CallDecoder}, + primitives::TxHash, + providers::Network, + rpc::types::TransactionRequest, + }, + std::time::Duration, + tokio::time::timeout, + }; + + const DEFAULT_WATCH_TIMEOUT: Duration = Duration::from_secs(2); -impl ProviderExt for AlloyProvider { - async fn send_and_watch(&self, tx: TransactionRequest) -> anyhow::Result { - let pending = self.send_transaction(tx).await?; - let result = timeout(DEFAULT_WATCH_TIMEOUT, pending.watch()).await??; - Ok(result) + pub trait ProviderExt { + /// Sends the transaction to the node and waits for confirmations. + /// + /// If confirmation takes longer than 25 seconds, the operation will + /// timeout. + fn send_and_watch( + &self, + tx: TransactionRequest, + ) -> impl Future>; } -} -pub trait CallBuilderExt { - /// Converts the current call into a [`TransactionRequest`], sends it to the - /// node and waits for confirmations. - /// - /// If confirmation takes longer than 25 seconds, the operation will - /// timeout. - fn send_and_watch(&self) -> impl Future>>; -} + impl ProviderExt for AlloyProvider { + async fn send_and_watch(&self, tx: TransactionRequest) -> anyhow::Result { + let pending = self.send_transaction(tx).await?; + let result = timeout(DEFAULT_WATCH_TIMEOUT, pending.watch()).await??; + Ok(result) + } + } -impl, D: CallDecoder, N: Network> CallBuilderExt for CallBuilder { - async fn send_and_watch(&self) -> anyhow::Result { - let pending = self.send().await?; - let result = timeout(DEFAULT_WATCH_TIMEOUT, pending.watch()).await??; - Ok(result) + pub trait CallBuilderExt { + /// Converts the current call into a [`TransactionRequest`], sends it to + /// the node and waits for confirmations. + /// + /// If confirmation takes longer than 25 seconds, the operation will + /// timeout. + fn send_and_watch(&self) -> impl Future>; + } + + impl, D: CallDecoder, N: Network> CallBuilderExt for CallBuilder { + async fn send_and_watch(&self) -> anyhow::Result { + let pending = self.send().await?; + let result = timeout(DEFAULT_WATCH_TIMEOUT, pending.watch()).await??; + Ok(result) + } } } + +#[cfg(feature = "test-util")] +pub use test_util::{CallBuilderExt, ProviderExt}; From d17fe8a6a2023ec3434e2ac78dcace8d89a9ea33 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 11 Sep 2025 14:56:00 +0300 Subject: [PATCH 018/112] BalancerV2BasePool alloy bindings (#3642) # Description Migrates `BalancerV2BasePool` bindings to alloy. Also, starts using `BalancerV2Vault` alloy bindings in the liquidity fetching logic, which doesn't touch the GPv2Settlement SC stuff. This is required to avoid non-pretty workaround to provide alloy provider for the `BalancerV2BasePool` instance here: https://github.com/cowprotocol/services/pull/3642/files#diff-c3c048247f32ab19c889ca44622f8857e743f2901b2ad2b7b51fb1439835962fR64-R65 `BalancerV2Vault` ethcontract instances remain since they are still used in the core SCs logic, which will be migrated much later. ## How to test Existing tests. --- Cargo.lock | 1 + crates/contracts/build.rs | 3 - crates/contracts/src/alloy.rs | 26 + crates/contracts/src/lib.rs | 1 - .../src/boundary/liquidity/balancer/v2/mod.rs | 11 +- .../boundary/liquidity/balancer/v2/stable.rs | 2 + .../liquidity/balancer/v2/weighted.rs | 2 + crates/e2e/tests/e2e/liquidity.rs | 2 +- crates/ethrpc/src/alloy/conversions.rs | 12 +- .../sources/balancer_v2/pool_fetching/mod.rs | 8 +- .../src/sources/balancer_v2/pools/common.rs | 514 ++++++++++++------ crates/solver/Cargo.toml | 1 + crates/solver/src/interactions/balancer_v2.rs | 61 ++- crates/solver/src/liquidity/balancer_v2.rs | 23 +- 14 files changed, 461 insertions(+), 206 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02d65ce03c..3a726d6261 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6008,6 +6008,7 @@ dependencies = [ name = "solver" version = "0.1.0" dependencies = [ + "alloy", "anyhow", "arc-swap", "async-trait", diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 4ad0abc2c4..62fe8cecca 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -152,9 +152,6 @@ fn main() { generate_contract_with_config("BalancerV2Authorizer", |builder| { builder.contract_mod_override("balancer_v2_authorizer") }); - generate_contract_with_config("BalancerV2BasePool", |builder| { - builder.contract_mod_override("balancer_v2_base_pool") - }); generate_contract_with_config("BalancerV2BasePoolFactory", |builder| { builder.contract_mod_override("balancer_v2_base_pool_factory") }); diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 582c1a588d..babb06f688 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -47,6 +47,32 @@ crate::bindings!(GnosisSafeCompatibilityFallbackHandler); crate::bindings!(GnosisSafeProxy); crate::bindings!(GnosisSafeProxyFactory); +crate::bindings!(BalancerV2BasePool); +crate::bindings!( + BalancerV2Vault, + crate::deployments! { + // + MAINNET => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 12272146), + // + GNOSIS => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 24821598), + // + SEPOLIA => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 3418831), + // + ARBITRUM_ONE => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 222832), + // + BASE => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 1196036), + // + AVALANCHE => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 26386141), + // + BNB => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 22691002), + // + OPTIMISM => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 7003431), + // + POLYGON => (address!("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), 15832990), + // Not available on Lens + } +); + pub use alloy::providers::DynProvider as Provider; /// Extension trait to attach some useful functions to the contract instance. diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 31cf0eba14..6be650a256 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -51,7 +51,6 @@ macro_rules! include_contracts { include_contracts! { AaveFlashLoanSolverWrapper; BalancerV2Authorizer; - BalancerV2BasePool; BalancerV2BasePoolFactory; BalancerV2ComposableStablePool; BalancerV2ComposableStablePoolFactory; diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs b/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs index 0dcff6f79b..7de4e30541 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs @@ -12,12 +12,15 @@ use { BalancerV2ComposableStablePoolFactory, BalancerV2LiquidityBootstrappingPoolFactory, BalancerV2StablePoolFactoryV2, - BalancerV2Vault, BalancerV2WeightedPoolFactory, BalancerV2WeightedPoolFactoryV3, GPv2Settlement, + alloy::BalancerV2Vault, + }, + ethrpc::{ + alloy::conversions::IntoAlloy, + block_stream::{BlockRetrieving, CurrentBlockWatcher}, }, - ethrpc::block_stream::{BlockRetrieving, CurrentBlockWatcher}, shared::{ http_solver::model::TokenAmount, sources::balancer_v2::{ @@ -56,7 +59,7 @@ fn to_interaction( // also baked into the Balancer V2 logic in the `shared` crate, so to // change this assumption, we would need to change it there as well. GPv2Settlement::at(&web3, receiver.0), - BalancerV2Vault::at(&web3, pool.vault.into()), + BalancerV2Vault::Instance::new(pool.vault.0.into_alloy(), ethrpc::mock::web3().alloy), Allowances::empty(receiver.0), ); @@ -107,7 +110,7 @@ async fn init_liquidity( ) -> Result> { let web3 = eth.web3().clone(); let contracts = BalancerContracts { - vault: BalancerV2Vault::at(&web3, config.vault.into()), + vault: BalancerV2Vault::Instance::new(config.vault.0.into_alloy(), web3.alloy.clone()), factories: [ config .weighted diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs b/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs index e211b9fe88..1ce175a390 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs @@ -6,6 +6,7 @@ use { liquidity::{self, balancer}, }, }, + ethrpc::alloy::conversions::IntoLegacy, solver::liquidity::{StablePoolOrder, balancer_v2}, }; @@ -52,6 +53,7 @@ fn vault(pool: &StablePoolOrder) -> eth::ContractAddress { .expect("downcast balancer settlement handler") .vault() .address() + .into_legacy() .into() } diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs b/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs index 520ad4cfc0..5d86fe77db 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs @@ -6,6 +6,7 @@ use { liquidity::{self, balancer}, }, }, + ethrpc::alloy::conversions::IntoLegacy, shared::sources::balancer_v2::pool_fetching::WeightedPoolVersion, solver::liquidity::{WeightedProductOrder, balancer_v2}, }; @@ -56,6 +57,7 @@ fn vault(pool: &WeightedProductOrder) -> eth::ContractAddress { .expect("downcast balancer settlement handler") .vault() .address() + .into_legacy() .into() } diff --git a/crates/e2e/tests/e2e/liquidity.rs b/crates/e2e/tests/e2e/liquidity.rs index 2c1468a576..2761a94d46 100644 --- a/crates/e2e/tests/e2e/liquidity.rs +++ b/crates/e2e/tests/e2e/liquidity.rs @@ -400,5 +400,5 @@ async fn fill_or_kill_zeroex_limit_order( .watch() .await?; - Ok(tx_hash.into_alloy()) + Ok(tx_hash.into_legacy()) } diff --git a/crates/ethrpc/src/alloy/conversions.rs b/crates/ethrpc/src/alloy/conversions.rs index 5ac5850342..eb63e05125 100644 --- a/crates/ethrpc/src/alloy/conversions.rs +++ b/crates/ethrpc/src/alloy/conversions.rs @@ -83,6 +83,14 @@ impl IntoAlloy for web3::types::BlockId { } } +impl IntoAlloy for ethcontract::tokens::Bytes> { + type To = alloy::primitives::Bytes; + + fn into_alloy(self) -> Self::To { + alloy::primitives::Bytes::copy_from_slice(self.0.as_slice()) + } +} + pub enum Account { Address(alloy::primitives::Address), Signer(Box + Send + Sync + 'static>), @@ -160,10 +168,10 @@ impl IntoLegacy for alloy::primitives::Address { } } -impl IntoAlloy for alloy::primitives::aliases::B256 { +impl IntoLegacy for alloy::primitives::aliases::B256 { type To = primitive_types::H256; - fn into_alloy(self) -> Self::To { + fn into_legacy(self) -> Self::To { primitive_types::H256(self.into()) } } diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs index c9e00a0744..b77aae6f91 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs @@ -41,11 +41,11 @@ use { BalancerV2LiquidityBootstrappingPoolFactory, BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory, BalancerV2StablePoolFactoryV2, - BalancerV2Vault, BalancerV2WeightedPool2TokensFactory, BalancerV2WeightedPoolFactory, BalancerV2WeightedPoolFactoryV3, BalancerV2WeightedPoolFactoryV4, + alloy::{BalancerV2Vault, InstanceExt}, }, ethcontract::{BlockId, H160, H256, Instance, dyns::DynInstance}, ethrpc::block_stream::{BlockRetrieving, CurrentBlockWatcher}, @@ -225,14 +225,14 @@ impl BalancerFactoryKind { /// All balancer related contracts that we expect to exist. pub struct BalancerContracts { - pub vault: BalancerV2Vault, + pub vault: BalancerV2Vault::Instance, pub factories: Vec<(BalancerFactoryKind, DynInstance)>, } impl BalancerContracts { pub async fn try_new(web3: &Web3, factory_kinds: Vec) -> Result { let web3 = ethrpc::instrumented::instrument_with_label(web3, "balancerV2".into()); - let vault = BalancerV2Vault::deployed(&web3) + let vault = BalancerV2Vault::Instance::deployed(&web3.alloy) .await .context("Cannot retrieve balancer vault")?; @@ -463,7 +463,7 @@ async fn create_aggregate_pool_fetcher( /// Helper method for creating a boxed `InternalPoolFetching` instance for the /// specified factory and parameters. fn create_internal_pool_fetcher( - vault: BalancerV2Vault, + vault: BalancerV2Vault::Instance, factory: Factory, block_retriever: Arc, token_infos: Arc, diff --git a/crates/shared/src/sources/balancer_v2/pools/common.rs b/crates/shared/src/sources/balancer_v2/pools/common.rs index e331752a3e..d5ea1b4739 100644 --- a/crates/shared/src/sources/balancer_v2/pools/common.rs +++ b/crates/shared/src/sources/balancer_v2/pools/common.rs @@ -10,9 +10,10 @@ use { token_info::TokenInfoFetching, }, anyhow::{Context, Result, anyhow, ensure}, - contracts::{BalancerV2BasePool, BalancerV2Vault}, - ethcontract::{BlockId, Bytes, H160, H256, U256}, - futures::{FutureExt as _, TryFutureExt, future::BoxFuture}, + contracts::alloy::{BalancerV2BasePool, BalancerV2Vault}, + ethcontract::{BlockId, H160, H256, U256}, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, + futures::{FutureExt as _, future::BoxFuture}, std::{collections::BTreeMap, future::Future, sync::Arc}, tokio::sync::oneshot, }; @@ -40,14 +41,14 @@ where /// Generic pool info fetcher for fetching pool info and state that is generic /// on a pool factory type and its inner pool type. pub struct PoolInfoFetcher { - vault: BalancerV2Vault, + vault: BalancerV2Vault::Instance, factory: Factory, token_infos: Arc, } impl PoolInfoFetcher { pub fn new( - vault: BalancerV2Vault, + vault: BalancerV2Vault::Instance, factory: Factory, token_infos: Arc, ) -> Self { @@ -59,9 +60,9 @@ impl PoolInfoFetcher { } /// Returns a Balancer base pool contract instance at the specified address. - fn base_pool_at(&self, pool_address: H160) -> BalancerV2BasePool { - let web3 = self.vault.raw_instance().web3(); - BalancerV2BasePool::at(&web3, pool_address) + fn base_pool_at(&self, pool_address: H160) -> BalancerV2BasePool::Instance { + let provider = self.vault.provider().clone(); + BalancerV2BasePool::Instance::new(pool_address.into_alloy(), provider) } /// Retrieves the scaling exponents for the specified tokens. @@ -87,13 +88,16 @@ impl PoolInfoFetcher { ) -> Result { let pool = self.base_pool_at(pool_address); - let pool_id = H256(pool.methods().get_pool_id().call().await?.0); - let (tokens, _, _) = self + let pool_id = pool.getPoolId().call().await?.into_legacy(); + let tokens = self .vault - .methods() - .get_pool_tokens(Bytes(pool_id.0)) + .getPoolTokens(pool_id.0.into()) .call() - .await?; + .await? + .tokens + .into_iter() + .map(|token| token.into_legacy()) + .collect::>(); let scaling_factors = self.scaling_factors(&tokens).await?; Ok(PoolInfo { @@ -110,18 +114,35 @@ impl PoolInfoFetcher { pool: &PoolInfo, block: BlockId, ) -> BoxFuture<'static, Result> { - let pool_contract = self.base_pool_at(pool.address); - let fetch_paused = pool_contract - .get_paused_state() - .block(block) - .call() - .map_ok(|result| result.0); - let fetch_swap_fee = pool_contract.get_swap_fee_percentage().block(block).call(); - let fetch_balances = self - .vault - .get_pool_tokens(Bytes(pool.id.0)) - .block(block) - .call(); + let block = block.into_alloy(); + let pool_address = pool.address; + let pool_id = pool.id; + let vault = self.vault.clone(); + let pool_contract_paused = self.base_pool_at(pool_address); + let pool_contract_fee = self.base_pool_at(pool_address); + + let fetch_paused = async move { + pool_contract_paused + .getPausedState() + .block(block) + .call() + .await + .map(|result| result.paused) + }; + let fetch_swap_fee = async move { + pool_contract_fee + .getSwapFeePercentage() + .block(block) + .call() + .await + }; + let pool_tokens = async move { + vault + .getPoolTokens(pool_id.0.into()) + .block(block) + .call() + .await + }; // Because of a `mockall` limitation, we **need** the future returned // here to be `'static`. This requires us to clone and move `pool` into @@ -129,18 +150,23 @@ impl PoolInfoFetcher { // `pool`, i.e. `'_`. let pool = pool.clone(); async move { - let (paused, swap_fee, balances) = - futures::try_join!(fetch_paused, fetch_swap_fee, fetch_balances)?; - let swap_fee = Bfp::from_wei(swap_fee); + let (paused, swap_fee, pool_tokens) = + futures::try_join!(fetch_paused, fetch_swap_fee, pool_tokens)?; + let swap_fee = Bfp::from_wei(swap_fee.into_legacy()); - let (token_addresses, balances, _) = balances; - ensure!(pool.tokens == token_addresses, "pool token mismatch"); + let balances = pool_tokens.balances; + let tokens = pool_tokens + .tokens + .into_iter() + .map(|t| t.into_legacy()) + .collect::>(); + ensure!(pool.tokens == tokens, "pool token mismatch"); let tokens = itertools::izip!(&pool.tokens, balances, &pool.scaling_factors) .map(|(&address, balance, &scaling_factor)| { ( address, TokenState { - balance, + balance: balance.into_legacy(), scaling_factor, }, ) @@ -338,37 +364,57 @@ mod tests { super::*, crate::{ sources::balancer_v2::{ + PoolKind, graph_api::{PoolType, Token}, - pools::{MockFactoryIndexing, PoolKind, weighted}, + pools::{MockFactoryIndexing, weighted}, }, token_info::{MockTokenInfoFetching, TokenInfo}, }, + alloy::{ + dyn_abi::{DynSolValue, FunctionExt}, + providers::{Provider, ProviderBuilder}, + transports::mock::Asserter, + }, anyhow::bail, - contracts::{BalancerV2WeightedPool, dummy_contract}, ethcontract::U256, - ethcontract_mock::Mock, - futures::future, maplit::{btreemap, hashmap}, mockall::predicate, + std::future, + web3::types::BlockNumber, }; #[tokio::test] async fn fetch_common_pool_info() { - let pool_id = H256([0x90; 32]); + let pool_id = alloy::primitives::FixedBytes([0x90; 32]); let tokens = [H160([1; 20]), H160([2; 20]), H160([3; 20])]; - let mock = Mock::new(42); - let web3 = mock.web3(); - - let pool = mock.deploy(BalancerV2BasePool::raw_contract().interface.abi.clone()); - pool.expect_call(BalancerV2BasePool::signatures().get_pool_id()) - .returns(Bytes(pool_id.0)); - - let vault = mock.deploy(BalancerV2Vault::raw_contract().interface.abi.clone()); - vault - .expect_call(BalancerV2Vault::signatures().get_pool_tokens()) - .predicate((predicate::eq(Bytes(pool_id.0)),)) - .returns((tokens.to_vec(), vec![], U256::zero())); + let asserter = Asserter::new(); + let provider = ProviderBuilder::new() + .connect_mocked_client(asserter.clone()) + .erased(); + + let pool = BalancerV2BasePool::Instance::new(H160::random().into_alloy(), provider.clone()); + let vault = BalancerV2Vault::Instance::new(H160::random().into_alloy(), provider.clone()); + asserter.push_success(&pool_id); + + let get_pool_tokens_response = { + let tokens_response = DynSolValue::Array( + tokens + .iter() + .copied() + .map(|t| DynSolValue::Address(t.into_alloy())) + .collect(), + ); + let balances_response = DynSolValue::Array(vec![]); + let last_block_reponse = DynSolValue::Uint(U256::zero().into_alloy(), 256); + BalancerV2Vault::abi_functions_by_name("getPoolTokens") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[tokens_response, balances_response, last_block_reponse]) + .unwrap() + }; + asserter.push_success(&get_pool_tokens_response); let mut token_infos = MockTokenInfoFetching::new(); token_infos @@ -383,20 +429,20 @@ mod tests { }); let pool_info_fetcher = PoolInfoFetcher { - vault: BalancerV2Vault::at(&web3, vault.address()), + vault, factory: MockFactoryIndexing::new(), token_infos: Arc::new(token_infos), }; let pool_info = pool_info_fetcher - .fetch_common_pool_info(pool.address(), 1337) + .fetch_common_pool_info(pool.address().into_legacy(), 1337) .await .unwrap(); assert_eq!( pool_info, PoolInfo { - id: pool_id, - address: pool.address(), + id: pool_id.into_legacy(), + address: pool.address().into_legacy(), tokens: tokens.to_vec(), scaling_factors: vec![Bfp::exp10(0), Bfp::exp10(0), Bfp::exp10(12)], block_created: 1337, @@ -411,44 +457,81 @@ mod tests { let balances = [bfp!("1000.0"), bfp!("10.0"), bfp!("15.0")]; let scaling_factors = [Bfp::exp10(0), Bfp::exp10(0), Bfp::exp10(12)]; - let mock = Mock::new(42); - let web3 = mock.web3(); - - let pool = mock.deploy(BalancerV2BasePool::raw_contract().interface.abi.clone()); - pool.expect_call(BalancerV2BasePool::signatures().get_paused_state()) - .returns((false, 0.into(), 0.into())); - pool.expect_call(BalancerV2BasePool::signatures().get_swap_fee_percentage()) - .returns(bfp!("0.003").as_uint256()); - - let vault = mock.deploy(BalancerV2Vault::raw_contract().interface.abi.clone()); - vault - .expect_call(BalancerV2Vault::signatures().get_pool_tokens()) - .predicate((predicate::eq(Bytes(pool_id.0)),)) - .returns(( - tokens.to_vec(), - balances.into_iter().map(Bfp::as_uint256).collect(), - 0.into(), - )); + let asserter = Asserter::new(); + let provider = ProviderBuilder::new() + .connect_mocked_client(asserter.clone()) + .erased(); + + let pool = BalancerV2BasePool::Instance::new(H160::random().into_alloy(), provider.clone()); + let vault = BalancerV2Vault::Instance::new(H160::random().into_alloy(), provider.clone()); + + let get_paused_state_response = BalancerV2BasePool::abi_functions_by_name("getPausedState") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[ + DynSolValue::Bool(false), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + ]) + .unwrap(); + asserter.push_success(&get_paused_state_response); + let get_swap_fee_percentage_response = + BalancerV2BasePool::abi_functions_by_name("getSwapFeePercentage") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[DynSolValue::Uint( + bfp!("0.003").as_uint256().into_alloy(), + 256, + )]) + .unwrap(); + asserter.push_success(&get_swap_fee_percentage_response); + + let get_pool_tokens_response = { + let tokens_response = DynSolValue::Array( + tokens + .iter() + .copied() + .map(|t| DynSolValue::Address(t.into_alloy())) + .collect(), + ); + let balances_response = DynSolValue::Array( + balances + .iter() + .map(|b| DynSolValue::Uint(b.as_uint256().into_alloy(), 256)) + .collect(), + ); + let last_block_reponse = DynSolValue::Uint(U256::zero().into_alloy(), 256); + BalancerV2Vault::abi_functions_by_name("getPoolTokens") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[tokens_response, balances_response, last_block_reponse]) + .unwrap() + }; + asserter.push_success(&get_pool_tokens_response); let token_infos = MockTokenInfoFetching::new(); let pool_info_fetcher = PoolInfoFetcher { - vault: BalancerV2Vault::at(&web3, vault.address()), + vault, factory: MockFactoryIndexing::new(), token_infos: Arc::new(token_infos), }; let pool_info = PoolInfo { id: pool_id, - address: pool.address(), + address: pool.address().into_legacy(), tokens: tokens.to_vec(), scaling_factors: scaling_factors.to_vec(), block_created: 1337, }; let pool_state = { - let block = web3.eth().block_number().await.unwrap(); - - let pool_state = pool_info_fetcher.fetch_common_pool_state(&pool_info, block.into()); + let pool_state = pool_info_fetcher.fetch_common_pool_state( + &pool_info, + BlockId::Number(BlockNumber::Number(1.into())), + ); pool_state.await.unwrap() }; @@ -480,44 +563,74 @@ mod tests { async fn fetch_state_errors_on_token_mismatch() { let tokens = [H160([1; 20]), H160([2; 20]), H160([3; 20])]; - let mock = Mock::new(42); - let web3 = mock.web3(); - - let pool = mock.deploy(BalancerV2BasePool::raw_contract().interface.abi.clone()); - pool.expect_call(BalancerV2BasePool::signatures().get_paused_state()) - .returns((false, 0.into(), 0.into())); - pool.expect_call(BalancerV2BasePool::signatures().get_swap_fee_percentage()) - .returns(0.into()); - - let vault = mock.deploy(BalancerV2Vault::raw_contract().interface.abi.clone()); - vault - .expect_call(BalancerV2Vault::signatures().get_pool_tokens()) - .predicate((predicate::eq(Bytes(Default::default())),)) - .returns(( - vec![H160([1; 20]), H160([4; 20])], - vec![0.into(), 0.into()], - 0.into(), - )); + let asserter = Asserter::new(); + let provider = ProviderBuilder::new() + .connect_mocked_client(asserter.clone()) + .erased(); + + let pool = BalancerV2BasePool::Instance::new(H160::random().into_alloy(), provider.clone()); + let vault = BalancerV2Vault::Instance::new(H160::random().into_alloy(), provider.clone()); + + let get_paused_state_response = BalancerV2BasePool::abi_functions_by_name("getPausedState") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[ + DynSolValue::Bool(false), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + ]) + .unwrap(); + asserter.push_success(&get_paused_state_response); + + let get_swap_fee_percentage_response = + BalancerV2BasePool::abi_functions_by_name("getSwapFeePercentage") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[DynSolValue::Uint(U256::zero().into_alloy(), 256)]) + .unwrap(); + asserter.push_success(&get_swap_fee_percentage_response); + + let get_pool_tokens_response = { + let tokens_response = DynSolValue::Array(vec![ + DynSolValue::Address(H160([1; 20]).into_alloy()), + DynSolValue::Address(H160([4; 20]).into_alloy()), // Mismatched token + ]); + let balances_response = DynSolValue::Array(vec![ + DynSolValue::Uint(U256::zero().into_alloy(), 256), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + ]); + let last_block_reponse = DynSolValue::Uint(U256::zero().into_alloy(), 256); + BalancerV2Vault::abi_functions_by_name("getPoolTokens") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[tokens_response, balances_response, last_block_reponse]) + .unwrap() + }; + asserter.push_success(&get_pool_tokens_response); let token_infos = MockTokenInfoFetching::new(); let pool_info_fetcher = PoolInfoFetcher { - vault: BalancerV2Vault::at(&web3, vault.address()), + vault, factory: MockFactoryIndexing::new(), token_infos: Arc::new(token_infos), }; let pool_info = PoolInfo { id: Default::default(), - address: pool.address(), + address: pool.address().into_legacy(), tokens: tokens.to_vec(), scaling_factors: vec![Bfp::exp10(0), Bfp::exp10(0), Bfp::exp10(0)], block_created: 1337, }; let pool_state = { - let block = web3.eth().block_number().await.unwrap(); - - let pool_state = pool_info_fetcher.fetch_common_pool_state(&pool_info, block.into()); + let pool_state = pool_info_fetcher.fetch_common_pool_state( + &pool_info, + BlockId::Number(BlockNumber::Number(1.into())), + ); pool_state.await }; @@ -529,19 +642,38 @@ mod tests { async fn fetch_specialized_pool_state() { let swap_fee = bfp!("0.003"); - let mock = Mock::new(42); - let web3 = mock.web3(); - - let pool = mock.deploy(BalancerV2WeightedPool::raw_contract().interface.abi.clone()); - pool.expect_call(BalancerV2WeightedPool::signatures().get_paused_state()) - .returns((false, 0.into(), 0.into())); - pool.expect_call(BalancerV2WeightedPool::signatures().get_swap_fee_percentage()) - .returns(swap_fee.as_uint256()); + let asserter = Asserter::new(); + let provider = ProviderBuilder::new() + .connect_mocked_client(asserter.clone()) + .erased(); + + let pool = BalancerV2BasePool::Instance::new(H160::random().into_alloy(), provider.clone()); + let vault = BalancerV2Vault::Instance::new(H160::random().into_alloy(), provider.clone()); + + let get_paused_state_response = BalancerV2BasePool::abi_functions_by_name("getPausedState") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[ + DynSolValue::Bool(false), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + ]) + .unwrap(); + asserter.push_success(&get_paused_state_response); + let get_swap_fee_percentage_response = + BalancerV2BasePool::abi_functions_by_name("getSwapFeePercentage") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[DynSolValue::Uint(swap_fee.as_uint256().into_alloy(), 256)]) + .unwrap(); + asserter.push_success(&get_swap_fee_percentage_response); let pool_info = weighted::PoolInfo { common: PoolInfo { id: H256([0x90; 32]), - address: pool.address(), + address: pool.address().into_legacy(), tokens: vec![H160([1; 20]), H160([2; 20]), H160([3; 20])], scaling_factors: vec![Bfp::exp10(0), Bfp::exp10(0), Bfp::exp10(12)], block_created: 1337, @@ -576,29 +708,41 @@ mod tests { version: Default::default(), }; - let vault = mock.deploy(BalancerV2Vault::raw_contract().interface.abi.clone()); - vault - .expect_call(BalancerV2Vault::signatures().get_pool_tokens()) - .predicate((predicate::eq(Bytes(pool_info.common.id.0)),)) - .returns(( - pool_info.common.tokens.clone(), + let get_pool_tokens_response = { + let tokens_response = DynSolValue::Array( + pool_info + .common + .tokens + .iter() + .copied() + .map(|t| DynSolValue::Address(t.into_alloy())) + .collect(), + ); + let balances_response = DynSolValue::Array( pool_state .tokens .values() - .map(|token| token.common.balance) + .map(|token| DynSolValue::Uint(token.common.balance.into_alloy(), 256)) .collect(), - 0.into(), - )); - - let block = web3.eth().block_number().await.unwrap(); + ); + let last_block_reponse = DynSolValue::Uint(U256::zero().into_alloy(), 256); + BalancerV2Vault::abi_functions_by_name("getPoolTokens") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[tokens_response, balances_response, last_block_reponse]) + .unwrap() + }; + asserter.push_success(&get_pool_tokens_response); let mut factory = MockFactoryIndexing::new(); + let block_id = BlockId::Number(BlockNumber::Number(1.into())); factory .expect_fetch_pool_state() .with( predicate::eq(pool_info.clone()), predicate::always(), - predicate::eq(BlockId::from(block)), + predicate::eq(block_id), ) .returning({ let pool_state = pool_state.clone(); @@ -606,13 +750,13 @@ mod tests { }); let pool_info_fetcher = PoolInfoFetcher { - vault: BalancerV2Vault::at(&web3, vault.address()), + vault, factory, token_infos: Arc::new(MockTokenInfoFetching::new()), }; let pool_status = pool_info_fetcher - .fetch_pool(&pool_info, block.into()) + .fetch_pool(&pool_info, block_id) .await .unwrap(); @@ -627,19 +771,47 @@ mod tests { #[tokio::test] async fn fetch_specialized_pool_state_for_paused_pool() { - let mock = Mock::new(42); - let web3 = mock.web3(); - - let pool = mock.deploy(BalancerV2WeightedPool::raw_contract().interface.abi.clone()); - pool.expect_call(BalancerV2WeightedPool::signatures().get_paused_state()) - .returns((true, 0.into(), 0.into())); - pool.expect_call(BalancerV2WeightedPool::signatures().get_swap_fee_percentage()) - .returns(Default::default()); + let asserter = Asserter::new(); + let provider = ProviderBuilder::new() + .connect_mocked_client(asserter.clone()) + .erased(); + + let pool = BalancerV2BasePool::Instance::new(H160::random().into_alloy(), provider.clone()); + let vault = BalancerV2Vault::Instance::new(H160::random().into_alloy(), provider.clone()); + + let get_paused_state_response = BalancerV2BasePool::abi_functions_by_name("getPausedState") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[ + DynSolValue::Bool(true), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + ]) + .unwrap(); + asserter.push_success(&get_paused_state_response); - let vault = mock.deploy(BalancerV2Vault::raw_contract().interface.abi.clone()); - vault - .expect_call(BalancerV2Vault::signatures().get_pool_tokens()) - .returns(Default::default()); + let get_swap_fee_percentage_response = + BalancerV2BasePool::abi_functions_by_name("getSwapFeePercentage") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[DynSolValue::Uint(U256::zero().into_alloy(), 256)]) + .unwrap(); + asserter.push_success(&get_swap_fee_percentage_response); + + let get_pool_tokens_response = { + let tokens_response = DynSolValue::Array(vec![]); + let balances_response = DynSolValue::Array(vec![]); + let last_block_reponse = DynSolValue::Uint(U256::zero().into_alloy(), 256); + BalancerV2Vault::abi_functions_by_name("getPoolTokens") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[tokens_response, balances_response, last_block_reponse]) + .unwrap() + }; + asserter.push_success(&get_pool_tokens_response); let mut factory = MockFactoryIndexing::new(); factory @@ -659,14 +831,14 @@ mod tests { }); let pool_info_fetcher = PoolInfoFetcher { - vault: BalancerV2Vault::at(&web3, vault.address()), + vault, factory, token_infos: Arc::new(MockTokenInfoFetching::new()), }; let pool_info = weighted::PoolInfo { common: PoolInfo { id: Default::default(), - address: pool.address(), + address: pool.address().into_legacy(), tokens: Default::default(), scaling_factors: Default::default(), block_created: Default::default(), @@ -675,9 +847,8 @@ mod tests { }; let pool_status = { - let block = web3.eth().block_number().await.unwrap(); pool_info_fetcher - .fetch_pool(&pool_info, block.into()) + .fetch_pool(&pool_info, BlockId::Number(BlockNumber::Number(1.into()))) .await .unwrap() }; @@ -687,19 +858,47 @@ mod tests { #[tokio::test] async fn fetch_specialized_pool_state_for_disabled_pool() { - let mock = Mock::new(42); - let web3 = mock.web3(); - - let pool = mock.deploy(BalancerV2WeightedPool::raw_contract().interface.abi.clone()); - pool.expect_call(BalancerV2WeightedPool::signatures().get_paused_state()) - .returns((false, 0.into(), 0.into())); - pool.expect_call(BalancerV2WeightedPool::signatures().get_swap_fee_percentage()) - .returns(Default::default()); + let asserter = Asserter::new(); + let provider = ProviderBuilder::new() + .connect_mocked_client(asserter.clone()) + .erased(); + + let pool = BalancerV2BasePool::Instance::new(H160::random().into_alloy(), provider.clone()); + let vault = BalancerV2Vault::Instance::new(H160::random().into_alloy(), provider.clone()); + + let get_paused_state_response = BalancerV2BasePool::abi_functions_by_name("getPausedState") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[ + DynSolValue::Bool(false), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + DynSolValue::Uint(U256::zero().into_alloy(), 256), + ]) + .unwrap(); + asserter.push_success(&get_paused_state_response); - let vault = mock.deploy(BalancerV2Vault::raw_contract().interface.abi.clone()); - vault - .expect_call(BalancerV2Vault::signatures().get_pool_tokens()) - .returns(Default::default()); + let get_swap_fee_percentage_response = + BalancerV2BasePool::abi_functions_by_name("getSwapFeePercentage") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[DynSolValue::Uint(U256::zero().into_alloy(), 256)]) + .unwrap(); + asserter.push_success(&get_swap_fee_percentage_response); + + let get_pool_tokens_response = { + let tokens_response = DynSolValue::Array(vec![]); + let balances_response = DynSolValue::Array(vec![]); + let last_block_reponse = DynSolValue::Uint(U256::zero().into_alloy(), 256); + BalancerV2Vault::abi_functions_by_name("getPoolTokens") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[tokens_response, balances_response, last_block_reponse]) + .unwrap() + }; + asserter.push_success(&get_pool_tokens_response); let mut factory = MockFactoryIndexing::new(); factory @@ -712,14 +911,14 @@ mod tests { .returning(|_, _, _| future::ready(Ok(None)).boxed()); let pool_info_fetcher = PoolInfoFetcher { - vault: BalancerV2Vault::at(&web3, vault.address()), + vault, factory, token_infos: Arc::new(MockTokenInfoFetching::new()), }; let pool_info = weighted::PoolInfo { common: PoolInfo { id: Default::default(), - address: pool.address(), + address: pool.address().into_legacy(), tokens: Default::default(), scaling_factors: Default::default(), block_created: Default::default(), @@ -728,9 +927,8 @@ mod tests { }; let pool_status = { - let block = web3.eth().block_number().await.unwrap(); pool_info_fetcher - .fetch_pool(&pool_info, block.into()) + .fetch_pool(&pool_info, BlockId::Number(BlockNumber::Number(1.into()))) .await .unwrap() }; @@ -746,7 +944,10 @@ mod tests { .returning(|_| hashmap! {}); let pool_info_fetcher = PoolInfoFetcher { - vault: dummy_contract!(BalancerV2Vault, H160([0xba; 20])), + vault: BalancerV2Vault::Instance::new( + H160([0xba; 20]).into_alloy(), + ethrpc::mock::web3().alloy, + ), factory: MockFactoryIndexing::new(), token_infos: Arc::new(token_infos), }; @@ -769,7 +970,10 @@ mod tests { }); let pool_info_fetcher = PoolInfoFetcher { - vault: dummy_contract!(BalancerV2Vault, H160([0xba; 20])), + vault: BalancerV2Vault::Instance::new( + H160([0xba; 20]).into_alloy(), + ethrpc::mock::web3().alloy, + ), factory: MockFactoryIndexing::new(), token_infos: Arc::new(token_infos), }; diff --git a/crates/solver/Cargo.toml b/crates/solver/Cargo.toml index deeec8329c..fd036272a8 100644 --- a/crates/solver/Cargo.toml +++ b/crates/solver/Cargo.toml @@ -11,6 +11,7 @@ path = "src/lib.rs" doctest = false [dependencies] +alloy = { workspace = true } anyhow = { workspace = true } arc-swap = { workspace = true } async-trait = { workspace = true } diff --git a/crates/solver/src/interactions/balancer_v2.rs b/crates/solver/src/interactions/balancer_v2.rs index 6239843568..a91d4f8810 100644 --- a/crates/solver/src/interactions/balancer_v2.rs +++ b/crates/solver/src/interactions/balancer_v2.rs @@ -1,7 +1,8 @@ use { - contracts::{BalancerV2Vault, GPv2Settlement}, + alloy::primitives::U256, + contracts::{GPv2Settlement, alloy::BalancerV2Vault}, ethcontract::{Bytes, H256}, - primitive_types::U256, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, shared::{ http_solver::model::TokenAmount, interaction::{EncodedInteraction, Interaction}, @@ -12,7 +13,7 @@ use { #[derive(Clone, Debug)] pub struct BalancerSwapGivenOutInteraction { pub settlement: GPv2Settlement, - pub vault: BalancerV2Vault, + pub vault: BalancerV2Vault::Instance, pub pool_id: H256, pub asset_in_max: TokenAmount, pub asset_out: TokenAmount, @@ -26,26 +27,36 @@ pub static NEVER: LazyLock = LazyLock::new(|| U256::from(1) << 255); impl BalancerSwapGivenOutInteraction { pub fn encode_swap(&self) -> EncodedInteraction { - let method = self.vault.swap( - ( - Bytes(self.pool_id.0), - 1, // GivenOut, - self.asset_in_max.token, - self.asset_out.token, - self.asset_out.amount, - self.user_data.clone(), - ), - ( - self.settlement.address(), // sender - false, // fromInternalBalance - self.settlement.address(), // recipient - false, // toInternalBalance - ), - self.asset_in_max.amount, - *NEVER, - ); - let calldata = method.tx.data.expect("no calldata").0; - (self.vault.address(), 0.into(), Bytes(calldata)) + let single_swap = BalancerV2Vault::IVault::SingleSwap { + poolId: self.pool_id.into_alloy(), + kind: 1, // GivenOut + assetIn: self.asset_in_max.token.into_alloy(), + assetOut: self.asset_out.token.into_alloy(), + amount: self.asset_out.amount.into_alloy(), + userData: self.user_data.clone().into_alloy(), + }; + let funds = BalancerV2Vault::IVault::FundManagement { + sender: self.settlement.address().into_alloy(), + fromInternalBalance: false, + recipient: self.settlement.address().into_alloy(), + toInternalBalance: false, + }; + let method = self + .vault + .swap( + single_swap, + funds, + self.asset_in_max.amount.into_alloy(), + *NEVER, + ) + .calldata() + .clone(); + + ( + self.vault.address().into_legacy(), + 0.into(), + Bytes(method.to_vec()), + ) } } @@ -61,7 +72,7 @@ mod tests { #[test] fn encode_unwrap_weth() { - let vault = dummy_contract!(BalancerV2Vault, [0x01; 20]); + let vault = BalancerV2Vault::Instance::new([0x01; 20].into(), ethrpc::mock::web3().alloy); let interaction = BalancerSwapGivenOutInteraction { settlement: dummy_contract!(GPv2Settlement, [0x02; 20]), vault: vault.clone(), @@ -95,7 +106,7 @@ mod tests { assert_eq!( interaction.encode(), ( - vault.address(), + vault.address().into_legacy(), 0.into(), Bytes( hex::decode( diff --git a/crates/solver/src/liquidity/balancer_v2.rs b/crates/solver/src/liquidity/balancer_v2.rs index 1d635ec8e4..441cbcf43d 100644 --- a/crates/solver/src/liquidity/balancer_v2.rs +++ b/crates/solver/src/liquidity/balancer_v2.rs @@ -17,8 +17,9 @@ use { settlement::SettlementEncoder, }, anyhow::Result, - contracts::{BalancerV2Vault, GPv2Settlement}, + contracts::{GPv2Settlement, alloy::BalancerV2Vault}, ethcontract::H256, + ethrpc::alloy::conversions::IntoLegacy, model::TokenPair, shared::{ ethrpc::Web3, @@ -33,7 +34,7 @@ use { /// A liquidity provider for Balancer V2 weighted pools. pub struct BalancerV2Liquidity { settlement: GPv2Settlement, - vault: BalancerV2Vault, + vault: BalancerV2Vault::Instance, pool_fetcher: Arc, allowance_manager: Box, } @@ -43,7 +44,7 @@ impl BalancerV2Liquidity { web3: Web3, pool_fetcher: Arc, settlement: GPv2Settlement, - vault: BalancerV2Vault, + vault: BalancerV2Vault::Instance, ) -> Self { let allowance_manager = AllowanceManager::new(web3, settlement.address()); Self { @@ -65,7 +66,7 @@ impl BalancerV2Liquidity { let allowances = self .allowance_manager - .get_allowances(tokens, self.vault.address()) + .get_allowances(tokens, self.vault.address().into_legacy()) .await?; let inner = Arc::new(Inner { @@ -134,7 +135,7 @@ pub struct SettlementHandler { struct Inner { settlement: GPv2Settlement, - vault: BalancerV2Vault, + vault: BalancerV2Vault::Instance, allowances: Allowances, } @@ -142,7 +143,7 @@ impl SettlementHandler { pub fn new( pool_id: H256, settlement: GPv2Settlement, - vault: BalancerV2Vault, + vault: BalancerV2Vault::Instance, allowances: Allowances, ) -> Self { SettlementHandler { @@ -155,7 +156,7 @@ impl SettlementHandler { } } - pub fn vault(&self) -> &BalancerV2Vault { + pub fn vault(&self) -> &BalancerV2Vault::Instance { &self.inner.vault } @@ -258,10 +259,10 @@ mod tests { }, }; - fn dummy_contracts() -> (GPv2Settlement, BalancerV2Vault) { + fn dummy_contracts() -> (GPv2Settlement, BalancerV2Vault::Instance) { ( dummy_contract!(GPv2Settlement, H160([0xc0; 20])), - dummy_contract!(BalancerV2Vault, H160([0xc1; 20])), + BalancerV2Vault::Instance::new([0xc1; 20].into(), ethrpc::mock::web3().alloy), ) } @@ -454,7 +455,7 @@ mod tests { settlement: settlement.clone(), vault: vault.clone(), allowances: Allowances::new( - vault.address(), + vault.address().into_legacy(), hashmap! { H160([0x70; 20]) => 0.into(), H160([0x71; 20]) => 100.into(), @@ -496,7 +497,7 @@ mod tests { [ Approval { token: H160([0x70; 20]), - spender: vault.address(), + spender: vault.address().into_legacy(), } .encode(), BalancerSwapGivenOutInteraction { From 69dc5f033863fe447545a1b2578129acbdb8ede7 Mon Sep 17 00:00:00 2001 From: Kaze Date: Thu, 11 Sep 2025 22:59:07 +0900 Subject: [PATCH 019/112] feat(playground): update and fix issues with the playground (#3645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Duarte --- playground/Dockerfile.cowswap | 2 +- playground/Dockerfile.explorer | 13 ++++++------- playground/README.md | 1 + playground/docker-compose.fork.yml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/playground/Dockerfile.cowswap b/playground/Dockerfile.cowswap index d53bc2ab61..ab1a3d3b9f 100644 --- a/playground/Dockerfile.cowswap +++ b/playground/Dockerfile.cowswap @@ -1,5 +1,5 @@ # Stage 1: Build the frontend -FROM docker.io/node:22-bullseye-slim as node-build +FROM docker.io/node:22-bookworm-slim AS node-build WORKDIR /usr/src/app # RPC URL args diff --git a/playground/Dockerfile.explorer b/playground/Dockerfile.explorer index 656ca7910a..312742ec71 100644 --- a/playground/Dockerfile.explorer +++ b/playground/Dockerfile.explorer @@ -1,5 +1,5 @@ # Stage 1: Build the frontend -FROM --platform=linux/amd64 docker.io/node:16-bullseye-slim as node-build +FROM docker.io/node:22-bookworm-slim as node-build WORKDIR /usr/src/app # RPC URL args @@ -12,11 +12,10 @@ ARG REACT_APP_ORDER_BOOK_URLS='{"1":"https://api.cow.fi/mainnet","100":"https:// # Install dependencies RUN --mount=type=cache,target=/var/cache/apt,sharing=locked apt-get update && \ - apt-get install -y git libssl-dev pkg-config git autoconf automake file g++ libtool make nasm libpng-dev optipng - + apt-get install -y git libssl-dev pkg-config git autoconf automake file g++ libtool make python3 # Clone the repo to the present working directory -RUN git clone https://github.com/cowprotocol/explorer . && \ +RUN git clone https://github.com/cowprotocol/cowswap . && \ git submodule update --init --recursive # Install npm dependencies @@ -56,10 +55,10 @@ RUN if [ -n "$ETH_RPC_URL" ]; then \ fi # Build the frontend -RUN APP_ID=1 yarn build +RUN APP_ID=1 yarn build:explorer # Stage 2: Copy the frontend to the nginx container -FROM docker.io/nginx:1.21-alpine as frontend -COPY --from=node-build /usr/src/app/dist /usr/share/nginx/html +FROM docker.io/nginx:1.21-alpine AS frontend +COPY --from=node-build /usr/src/app/build/explorer /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/playground/README.md b/playground/README.md index d3b4827497..65d7293e20 100644 --- a/playground/README.md +++ b/playground/README.md @@ -40,6 +40,7 @@ Now with Rabby configured, and the services started, you can browse to http://lo > Initially you will start with 10000 ETH, to run proper transaction you will need to wrap some ETH first. > The EthFlow is not configured by default, the next section explains how to set it up. > You can follow along with watching the logs of the `autopilot`, `driver`, and `baseline` solver to see how the Protocol interacts. +> The CoW Explorer is avialable at http://localhost:8001 to see more information about transaction status ### Resetting the playground diff --git a/playground/docker-compose.fork.yml b/playground/docker-compose.fork.yml index 2123a53948..67d671b6ff 100644 --- a/playground/docker-compose.fork.yml +++ b/playground/docker-compose.fork.yml @@ -99,7 +99,7 @@ services: restart: always environment: - DB_URL=postgres://db:5432/?user=${POSTGRES_USER}&password=${POSTGRES_PASSWORD} - - LOG_FILTER=warn,autopilot=trace,shared=info,shared::price_estimation=debug + - LOG_FILTER=warn,autopilot=info,shared=info,shared::price_estimation=info - NODE_URL=http://chain:8545 - SIMULATION_NODE_URL=http://chain:8545 - SETTLE_INTERVAL=15s From 81c44a0faf6c7074e728c13c4c979d5daee0f6c5 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Thu, 11 Sep 2025 17:49:59 +0200 Subject: [PATCH 020/112] Opt-out of quote verification (#3650) # Description This PR is really stupid but unfortunately necessary at the moment... Some tokens are weird to route optimally and only few solvers support them. However, anybody can create a uniswap pool for these tokens which may cause any good solver to be able to route these tokens automatically. This is generally a good thing except in one particular edge case: If the "good" solver is not able to provide calldata with their quote that can be verified but the "bad" solver's calldata verifies just fine we'll currently show the quote from the bad solver despite having very high confidence that the other solver is actually better and knows how to route the tokens. To avoid showing terrible prices in this VERY specific scenario we need an escape hatch to conditionally bypass the quote verification. I decided to go for a simple token list. If either the sell or the buy token show up in the list that we define we'll skip the quote verification altogether. That way quote from all solvers (good and bad) will be unverified which levels the playing field and we again pick the quote based on the highest out amount. --------- Co-authored-by: ilya --- crates/e2e/tests/e2e/quote_verification.rs | 1 + crates/shared/src/price_estimation/factory.rs | 1 + crates/shared/src/price_estimation/mod.rs | 12 ++++ .../src/price_estimation/trade_verifier.rs | 67 ++++++++++--------- 4 files changed, 50 insertions(+), 31 deletions(-) diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 577e040857..623999033b 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -141,6 +141,7 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { onchain.contracts().gp_settlement.address(), onchain.contracts().weth.address(), BigDecimal::zero(), + Default::default(), ) .await .unwrap(); diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 883150aad0..28c95a8d83 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -125,6 +125,7 @@ impl<'a> PriceEstimatorFactory<'a> { network.settlement, network.native_token, args.quote_inaccuracy_limit.clone(), + args.tokens_without_verification.iter().cloned().collect(), ) .await?; Ok(Some(Arc::new(verifier))) diff --git a/crates/shared/src/price_estimation/mod.rs b/crates/shared/src/price_estimation/mod.rs index 616b8f9239..53cd207505 100644 --- a/crates/shared/src/price_estimation/mod.rs +++ b/crates/shared/src/price_estimation/mod.rs @@ -262,6 +262,13 @@ pub struct Arguments { value_parser = parse_tuple:: )] pub native_price_approximation_tokens: Vec<(H160, H160)>, + + /// Tokens for which quote verification should not be attempted. This is an + /// escape hatch when there is a very bad but verifiable liquidity source + /// that would win against a very good but unverifiable liquidity source + /// (e.g. private liquidity that exists but can't be verified). + #[clap(long, env, value_delimiter = ',')] + pub tokens_without_verification: Vec, } /// Custom Clap parser for tuple pair @@ -354,6 +361,7 @@ impl Display for Arguments { quote_timeout, balance_overrides, native_price_approximation_tokens, + tokens_without_verification, } = self; display_option( @@ -423,6 +431,10 @@ impl Display for Arguments { f, "native_price_approximation_tokens: {native_price_approximation_tokens:?}" )?; + writeln!( + f, + "tokens_without_verification: {tokens_without_verification:?}" + )?; Ok(()) } diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier.rs index fbcaff71d6..16ac4d2afb 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier.rs @@ -37,7 +37,10 @@ use { conversions::{big_decimal_to_big_rational, u256_to_big_rational}, nonzero::U256 as NonZeroU256, }, - std::{collections::HashMap, sync::Arc}, + std::{ + collections::{HashMap, HashSet}, + sync::Arc, + }, tracing::instrument, web3::{ethabi::Token, types::CallRequest}, }; @@ -67,6 +70,7 @@ pub struct TradeVerifier { native_token: H160, quote_inaccuracy_limit: BigRational, domain_separator: DomainSeparator, + tokens_without_verification: HashSet, } impl TradeVerifier { @@ -84,6 +88,7 @@ impl TradeVerifier { settlement: H160, native_token: H160, quote_inaccuracy_limit: BigDecimal, + tokens_without_verification: HashSet, ) -> Result { let settlement_contract = GPv2Settlement::at(&web3, settlement); let domain_separator = @@ -98,6 +103,7 @@ impl TradeVerifier { quote_inaccuracy_limit: big_decimal_to_big_rational("e_inaccuracy_limit), web3, domain_separator, + tokens_without_verification, }) } @@ -412,42 +418,41 @@ impl TradeVerifying for TradeVerifier { &query.kind, ) .context("failed to compute trade out amount")?; + + let unverified_result = trade + .gas_estimate() + .map(|gas| Estimate { + out_amount, + gas, + solver: trade.solver(), + verified: false, + execution: QuoteExecution { + interactions: map_interactions_data(&trade.interactions()), + pre_interactions: map_interactions_data(&trade.pre_interactions()), + jit_orders: trade.jit_orders(), + }, + }) + .context("solver provided no gas estimate"); + + let skip_verification = [&query.buy_token, &query.sell_token] + .iter() + .any(|token| self.tokens_without_verification.contains(token)); + if skip_verification { + tracing::debug!(estimate = ?unverified_result, "quote verification skipped"); + return unverified_result; + } + match self .verify_inner(query, verification.clone(), &trade, &out_amount) .await { Ok(verified) => Ok(verified), - Err(Error::SimulationFailed(err)) => match trade.gas_estimate() { - Some(gas) => { - let estimate = Estimate { - out_amount, - gas, - solver: trade.solver(), - verified: false, - execution: QuoteExecution { - interactions: map_interactions_data(&trade.interactions()), - pre_interactions: map_interactions_data(&trade.pre_interactions()), - jit_orders: trade.jit_orders(), - }, - }; - tracing::warn!( - ?err, - estimate = ?trade, - "failed verification; returning unverified estimate" - ); - Ok(estimate) - } - None => { - tracing::warn!( - ?err, - estimate = ?trade, - "failed verification and no gas estimate provided; discarding estimate" - ); - Err(err) - } - }, + Err(Error::SimulationFailed(err)) => { + tracing::debug!(estimate = ?unverified_result, ?err, "quote verification failed"); + unverified_result + } Err(err @ Error::TooInaccurate) => { - tracing::warn!("discarding quote because it's too inaccurate"); + tracing::debug!("discarding quote because it's too inaccurate"); Err(err.into()) } } From 331bfa8e83c190b8f57fd46835c0fdb891579707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 12 Sep 2025 10:06:58 +0100 Subject: [PATCH 021/112] Add MutWallet to the testing provider (#3651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the MutWallet to enable adding signers when needed. This is a "solution" to alloy-rs/alloy#2829 Since Alloy's Provider is built on layers and in the end it seals them up, it becomes impossible to change the signing wallet. The solution is to share the wallet behind the scenes through the classic Arc + lock (Rw in this case) The main challenge here was running tokio's locks in "sync" contexts: During tests we have "current_thread" so we can't run blocking operations (.blocking_read), we can however, run a separate thread and wait for it (ironic because it blocks the thread anyway — though this is ok because it's for tests) Outside tests, we're running the multi-thread runtime, thus spawning an extra thread and waiting is a waste because we're blocked waiting for it anyways, using block_in_place we can signal the runtime that we're in a pickle and we're going to block, evicting other tasks and keeping the system chugging along --- .../e2e/src/setup/onchain_components/mod.rs | 8 ++ .../e2e/src/setup/onchain_components/safe.rs | 30 ++-- crates/e2e/tests/e2e/hooks.rs | 3 +- crates/ethrpc/Cargo.toml | 2 +- crates/ethrpc/src/alloy/mod.rs | 27 +++- crates/ethrpc/src/alloy/wallet.rs | 135 ++++++++++++++++++ crates/ethrpc/src/block_stream/mod.rs | 4 +- crates/ethrpc/src/instrumented.rs | 1 + crates/ethrpc/src/lib.rs | 12 +- crates/ethrpc/src/mock.rs | 3 +- 10 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 crates/ethrpc/src/alloy/wallet.rs diff --git a/crates/e2e/src/setup/onchain_components/mod.rs b/crates/e2e/src/setup/onchain_components/mod.rs index 24c9f03373..8600556a00 100644 --- a/crates/e2e/src/setup/onchain_components/mod.rs +++ b/crates/e2e/src/setup/onchain_components/mod.rs @@ -3,6 +3,7 @@ use { nodes::forked_node::ForkedNodeApi, setup::{DeployedContracts, deploy::Contracts}, }, + ::alloy::signers::local::PrivateKeySigner, app_data::Hook, contracts::{CowProtocolToken, ERC20Mintable}, ethcontract::{ @@ -286,6 +287,9 @@ impl OnchainComponents { assert_eq!(res.len(), N); for account in &res { + let signer = PrivateKeySigner::from_slice(account.private_key()).unwrap(); + self.web3.wallet.register_signer(signer); + self.send_wei(account.address(), with_wei).await; } @@ -298,6 +302,10 @@ impl OnchainComponents { let solvers = self.make_accounts::(with_wei).await; for solver in &solvers { + self.web3 + .wallet + .register_signer(PrivateKeySigner::from_slice(solver.private_key()).unwrap()); + self.contracts .gp_authenticator .add_solver(solver.address()) diff --git a/crates/e2e/src/setup/onchain_components/safe.rs b/crates/e2e/src/setup/onchain_components/safe.rs index a6c8b7bed7..6490afb20f 100644 --- a/crates/e2e/src/setup/onchain_components/safe.rs +++ b/crates/e2e/src/setup/onchain_components/safe.rs @@ -12,13 +12,7 @@ use { GnosisSafeProxyFactory, }, ethcontract::transaction::TransactionBuilder, - ethrpc::{ - AlloyProvider, - alloy::{ - ProviderSignerExt, - conversions::{IntoAlloy, TryIntoAlloyAsync}, - }, - }, + ethrpc::{AlloyProvider, alloy::conversions::IntoAlloy}, hex_literal::hex, model::{ DomainSeparator, @@ -39,11 +33,8 @@ pub struct Infrastructure { impl Infrastructure { pub async fn new(provider: AlloyProvider) -> Self { - let first_account = *provider.get_accounts().await.unwrap().first().unwrap(); - let singleton = { let deployed_address = GnosisSafe::Instance::deploy_builder(provider.clone()) - .from(first_account) .deploy() .await .unwrap(); @@ -52,7 +43,6 @@ impl Infrastructure { let fallback = { let deployed_address = GnosisSafeCompatibilityFallbackHandler::Instance::deploy_builder(provider.clone()) - .from(first_account) .deploy() .await .unwrap(); @@ -64,7 +54,6 @@ impl Infrastructure { let factory = { let deployed_address = GnosisSafeProxyFactory::Instance::deploy_builder(provider.clone()) - .from(first_account) .deploy() .await .unwrap(); @@ -84,15 +73,14 @@ impl Infrastructure { owners: Vec, threshold: usize, ) -> GnosisSafe::Instance { - let provider = self - .provider - .with_signer(owners[0].account().clone().try_into_alloy().await.unwrap()); - let safe_proxy = - GnosisSafeProxy::Instance::deploy_builder(provider.clone(), *self.singleton.address()) - .deploy() - .await - .unwrap(); - let safe = GnosisSafe::Instance::new(safe_proxy, provider.clone()); + let safe_proxy = GnosisSafeProxy::Instance::deploy_builder( + self.provider.clone(), + *self.singleton.address(), + ) + .deploy() + .await + .unwrap(); + let safe = GnosisSafe::Instance::new(safe_proxy, self.provider.clone()); contracts::alloy::tx!( safe.setup( diff --git a/crates/e2e/tests/e2e/hooks.rs b/crates/e2e/tests/e2e/hooks.rs index 5dfd68d9cd..3d91fd33c2 100644 --- a/crates/e2e/tests/e2e/hooks.rs +++ b/crates/e2e/tests/e2e/hooks.rs @@ -546,8 +546,7 @@ async fn quote_verification(web3: Web3) { .clone(), ); let safe_address = safe_creation_builder.clone().call().await.unwrap(); - let first_account = *web3.alloy.get_accounts().await.unwrap().first().unwrap(); - contracts::alloy::tx!(safe_creation_builder, first_account); + contracts::alloy::tx!(safe_creation_builder); let safe = Safe::deployed( chain_id, diff --git a/crates/ethrpc/Cargo.toml b/crates/ethrpc/Cargo.toml index 7fb65696bf..a1ba41c8cc 100644 --- a/crates/ethrpc/Cargo.toml +++ b/crates/ethrpc/Cargo.toml @@ -29,7 +29,7 @@ reqwest = { workspace = true, features = ["cookies"] } scopeguard = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -tokio = { workspace = true, features = [] } +tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } tokio-stream = { workspace = true } tower = { workspace = true } tracing = { workspace = true } diff --git a/crates/ethrpc/src/alloy/mod.rs b/crates/ethrpc/src/alloy/mod.rs index 167ccc6396..90c0015b9e 100644 --- a/crates/ethrpc/src/alloy/mod.rs +++ b/crates/ethrpc/src/alloy/mod.rs @@ -2,6 +2,8 @@ mod buffering; pub mod conversions; mod instrumentation; +mod wallet; + use { crate::AlloyProvider, alloy::{ @@ -12,22 +14,35 @@ use { buffering::BatchCallLayer, instrumentation::{InstrumentationLayer, LabelingLayer}, }; -pub use {conversions::Account, instrumentation::ProviderLabelingExt}; +pub use {conversions::Account, instrumentation::ProviderLabelingExt, wallet::MutWallet}; -pub fn provider(url: &str) -> AlloyProvider { - let rpc = ClientBuilder::default() +/// Creates an [`RpcClient`] from the given URL with [`LabelingLayer`], +/// [`InstrumentationLayer`] and [`BatchCallLayer`]. +fn rpc(url: &str) -> RpcClient { + ClientBuilder::default() .layer(LabelingLayer { label: "main".into(), }) .layer(InstrumentationLayer) .layer(BatchCallLayer::new(Default::default())) - .http(url.parse().unwrap()); - ProviderBuilder::new() + .http(url.parse().unwrap()) +} + +/// Creates a provider with the provided URL and an empty [`MutWallet`]. +/// +/// Returns a copy of the [`MutWallet`] so the caller can modify it later. +pub fn provider(url: &str) -> (AlloyProvider, MutWallet) { + let rpc = rpc(url); + let wallet = MutWallet::default(); + let provider = ProviderBuilder::new() + .wallet(wallet.clone()) // will query the node for the nonce every time that it is needed // adds overhead but makes working with alloy/ethcontract at the same time much simpler .with_simple_nonce_management() .connect_client(rpc) - .erased() + .erased(); + + (provider, wallet) } pub trait ProviderSignerExt { diff --git a/crates/ethrpc/src/alloy/wallet.rs b/crates/ethrpc/src/alloy/wallet.rs new file mode 100644 index 0000000000..9875679531 --- /dev/null +++ b/crates/ethrpc/src/alloy/wallet.rs @@ -0,0 +1,135 @@ +use { + alloy::{ + consensus::{TxEnvelope, TypedTransaction}, + network::{Ethereum, EthereumWallet, Network, NetworkWallet, TxSigner}, + primitives::Address, + signers::Signature, + transports::impl_future, + }, + std::{sync::Arc, thread}, + tokio::sync::RwLock, +}; + +/// A mutable version of [`EthereumWallet`], cheaply cloneable (through +/// [`Arc`]). +/// +/// Requires a tokio runtime to be present, otherwise operations will panic. +#[derive(Debug, Clone, Default)] +pub struct MutWallet(Arc>); + +impl MutWallet { + pub fn new(wallet: EthereumWallet) -> Self { + Self(Arc::new(RwLock::new(wallet))) + } +} + +impl MutWallet { + /// Calls the inner [`EthereumWallet`]'s + /// [`register_signer`](EthereumWallet::register_signer), if no default + /// signer has been setup (i.e. the wallet was created using + /// [`MutWallet::default`]) it will register one. + pub fn register_signer(&mut self, signer: S) + where + S: TxSigner + Send + Sync + 'static, + { + self.handle_blocking_operation(move |wallet| { + // If the wallet is created using MutWallet::default(), there will not be + // default signer; this stops us from *not* using `.from` (since it + // is filled with the default signer). At the same time, we can't + // constantly register new default signers, because it breaks the caller's + // expectations. As such, if the current default signer address is + // the default address (0x000...000) we register the signer as the + // default one. + let register_default = { + let r_lock = wallet.0.blocking_read(); + let default_address = + >::default_signer_address(&r_lock); + + default_address == Address::default() + }; + + let mut w_lock = wallet.0.blocking_write(); + if register_default { + w_lock.register_default_signer(signer); + } else { + w_lock.register_signer(signer); + } + }); + } + + /// Handles blocking operations such as the + /// [`blocking_read`](RwLock::blocking_read) + /// and [`blocking_write`](RwLock::blocking_write). + /// + /// This function *will panic* in case there is no runtime present, or the + /// runtime flavor is not `current_thread` or `multi_thread`. + // This function is necessary to handle the blocking lock operations under + // required by synchronous function calls which are problematic when the runtime + // flavour is `current_thread` (which will panic when blocked by certain + // operations). + fn handle_blocking_operation(&self, f: F) -> R + where + F: FnOnce(Self) -> R + Send + 'static, + R: Send + 'static, + { + let wallet = self.clone(); + let rt = tokio::runtime::Handle::current(); + + match rt.runtime_flavor() { + tokio::runtime::RuntimeFlavor::CurrentThread => thread::spawn(move || f(wallet)) + .join() + .expect("failed to join thread"), + tokio::runtime::RuntimeFlavor::MultiThread => { + tokio::task::block_in_place(move || f(wallet)) + } + _ => panic!("unsupported runtime flavor"), + } + } +} + +impl NetworkWallet for MutWallet +where + N: Network, +{ + /// Get the default signer address. This address should be used + /// in [`NetworkWallet::sign_transaction_from`] when no specific signer is + /// specified. + fn default_signer_address(&self) -> Address { + self.handle_blocking_operation(|wallet| { + let r_lock = wallet.0.blocking_read(); + >::default_signer_address(&r_lock) + }) + } + + /// Return true if the signer contains a credential for the given address. + fn has_signer_for(&self, address: &Address) -> bool { + let address = *address; + self.handle_blocking_operation(move |wallet| { + let r_lock = wallet.0.blocking_read(); + >::has_signer_for(&r_lock, &address) + }) + } + + /// Return an iterator of all signer addresses. + fn signer_addresses(&self) -> impl Iterator { + self.handle_blocking_operation(move |wallet| { + let r_lock = wallet.0.blocking_read(); + >::signer_addresses(&r_lock).collect::>() + }) + .into_iter() + } + + /// Asynchronously sign an unsigned transaction, with a specified + /// credential. + #[doc(alias = "sign_tx_from")] + fn sign_transaction_from( + &self, + sender: Address, + tx: N::UnsignedTx, + ) -> impl_future!(>) { + async move { + let r_lock = self.0.read().await; + >::sign_transaction_from(&r_lock, sender, tx).await + } + } +} diff --git a/crates/ethrpc/src/block_stream/mod.rs b/crates/ethrpc/src/block_stream/mod.rs index 7f732b4f16..fbdc6bf85b 100644 --- a/crates/ethrpc/src/block_stream/mod.rs +++ b/crates/ethrpc/src/block_stream/mod.rs @@ -125,10 +125,12 @@ pub async fn current_block_stream( url.clone(), "block_stream".into(), ))); + let (alloy, wallet) = crate::alloy::provider(url.as_str()); let web3 = crate::Web3 { legacy: web3, // TODO: replace this with an unbuffered alloy provider - alloy: crate::alloy::provider(url.as_str()), + alloy, + wallet, }; let web3 = instrument_with_label(&web3, "base_currentBlockStream".into()); diff --git a/crates/ethrpc/src/instrumented.rs b/crates/ethrpc/src/instrumented.rs index d81fc14b0c..0cb9bf9ffb 100644 --- a/crates/ethrpc/src/instrumented.rs +++ b/crates/ethrpc/src/instrumented.rs @@ -81,6 +81,7 @@ pub fn instrument_with_label(web3: &crate::Web3, label: String) -> crate::Web3 { crate::Web3 { legacy: web3::Web3::new(DynTransport::new(instrumented)), alloy: web3.alloy.labeled(label), + wallet: web3.wallet.clone(), } } diff --git a/crates/ethrpc/src/lib.rs b/crates/ethrpc/src/lib.rs index eea5f99a32..2ca0f44dd8 100644 --- a/crates/ethrpc/src/lib.rs +++ b/crates/ethrpc/src/lib.rs @@ -10,6 +10,7 @@ pub mod multicall; use { self::{buffered::BufferedTransport, http::HttpTransport}, + crate::alloy::MutWallet, ::alloy::providers::DynProvider, ethcontract::{batch::CallBatch, transport::DynTransport}, reqwest::{Client, Url}, @@ -33,6 +34,7 @@ pub type AlloyProvider = DynProvider; pub struct Web3 { pub legacy: web3::Web3, pub alloy: AlloyProvider, + pub wallet: MutWallet, } impl std::ops::Deref for Web3 { @@ -53,9 +55,11 @@ impl Web3 { pub fn new_from_url(url: &str) -> Self { let legacy_transport = create_test_transport(url); let web3 = web3::Web3::new(legacy_transport); + let (alloy, wallet) = crate::alloy::provider(url); Self { legacy: web3, - alloy: crate::alloy::provider(url), + alloy, + wallet, } } } @@ -117,14 +121,16 @@ pub fn web3( None => Web3Transport::new(http), }; let instrumented = instrumented::InstrumentedTransport::new(name.to_string(), transport); + let (alloy, wallet) = alloy::provider(url.as_str()); Web3 { legacy: web3::Web3::new(Web3Transport::new(instrumented)), - alloy: alloy::provider(url.as_str()), + alloy, + wallet, } } /// Convenience method to create a transport from a URL. -fn create_test_transport(url: &str) -> Web3Transport { +pub fn create_test_transport(url: &str) -> Web3Transport { let http_transport = HttpTransport::new( Client::builder() .timeout(Duration::from_secs(10)) diff --git a/crates/ethrpc/src/mock.rs b/crates/ethrpc/src/mock.rs index 514285e883..9a3c6c35a6 100644 --- a/crates/ethrpc/src/mock.rs +++ b/crates/ethrpc/src/mock.rs @@ -1,7 +1,7 @@ //! Mockable Web3 transport implementation. use { - crate::Web3, + crate::{Web3, alloy::MutWallet}, alloy::providers::{Provider, ProviderBuilder, mock::Asserter}, ethcontract::{ futures::future::{self, Ready}, @@ -29,6 +29,7 @@ pub fn web3() -> Web3 { alloy: ProviderBuilder::new() .connect_mocked_client(Asserter::new()) .erased(), + wallet: MutWallet::default(), } } From 3dbd47344461941046c6df368aa5f084fa4b2a8f Mon Sep 17 00:00:00 2001 From: Igor Markin Date: Fri, 12 Sep 2025 13:36:05 +0300 Subject: [PATCH 022/112] feat(e2e): driver config overrides (#3643) --- Cargo.lock | 1 + crates/e2e/Cargo.toml | 1 + crates/e2e/src/setup/colocation.rs | 30 +++- crates/e2e/src/setup/colocation/utils.rs | 1 + crates/e2e/src/setup/colocation/utils/toml.rs | 156 ++++++++++++++++++ 5 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 crates/e2e/src/setup/colocation/utils.rs create mode 100644 crates/e2e/src/setup/colocation/utils/toml.rs diff --git a/Cargo.lock b/Cargo.lock index 3a726d6261..d152b3578c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2547,6 +2547,7 @@ dependencies = [ "sqlx", "tempfile", "tokio", + "toml", "tracing", "warp", "web3", diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index d2052c6bfc..3d639a271e 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -40,6 +40,7 @@ solvers-dto = { workspace = true } sqlx = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["macros", "process"] } +toml = { workspace = true } tracing = { workspace = true } warp = { workspace = true } web3 = { workspace = true, features = ["http"] } diff --git a/crates/e2e/src/setup/colocation.rs b/crates/e2e/src/setup/colocation.rs index fddb9027f0..ae11b224c6 100644 --- a/crates/e2e/src/setup/colocation.rs +++ b/crates/e2e/src/setup/colocation.rs @@ -6,6 +6,8 @@ use { tokio::task::JoinHandle, }; +pub mod utils; + pub struct SolverEngine { pub name: String, pub endpoint: Url, @@ -107,6 +109,22 @@ pub fn start_driver( solvers: Vec, liquidity: LiquidityProvider, quote_using_limit_orders: bool, +) -> JoinHandle<()> { + start_driver_with_config_override( + contracts, + solvers, + liquidity, + quote_using_limit_orders, + None, + ) +} + +pub fn start_driver_with_config_override( + contracts: &Contracts, + solvers: Vec, + liquidity: LiquidityProvider, + quote_using_limit_orders: bool, + config_override: Option<&str>, ) -> JoinHandle<()> { let base_tokens: HashSet<_> = solvers .iter() @@ -208,7 +226,7 @@ factory = "{:?}" }) .unwrap_or_default(); - let config_file = config_tmp_file(format!( + let base_config = format!( r#" app-data-fetching-enabled = true orderbook-url = "http://localhost:8080" @@ -246,7 +264,15 @@ mempool = "public" contracts.weth.address(), contracts.balances.address(), contracts.signatures.address(), - )); + ); + + let final_config = if let Some(override_str) = config_override { + utils::toml::merge_raw(&base_config, override_str).expect("Failed to merge driver config") + } else { + base_config + }; + + let config_file = config_tmp_file(final_config); let args = vec![ "driver".to_string(), format!("--config={}", config_file.display()), diff --git a/crates/e2e/src/setup/colocation/utils.rs b/crates/e2e/src/setup/colocation/utils.rs new file mode 100644 index 0000000000..47cc3312d7 --- /dev/null +++ b/crates/e2e/src/setup/colocation/utils.rs @@ -0,0 +1 @@ +pub mod toml; diff --git a/crates/e2e/src/setup/colocation/utils/toml.rs b/crates/e2e/src/setup/colocation/utils/toml.rs new file mode 100644 index 0000000000..70a95d48a1 --- /dev/null +++ b/crates/e2e/src/setup/colocation/utils/toml.rs @@ -0,0 +1,156 @@ +use { + anyhow::{Context, Result}, + toml::{Table, Value}, +}; + +/// Merge two TOML strings, with the override taking precedence +/// +/// # Arguments +/// * `base` - The base TOML configuration as a string +/// * `overlay` - The override TOML configuration as a string +/// +/// # Returns +/// A merged TOML configuration string where override values take precedence +/// over base values. Tables are merged recursively, while arrays and primitive +/// values are replaced entirely. +pub fn merge_raw(base: &str, overlay: &str) -> Result { + // Parse base config + let mut base_table: Table = toml::from_str(base).context("Failed to parse base TOML config")?; + + // Parse overlay config + let overlay_table: Table = + toml::from_str(overlay).context("Failed to parse overlay TOML config")?; + + // Apply overlay to base + merge_tables(&mut base_table, &overlay_table); + + // Serialize back to string + toml::to_string_pretty(&base_table).context("Failed to serialize TOML config") +} + +/// Recursively merge two TOML tables +fn merge_tables(base: &mut Table, overlay: &Table) { + for (key, value) in overlay { + match (base.get_mut(key), value) { + (Some(Value::Table(base_table)), Value::Table(overlay_table)) => { + // Both are tables, merge recursively + merge_tables(base_table, overlay_table); + } + _ => { + // Replace value entirely (including arrays) + base.insert(key.clone(), value.clone()); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_field_override() { + let base_config = r#" +orderbook-url = "http://localhost:8080" +"#; + + let raw_override = r#" +orderbook-url = "http://remotehost:8080" +"#; + + let result = merge_raw(base_config, raw_override).unwrap(); + + let parsed: Table = toml::from_str(&result).unwrap(); + + // Check that field was overridden + let orderbook_url = parsed.get("orderbook-url").unwrap().as_str().unwrap(); + assert_eq!(orderbook_url, "http://remotehost:8080"); + } + + #[test] + fn test_section_override() { + let base_config = r#" +[database] +host = "localhost" +port = 5432 + +[logging] +level = "info" +"#; + + let raw_override = r#" +[database] +host = "remote-host" +port = 3306 +ssl = true + +[metrics] +enabled = true +port = 9090 +"#; + + let result = merge_raw(base_config, raw_override).unwrap(); + + let parsed: Table = toml::from_str(&result).unwrap(); + + // Check that database section was merged + let database = parsed.get("database").unwrap().as_table().unwrap(); + assert_eq!( + database.get("host").unwrap().as_str().unwrap(), + "remote-host" + ); + assert_eq!(database.get("port").unwrap().as_integer().unwrap(), 3306); + assert!(database.get("ssl").unwrap().as_bool().unwrap()); + + // Check that new metrics section was added + let metrics = parsed.get("metrics").unwrap().as_table().unwrap(); + assert!(metrics.get("enabled").unwrap().as_bool().unwrap()); + assert_eq!(metrics.get("port").unwrap().as_integer().unwrap(), 9090); + + // Check that logging section remains unchanged + let logging = parsed.get("logging").unwrap().as_table().unwrap(); + assert_eq!(logging.get("level").unwrap().as_str().unwrap(), "info"); + } + + #[test] + fn test_array_override() { + let base_config = r#" +[[servers]] +name = "server1" +port = 8080 + +[config] +active = true +"#; + + let raw_override = r#" +[[servers]] +name = "new-server1" +port = 9000 + +[[servers]] +name = "new-server2" +port = 9001 +"#; + + let result = merge_raw(base_config, raw_override).unwrap(); + + let parsed: Table = toml::from_str(&result).unwrap(); + + // Arrays are replaced entirely, not merged + let servers = parsed.get("servers").unwrap().as_array().unwrap(); + assert_eq!(servers.len(), 2); + assert_eq!( + servers[0].get("name").unwrap().as_str().unwrap(), + "new-server1" + ); + assert_eq!( + servers[1].get("name").unwrap().as_str().unwrap(), + "new-server2" + ); + + // Other sections remain + let config = parsed.get("config").unwrap().as_table().unwrap(); + assert!(config.get("active").unwrap().as_bool().unwrap()); + } +} From c1d53e83a00dbba375d43c38ac64ff7dd52842fb Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 12 Sep 2025 17:07:29 +0200 Subject: [PATCH 023/112] Use caching balance fetcher in the driver (#3652) # Description Fetching balances is currently the biggest time sink in the auction pre-processing step. Interestingly the autopilot is able to it way faster with many more orders. So the easiest way to speed things up in the driver is to use the same component the autopilot uses for that. Instead of fetching the balances only after we receive the auction we now have a background task that keeps track of used balances and updates them on every new block. If an auction contains a new order that has no balance yet the balance will be fetched on demand. # Changes Use caching account balance fetching in the driver. Unfortunately the code is a bit ugly because the `shared` code uses different types than the `driver` code so there are quite a few conversions needed. By using the same data types we could probably reduce the times a bit more by avoiding expensive conversions and clones. ## How to test ran a test on shadow mainnet and the metrics show significant improvements. Total pre-processing time goes from the 750-1000ms bucket to primarily the 250-500ms bucket Screenshot 2025-09-11 at 12 59 06 balance fetching goes from 500-750ms to 50-100ms Screenshot 2025-09-11 at 12 59 18 solver time to compute solutions goes from 9s to 9.5s Screenshot 2025-09-11 at 12 59 25 --- .../src/domain/competition/order/mod.rs | 2 +- .../src/domain/competition/pre_processing.rs | 98 ++++++++++++------- crates/driver/src/infra/api/mod.rs | 15 ++- crates/driver/src/run.rs | 1 - 4 files changed, 74 insertions(+), 42 deletions(-) diff --git a/crates/driver/src/domain/competition/order/mod.rs b/crates/driver/src/domain/competition/order/mod.rs index e3ca8e9b60..d6bb460168 100644 --- a/crates/driver/src/domain/competition/order/mod.rs +++ b/crates/driver/src/domain/competition/order/mod.rs @@ -350,7 +350,7 @@ impl From for BuyTokenBalance { /// The address which placed the order. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Into)] -pub struct Trader(eth::Address); +pub struct Trader(pub eth::Address); /// A just-in-time order. JIT orders are added at solving time by the solver to /// generate a more optimal solution for the auction. Very similar to a regular diff --git a/crates/driver/src/domain/competition/pre_processing.rs b/crates/driver/src/domain/competition/pre_processing.rs index 58ccfcfe80..028363e9fc 100644 --- a/crates/driver/src/domain/competition/pre_processing.rs +++ b/crates/driver/src/domain/competition/pre_processing.rs @@ -1,7 +1,7 @@ use { super::{Auction, Order, order}, crate::{ - domain::{eth, liquidity}, + domain::{competition::order::SellTokenBalance, eth, liquidity}, infra::{self, api::routes::solve::dto::SolveRequest, observe::metrics, tokens}, util::Bytes, }, @@ -12,8 +12,15 @@ use { future::{BoxFuture, join_all}, }, itertools::Itertools, - model::{order::OrderKind, signature::Signature}, - shared::signature_validator::SignatureValidating, + model::{ + interaction::InteractionData, + order::{OrderKind, SellTokenSource}, + signature::Signature, + }, + shared::{ + account_balances::{BalanceFetching, Query}, + signature_validator::SignatureValidating, + }, std::{collections::HashMap, future::Future, sync::Arc}, tap::TapFallible, tokio::sync::Mutex, @@ -21,6 +28,7 @@ use { }; type Shared = futures::future::Shared>; + type BalanceGroup = (order::Trader, eth::TokenAddress, order::SellTokenBalance); type Balances = HashMap; @@ -48,7 +56,7 @@ pub struct Utilities { app_data_retriever: Option, liquidity_fetcher: infra::liquidity::Fetcher, tokens: tokens::Fetcher, - disable_access_list_simulation: bool, + balance_fetcher: Arc, } impl std::fmt::Debug for Utilities { @@ -108,8 +116,8 @@ impl DataAggregator { eth: infra::Ethereum, app_data_retriever: Option, liquidity_fetcher: infra::liquidity::Fetcher, - disable_access_list_simulation: bool, tokens: tokens::Fetcher, + balance_fetcher: Arc, ) -> Self { let signature_validator = shared::signature_validator::validator( eth.web3(), @@ -126,8 +134,8 @@ impl DataAggregator { signature_validator, app_data_retriever, liquidity_fetcher, - disable_access_list_simulation, tokens, + balance_fetcher, }), control: Mutex::new(ControlBlock { solve_request: Default::default(), @@ -218,8 +226,7 @@ impl Utilities { /// Fetches the tradable balance for every order owner. async fn fetch_balances(self: Arc, auction: Arc) -> Arc { let _timer = metrics::get().processing_stage_timer("fetch_balances"); - let ethereum = self.eth.with_metric_label("orderBalances".into()); - let mut tokens: HashMap<_, _> = Default::default(); + // Collect trader/token/source/interaction tuples for fetching available // balances. Note that we are pessimistic here, if a trader is selling // the same token with the same source in two different orders using a @@ -227,7 +234,7 @@ impl Utilities { // pre-interactions were specified. This is done to avoid creating // dependencies between orders (i.e. order 1 is required for executing // order 2) which we currently cannot express with the solver interface. - let traders = auction + let queries = auction .orders .iter() .chunk_by(|order| (order.trader(), order.sell.token, order.sell_token_balance)) @@ -235,41 +242,56 @@ impl Utilities { .map(|((trader, token, source), mut orders)| { let first = orders.next().expect("group contains at least 1 order"); let mut others = orders; - tokens.entry(token).or_insert_with(|| ethereum.erc20(token)); - if others.all(|order| order.pre_interactions == first.pre_interactions) { - (trader, token, source, &first.pre_interactions[..]) - } else { - (trader, token, source, Default::default()) + let all_interactions_equal = + others.all(|order| order.pre_interactions == first.pre_interactions); + Query { + owner: trader.0.0, + token: token.0.0, + source: match source { + SellTokenBalance::Erc20 => SellTokenSource::Erc20, + SellTokenBalance::Internal => SellTokenSource::Internal, + SellTokenBalance::External => SellTokenSource::External, + }, + interactions: if all_interactions_equal { + first + .pre_interactions + .iter() + .map(|i| InteractionData { + target: i.target.0, + value: i.value.0, + call_data: i.call_data.0.clone(), + }) + .collect() + } else { + Vec::default() + }, } }) .collect::>(); - let balances = join_all(traders.into_iter().map( - |(trader, token, source, interactions)| { - let token_contract = tokens.get(&token); - let token_contract = token_contract.expect("all tokens were created earlier"); - let fetch_balance = token_contract.tradable_balance( - trader.into(), - source, - interactions, - self.disable_access_list_simulation, - ); - - async move { - let balance = fetch_balance.await; + let balances = self.balance_fetcher.get_balances(&queries).await; + + let result: HashMap<_, _> = queries + .into_iter() + .zip(balances) + .filter_map(|(query, balance)| { + let balance = balance.ok()?; + Some(( ( - (trader, token, source), - balance.map(order::SellAmount::from).ok(), - ) - } - }, - )) - .await - .into_iter() - .filter_map(|(key, value)| Some((key, value?))) - .collect(); + order::Trader(query.owner.into()), + query.token.into(), + match query.source { + SellTokenSource::Erc20 => SellTokenBalance::Erc20, + SellTokenSource::Internal => SellTokenBalance::Internal, + SellTokenSource::External => SellTokenBalance::External, + }, + ), + order::SellAmount(balance), + )) + }) + .collect(); - Arc::new(balances) + Arc::new(result) } /// Fetches the app data for all orders in the auction. diff --git a/crates/driver/src/infra/api/mod.rs b/crates/driver/src/infra/api/mod.rs index 489a977ee5..7fe5ba559d 100644 --- a/crates/driver/src/infra/api/mod.rs +++ b/crates/driver/src/infra/api/mod.rs @@ -18,6 +18,7 @@ use { error::Error, futures::Future, observe::distributed_tracing::tracing_axum::{make_span, record_trace_id}, + shared::account_balances::{self, BalanceSimulator}, std::{net::SocketAddr, sync::Arc}, tokio::sync::oneshot, }; @@ -46,20 +47,30 @@ impl Api { shutdown: impl Future + Send + 'static, order_priority_strategies: Vec, app_data_retriever: Option, - disable_access_list_simulation: bool, ) -> Result<(), hyper::Error> { // Add middleware. let mut app = axum::Router::new().layer(tower::ServiceBuilder::new().layer( tower_http::limit::RequestBodyLimitLayer::new(REQUEST_BODY_LIMIT), )); + let balance_fetcher = account_balances::cached( + self.eth.web3(), + BalanceSimulator::new( + self.eth.contracts().settlement().clone(), + self.eth.contracts().balance_helper().clone(), + self.eth.contracts().vault_relayer().0, + Some(self.eth.contracts().vault().address()), + ), + self.eth.current_block().clone(), + ); + let tokens = tokens::Fetcher::new(&self.eth); let fetcher = Arc::new(domain::competition::DataAggregator::new( self.eth.clone(), app_data_retriever.clone(), self.liquidity.clone(), - disable_access_list_simulation, tokens.clone(), + balance_fetcher, )); let order_sorting_strategies = diff --git a/crates/driver/src/run.rs b/crates/driver/src/run.rs index 5b176dcbb5..8591df709d 100644 --- a/crates/driver/src/run.rs +++ b/crates/driver/src/run.rs @@ -103,7 +103,6 @@ async fn run_with(args: cli::Args, addr_sender: Option Date: Mon, 15 Sep 2025 11:13:53 +0200 Subject: [PATCH 024/112] Speed up saving solver_competitions (#3656) # Description Currently saving solver competitions is quite slow (100ms-500ms with outliers going over 3s!). AFAICS the way we handle uploading the JSON is very suboptimal: 1. we do `serde_json::to_value()` while `sqlx` later does `serde_json::to_string()` - we might as well directly do the conversion to string ourselves 2. we block the runtime while we serialize the solver competition JSON. Instead we can offload the serialization to a blocking task and let it already do work while we continue with the other DB queries. Screenshot 2025-09-12 at 22 03 01 # Changes Upload the JSON by inserting a JSON string directly. Offload the serialization to a blocking task and do the solver competition at the very end so we can already finish all the other DB queries in the mean time. This required me to take the `SolverCompetition` as an owned value which also enabled me to turn some `.iter()` into `.into_iter()` which gives the optimized more opportunities to avoid cloning memory. ## How to test updated the `roundtrip` test to work with the string approach --- crates/autopilot/src/database/competition.rs | 53 ++++++++++--------- crates/autopilot/src/infra/persistence/mod.rs | 2 +- crates/autopilot/src/run_loop.rs | 2 +- crates/database/src/solver_competition.rs | 13 +++-- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/crates/autopilot/src/database/competition.rs b/crates/autopilot/src/database/competition.rs index da9186505b..38a7915e63 100644 --- a/crates/autopilot/src/database/competition.rs +++ b/crates/autopilot/src/database/competition.rs @@ -33,23 +33,31 @@ pub struct Competition { } impl super::Postgres { - pub async fn save_competition(&self, competition: &Competition) -> anyhow::Result<()> { + pub async fn save_competition(&self, competition: Competition) -> anyhow::Result<()> { let _timer = super::Metrics::get() .database_queries .with_label_values(&["save_competition"]) .start_timer(); - let json = &serde_json::to_value(&competition.competition_table)?; + let competition_orders = competition + .competition_table + .auction + .orders + .iter() + .map(|order| ByteArray(order.0)) + .collect::>(); - let mut ex = self.pool.begin().await.context("begin")?; + // offload CPU intensive work of serializing to a blocking thread so we can + // already start with the DB queries in the mean time. + let json = tokio::task::spawn_blocking(move || { + serde_json::to_string(&competition.competition_table) + }); - database::solver_competition::save(&mut ex, competition.auction_id, json) - .await - .context("solver_competition::save")?; + let mut ex = self.pool.begin().await.context("begin")?; let reference_scores: Vec<_> = competition .reference_scores - .iter() + .into_iter() .map(|(solver, score)| database::reference_scores::Score { auction_id: competition.auction_id, solver: ByteArray(solver.0.0), @@ -65,7 +73,7 @@ impl super::Postgres { &mut ex, competition .participants - .iter() + .into_iter() .map(|p| Participant { auction_id: competition.auction_id, participant: ByteArray(p.0), @@ -80,11 +88,11 @@ impl super::Postgres { &mut ex, competition .prices - .iter() + .into_iter() .map(|(token, price)| AuctionPrice { auction_id: competition.auction_id, token: ByteArray(token.0), - price: u256_to_big_decimal(price), + price: u256_to_big_decimal(&price), }) .collect::>() .as_slice(), @@ -92,20 +100,17 @@ impl super::Postgres { .await .context("auction_prices::insert")?; - database::auction_orders::insert( - &mut ex, - competition.auction_id, - competition - .competition_table - .auction - .orders - .iter() - .map(|order| ByteArray(order.0)) - .collect::>() - .as_slice(), - ) - .await - .context("auction_orders::insert")?; + database::auction_orders::insert(&mut ex, competition.auction_id, &competition_orders) + .await + .context("auction_orders::insert")?; + + let json = json + .await + .context("failed to await blocking task")? + .context("failed to serialize solver competition")?; + database::solver_competition::save(&mut ex, competition.auction_id, &json) + .await + .context("solver_competition::save")?; ex.commit().await.context("commit") } diff --git a/crates/autopilot/src/infra/persistence/mod.rs b/crates/autopilot/src/infra/persistence/mod.rs index cff8f10e43..b8f3ae1dd0 100644 --- a/crates/autopilot/src/infra/persistence/mod.rs +++ b/crates/autopilot/src/infra/persistence/mod.rs @@ -119,7 +119,7 @@ impl Persistence { /// Saves the competition data to the DB pub async fn save_competition( &self, - competition: &boundary::Competition, + competition: boundary::Competition, ) -> Result<(), DatabaseError> { self.postgres .save_competition(competition) diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index 85d9820153..7ad856bf9d 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -508,7 +508,7 @@ impl RunLoop { tracing::trace!(?competition, "saving competition"); futures::try_join!( self.persistence - .save_competition(&competition) + .save_competition(competition) .map_err(|e| e.0.context("failed to save competition")), self.persistence .save_surplus_capturing_jit_order_owners( diff --git a/crates/database/src/solver_competition.rs b/crates/database/src/solver_competition.rs index de32a846a5..555440ad09 100644 --- a/crates/database/src/solver_competition.rs +++ b/crates/database/src/solver_competition.rs @@ -14,13 +14,17 @@ use { pub async fn save( ex: &mut PgConnection, id: AuctionId, - data: &JsonValue, + json_string: &str, ) -> Result<(), sqlx::Error> { const QUERY: &str = r#" INSERT INTO solver_competitions (id, json) -VALUES ($1, $2) +VALUES ($1, $2::jsonb) ;"#; - sqlx::query(QUERY).bind(id).bind(data).execute(ex).await?; + sqlx::query(QUERY) + .bind(id) + .bind(json_string) + .execute(ex) + .await?; Ok(()) } @@ -117,7 +121,8 @@ mod tests { crate::clear_DANGER_(&mut db).await.unwrap(); let value = JsonValue::Bool(true); - save(&mut db, 0, &value).await.unwrap(); + let value_str = serde_json::to_string(&value).unwrap(); + save(&mut db, 0, &value_str).await.unwrap(); // load by id works let value_ = load_by_id(&mut db, 0).await.unwrap().unwrap(); From 02e9684ec5b13b175c79aaddac63565b8398471a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Mon, 15 Sep 2025 16:00:29 +0100 Subject: [PATCH 025/112] Migrate ERC20Mintable to alloy-rs (#3634) --- Cargo.lock | 90 ++++++ crates/contracts/build.rs | 1 - crates/contracts/src/alloy.rs | 2 + crates/contracts/src/lib.rs | 1 - crates/driver/Cargo.toml | 2 + crates/driver/src/tests/setup/blockchain.rs | 267 ++++++++-------- crates/driver/src/tests/setup/mod.rs | 8 +- crates/e2e/Cargo.toml | 2 +- crates/e2e/src/setup/mod.rs | 16 +- .../e2e/src/setup/onchain_components/mod.rs | 127 +++++--- .../e2e/src/setup/onchain_components/safe.rs | 52 ++-- crates/e2e/tests/e2e/app_data.rs | 42 ++- crates/e2e/tests/e2e/app_data_signer.rs | 54 ++-- crates/e2e/tests/e2e/buffers.rs | 46 ++- crates/e2e/tests/e2e/cow_amm.rs | 118 ++++++-- crates/e2e/tests/e2e/eth_integration.rs | 46 ++- crates/e2e/tests/e2e/eth_safe.rs | 51 ++-- crates/e2e/tests/e2e/ethflow.rs | 61 ++-- crates/e2e/tests/e2e/hooks.rs | 58 ++-- crates/e2e/tests/e2e/jit_orders.rs | 54 +++- crates/e2e/tests/e2e/limit_orders.rs | 286 ++++++++++++------ crates/e2e/tests/e2e/order_cancellation.rs | 22 +- crates/e2e/tests/e2e/partial_fill.rs | 35 ++- .../tests/e2e/partially_fillable_balance.rs | 90 +++--- .../e2e/tests/e2e/partially_fillable_pool.rs | 86 ++++-- .../e2e/tests/e2e/place_order_with_quote.rs | 14 +- crates/e2e/tests/e2e/protocol_fee.rs | 173 ++++++----- crates/e2e/tests/e2e/quote_verification.rs | 58 ++-- crates/e2e/tests/e2e/quoting.rs | 34 ++- crates/e2e/tests/e2e/refunder.rs | 8 +- crates/e2e/tests/e2e/replace_order.rs | 252 +++++++++------ crates/e2e/tests/e2e/smart_contract_orders.rs | 17 +- crates/e2e/tests/e2e/solver_competition.rs | 109 ++++--- .../tests/e2e/solver_participation_guard.rs | 83 +++-- crates/e2e/tests/e2e/submission.rs | 12 +- .../tests/e2e/tracking_insufficient_funds.rs | 5 +- crates/e2e/tests/e2e/uncovered_order.rs | 3 +- crates/e2e/tests/e2e/univ2.rs | 37 ++- crates/e2e/tests/e2e/vault_balances.rs | 22 +- 39 files changed, 1602 insertions(+), 842 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d152b3578c..63fe1cffe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,9 +548,12 @@ dependencies = [ "alloy-primitives", "alloy-signer", "async-trait", + "coins-bip32", + "coins-bip39", "k256", "rand 0.8.5", "thiserror 2.0.12", + "zeroize", ] [[package]] @@ -1570,6 +1573,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bigdecimal" version = "0.3.1" @@ -1685,6 +1694,16 @@ dependencies = [ "syn_derive", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -1866,6 +1885,57 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +[[package]] +name = "coins-bip32" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2073678591747aed4000dd468b97b14d7007f7936851d3f2f01846899f5ebf08" +dependencies = [ + "bs58", + "coins-core", + "digest 0.10.7", + "hmac", + "k256", + "serde", + "sha2", + "thiserror 1.0.61", +] + +[[package]] +name = "coins-bip39" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b169b26623ff17e9db37a539fe4f15342080df39f129ef7631df7683d6d9d4" +dependencies = [ + "bitvec", + "coins-bip32", + "hmac", + "once_cell", + "pbkdf2", + "rand 0.8.5", + "sha2", + "thiserror 1.0.61", +] + +[[package]] +name = "coins-core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b962ad8545e43a28e14e87377812ba9ae748dd4fd963f4c10e9fcc6d13475b" +dependencies = [ + "base64 0.21.7", + "bech32", + "bs58", + "const-hex", + "digest 0.10.7", + "generic-array", + "ripemd", + "serde", + "sha2", + "sha3", + "thiserror 1.0.61", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -3787,6 +3857,7 @@ dependencies = [ "once_cell", "serdect", "sha2", + "signature", ] [[package]] @@ -4625,6 +4696,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -5330,6 +5411,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "rkyv" version = "0.7.44" diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 62fe8cecca..7e7d246756 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -1088,7 +1088,6 @@ fn main() { builder.add_network_str(GNOSIS, "0x6093AeBAC87d62b1A5a4cEec91204e35020E38bE") }); generate_contract("ERC20"); - generate_contract("ERC20Mintable"); generate_contract("ERC3156FlashLoanSolverWrapper"); generate_contract_with_config("FlashLoanRouter", |builder| { let mut builder = builder; diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index babb06f688..da49911c5c 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -42,6 +42,8 @@ crate::bindings!( } ); +crate::bindings!(ERC20Mintable); + crate::bindings!(GnosisSafe); crate::bindings!(GnosisSafeCompatibilityFallbackHandler); crate::bindings!(GnosisSafeProxy); diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 6be650a256..fd6ac5ac17 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -80,7 +80,6 @@ include_contracts! { CowProtocolToken; ERC1271SignatureValidator; ERC20; - ERC20Mintable; ERC3156FlashLoanSolverWrapper; FlashLoanRouter; GPv2AllowListAuthentication; diff --git a/crates/driver/Cargo.toml b/crates/driver/Cargo.toml index cf31441810..1d4485d6c9 100644 --- a/crates/driver/Cargo.toml +++ b/crates/driver/Cargo.toml @@ -84,6 +84,8 @@ maplit = { workspace = true } mockall = { workspace = true } tokio = { workspace = true, features = ["test-util", "process"] } tempfile = { workspace = true } +ethrpc = {workspace = true, features = ["test-util"]} +contracts = { workspace = true , features = ["test-util"]} [build-dependencies] anyhow = { workspace = true } diff --git a/crates/driver/src/tests/setup/blockchain.rs b/crates/driver/src/tests/setup/blockchain.rs index 1ed1688a53..7aea616855 100644 --- a/crates/driver/src/tests/setup/blockchain.rs +++ b/crates/driver/src/tests/setup/blockchain.rs @@ -7,8 +7,16 @@ use { }, tests::{self, boundary, cases::EtherExt}, }, + alloy::{primitives::U256, signers::local::PrivateKeySigner}, + contracts::alloy::ERC20Mintable, ethcontract::PrivateKey, - ethrpc::Web3, + ethrpc::{ + Web3, + alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, + }, futures::Future, secp256k1::SecretKey, serde_json::json, @@ -33,7 +41,7 @@ pub struct Blockchain { pub trader_secret_key: SecretKey, pub web3: Web3, pub web3_url: String, - pub tokens: HashMap<&'static str, contracts::ERC20Mintable>, + pub tokens: HashMap<&'static str, ERC20Mintable::Instance>, pub weth: contracts::WETH9, pub settlement: contracts::GPv2Settlement, pub balances: contracts::support::Balances, @@ -211,14 +219,16 @@ impl Blockchain { // TODO All these various deployments that are happening from the trader account // should be happening from the primary_account of the node, will do this // later - let node = Node::new(&config.rpc_args).await; - let web3 = Web3::new_from_url(&node.url()); + let mut web3 = Web3::new_from_url(&node.url()); + let private_key = config.main_trader_secret_key.as_ref(); let main_trader_account = ethcontract::Account::Offline( - ethcontract::PrivateKey::from_slice(config.main_trader_secret_key.as_ref()).unwrap(), + ethcontract::PrivateKey::from_slice(private_key).unwrap(), None, ); + let signer = PrivateKeySigner::from_slice(private_key).unwrap(); + web3.wallet.register_signer(signer); // Use the primary account to fund the trader, cow amm and the solver with ETH. let balance = web3 @@ -397,8 +407,12 @@ impl Blockchain { .unwrap(); if !config.balance.is_zero() { + let private_key = config.private_key.as_ref(); + let signer = PrivateKeySigner::from_slice(private_key).unwrap(); + web3.wallet.register_signer(signer); + let trader_account = ethcontract::Account::Offline( - ethcontract::PrivateKey::from_slice(config.private_key.as_ref()).unwrap(), + ethcontract::PrivateKey::from_slice(private_key).unwrap(), None, ); trader_accounts.push(trader_account); @@ -412,25 +426,15 @@ impl Blockchain { let mut tokens = HashMap::new(); for pool in config.pools.iter() { if pool.reserve_a.token != "WETH" && !tokens.contains_key(pool.reserve_a.token) { - let token = wait_for( - &web3, - contracts::ERC20Mintable::builder(&web3) - .from(main_trader_account.clone()) - .deploy(), - ) - .await - .unwrap(); + let token = ERC20Mintable::Instance::deploy(web3.alloy.clone()) + .await + .unwrap(); tokens.insert(pool.reserve_a.token, token); } if pool.reserve_b.token != "WETH" && !tokens.contains_key(pool.reserve_b.token) { - let token = wait_for( - &web3, - contracts::ERC20Mintable::builder(&web3) - .from(main_trader_account.clone()) - .deploy(), - ) - .await - .unwrap(); + let token = ERC20Mintable::Instance::deploy(web3.alloy.clone()) + .await + .unwrap(); tokens.insert(pool.reserve_b.token, token); } } @@ -452,12 +456,20 @@ impl Blockchain { let token_a = if pool.reserve_a.token == "WETH" { weth.address() } else { - tokens.get(pool.reserve_a.token).unwrap().address() + tokens + .get(pool.reserve_a.token) + .unwrap() + .address() + .into_legacy() }; let token_b = if pool.reserve_b.token == "WETH" { weth.address() } else { - tokens.get(pool.reserve_b.token).unwrap().address() + tokens + .get(pool.reserve_b.token) + .unwrap() + .address() + .into_legacy() }; // Create the pair. wait_for( @@ -514,54 +526,53 @@ impl Blockchain { } else { for trader_account in trader_accounts.iter() { let vault_relayer = settlement.vault_relayer().call().await.unwrap(); - wait_for( - &web3, - tokens - .get(pool.reserve_a.token) - .unwrap() - .approve(vault_relayer, ethcontract::U256::max_value()) - .from(trader_account.clone()) - .send(), - ) - .await - .unwrap(); - } - wait_for( - &web3, - tokens - .get(pool.reserve_a.token) - .unwrap() - .mint(pair.address(), pool.reserve_a.amount) - .from(main_trader_account.clone()) - .send(), - ) - .await - .unwrap(); - wait_for( - &web3, tokens .get(pool.reserve_a.token) .unwrap() - .mint(settlement.address(), pool.reserve_a.amount) - .from(main_trader_account.clone()) - .send(), - ) - .await - .unwrap(); + .approve(vault_relayer.into_alloy(), U256::MAX) + .from(trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + } - for trader_account in trader_accounts.iter() { - wait_for( - &web3, - tokens - .get(pool.reserve_a.token) - .unwrap() - .mint(trader_account.address(), pool.reserve_a.amount) - .from(main_trader_account.clone()) - .send(), + tokens + .get(pool.reserve_a.token) + .unwrap() + .mint( + pair.address().into_alloy(), + pool.reserve_a.amount.into_alloy(), ) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() .await .unwrap(); + + tokens + .get(pool.reserve_a.token) + .unwrap() + .mint( + settlement.address().into_alloy(), + pool.reserve_a.amount.into_alloy(), + ) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + for trader_account in trader_accounts.iter() { + tokens + .get(pool.reserve_a.token) + .unwrap() + .mint( + trader_account.address().into_alloy(), + pool.reserve_a.amount.into_alloy(), + ) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } } if pool.reserve_b.token == "WETH" { @@ -594,53 +605,52 @@ impl Blockchain { } else { for trader_account in trader_accounts.iter() { let vault_relayer = settlement.vault_relayer().call().await.unwrap(); - wait_for( - &web3, - tokens - .get(pool.reserve_b.token) - .unwrap() - .approve(vault_relayer, ethcontract::U256::max_value()) - .from(trader_account.clone()) - .send(), - ) - .await - .unwrap(); - } - wait_for( - &web3, - tokens - .get(pool.reserve_b.token) - .unwrap() - .mint(pair.address(), pool.reserve_b.amount) - .from(main_trader_account.clone()) - .send(), - ) - .await - .unwrap(); - wait_for( - &web3, tokens .get(pool.reserve_b.token) .unwrap() - .mint(settlement.address(), pool.reserve_b.amount) - .from(main_trader_account.clone()) - .send(), - ) - .await - .unwrap(); - for trader_account in trader_accounts.iter() { - wait_for( - &web3, - tokens - .get(pool.reserve_b.token) - .unwrap() - .mint(trader_account.address(), pool.reserve_b.amount) - .from(main_trader_account.clone()) - .send(), + .approve(vault_relayer.into_alloy(), U256::MAX) + .from(trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + } + + tokens + .get(pool.reserve_b.token) + .unwrap() + .mint( + pair.address().into_alloy(), + pool.reserve_b.amount.into_alloy(), ) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() .await .unwrap(); + + tokens + .get(pool.reserve_b.token) + .unwrap() + .mint( + settlement.address().into_alloy(), + pool.reserve_b.amount.into_alloy(), + ) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + for trader_account in trader_accounts.iter() { + tokens + .get(pool.reserve_b.token) + .unwrap() + .mint( + trader_account.address().into_alloy(), + pool.reserve_b.amount.into_alloy(), + ) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } } wait_for( @@ -791,35 +801,30 @@ impl Blockchain { if order.sell_token == "WETH" { todo!("deposit trader funds into the weth contract, none of the tests do this yet") } else if order.funded { - wait_for( - &self.web3, - self.tokens - .get(order.sell_token) - .unwrap() - .mint( - trader_account.address(), - "1e-7".ether().into_wei() * execution.sell, - ) - .from(trader_account.clone()) - .send(), - ) - .await - .unwrap(); + self.tokens + .get(order.sell_token) + .unwrap() + .mint( + trader_account.address().into_alloy(), + ("1e-7".ether().into_wei() * execution.sell).into_alloy(), + ) + .from(trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } // Approve the tokens needed for the solution. let vault_relayer = self.settlement.vault_relayer().call().await.unwrap(); - wait_for( - &self.web3, - self.tokens - .get(order.sell_token) - .unwrap() - .approve(vault_relayer, ethcontract::U256::max_value()) - .from(trader_account.clone()) - .send(), - ) - .await - .unwrap(); + + self.tokens + .get(order.sell_token) + .unwrap() + .approve(vault_relayer.into_alloy(), U256::MAX) + .from(trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Create the interactions fulfilling the order. let transfer_interaction = sell_token @@ -899,7 +904,7 @@ impl Blockchain { match token { "WETH" => self.weth.address(), "ETH" => eth::ETH_TOKEN.into(), - _ => self.tokens.get(token).unwrap().address(), + _ => self.tokens.get(token).unwrap().address().into_legacy(), } } @@ -908,7 +913,7 @@ impl Blockchain { pub fn get_token_wrapped(&self, token: &str) -> eth::H160 { match token { "WETH" | "ETH" => self.weth.address(), - _ => self.tokens.get(token).unwrap().address(), + _ => self.tokens.get(token).unwrap().address().into_legacy(), } } diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index 2ebd300f6a..32299e4467 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -4,7 +4,7 @@ use { self::{driver::Driver, solver::Solver as SolverInstance}, crate::{ domain::{ - competition::{order, order::app_data::AppData}, + competition::order::{self, app_data::AppData}, eth, time, }, @@ -40,6 +40,7 @@ use { }, bigdecimal::{BigDecimal, FromPrimitive}, ethcontract::dyns::DynTransport, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, futures::future::join_all, hyper::StatusCode, model::order::{BuyTokenDestination, SellTokenSource}, @@ -1187,10 +1188,11 @@ impl Test { let mut balances = HashMap::new(); for (token, contract) in self.blockchain.tokens.iter() { let balance = contract - .balance_of(self.trader_address) + .balanceOf(self.trader_address.into_alloy()) .call() .await - .unwrap(); + .unwrap() + .into_legacy(); balances.insert(*token, balance); } balances.insert( diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 3d639a271e..507d6b8f1c 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -9,7 +9,7 @@ edition = "2024" license = "MIT OR Apache-2.0" [dependencies] -alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "rpc-types", "transports", "reqwest", "signers", "signer-local"] } +alloy = { workspace = true, default-features = false, features = ["json-rpc", "providers", "rpc-client", "rpc-types", "transports", "reqwest", "signers", "signer-local", "signer-mnemonic"] } app-data = { workspace = true } anyhow = { workspace = true } autopilot = { workspace = true } diff --git a/crates/e2e/src/setup/mod.rs b/crates/e2e/src/setup/mod.rs index 508eeb4528..966a53bfb4 100644 --- a/crates/e2e/src/setup/mod.rs +++ b/crates/e2e/src/setup/mod.rs @@ -8,6 +8,7 @@ mod solver; use { crate::nodes::{NODE_HOST, Node}, + ::alloy::signers::local::{MnemonicBuilder, coins_bip39::English}, anyhow::{Result, anyhow}, ethcontract::futures::FutureExt, ethrpc::Web3, @@ -212,7 +213,20 @@ async fn run( let _ = node_panic_handle.lock().unwrap().take(); })); - let web3 = Web3::new_from_url(NODE_HOST); + let mut web3 = Web3::new_from_url(NODE_HOST); + let phrase = "test test test test test test test test test test test junk"; + let signers = (0..10).map(|i| { + MnemonicBuilder::::default() + .phrase(phrase) + .index(i) + .unwrap() + .build() + .unwrap() + }); + + for signer in signers { + web3.wallet.register_signer(signer); + } services::clear_database().await; // Hack: the closure may actually be unwind unsafe; moreover, `catch_unwind` diff --git a/crates/e2e/src/setup/onchain_components/mod.rs b/crates/e2e/src/setup/onchain_components/mod.rs index 8600556a00..7eb6c63a8c 100644 --- a/crates/e2e/src/setup/onchain_components/mod.rs +++ b/crates/e2e/src/setup/onchain_components/mod.rs @@ -5,7 +5,8 @@ use { }, ::alloy::signers::local::PrivateKeySigner, app_data::Hook, - contracts::{CowProtocolToken, ERC20Mintable}, + contracts::{CowProtocolToken, alloy::ERC20Mintable}, + core::panic, ethcontract::{ Account, Bytes, @@ -14,6 +15,10 @@ use { U256, transaction::{TransactionBuilder, TransactionResult}, }, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, hex_literal::hex, model::{ DomainSeparator, @@ -169,18 +174,23 @@ impl Iterator for AccountGenerator { #[derive(Debug)] pub struct MintableToken { - contract: ERC20Mintable, + contract: ERC20Mintable::Instance, minter: Account, } impl MintableToken { pub async fn mint(&self, to: H160, amount: U256) { - tx!(self.minter, self.contract.mint(to, amount)); + self.contract + .mint(to.into_alloy(), amount.into_alloy()) + .from(self.minter.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } } impl Deref for MintableToken { - type Target = ERC20Mintable; + type Target = ERC20Mintable::Instance; fn deref(&self) -> &Self::Target { &self.contract @@ -389,12 +399,16 @@ impl OnchainComponents { /// Deploy `N` tokens without any onchain liquidity pub async fn deploy_tokens(&self, minter: &Account) -> [MintableToken; N] { let mut res = Vec::with_capacity(N); + for _ in 0..N { - let contract = ERC20Mintable::builder(&self.web3) - .from(minter.clone()) + let contract_address = ERC20Mintable::Instance::deploy_builder(self.web3.alloy.clone()) + // We can't escape the .from here because we need to ensure Minter permissions later on + .from(minter.address().into_alloy()) .deploy() .await - .expect("MintableERC20 deployment failed"); + .expect("ERC20Mintable deployment failed"); + let contract = ERC20Mintable::Instance::new(contract_address, self.web3.alloy.clone()); + res.push(MintableToken { contract, minter: minter.clone(), @@ -431,19 +445,32 @@ impl OnchainComponents { weth_amount: U256, ) { for MintableToken { contract, minter } in tokens { - tx!(minter, contract.mint(minter.address(), token_amount)); + contract + .mint(minter.address().into_alloy(), token_amount.into_alloy()) + .from(minter.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx_value!(minter, weth_amount, self.contracts.weth.deposit()); tx!( minter, - self.contracts - .uniswap_v2_factory - .create_pair(contract.address(), self.contracts.weth.address()) - ); - tx!( - minter, - contract.approve(self.contracts.uniswap_v2_router.address(), token_amount) + self.contracts.uniswap_v2_factory.create_pair( + contract.address().into_legacy(), + self.contracts.weth.address() + ) ); + + contract + .approve( + self.contracts.uniswap_v2_router.address().into_alloy(), + token_amount.into_alloy(), + ) + .from(minter.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + tx!( minter, self.contracts @@ -453,7 +480,7 @@ impl OnchainComponents { tx!( minter, self.contracts.uniswap_v2_router.add_liquidity( - contract.address(), + contract.address().into_legacy(), self.contracts.weth.address(), token_amount, weth_amount, @@ -477,27 +504,38 @@ impl OnchainComponents { tx!( lp, - self.contracts - .uniswap_v2_factory - .create_pair(asset_a.0.address(), asset_b.0.address()) - ); - tx!( - lp, - asset_a - .0 - .approve(self.contracts.uniswap_v2_router.address(), asset_a.1) - ); - tx!( - lp, - asset_b - .0 - .approve(self.contracts.uniswap_v2_router.address(), asset_b.1) + self.contracts.uniswap_v2_factory.create_pair( + asset_a.0.address().into_legacy(), + asset_b.0.address().into_legacy() + ) ); + + asset_a + .0 + .approve( + self.contracts.uniswap_v2_router.address().into_alloy(), + asset_a.1.into_alloy(), + ) + .from(lp.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + asset_b + .0 + .approve( + self.contracts.uniswap_v2_router.address().into_alloy(), + asset_b.1.into_alloy(), + ) + .from(lp.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( lp, self.contracts.uniswap_v2_router.add_liquidity( - asset_a.0.address(), - asset_b.0.address(), + asset_a.0.address().into_legacy(), + asset_b.0.address().into_legacy(), asset_a.1, asset_b.1, 0_u64.into(), @@ -516,7 +554,7 @@ impl OnchainComponents { &self.web3, self.contracts .uniswap_v2_factory - .get_pair(self.contracts.weth.address(), token.address()) + .get_pair(self.contracts.weth.address(), token.address().into_legacy()) .call() .await .expect("failed to get Uniswap V2 pair"), @@ -526,16 +564,17 @@ impl OnchainComponents { // Mint amount + 1 to the pool, and then swap out 1 of the minted token // in order to force it to update its K-value. token.mint(pair.address(), amount + 1).await; - let (out0, out1) = if TokenPair::new(self.contracts.weth.address(), token.address()) - .unwrap() - .get() - .0 - == token.address() - { - (1, 0) - } else { - (0, 1) - }; + let (out0, out1) = + if TokenPair::new(self.contracts.weth.address(), token.address().into_legacy()) + .unwrap() + .get() + .0 + == token.address().into_legacy() + { + (1, 0) + } else { + (0, 1) + }; pair.swap( out0.into(), out1.into(), diff --git a/crates/e2e/src/setup/onchain_components/safe.rs b/crates/e2e/src/setup/onchain_components/safe.rs index 6490afb20f..ac9b360522 100644 --- a/crates/e2e/src/setup/onchain_components/safe.rs +++ b/crates/e2e/src/setup/onchain_components/safe.rs @@ -12,7 +12,10 @@ use { GnosisSafeProxyFactory, }, ethcontract::transaction::TransactionBuilder, - ethrpc::{AlloyProvider, alloy::conversions::IntoAlloy}, + ethrpc::{ + AlloyProvider, + alloy::{CallBuilderExt, conversions::IntoAlloy}, + }, hex_literal::hex, model::{ DomainSeparator, @@ -33,32 +36,15 @@ pub struct Infrastructure { impl Infrastructure { pub async fn new(provider: AlloyProvider) -> Self { - let singleton = { - let deployed_address = GnosisSafe::Instance::deploy_builder(provider.clone()) - .deploy() - .await - .unwrap(); - GnosisSafe::Instance::new(deployed_address, provider.clone()) - }; - let fallback = { - let deployed_address = - GnosisSafeCompatibilityFallbackHandler::Instance::deploy_builder(provider.clone()) - .deploy() - .await - .unwrap(); - GnosisSafeCompatibilityFallbackHandler::Instance::new( - deployed_address, - provider.clone(), - ) - }; - let factory = { - let deployed_address = - GnosisSafeProxyFactory::Instance::deploy_builder(provider.clone()) - .deploy() - .await - .unwrap(); - GnosisSafeProxyFactory::Instance::new(deployed_address, provider.clone()) - }; + let singleton = GnosisSafe::Instance::deploy(provider.clone()) + .await + .unwrap(); + let fallback = GnosisSafeCompatibilityFallbackHandler::Instance::deploy(provider.clone()) + .await + .unwrap(); + let factory = GnosisSafeProxyFactory::Instance::deploy(provider.clone()) + .await + .unwrap(); Self { singleton, @@ -136,8 +122,8 @@ impl Safe { } async fn exec_alloy_tx(&self, to: Address, value: U256, calldata: Bytes) { - contracts::alloy::tx!( - self.contract.execTransaction( + self.contract + .execTransaction( to, value, calldata, @@ -150,9 +136,11 @@ impl Safe { crate::setup::safe::gnosis_safe_prevalidated_signature( self.owner.address().into_alloy(), ), - ), - self.owner.address().into_alloy() - ); + ) + .from(self.owner.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } pub async fn exec_call( diff --git a/crates/e2e/tests/e2e/app_data.rs b/crates/e2e/tests/e2e/app_data.rs index 39002f47d8..126297f6f0 100644 --- a/crates/e2e/tests/e2e/app_data.rs +++ b/crates/e2e/tests/e2e/app_data.rs @@ -1,6 +1,10 @@ use { app_data::{AppDataHash, hash_full_app_data}, - e2e::{setup::*, tx}, + e2e::setup::*, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, @@ -35,18 +39,24 @@ async fn app_data(web3: Web3) { .await; token_a.mint(trader.address(), to_wei(10)).await; - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let mut valid_to: u32 = model::time::now_in_epoch_seconds() + 300; let mut create_order = |app_data| { let order = OrderCreation { app_data, - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(2), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(1), valid_to, kind: OrderKind::Sell, @@ -194,18 +204,24 @@ async fn app_data_full_format(web3: Web3) { .await; token_a.mint(trader.address(), to_wei(10)).await; - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let mut valid_to: u32 = model::time::now_in_epoch_seconds() + 300; let mut create_order = |app_data| { let order = OrderCreation { app_data, - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(2), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(1), valid_to, kind: OrderKind::Sell, diff --git a/crates/e2e/tests/e2e/app_data_signer.rs b/crates/e2e/tests/e2e/app_data_signer.rs index 18bdd6ea86..6c42891646 100644 --- a/crates/e2e/tests/e2e/app_data_signer.rs +++ b/crates/e2e/tests/e2e/app_data_signer.rs @@ -1,9 +1,10 @@ use { - e2e::{ - setup::{OnchainComponents, Services, TestAccount, run_test, safe::Safe, to_wei}, - tx, + alloy::primitives::Address, + e2e::setup::{OnchainComponents, Services, TestAccount, run_test, safe::Safe, to_wei}, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, }, - ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, signature::EcdsaSigningScheme, @@ -28,23 +29,35 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { .await; token_a.mint(trader.address(), to_wei(10)).await; - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a.mint(adversary.address(), to_wei(10)).await; - tx!( - adversary.account(), - token_a.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(adversary.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let mut valid_to: u32 = model::time::now_in_epoch_seconds() + 300; let mut create_order = |app_data| { let order = OrderCreation { app_data, - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(2), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(1), valid_to, kind: OrderKind::Sell, @@ -93,8 +106,15 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { let safe = Safe::deploy(safe_owner.clone(), web3.alloy.clone()).await; token_a.mint(safe.address().into_legacy(), to_wei(10)).await; - safe.exec_call(token_a.approve(onchain.contracts().allowance, to_wei(10))) - .await; + safe.exec_alloy_call( + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .into_transaction_request(), + ) + .await; // Accepted: owner retrieved from app data. let full_app_data = full_app_data_with_signer(safe.address()); @@ -111,7 +131,7 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { assert!(err.1.contains("AppdataFromMismatch")); } -fn full_app_data_with_signer(signer: alloy::primitives::Address) -> OrderCreationAppData { +fn full_app_data_with_signer(signer: Address) -> OrderCreationAppData { let app_data = format!("{{\"metadata\": {{\"signer\": \"{signer:?}\"}}}}"); OrderCreationAppData::Full { full: app_data.to_string(), diff --git a/crates/e2e/tests/e2e/buffers.rs b/crates/e2e/tests/e2e/buffers.rs index 44471d9c5a..c6ff11b010 100644 --- a/crates/e2e/tests/e2e/buffers.rs +++ b/crates/e2e/tests/e2e/buffers.rs @@ -1,5 +1,10 @@ use { - e2e::{setup::*, tx}, + ::alloy::primitives::U256, + e2e::setup::*, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -33,10 +38,16 @@ async fn onchain_settlement_without_liquidity(web3: Web3) { token_b.mint(solver.address(), to_wei(1000)).await; // Approve GPv2 for trading - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(100)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Start system colocation::start_driver( @@ -83,9 +94,9 @@ async fn onchain_settlement_without_liquidity(web3: Web3) { // Place Order let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(9), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Buy, @@ -100,18 +111,24 @@ async fn onchain_settlement_without_liquidity(web3: Web3) { tracing::info!("waiting for first trade"); onchain.mint_block().await; - let trade_happened = - || async { token_b.balance_of(trader.address()).call().await.unwrap() == order.buy_amount }; + let trade_happened = || async { + token_b + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap() + == order.buy_amount.into_alloy() + }; wait_for_condition(TIMEOUT, trade_happened).await.unwrap(); // Check that settlement buffers were traded. let settlement_contract_balance = token_b - .balance_of(onchain.contracts().gp_settlement.address()) + .balanceOf(onchain.contracts().gp_settlement.address().into_alloy()) .call() .await .unwrap(); // Check that internal buffers were used - assert!(settlement_contract_balance == 0.into()); + assert!(settlement_contract_balance == U256::ZERO); // Same order can trade again with external liquidity let order = OrderCreation { @@ -127,7 +144,12 @@ async fn onchain_settlement_without_liquidity(web3: Web3) { tracing::info!("waiting for second trade"); let trade_happened = || async { - token_b.balance_of(trader.address()).call().await.unwrap() == order.buy_amount * 2 + token_b + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap() + == (order.buy_amount.into_alloy() * U256::from(2)) }; wait_for_condition(TIMEOUT, trade_happened).await.unwrap(); } diff --git a/crates/e2e/tests/e2e/cow_amm.rs b/crates/e2e/tests/e2e/cow_amm.rs index f770834298..30994c39cd 100644 --- a/crates/e2e/tests/e2e/cow_amm.rs +++ b/crates/e2e/tests/e2e/cow_amm.rs @@ -8,11 +8,27 @@ use { e2e::{ deploy, nodes::forked_node::ForkedNodeApi, - setup::{colocation::SolverEngine, mock::Mock, *}, + setup::{ + DeployedContracts, + OnchainComponents, + Services, + TIMEOUT, + colocation::{self, SolverEngine}, + mock::Mock, + run_forked_test_with_block_number, + run_test, + to_wei, + to_wei_with_exp, + wait_for_condition, + }, tx, tx_value, }, ethcontract::{BlockId, BlockNumber, H160, U256, web3::ethabi::Token}, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderClass, OrderCreation, OrderData, OrderKind, OrderUid}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, @@ -72,10 +88,15 @@ async fn cow_amm_jit(web3: Web3) { // Fund cow amm owner with 2_000 dai and allow factory take them dai.mint(cow_amm_owner.address(), to_wei(2_000)).await; - tx!( - cow_amm_owner.account(), - dai.approve(cow_amm_factory.address(), to_wei(2_000)) - ); + + dai.approve( + cow_amm_factory.address().into_alloy(), + to_wei(2_000).into_alloy(), + ) + .from(cow_amm_owner.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Fund cow amm owner with 1 WETH and allow factory take them tx_value!( cow_amm_owner.account(), @@ -93,7 +114,10 @@ async fn cow_amm_jit(web3: Web3) { let pair = onchain .contracts() .uniswap_v2_factory - .get_pair(onchain.contracts().weth.address(), dai.address()) + .get_pair( + onchain.contracts().weth.address(), + dai.address().into_legacy(), + ) .call() .await .expect("failed to get Uniswap V2 pair"); @@ -101,7 +125,7 @@ async fn cow_amm_jit(web3: Web3) { let cow_amm = cow_amm_factory .amm_deterministic_address( cow_amm_owner.address(), - dai.address(), + dai.address().into_legacy(), onchain.contracts().weth.address(), ) .call() @@ -116,7 +140,7 @@ async fn cow_amm_jit(web3: Web3) { cow_amm_factory .create( - dai.address(), + dai.address().into_legacy(), to_wei(2_000), onchain.contracts().weth.address(), to_wei(1), @@ -198,7 +222,7 @@ async fn cow_amm_jit(web3: Web3) { // surplus. let cow_amm_order = OrderData { sell_token: onchain.contracts().weth.address(), - buy_token: dai.address(), + buy_token: dai.address().into_legacy(), receiver: None, sell_amount: U256::exp10(17), buy_amount: to_wei(230), @@ -292,7 +316,7 @@ async fn cow_amm_jit(web3: Web3) { let user_order = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: U256::exp10(17), // 0.1 WETH - buy_token: dai.address(), + buy_token: dai.address().into_legacy(), buy_amount: to_wei(230), // 230 DAI valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -305,8 +329,16 @@ async fn cow_amm_jit(web3: Web3) { ); let user_order_id = services.create_order(&user_order).await.unwrap(); - let amm_balance_before = dai.balance_of(cow_amm.address()).call().await.unwrap(); - let bob_balance_before = dai.balance_of(bob.address()).call().await.unwrap(); + let amm_balance_before = dai + .balanceOf(cow_amm.address().into_alloy()) + .call() + .await + .unwrap(); + let bob_balance_before = dai + .balanceOf(bob.address().into_alloy()) + .call() + .await + .unwrap(); let fee = U256::exp10(16); // 0.01 WETH @@ -314,7 +346,7 @@ async fn cow_amm_jit(web3: Web3) { id: 1, // assume price of the univ2 pool prices: HashMap::from([ - (dai.address(), to_wei(100)), + (dai.address().into_legacy(), to_wei(100)), (onchain.contracts().weth.address(), to_wei(300_000)), ]), trades: vec![ @@ -354,14 +386,23 @@ async fn cow_amm_jit(web3: Web3) { tracing::info!("Waiting for trade."); onchain.mint_block().await; wait_for_condition(TIMEOUT, || async { - let amm_balance = dai.balance_of(cow_amm.address()).call().await.unwrap(); - let bob_balance = dai.balance_of(bob.address()).call().await.unwrap(); + let amm_balance = dai + .balanceOf(cow_amm.address().into_alloy()) + .call() + .await + .unwrap(); + let bob_balance = dai + .balanceOf(bob.address().into_alloy()) + .call() + .await + .unwrap(); let amm_received = amm_balance - amm_balance_before; let bob_received = bob_balance - bob_balance_before; // bob and CoW AMM both got surplus and an equal amount - amm_received >= cow_amm_order.buy_amount && bob_received > user_order.buy_amount + amm_received >= cow_amm_order.buy_amount.into_alloy() + && bob_received > user_order.buy_amount.into_alloy() }) .await .unwrap(); @@ -670,10 +711,15 @@ async fn cow_amm_opposite_direction(web3: Web3) { // Fund the CoW AMM owner with DAI and WETH and approve the factory to transfer // them dai.mint(cow_amm_owner.address(), to_wei(2_000)).await; - tx!( - cow_amm_owner.account(), - dai.approve(cow_amm_factory.address(), to_wei(2_000)) - ); + + dai.approve( + cow_amm_factory.address().into_alloy(), + to_wei(2_000).into_alloy(), + ) + .from(cow_amm_owner.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx_value!( cow_amm_owner.account(), @@ -697,7 +743,10 @@ async fn cow_amm_opposite_direction(web3: Web3) { let pair = onchain .contracts() .uniswap_v2_factory - .get_pair(onchain.contracts().weth.address(), dai.address()) + .get_pair( + onchain.contracts().weth.address(), + dai.address().into_legacy(), + ) .call() .await .expect("failed to get Uniswap V2 pair"); @@ -705,7 +754,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { let cow_amm_address = cow_amm_factory .amm_deterministic_address( cow_amm_owner.address(), - dai.address(), + dai.address().into_legacy(), onchain.contracts().weth.address(), ) .call() @@ -721,7 +770,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { // Create the CoW AMM cow_amm_factory .create( - dai.address(), + dai.address().into_legacy(), to_wei(2_000), onchain.contracts().weth.address(), to_wei(1), @@ -795,7 +844,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { // CoW AMM order remains the same (selling WETH for DAI) let cow_amm_order = OrderData { sell_token: onchain.contracts().weth.address(), - buy_token: dai.address(), + buy_token: dai.address().into_legacy(), receiver: None, sell_amount: U256::exp10(17), // 0.1 WETH buy_amount: executed_amount, // 230 DAI @@ -869,10 +918,15 @@ async fn cow_amm_opposite_direction(web3: Web3) { // Fund trader "bob" with DAI and approve allowance dai.mint(bob.address(), to_wei(250)).await; - tx!( - bob.account(), - dai.approve(onchain.contracts().allowance, U256::MAX) - ); + + dai.approve( + onchain.contracts().allowance.into_alloy(), + alloy::primitives::U256::MAX, + ) + .from(bob.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Get balances before the trade let amm_weth_balance_before = onchain @@ -902,7 +956,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { Solution { id: 1, prices: HashMap::from([ - (dai.address(), to_wei(1)), // 1 DAI = $1 + (dai.address().into_legacy(), to_wei(1)), // 1 DAI = $1 (onchain.contracts().weth.address(), to_wei(2300)), // 1 WETH = $2300 ]), trades: vec![ @@ -945,7 +999,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { let quote_request = OrderQuoteRequest { from: bob.address(), - sell_token: dai.address(), + sell_token: dai.address().into_legacy(), buy_token: onchain.contracts().weth.address(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::AfterFee { @@ -958,7 +1012,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { // Must align with the mocked_solutions. let quote_response = services.submit_quote("e_request).await.unwrap(); assert!(quote_response.verified); - assert_eq!(quote_response.quote.sell_token, dai.address()); + assert_eq!(quote_response.quote.sell_token, dai.address().into_legacy()); assert_eq!( quote_response.quote.buy_token, onchain.contracts().weth.address() @@ -969,7 +1023,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { // Place user order where bob sells DAI to buy WETH (opposite direction) let user_order = OrderCreation { - sell_token: dai.address(), + sell_token: dai.address().into_legacy(), sell_amount: executed_amount, // 230 DAI buy_token: onchain.contracts().weth.address(), buy_amount: U256::from(90000000000000000u64), // 0.09 WETH to generate some surplus diff --git a/crates/e2e/tests/e2e/eth_integration.rs b/crates/e2e/tests/e2e/eth_integration.rs index 8ada5978b2..bfdf0e919e 100644 --- a/crates/e2e/tests/e2e/eth_integration.rs +++ b/crates/e2e/tests/e2e/eth_integration.rs @@ -1,6 +1,10 @@ use { - e2e::{setup::*, tx}, + e2e::setup::*, ethcontract::prelude::Address, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{BUY_ETH_ADDRESS, OrderCreation, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, @@ -32,14 +36,26 @@ async fn eth_integration(web3: Web3) { token.mint(trader_b.address(), to_wei(51)).await; // Approve GPv2 for trading - tx!( - trader_a.account(), - token.approve(onchain.contracts().allowance, to_wei(51)) - ); - tx!( - trader_b.account(), - token.approve(onchain.contracts().allowance, to_wei(51)) - ); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(51).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(51).into_alloy(), + ) + .from(trader_b.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let trader_a_eth_balance_before = web3.eth().balance(trader_a.address(), None).await.unwrap(); @@ -63,16 +79,20 @@ async fn eth_integration(web3: Web3) { services.submit_quote(&request).await } }; - quote(token.address(), BUY_ETH_ADDRESS).await.unwrap(); + quote(token.address().into_legacy(), BUY_ETH_ADDRESS) + .await + .unwrap(); // Eth is only supported as the buy token - let (status, body) = quote(BUY_ETH_ADDRESS, token.address()).await.unwrap_err(); + let (status, body) = quote(BUY_ETH_ADDRESS, token.address().into_legacy()) + .await + .unwrap_err(); assert_eq!(status, 400, "{body}"); // Place Orders assert_ne!(onchain.contracts().weth.address(), BUY_ETH_ADDRESS); let order_buy_eth_a = OrderCreation { kind: OrderKind::Buy, - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: to_wei(50), buy_token: BUY_ETH_ADDRESS, buy_amount: to_wei(49), @@ -87,7 +107,7 @@ async fn eth_integration(web3: Web3) { services.create_order(&order_buy_eth_a).await.unwrap(); let order_buy_eth_b = OrderCreation { kind: OrderKind::Sell, - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: to_wei(50), buy_token: BUY_ETH_ADDRESS, buy_amount: to_wei(49), diff --git a/crates/e2e/tests/e2e/eth_safe.rs b/crates/e2e/tests/e2e/eth_safe.rs index b54f756ce3..f097522419 100644 --- a/crates/e2e/tests/e2e/eth_safe.rs +++ b/crates/e2e/tests/e2e/eth_safe.rs @@ -1,18 +1,18 @@ use { ::alloy::{primitives::U256, providers::Provider}, - e2e::{ - setup::{ - OnchainComponents, - Services, - TIMEOUT, - run_test, - safe::Safe, - to_wei, - wait_for_condition, - }, - tx, + e2e::setup::{ + OnchainComponents, + Services, + TIMEOUT, + run_test, + safe::Safe, + to_wei, + wait_for_condition, + }, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, }, - ethrpc::alloy::conversions::IntoLegacy, model::{ order::{BUY_ETH_ADDRESS, OrderCreation, OrderKind}, signature::{Signature, hashed_eip712_message}, @@ -38,13 +38,26 @@ async fn test(web3: Web3) { .await; token.mint(trader.address(), to_wei(4)).await; - safe.exec_call(token.approve(onchain.contracts().allowance, to_wei(4))) - .await; + safe.exec_alloy_call( + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(4).into_alloy(), + ) + .into_transaction_request(), + ) + .await; token.mint(safe.address().into_legacy(), to_wei(4)).await; - tx!( - trader.account(), - token.approve(onchain.contracts().allowance, to_wei(4)) - ); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(4).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tracing::info!("Starting services."); let services = Services::new(&onchain).await; @@ -61,7 +74,7 @@ async fn test(web3: Web3) { assert_eq!(balance, 0.into()); let mut order = OrderCreation { from: Some(safe.address().into_legacy()), - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: to_wei(4), buy_token: BUY_ETH_ADDRESS, buy_amount: to_wei(3), diff --git a/crates/e2e/tests/e2e/ethflow.rs b/crates/e2e/tests/e2e/ethflow.rs index f96356e587..6cd7332652 100644 --- a/crates/e2e/tests/e2e/ethflow.rs +++ b/crates/e2e/tests/e2e/ethflow.rs @@ -1,11 +1,32 @@ use { anyhow::bail, autopilot::database::onchain_order_events::ethflow_events::WRAP_ALL_SELECTOR, - contracts::{CoWSwapEthFlow, ERC20Mintable, WETH9}, + contracts::{CoWSwapEthFlow, WETH9, alloy::ERC20Mintable}, database::order_events::OrderEventLabel, - e2e::{nodes::local_node::TestNodeApi, setup::*, tx, tx_value}, + e2e::{ + nodes::local_node::TestNodeApi, + setup::{ + ACCOUNT_ENDPOINT, + API_HOST, + Contracts, + OnchainComponents, + Services, + TIMEOUT, + TRADES_ENDPOINT, + TestAccount, + run_test, + to_wei, + wait_for_condition, + }, + tx, + tx_value, + }, ethcontract::{Account, Bytes, H160, H256, U256, transaction::TransactionResult}, - ethrpc::{Web3, block_stream::timestamp_of_current_block_in_seconds}, + ethrpc::{ + Web3, + alloy::conversions::{IntoAlloy, IntoLegacy}, + block_stream::timestamp_of_current_block_in_seconds, + }, hex_literal::hex, model::{ DomainSeparator, @@ -81,7 +102,7 @@ async fn eth_flow_tx(web3: Web3) { .await; // Get a quote from the services - let buy_token = dai.address(); + let buy_token = dai.address().into_legacy(); let receiver = H160([0x42; 20]); let sell_amount = to_wei(1); let intent = EthFlowTradeIntent { @@ -94,8 +115,9 @@ async fn eth_flow_tx(web3: Web3) { services.start_protocol(solver).await; let approve_call_data = { - let bytes = dai.approve(trader.address(), to_wei(10)).tx.data.unwrap(); - format!("0x{}", hex::encode(&bytes.0)) + let call_builder = dai.approve(trader.address().into_alloy(), to_wei(10).into_alloy()); + let calldata = call_builder.calldata(); + format!("0x{}", hex::encode(calldata)) }; let hash = services @@ -122,7 +144,7 @@ async fn eth_flow_tx(web3: Web3) { }} }} }}"#, - dai.address(), + dai.address().into_legacy(), approve_call_data, onchain.contracts().weth.address(), approve_call_data, @@ -197,11 +219,11 @@ async fn eth_flow_tx(web3: Web3) { // which proofs that the interactions were correctly sandboxed. let trampoline = onchain.contracts().hooks.address(); let allowance = dai - .allowance(trampoline, trader.address()) + .allowance(trampoline.into_alloy(), trader.address().into_alloy()) .call() .await .unwrap(); - assert_eq!(allowance, to_wei(10)); + assert_eq!(allowance, to_wei(10).into_alloy()); let allowance = onchain .contracts() @@ -216,11 +238,11 @@ async fn eth_flow_tx(web3: Web3) { // able to set an allowance on behalf of the settlement contract. let settlement = onchain.contracts().gp_settlement.address(); let allowance = dai - .allowance(settlement, trader.address()) + .allowance(settlement.into_alloy(), trader.address().into_alloy()) .call() .await .unwrap(); - assert_eq!(allowance, 0.into()); + assert_eq!(allowance, alloy::primitives::U256::ZERO); let allowance = onchain .contracts() @@ -250,7 +272,7 @@ async fn eth_flow_without_quote(web3: Web3) { + timestamp_of_current_block_in_seconds(&web3).await.unwrap() + 3600; let ethflow_order = ExtendedEthFlowOrder(EthflowOrder { - buy_token: dai.address(), + buy_token: dai.address().into_legacy(), sell_amount: to_wei(1), buy_amount: 1.into(), valid_to, @@ -302,7 +324,7 @@ async fn eth_flow_indexing_after_refund(web3: Web3) { &services, &(EthFlowTradeIntent { sell_amount: 42.into(), - buy_token: dai.address(), + buy_token: dai.address().into_legacy(), receiver: H160([42; 20]), }) .to_quote_request(dummy_trader.account().address(), &onchain.contracts().weth), @@ -328,7 +350,7 @@ async fn eth_flow_indexing_after_refund(web3: Web3) { .await; // Create the actual order that should be picked up by the services and matched. - let buy_token = dai.address(); + let buy_token = dai.address().into_legacy(); let receiver = H160([0x42; 20]); let sell_amount = to_wei(1); let valid_to = chrono::offset::Utc::now().timestamp() as u32 @@ -478,14 +500,17 @@ async fn test_trade_availability_in_api( async fn test_order_was_settled(ethflow_order: &ExtendedEthFlowOrder, web3: &Web3) { wait_for_condition(TIMEOUT, || async { - let buy_token = ERC20Mintable::at(web3, ethflow_order.0.buy_token); + let buy_token = ERC20Mintable::Instance::new( + ethflow_order.0.buy_token.into_alloy(), + web3.alloy.clone(), + ); let receiver_buy_token_balance = buy_token - .balance_of(ethflow_order.0.receiver) + .balanceOf(ethflow_order.0.receiver.into_alloy()) .call() .await .expect("Unable to get token balance"); - receiver_buy_token_balance >= ethflow_order.0.buy_amount + receiver_buy_token_balance >= ethflow_order.0.buy_amount.into_alloy() }) .await .unwrap(); @@ -817,7 +842,7 @@ async fn eth_flow_zero_buy_amount(web3: Web3) { + timestamp_of_current_block_in_seconds(&web3).await.unwrap() + 3600; let ethflow_order = ExtendedEthFlowOrder(EthflowOrder { - buy_token: dai.address(), + buy_token: dai.address().into_legacy(), sell_amount: to_wei(1), buy_amount: buy_amount.into(), valid_to, diff --git a/crates/e2e/tests/e2e/hooks.rs b/crates/e2e/tests/e2e/hooks.rs index 3d91fd33c2..c878ea8878 100644 --- a/crates/e2e/tests/e2e/hooks.rs +++ b/crates/e2e/tests/e2e/hooks.rs @@ -17,7 +17,10 @@ use { tx_value, }, ethcontract::U256, - ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, @@ -297,14 +300,16 @@ async fn signature(web3: Web3) { // Sign an approval transaction for trading. This will be at nonce 0 because // it is the first transaction evah! + let approval_call_data = token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(5).into_alloy(), + ) + .calldata() + .to_vec(); let approval_builder = safe.sign_transaction( - token.address().into_alloy(), - token - .approve(onchain.contracts().allowance, to_wei(5)) - .tx - .data - .unwrap() - .0, + *token.address(), + approval_call_data, alloy::primitives::U256::ZERO, ); let call_data = approval_builder.calldata().to_vec(); @@ -337,7 +342,7 @@ async fn signature(web3: Web3) { // `from` currently has. sell_amount: to_wei(6), partially_fillable: true, - sell_token: token.address(), + sell_token: token.address().into_legacy(), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(3), valid_to: model::time::now_in_epoch_seconds() + 300, @@ -363,10 +368,11 @@ async fn signature(web3: Web3) { onchain.mint_block().await; let balance = token - .balance_of(safe.address().into_legacy()) + .balanceOf(safe.address()) .call() .await - .unwrap(); + .unwrap() + .into_legacy(); assert_eq!(balance, to_wei(5)); // Check that the Safe really hasn't been deployed yet. @@ -380,7 +386,7 @@ async fn signature(web3: Web3) { tracing::info!("Waiting for trade."); let trade_happened = || async { token - .balance_of(safe.address().into_legacy()) + .balanceOf(safe.address()) .call() .await .unwrap() @@ -448,7 +454,7 @@ async fn partial_fills(web3: Web3) { let order = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: to_wei(2), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -558,21 +564,25 @@ async fn quote_verification(web3: Web3) { .deploy_tokens_with_weth_uni_v2_pools(to_wei(100_000), to_wei(100_000)) .await; token.mint(safe.address().into_legacy(), to_wei(5)).await; - tx!( - trader.account(), - token.approve(onchain.contracts().allowance, to_wei(5)) - ); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(5).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Sign transaction transferring 5 token from the safe to the trader // to fund the trade in a pre-hook. let transfer_builder = safe.sign_transaction( - token.address().into_alloy(), + *token.address(), token - .transfer(trader.address(), to_wei(5)) - .tx - .data - .unwrap() - .0, + .transfer(trader.address().into_alloy(), to_wei(5).into_alloy()) + .calldata() + .to_vec(), alloy::primitives::U256::ZERO, ); let call_data = transfer_builder.calldata().to_vec(); @@ -596,7 +606,7 @@ async fn quote_verification(web3: Web3) { let quote = services .submit_quote(&OrderQuoteRequest { from: trader.address(), - sell_token: token.address(), + sell_token: token.address().into_legacy(), buy_token: onchain.contracts().weth.address(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { diff --git a/crates/e2e/tests/e2e/jit_orders.rs b/crates/e2e/tests/e2e/jit_orders.rs index e25ea90f1a..8d922a19ff 100644 --- a/crates/e2e/tests/e2e/jit_orders.rs +++ b/crates/e2e/tests/e2e/jit_orders.rs @@ -5,6 +5,10 @@ use { tx_value, }, ethcontract::prelude::U256, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderClass, OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -45,10 +49,16 @@ async fn single_limit_order_test(web3: Web3) { .weth .approve(onchain.contracts().allowance, U256::MAX) ); - tx!( - solver.account(), - token.approve(onchain.contracts().allowance, U256::MAX) - ); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + ::alloy::primitives::U256::MAX, + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let services = Services::new(&onchain).await; @@ -71,7 +81,7 @@ async fn single_limit_order_test(web3: Web3) { name: "mock_solver".into(), account: solver.clone(), endpoint: mock_solver.url.clone(), - base_tokens: vec![token.address()], + base_tokens: vec![token.address().into_legacy()], merge_solutions: true, }, ], @@ -104,7 +114,7 @@ async fn single_limit_order_test(web3: Web3) { let order = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: to_wei(10), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -116,8 +126,16 @@ async fn single_limit_order_test(web3: Web3) { SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()), ); - let trader_balance_before = token.balance_of(trader.address()).call().await.unwrap(); - let solver_balance_before = token.balance_of(solver.address()).call().await.unwrap(); + let trader_balance_before = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + let solver_balance_before = token + .balanceOf(solver.address().into_alloy()) + .call() + .await + .unwrap(); let order_id = services.create_order(&order).await.unwrap(); let limit_order = services.get_order(&order_id).await.unwrap(); onchain.mint_block().await; @@ -127,7 +145,7 @@ async fn single_limit_order_test(web3: Web3) { owner: trader.address(), sell: Asset { amount: to_wei(10), - token: token.address(), + token: token.address().into_legacy(), }, buy: Asset { amount: to_wei(1), @@ -148,7 +166,7 @@ async fn single_limit_order_test(web3: Web3) { mock_solver.configure_solution(Some(Solution { id: 0, prices: HashMap::from([ - (token.address(), to_wei(1)), + (token.address().into_legacy(), to_wei(1)), (onchain.contracts().weth.address(), to_wei(1)), ]), trades: vec![ @@ -176,15 +194,23 @@ async fn single_limit_order_test(web3: Web3) { tracing::info!("Waiting for trade."); onchain.mint_block().await; wait_for_condition(TIMEOUT, || async { - let trader_balance_after = token.balance_of(trader.address()).call().await.unwrap(); - let solver_balance_after = token.balance_of(solver.address()).call().await.unwrap(); + let trader_balance_after = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + let solver_balance_after = token + .balanceOf(solver.address().into_alloy()) + .call() + .await + .unwrap(); let trader_balance_increased = - trader_balance_after.saturating_sub(trader_balance_before) >= to_wei(5); + trader_balance_after.saturating_sub(trader_balance_before) >= to_wei(5).into_alloy(); // Since the fee is 0 in the custom solution, the balance difference has to be // exactly 10 wei let solver_balance_decreased = - solver_balance_before.saturating_sub(solver_balance_after) == to_wei(10); + solver_balance_before.saturating_sub(solver_balance_after) == to_wei(10).into_alloy(); trader_balance_increased && solver_balance_decreased }) .await diff --git a/crates/e2e/tests/e2e/limit_orders.rs b/crates/e2e/tests/e2e/limit_orders.rs index 07bba23812..6b06b002ce 100644 --- a/crates/e2e/tests/e2e/limit_orders.rs +++ b/crates/e2e/tests/e2e/limit_orders.rs @@ -6,6 +6,10 @@ use { driver::domain::eth::NonZeroU256, e2e::{nodes::forked_node::ForkedNodeApi, setup::*, tx}, ethcontract::{H160, prelude::U256}, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, fee::{FeePolicyOrderClass, ProtocolFee, ProtocolFeesConfig}, model::{ order::{OrderClass, OrderCreation, OrderKind}, @@ -108,30 +112,36 @@ async fn single_limit_order_test(web3: Web3) { token_b.mint(solver.address(), to_wei(1000)).await; tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -142,19 +152,25 @@ async fn single_limit_order_test(web3: Web3) { ); // Approve GPv2 for trading - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Place Orders let services = Services::new(&onchain).await; services.start_protocol(solver).await; let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -165,7 +181,11 @@ async fn single_limit_order_test(web3: Web3) { &onchain.contracts().domain_separator, SecretKeyRef::from(&SecretKey::from_slice(trader_a.private_key()).unwrap()), ); - let balance_before = token_b.balance_of(trader_a.address()).call().await.unwrap(); + let balance_before = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); let order_id = services.create_order(&order).await.unwrap(); // we hide the quote's execution plan while the order is still fillable @@ -182,8 +202,12 @@ async fn single_limit_order_test(web3: Web3) { // Drive solution tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { - let balance_after = token_b.balance_of(trader_a.address()).call().await.unwrap(); - balance_after.checked_sub(balance_before).unwrap() >= to_wei(5) + let balance_after = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); + balance_after.checked_sub(balance_before).unwrap() >= to_wei(5).into_alloy() }) .await .unwrap(); @@ -216,30 +240,36 @@ async fn two_limit_orders_test(web3: Web3) { // Create and fund Uniswap pool tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -250,23 +280,35 @@ async fn two_limit_orders_test(web3: Web3) { ); // Approve GPv2 for trading - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(10)) - ); - tx!( - trader_b.account(), - token_b.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader_b.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Place Orders let services = Services::new(&onchain).await; services.start_protocol(solver).await; let order_a = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -278,8 +320,16 @@ async fn two_limit_orders_test(web3: Web3) { SecretKeyRef::from(&SecretKey::from_slice(trader_a.private_key()).unwrap()), ); - let balance_before_a = token_b.balance_of(trader_a.address()).call().await.unwrap(); - let balance_before_b = token_a.balance_of(trader_b.address()).call().await.unwrap(); + let balance_before_a = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); + let balance_before_b = token_a + .balanceOf(trader_b.address().into_alloy()) + .call() + .await + .unwrap(); let order_id = services.create_order(&order_a).await.unwrap(); onchain.mint_block().await; @@ -288,9 +338,9 @@ async fn two_limit_orders_test(web3: Web3) { assert!(limit_order.metadata.class.is_limit()); let order_b = OrderCreation { - sell_token: token_b.address(), + sell_token: token_b.address().into_legacy(), sell_amount: to_wei(5), - buy_token: token_a.address(), + buy_token: token_a.address().into_legacy(), buy_amount: to_wei(2), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -309,10 +359,20 @@ async fn two_limit_orders_test(web3: Web3) { // Drive solution tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { - let balance_after_a = token_b.balance_of(trader_a.address()).call().await.unwrap(); - let balance_after_b = token_a.balance_of(trader_b.address()).call().await.unwrap(); - let order_a_settled = balance_after_a.saturating_sub(balance_before_a) >= to_wei(5); - let order_b_settled = balance_after_b.saturating_sub(balance_before_b) >= to_wei(2); + let balance_after_a = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); + let balance_after_b = token_a + .balanceOf(trader_b.address().into_alloy()) + .call() + .await + .unwrap(); + let order_a_settled = + balance_after_a.saturating_sub(balance_before_a) >= to_wei(5).into_alloy(); + let order_b_settled = + balance_after_b.saturating_sub(balance_before_b) >= to_wei(2).into_alloy(); order_a_settled && order_b_settled }) .await @@ -347,14 +407,26 @@ async fn two_limit_orders_multiple_winners_test(web3: Web3) { .await; // Approve GPv2 for trading - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(100)) - ); - tx!( - trader_b.account(), - token_b.approve(onchain.contracts().allowance, to_wei(100)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader_b.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Start system, with two solvers, one that knows about base_a and one that // knows about base_b @@ -365,7 +437,7 @@ async fn two_limit_orders_multiple_winners_test(web3: Web3) { "test_solver".into(), solver_a.clone(), onchain.contracts().weth.address(), - vec![base_a.address()], + vec![base_a.address().into_legacy()], 2, false, ) @@ -374,7 +446,7 @@ async fn two_limit_orders_multiple_winners_test(web3: Web3) { "solver2".into(), solver_b.clone(), onchain.contracts().weth.address(), - vec![base_b.address()], + vec![base_b.address().into_legacy()], 2, false, ) @@ -393,9 +465,9 @@ async fn two_limit_orders_multiple_winners_test(web3: Web3) { // Place Orders let order_a = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_c.address(), + buy_token: token_c.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -409,9 +481,9 @@ async fn two_limit_orders_multiple_winners_test(web3: Web3) { let uid_a = services.create_order(&order_a).await.unwrap(); let order_b = OrderCreation { - sell_token: token_b.address(), + sell_token: token_b.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_d.address(), + buy_token: token_d.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -567,10 +639,16 @@ async fn too_many_limit_orders_test(web3: Web3) { token_a.mint(trader.address(), to_wei(1)).await; // Approve GPv2 for trading - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(101)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(101).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Place Orders let services = Services::new(&onchain).await; @@ -598,7 +676,7 @@ async fn too_many_limit_orders_test(web3: Web3) { .await; let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(1), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(1), @@ -616,7 +694,7 @@ async fn too_many_limit_orders_test(web3: Web3) { // Attempt to place another order, but the orderbook is configured to allow only // one limit order per user. let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(1), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(2), @@ -646,10 +724,16 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { token.mint(trader.address(), to_wei(100)).await; // Approve GPv2 for trading - tx!( - trader.account(), - token.approve(onchain.contracts().allowance, to_wei(101)) - ); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(101).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Place Orders let services = Services::new(&onchain).await; @@ -678,7 +762,7 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { let quote_request = OrderQuoteRequest { from: trader.address(), - sell_token: token.address(), + sell_token: token.address().into_legacy(), buy_token: onchain.contracts().weth.address(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { @@ -691,7 +775,7 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { // Place "in-market" order let order = OrderCreation { - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: quote.quote.sell_amount, buy_token: onchain.contracts().weth.address(), buy_amount: quote.quote.buy_amount.saturating_sub(to_wei(4)), @@ -708,7 +792,7 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { // Place a "limit" order let order = OrderCreation { - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: to_wei(1), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(3), @@ -727,7 +811,7 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { // Place another "in-market" order in order to check it is not limited let order = OrderCreation { - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: quote.quote.sell_amount, buy_token: onchain.contracts().weth.address(), buy_amount: quote.quote.buy_amount.saturating_sub(to_wei(2)), @@ -744,7 +828,7 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { // Place a "limit" order in order to see if fails let order = OrderCreation { - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: to_wei(1), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(2), @@ -977,10 +1061,16 @@ async fn no_liquidity_limit_order(web3: Web3) { token_a.mint(trader_a.address(), to_wei(10)).await; // Approve GPv2 for trading - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Setup services let protocol_fees_config = ProtocolFeesConfig(vec![ @@ -1017,7 +1107,7 @@ async fn no_liquidity_limit_order(web3: Web3) { // Place order let mut order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(1), @@ -1036,7 +1126,7 @@ async fn no_liquidity_limit_order(web3: Web3) { assert_eq!(limit_order.metadata.class, OrderClass::Limit); // Cannot place orders with unsupported tokens - order.sell_token = unsupported.address(); + order.sell_token = unsupported.address().into_legacy(); services .create_order(&order.sign( EcdsaSigningScheme::Eip712, diff --git a/crates/e2e/tests/e2e/order_cancellation.rs b/crates/e2e/tests/e2e/order_cancellation.rs index 2a7d385a02..a2fcc347f7 100644 --- a/crates/e2e/tests/e2e/order_cancellation.rs +++ b/crates/e2e/tests/e2e/order_cancellation.rs @@ -1,6 +1,10 @@ use { database::order_events::OrderEventLabel, - e2e::{setup::*, tx}, + e2e::setup::*, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{ CancellationPayload, @@ -40,10 +44,16 @@ async fn order_cancellation(web3: Web3) { token.mint(trader.address(), to_wei(10)).await; // Approve GPv2 for trading - tx!( - trader.account(), - token.approve(onchain.contracts().allowance, to_wei(10)) - ); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let services = Services::new(&onchain).await; colocation::start_driver( @@ -86,7 +96,7 @@ async fn order_cancellation(web3: Web3) { let request = OrderQuoteRequest { from: trader.address(), - sell_token: token.address(), + sell_token: token.address().into_legacy(), buy_token: onchain.contracts().weth.address(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::AfterFee { diff --git a/crates/e2e/tests/e2e/partial_fill.rs b/crates/e2e/tests/e2e/partial_fill.rs index e7362fe06d..4442bc8a7e 100644 --- a/crates/e2e/tests/e2e/partial_fill.rs +++ b/crates/e2e/tests/e2e/partial_fill.rs @@ -1,6 +1,7 @@ use { + ::alloy::primitives::U256, e2e::{setup::*, tx, tx_value}, - ethcontract::U256, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderKind}, signature::{EcdsaSigningScheme, Signature, SigningScheme}, @@ -46,12 +47,16 @@ async fn test(web3: Web3) { services.start_protocol(solver.clone()).await; tracing::info!("Placing order"); - let balance = token.balance_of(trader.address()).call().await.unwrap(); - assert_eq!(balance, 0.into()); + let balance = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + assert_eq!(balance, U256::ZERO); let order = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: to_wei(4), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(3), valid_to: model::time::now_in_epoch_seconds() + 300, partially_fillable: true, @@ -69,8 +74,14 @@ async fn test(web3: Web3) { onchain.mint_block().await; tracing::info!("Waiting for trade."); - let trade_happened = - || async { token.balance_of(trader.address()).call().await.unwrap() != 0.into() }; + let trade_happened = || async { + token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap() + != U256::ZERO + }; wait_for_condition(TIMEOUT, trade_happened).await.unwrap(); // We expect the partially fillable order to only fill half-way. @@ -84,18 +95,22 @@ async fn test(web3: Web3) { assert!( // Sell balance is strictly less than 2.0 because of the fee. (1_999_000_000_000_000_000_u128..2_000_000_000_000_000_000_u128) - .contains(&sell_balance.as_u128()) + .contains(&u128::try_from(sell_balance).unwrap()) ); - let buy_balance = token.balance_of(trader.address()).call().await.unwrap(); + let buy_balance = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); assert!( (1_650_000_000_000_000_000_u128..1_670_000_000_000_000_000_u128) - .contains(&buy_balance.as_u128()) + .contains(&u128::try_from(buy_balance).unwrap()) ); let settlement_event_processed = || async { onchain.mint_block().await; let order = services.get_order(&uid).await.unwrap(); - order.metadata.executed_fee > U256::zero() + order.metadata.executed_fee > U256::ZERO.into_legacy() }; wait_for_condition(TIMEOUT, settlement_event_processed) .await diff --git a/crates/e2e/tests/e2e/partially_fillable_balance.rs b/crates/e2e/tests/e2e/partially_fillable_balance.rs index b74d7a54d9..1672b435f3 100644 --- a/crates/e2e/tests/e2e/partially_fillable_balance.rs +++ b/crates/e2e/tests/e2e/partially_fillable_balance.rs @@ -1,6 +1,10 @@ use { + ::alloy::primitives::U256, e2e::{setup::*, tx}, - ethcontract::prelude::U256, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -31,51 +35,62 @@ async fn test(web3: Web3) { tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), 0_u64.into(), solver.address(), - U256::max_value(), + U256::MAX.into_legacy(), ) ); - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(500)) - ); + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(500).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let services = Services::new(&onchain).await; services.start_protocol(solver).await; let order_a = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(100), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(50), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -95,7 +110,11 @@ async fn test(web3: Web3) { tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { - let balance = token_b.balance_of(trader_a.address()).call().await.unwrap(); + let balance = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); !balance.is_zero() }) .await @@ -104,19 +123,20 @@ async fn test(web3: Web3) { // Expecting a partial fill because order sells 100 but user only has balance of // 50. let sell_balance = token_a - .balance_of(trader_a.address()) + .balanceOf(trader_a.address().into_alloy()) .call() .await - .unwrap() - .to_f64_lossy(); + .unwrap(); // Depending on how the solver works might not have sold all balance. - assert!((0e18..=1e18).contains(&sell_balance)); + assert!(U256::ZERO <= sell_balance && sell_balance < U256::from(10u64.pow(18))); let buy_balance = token_b - .balance_of(trader_a.address()) + .balanceOf(trader_a.address().into_alloy()) .call() .await - .unwrap() - .to_f64_lossy(); + .unwrap(); // We don't know exact buy balance because of the fee. - assert!((45e18..=55e18).contains(&buy_balance)); + assert!( + U256::from(45) * U256::from(10u64.pow(18)) <= buy_balance + && buy_balance <= U256::from(55) * U256::from(10u64.pow(18)) + ); } diff --git a/crates/e2e/tests/e2e/partially_fillable_pool.rs b/crates/e2e/tests/e2e/partially_fillable_pool.rs index 18a1adcf07..d2af484c39 100644 --- a/crates/e2e/tests/e2e/partially_fillable_pool.rs +++ b/crates/e2e/tests/e2e/partially_fillable_pool.rs @@ -1,6 +1,10 @@ use { e2e::{setup::*, tx}, ethcontract::prelude::U256, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -9,7 +13,6 @@ use { shared::ethrpc::Web3, web3::signing::SecretKeyRef, }; - #[tokio::test] #[ignore] async fn local_node_partially_fillable_pool() { @@ -31,30 +34,36 @@ async fn test(web3: Web3) { tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -64,19 +73,24 @@ async fn test(web3: Web3) { ) ); - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(500)) - ); + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(500).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let services = Services::new(&onchain).await; services.start_protocol(solver).await; onchain.mint_block().await; let order_a = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(500), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(390), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -95,7 +109,11 @@ async fn test(web3: Web3) { tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { - let balance = token_b.balance_of(trader_a.address()).call().await.unwrap(); + let balance = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); onchain.mint_block().await; !balance.is_zero() }) @@ -103,16 +121,24 @@ async fn test(web3: Web3) { .unwrap(); // Expecting a partial fill because the pool cannot trade the full amount. - let sell_balance = token_a.balance_of(trader_a.address()).call().await.unwrap(); + let sell_balance = token_a + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); assert!( // Sell balance is strictly less than 250.0 because of the fee. (249_999_000_000_000_000_000_u128..250_000_000_000_000_000_000_u128) - .contains(&sell_balance.as_u128()) + .contains(&u128::try_from(sell_balance).unwrap()) ); - let buy_balance = token_b.balance_of(trader_a.address()).call().await.unwrap(); + let buy_balance = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); assert!( (199_000_000_000_000_000_000_u128..201_000_000_000_000_000_000_u128) - .contains(&buy_balance.as_u128()) + .contains(&u128::try_from(buy_balance).unwrap()) ); let metadata_updated = || async { diff --git a/crates/e2e/tests/e2e/place_order_with_quote.rs b/crates/e2e/tests/e2e/place_order_with_quote.rs index efc259e782..2290629bbb 100644 --- a/crates/e2e/tests/e2e/place_order_with_quote.rs +++ b/crates/e2e/tests/e2e/place_order_with_quote.rs @@ -1,6 +1,8 @@ use { + ::alloy::primitives::U256, driver::domain::eth::NonZeroU256, e2e::{nodes::local_node::TestNodeApi, setup::*, tx, tx_value}, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderKind}, quote::{OrderQuoteRequest, OrderQuoteSide, SellAmount}, @@ -55,7 +57,7 @@ async fn place_order_with_quote(web3: Web3) { let quote_request = OrderQuoteRequest { from: trader.address(), sell_token: onchain.contracts().weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: NonZeroU256::try_from(quote_sell_amount).unwrap(), @@ -74,13 +76,17 @@ async fn place_order_with_quote(web3: Web3) { tracing::debug!(?quote_metadata); tracing::info!("Placing order"); - let balance = token.balance_of(trader.address()).call().await.unwrap(); - assert_eq!(balance, 0.into()); + let balance = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + assert_eq!(balance, U256::ZERO); let order = OrderCreation { quote_id: quote_response.id, sell_token: onchain.contracts().weth.address(), sell_amount: quote_sell_amount, - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: quote_response.quote.buy_amount, valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index ef3d5bbdb2..c4bde8b295 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -7,6 +7,10 @@ use { tx_value, }, ethcontract::{Address, prelude::U256}, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{Order, OrderCreation, OrderCreationAppData, OrderKind}, quote::{ @@ -90,35 +94,40 @@ async fn combined_protocol_fees(web3: Web3) { &partner_fee_order_token, ] { token.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - token.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); - for trader in &[&trader] { - tx!( - trader.account(), - token.approve(onchain.contracts().uniswap_v2_router.address(), to_wei(100)) - ); - } - } + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); - for trader in &[&trader] { - tx!( - trader.account(), - onchain - .contracts() - .weth - .approve(onchain.contracts().allowance, to_wei(100)) - ); - tx_value!( - trader.account(), - to_wei(100), - onchain.contracts().weth.deposit() - ); + token + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } + + tx!( + trader.account(), + onchain + .contracts() + .weth + .approve(onchain.contracts().allowance, to_wei(100)) + ); + tx_value!( + trader.account(), + to_wei(100), + onchain.contracts().weth.deposit() + ); tx!( solver.account(), onchain @@ -156,7 +165,7 @@ async fn combined_protocol_fees(web3: Web3) { get_quote( &services, onchain.contracts().weth.address(), - token.address(), + token.address().into_legacy(), OrderKind::Sell, sell_amount, quote_valid_to, @@ -221,7 +230,7 @@ async fn combined_protocol_fees(web3: Web3) { let new_market_order_quote = get_quote( &services, onchain.contracts().weth.address(), - market_order_token.address(), + market_order_token.address().into_legacy(), OrderKind::Sell, sell_amount, model::time::now_in_epoch_seconds() + 300, @@ -250,7 +259,7 @@ async fn combined_protocol_fees(web3: Web3) { get_quote( &services, onchain.contracts().weth.address(), - token.address(), + token.address().into_legacy(), OrderKind::Sell, sell_amount, quote_valid_to, @@ -353,10 +362,12 @@ async fn combined_protocol_fees(web3: Web3) { &limit_order_token, &partner_fee_order_token, ] - .map(|token| { + .map(|token| async { token - .balance_of(onchain.contracts().gp_settlement.address()) + .balanceOf(onchain.contracts().gp_settlement.address().into_alloy()) .call() + .await + .map(|balance| balance.into_legacy()) }), ) .await @@ -419,17 +430,26 @@ async fn surplus_partner_fee(web3: Web3) { .await; token.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - token.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); - tx!( - trader.account(), - token.approve(onchain.contracts().uniswap_v2_router.address(), to_wei(100)) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( trader.account(), onchain @@ -468,7 +488,7 @@ async fn surplus_partner_fee(web3: Web3) { sell_token: onchain.contracts().weth.address(), // just set any low amount since it doesn't matter for this test buy_amount: to_wei(1), - buy_token: token.address(), + buy_token: token.address().into_legacy(), app_data: partner_fee_app_data.clone(), valid_to: model::time::now_in_epoch_seconds() + 300, ..Default::default() @@ -626,30 +646,36 @@ async fn volume_fee_buy_order_test(web3: Web3) { token_dai.mint(solver.address(), to_wei(1000)).await; tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_gno.address(), token_dai.address()) - ); - tx!( - solver.account(), - token_gno.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_gno.address().into_legacy(), + token_dai.address().into_legacy() ) ); - tx!( - solver.account(), - token_dai.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_gno + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_dai + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_gno.address(), - token_dai.address(), + token_gno.address().into_legacy(), + token_dai.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -660,10 +686,16 @@ async fn volume_fee_buy_order_test(web3: Web3) { ); // Approve GPv2 for trading - tx!( - trader.account(), - token_gno.approve(onchain.contracts().allowance, to_wei(100)) - ); + + token_gno + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Place Orders let services = Services::new(&onchain).await; @@ -679,8 +711,8 @@ async fn volume_fee_buy_order_test(web3: Web3) { let quote = get_quote( &services, - token_gno.address(), - token_dai.address(), + token_gno.address().into_legacy(), + token_dai.address().into_legacy(), OrderKind::Buy, to_wei(5), model::time::now_in_epoch_seconds() + 300, @@ -690,9 +722,9 @@ async fn volume_fee_buy_order_test(web3: Web3) { .quote; let order = OrderCreation { - sell_token: token_gno.address(), + sell_token: token_gno.address().into_legacy(), sell_amount: quote.sell_amount * 3 / 2, - buy_token: token_dai.address(), + buy_token: token_dai.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Buy, @@ -720,9 +752,10 @@ async fn volume_fee_buy_order_test(web3: Web3) { // Check settlement contract balance let balance_after = token_gno - .balance_of(onchain.contracts().gp_settlement.address()) + .balanceOf(onchain.contracts().gp_settlement.address().into_alloy()) .call() .await - .unwrap(); + .unwrap() + .into_legacy(); assert_eq!(order.metadata.executed_fee, balance_after); } diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 623999033b..7a4ebd11e0 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -1,8 +1,14 @@ use { bigdecimal::{BigDecimal, Zero}, - e2e::{setup::*, tx}, + e2e::setup::*, ethcontract::{H160, U256}, - ethrpc::Web3, + ethrpc::{ + Web3, + alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, + }, model::{ interaction::InteractionData, order::{BuyTokenDestination, OrderKind, SellTokenSource}, @@ -87,10 +93,16 @@ async fn standard_verified_quote(web3: Web3) { .await; token.mint(trader.address(), to_wei(1)).await; - tx!( - trader.account(), - token.approve(onchain.contracts().allowance, to_wei(1)) - ); + + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(1).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tracing::info!("Starting services."); let services = Services::new(&onchain).await; @@ -100,7 +112,7 @@ async fn standard_verified_quote(web3: Web3) { let response = services .submit_quote(&OrderQuoteRequest { from: trader.address(), - sell_token: token.address(), + sell_token: token.address().into_legacy(), buy_token: onchain.contracts().weth.address(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { @@ -193,9 +205,9 @@ async fn test_bypass_verification_for_rfq_quotes(web3: Web3) { verified: true, execution: QuoteExecution { interactions: vec![InteractionData { - target: H160::from_str("0xdef1c0ded9bec7f1a1670819833240f027b25eff").unwrap(), + target: H160::from_str("0xdef1c0ded9bec7f1a1670819833240f027b25eff").unwrap(), value: 0.into(), - call_data: hex::decode("aa77476c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000e357b42c3a9d8ccf0000000000000000000000000000000000000000000000000000000004d0e79e000000000000000000000000a69babef1ca67a37ffaf7a485dfff3382056e78c0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066360af101ffffffffffffffffffffffffffffffffffffff0f3f47f166360a8d0000003f0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001c66b3383f287dd9c85ad90e7c5a576ea4ba1bdf5a001d794a9afa379e6b2517b47e487a1aef32e75af432cbdbd301ada42754eaeac21ec4ca744afd92732f47540000000000000000000000000000000000000000000000000000000004d0c80f").unwrap() + call_data: hex::decode("aa77476c000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000002260fac5e5542a773aa44fbcfedf7c193bc2c599000000000000000000000000000000000000000000000000e357b42c3a9d8ccf0000000000000000000000000000000000000000000000000000000004d0e79e000000000000000000000000a69babef1ca67a37ffaf7a485dfff3382056e78c0000000000000000000000009008d19f58aabd9ed0d60971565aa8510560ab41000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066360af101ffffffffffffffffffffffffffffffffffffff0f3f47f166360a8d0000003f0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000001c66b3383f287dd9c85ad90e7c5a576ea4ba1bdf5a001d794a9afa379e6b2517b47e487a1aef32e75af432cbdbd301ada42754eaeac21ec4ca744afd92732f47540000000000000000000000000000000000000000000000000000000004d0c80f").unwrap() }], pre_interactions: vec![], jit_orders: vec![], @@ -251,7 +263,7 @@ async fn verified_quote_eth_balance(web3: Web3) { .submit_quote(&OrderQuoteRequest { from: trader.address(), sell_token: weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: to_wei(1).try_into().unwrap(), @@ -288,7 +300,7 @@ async fn verified_quote_for_settlement_contract(web3: Web3) { let request = OrderQuoteRequest { sell_token: onchain.contracts().weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: to_wei(3).try_into().unwrap(), @@ -377,19 +389,29 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { // quote where the trader has no balances or approval set from TOKEN->WETH assert_eq!( ( - token.balance_of(trader.address()).call().await.unwrap(), token - .allowance(trader.address(), onchain.contracts().allowance) + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(), + token + .allowance( + trader.address().into_alloy(), + onchain.contracts().allowance.into_alloy() + ) .call() .await .unwrap(), ), - (U256::zero(), U256::zero()), + ( + ::alloy::primitives::U256::ZERO, + ::alloy::primitives::U256::ZERO + ), ); let response = services .submit_quote(&OrderQuoteRequest { from: trader.address(), - sell_token: token.address(), + sell_token: token.address().into_legacy(), buy_token: weth.address(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { @@ -423,7 +445,7 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { .submit_quote(&OrderQuoteRequest { from: trader.address(), sell_token: weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: to_wei(1).try_into().unwrap(), @@ -441,7 +463,7 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { .submit_quote(&OrderQuoteRequest { from: H160::zero(), sell_token: weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: to_wei(1).try_into().unwrap(), @@ -459,7 +481,7 @@ async fn verified_quote_with_simulated_balance(web3: Web3) { .submit_quote(&OrderQuoteRequest { from: H160::zero(), sell_token: weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: to_wei(1).try_into().unwrap(), diff --git a/crates/e2e/tests/e2e/quoting.rs b/crates/e2e/tests/e2e/quoting.rs index 34ee38ee82..03692c76fa 100644 --- a/crates/e2e/tests/e2e/quoting.rs +++ b/crates/e2e/tests/e2e/quoting.rs @@ -4,6 +4,10 @@ use { tx, tx_value, }, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, futures::FutureExt, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind}, @@ -73,7 +77,7 @@ async fn test(web3: Web3) { let request = OrderQuoteRequest { from: trader.address(), sell_token: onchain.contracts().weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: NonZeroU256::try_from(to_wei(1)).unwrap(), @@ -206,7 +210,7 @@ async fn uses_stale_liquidity(web3: Web3) { let quote = OrderQuoteRequest { from: trader.address(), sell_token: onchain.contracts().weth.address(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::AfterFee { value: NonZeroU256::new(to_wei(1)).unwrap(), @@ -266,14 +270,14 @@ async fn quote_timeout(web3: Web3) { name: "test_solver".into(), account: solver.clone(), endpoint: mock_solver.url.clone(), - base_tokens: vec![sell_token.address()], + base_tokens: vec![sell_token.address().into_legacy()], merge_solutions: true, }, SolverEngine { name: "test_quoter".into(), account: solver.clone(), endpoint: mock_solver.url.clone(), - base_tokens: vec![sell_token.address()], + base_tokens: vec![sell_token.address().into_legacy()], merge_solutions: true, }, ], @@ -307,7 +311,7 @@ async fn quote_timeout(web3: Web3) { let quote_request = |timeout| OrderQuoteRequest { from: trader.address(), sell_token: onchain.contracts().weth.address(), - buy_token: sell_token.address(), + buy_token: sell_token.address().into_legacy(), side: OrderQuoteSide::Sell { sell_amount: SellAmount::BeforeFee { value: NonZeroU256::try_from(to_wei(1)).unwrap(), @@ -329,7 +333,9 @@ async fn quote_timeout(web3: Web3) { // native token price requests are also capped to the max timeout let start = std::time::Instant::now(); - let res = services.get_native_price(&sell_token.address()).await; + let res = services + .get_native_price(&sell_token.address().into_legacy()) + .await; assert!(res.unwrap_err().1.contains("NoLiquidity")); assert_within_variance(start, MAX_QUOTE_TIME_MS); @@ -359,13 +365,19 @@ async fn quote_timeout(web3: Web3) { // set up trader to pass balance checks during order creation sell_token.mint(trader.address(), to_wei(1)).await; - tx!( - trader.account(), - sell_token.approve(onchain.contracts().allowance, to_wei(1)) - ); + + sell_token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(1).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let order = OrderCreation { - sell_token: sell_token.address(), + sell_token: sell_token.address().into_legacy(), sell_amount: to_wei(1), buy_token: Default::default(), buy_amount: to_wei(1), diff --git a/crates/e2e/tests/e2e/refunder.rs b/crates/e2e/tests/e2e/refunder.rs index da9183ad5d..8c484de9ea 100644 --- a/crates/e2e/tests/e2e/refunder.rs +++ b/crates/e2e/tests/e2e/refunder.rs @@ -3,7 +3,11 @@ use { chrono::{TimeZone, Utc}, e2e::{nodes::local_node::TestNodeApi, setup::*}, ethcontract::{H160, U256}, - ethrpc::{Web3, block_stream::timestamp_of_current_block_in_seconds}, + ethrpc::{ + Web3, + alloy::conversions::IntoLegacy, + block_stream::timestamp_of_current_block_in_seconds, + }, model::quote::{OrderQuoteRequest, OrderQuoteSide, QuoteSigningScheme, Validity}, number::nonzero::U256 as NonZeroU256, refunder::refund_service::RefundService, @@ -29,7 +33,7 @@ async fn refunder_tx(web3: Web3) { services.start_protocol(solver).await; // Get quote id for order placement - let buy_token = token.address(); + let buy_token = token.address().into_legacy(); let receiver = Some(H160([42; 20])); let sell_amount = U256::from("3000000000000000"); diff --git a/crates/e2e/tests/e2e/replace_order.rs b/crates/e2e/tests/e2e/replace_order.rs index 7ce74dc046..245c330a4b 100644 --- a/crates/e2e/tests/e2e/replace_order.rs +++ b/crates/e2e/tests/e2e/replace_order.rs @@ -1,6 +1,10 @@ use { e2e::{nodes::local_node::TestNodeApi, setup::*, tx}, ethcontract::prelude::U256, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind, OrderStatus}, signature::EcdsaSigningScheme, @@ -51,30 +55,36 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { token_b.mint(solver.address(), to_wei(1000)).await; tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -85,10 +95,16 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { ); // Approve GPv2 for trading - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(15)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(15).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // disable auto mining to prevent order being immediately executed web3.api::>() @@ -101,9 +117,9 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { services.start_protocol(solver).await; let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -114,7 +130,11 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { &onchain.contracts().domain_separator, SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()), ); - let balance_before = token_a.balance_of(trader.address()).call().await.unwrap(); + let balance_before = token_a + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); onchain.mint_block().await; let order_id = services.create_order(&order).await.unwrap(); @@ -132,9 +152,9 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { // Replace order let new_order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(3), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -174,8 +194,12 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { tracing::info!("Waiting for the old order to be executed"); wait_for_condition(TIMEOUT, || async { - let balance_after = token_a.balance_of(trader.address()).call().await.unwrap(); - balance_before.saturating_sub(balance_after) == to_wei(10) + let balance_after = token_a + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + balance_before.saturating_sub(balance_after) == to_wei(10).into_alloy() && !services.get_trades(&order_id).await.unwrap().is_empty() }) .await @@ -216,30 +240,36 @@ async fn try_replace_someone_else_order_test(web3: Web3) { token_b.mint(solver.address(), to_wei(1000)).await; tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -250,14 +280,26 @@ async fn try_replace_someone_else_order_test(web3: Web3) { ); // Approve GPv2 for trading - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(15)) - ); - tx!( - trader_b.account(), - token_a.approve(onchain.contracts().allowance, to_wei(15)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(15).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(15).into_alloy(), + ) + .from(trader_b.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Place Orders let services = Services::new(&onchain).await; @@ -266,9 +308,9 @@ async fn try_replace_someone_else_order_test(web3: Web3) { onchain.mint_block().await; let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, partially_fillable: false, @@ -284,9 +326,9 @@ async fn try_replace_someone_else_order_test(web3: Web3) { // Replace order let new_order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(3), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -303,7 +345,11 @@ async fn try_replace_someone_else_order_test(web3: Web3) { &onchain.contracts().domain_separator, SecretKeyRef::from(&SecretKey::from_slice(trader_b.private_key()).unwrap()), ); - let balance_before = token_a.balance_of(trader_a.address()).call().await.unwrap(); + let balance_before = token_a + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); let response = services.create_order(&new_order).await; let (error_code, _) = response.err().unwrap(); assert_eq!(error_code, StatusCode::UNAUTHORIZED); @@ -311,8 +357,12 @@ async fn try_replace_someone_else_order_test(web3: Web3) { // Drive solution tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { - let balance_after = token_a.balance_of(trader_a.address()).call().await.unwrap(); - balance_before.saturating_sub(balance_after) == to_wei(10) + let balance_after = token_a + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); + balance_before.saturating_sub(balance_after) == to_wei(10).into_alloy() }) .await .unwrap(); @@ -335,30 +385,36 @@ async fn single_replace_order_test(web3: Web3) { token_b.mint(solver.address(), to_wei(1000)).await; tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -369,10 +425,16 @@ async fn single_replace_order_test(web3: Web3) { ); // Approve GPv2 for trading - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(15)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(15).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // disble solver to prevent orders from being settled while we // want to replace them @@ -392,11 +454,15 @@ async fn single_replace_order_test(web3: Web3) { ) .await; - let balance_before = token_a.balance_of(trader.address()).call().await.unwrap(); + let balance_before = token_a + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -423,9 +489,9 @@ async fn single_replace_order_test(web3: Web3) { // Replace order let new_order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(3), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -471,9 +537,13 @@ async fn single_replace_order_test(web3: Web3) { // Drive solution to verify that new order can be settled tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { - let balance_after = token_a.balance_of(trader.address()).call().await.unwrap(); + let balance_after = token_a + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); onchain.mint_block().await; - balance_before.saturating_sub(balance_after) == to_wei(3) + balance_before.saturating_sub(balance_after) == to_wei(3).into_alloy() }) .await .unwrap(); diff --git a/crates/e2e/tests/e2e/smart_contract_orders.rs b/crates/e2e/tests/e2e/smart_contract_orders.rs index f56aa8fa67..fbc8b3ec6e 100644 --- a/crates/e2e/tests/e2e/smart_contract_orders.rs +++ b/crates/e2e/tests/e2e/smart_contract_orders.rs @@ -4,7 +4,7 @@ use { tx, }, ethcontract::{Bytes, H160, U256}, - ethrpc::alloy::conversions::IntoLegacy, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderCreationAppData, OrderKind, OrderStatus, OrderUid}, signature::Signature, @@ -39,15 +39,22 @@ async fn smart_contract_orders(web3: Web3) { token.mint(safe.address().into_legacy(), to_wei(10)).await; // Approve GPv2 for trading - safe.exec_call(token.approve(onchain.contracts().allowance, to_wei(10))) - .await; + safe.exec_alloy_call( + token + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(10).into_alloy(), + ) + .into_transaction_request(), + ) + .await; let services = Services::new(&onchain).await; services.start_protocol(solver).await; let order_template = OrderCreation { kind: OrderKind::Sell, - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: to_wei(5), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(3), @@ -130,7 +137,7 @@ async fn smart_contract_orders(web3: Web3) { tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { let token_balance = token - .balance_of(safe.address().into_legacy()) + .balanceOf(safe.address()) .call() .await .expect("Couldn't fetch token balance"); diff --git a/crates/e2e/tests/e2e/solver_competition.rs b/crates/e2e/tests/e2e/solver_competition.rs index eeb8e26358..2b3edb25f5 100644 --- a/crates/e2e/tests/e2e/solver_competition.rs +++ b/crates/e2e/tests/e2e/solver_competition.rs @@ -1,9 +1,10 @@ use { - e2e::{ - setup::{colocation::SolverEngine, mock::Mock, *}, - tx, + ::alloy::primitives::U256, + e2e::setup::{colocation::SolverEngine, mock::Mock, *}, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, }, - ethcontract::prelude::U256, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -47,10 +48,16 @@ async fn solver_competition(web3: Web3) { token_a.mint(solver.address(), to_wei(1000)).await; // Approve GPv2 for trading - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(100)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Start system colocation::start_driver( @@ -94,7 +101,7 @@ async fn solver_competition(web3: Web3) { // Place Order let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(5), @@ -111,8 +118,14 @@ async fn solver_competition(web3: Web3) { onchain.mint_block().await; tracing::info!("waiting for trade"); - let trade_happened = - || async { token_a.balance_of(trader.address()).call().await.unwrap() == U256::zero() }; + let trade_happened = || async { + token_a + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap() + == U256::ZERO + }; wait_for_condition(TIMEOUT, trade_happened).await.unwrap(); let indexed_trades = || async { @@ -170,14 +183,26 @@ async fn wrong_solution_submission_address(web3: Web3) { .await; // Approve GPv2 for trading - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(100)) - ); - tx!( - trader_b.account(), - token_b.approve(onchain.contracts().allowance, to_wei(100)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(100).into_alloy(), + ) + .from(trader_b.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Start system, with two solvers, one that knows about base_a and one that // knows about base_b @@ -188,7 +213,7 @@ async fn wrong_solution_submission_address(web3: Web3) { "test_solver".into(), solver.clone(), onchain.contracts().weth.address(), - vec![base_a.address()], + vec![base_a.address().into_legacy()], 1, true, ) @@ -197,7 +222,7 @@ async fn wrong_solution_submission_address(web3: Web3) { "solver2".into(), solver.clone(), onchain.contracts().weth.address(), - vec![base_b.address()], + vec![base_b.address().into_legacy()], 1, true, ) @@ -224,7 +249,7 @@ async fn wrong_solution_submission_address(web3: Web3) { // Place Orders let order_a = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(5), @@ -242,7 +267,7 @@ async fn wrong_solution_submission_address(web3: Web3) { onchain.mint_block().await; let order_b = OrderCreation { - sell_token: token_b.address(), + sell_token: token_b.address().into_legacy(), sell_amount: to_wei(10), buy_token: onchain.contracts().weth.address(), buy_amount: to_wei(5), @@ -304,10 +329,16 @@ async fn store_filtered_solutions(web3: Web3) { // set up trader for their order token_a.mint(trader.address(), to_wei(2)).await; - tx!( - trader.account(), - token_a.approve(onchain.contracts().allowance, to_wei(2)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(2).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); let services = Services::new(&onchain).await; @@ -315,7 +346,11 @@ async fn store_filtered_solutions(web3: Web3) { let bad_solver = Mock::default(); // Start system - let base_tokens = vec![token_a.address(), token_b.address(), token_c.address()]; + let base_tokens = vec![ + token_a.address().into_legacy(), + token_b.address().into_legacy(), + token_c.address().into_legacy(), + ]; colocation::start_driver( onchain.contracts(), vec![ @@ -372,9 +407,9 @@ async fn store_filtered_solutions(web3: Web3) { // Place order let order_ab = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(1), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -387,9 +422,9 @@ async fn store_filtered_solutions(web3: Web3) { ); let order_ac = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(1), - buy_token: token_c.address(), + buy_token: token_c.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -417,8 +452,8 @@ async fn store_filtered_solutions(web3: Web3) { good_solver.configure_solution(Some(Solution { id: 0, prices: HashMap::from([ - (token_a.address(), to_wei(3)), - (token_b.address(), to_wei(1)), + (token_a.address().into_legacy(), to_wei(3)), + (token_b.address().into_legacy(), to_wei(1)), ]), trades: vec![solvers_dto::solution::Trade::Fulfillment( solvers_dto::solution::Fulfillment { @@ -440,9 +475,9 @@ async fn store_filtered_solutions(web3: Web3) { bad_solver.configure_solution(Some(Solution { id: 0, prices: HashMap::from([ - (token_a.address(), to_wei(2)), - (token_b.address(), to_wei(1)), - (token_c.address(), to_wei(1)), + (token_a.address().into_legacy(), to_wei(2)), + (token_b.address().into_legacy(), to_wei(1)), + (token_c.address().into_legacy(), to_wei(1)), ]), trades: vec![ solvers_dto::solution::Trade::Fulfillment(solvers_dto::solution::Fulfillment { diff --git a/crates/e2e/tests/e2e/solver_participation_guard.rs b/crates/e2e/tests/e2e/solver_participation_guard.rs index b620f31001..fbff92c972 100644 --- a/crates/e2e/tests/e2e/solver_participation_guard.rs +++ b/crates/e2e/tests/e2e/solver_participation_guard.rs @@ -14,7 +14,13 @@ use { }, tx, }, - ethrpc::Web3, + ethrpc::{ + Web3, + alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, + }, model::{ order::{OrderClass, OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -251,30 +257,36 @@ async fn setup( token_b.mint(solver.address(), to_wei(1000)).await; tx!( solver.account(), - onchain - .contracts() - .uniswap_v2_factory - .create_pair(token_a.address(), token_b.address()) - ); - tx!( - solver.account(), - token_a.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + onchain.contracts().uniswap_v2_factory.create_pair( + token_a.address().into_legacy(), + token_b.address().into_legacy() ) ); - tx!( - solver.account(), - token_b.approve( - onchain.contracts().uniswap_v2_router.address(), - to_wei(1000) + + token_a + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), ) - ); + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + + token_b + .approve( + onchain.contracts().uniswap_v2_router.address().into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( solver.account(), onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address(), - token_b.address(), + token_a.address().into_legacy(), + token_b.address().into_legacy(), to_wei(1000), to_wei(1000), 0_u64.into(), @@ -285,10 +297,16 @@ async fn setup( ); // Approve GPv2 for trading - tx!( - trader_a.account(), - token_a.approve(onchain.contracts().allowance, to_wei(1000)) - ); + + token_a + .approve( + onchain.contracts().allowance.into_alloy(), + to_wei(1000).into_alloy(), + ) + .from(trader_a.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); (trader_a, token_a, token_b) } @@ -325,9 +343,9 @@ async fn execute_order( services: &Services<'_>, ) -> anyhow::Result<()> { let order = OrderCreation { - sell_token: token_a.address(), + sell_token: token_a.address().into_legacy(), sell_amount: to_wei(10), - buy_token: token_b.address(), + buy_token: token_b.address().into_legacy(), buy_amount: to_wei(5), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, @@ -338,7 +356,11 @@ async fn execute_order( &onchain.contracts().domain_separator, SecretKeyRef::from(&SecretKey::from_slice(trader_a.private_key()).unwrap()), ); - let balance_before = token_b.balance_of(trader_a.address()).call().await.unwrap(); + let balance_before = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); let order_id = services.create_order(&order).await.unwrap(); onchain.mint_block().await; let limit_order = services.get_order(&order_id).await.unwrap(); @@ -348,8 +370,13 @@ async fn execute_order( // Drive solution tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { - let balance_after = token_b.balance_of(trader_a.address()).call().await.unwrap(); - let balance_changes = balance_after.checked_sub(balance_before).unwrap() >= to_wei(5); + let balance_after = token_b + .balanceOf(trader_a.address().into_alloy()) + .call() + .await + .unwrap(); + let balance_changes = + balance_after.checked_sub(balance_before).unwrap() >= to_wei(5).into_alloy(); let auction_ids_after = fetch_last_settled_auction_ids(services.db()).await.len() > auction_ids_before; balance_changes && auction_ids_after diff --git a/crates/e2e/tests/e2e/submission.rs b/crates/e2e/tests/e2e/submission.rs index 9507a645e5..17f825c8a0 100644 --- a/crates/e2e/tests/e2e/submission.rs +++ b/crates/e2e/tests/e2e/submission.rs @@ -1,6 +1,8 @@ use { + ::alloy::primitives::U256, e2e::{nodes::local_node::TestNodeApi, setup::*, tx, tx_value}, ethcontract::{BlockId, H160, H256}, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, futures::{Stream, StreamExt}, model::{ order::{OrderCreation, OrderKind}, @@ -52,12 +54,16 @@ async fn test_cancel_on_expiry(web3: Web3) { .expect("Must be able to disable automine"); tracing::info!("Placing order"); - let balance = token.balance_of(trader.address()).call().await.unwrap(); - assert_eq!(balance, 0.into()); + let balance = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + assert_eq!(balance, U256::ZERO); let order = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: to_wei(2), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Buy, diff --git a/crates/e2e/tests/e2e/tracking_insufficient_funds.rs b/crates/e2e/tests/e2e/tracking_insufficient_funds.rs index 3996a2498b..0441b914db 100644 --- a/crates/e2e/tests/e2e/tracking_insufficient_funds.rs +++ b/crates/e2e/tests/e2e/tracking_insufficient_funds.rs @@ -1,6 +1,7 @@ use { database::order_events::{OrderEvent, OrderEventLabel}, e2e::{setup::*, tx, tx_value}, + ethrpc::alloy::conversions::IntoLegacy, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -59,7 +60,7 @@ async fn test(web3: Web3) { let order_a = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: to_wei(2), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Buy, @@ -73,7 +74,7 @@ async fn test(web3: Web3) { let order_b = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: to_wei(2), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Buy, diff --git a/crates/e2e/tests/e2e/uncovered_order.rs b/crates/e2e/tests/e2e/uncovered_order.rs index 2ae1102c61..05c6113a17 100644 --- a/crates/e2e/tests/e2e/uncovered_order.rs +++ b/crates/e2e/tests/e2e/uncovered_order.rs @@ -1,5 +1,6 @@ use { e2e::{setup::*, tx, tx_value}, + ethrpc::alloy::conversions::IntoLegacy, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -43,7 +44,7 @@ async fn test(web3: Web3) { sell_token: weth.address(), sell_amount: to_wei(2), fee_amount: 0.into(), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Sell, diff --git a/crates/e2e/tests/e2e/univ2.rs b/crates/e2e/tests/e2e/univ2.rs index 947b2fb84c..eeabf090fb 100644 --- a/crates/e2e/tests/e2e/univ2.rs +++ b/crates/e2e/tests/e2e/univ2.rs @@ -1,7 +1,8 @@ use { + ::alloy::primitives::U256, database::order_events::{OrderEvent, OrderEventLabel}, e2e::{setup::*, tx, tx_value}, - ethcontract::U256, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderKind}, signature::EcdsaSigningScheme, @@ -45,12 +46,16 @@ async fn test(web3: Web3) { services.start_protocol(solver.clone()).await; tracing::info!("Placing order"); - let balance = token.balance_of(trader.address()).call().await.unwrap(); - assert_eq!(balance, 0.into()); + let balance = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + assert_eq!(balance, U256::ZERO); let order = OrderCreation { sell_token: onchain.contracts().weth.address(), sell_amount: to_wei(2), - buy_token: token.address(), + buy_token: token.address().into_legacy(), buy_amount: to_wei(1), valid_to: model::time::now_in_epoch_seconds() + 300, kind: OrderKind::Buy, @@ -72,7 +77,11 @@ async fn test(web3: Web3) { Default::default(), Default::default(), [ - vec![(trader.address(), U256::from(0), Default::default())], + vec![( + trader.address(), + U256::ZERO.into_legacy(), + Default::default() + )], Default::default(), Default::default() ], @@ -80,12 +89,22 @@ async fn test(web3: Web3) { ); tracing::info!("Waiting for trade."); - let trade_happened = - || async { token.balance_of(trader.address()).call().await.unwrap() != 0.into() }; + let trade_happened = || async { + token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap() + != U256::ZERO + }; wait_for_condition(TIMEOUT, trade_happened).await.unwrap(); - let balance = token.balance_of(trader.address()).call().await.unwrap(); - assert_eq!(balance, to_wei(1)); + let balance = token + .balanceOf(trader.address().into_alloy()) + .call() + .await + .unwrap(); + assert_eq!(balance, to_wei(1).into_alloy()); let all_events_registered = || async { let events = crate::database::events_of_order(services.db(), &uid).await; diff --git a/crates/e2e/tests/e2e/vault_balances.rs b/crates/e2e/tests/e2e/vault_balances.rs index 06a7e6d09a..4ad3ab96d6 100644 --- a/crates/e2e/tests/e2e/vault_balances.rs +++ b/crates/e2e/tests/e2e/vault_balances.rs @@ -1,5 +1,9 @@ use { e2e::{setup::*, tx}, + ethrpc::alloy::{ + CallBuilderExt, + conversions::{IntoAlloy, IntoLegacy}, + }, model::{ order::{OrderCreation, OrderKind, SellTokenSource}, signature::EcdsaSigningScheme, @@ -27,10 +31,16 @@ async fn vault_balances(web3: Web3) { token.mint(trader.address(), to_wei(10)).await; // Approve GPv2 for trading - tx!( - trader.account(), - token.approve(onchain.contracts().balancer_vault.address(), to_wei(10)) - ); + + token + .approve( + onchain.contracts().balancer_vault.address().into_alloy(), + to_wei(10).into_alloy(), + ) + .from(trader.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( trader.account(), onchain.contracts().balancer_vault.set_relayer_approval( @@ -46,7 +56,7 @@ async fn vault_balances(web3: Web3) { // Place Orders let order = OrderCreation { kind: OrderKind::Sell, - sell_token: token.address(), + sell_token: token.address().into_legacy(), sell_amount: to_wei(10), sell_token_balance: SellTokenSource::External, buy_token: onchain.contracts().weth.address(), @@ -73,7 +83,7 @@ async fn vault_balances(web3: Web3) { tracing::info!("Waiting for trade."); wait_for_condition(TIMEOUT, || async { let token_balance = token - .balance_of(trader.address()) + .balanceOf(trader.address().into_alloy()) .call() .await .expect("Couldn't fetch token balance"); From 6b6f115e1df8a0d9284fbd593989e71e4c093511 Mon Sep 17 00:00:00 2001 From: ilya Date: Mon, 15 Sep 2025 18:13:50 +0300 Subject: [PATCH 026/112] Generalize the event handling module (#3648) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This is the first prerequisite for the upcoming PRs that generalize the event handling module, making it possible to integrate with Alloy smart contract instances. Instead of relying on `ethcontract-rs`–specific types in the interface, the trait is now generic and can work with primitives from either `ethcontract-rs` or `alloy`. All the interfaces in the `event_handling.rs` file don't depend on the `ethercontract-rs` crate, meaning we can use any underlying implementation in the future. Some common logic was extracted to the `EthcontractEventRetrieving` trait. This is required to remove `ethcontract-rs` crate dependency from the `EventRetrieving` trait and deduplicate the code. The change looks pretty straightforward, but you can't imagine how many failed attempts were made to reach its current state 🤕 --- .../src/boundary/events/settlement.rs | 2 +- .../ethflow_events/event_retriever.rs | 8 +- .../database/ethflow_events/event_storing.rs | 2 +- .../onchain_order_events/event_retriever.rs | 8 +- .../src/database/onchain_order_events/mod.rs | 2 +- crates/autopilot/src/event_updater.rs | 13 +- crates/cow-amm/src/cache.rs | 2 +- crates/cow-amm/src/factory.rs | 8 +- crates/shared/src/event_handling.rs | 139 ++++++++++++------ .../balancer_v2/pool_fetching/pool_storage.rs | 2 +- .../balancer_v2/pool_fetching/registry.rs | 12 +- .../src/sources/uniswap_v3/event_fetching.rs | 6 +- .../src/sources/uniswap_v3/pool_fetching.rs | 8 +- 13 files changed, 133 insertions(+), 79 deletions(-) diff --git a/crates/autopilot/src/boundary/events/settlement.rs b/crates/autopilot/src/boundary/events/settlement.rs index 655b234696..0a4ea37148 100644 --- a/crates/autopilot/src/boundary/events/settlement.rs +++ b/crates/autopilot/src/boundary/events/settlement.rs @@ -29,7 +29,7 @@ impl Indexer { pub(crate) const INDEX_NAME: &str = "settlements"; #[async_trait::async_trait] -impl EventStoring for Indexer { +impl EventStoring> for Indexer { async fn last_event_block(&self) -> Result { super::read_last_block_from_db(&self.db.pool, INDEX_NAME) .await diff --git a/crates/autopilot/src/database/ethflow_events/event_retriever.rs b/crates/autopilot/src/database/ethflow_events/event_retriever.rs index 88f7cb5b43..900706511f 100644 --- a/crates/autopilot/src/database/ethflow_events/event_retriever.rs +++ b/crates/autopilot/src/database/ethflow_events/event_retriever.rs @@ -2,9 +2,9 @@ //! contract. use { - ethcontract::{H160, H256, contract::AllEventsBuilder, transport::DynTransport}, + ethcontract::{H160, H256, contract::AllEventsBuilder, dyns::DynAllEventsBuilder}, hex_literal::hex, - shared::{ethrpc::Web3, event_handling::EventRetrieving}, + shared::{ethrpc::Web3, event_handling::EthcontractEventRetrieving}, }; const ORDER_REFUND_TOPIC: H256 = H256(hex!( @@ -26,10 +26,10 @@ impl EthFlowRefundRetriever { } } -impl EventRetrieving for EthFlowRefundRetriever { +impl EthcontractEventRetrieving for EthFlowRefundRetriever { type Event = contracts::cowswap_eth_flow::Event; - fn get_events(&self) -> AllEventsBuilder { + fn get_events(&self) -> DynAllEventsBuilder { let mut events = AllEventsBuilder::new(self.web3.legacy.clone(), H160::default(), None); // We want to observe multiple addresses for events. events.filter = events.filter.address(self.addresses.clone()); diff --git a/crates/autopilot/src/database/ethflow_events/event_storing.rs b/crates/autopilot/src/database/ethflow_events/event_storing.rs index e752300cd1..0e867ca5db 100644 --- a/crates/autopilot/src/database/ethflow_events/event_storing.rs +++ b/crates/autopilot/src/database/ethflow_events/event_storing.rs @@ -39,7 +39,7 @@ type EthFlowEvent = contracts::cowswap_eth_flow::Event; pub(crate) const INDEX_NAME: &str = "ethflow_refunds"; #[async_trait::async_trait] -impl EventStoring for Postgres { +impl EventStoring> for Postgres { async fn last_event_block(&self) -> Result { crate::boundary::events::read_last_block_from_db(&self.pool, INDEX_NAME).await } diff --git a/crates/autopilot/src/database/onchain_order_events/event_retriever.rs b/crates/autopilot/src/database/onchain_order_events/event_retriever.rs index cec9145e28..4171403e0c 100644 --- a/crates/autopilot/src/database/onchain_order_events/event_retriever.rs +++ b/crates/autopilot/src/database/onchain_order_events/event_retriever.rs @@ -1,8 +1,8 @@ use { contracts::cowswap_onchain_orders, - ethcontract::{H160, H256, contract::AllEventsBuilder, transport::DynTransport}, + ethcontract::{H160, H256, contract::AllEventsBuilder, dyns::DynAllEventsBuilder}, hex_literal::hex, - shared::{ethrpc::Web3, event_handling::EventRetrieving}, + shared::{ethrpc::Web3, event_handling::EthcontractEventRetrieving}, }; const ORDER_PLACEMENT_TOPIC: H256 = H256(hex!( @@ -36,10 +36,10 @@ impl CoWSwapOnchainOrdersContract { } } -impl EventRetrieving for CoWSwapOnchainOrdersContract { +impl EthcontractEventRetrieving for CoWSwapOnchainOrdersContract { type Event = cowswap_onchain_orders::Event; - fn get_events(&self) -> AllEventsBuilder { + fn get_events(&self) -> DynAllEventsBuilder { let mut events = AllEventsBuilder::new(self.web3.legacy.clone(), H160::default(), None); // We want to observe multiple addresses for events. events.filter = events.filter.address(self.addresses.clone()); diff --git a/crates/autopilot/src/database/onchain_order_events/mod.rs b/crates/autopilot/src/database/onchain_order_events/mod.rs index 1eb9c04bd8..787643e243 100644 --- a/crates/autopilot/src/database/onchain_order_events/mod.rs +++ b/crates/autopilot/src/database/onchain_order_events/mod.rs @@ -155,7 +155,7 @@ where pub(crate) const INDEX_NAME: &str = "onchain_orders"; #[async_trait::async_trait] -impl EventStoring +impl EventStoring> for OnchainOrderParser { async fn last_event_block(&self) -> Result { diff --git a/crates/autopilot/src/event_updater.rs b/crates/autopilot/src/event_updater.rs index 464b5466a1..b29e12b5c9 100644 --- a/crates/autopilot/src/event_updater.rs +++ b/crates/autopilot/src/event_updater.rs @@ -9,14 +9,14 @@ use { tokio::sync::Mutex, }; -pub struct EventUpdater< - Database: EventStoring<::Event>, - W: EventRetrieving + Send + Sync, ->(Mutex>); +pub struct EventUpdater(Mutex>) +where + Database: EventStoring, + W: EventRetrieving + Send + Sync; impl EventUpdater where - Database: EventStoring<::Event>, + Database: EventStoring, W: EventRetrieving + Send + Sync, { /// Creates a new event updater. @@ -66,8 +66,9 @@ where #[async_trait::async_trait] impl Maintaining for EventUpdater where - Database: EventStoring<::Event>, + Database: EventStoring + Send + Sync, W: EventRetrieving + Send + Sync, + W::Event: Send + Sync, { async fn run_maintenance(&self) -> Result<()> { self.0.run_maintenance().await diff --git a/crates/cow-amm/src/cache.rs b/crates/cow-amm/src/cache.rs index 5460967b77..44b4fe6b59 100644 --- a/crates/cow-amm/src/cache.rs +++ b/crates/cow-amm/src/cache.rs @@ -50,7 +50,7 @@ struct Inner { } #[async_trait::async_trait] -impl EventStoring for Storage { +impl EventStoring> for Storage { async fn replace_events( &mut self, events: Vec>, diff --git a/crates/cow-amm/src/factory.rs b/crates/cow-amm/src/factory.rs index b1022ecf5e..81f5e83a81 100644 --- a/crates/cow-amm/src/factory.rs +++ b/crates/cow-amm/src/factory.rs @@ -1,8 +1,8 @@ use { contracts::cow_amm_legacy_helper::Event as CowAmmEvent, - ethcontract::{Address, H256, contract::AllEventsBuilder, dyns::DynTransport}, + ethcontract::{Address, H256, contract::AllEventsBuilder, dyns::DynAllEventsBuilder}, ethrpc::Web3, - shared::event_handling::EventRetrieving, + shared::event_handling::EthcontractEventRetrieving, }; const AMM_DEPLOYED_TOPIC: H256 = H256(hex_literal::hex!( @@ -14,10 +14,10 @@ pub(crate) struct Factory { pub(crate) address: Address, } -impl EventRetrieving for Factory { +impl EthcontractEventRetrieving for Factory { type Event = CowAmmEvent; - fn get_events(&self) -> AllEventsBuilder { + fn get_events(&self) -> DynAllEventsBuilder { let mut events = AllEventsBuilder::new(self.web3.legacy.clone(), self.address, None); events.filter = events.filter.topic0(Some(AMM_DEPLOYED_TOPIC).into()); events diff --git a/crates/shared/src/event_handling.rs b/crates/shared/src/event_handling.rs index 0f5ad64a76..ceb2886aa2 100644 --- a/crates/shared/src/event_handling.rs +++ b/crates/shared/src/event_handling.rs @@ -1,16 +1,10 @@ use { crate::maintenance::Maintaining, - anyhow::{Context, Error, Result}, - ethcontract::{ - Event as EthcontractEvent, - EventMetadata, - contract::{AllEventsBuilder, ParseLog}, - dyns::DynTransport, - errors::ExecutionError, - }, + anyhow::{Context, Result}, + ethcontract::{EventMetadata, dyns::DynAllEventsBuilder}, ethrpc::block_stream::{BlockNumberHash, BlockRetrieving, RangeInclusive}, futures::{Stream, StreamExt, TryStreamExt, future}, - std::sync::Arc, + std::{pin::Pin, sync::Arc}, tokio::sync::Mutex, tracing::Instrument, }; @@ -41,15 +35,16 @@ const MAX_PARALLEL_RPC_CALLS: usize = 128; /// `last_handled_blocks` to make sure the data is consistent. 5. If history /// update is successful, proceed with latest update, and if successful /// update `last_handled_blocks`. -pub struct EventHandler +pub struct EventHandler where - C: EventRetrieving, - S: EventStoring, + C: EventRetrieving, + S: EventStoring, { block_retriever: Arc, contract: C, store: S, last_handled_blocks: Vec, + _phantom: std::marker::PhantomData, } /// `EventStoring` is used by `EventHandler` for the purpose of giving the user @@ -59,23 +54,19 @@ where /// Databases: might transform, filter and classify which events are inserted /// HashSet: For less persistent (in memory) storing, insert events into a set. #[async_trait::async_trait] -pub trait EventStoring: Send + Sync { +pub trait EventStoring: Send + Sync { /// Returns ok, on successful execution, otherwise an appropriate error /// /// # Arguments /// * `events` the contract events to be replaced by the implementer /// * `range` indicates a particular range of blocks on which to operate. - async fn replace_events( - &mut self, - events: Vec>, - range: RangeInclusive, - ) -> Result<()>; + async fn replace_events(&mut self, events: Vec, range: RangeInclusive) -> Result<()>; /// Returns ok, on successful execution, otherwise an appropriate error /// /// # Arguments /// * `events` the contract events to be appended by the implementer - async fn append_events(&mut self, events: Vec>) -> Result<()>; + async fn append_events(&mut self, events: Vec) -> Result<()>; /// Fetches the last processed block to know where to resume indexing after /// a restart. @@ -86,9 +77,70 @@ pub trait EventStoring: Send + Sync { async fn persist_last_indexed_block(&mut self, last_block: u64) -> Result<()>; } +pub type EventStream = Pin> + Send>>; + +/// Defines an interface for retrieving events from a `ethcontract` crate +/// contract. Should be removed once fully migrated to alloy. +pub trait EthcontractEventRetrieving { + type Event: ethcontract::contract::ParseLog + 'static; + + fn get_events(&self) -> DynAllEventsBuilder; +} + +/// Common `ethcontract` crate-related event retrieval patterns are extracted to +/// this trait to avoid code duplication and any dependencies on the +/// `ethcontract` crate in the main event handling traits. This will be removed +/// once fully migrated to alloy. +#[async_trait::async_trait] +impl EventRetrieving for T +where + T: EthcontractEventRetrieving + Send + Sync, +{ + type Event = ethcontract::Event; + + async fn get_events_by_block_hash( + &self, + block_hash: ethcontract::H256, + ) -> Result> { + Ok(self.get_events().block_hash(block_hash).query().await?) + } + + async fn get_events_by_block_range( + &self, + block_range: &RangeInclusive, + ) -> Result> { + let stream = self + .get_events() + .from_block((*block_range.start()).into()) + .to_block((*block_range.end()).into()) + .block_page_size(500) + .query_paginated() + .await? + .map_err(anyhow::Error::from); + + Ok(Box::pin(stream)) + } + + fn address(&self) -> Vec { + self.get_events().filter.address + } +} + +#[async_trait::async_trait] pub trait EventRetrieving { - type Event: ParseLog; - fn get_events(&self) -> AllEventsBuilder; + type Event; + + async fn get_events_by_block_hash( + &self, + block_hash: ethcontract::H256, + ) -> Result>; + + async fn get_events_by_block_range( + &self, + block_range: &RangeInclusive, + ) -> Result>; + + fn address(&self) -> Vec; } #[derive(Debug)] @@ -101,10 +153,10 @@ struct EventRange { is_reorg: bool, } -impl EventHandler +impl EventHandler where - C: EventRetrieving, - S: EventStoring, + C: EventRetrieving, + S: EventStoring, { pub fn new( block_retriever: Arc, @@ -122,6 +174,7 @@ where None => vec![], } }, + _phantom: std::marker::PhantomData, } } @@ -407,13 +460,13 @@ where async fn past_events_by_block_hashes( &self, blocks: &[BlockNumberHash], - ) -> (Vec, Vec>) { + ) -> (Vec, Vec) { let (mut blocks_filtered, mut events) = (vec![], vec![]); for chunk in blocks.chunks(MAX_PARALLEL_RPC_CALLS) { for (i, result) in future::join_all( chunk .iter() - .map(|block| self.contract.get_events().block_hash(block.1).query()), + .map(|block| self.contract.get_events_by_block_hash(block.1)), ) .await .into_iter() @@ -442,17 +495,8 @@ where async fn past_events_by_block_number_range( &self, block_range: &RangeInclusive, - ) -> Result>> + use, ExecutionError> - { - Ok(self - .contract - .get_events() - .from_block((*block_range.start()).into()) - .to_block((*block_range.end()).into()) - .block_page_size(500) - .query_paginated() - .await? - .map_err(Error::from)) + ) -> Result> + use + Send> { + self.contract.get_events_by_block_range(block_range).await } fn update_last_handled_blocks(&mut self, blocks: &[BlockNumberHash]) { @@ -531,15 +575,15 @@ fn split_range(range: RangeInclusive) -> (Option>, Rang } #[async_trait::async_trait] -impl Maintaining for Mutex> +impl Maintaining for Mutex> where - C: EventRetrieving + Send + Sync, - C::Event: Send, - S: EventStoring + Send + Sync, + E: Send + Sync, + C: EventRetrieving + Send + Sync, + S: EventStoring + Send + Sync, { async fn run_maintenance(&self) -> Result<()> { let mut inner = self.lock().await; - let address = inner.contract.get_events().filter.address; + let address = inner.contract.address(); inner .update_events() .instrument(tracing::info_span!("address", ?address)) @@ -587,13 +631,10 @@ macro_rules! impl_event_retrieving { } } - impl $crate::event_handling::EventRetrieving for $name { + impl $crate::event_handling::EthcontractEventRetrieving for $name { type Event = $($contract_module)*::Event; - fn get_events(&self) -> ::ethcontract::contract::AllEventsBuilder< - ::ethcontract::dyns::DynTransport, - Self::Event, - > { + fn get_events(&self) -> ::ethcontract::dyns::DynAllEventsBuilder { self.0.all_events() } } @@ -621,7 +662,7 @@ mod tests { use { super::*, contracts::{GPv2Settlement, gpv2_settlement}, - ethcontract::{BlockNumber, H256}, + ethcontract::{BlockNumber, Event as EthcontractEvent, H256}, ethrpc::{Web3, block_stream::block_number_to_block_number_hash}, std::str::FromStr, }; @@ -636,7 +677,7 @@ mod tests { } #[async_trait::async_trait] - impl EventStoring for EventStorage + impl EventStoring> for EventStorage where T: Send + Sync, { diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs index ae53bbfdf9..2de5adb6cb 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs @@ -183,7 +183,7 @@ where } #[async_trait::async_trait] -impl EventStoring for PoolStorage +impl EventStoring> for PoolStorage where Factory: FactoryIndexing, { diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs index 09d528bbb0..62ed87f10f 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs @@ -4,7 +4,7 @@ use { super::{internal::InternalPoolFetching, pool_storage::PoolStorage}, crate::{ - event_handling::{EventHandler, EventRetrieving}, + event_handling::{EthcontractEventRetrieving, EventHandler}, maintenance::Maintaining, recent_block_cache::Block, sources::balancer_v2::pools::{ @@ -38,7 +38,7 @@ const POOL_CREATED_TOPIC: H256 = H256(hex!( "83a48fbcfc991335314e74d0496aab6a1987e992ddc85dddbcc4d6dd6ef2e9fc" )); -impl EventRetrieving for BasePoolFactoryContract { +impl EthcontractEventRetrieving for BasePoolFactoryContract { type Event = balancer_v2_base_pool_factory::Event; fn get_events(&self) -> DynAllEventsBuilder { @@ -49,7 +49,13 @@ impl EventRetrieving for BasePoolFactoryContract { } /// Type alias for the internal event updater type. -type PoolUpdater = Mutex>>; +type PoolUpdater = Mutex< + EventHandler< + BasePoolFactoryContract, + PoolStorage, + ethcontract::Event, + >, +>; /// The Pool Registry maintains an event handler for each of the Balancer Pool /// Factory contracts and maintains a `PoolStorage` for each. diff --git a/crates/shared/src/sources/uniswap_v3/event_fetching.rs b/crates/shared/src/sources/uniswap_v3/event_fetching.rs index 88c4b2158c..682064f419 100644 --- a/crates/shared/src/sources/uniswap_v3/event_fetching.rs +++ b/crates/shared/src/sources/uniswap_v3/event_fetching.rs @@ -1,5 +1,5 @@ use { - crate::event_handling::{EventRetrieving, EventStoring}, + crate::event_handling::{EthcontractEventRetrieving, EventStoring}, anyhow::{Context, Result}, contracts::{ UniswapV3Pool, @@ -76,7 +76,7 @@ impl ParseLog for UniswapV3Event { pub struct UniswapV3PoolEventFetcher(pub Web3); -impl EventRetrieving for UniswapV3PoolEventFetcher { +impl EthcontractEventRetrieving for UniswapV3PoolEventFetcher { type Event = UniswapV3Event; fn get_events(&self) -> DynAllEventsBuilder { @@ -118,7 +118,7 @@ impl RecentEventsCache { } #[async_trait::async_trait] -impl EventStoring for RecentEventsCache { +impl EventStoring> for RecentEventsCache { async fn replace_events( &mut self, events: Vec>, diff --git a/crates/shared/src/sources/uniswap_v3/pool_fetching.rs b/crates/shared/src/sources/uniswap_v3/pool_fetching.rs index c0f110e557..8fa943d8d5 100644 --- a/crates/shared/src/sources/uniswap_v3/pool_fetching.rs +++ b/crates/shared/src/sources/uniswap_v3/pool_fetching.rs @@ -266,7 +266,13 @@ pub struct UniswapV3PoolFetcher { checkpoint: PoolsCheckpointHandler, /// Recent events used on top of pools_checkpoint to get the `latest_block` /// pools state. - events: tokio::sync::Mutex>, + events: tokio::sync::Mutex< + EventHandler< + UniswapV3PoolEventFetcher, + RecentEventsCache, + ethcontract::Event, + >, + >, } impl UniswapV3PoolFetcher { From 53c80eeee23e79962c32b2e0f6ec0da281c91b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 16 Sep 2025 09:33:53 +0100 Subject: [PATCH 027/112] Remove alloy version of tx! macro (#3658) --- crates/contracts/Cargo.toml | 1 - crates/contracts/src/alloy.rs | 38 ------------------- crates/driver/Cargo.toml | 2 +- crates/e2e/Cargo.toml | 2 +- .../e2e/src/setup/onchain_components/safe.rs | 31 +++++++-------- crates/e2e/tests/e2e/hooks.rs | 2 +- 6 files changed, 19 insertions(+), 57 deletions(-) diff --git a/crates/contracts/Cargo.toml b/crates/contracts/Cargo.toml index eba5e4f952..5b981ca22e 100644 --- a/crates/contracts/Cargo.toml +++ b/crates/contracts/Cargo.toml @@ -19,7 +19,6 @@ bin = [ "tracing", "tracing-subscriber", ] -test-util = [] [dependencies] alloy = { workspace = true, features = ["sol-types", "json", "contract", "json-abi"] } diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index da49911c5c..d9809032c3 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -1,6 +1,3 @@ -#[cfg(feature = "test-util")] -pub use crate::{tx, tx_value}; - pub mod networks { pub const MAINNET: u64 = 1; pub const GNOSIS: u64 = 100; @@ -238,41 +235,6 @@ macro_rules! bindings { }; } -#[cfg(feature = "test-util")] -#[macro_export] -macro_rules! tx_value { - ($call:expr, $value:expr) => {{ - const NAME: &str = stringify!($call); - $call - .value($value) - .send() - .await - .expect(&format!("failed to send: {}", NAME)) - .watch() - .await - .expect(&format!("failed to get confirmations for: {}", NAME)) - }}; - ($call:expr, $value:expr, $acc:expr) => {{ - const NAME: &str = stringify!($call); - $call - .from($acc) - .value($value) - .send() - .await - .expect(&format!("failed to send: {}", NAME)) - .watch() - .await - .expect(&format!("failed to get confirmations for: {}", NAME)) - }}; -} - -#[cfg(feature = "test-util")] -#[macro_export] -macro_rules! tx { - ($call:expr) => {{ $crate::tx_value!($call, ::alloy::primitives::U256::ZERO) }}; - ($call:expr, $acc:expr) => {{ $crate::tx_value!($call, ::alloy::primitives::U256::ZERO, $acc) }}; -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/driver/Cargo.toml b/crates/driver/Cargo.toml index 1d4485d6c9..f1bd1dab9c 100644 --- a/crates/driver/Cargo.toml +++ b/crates/driver/Cargo.toml @@ -85,7 +85,7 @@ mockall = { workspace = true } tokio = { workspace = true, features = ["test-util", "process"] } tempfile = { workspace = true } ethrpc = {workspace = true, features = ["test-util"]} -contracts = { workspace = true , features = ["test-util"]} +contracts = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/crates/e2e/Cargo.toml b/crates/e2e/Cargo.toml index 507d6b8f1c..6913a17762 100644 --- a/crates/e2e/Cargo.toml +++ b/crates/e2e/Cargo.toml @@ -17,7 +17,7 @@ axum = { workspace = true } bigdecimal = { workspace = true } chrono = { workspace = true } clap = { workspace = true } -contracts = { workspace = true, features = ["test-util"] } +contracts = { workspace = true } database = { workspace = true } driver = { workspace = true } ethcontract = { workspace = true } diff --git a/crates/e2e/src/setup/onchain_components/safe.rs b/crates/e2e/src/setup/onchain_components/safe.rs index ac9b360522..fd92cc71a9 100644 --- a/crates/e2e/src/setup/onchain_components/safe.rs +++ b/crates/e2e/src/setup/onchain_components/safe.rs @@ -68,21 +68,22 @@ impl Infrastructure { .unwrap(); let safe = GnosisSafe::Instance::new(safe_proxy, self.provider.clone()); - contracts::alloy::tx!( - safe.setup( - owners - .into_iter() - .map(|owner| owner.address().into_alloy()) - .collect(), - U256::from(threshold), - Address::default(), // delegate call - Bytes::default(), // delegate call bytes - *self.fallback.address(), - Address::default(), // relayer payment token - U256::ZERO, // relayer payment amount - Address::default(), // relayer address - ) - ); + safe.setup( + owners + .into_iter() + .map(|owner| owner.address().into_alloy()) + .collect(), + U256::from(threshold), + Address::default(), // delegate call + Bytes::default(), // delegate call bytes + *self.fallback.address(), + Address::default(), // relayer payment token + U256::ZERO, // relayer payment amount + Address::default(), // relayer address + ) + .send_and_watch() + .await + .unwrap(); safe } diff --git a/crates/e2e/tests/e2e/hooks.rs b/crates/e2e/tests/e2e/hooks.rs index c878ea8878..3fd4887e75 100644 --- a/crates/e2e/tests/e2e/hooks.rs +++ b/crates/e2e/tests/e2e/hooks.rs @@ -552,7 +552,7 @@ async fn quote_verification(web3: Web3) { .clone(), ); let safe_address = safe_creation_builder.clone().call().await.unwrap(); - contracts::alloy::tx!(safe_creation_builder); + safe_creation_builder.send_and_watch().await.unwrap(); let safe = Safe::deployed( chain_id, From 4067d4d9195df5703781873ff711c9c9bd19c371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 16 Sep 2025 09:44:35 +0100 Subject: [PATCH 028/112] Replace to_wei(amount).into_alloy() calls to eth(amount) (#3655) --- .../e2e/src/setup/onchain_components/mod.rs | 7 +++ crates/e2e/tests/e2e/app_data.rs | 12 +--- crates/e2e/tests/e2e/app_data_signer.rs | 17 ++--- crates/e2e/tests/e2e/buffers.rs | 7 +-- crates/e2e/tests/e2e/cow_amm.rs | 27 ++++---- crates/e2e/tests/e2e/eth_integration.rs | 12 +--- crates/e2e/tests/e2e/eth_safe.rs | 11 +--- crates/e2e/tests/e2e/ethflow.rs | 5 +- crates/e2e/tests/e2e/hooks.rs | 13 ++-- crates/e2e/tests/e2e/jit_orders.rs | 6 +- crates/e2e/tests/e2e/limit_orders.rs | 62 ++++++------------- crates/e2e/tests/e2e/order_cancellation.rs | 7 +-- .../tests/e2e/partially_fillable_balance.rs | 14 ++--- .../e2e/tests/e2e/partially_fillable_pool.rs | 14 ++--- crates/e2e/tests/e2e/protocol_fee.rs | 19 +++--- crates/e2e/tests/e2e/quote_verification.rs | 7 +-- crates/e2e/tests/e2e/quoting.rs | 7 +-- crates/e2e/tests/e2e/replace_order.rs | 44 ++++++------- crates/e2e/tests/e2e/smart_contract_orders.rs | 7 +-- crates/e2e/tests/e2e/solver_competition.rs | 22 ++----- .../tests/e2e/solver_participation_guard.rs | 13 ++-- crates/e2e/tests/e2e/univ2.rs | 8 ++- crates/e2e/tests/e2e/vault_balances.rs | 7 ++- 23 files changed, 132 insertions(+), 216 deletions(-) diff --git a/crates/e2e/src/setup/onchain_components/mod.rs b/crates/e2e/src/setup/onchain_components/mod.rs index 7eb6c63a8c..eaa570c715 100644 --- a/crates/e2e/src/setup/onchain_components/mod.rs +++ b/crates/e2e/src/setup/onchain_components/mod.rs @@ -80,6 +80,13 @@ pub fn to_wei(base: u32) -> U256 { to_wei_with_exp(base, 18) } +/// Returns the provided Eth amount in wei. +/// +/// Equivalent to `amount * 10^18`. +pub fn eth(amount: u32) -> ::alloy::primitives::U256 { + ::alloy::primitives::U256::from(amount) * ::alloy::primitives::utils::Unit::ETHER.wei() +} + pub async fn hook_for_transaction(tx: TransactionBuilder) -> Hook where T: web3::Transport, diff --git a/crates/e2e/tests/e2e/app_data.rs b/crates/e2e/tests/e2e/app_data.rs index 126297f6f0..83d5efa315 100644 --- a/crates/e2e/tests/e2e/app_data.rs +++ b/crates/e2e/tests/e2e/app_data.rs @@ -1,6 +1,6 @@ use { app_data::{AppDataHash, hash_full_app_data}, - e2e::setup::*, + e2e::setup::{eth, *}, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -41,10 +41,7 @@ async fn app_data(web3: Web3) { token_a.mint(trader.address(), to_wei(10)).await; token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -206,10 +203,7 @@ async fn app_data_full_format(web3: Web3) { token_a.mint(trader.address(), to_wei(10)).await; token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/app_data_signer.rs b/crates/e2e/tests/e2e/app_data_signer.rs index 6c42891646..43a28a7f30 100644 --- a/crates/e2e/tests/e2e/app_data_signer.rs +++ b/crates/e2e/tests/e2e/app_data_signer.rs @@ -1,6 +1,6 @@ use { alloy::primitives::Address, - e2e::setup::{OnchainComponents, Services, TestAccount, run_test, safe::Safe, to_wei}, + e2e::setup::{OnchainComponents, Services, TestAccount, eth, run_test, safe::Safe, to_wei}, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -31,10 +31,7 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { token_a.mint(trader.address(), to_wei(10)).await; token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -42,10 +39,7 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { token_a.mint(adversary.address(), to_wei(10)).await; token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(adversary.address().into_alloy()) .send_and_watch() .await @@ -108,10 +102,7 @@ async fn order_creation_checks_metadata_signer(web3: Web3) { token_a.mint(safe.address().into_legacy(), to_wei(10)).await; safe.exec_alloy_call( token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .into_transaction_request(), ) .await; diff --git a/crates/e2e/tests/e2e/buffers.rs b/crates/e2e/tests/e2e/buffers.rs index c6ff11b010..121e62d7b1 100644 --- a/crates/e2e/tests/e2e/buffers.rs +++ b/crates/e2e/tests/e2e/buffers.rs @@ -1,6 +1,6 @@ use { ::alloy::primitives::U256, - e2e::setup::*, + e2e::setup::{eth, *}, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -40,10 +40,7 @@ async fn onchain_settlement_without_liquidity(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(100).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(100)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/cow_amm.rs b/crates/e2e/tests/e2e/cow_amm.rs index 30994c39cd..44675828f7 100644 --- a/crates/e2e/tests/e2e/cow_amm.rs +++ b/crates/e2e/tests/e2e/cow_amm.rs @@ -14,6 +14,7 @@ use { Services, TIMEOUT, colocation::{self, SolverEngine}, + eth, mock::Mock, run_forked_test_with_block_number, run_test, @@ -89,14 +90,11 @@ async fn cow_amm_jit(web3: Web3) { // Fund cow amm owner with 2_000 dai and allow factory take them dai.mint(cow_amm_owner.address(), to_wei(2_000)).await; - dai.approve( - cow_amm_factory.address().into_alloy(), - to_wei(2_000).into_alloy(), - ) - .from(cow_amm_owner.address().into_alloy()) - .send_and_watch() - .await - .unwrap(); + dai.approve(cow_amm_factory.address().into_alloy(), eth(2_000)) + .from(cow_amm_owner.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Fund cow amm owner with 1 WETH and allow factory take them tx_value!( cow_amm_owner.account(), @@ -712,14 +710,11 @@ async fn cow_amm_opposite_direction(web3: Web3) { // them dai.mint(cow_amm_owner.address(), to_wei(2_000)).await; - dai.approve( - cow_amm_factory.address().into_alloy(), - to_wei(2_000).into_alloy(), - ) - .from(cow_amm_owner.address().into_alloy()) - .send_and_watch() - .await - .unwrap(); + dai.approve(cow_amm_factory.address().into_alloy(), eth(2_000)) + .from(cow_amm_owner.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx_value!( cow_amm_owner.account(), diff --git a/crates/e2e/tests/e2e/eth_integration.rs b/crates/e2e/tests/e2e/eth_integration.rs index bfdf0e919e..74c5de80de 100644 --- a/crates/e2e/tests/e2e/eth_integration.rs +++ b/crates/e2e/tests/e2e/eth_integration.rs @@ -1,5 +1,5 @@ use { - e2e::setup::*, + e2e::setup::{eth, *}, ethcontract::prelude::Address, ethrpc::alloy::{ CallBuilderExt, @@ -38,20 +38,14 @@ async fn eth_integration(web3: Web3) { // Approve GPv2 for trading token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(51).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(51)) .from(trader_a.address().into_alloy()) .send_and_watch() .await .unwrap(); token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(51).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(51)) .from(trader_b.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/eth_safe.rs b/crates/e2e/tests/e2e/eth_safe.rs index f097522419..00d7a0ec67 100644 --- a/crates/e2e/tests/e2e/eth_safe.rs +++ b/crates/e2e/tests/e2e/eth_safe.rs @@ -4,6 +4,7 @@ use { OnchainComponents, Services, TIMEOUT, + eth, run_test, safe::Safe, to_wei, @@ -40,20 +41,14 @@ async fn test(web3: Web3) { token.mint(trader.address(), to_wei(4)).await; safe.exec_alloy_call( token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(4).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(4)) .into_transaction_request(), ) .await; token.mint(safe.address().into_legacy(), to_wei(4)).await; token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(4).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(4)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/ethflow.rs b/crates/e2e/tests/e2e/ethflow.rs index 6cd7332652..e6f918a6b3 100644 --- a/crates/e2e/tests/e2e/ethflow.rs +++ b/crates/e2e/tests/e2e/ethflow.rs @@ -14,6 +14,7 @@ use { TIMEOUT, TRADES_ENDPOINT, TestAccount, + eth, run_test, to_wei, wait_for_condition, @@ -115,7 +116,7 @@ async fn eth_flow_tx(web3: Web3) { services.start_protocol(solver).await; let approve_call_data = { - let call_builder = dai.approve(trader.address().into_alloy(), to_wei(10).into_alloy()); + let call_builder = dai.approve(trader.address().into_alloy(), eth(10)); let calldata = call_builder.calldata(); format!("0x{}", hex::encode(calldata)) }; @@ -223,7 +224,7 @@ async fn eth_flow_tx(web3: Web3) { .call() .await .unwrap(); - assert_eq!(allowance, to_wei(10).into_alloy()); + assert_eq!(allowance, eth(10)); let allowance = onchain .contracts() diff --git a/crates/e2e/tests/e2e/hooks.rs b/crates/e2e/tests/e2e/hooks.rs index 3fd4887e75..b2cd89cb07 100644 --- a/crates/e2e/tests/e2e/hooks.rs +++ b/crates/e2e/tests/e2e/hooks.rs @@ -6,6 +6,7 @@ use { OnchainComponents, Services, TIMEOUT, + eth, hook_for_transaction, onchain_components, run_test, @@ -301,10 +302,7 @@ async fn signature(web3: Web3) { // Sign an approval transaction for trading. This will be at nonce 0 because // it is the first transaction evah! let approval_call_data = token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(5).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(5)) .calldata() .to_vec(); let approval_builder = safe.sign_transaction( @@ -566,10 +564,7 @@ async fn quote_verification(web3: Web3) { token.mint(safe.address().into_legacy(), to_wei(5)).await; token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(5).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(5)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -580,7 +575,7 @@ async fn quote_verification(web3: Web3) { let transfer_builder = safe.sign_transaction( *token.address(), token - .transfer(trader.address().into_alloy(), to_wei(5).into_alloy()) + .transfer(trader.address().into_alloy(), eth(5)) .calldata() .to_vec(), alloy::primitives::U256::ZERO, diff --git a/crates/e2e/tests/e2e/jit_orders.rs b/crates/e2e/tests/e2e/jit_orders.rs index 8d922a19ff..4994c6d2bb 100644 --- a/crates/e2e/tests/e2e/jit_orders.rs +++ b/crates/e2e/tests/e2e/jit_orders.rs @@ -1,6 +1,6 @@ use { e2e::{ - setup::{colocation::SolverEngine, mock::Mock, solution::JitOrder, *}, + setup::{colocation::SolverEngine, eth, mock::Mock, solution::JitOrder, *}, tx, tx_value, }, @@ -206,11 +206,11 @@ async fn single_limit_order_test(web3: Web3) { .unwrap(); let trader_balance_increased = - trader_balance_after.saturating_sub(trader_balance_before) >= to_wei(5).into_alloy(); + trader_balance_after.saturating_sub(trader_balance_before) >= eth(5); // Since the fee is 0 in the custom solution, the balance difference has to be // exactly 10 wei let solver_balance_decreased = - solver_balance_before.saturating_sub(solver_balance_after) == to_wei(10).into_alloy(); + solver_balance_before.saturating_sub(solver_balance_after) == eth(10); trader_balance_increased && solver_balance_decreased }) .await diff --git a/crates/e2e/tests/e2e/limit_orders.rs b/crates/e2e/tests/e2e/limit_orders.rs index 6b06b002ce..ade6bd6ea3 100644 --- a/crates/e2e/tests/e2e/limit_orders.rs +++ b/crates/e2e/tests/e2e/limit_orders.rs @@ -4,7 +4,11 @@ use { contracts::ERC20, database::byte_array::ByteArray, driver::domain::eth::NonZeroU256, - e2e::{nodes::forked_node::ForkedNodeApi, setup::*, tx}, + e2e::{ + nodes::forked_node::ForkedNodeApi, + setup::{eth, *}, + tx, + }, ethcontract::{H160, prelude::U256}, ethrpc::alloy::{ CallBuilderExt, @@ -121,7 +125,7 @@ async fn single_limit_order_test(web3: Web3) { token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -131,7 +135,7 @@ async fn single_limit_order_test(web3: Web3) { token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -154,10 +158,7 @@ async fn single_limit_order_test(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader_a.address().into_alloy()) .send_and_watch() .await @@ -207,7 +208,7 @@ async fn single_limit_order_test(web3: Web3) { .call() .await .unwrap(); - balance_after.checked_sub(balance_before).unwrap() >= to_wei(5).into_alloy() + balance_after.checked_sub(balance_before).unwrap() >= eth(5) }) .await .unwrap(); @@ -249,7 +250,7 @@ async fn two_limit_orders_test(web3: Web3) { token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -259,7 +260,7 @@ async fn two_limit_orders_test(web3: Web3) { token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -282,20 +283,14 @@ async fn two_limit_orders_test(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader_a.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader_b.address().into_alloy()) .send_and_watch() .await @@ -369,10 +364,8 @@ async fn two_limit_orders_test(web3: Web3) { .call() .await .unwrap(); - let order_a_settled = - balance_after_a.saturating_sub(balance_before_a) >= to_wei(5).into_alloy(); - let order_b_settled = - balance_after_b.saturating_sub(balance_before_b) >= to_wei(2).into_alloy(); + let order_a_settled = balance_after_a.saturating_sub(balance_before_a) >= eth(5); + let order_b_settled = balance_after_b.saturating_sub(balance_before_b) >= eth(2); order_a_settled && order_b_settled }) .await @@ -409,20 +402,14 @@ async fn two_limit_orders_multiple_winners_test(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(100).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(100)) .from(trader_a.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(100).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(100)) .from(trader_b.address().into_alloy()) .send_and_watch() .await @@ -641,10 +628,7 @@ async fn too_many_limit_orders_test(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(101).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(101)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -726,10 +710,7 @@ async fn limit_does_not_apply_to_in_market_orders_test(web3: Web3) { // Approve GPv2 for trading token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(101).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(101)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -1063,10 +1044,7 @@ async fn no_liquidity_limit_order(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader_a.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/order_cancellation.rs b/crates/e2e/tests/e2e/order_cancellation.rs index a2fcc347f7..ac4eec3c10 100644 --- a/crates/e2e/tests/e2e/order_cancellation.rs +++ b/crates/e2e/tests/e2e/order_cancellation.rs @@ -1,6 +1,6 @@ use { database::order_events::OrderEventLabel, - e2e::setup::*, + e2e::setup::{eth, *}, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -46,10 +46,7 @@ async fn order_cancellation(web3: Web3) { // Approve GPv2 for trading token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/partially_fillable_balance.rs b/crates/e2e/tests/e2e/partially_fillable_balance.rs index 1672b435f3..3c7723247c 100644 --- a/crates/e2e/tests/e2e/partially_fillable_balance.rs +++ b/crates/e2e/tests/e2e/partially_fillable_balance.rs @@ -1,6 +1,9 @@ use { ::alloy::primitives::U256, - e2e::{setup::*, tx}, + e2e::{ + setup::{eth, *}, + tx, + }, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -44,7 +47,7 @@ async fn test(web3: Web3) { token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -54,7 +57,7 @@ async fn test(web3: Web3) { token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -75,10 +78,7 @@ async fn test(web3: Web3) { ); token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(500).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(500)) .from(trader_a.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/partially_fillable_pool.rs b/crates/e2e/tests/e2e/partially_fillable_pool.rs index d2af484c39..0b08272bcd 100644 --- a/crates/e2e/tests/e2e/partially_fillable_pool.rs +++ b/crates/e2e/tests/e2e/partially_fillable_pool.rs @@ -1,5 +1,8 @@ use { - e2e::{setup::*, tx}, + e2e::{ + setup::{eth, *}, + tx, + }, ethcontract::prelude::U256, ethrpc::alloy::{ CallBuilderExt, @@ -43,7 +46,7 @@ async fn test(web3: Web3) { token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -53,7 +56,7 @@ async fn test(web3: Web3) { token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -74,10 +77,7 @@ async fn test(web3: Web3) { ); token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(500).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(500)) .from(trader_a.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index c4bde8b295..6e332985aa 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -2,7 +2,7 @@ use { driver::domain::eth::NonZeroU256, e2e::{ assert_approximately_eq, - setup::{fee::*, *}, + setup::{eth, fee::*, *}, tx, tx_value, }, @@ -98,7 +98,7 @@ async fn combined_protocol_fees(web3: Web3) { token .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -108,7 +108,7 @@ async fn combined_protocol_fees(web3: Web3) { token .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(100).into_alloy(), + eth(100), ) .from(trader.address().into_alloy()) .send_and_watch() @@ -434,7 +434,7 @@ async fn surplus_partner_fee(web3: Web3) { token .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -444,7 +444,7 @@ async fn surplus_partner_fee(web3: Web3) { token .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(100).into_alloy(), + eth(100), ) .from(trader.address().into_alloy()) .send_and_watch() @@ -655,7 +655,7 @@ async fn volume_fee_buy_order_test(web3: Web3) { token_gno .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -665,7 +665,7 @@ async fn volume_fee_buy_order_test(web3: Web3) { token_dai .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -688,10 +688,7 @@ async fn volume_fee_buy_order_test(web3: Web3) { // Approve GPv2 for trading token_gno - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(100).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(100)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/quote_verification.rs b/crates/e2e/tests/e2e/quote_verification.rs index 7a4ebd11e0..b115ec3120 100644 --- a/crates/e2e/tests/e2e/quote_verification.rs +++ b/crates/e2e/tests/e2e/quote_verification.rs @@ -1,6 +1,6 @@ use { bigdecimal::{BigDecimal, Zero}, - e2e::setup::*, + e2e::setup::{eth, *}, ethcontract::{H160, U256}, ethrpc::{ Web3, @@ -95,10 +95,7 @@ async fn standard_verified_quote(web3: Web3) { token.mint(trader.address(), to_wei(1)).await; token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(1).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(1)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/quoting.rs b/crates/e2e/tests/e2e/quoting.rs index 03692c76fa..23c6804fee 100644 --- a/crates/e2e/tests/e2e/quoting.rs +++ b/crates/e2e/tests/e2e/quoting.rs @@ -1,6 +1,6 @@ use { e2e::{ - setup::{colocation::SolverEngine, mock::Mock, *}, + setup::{colocation::SolverEngine, eth, mock::Mock, *}, tx, tx_value, }, @@ -367,10 +367,7 @@ async fn quote_timeout(web3: Web3) { sell_token.mint(trader.address(), to_wei(1)).await; sell_token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(1).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(1)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/replace_order.rs b/crates/e2e/tests/e2e/replace_order.rs index 245c330a4b..88eb575cd9 100644 --- a/crates/e2e/tests/e2e/replace_order.rs +++ b/crates/e2e/tests/e2e/replace_order.rs @@ -1,5 +1,9 @@ use { - e2e::{nodes::local_node::TestNodeApi, setup::*, tx}, + e2e::{ + nodes::local_node::TestNodeApi, + setup::{eth, *}, + tx, + }, ethcontract::prelude::U256, ethrpc::alloy::{ CallBuilderExt, @@ -64,7 +68,7 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -74,7 +78,7 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -97,10 +101,7 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(15).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(15)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -199,7 +200,7 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { .call() .await .unwrap(); - balance_before.saturating_sub(balance_after) == to_wei(10).into_alloy() + balance_before.saturating_sub(balance_after) == eth(10) && !services.get_trades(&order_id).await.unwrap().is_empty() }) .await @@ -249,7 +250,7 @@ async fn try_replace_someone_else_order_test(web3: Web3) { token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -259,7 +260,7 @@ async fn try_replace_someone_else_order_test(web3: Web3) { token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -282,20 +283,14 @@ async fn try_replace_someone_else_order_test(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(15).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(15)) .from(trader_a.address().into_alloy()) .send_and_watch() .await .unwrap(); token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(15).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(15)) .from(trader_b.address().into_alloy()) .send_and_watch() .await @@ -362,7 +357,7 @@ async fn try_replace_someone_else_order_test(web3: Web3) { .call() .await .unwrap(); - balance_before.saturating_sub(balance_after) == to_wei(10).into_alloy() + balance_before.saturating_sub(balance_after) == eth(10) }) .await .unwrap(); @@ -394,7 +389,7 @@ async fn single_replace_order_test(web3: Web3) { token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -404,7 +399,7 @@ async fn single_replace_order_test(web3: Web3) { token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -427,10 +422,7 @@ async fn single_replace_order_test(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(15).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(15)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -543,7 +535,7 @@ async fn single_replace_order_test(web3: Web3) { .await .unwrap(); onchain.mint_block().await; - balance_before.saturating_sub(balance_after) == to_wei(3).into_alloy() + balance_before.saturating_sub(balance_after) == eth(3) }) .await .unwrap(); diff --git a/crates/e2e/tests/e2e/smart_contract_orders.rs b/crates/e2e/tests/e2e/smart_contract_orders.rs index fbc8b3ec6e..bbaddcf2d1 100644 --- a/crates/e2e/tests/e2e/smart_contract_orders.rs +++ b/crates/e2e/tests/e2e/smart_contract_orders.rs @@ -1,6 +1,6 @@ use { e2e::{ - setup::{safe::Safe, *}, + setup::{eth, safe::Safe, *}, tx, }, ethcontract::{Bytes, H160, U256}, @@ -41,10 +41,7 @@ async fn smart_contract_orders(web3: Web3) { // Approve GPv2 for trading safe.exec_alloy_call( token - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(10).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(10)) .into_transaction_request(), ) .await; diff --git a/crates/e2e/tests/e2e/solver_competition.rs b/crates/e2e/tests/e2e/solver_competition.rs index 2b3edb25f5..f57b9ce5f8 100644 --- a/crates/e2e/tests/e2e/solver_competition.rs +++ b/crates/e2e/tests/e2e/solver_competition.rs @@ -1,6 +1,6 @@ use { ::alloy::primitives::U256, - e2e::setup::{colocation::SolverEngine, mock::Mock, *}, + e2e::setup::{colocation::SolverEngine, eth, mock::Mock, *}, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -50,10 +50,7 @@ async fn solver_competition(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(100).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(100)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -185,20 +182,14 @@ async fn wrong_solution_submission_address(web3: Web3) { // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(100).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(100)) .from(trader_a.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(100).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(100)) .from(trader_b.address().into_alloy()) .send_and_watch() .await @@ -331,10 +322,7 @@ async fn store_filtered_solutions(web3: Web3) { token_a.mint(trader.address(), to_wei(2)).await; token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(2).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(2)) .from(trader.address().into_alloy()) .send_and_watch() .await diff --git a/crates/e2e/tests/e2e/solver_participation_guard.rs b/crates/e2e/tests/e2e/solver_participation_guard.rs index fbff92c972..2211bd4f4f 100644 --- a/crates/e2e/tests/e2e/solver_participation_guard.rs +++ b/crates/e2e/tests/e2e/solver_participation_guard.rs @@ -8,6 +8,7 @@ use { Services, TIMEOUT, TestAccount, + eth, run_test, to_wei, wait_for_condition, @@ -266,7 +267,7 @@ async fn setup( token_a .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -276,7 +277,7 @@ async fn setup( token_b .approve( onchain.contracts().uniswap_v2_router.address().into_alloy(), - to_wei(1000).into_alloy(), + eth(1000), ) .from(solver.address().into_alloy()) .send_and_watch() @@ -299,10 +300,7 @@ async fn setup( // Approve GPv2 for trading token_a - .approve( - onchain.contracts().allowance.into_alloy(), - to_wei(1000).into_alloy(), - ) + .approve(onchain.contracts().allowance.into_alloy(), eth(1000)) .from(trader_a.address().into_alloy()) .send_and_watch() .await @@ -375,8 +373,7 @@ async fn execute_order( .call() .await .unwrap(); - let balance_changes = - balance_after.checked_sub(balance_before).unwrap() >= to_wei(5).into_alloy(); + let balance_changes = balance_after.checked_sub(balance_before).unwrap() >= eth(5); let auction_ids_after = fetch_last_settled_auction_ids(services.db()).await.len() > auction_ids_before; balance_changes && auction_ids_after diff --git a/crates/e2e/tests/e2e/univ2.rs b/crates/e2e/tests/e2e/univ2.rs index eeabf090fb..d37291ba72 100644 --- a/crates/e2e/tests/e2e/univ2.rs +++ b/crates/e2e/tests/e2e/univ2.rs @@ -1,7 +1,11 @@ use { ::alloy::primitives::U256, database::order_events::{OrderEvent, OrderEventLabel}, - e2e::{setup::*, tx, tx_value}, + e2e::{ + setup::{eth, *}, + tx, + tx_value, + }, ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::{ order::{OrderCreation, OrderKind}, @@ -104,7 +108,7 @@ async fn test(web3: Web3) { .call() .await .unwrap(); - assert_eq!(balance, to_wei(1).into_alloy()); + assert_eq!(balance, eth(1)); let all_events_registered = || async { let events = crate::database::events_of_order(services.db(), &uid).await; diff --git a/crates/e2e/tests/e2e/vault_balances.rs b/crates/e2e/tests/e2e/vault_balances.rs index 4ad3ab96d6..8cef6b6fc6 100644 --- a/crates/e2e/tests/e2e/vault_balances.rs +++ b/crates/e2e/tests/e2e/vault_balances.rs @@ -1,5 +1,8 @@ use { - e2e::{setup::*, tx}, + e2e::{ + setup::{eth, *}, + tx, + }, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -35,7 +38,7 @@ async fn vault_balances(web3: Web3) { token .approve( onchain.contracts().balancer_vault.address().into_alloy(), - to_wei(10).into_alloy(), + eth(10), ) .from(trader.address().into_alloy()) .send_and_watch() From 7a704c4105ebeda58875126d3315b89840b6d1b1 Mon Sep 17 00:00:00 2001 From: supernovahs <91280922+supernovahs@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:44:35 +0530 Subject: [PATCH 029/112] log rpc request id (#3606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description closes #3404 Add RPC request ID logging to HTTP transport layer for improved observability and debugging. # Changes - [ ] ... - [ ] ... ## How to test --------- Co-authored-by: Martin Magnus Co-authored-by: José Duarte --- crates/ethrpc/src/http.rs | 40 +++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/crates/ethrpc/src/http.rs b/crates/ethrpc/src/http.rs index 2c49d6b799..20f110b77f 100644 --- a/crates/ethrpc/src/http.rs +++ b/crates/ethrpc/src/http.rs @@ -72,6 +72,9 @@ async fn execute_rpc( request: &Request, ) -> Result { let body = serde_json::to_string(&request)?; + + let request_id = observe::distributed_tracing::request_id::from_current_span(); + tracing::trace!(name = %inner.name, %id, %body, "executing request"); let mut request_builder = client .post(inner.url.clone()) @@ -81,13 +84,13 @@ async fn execute_rpc( .body(body); match request { Request::Single(Call::MethodCall(call)) => { - if let Some(metadata) = observe::distributed_tracing::request_id::from_current_span() { + if let Some(metadata) = &request_id { request_builder = request_builder.header("X-REQUEST-ID", metadata); } request_builder = request_builder.header("X-RPC-METHOD", call.method.clone()); } Request::Batch(_) => { - if let Some(metadata) = observe::distributed_tracing::request_id::from_current_span() { + if let Some(ref metadata) = request_id { request_builder = request_builder.header("X-RPC-BATCH-METADATA", metadata); } } @@ -97,12 +100,24 @@ async fn execute_rpc( .send() .await .map_err(|err: reqwest::Error| { - tracing::warn!(name = %inner.name, %id, %err, "failed to send request"); + tracing::warn!( + name = %inner.name, + rpc_request_id = %id, + request_id, + error = %err, + "failed to send request" + ); Web3Error::Transport(TransportError::Message(err.to_string())) })?; let status = response.status(); let text = response.text().await.map_err(|err: reqwest::Error| { - tracing::warn!(name = %inner.name, %id, %err, "failed to get response body"); + tracing::warn!( + name = %inner.name, + rpc_request_id = %id, + request_id, + error = %err, + "failed to get response body" + ); Web3Error::Transport(TransportError::Message(err.to_string())) })?; // Log the raw text before decoding to get more information on responses that @@ -110,17 +125,26 @@ async fn execute_rpc( // newlines in the output. tracing::trace!(name = %inner.name, %id, body = %text.trim(), "received response"); if !status.is_success() { - return Err(Web3Error::Transport(TransportError::Message(format!( - "HTTP error {status}" - )))); + let error_msg = format!("HTTP error {status}"); + + return Err(Web3Error::Transport(TransportError::Message(error_msg))); } let result = jsonrpc_core::serde_from_str(&text).map_err(|err| { + tracing::warn!( + name = %inner.name, + rpc_request_id = %id, + request_id, + error = %err, + raw_response = %text.trim(), + "failed to decode JSON response" + ); Web3Error::Decoder(format!( - "{:?}, raw response: {}, {}, {}", + "{:?}, raw response: {}, rpc_request_id: {}, request_id: {:?}, {}", err, inner.name, id, + request_id, text.trim() )) })?; From 0a09b149d1ec3942ca8f1cfa55d7a866415265d2 Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 16 Sep 2025 08:51:01 -0300 Subject: [PATCH 030/112] Add BalancerQueries contract (#3646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR adds a helper contract called BalancerQueries to the contracts crate. Giving a bit more context, currently the Balancer solver relies on SOR quotes only, which might be running on slightly out-of-date data from the Balancer API. Being able to update and validate amounts with an extra on-chain query swap step would be ideal. On Balancer V3 it's possible to query swaps with the BalancerRouter, which is already present in the contracts crate, but on Balancer V2 the best way is to use this BalancerQueries helper contract, that is not yet available. # Changes I simply followed the steps to add a new contract provided in the [contracts readme.md file](https://github.com/cowprotocol/services/blob/main/crates/contracts/README.md) > - In vendor.rs extend ARTIFACTS with the package and contract name. > - Run the vendor binary for example with (cd crates/contracts; cargo run --bin vendor --features bin). This creates a new json file for this contract in the artifacts folder. > - In build.rs add a generate_contract call for the contract. This creates the ethcontract generated rust code file. > - In lib.rs add an include! call for the contract. This imports the rust code into the library. ## How to test Not sure this addition requires tests to be added. If you think it's necessary, please share a template or even some example I can follow, because I couldn't find test examples from the existing contracts. ## Related Issues Gettin this PR merged and released will unblock https://github.com/gnosis/solvers/pull/161 --------- Co-authored-by: José Duarte --- .../contracts/artifacts/BalancerQueries.json | 1 + crates/contracts/build.rs | 60 +++++++++++++++++++ crates/contracts/src/bin/vendor.rs | 5 ++ crates/contracts/src/lib.rs | 1 + 4 files changed, 67 insertions(+) create mode 100644 crates/contracts/artifacts/BalancerQueries.json diff --git a/crates/contracts/artifacts/BalancerQueries.json b/crates/contracts/artifacts/BalancerQueries.json new file mode 100644 index 0000000000..cabb2f8194 --- /dev/null +++ b/crates/contracts/artifacts/BalancerQueries.json @@ -0,0 +1 @@ +{"abi":[{"inputs":[{"internalType":"contract IVault","name":"_vault","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"enum IVault.SwapKind","name":"kind","type":"uint8"},{"components":[{"internalType":"bytes32","name":"poolId","type":"bytes32"},{"internalType":"uint256","name":"assetInIndex","type":"uint256"},{"internalType":"uint256","name":"assetOutIndex","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"userData","type":"bytes"}],"internalType":"struct IVault.BatchSwapStep[]","name":"swaps","type":"tuple[]"},{"internalType":"contract IAsset[]","name":"assets","type":"address[]"},{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"bool","name":"fromInternalBalance","type":"bool"},{"internalType":"address payable","name":"recipient","type":"address"},{"internalType":"bool","name":"toInternalBalance","type":"bool"}],"internalType":"struct IVault.FundManagement","name":"funds","type":"tuple"}],"name":"queryBatchSwap","outputs":[{"internalType":"int256[]","name":"assetDeltas","type":"int256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"poolId","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"components":[{"internalType":"contract IAsset[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"minAmountsOut","type":"uint256[]"},{"internalType":"bytes","name":"userData","type":"bytes"},{"internalType":"bool","name":"toInternalBalance","type":"bool"}],"internalType":"struct IVault.ExitPoolRequest","name":"request","type":"tuple"}],"name":"queryExit","outputs":[{"internalType":"uint256","name":"bptIn","type":"uint256"},{"internalType":"uint256[]","name":"amountsOut","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"poolId","type":"bytes32"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"components":[{"internalType":"contract IAsset[]","name":"assets","type":"address[]"},{"internalType":"uint256[]","name":"maxAmountsIn","type":"uint256[]"},{"internalType":"bytes","name":"userData","type":"bytes"},{"internalType":"bool","name":"fromInternalBalance","type":"bool"}],"internalType":"struct IVault.JoinPoolRequest","name":"request","type":"tuple"}],"name":"queryJoin","outputs":[{"internalType":"uint256","name":"bptOut","type":"uint256"},{"internalType":"uint256[]","name":"amountsIn","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"poolId","type":"bytes32"},{"internalType":"enum IVault.SwapKind","name":"kind","type":"uint8"},{"internalType":"contract IAsset","name":"assetIn","type":"address"},{"internalType":"contract IAsset","name":"assetOut","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"userData","type":"bytes"}],"internalType":"struct IVault.SingleSwap","name":"singleSwap","type":"tuple"},{"components":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"bool","name":"fromInternalBalance","type":"bool"},{"internalType":"address payable","name":"recipient","type":"address"},{"internalType":"bool","name":"toInternalBalance","type":"bool"}],"internalType":"struct IVault.FundManagement","name":"funds","type":"tuple"}],"name":"querySwap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"contract IVault","name":"","type":"address"}],"stateMutability":"view","type":"function"}]} \ No newline at end of file diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 7e7d246756..a6d7a7db4c 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -655,6 +655,66 @@ fn main() { // Not available on Lens }, ); + generate_contract_with_config("BalancerQueries", |builder| { + builder + .contract_mod_override("balancer_queries") + .add_network( + MAINNET, + Network { + address: addr("0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(15188261)), + }, + ) + .add_network( + ARBITRUM_ONE, + Network { + address: addr("0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(18238624)), + }, + ) + .add_network( + OPTIMISM, + Network { + address: addr("0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(15288107)), + }, + ) + .add_network( + BASE, + Network { + address: addr("0x300Ab2038EAc391f26D9F895dc61F8F66a548833"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(1205869)), + }, + ) + .add_network( + GNOSIS, + Network { + address: addr("0x0F3e0c4218b7b0108a3643cFe9D3ec0d4F57c54e"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(24821845)), + }, + ) + .add_network( + POLYGON, + Network { + address: addr("0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(30988035)), + }, + ) + .add_network( + AVALANCHE, + Network { + address: addr("0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(26387068)), + }, + ) + }); generate_contract_with_config("BalancerV2ComposableStablePoolFactory", |builder| { builder .contract_mod_override("balancer_v2_composable_stable_pool_factory") diff --git a/crates/contracts/src/bin/vendor.rs b/crates/contracts/src/bin/vendor.rs index dc5d998d0e..bd0c12f186 100644 --- a/crates/contracts/src/bin/vendor.rs +++ b/crates/contracts/src/bin/vendor.rs @@ -124,6 +124,11 @@ fn run() -> Result<()> { tasks/deprecated/20230206-composable-stable-pool-v3/artifact/\ ComposableStablePool.json", )? + .github( + "BalancerQueries", + "balancer/balancer-deployments/80505f7061ff668dc55173344564fa1ddd83b726/\ + v2/tasks/20220721-balancer-queries/artifact/BalancerQueries.json", + )? .github( "BalancerV2ComposableStablePoolFactory", "balancer/balancer-deployments/7211ed9f209d767f8ff411bb57262daf83ecb439/\ diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index fd6ac5ac17..a481210cdb 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -50,6 +50,7 @@ macro_rules! include_contracts { include_contracts! { AaveFlashLoanSolverWrapper; + BalancerQueries; BalancerV2Authorizer; BalancerV2BasePoolFactory; BalancerV2ComposableStablePool; From 83a7ec1d98b65b7a9d996da738747e1c9daceb0a Mon Sep 17 00:00:00 2001 From: ilya Date: Tue, 16 Sep 2025 15:23:13 +0300 Subject: [PATCH 031/112] Alloy BalancerV2 liquidity bindings (#3653) # Description Migrates BalancerV2 liquidity-related SCs to alloy. Utilizes generalized interfaces implemented in https://github.com/cowprotocol/services/pull/3648. All the SCs are heavily coupled, so I couldn't split this PR into multiple ones without incorporating annoying intermediate workarounds. ## How to test Mainnet shadow: so far, no performance changes have been observed. --- .../src/database/onchain_order_events/mod.rs | 6 +- crates/autopilot/src/event_updater.rs | 16 +- crates/contracts/build.rs | 831 +----------------- crates/contracts/src/alloy.rs | 251 +++++- crates/contracts/src/lib.rs | 42 +- .../src/boundary/liquidity/balancer/v2/mod.rs | 64 +- crates/driver/src/infra/config/file/load.rs | 22 +- crates/driver/src/infra/config/file/mod.rs | 11 +- crates/driver/src/infra/liquidity/config.rs | 81 +- crates/shared/src/event_handling.rs | 66 +- .../sources/balancer_v2/pool_fetching/mod.rs | 186 ++-- .../balancer_v2/pool_fetching/pool_storage.rs | 31 +- .../balancer_v2/pool_fetching/registry.rs | 61 +- .../src/sources/balancer_v2/pools/common.rs | 4 +- .../balancer_v2/pools/composable_stable.rs | 43 +- .../pools/liquidity_bootstrapping.rs | 34 +- .../src/sources/balancer_v2/pools/stable.rs | 29 +- .../src/sources/balancer_v2/pools/weighted.rs | 70 +- 18 files changed, 761 insertions(+), 1087 deletions(-) diff --git a/crates/autopilot/src/database/onchain_order_events/mod.rs b/crates/autopilot/src/database/onchain_order_events/mod.rs index 787643e243..437697a500 100644 --- a/crates/autopilot/src/database/onchain_order_events/mod.rs +++ b/crates/autopilot/src/database/onchain_order_events/mod.rs @@ -155,8 +155,10 @@ where pub(crate) const INDEX_NAME: &str = "onchain_orders"; #[async_trait::async_trait] -impl EventStoring> - for OnchainOrderParser +impl EventStoring> for OnchainOrderParser +where + T: Send + Sync + Clone, + W: Send + Sync + Clone, { async fn last_event_block(&self) -> Result { let _timer = DatabaseMetrics::get() diff --git a/crates/autopilot/src/event_updater.rs b/crates/autopilot/src/event_updater.rs index b29e12b5c9..1a2117be45 100644 --- a/crates/autopilot/src/event_updater.rs +++ b/crates/autopilot/src/event_updater.rs @@ -9,14 +9,14 @@ use { tokio::sync::Mutex, }; -pub struct EventUpdater(Mutex>) +pub struct EventUpdater(Mutex>) where - Database: EventStoring, + DB: EventStoring, W: EventRetrieving + Send + Sync; -impl EventUpdater +impl EventUpdater where - Database: EventStoring, + DB: EventStoring, W: EventRetrieving + Send + Sync, { /// Creates a new event updater. @@ -26,7 +26,7 @@ where /// in the database. pub fn new( contract: W, - db: Database, + db: DB, block_retriever: Arc, start_sync_at_block: Option, ) -> Self { @@ -47,7 +47,7 @@ where /// there are more recent events available, then the sync start is ignored. pub async fn new_skip_blocks_before( contract: W, - db: Database, + db: DB, block_retriever: Arc, start_sync_at_block: BlockNumberHash, ) -> Result { @@ -64,9 +64,9 @@ where } #[async_trait::async_trait] -impl Maintaining for EventUpdater +impl Maintaining for EventUpdater where - Database: EventStoring + Send + Sync, + DB: EventStoring + Send + Sync, W: EventRetrieving + Send + Sync, W::Event: Send + Sync, { diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index a6d7a7db4c..cbbd92c815 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -152,9 +152,6 @@ fn main() { generate_contract_with_config("BalancerV2Authorizer", |builder| { builder.contract_mod_override("balancer_v2_authorizer") }); - generate_contract_with_config("BalancerV2BasePoolFactory", |builder| { - builder.contract_mod_override("balancer_v2_base_pool_factory") - }); // Balancer addresses can be obtained from: // generate_contract_with_config("BalancerV2Vault", |builder| { @@ -242,419 +239,66 @@ fn main() { ) // Not available on Lens }); - generate_contract_with_config("BalancerV2WeightedPoolFactory", |builder| { - builder - .contract_mod_override("balancer_v2_weighted_pool_factory") - .add_network( - // - MAINNET, - Network { - address: addr("0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(12272147)), - }, - ) - .add_network( - // - GOERLI, - Network { - address: addr("0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(4648101)), - }, - ) - // Not available on Sepolia (only version ≥ 4) - // - // Not available on Lens - }); - generate_contract_with_config("BalancerV2WeightedPoolFactoryV3", |builder| { - builder - .contract_mod_override("balancer_v2_weighted_pool_factory_v3") - .add_network( - // - MAINNET, - Network { - address: addr("0x5Dd94Da3644DDD055fcf6B3E1aa310Bb7801EB8b"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(16520627)), - }, - ) - .add_network( - // - GOERLI, - Network { - address: addr("0x26575A44755E0aaa969FDda1E4291Df22C5624Ea"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(8456831)), - }, - ) - .add_network( - GNOSIS, - Network { - address: addr("0xC128a9954e6c874eA3d62ce62B468bA073093F25"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(26226256)), - }, - ) - .add_network( - AVALANCHE, - Network { - address: addr("0x94f68b54191F62f781Fe8298A8A5Fa3ed772d227"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(26389236)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0xA0DAbEBAAd1b243BBb243f933013d560819eB66f"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(72832703)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0x82e4cFaef85b1B6299935340c964C942280327f4"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(39036828)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0x6e4cF292C5349c79cCd66349c3Ed56357dD11B46"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(25474982)), - }, - ) - // Not available on Sepolia (only version ≥ 4) - // - // Not available on Lens - }); - generate_contract_with_config("BalancerV2WeightedPoolFactoryV4", |builder| { + generate_contract_with_config("BalancerV3BatchRouter", |builder| { builder - .contract_mod_override("balancer_v2_weighted_pool_factory_v4") .add_network( MAINNET, Network { - address: addr("0x897888115Ada5773E02aA29F775430BFB5F34c51"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(16878323)), - }, - ) - .add_network( - GOERLI, - Network { - address: addr("0x230a59F4d9ADc147480f03B0D3fFfeCd56c3289a"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(8694778)), + address: addr("0x136f1EFcC3f8f88516B9E94110D56FDBfB1778d1"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(21339510)), }, ) .add_network( GNOSIS, Network { - address: addr("0x6CaD2ea22BFA7F4C14Aae92E47F510Cd5C509bc7"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(27055829)), + address: addr("0xe2fa4e1d17725e72dcdAfe943Ecf45dF4B9E285b"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(37377506)), }, ) .add_network( - // SEPOLIA, Network { - address: addr("0x7920BFa1b2041911b354747CA7A6cDD2dfC50Cfd"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(3424893)), + address: addr("0xC85b652685567C1B074e8c0D4389f83a2E458b1C"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(7219301)), }, ) .add_network( ARBITRUM_ONE, Network { - address: addr("0xc7E5ED1054A24Ef31D827E6F86caA58B3Bc168d7"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(72222060)), + address: addr("0xaD89051bEd8d96f045E8912aE1672c6C0bF8a85E"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(297828544)), }, ) .add_network( BASE, Network { - address: addr("0x4C32a8a8fDa4E24139B51b456B42290f51d6A1c4"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(1204869)), + address: addr("0x85a80afee867aDf27B50BdB7b76DA70f1E853062"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(25347205)), }, ) .add_network( AVALANCHE, Network { - address: addr("0x230a59F4d9ADc147480f03B0D3fFfeCd56c3289a"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(27739006)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0x230a59F4d9ADc147480f03B0D3fFfeCd56c3289a"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(82737545)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0xFc8a407Bba312ac761D8BFe04CE1201904842B76"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(40611103)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0x230a59F4d9ADc147480f03B0D3fFfeCd56c3289a"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(26665331)), - }, - ) - // Not available on Base and Lens - // - }); - generate_contract_with_config("BalancerV2WeightedPool2TokensFactory", |builder| { - builder - .contract_mod_override("balancer_v2_weighted_pool_2_tokens_factory") - .add_network( - // - MAINNET, - Network { - address: addr("0xa5bf2ddf098bb0ef6d120c98217dd6b141c74ee0"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(12349891)), - }, - ) - .add_network( - // - GOERLI, - Network { - address: addr("0xa5bf2ddf098bb0ef6d120c98217dd6b141c74ee0"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(4716924)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0xCF0a32Bbef8F064969F21f7e02328FB577382018"), - deployment_information: Some(DeploymentInformation::BlockNumber(222864)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0xdAE7e32ADc5d490a43cCba1f0c736033F2b4eFca"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(7005512)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(15832998)), - }, - ) - // Not available on Sepolia, Base, Avalanche, BNB and Lens - // - // - }); - generate_contract_with_config("BalancerV2StablePoolFactoryV2", |builder| { - builder - .contract_mod_override("balancer_v2_stable_pool_factory_v2") - .add_network( - // - MAINNET, - Network { - address: addr("0x8df6efec5547e31b0eb7d1291b511ff8a2bf987c"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(14934936)), - }, - ) - .add_network( - // - GOERLI, - Network { - address: addr("0xD360B8afb3d7463bE823bE1Ec3c33aA173EbE86e"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(7169381)), - }, - ) - .add_network( - GNOSIS, - Network { - address: addr("0xf23b4DB826DbA14c0e857029dfF076b1c0264843"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(25415344)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0xEF44D6786b2b4d544b7850Fe67CE6381626Bf2D6"), - deployment_information: Some(DeploymentInformation::BlockNumber(14244664)), + address: addr("0xc9b36096f5201ea332Db35d6D195774ea0D5988f"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(59965747)), }, ) .add_network( OPTIMISM, Network { - address: addr("0xeb151668006CD04DAdD098AFd0a82e78F77076c3"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(11088891)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0xcA96C4f198d343E251b1a01F3EBA061ef3DA73C1"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(29371951)), - }, - ) - // Not available on Sepolia, Base, Avalanche, BNB and Lens - // - // - }); - generate_contract_with_config("BalancerV2LiquidityBootstrappingPoolFactory", |builder| { - builder - .contract_mod_override("balancer_v2_liquidity_bootstrapping_pool_factory") - .add_network( - // - MAINNET, - Network { - address: addr("0x751A0bC0e3f75b38e01Cf25bFCE7fF36DE1C87DE"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(12871780)), - }, - ) - .add_network( - GOERLI, - Network { - address: addr("0xb48Cc42C45d262534e46d5965a9Ac496F1B7a830"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(6993037)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0x142B9666a0a3A30477b052962ddA81547E7029ab"), - deployment_information: Some(DeploymentInformation::BlockNumber(222870)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0x751A0bC0e3f75b38e01Cf25bFCE7fF36DE1C87DE"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(17116402)), + address: addr("0xaD89051bEd8d96f045E8912aE1672c6C0bF8a85E"), + // + deployment_information: Some(DeploymentInformation::BlockNumber(133969588)), }, ) - // Not available on Sepolia, Base, Avalanche, BNB, Optimism and Lens - // - // + // Not available on Lens }); - generate_contract_with_config( - "BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory", - |builder| { - builder - .contract_mod_override( - "balancer_v2_no_protocol_fee_liquidity_bootstrapping_pool_factory", - ) - .add_network( - // - MAINNET, - Network { - address: addr("0x0F3e0c4218b7b0108a3643cFe9D3ec0d4F57c54e"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(13730248)), - }, - ) - .add_network( - // - GOERLI, - Network { - address: addr("0xB0C726778C3AE4B3454D85557A48e8fa502bDD6A"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(6993471)), - }, - ) - .add_network( - // - GNOSIS, - Network { - address: addr("0x85a80afee867aDf27B50BdB7b76DA70f1E853062"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(25415236)), - }, - ) - .add_network( - // - SEPOLIA, - Network { - address: addr("0x45fFd460cC6642B8D8Fb12373DFd77Ceb0f4932B"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(3419649)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0x1802953277FD955f9a254B80Aa0582f193cF1d77"), - deployment_information: Some(DeploymentInformation::BlockNumber(4859669)), - }, - ) - .add_network( - BASE, - Network { - address: addr("0x0c6052254551EAe3ECac77B01DFcf1025418828f"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(1206531)), - }, - ) - .add_network( - AVALANCHE, - Network { - address: addr("0x0F3e0c4218b7b0108a3643cFe9D3ec0d4F57c54e"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(26386552)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(22691243)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0xf302f9F50958c5593770FDf4d4812309fF77414f"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(7005915)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0x41B953164995c11C81DA73D212ED8Af25741b7Ac"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(22067480)), - }, - ) - // Not available on Lens - }, - ); generate_contract_with_config("BalancerQueries", |builder| { builder .contract_mod_override("balancer_queries") @@ -715,435 +359,6 @@ fn main() { }, ) }); - generate_contract_with_config("BalancerV2ComposableStablePoolFactory", |builder| { - builder - .contract_mod_override("balancer_v2_composable_stable_pool_factory") - .add_network( - MAINNET, - Network { - address: addr("0xf9ac7B9dF2b3454E841110CcE5550bD5AC6f875F"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(15485885)), - }, - ) - .add_network( - GOERLI, - Network { - address: addr("0xB848f50141F3D4255b37aC288C25C109104F2158"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(7542764)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0xaEb406b0E430BF5Ea2Dc0B9Fe62E4E53f74B3a33"), - deployment_information: Some(DeploymentInformation::BlockNumber(23227044)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0xf302f9F50958c5593770FDf4d4812309fF77414f"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(22691193)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0xf145caFB67081895EE80eB7c04A30Cf87f07b745"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(22182522)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0x136FD06Fa01eCF624C7F2B3CB15742c1339dC2c4"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(32774224)), - }, - ) - // Not available on Sepolia, Gnosis Chain, Base, Avalanche and Lens - // - // - // - }); - generate_contract_with_config("BalancerV2ComposableStablePoolFactoryV3", |builder| { - builder - .contract_mod_override("balancer_v2_composable_stable_pool_factory_v3") - .add_network( - MAINNET, - Network { - address: addr("0xdba127fBc23fb20F5929C546af220A991b5C6e01"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(16580899)), - }, - ) - .add_network( - GOERLI, - Network { - address: addr("0xbfD9769b061E57e478690299011A028194D66e3C"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(8456835)), - }, - ) - .add_network( - GNOSIS, - Network { - address: addr("0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(26365805)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0x1c99324EDC771c82A0DCCB780CC7DDA0045E50e7"), - deployment_information: Some(DeploymentInformation::BlockNumber(58948370)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0xacAaC3e6D6Df918Bf3c809DFC7d42de0e4a72d4C"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(25475700)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0xe2E901AB09f37884BA31622dF3Ca7FC19AA443Be"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(72832821)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0x7bc6C0E73EDAa66eF3F6E2f27b0EE8661834c6C9"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(39037615)), - }, - ) - // Not available on Sepolia (only version ≥ 4) and on Base (only version ≥ 5) - // - // - // Not available on Lens - }); - generate_contract_with_config("BalancerV2ComposableStablePoolFactoryV4", |builder| { - builder - .contract_mod_override("balancer_v2_composable_stable_pool_factory_v4") - .add_network( - MAINNET, - Network { - address: addr("0xfADa0f4547AB2de89D1304A668C39B3E09Aa7c76"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(16878679)), - }, - ) - .add_network( - GOERLI, - Network { - address: addr("0x1802953277FD955f9a254B80Aa0582f193cF1d77"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(8695012)), - }, - ) - .add_network( - GNOSIS, - Network { - address: addr("0xD87F44Df0159DC78029AB9CA7D7e57E7249F5ACD"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(27056416)), - }, - ) - .add_network( - // - SEPOLIA, - Network { - address: addr("0xA3fd20E29358c056B727657E83DFd139abBC9924"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(3425277)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0x2498A2B0d6462d2260EAC50aE1C3e03F4829BA95"), - deployment_information: Some(DeploymentInformation::BlockNumber(72235860)), - }, - ) - .add_network( - AVALANCHE, - Network { - address: addr("0x3B1eb8EB7b43882b385aB30533D9A2BeF9052a98"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(29221425)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0x1802953277FD955f9a254B80Aa0582f193cF1d77"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(26666380)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0x1802953277FD955f9a254B80Aa0582f193cF1d77"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(82748180)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0x6Ab5549bBd766A43aFb687776ad8466F8b42f777"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(40613553)), - }, - ) - // Not available on Base and Lens - // - }); - generate_contract_with_config("BalancerV2ComposableStablePoolFactoryV5", |builder| { - builder - .contract_mod_override("balancer_v2_composable_stable_pool_factory_v5") - .add_network( - MAINNET, - Network { - address: addr("0xDB8d758BCb971e482B2C45f7F8a7740283A1bd3A"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(17672478)), - }, - ) - .add_network( - GOERLI, - Network { - address: addr("0x4bdCc2fb18AEb9e2d281b0278D946445070EAda7"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(9329440)), - }, - ) - .add_network( - GNOSIS, - Network { - address: addr("0x4bdCc2fb18AEb9e2d281b0278D946445070EAda7"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(28900564)), - }, - ) - .add_network( - // - SEPOLIA, - Network { - address: addr("0xa523f47A933D5020b23629dDf689695AA94612Dc"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(3872211)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0xA8920455934Da4D853faac1f94Fe7bEf72943eF1"), - deployment_information: Some(DeploymentInformation::BlockNumber(110212282)), - }, - ) - .add_network( - BASE, - Network { - address: addr("0x8df317a729fcaA260306d7de28888932cb579b88"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(1204710)), - }, - ) - .add_network( - AVALANCHE, - Network { - address: addr("0xE42FFA682A26EF8F25891db4882932711D42e467"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(32478827)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0x4fb47126Fa83A8734991E41B942Ac29A3266C968"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(29877945)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0x043A2daD730d585C44FB79D2614F295D2d625412"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(106752707)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0xe2fa4e1d17725e72dcdAfe943Ecf45dF4B9E285b"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(44961548)), - }, - ) - // Not available on Lens - }); - generate_contract_with_config("BalancerV2ComposableStablePoolFactoryV6", |builder| { - builder - .contract_mod_override("balancer_v2_composable_stable_pool_factory_v6") - .add_network( - MAINNET, - Network { - address: addr("0x5B42eC6D40f7B7965BE5308c70e2603c0281C1E9"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(19314764)), - }, - ) - .add_network( - GNOSIS, - Network { - address: addr("0x47B489bf5836f83ABD928C316F8e39bC0587B020"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(32650879)), - }, - ) - .add_network( - // - SEPOLIA, - Network { - address: addr("0x05503B3aDE04aCA81c8D6F88eCB73Ba156982D2B"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(5369821)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0x4bdCc2fb18AEb9e2d281b0278D946445070EAda7"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(184805448)), - }, - ) - .add_network( - BASE, - Network { - address: addr("0x956CCab09898C0AF2aCa5e6C229c3aD4E93d9288"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(11099703)), - }, - ) - .add_network( - AVALANCHE, - Network { - address: addr("0xb9F8AB3ED3F3aCBa64Bc6cd2DcA74B7F38fD7B88"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(42186350)), - }, - ) - .add_network( - BNB, - Network { - address: addr("0x6B5dA774890Db7B7b96C6f44e6a4b0F657399E2e"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(36485719)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0x4bdCc2fb18AEb9e2d281b0278D946445070EAda7"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(116694338)), - }, - ) - .add_network( - POLYGON, - Network { - address: addr("0xEAedc32a51c510d35ebC11088fD5fF2b47aACF2E"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(53996258)), - }, - ) - // Not available on Lens - }); - generate_contract("BalancerV2WeightedPool"); - generate_contract_with_config("BalancerV2StablePool", |builder| { - builder.add_method_alias( - "onSwap((uint8,address,address,uint256,bytes32,uint256,address,address,bytes),\ - uint256[],uint256,uint256)", - "on_swap_with_balances", - ) - }); - generate_contract("BalancerV2LiquidityBootstrappingPool"); - generate_contract("BalancerV2ComposableStablePool"); - generate_contract_with_config("BalancerV3BatchRouter", |builder| { - builder - .add_network( - MAINNET, - Network { - address: addr("0x136f1EFcC3f8f88516B9E94110D56FDBfB1778d1"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(21339510)), - }, - ) - .add_network( - GNOSIS, - Network { - address: addr("0xe2fa4e1d17725e72dcdAfe943Ecf45dF4B9E285b"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(37377506)), - }, - ) - .add_network( - SEPOLIA, - Network { - address: addr("0xC85b652685567C1B074e8c0D4389f83a2E458b1C"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(7219301)), - }, - ) - .add_network( - ARBITRUM_ONE, - Network { - address: addr("0xaD89051bEd8d96f045E8912aE1672c6C0bF8a85E"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(297828544)), - }, - ) - .add_network( - BASE, - Network { - address: addr("0x85a80afee867aDf27B50BdB7b76DA70f1E853062"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(25347205)), - }, - ) - .add_network( - AVALANCHE, - Network { - address: addr("0xc9b36096f5201ea332Db35d6D195774ea0D5988f"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(59965747)), - }, - ) - .add_network( - OPTIMISM, - Network { - address: addr("0xaD89051bEd8d96f045E8912aE1672c6C0bF8a85E"), - // - deployment_information: Some(DeploymentInformation::BlockNumber(133969588)), - }, - ) - // Not available on Lens - }); generate_contract_with_config("BaoswapRouter", |builder| { builder.add_network_str(GNOSIS, "0x6093AeBAC87d62b1A5a4cEec91204e35020E38bE") }); diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index d9809032c3..4a91653cd7 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -47,6 +47,251 @@ crate::bindings!(GnosisSafeProxy); crate::bindings!(GnosisSafeProxyFactory); crate::bindings!(BalancerV2BasePool); +crate::bindings!(BalancerV2BasePoolFactory); +crate::bindings!(BalancerV2WeightedPool); +crate::bindings!(BalancerV2StablePool); +crate::bindings!(BalancerV2ComposableStablePool); +crate::bindings!(BalancerV2LiquidityBootstrappingPool); +crate::bindings!( + BalancerV2WeightedPoolFactory, + // + crate::deployments! { + // + MAINNET => (address!("0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9"), 12272147) + // Not available on Sepolia (only version ≥ 4) + // + // Not available on Lens + } +); +crate::bindings!( + BalancerV2WeightedPoolFactoryV3, + // + crate::deployments! { + // + MAINNET => (address!("0x5Dd94Da3644DDD055fcf6B3E1aa310Bb7801EB8b"), 16520627), + // + GNOSIS => (address!("0xC128a9954e6c874eA3d62ce62B468bA073093F25"), 26226256), + // + AVALANCHE => (address!("0x94f68b54191F62f781Fe8298A8A5Fa3ed772d227"), 26389236), + // + OPTIMISM => (address!("0xA0DAbEBAAd1b243BBb243f933013d560819eB66f"), 72832703), + // + POLYGON => (address!("0x82e4cFaef85b1B6299935340c964C942280327f4"), 39036828), + // + BNB => (address!("0x6e4cF292C5349c79cCd66349c3Ed56357dD11B46"), 25474982), + // Not available on Sepolia (only version ≥ 4) + // + // Not available on Lens + } +); +crate::bindings!( + BalancerV2WeightedPoolFactoryV4, + crate::deployments! { + // + MAINNET => (address!("0x897888115Ada5773E02aA29F775430BFB5F34c51"), 16878323), + // + GNOSIS => (address!("0x6CaD2ea22BFA7F4C14Aae92E47F510Cd5C509bc7"), 27055829), + // + // + SEPOLIA => (address!("0x7920BFa1b2041911b354747CA7A6cDD2dfC50Cfd"), 3424893), + // + ARBITRUM_ONE => (address!("0xc7E5ED1054A24Ef31D827E6F86caA58B3Bc168d7"), 72222060), + // + BASE => (address!("0x4C32a8a8fDa4E24139B51b456B42290f51d6A1c4"), 1204869), + // + AVALANCHE => (address!("0x230a59F4d9ADc147480f03B0D3fFfeCd56c3289a"), 27739006), + // + OPTIMISM => (address!("0x230a59F4d9ADc147480f03B0D3fFfeCd56c3289a"), 82737545), + // + OPTIMISM => (address!("0xFc8a407Bba312ac761D8BFe04CE1201904842B76"), 40611103), + // + BNB => (address!("0x230a59F4d9ADc147480f03B0D3fFfeCd56c3289a"), 26665331), + // Not available on Base and Lens + // + } +); +crate::bindings!( + BalancerV2WeightedPool2TokensFactory, + // + crate::deployments! { + // + MAINNET => (address!("0xa5bf2ddf098bb0ef6d120c98217dd6b141c74ee0"), 12349891), + ARBITRUM_ONE => (address!("0xCF0a32Bbef8F064969F21f7e02328FB577382018"), 222864), + // + OPTIMISM => (address!("0xdAE7e32ADc5d490a43cCba1f0c736033F2b4eFca"), 7005512), + // + POLYGON => (address!("0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9"), 15832998), + // Not available on Sepolia, Base, Avalanche, BNB and Lens + // + // + } +); +crate::bindings!( + BalancerV2StablePoolFactoryV2, + // + crate::deployments! { + // + MAINNET => (address!("0x8df6efec5547e31b0eb7d1291b511ff8a2bf987c"), 14934936), + // + GNOSIS => (address!("0xf23b4DB826DbA14c0e857029dfF076b1c0264843"), 25415344), + ARBITRUM_ONE => (address!("0xEF44D6786b2b4d544b7850Fe67CE6381626Bf2D6"), 14244664), + // + OPTIMISM => (address!("0xeb151668006CD04DAdD098AFd0a82e78F77076c3"), 11088891), + // + POLYGON => (address!("0xcA96C4f198d343E251b1a01F3EBA061ef3DA73C1"), 29371951), + // Not available on Sepolia, Base, Avalanche, BNB and Lens + // + // + } +); +crate::bindings!( + BalancerV2LiquidityBootstrappingPoolFactory, + // + crate::deployments! { + // + MAINNET => (address!("0x751A0bC0e3f75b38e01Cf25bFCE7fF36DE1C87DE"), 12871780), + ARBITRUM_ONE => (address!("0x142B9666a0a3A30477b052962ddA81547E7029ab"), 222870), + // + POLYGON => (address!("0x751A0bC0e3f75b38e01Cf25bFCE7fF36DE1C87DE"), 17116402), + // Not available on Sepolia, Base, Avalanche, BNB, Optimism and Lens + // + // + } +); +crate::bindings!( + BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory, + // + crate::deployments! { + // + MAINNET => (address!("0x0F3e0c4218b7b0108a3643cFe9D3ec0d4F57c54e"), 13730248), + // + // + GNOSIS => (address!("0x85a80afee867aDf27B50BdB7b76DA70f1E853062"), 25415236), + // + // + SEPOLIA => (address!("0x45fFd460cC6642B8D8Fb12373DFd77Ceb0f4932B"), 25415236), + ARBITRUM_ONE => (address!("0x1802953277FD955f9a254B80Aa0582f193cF1d77"), 4859669), + // + BASE => (address!("0x0c6052254551EAe3ECac77B01DFcf1025418828f"), 1206531), + // + AVALANCHE => (address!("0x0F3e0c4218b7b0108a3643cFe9D3ec0d4F57c54e"), 26386552), + // + BNB => (address!("0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD"), 22691243), + // + OPTIMISM => (address!("0xf302f9F50958c5593770FDf4d4812309fF77414f"), 7005915), + // + POLYGON => (address!("0x41B953164995c11C81DA73D212ED8Af25741b7Ac"), 22067480), + // Not available on Lens + } +); +crate::bindings!( + BalancerV2ComposableStablePoolFactory, + crate::deployments! { + // + MAINNET => (address!("0xf9ac7B9dF2b3454E841110CcE5550bD5AC6f875F"), 15485885), + ARBITRUM_ONE => (address!("0xaEb406b0E430BF5Ea2Dc0B9Fe62E4E53f74B3a33"), 23227044), + // + BNB => (address!("0xf302f9F50958c5593770FDf4d4812309fF77414f"), 22691193), + // + OPTIMISM => (address!("0xf145caFB67081895EE80eB7c04A30Cf87f07b745"), 22182522), + // + POLYGON => (address!("0x136FD06Fa01eCF624C7F2B3CB15742c1339dC2c4"), 32774224), + // Not available on Sepolia, Gnosis Chain, Base, Avalanche and Lens + // + // + } +); +crate::bindings!( + BalancerV2ComposableStablePoolFactoryV3, + crate::deployments! { + // + MAINNET => (address!("0xdba127fBc23fb20F5929C546af220A991b5C6e01"), 16580899), + // + GNOSIS => (address!("0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD"), 26365805), + ARBITRUM_ONE => (address!("0x1c99324EDC771c82A0DCCB780CC7DDA0045E50e7"), 58948370), + // + BNB => (address!("0xacAaC3e6D6Df918Bf3c809DFC7d42de0e4a72d4C"), 25475700), + // + OPTIMISM => (address!("0xe2E901AB09f37884BA31622dF3Ca7FC19AA443Be"), 72832821), + // + POLYGON => (address!("0x7bc6C0E73EDAa66eF3F6E2f27b0EE8661834c6C9"), 39037615), + // Not available on Sepolia (only version ≥ 4) and on Base (only version ≥ 5) + // + // + // Not available on Lens + } +); +crate::bindings!( + BalancerV2ComposableStablePoolFactoryV4, + crate::deployments! { + // + MAINNET => (address!("0xfADa0f4547AB2de89D1304A668C39B3E09Aa7c76"), 16878679), + // + GNOSIS => (address!("0xD87F44Df0159DC78029AB9CA7D7e57E7249F5ACD"), 27056416), + // + // + SEPOLIA => (address!("0xA3fd20E29358c056B727657E83DFd139abBC9924"), 3425277), + ARBITRUM_ONE => (address!("0x2498A2B0d6462d2260EAC50aE1C3e03F4829BA95"), 72235860), + // + AVALANCHE => (address!("0x3B1eb8EB7b43882b385aB30533D9A2BeF9052a98"), 29221425), + // + BNB => (address!("0x1802953277FD955f9a254B80Aa0582f193cF1d77"), 26666380), + // + OPTIMISM => (address!("0x1802953277FD955f9a254B80Aa0582f193cF1d77"), 82748180), + // + POLYGON => (address!("0x6Ab5549bBd766A43aFb687776ad8466F8b42f777"), 40613553), + // Not available on Base and Lens + // + } +); +crate::bindings!( + BalancerV2ComposableStablePoolFactoryV5, + crate::deployments! { + // + MAINNET => (address!("0xDB8d758BCb971e482B2C45f7F8a7740283A1bd3A"), 17672478), + // + GNOSIS => (address!("0x4bdCc2fb18AEb9e2d281b0278D946445070EAda7"), 28900564), + // + // + SEPOLIA => (address!("0xa523f47A933D5020b23629dDf689695AA94612Dc"), 3872211), + ARBITRUM_ONE => (address!("0xA8920455934Da4D853faac1f94Fe7bEf72943eF1"), 110212282), + // + BASE => (address!("0x8df317a729fcaA260306d7de28888932cb579b88"), 1204710), + // + AVALANCHE => (address!("0xE42FFA682A26EF8F25891db4882932711D42e467"), 32478827), + // + BNB => (address!("0x4fb47126Fa83A8734991E41B942Ac29A3266C968"), 29877945), + // + OPTIMISM => (address!("0x043A2daD730d585C44FB79D2614F295D2d625412"), 106752707), + // + POLYGON => (address!("0xe2fa4e1d17725e72dcdAfe943Ecf45dF4B9E285b"), 44961548), + // Not available on Lens + } +); +crate::bindings!( + BalancerV2ComposableStablePoolFactoryV6, + crate::deployments! { + // + MAINNET => (address!("0x5B42eC6D40f7B7965BE5308c70e2603c0281C1E9"), 19314764), + // + GNOSIS => (address!("0x47B489bf5836f83ABD928C316F8e39bC0587B020"), 32650879), + // + SEPOLIA => (address!("0x05503B3aDE04aCA81c8D6F88eCB73Ba156982D2B"), 5369821), + // + ARBITRUM_ONE => (address!("0x4bdCc2fb18AEb9e2d281b0278D946445070EAda7"), 184805448), + // + BASE => (address!("0x956CCab09898C0AF2aCa5e6C229c3aD4E93d9288"), 11099703), + // + AVALANCHE => (address!("0xb9F8AB3ED3F3aCBa64Bc6cd2DcA74B7F38fD7B88"), 42186350), + // + BNB => (address!("0x6B5dA774890Db7B7b96C6f44e6a4b0F657399E2e"), 36485719), + // + OPTIMISM => (address!("0x4bdCc2fb18AEb9e2d281b0278D946445070EAda7"), 116694338), + // + POLYGON => (address!("0xEAedc32a51c510d35ebC11088fD5fF2b47aACF2E"), 53996258), + // Not available on Lens + } +); crate::bindings!( BalancerV2Vault, crate::deployments! { @@ -192,10 +437,14 @@ macro_rules! bindings { $crate::alloy::networks::*, }; - pub static DEPLOYMENT_INFO: LazyLock)>> = LazyLock::new(|| { + static DEPLOYMENT_INFO: LazyLock)>> = LazyLock::new(|| { $deployment_info }); + pub fn deployment_address(chain_id: &u64) -> Option { + DEPLOYMENT_INFO.get(chain_id).map(|(addr, _)| *addr) + } + impl $crate::alloy::InstanceExt for Instance { fn deployed(provider: &DynProvider) -> impl Future> + Send { async move { diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index a481210cdb..1c8d17d639 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -52,24 +52,7 @@ include_contracts! { AaveFlashLoanSolverWrapper; BalancerQueries; BalancerV2Authorizer; - BalancerV2BasePoolFactory; - BalancerV2ComposableStablePool; - BalancerV2ComposableStablePoolFactory; - BalancerV2ComposableStablePoolFactoryV3; - BalancerV2ComposableStablePoolFactoryV4; - BalancerV2ComposableStablePoolFactoryV5; - BalancerV2ComposableStablePoolFactoryV6; - BalancerV2LiquidityBootstrappingPool; - BalancerV2LiquidityBootstrappingPoolFactory; - BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory; - BalancerV2StablePool; - BalancerV2StablePoolFactoryV2; BalancerV2Vault; - BalancerV2WeightedPool2TokensFactory; - BalancerV2WeightedPool; - BalancerV2WeightedPoolFactory; - BalancerV2WeightedPoolFactoryV3; - BalancerV2WeightedPoolFactoryV4; BalancerV3BatchRouter; BaoswapRouter; CowAmm; @@ -197,7 +180,9 @@ mod tests { assert_has_deployment_address!(WETH9 for *network); assert_has_deployment_address!(HooksTrampoline for *network); assert_has_deployment_address!(BalancerV2Vault for *network); - assert_has_deployment_address!(BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory for *network); + assert!( + alloy::BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory::deployment_address(network).is_some() + ) } for network in &[MAINNET, GNOSIS, SEPOLIA] { assert_has_deployment_address!(CowProtocolToken for *network); @@ -221,18 +206,23 @@ mod tests { assert_has_deployment_address!(IUniswapV3Factory for *network); } for network in &[MAINNET, ARBITRUM_ONE] { - assert_has_deployment_address!(BalancerV2WeightedPool2TokensFactory for *network); - assert_has_deployment_address!(BalancerV2LiquidityBootstrappingPoolFactory for *network); + assert!( + alloy::BalancerV2WeightedPool2TokensFactory::deployment_address(network).is_some() + ); + assert!( + alloy::BalancerV2LiquidityBootstrappingPoolFactory::deployment_address(network) + .is_some() + ); } for network in &[MAINNET, ARBITRUM_ONE] { assert_has_deployment_address!(PancakeRouter for *network); } - assert_has_deployment_address!(BalancerV2WeightedPoolFactory for MAINNET); + assert!(alloy::BalancerV2WeightedPoolFactory::deployment_address(&MAINNET).is_some()); for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { - assert_has_deployment_address!(BalancerV2StablePoolFactoryV2 for *network); + assert!(alloy::BalancerV2StablePoolFactoryV2::deployment_address(network).is_some()); } for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { @@ -264,12 +254,14 @@ mod tests { assert_has_deployment_information!(GPv2Settlement for *network); assert_has_deployment_information!(BalancerV2Vault for *network); } - assert_has_deployment_information!(BalancerV2WeightedPoolFactory for MAINNET); + assert!(alloy::BalancerV2WeightedPoolFactory::deployment_address(&MAINNET).is_some()); for network in &[MAINNET, ARBITRUM_ONE] { - assert_has_deployment_information!(BalancerV2WeightedPool2TokensFactory for *network); + assert!( + alloy::BalancerV2WeightedPool2TokensFactory::deployment_address(network).is_some() + ); } for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { - assert_has_deployment_information!(BalancerV2StablePoolFactoryV2 for *network); + assert!(alloy::BalancerV2StablePoolFactoryV2::deployment_address(network).is_some()); } } diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs b/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs index 7de4e30541..8cd532b49d 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/mod.rs @@ -9,13 +9,15 @@ use { }, anyhow::{Context, Result}, contracts::{ - BalancerV2ComposableStablePoolFactory, - BalancerV2LiquidityBootstrappingPoolFactory, - BalancerV2StablePoolFactoryV2, - BalancerV2WeightedPoolFactory, - BalancerV2WeightedPoolFactoryV3, GPv2Settlement, - alloy::BalancerV2Vault, + alloy::{ + BalancerV2ComposableStablePoolFactory, + BalancerV2LiquidityBootstrappingPoolFactory, + BalancerV2StablePoolFactoryV2, + BalancerV2Vault, + BalancerV2WeightedPoolFactory, + BalancerV2WeightedPoolFactoryV3, + }, }, ethrpc::{ alloy::conversions::IntoAlloy, @@ -24,9 +26,8 @@ use { shared::{ http_solver::model::TokenAmount, sources::balancer_v2::{ - BalancerFactoryKind, BalancerPoolFetcher, - pool_fetching::BalancerContracts, + pool_fetching::{BalancerContracts, BalancerFactoryInstance}, }, token_info::{CachedTokenInfoFetcher, TokenInfoFetcher}, }, @@ -116,23 +117,18 @@ async fn init_liquidity( .weighted .iter() .map(|&factory| { - ( - BalancerFactoryKind::Weighted, - BalancerV2WeightedPoolFactory::at(&web3, factory.into()) - .raw_instance() - .clone(), - ) + BalancerFactoryInstance::Weighted(BalancerV2WeightedPoolFactory::Instance::new( + factory, + web3.alloy.clone(), + )) }) .collect::>(), config .weighted_v3plus .iter() .map(|&factory| { - ( - BalancerFactoryKind::WeightedV3, - BalancerV2WeightedPoolFactoryV3::at(&web3, factory.into()) - .raw_instance() - .clone(), + BalancerFactoryInstance::WeightedV3( + BalancerV2WeightedPoolFactoryV3::Instance::new(factory, web3.alloy.clone()), ) }) .collect::>(), @@ -140,23 +136,21 @@ async fn init_liquidity( .stable .iter() .map(|&factory| { - ( - BalancerFactoryKind::StableV2, - BalancerV2StablePoolFactoryV2::at(&web3, factory.into()) - .raw_instance() - .clone(), - ) + BalancerFactoryInstance::StableV2(BalancerV2StablePoolFactoryV2::Instance::new( + factory, + web3.alloy.clone(), + )) }) .collect::>(), config .liquidity_bootstrapping .iter() .map(|&factory| { - ( - BalancerFactoryKind::LiquidityBootstrapping, - BalancerV2LiquidityBootstrappingPoolFactory::at(&web3, factory.into()) - .raw_instance() - .clone(), + BalancerFactoryInstance::LiquidityBootstrapping( + BalancerV2LiquidityBootstrappingPoolFactory::Instance::new( + factory, + web3.alloy.clone(), + ), ) }) .collect::>(), @@ -164,11 +158,11 @@ async fn init_liquidity( .composable_stable .iter() .map(|&factory| { - ( - BalancerFactoryKind::ComposableStable, - BalancerV2ComposableStablePoolFactory::at(&web3, factory.into()) - .raw_instance() - .clone(), + BalancerFactoryInstance::ComposableStable( + BalancerV2ComposableStablePoolFactory::Instance::new( + factory, + web3.alloy.clone(), + ), ) }) .collect::>(), diff --git a/crates/driver/src/infra/config/file/load.rs b/crates/driver/src/infra/config/file/load.rs index 47feef96e5..6e518b7bb7 100644 --- a/crates/driver/src/infra/config/file/load.rs +++ b/crates/driver/src/infra/config/file/load.rs @@ -285,23 +285,11 @@ pub async fn load(chain: Chain, path: &Path) -> infra::Config { reinit_interval, } => liquidity::config::BalancerV2 { vault: vault.into(), - weighted: weighted - .into_iter() - .map(eth::ContractAddress::from) - .collect(), - weighted_v3plus: weighted_v3plus - .into_iter() - .map(eth::ContractAddress::from) - .collect(), - stable: stable.into_iter().map(eth::ContractAddress::from).collect(), - liquidity_bootstrapping: liquidity_bootstrapping - .into_iter() - .map(eth::ContractAddress::from) - .collect(), - composable_stable: composable_stable - .into_iter() - .map(eth::ContractAddress::from) - .collect(), + weighted, + weighted_v3plus, + stable, + liquidity_bootstrapping, + composable_stable, pool_deny_list: pool_deny_list.clone(), graph_url, reinit_interval, diff --git a/crates/driver/src/infra/config/file/mod.rs b/crates/driver/src/infra/config/file/mod.rs index 7c2ad98424..2b52c316ac 100644 --- a/crates/driver/src/infra/config/file/mod.rs +++ b/crates/driver/src/infra/config/file/mod.rs @@ -1,6 +1,7 @@ pub use load::load; use { crate::{domain::eth, infra, util::serialize}, + alloy::primitives::Address, reqwest::Url, serde::{Deserialize, Deserializer, Serialize}, serde_with::serde_as, @@ -636,26 +637,26 @@ enum BalancerV2Config { /// The weighted pool factory contract addresses. #[serde(default)] - weighted: Vec, + weighted: Vec
, /// The weighted pool factory v3+ contract addresses. #[serde(default)] - weighted_v3plus: Vec, + weighted_v3plus: Vec
, /// The stable pool factory contract addresses. #[serde(default)] - stable: Vec, + stable: Vec
, /// The liquidity bootstrapping pool factory contract addresses. /// /// These are weighted pools with dynamic weights for initial token /// offerings. #[serde(default)] - liquidity_bootstrapping: Vec, + liquidity_bootstrapping: Vec
, /// The composable stable pool factory contract addresses. #[serde(default)] - composable_stable: Vec, + composable_stable: Vec
, /// Deny listed Balancer V2 pools. #[serde(default)] diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index 48aa4e687e..5c8959bcad 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -1,5 +1,6 @@ use { crate::{domain::eth, infra::blockchain::contracts::deployment_address}, + alloy::primitives::Address, chain::Chain, derive_more::Debug, hex_literal::hex, @@ -188,19 +189,19 @@ pub struct BalancerV2 { pub vault: eth::ContractAddress, /// Weighted pool factory addresses. - pub weighted: Vec, + pub weighted: Vec
, /// Weighted pool factory v3+ addresses. - pub weighted_v3plus: Vec, + pub weighted_v3plus: Vec
, /// Stable pool factory addresses. - pub stable: Vec, + pub stable: Vec
, /// Liquidity bootstrapping pool factory addresses. - pub liquidity_bootstrapping: Vec, + pub liquidity_bootstrapping: Vec
, /// Composable stable pool factory addresses. - pub composable_stable: Vec, + pub composable_stable: Vec
, /// Deny listed Balancer V2 pools. /// @@ -221,37 +222,51 @@ impl BalancerV2 { /// Returns the liquidity configuration for Balancer V2. #[allow(clippy::self_named_constructors)] pub fn balancer_v2(graph_url: &Url, chain: Chain) -> Option { - let factory_addresses = - |contracts: &[ðcontract::Contract]| -> Vec { - contracts - .iter() - .copied() - .filter_map(|c| deployment_address(c, chain)) - .collect() - }; + macro_rules! address_for { + ( $chain:expr, [ $( $($p:ident)::+ ),* $(,)? ] ) => {{ + let arr = [ $({ + $($p)::+::deployment_address(&$chain.id()) + }),* ]; + arr.into_iter() + .flatten() + .collect::>() + }}; + } Some(Self { vault: deployment_address(contracts::BalancerV2Vault::raw_contract(), chain)?, - weighted: factory_addresses(&[ - contracts::BalancerV2WeightedPoolFactory::raw_contract(), - contracts::BalancerV2WeightedPool2TokensFactory::raw_contract(), - ]), - weighted_v3plus: factory_addresses(&[ - contracts::BalancerV2WeightedPoolFactoryV3::raw_contract(), - contracts::BalancerV2WeightedPoolFactoryV4::raw_contract(), - ]), - stable: factory_addresses(&[contracts::BalancerV2StablePoolFactoryV2::raw_contract()]), - liquidity_bootstrapping: factory_addresses(&[ - contracts::BalancerV2LiquidityBootstrappingPoolFactory::raw_contract(), - contracts::BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory::raw_contract(), - ]), - composable_stable: factory_addresses(&[ - contracts::BalancerV2ComposableStablePoolFactory::raw_contract(), - contracts::BalancerV2ComposableStablePoolFactoryV3::raw_contract(), - contracts::BalancerV2ComposableStablePoolFactoryV4::raw_contract(), - contracts::BalancerV2ComposableStablePoolFactoryV5::raw_contract(), - contracts::BalancerV2ComposableStablePoolFactoryV6::raw_contract(), - ]), + weighted: address_for!( + chain, + [ + contracts::alloy::BalancerV2WeightedPoolFactory, + contracts::alloy::BalancerV2WeightedPool2TokensFactory, + ] + ), + weighted_v3plus: address_for!( + chain, + [ + contracts::alloy::BalancerV2WeightedPoolFactoryV3, + contracts::alloy::BalancerV2WeightedPoolFactoryV4, + ] + ), + stable: address_for!(chain, [contracts::alloy::BalancerV2StablePoolFactoryV2,]), + liquidity_bootstrapping: address_for!( + chain, + [ + contracts::alloy::BalancerV2LiquidityBootstrappingPoolFactory, + contracts::alloy::BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory, + ] + ), + composable_stable: address_for!( + chain, + [ + contracts::alloy::BalancerV2ComposableStablePoolFactory, + contracts::alloy::BalancerV2ComposableStablePoolFactoryV3, + contracts::alloy::BalancerV2ComposableStablePoolFactoryV4, + contracts::alloy::BalancerV2ComposableStablePoolFactoryV5, + contracts::alloy::BalancerV2ComposableStablePoolFactoryV6, + ] + ), pool_deny_list: Vec::new(), graph_url: graph_url.clone(), reinit_interval: None, diff --git a/crates/shared/src/event_handling.rs b/crates/shared/src/event_handling.rs index ceb2886aa2..c7c58f7eb0 100644 --- a/crates/shared/src/event_handling.rs +++ b/crates/shared/src/event_handling.rs @@ -1,12 +1,19 @@ use { crate::maintenance::Maintaining, + alloy::{contract::Event, providers::DynProvider, rpc::types::Log, sol_types::SolEvent}, anyhow::{Context, Result}, ethcontract::{EventMetadata, dyns::DynAllEventsBuilder}, - ethrpc::block_stream::{BlockNumberHash, BlockRetrieving, RangeInclusive}, + ethrpc::{ + alloy::conversions::{IntoAlloy, IntoLegacy}, + block_stream::{BlockNumberHash, BlockRetrieving, RangeInclusive}, + }, futures::{Stream, StreamExt, TryStreamExt, future}, + itertools::Itertools, + primitive_types::H256, std::{pin::Pin, sync::Arc}, tokio::sync::Mutex, tracing::Instrument, + web3::types::Address, }; // We expect that there is never a reorg that changes more than the last n @@ -87,6 +94,63 @@ pub trait EthcontractEventRetrieving { fn get_events(&self) -> DynAllEventsBuilder; } +pub trait AlloyEventRetrieving { + type Event: SolEvent + Send + Sync + 'static; + + fn get_events(&self) -> Event<&DynProvider, Self::Event>; +} + +/// A thin wrapper to avoid conflicts with EthcontractEventRetrieving +/// implementation. Will be dropped once fully migrated to alloy. +pub struct AlloyEventRetriever(pub T); + +/// Common `alloy` crate-related event retrieval patterns are extracted to +/// this trait to avoid code duplication in implementations. +#[async_trait::async_trait] +impl EventRetrieving for AlloyEventRetriever +where + T: AlloyEventRetrieving + Send + Sync + 'static, +{ + type Event = (T::Event, Log); + + async fn get_events_by_block_hash(&self, block_hash: H256) -> Result> { + self.0 + .get_events() + .at_block_hash(block_hash.into_alloy()) + .query() + .await + .map_err(anyhow::Error::from) + } + + async fn get_events_by_block_range( + &self, + block_range: &RangeInclusive, + ) -> Result> { + let stream = self + .0 + .get_events() + .from_block(*block_range.start()) + .to_block(*block_range.end()) + .watch() + .await + .map_err(anyhow::Error::from)? + .into_stream() + .map_err(anyhow::Error::from); + + Ok(Box::pin(stream)) + } + + fn address(&self) -> Vec
{ + self.0 + .get_events() + .filter + .address + .iter() + .map(|addr| addr.into_legacy()) + .collect_vec() + } +} + /// Common `ethcontract` crate-related event retrieval patterns are extracted to /// this trait to avoid code duplication and any dependencies on the /// `ethcontract` crate in the main event handling traits. This will be removed diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs index b77aae6f91..0dd41c6791 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/mod.rs @@ -26,13 +26,14 @@ use { swap::fixed_point::Bfp, }, crate::{ - ethrpc::{Web3, Web3Transport}, + ethrpc::Web3, recent_block_cache::{Block, CacheConfig}, token_info::TokenInfoFetching, }, + alloy::providers::DynProvider, anyhow::{Context, Result}, clap::ValueEnum, - contracts::{ + contracts::alloy::{ BalancerV2ComposableStablePoolFactory, BalancerV2ComposableStablePoolFactoryV3, BalancerV2ComposableStablePoolFactoryV4, @@ -41,18 +42,19 @@ use { BalancerV2LiquidityBootstrappingPoolFactory, BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory, BalancerV2StablePoolFactoryV2, + BalancerV2Vault, BalancerV2WeightedPool2TokensFactory, BalancerV2WeightedPoolFactory, BalancerV2WeightedPoolFactoryV3, BalancerV2WeightedPoolFactoryV4, - alloy::{BalancerV2Vault, InstanceExt}, + InstanceExt, }, - ethcontract::{BlockId, H160, H256, Instance, dyns::DynInstance}, + ethcontract::{BlockId, H160, H256}, ethrpc::block_stream::{BlockRetrieving, CurrentBlockWatcher}, model::TokenPair, reqwest::{Client, Url}, std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{BTreeMap, HashSet}, sync::Arc, }, tracing::instrument, @@ -185,6 +187,63 @@ pub enum BalancerFactoryKind { ComposableStableV6, } +pub enum BalancerFactoryInstance { + Weighted(BalancerV2WeightedPoolFactory::Instance), + WeightedV3(BalancerV2WeightedPoolFactoryV3::Instance), + WeightedV4(BalancerV2WeightedPoolFactoryV4::Instance), + Weighted2Token(BalancerV2WeightedPool2TokensFactory::Instance), + StableV2(BalancerV2StablePoolFactoryV2::Instance), + LiquidityBootstrapping(BalancerV2LiquidityBootstrappingPoolFactory::Instance), + NoProtocolFeeLiquidityBootstrapping( + BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory::Instance, + ), + ComposableStable(BalancerV2ComposableStablePoolFactory::Instance), + ComposableStableV3(BalancerV2ComposableStablePoolFactoryV3::Instance), + ComposableStableV4(BalancerV2ComposableStablePoolFactoryV4::Instance), + ComposableStableV5(BalancerV2ComposableStablePoolFactoryV5::Instance), + ComposableStableV6(BalancerV2ComposableStablePoolFactoryV6::Instance), +} + +impl BalancerFactoryInstance { + pub fn address(&self) -> &alloy::primitives::Address { + match self { + BalancerFactoryInstance::Weighted(instance) => instance.address(), + BalancerFactoryInstance::WeightedV3(instance) => instance.address(), + BalancerFactoryInstance::WeightedV4(instance) => instance.address(), + BalancerFactoryInstance::Weighted2Token(instance) => instance.address(), + BalancerFactoryInstance::StableV2(instance) => instance.address(), + BalancerFactoryInstance::LiquidityBootstrapping(instance) => instance.address(), + BalancerFactoryInstance::NoProtocolFeeLiquidityBootstrapping(instance) => { + instance.address() + } + BalancerFactoryInstance::ComposableStable(instance) => instance.address(), + BalancerFactoryInstance::ComposableStableV3(instance) => instance.address(), + BalancerFactoryInstance::ComposableStableV4(instance) => instance.address(), + BalancerFactoryInstance::ComposableStableV5(instance) => instance.address(), + BalancerFactoryInstance::ComposableStableV6(instance) => instance.address(), + } + } + + pub fn provider(&self) -> &DynProvider { + match self { + BalancerFactoryInstance::Weighted(instance) => instance.provider(), + BalancerFactoryInstance::WeightedV3(instance) => instance.provider(), + BalancerFactoryInstance::WeightedV4(instance) => instance.provider(), + BalancerFactoryInstance::Weighted2Token(instance) => instance.provider(), + BalancerFactoryInstance::StableV2(instance) => instance.provider(), + BalancerFactoryInstance::LiquidityBootstrapping(instance) => instance.provider(), + BalancerFactoryInstance::NoProtocolFeeLiquidityBootstrapping(instance) => { + instance.provider() + } + BalancerFactoryInstance::ComposableStable(instance) => instance.provider(), + BalancerFactoryInstance::ComposableStableV3(instance) => instance.provider(), + BalancerFactoryInstance::ComposableStableV4(instance) => instance.provider(), + BalancerFactoryInstance::ComposableStableV5(instance) => instance.provider(), + BalancerFactoryInstance::ComposableStableV6(instance) => instance.provider(), + } + } +} + impl BalancerFactoryKind { /// Returns a vector with supported factories for the specified chain ID. pub fn for_chain(chain_id: u64) -> Vec { @@ -226,7 +285,7 @@ impl BalancerFactoryKind { /// All balancer related contracts that we expect to exist. pub struct BalancerContracts { pub vault: BalancerV2Vault::Instance, - pub factories: Vec<(BalancerFactoryKind, DynInstance)>, + pub factories: Vec, } impl BalancerContracts { @@ -238,57 +297,72 @@ impl BalancerContracts { macro_rules! instance { ($factory:ident) => {{ - $factory::deployed(&web3) + $factory::Instance::deployed(&web3.alloy) .await .context(format!( "Cannot retrieve Balancer factory {}", stringify!($factory) ))? - .raw_instance() - .clone() }}; } - let mut factories = HashMap::new(); + let mut factories = Vec::new(); for kind in factory_kinds { let instance = match &kind { - BalancerFactoryKind::Weighted => instance!(BalancerV2WeightedPoolFactory), - BalancerFactoryKind::WeightedV3 => instance!(BalancerV2WeightedPoolFactoryV3), - BalancerFactoryKind::WeightedV4 => instance!(BalancerV2WeightedPoolFactoryV4), - BalancerFactoryKind::Weighted2Token => { - instance!(BalancerV2WeightedPool2TokensFactory) + BalancerFactoryKind::Weighted => { + BalancerFactoryInstance::Weighted(instance!(BalancerV2WeightedPoolFactory)) + } + BalancerFactoryKind::WeightedV3 => { + BalancerFactoryInstance::WeightedV3(instance!(BalancerV2WeightedPoolFactoryV3)) + } + BalancerFactoryKind::WeightedV4 => { + BalancerFactoryInstance::WeightedV4(instance!(BalancerV2WeightedPoolFactoryV4)) + } + BalancerFactoryKind::Weighted2Token => BalancerFactoryInstance::Weighted2Token( + instance!(BalancerV2WeightedPool2TokensFactory), + ), + BalancerFactoryKind::StableV2 => { + BalancerFactoryInstance::StableV2(instance!(BalancerV2StablePoolFactoryV2)) } - BalancerFactoryKind::StableV2 => instance!(BalancerV2StablePoolFactoryV2), BalancerFactoryKind::LiquidityBootstrapping => { - instance!(BalancerV2LiquidityBootstrappingPoolFactory) + BalancerFactoryInstance::LiquidityBootstrapping(instance!( + BalancerV2LiquidityBootstrappingPoolFactory + )) } BalancerFactoryKind::NoProtocolFeeLiquidityBootstrapping => { - instance!(BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory) - } - BalancerFactoryKind::ComposableStable => { - instance!(BalancerV2ComposableStablePoolFactory) + BalancerFactoryInstance::NoProtocolFeeLiquidityBootstrapping(instance!( + BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory + )) } + BalancerFactoryKind::ComposableStable => BalancerFactoryInstance::ComposableStable( + instance!(BalancerV2ComposableStablePoolFactory), + ), BalancerFactoryKind::ComposableStableV3 => { - instance!(BalancerV2ComposableStablePoolFactoryV3) + BalancerFactoryInstance::ComposableStableV3(instance!( + BalancerV2ComposableStablePoolFactoryV3 + )) } BalancerFactoryKind::ComposableStableV4 => { - instance!(BalancerV2ComposableStablePoolFactoryV4) + BalancerFactoryInstance::ComposableStableV4(instance!( + BalancerV2ComposableStablePoolFactoryV4 + )) } BalancerFactoryKind::ComposableStableV5 => { - instance!(BalancerV2ComposableStablePoolFactoryV5) + BalancerFactoryInstance::ComposableStableV5(instance!( + BalancerV2ComposableStablePoolFactoryV5 + )) } BalancerFactoryKind::ComposableStableV6 => { - instance!(BalancerV2ComposableStablePoolFactoryV6) + BalancerFactoryInstance::ComposableStableV6(instance!( + BalancerV2ComposableStablePoolFactoryV6 + )) } }; - factories.insert(kind, instance); + factories.push(instance); } - Ok(Self { - vault, - factories: factories.into_iter().collect(), - }) + Ok(Self { vault, factories }) } } @@ -395,18 +469,16 @@ async fn create_aggregate_pool_fetcher( macro_rules! registry { ($factory:ident, $instance:expr_2021) => {{ + use ethrpc::alloy::conversions::IntoLegacy; + create_internal_pool_fetcher( contracts.vault.clone(), - $factory::with_deployment_info( - &$instance.web3(), - $instance.address(), - $instance.deployment_information(), - ), + $factory::Instance::new(*$instance.address(), $instance.provider().clone()), block_retriever.clone(), token_infos.clone(), $instance, registered_pools_by_factory - .remove(&$instance.address()) + .remove(&(*$instance.address()).into_legacy()) .unwrap_or_else(|| RegisteredPools::empty(fetched_block_number)), fetched_block_hash, )? @@ -414,26 +486,42 @@ async fn create_aggregate_pool_fetcher( } let mut fetchers = Vec::new(); - for (kind, instance) in &contracts.factories { - let registry = match kind { - BalancerFactoryKind::Weighted | BalancerFactoryKind::Weighted2Token => { + for instance in &contracts.factories { + let registry = match &instance { + BalancerFactoryInstance::Weighted(_) => { + registry!(BalancerV2WeightedPoolFactory, instance) + } + BalancerFactoryInstance::Weighted2Token(_) => { registry!(BalancerV2WeightedPoolFactory, instance) } - BalancerFactoryKind::WeightedV3 | BalancerFactoryKind::WeightedV4 => { + BalancerFactoryInstance::WeightedV3(_) => { registry!(BalancerV2WeightedPoolFactoryV3, instance) } - BalancerFactoryKind::StableV2 => { + BalancerFactoryInstance::WeightedV4(_) => { + registry!(BalancerV2WeightedPoolFactoryV3, instance) + } + BalancerFactoryInstance::StableV2(_) => { registry!(BalancerV2StablePoolFactoryV2, instance) } - BalancerFactoryKind::LiquidityBootstrapping - | BalancerFactoryKind::NoProtocolFeeLiquidityBootstrapping => { + BalancerFactoryInstance::LiquidityBootstrapping(_) => { + registry!(BalancerV2LiquidityBootstrappingPoolFactory, instance) + } + BalancerFactoryInstance::NoProtocolFeeLiquidityBootstrapping(_) => { registry!(BalancerV2LiquidityBootstrappingPoolFactory, instance) } - BalancerFactoryKind::ComposableStable - | BalancerFactoryKind::ComposableStableV3 - | BalancerFactoryKind::ComposableStableV4 - | BalancerFactoryKind::ComposableStableV5 - | BalancerFactoryKind::ComposableStableV6 => { + BalancerFactoryInstance::ComposableStable(_) => { + registry!(BalancerV2ComposableStablePoolFactory, instance) + } + BalancerFactoryInstance::ComposableStableV3(_) => { + registry!(BalancerV2ComposableStablePoolFactory, instance) + } + BalancerFactoryInstance::ComposableStableV4(_) => { + registry!(BalancerV2ComposableStablePoolFactory, instance) + } + BalancerFactoryInstance::ComposableStableV5(_) => { + registry!(BalancerV2ComposableStablePoolFactory, instance) + } + BalancerFactoryInstance::ComposableStableV6(_) => { registry!(BalancerV2ComposableStablePoolFactory, instance) } }; @@ -467,7 +555,7 @@ fn create_internal_pool_fetcher( factory: Factory, block_retriever: Arc, token_infos: Arc, - factory_instance: &Instance, + factory_instance: &BalancerFactoryInstance, registered_pools: RegisteredPools, fetched_block_hash: H256, ) -> Result> diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs index 2de5adb6cb..1094d1a605 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs @@ -20,13 +20,11 @@ use { event_handling::EventStoring, sources::balancer_v2::pools::{FactoryIndexing, PoolIndexing, common}, }, + alloy::rpc::types::Log, anyhow::{Context, Result}, - contracts::balancer_v2_base_pool_factory::{ - Event as BasePoolFactoryEvent, - event_data::PoolCreated, - }, - ethcontract::{Event, H160, H256}, - ethrpc::block_stream::RangeInclusive, + contracts::alloy::BalancerV2BasePoolFactory::BalancerV2BasePoolFactory::PoolCreated, + ethcontract::{H160, H256}, + ethrpc::{alloy::conversions::IntoLegacy, block_stream::RangeInclusive}, model::TokenPair, std::{ cmp, @@ -134,7 +132,7 @@ where ) -> Result<()> { let pool = self .pool_info_fetcher - .fetch_pool_info(pool_creation.pool, block_created) + .fetch_pool_info(pool_creation.pool.into_legacy(), block_created) .await?; self.insert_pool(pool); @@ -183,13 +181,13 @@ where } #[async_trait::async_trait] -impl EventStoring> for PoolStorage +impl EventStoring<(PoolCreated, Log)> for PoolStorage where Factory: FactoryIndexing, { async fn replace_events( &mut self, - events: Vec>, + events: Vec<(PoolCreated, Log)>, range: RangeInclusive, ) -> Result<()> { tracing::debug!("replacing {} events for block {:?}", events.len(), range); @@ -198,15 +196,13 @@ where self.append_events(events).await } - async fn append_events(&mut self, events: Vec>) -> Result<()> { + async fn append_events(&mut self, events: Vec<(PoolCreated, Log)>) -> Result<()> { tracing::debug!("inserting {} events", events.len()); - for event in events { - let block_created = event.meta.context("event missing metadata")?.block_number; - let BasePoolFactoryEvent::PoolCreated(pool_created) = event.data; + for (event, log) in events { + let block_created = log.block_number.context("event missing block number")?; - self.index_pool_creation(pool_created, block_created) - .await?; + self.index_pool_creation(event, block_created).await?; } Ok(()) @@ -230,6 +226,7 @@ mod tests { pools::{MockFactoryIndexing, common::MockPoolInfoFetching, weighted}, swap::fixed_point::Bfp, }, + ethrpc::alloy::conversions::IntoAlloy, maplit::{hashmap, hashset}, mockall::predicate::eq, }; @@ -256,7 +253,7 @@ mod tests { .map(|i| { ( PoolCreated { - pool: pool_addresses[i], + pool: pool_addresses[i].into_alloy(), }, i as u64, ) @@ -438,7 +435,7 @@ mod tests { weights: vec![Bfp::from_wei(1337.into())], }; let new_creation = PoolCreated { - pool: new_pool.common.address, + pool: new_pool.common.address.into_alloy(), }; mock_pool_fetcher diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs index 62ed87f10f..e9b0ee06c6 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs @@ -4,56 +4,48 @@ use { super::{internal::InternalPoolFetching, pool_storage::PoolStorage}, crate::{ - event_handling::{EthcontractEventRetrieving, EventHandler}, + event_handling::{AlloyEventRetriever, AlloyEventRetrieving, EventHandler}, maintenance::Maintaining, recent_block_cache::Block, - sources::balancer_v2::pools::{ - FactoryIndexing, - Pool, - PoolStatus, - common::PoolInfoFetching, + sources::balancer_v2::{ + pool_fetching::BalancerFactoryInstance, + pools::{FactoryIndexing, Pool, PoolStatus, common::PoolInfoFetching}, }, }, + alloy::{contract::Event, providers::DynProvider, rpc::types::Log}, anyhow::Result, contracts::{ - BalancerV2BasePoolFactory, - balancer_v2_base_pool_factory, + alloy::{ + BalancerV2BasePoolFactory, + BalancerV2BasePoolFactory::BalancerV2BasePoolFactory::PoolCreated, + }, errors::EthcontractErrorType, }, - ethcontract::{BlockId, H256, Instance, dyns::DynAllEventsBuilder, errors::MethodError}, - ethrpc::{ - Web3Transport, - block_stream::{BlockNumberHash, BlockRetrieving}, - }, + ethcontract::{BlockId, H256, errors::MethodError}, + ethrpc::block_stream::{BlockNumberHash, BlockRetrieving}, futures::future, - hex_literal::hex, model::TokenPair, std::{collections::HashSet, sync::Arc}, tokio::sync::Mutex, }; -pub struct BasePoolFactoryContract(BalancerV2BasePoolFactory); +pub struct BasePoolFactoryContract(BalancerV2BasePoolFactory::Instance); -const POOL_CREATED_TOPIC: H256 = H256(hex!( - "83a48fbcfc991335314e74d0496aab6a1987e992ddc85dddbcc4d6dd6ef2e9fc" -)); - -impl EthcontractEventRetrieving for BasePoolFactoryContract { - type Event = balancer_v2_base_pool_factory::Event; +#[async_trait::async_trait] +impl AlloyEventRetrieving for BasePoolFactoryContract { + type Event = PoolCreated; - fn get_events(&self) -> DynAllEventsBuilder { - let mut events = self.0.all_events(); - events.filter = events.filter.topic0(POOL_CREATED_TOPIC.into()); - events + fn get_events(&self) -> Event<&DynProvider, Self::Event> { + self.0.PoolCreated_filter() } } /// Type alias for the internal event updater type. type PoolUpdater = Mutex< EventHandler< - BasePoolFactoryContract, + AlloyEventRetriever, PoolStorage, - ethcontract::Event, + (PoolCreated, Log), >, >; @@ -79,13 +71,13 @@ where pub fn new( block_retreiver: Arc, fetcher: Arc>, - factory_instance: &Instance, + factory_instance: &BalancerFactoryInstance, initial_pools: Vec, start_sync_at_block: Option, ) -> Self { let updater = Mutex::new(EventHandler::new( block_retreiver, - BasePoolFactoryContract(base_pool_factory(factory_instance)), + AlloyEventRetriever(BasePoolFactoryContract(base_pool_factory(factory_instance))), PoolStorage::new(initial_pools, fetcher.clone()), start_sync_at_block, )); @@ -134,11 +126,12 @@ where } } -fn base_pool_factory(contract_instance: &Instance) -> BalancerV2BasePoolFactory { - BalancerV2BasePoolFactory::with_deployment_info( - &contract_instance.web3(), - contract_instance.address(), - contract_instance.deployment_information(), +fn base_pool_factory( + contract_instance: &BalancerFactoryInstance, +) -> BalancerV2BasePoolFactory::Instance { + BalancerV2BasePoolFactory::Instance::new( + *contract_instance.address(), + contract_instance.provider().clone(), ) } diff --git a/crates/shared/src/sources/balancer_v2/pools/common.rs b/crates/shared/src/sources/balancer_v2/pools/common.rs index d5ea1b4739..8092ecd103 100644 --- a/crates/shared/src/sources/balancer_v2/pools/common.rs +++ b/crates/shared/src/sources/balancer_v2/pools/common.rs @@ -471,8 +471,8 @@ mod tests { .unwrap() .abi_encode_output(&[ DynSolValue::Bool(false), - DynSolValue::Uint(U256::zero().into_alloy(), 256), - DynSolValue::Uint(U256::zero().into_alloy(), 256), + DynSolValue::Uint(alloy::primitives::U256::ZERO, 256), + DynSolValue::Uint(alloy::primitives::U256::ZERO, 256), ]) .unwrap(); asserter.push_success(&get_paused_state_response); diff --git a/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs b/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs index 74d3705946..258fbbdb2d 100644 --- a/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs +++ b/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs @@ -7,8 +7,9 @@ use { swap::fixed_point::Bfp, }, anyhow::Result, - contracts::{BalancerV2ComposableStablePool, BalancerV2ComposableStablePoolFactory}, + contracts::alloy::{BalancerV2ComposableStablePool, BalancerV2ComposableStablePoolFactory}, ethcontract::BlockId, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, futures::{FutureExt as _, future::BoxFuture}, }; @@ -32,7 +33,7 @@ impl PoolIndexing for PoolInfo { } #[async_trait::async_trait] -impl FactoryIndexing for BalancerV2ComposableStablePoolFactory { +impl FactoryIndexing for BalancerV2ComposableStablePoolFactory::Instance { type PoolInfo = PoolInfo; type PoolState = PoolState; @@ -46,17 +47,31 @@ impl FactoryIndexing for BalancerV2ComposableStablePoolFactory { common_pool_state: BoxFuture<'static, common::PoolState>, block: BlockId, ) -> BoxFuture<'static, Result>> { - let pool_contract = BalancerV2ComposableStablePool::at( - &self.raw_instance().web3(), - pool_info.common.address, + let pool_contract = BalancerV2ComposableStablePool::Instance::new( + pool_info.common.address.into_alloy(), + self.provider().clone(), ); let fetch_common = common_pool_state.map(Result::Ok); - let fetch_scaling_factors = pool_contract.get_scaling_factors().block(block).call(); - let fetch_amplification_parameter = pool_contract - .get_amplification_parameter() - .block(block) - .call(); + let scaling_factors_block = block.into_alloy(); + let amp_param_block = scaling_factors_block; + let pool_contract_clone = pool_contract.clone(); + let fetch_scaling_factors = async move { + pool_contract + .getScalingFactors() + .block(scaling_factors_block) + .call() + .await + .map_err(anyhow::Error::from) + }; + let fetch_amplification_parameter = async move { + pool_contract_clone + .getAmplificationParameter() + .block(amp_param_block) + .call() + .await + .map_err(anyhow::Error::from) + }; async move { let (common, scaling_factors, amplification_parameter) = futures::try_join!( @@ -65,8 +80,10 @@ impl FactoryIndexing for BalancerV2ComposableStablePoolFactory { fetch_amplification_parameter )?; let amplification_parameter = { - let (factor, _, precision) = amplification_parameter; - AmplificationParameter::try_new(factor, precision)? + AmplificationParameter::try_new( + amplification_parameter.value.into_legacy(), + amplification_parameter.precision.into_legacy(), + )? }; Ok(Some(PoolState { @@ -78,7 +95,7 @@ impl FactoryIndexing for BalancerV2ComposableStablePoolFactory { ( address, common::TokenState { - scaling_factor: Bfp::from_wei(scaling_factor), + scaling_factor: Bfp::from_wei(scaling_factor.into_legacy()), ..token }, ) diff --git a/crates/shared/src/sources/balancer_v2/pools/liquidity_bootstrapping.rs b/crates/shared/src/sources/balancer_v2/pools/liquidity_bootstrapping.rs index 0700fcc7ea..8080ca3138 100644 --- a/crates/shared/src/sources/balancer_v2/pools/liquidity_bootstrapping.rs +++ b/crates/shared/src/sources/balancer_v2/pools/liquidity_bootstrapping.rs @@ -7,11 +7,12 @@ use { swap::fixed_point::Bfp, }, anyhow::Result, - contracts::{ + contracts::alloy::{ BalancerV2LiquidityBootstrappingPool, BalancerV2LiquidityBootstrappingPoolFactory, }, ethcontract::BlockId, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, futures::{FutureExt as _, future::BoxFuture}, }; @@ -39,7 +40,7 @@ impl PoolIndexing for PoolInfo { } #[async_trait::async_trait] -impl FactoryIndexing for BalancerV2LiquidityBootstrappingPoolFactory { +impl FactoryIndexing for BalancerV2LiquidityBootstrappingPoolFactory::Instance { type PoolInfo = PoolInfo; type PoolState = PoolState; @@ -53,16 +54,33 @@ impl FactoryIndexing for BalancerV2LiquidityBootstrappingPoolFactory { common_pool_state: BoxFuture<'static, common::PoolState>, block: BlockId, ) -> BoxFuture<'static, Result>> { - let pool_contract = BalancerV2LiquidityBootstrappingPool::at( - &self.raw_instance().web3(), - pool_info.common.address, + let pool_contract = BalancerV2LiquidityBootstrappingPool::Instance::new( + pool_info.common.address.into_alloy(), + self.provider().clone(), ); let fetch_common = common_pool_state.map(Result::Ok); // Liquidity bootstrapping pools use dynamic weights, meaning that we // need to fetch them every time. - let fetch_weights = pool_contract.get_normalized_weights().block(block).call(); - let fetch_swap_enabled = pool_contract.get_swap_enabled().block(block).call(); + let weights_block = block.into_alloy(); + let swap_block = weights_block; + let pool_contract_clone = pool_contract.clone(); + let fetch_weights = async move { + pool_contract + .getNormalizedWeights() + .block(weights_block) + .call() + .await + .map_err(anyhow::Error::from) + }; + let fetch_swap_enabled = async move { + pool_contract_clone + .getSwapEnabled() + .block(swap_block) + .call() + .await + .map_err(anyhow::Error::from) + }; async move { let (common, weights, swap_enabled) = @@ -80,7 +98,7 @@ impl FactoryIndexing for BalancerV2LiquidityBootstrappingPoolFactory { address, TokenState { common, - weight: Bfp::from_wei(weight), + weight: Bfp::from_wei(weight.into_legacy()), }, ) }) diff --git a/crates/shared/src/sources/balancer_v2/pools/stable.rs b/crates/shared/src/sources/balancer_v2/pools/stable.rs index b30452942d..b60f8b96df 100644 --- a/crates/shared/src/sources/balancer_v2/pools/stable.rs +++ b/crates/shared/src/sources/balancer_v2/pools/stable.rs @@ -10,8 +10,9 @@ use { }, }, anyhow::{Result, ensure}, - contracts::{BalancerV2StablePool, BalancerV2StablePoolFactoryV2}, + contracts::alloy::{BalancerV2StablePool, BalancerV2StablePoolFactoryV2}, ethcontract::{BlockId, H160, U256}, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, futures::{FutureExt as _, future::BoxFuture}, num::BigRational, std::collections::BTreeMap, @@ -78,7 +79,7 @@ impl AmplificationParameter { } #[async_trait::async_trait] -impl FactoryIndexing for BalancerV2StablePoolFactoryV2 { +impl FactoryIndexing for BalancerV2StablePoolFactoryV2::Instance { type PoolInfo = PoolInfo; type PoolState = PoolState; @@ -92,21 +93,29 @@ impl FactoryIndexing for BalancerV2StablePoolFactoryV2 { common_pool_state: BoxFuture<'static, common::PoolState>, block: BlockId, ) -> BoxFuture<'static, Result>> { - let pool_contract = - BalancerV2StablePool::at(&self.raw_instance().web3(), pool_info.common.address); + let pool_contract = BalancerV2StablePool::Instance::new( + pool_info.common.address.into_alloy(), + self.provider().clone(), + ); let fetch_common = common_pool_state.map(Result::Ok); - let fetch_amplification_parameter = pool_contract - .get_amplification_parameter() - .block(block) - .call(); + let fetch_amplification_parameter = async move { + pool_contract + .getAmplificationParameter() + .block(block.into_alloy()) + .call() + .await + .map_err(anyhow::Error::from) + }; async move { let (common, amplification_parameter) = futures::try_join!(fetch_common, fetch_amplification_parameter)?; let amplification_parameter = { - let (factor, _, precision) = amplification_parameter; - AmplificationParameter::try_new(factor, precision)? + AmplificationParameter::try_new( + amplification_parameter.value.into_legacy(), + amplification_parameter.precision.into_legacy(), + )? }; Ok(Some(PoolState { diff --git a/crates/shared/src/sources/balancer_v2/pools/weighted.rs b/crates/shared/src/sources/balancer_v2/pools/weighted.rs index e8e71a6eef..f0a46e6b4b 100644 --- a/crates/shared/src/sources/balancer_v2/pools/weighted.rs +++ b/crates/shared/src/sources/balancer_v2/pools/weighted.rs @@ -7,12 +7,13 @@ use { swap::fixed_point::Bfp, }, anyhow::{Result, anyhow}, - contracts::{ + contracts::alloy::{ BalancerV2WeightedPool, BalancerV2WeightedPoolFactory, BalancerV2WeightedPoolFactoryV3, }, ethcontract::{BlockId, H160}, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, futures::{FutureExt as _, future::BoxFuture}, std::collections::BTreeMap, }; @@ -65,19 +66,21 @@ impl PoolIndexing for PoolInfo { } #[async_trait::async_trait] -impl FactoryIndexing for BalancerV2WeightedPoolFactory { +impl FactoryIndexing for BalancerV2WeightedPoolFactory::Instance { type PoolInfo = PoolInfo; type PoolState = PoolState; async fn specialize_pool_info(&self, pool: common::PoolInfo) -> Result { - let pool_contract = BalancerV2WeightedPool::at(&self.raw_instance().web3(), pool.address); + let pool_contract = BalancerV2WeightedPool::Instance::new( + pool.address.into_alloy(), + self.provider().clone(), + ); let weights = pool_contract - .methods() - .get_normalized_weights() + .getNormalizedWeights() .call() .await? .into_iter() - .map(Bfp::from_wei) + .map(|weight| Bfp::from_wei(weight.into_legacy())) .collect(); Ok(PoolInfo { @@ -97,12 +100,13 @@ impl FactoryIndexing for BalancerV2WeightedPoolFactory { } #[async_trait::async_trait] -impl FactoryIndexing for BalancerV2WeightedPoolFactoryV3 { - type PoolInfo = ::PoolInfo; - type PoolState = ::PoolState; +impl FactoryIndexing for BalancerV2WeightedPoolFactoryV3::Instance { + type PoolInfo = ::PoolInfo; + type PoolState = ::PoolState; async fn specialize_pool_info(&self, pool: common::PoolInfo) -> Result { - let v0 = BalancerV2WeightedPoolFactory::at(&self.raw_instance().web3(), self.address()); + let v0 = + BalancerV2WeightedPoolFactory::Instance::new(*self.address(), self.provider().clone()); v0.specialize_pool_info(pool).await } @@ -145,7 +149,11 @@ mod tests { use { super::*, crate::sources::balancer_v2::graph_api::Token, - contracts::dummy_contract, + alloy::{ + dyn_abi::{DynSolValue, FunctionExt}, + primitives::Address, + providers::{Provider, ProviderBuilder, mock::Asserter}, + }, ethcontract::{H160, H256}, ethcontract_mock::Mock, futures::future, @@ -221,19 +229,40 @@ mod tests { async fn fetch_weighted_pool() { let weights = [bfp!("0.5"), bfp!("0.25"), bfp!("0.25")]; - let mock = Mock::new(42); - let web3 = mock.web3(); + let asserter = Asserter::new(); + let provider = ProviderBuilder::new() + .connect_mocked_client(asserter.clone()) + .erased(); + + let pool = + BalancerV2WeightedPool::Instance::new(Address::new([0x90; 20]), provider.clone()); + let factory = BalancerV2WeightedPoolFactory::Instance::new( + Address::new([0xfa; 20]), + provider.clone(), + ); + let get_normalized_weights_response = { + let weighted_response = DynSolValue::Array( + weights + .iter() + .copied() + .map(|w| DynSolValue::Uint(w.as_uint256().into_alloy(), 256)) + .collect(), + ); - let pool = mock.deploy(BalancerV2WeightedPool::raw_contract().interface.abi.clone()); - pool.expect_call(BalancerV2WeightedPool::signatures().get_normalized_weights()) - .returns(weights.iter().copied().map(Bfp::as_uint256).collect()); + BalancerV2WeightedPool::abi_functions_by_name("getNormalizedWeights") + .unwrap() + .first() + .unwrap() + .abi_encode_output(&[weighted_response]) + .unwrap() + }; + asserter.push_success(&get_normalized_weights_response); - let factory = BalancerV2WeightedPoolFactory::at(&web3, H160([0xfa; 20])); let pool = factory .specialize_pool_info(common::PoolInfo { id: H256([0x90; 32]), tokens: vec![H160([1; 20]), H160([2; 20]), H160([3; 20])], - address: pool.address(), + address: pool.address().into_legacy(), scaling_factors: vec![Bfp::exp10(0), Bfp::exp10(0), Bfp::exp10(0)], block_created: 42, }) @@ -261,7 +290,10 @@ mod tests { let mock = Mock::new(42); let web3 = mock.web3(); - let factory = dummy_contract!(BalancerV2WeightedPoolFactory, H160::default()); + let factory = BalancerV2WeightedPoolFactory::Instance::new( + Address::default(), + ethrpc::mock::web3().alloy, + ); let pool_info = PoolInfo { common: common::PoolInfo { id: H256([0x90; 32]), From 66df1e8946fbb23e83f21cb7143984826f6bd838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Tue, 16 Sep 2025 14:29:43 +0100 Subject: [PATCH 032/112] Migrate BaoswapRouter to alloy (#3663) --- crates/contracts/build.rs | 3 -- crates/contracts/src/alloy.rs | 16 +++++++ crates/contracts/src/lib.rs | 2 - crates/driver/src/infra/liquidity/config.rs | 14 +++++-- crates/shared/src/sources/swapr.rs | 46 +++++++++++++++++++++ crates/shared/src/sources/uniswap_v2/mod.rs | 17 +++++--- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index cbbd92c815..15ffbf5245 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -359,9 +359,6 @@ fn main() { }, ) }); - generate_contract_with_config("BaoswapRouter", |builder| { - builder.add_network_str(GNOSIS, "0x6093AeBAC87d62b1A5a4cEec91204e35020E38bE") - }); generate_contract("ERC20"); generate_contract("ERC3156FlashLoanSolverWrapper"); generate_contract_with_config("FlashLoanRouter", |builder| { diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 4a91653cd7..08eb307005 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -317,6 +317,15 @@ crate::bindings!( } ); +// UniV2 +crate::bindings!( + BaoswapRouter, + crate::deployments! { + // https://gnosisscan.io/tx/0xdcbfa037f2c6c7456022df0632ec8d6a75d0f9a195238eec679d5d26895eb7b1 + GNOSIS => (address!("0x6093AeBAC87d62b1A5a4cEec91204e35020E38bE")) + } +); + pub use alloy::providers::DynProvider as Provider; /// Extension trait to attach some useful functions to the contract instance. @@ -441,6 +450,7 @@ macro_rules! bindings { $deployment_info }); + /// Returns the contract's deployment address (if one exists) for the given chain. pub fn deployment_address(chain_id: &u64) -> Option { DEPLOYMENT_INFO.get(chain_id).map(|(addr, _)| *addr) } @@ -486,9 +496,15 @@ macro_rules! bindings { #[cfg(test)] mod tests { + use super::networks::*; use super::*; use alloy::primitives::Selector; + #[test] + fn test_has_address() { + assert!(BaoswapRouter::deployment_address(&GNOSIS).is_some()); + } + #[test] fn test_selector_by_name_valid_function() { let result = ChainalysisOracle::selector_by_name("isSanctioned"); diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 1c8d17d639..0771fc71b6 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -54,7 +54,6 @@ include_contracts! { BalancerV2Authorizer; BalancerV2Vault; BalancerV3BatchRouter; - BaoswapRouter; CowAmm; CowAmmConstantProductFactory; CowAmmLegacyHelper; @@ -230,7 +229,6 @@ mod tests { } // only gnosis - assert_has_deployment_address!(BaoswapRouter for GNOSIS); assert_has_deployment_address!(HoneyswapRouter for GNOSIS); // only sepolia diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index 5c8959bcad..2716267587 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -1,10 +1,15 @@ use { - crate::{domain::eth, infra::blockchain::contracts::deployment_address}, + crate::{ + domain::{eth, eth::ContractAddress}, + infra::blockchain::contracts::deployment_address, + }, alloy::primitives::Address, chain::Chain, derive_more::Debug, + ethrpc::alloy::conversions::IntoLegacy, hex_literal::hex, reqwest::Url, + shared::sources::uniswap_v2::BAOSWAP_INIT, std::{collections::HashSet, time::Duration}, }; @@ -83,9 +88,10 @@ impl UniswapV2 { /// Returns the liquidity configuration for Baoswap. pub fn baoswap(chain: Chain) -> Option { Some(Self { - router: deployment_address(contracts::BaoswapRouter::raw_contract(), chain)?, - pool_code: hex!("0bae3ead48c325ce433426d2e8e6b07dac10835baec21e163760682ea3d3520d") - .into(), + router: ContractAddress::from( + contracts::alloy::BaoswapRouter::deployment_address(&chain.id())?.into_legacy(), + ), + pool_code: BAOSWAP_INIT.into(), missing_pool_cache_time: Duration::from_secs(60 * 60), }) } diff --git a/crates/shared/src/sources/swapr.rs b/crates/shared/src/sources/swapr.rs index 1543cdea07..0b5b3fbd2f 100644 --- a/crates/shared/src/sources/swapr.rs +++ b/crates/shared/src/sources/swapr.rs @@ -57,8 +57,13 @@ mod tests { recent_block_cache::Block, sources::{BaselineSource, uniswap_v2}, }, + alloy::{ + primitives::{Address, address}, + providers::Provider, + }, contracts::errors::testing_contract_error, ethcontract::H160, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, maplit::hashset, }; @@ -101,6 +106,47 @@ mod tests { ); } + const GNOSIS_CHAIN_WETH: Address = address!("6A023CCd1ff6F2045C3309768eAd9E68F978f6e1"); + const GNOSIS_CHAIN_WXDAI: Address = address!("e91D153E0b41518A2Ce8Dd3D7944Fa863463a97d"); + + #[tokio::test] + #[ignore] + async fn fetch_baoswap_pool() { + let web3 = Web3::new_from_env(); + let version = web3.alloy.get_chain_id().await.unwrap().to_string(); + let pool_fetcher = uniswap_v2::UniV2BaselineSourceParameters::from_baseline_source( + BaselineSource::Baoswap, + &version, + ) + .unwrap() + .into_source(&web3) + .await + .unwrap() + .pool_fetching; + let pool = pool_fetcher + .fetch( + hashset! { + TokenPair::new( + GNOSIS_CHAIN_WETH.into_legacy(), + GNOSIS_CHAIN_WXDAI.into_legacy(), + ) + .unwrap(), + }, + Block::Recent, + ) + .await + .unwrap() + .into_iter() + .next() + .unwrap(); + + println!("WETH <> wxDAI pool: {pool:#?}"); + assert_eq!( + pool.address.into_alloy(), + address!("8c36f7ca02d50bf8e705f582328b873acbe9438d") + ); + } + #[tokio::test] #[ignore] async fn fetch_swapr_pool() { diff --git a/crates/shared/src/sources/uniswap_v2/mod.rs b/crates/shared/src/sources/uniswap_v2/mod.rs index b04b1a7b30..880b078c33 100644 --- a/crates/shared/src/sources/uniswap_v2/mod.rs +++ b/crates/shared/src/sources/uniswap_v2/mod.rs @@ -16,6 +16,7 @@ use { anyhow::{Context, Result}, contracts::IUniswapLikeRouter, ethcontract::{H160, H256}, + ethrpc::alloy::conversions::IntoLegacy, hex_literal::hex, std::{fmt::Display, str::FromStr, sync::Arc}, }; @@ -63,6 +64,7 @@ pub struct UniV2BaselineSource { impl UniV2BaselineSourceParameters { pub fn from_baseline_source(source: BaselineSource, chain: &str) -> Option { use BaselineSource as BS; + let (contract, init_code_digest, pool_reading) = match source { BS::None | BS::BalancerV2 | BS::ZeroEx | BS::UniswapV3 => None, BS::UniswapV2 => Some(( @@ -80,11 +82,6 @@ impl UniV2BaselineSourceParameters { SUSHISWAP_INIT, PoolReadingStyle::Default, )), - BS::Baoswap => Some(( - contracts::BaoswapRouter::raw_contract(), - BAOSWAP_INIT, - PoolReadingStyle::Default, - )), BS::Swapr => Some(( contracts::SwaprRouter::raw_contract(), SWAPR_INIT, @@ -95,6 +92,16 @@ impl UniV2BaselineSourceParameters { TESTNET_UNISWAP_INIT, PoolReadingStyle::Default, )), + BS::Baoswap => { + return Some(Self { + router: contracts::alloy::BaoswapRouter::deployment_address( + &chain.parse::().expect("chain id should be an integer"), + ) + .map(|address| address.into_legacy())?, + init_code_digest: H256(BAOSWAP_INIT), + pool_reading: PoolReadingStyle::Default, + }); + } }?; Some(Self { router: contract.networks.get(chain)?.address, From c74d43b7426c88556d34b3f08c39ef29a614d21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 17 Sep 2025 09:22:48 +0100 Subject: [PATCH 033/112] Migrate HoneyswapRouter to alloy (#3664) --- crates/contracts/src/alloy.rs | 7 ++ crates/contracts/src/lib.rs | 4 - crates/driver/src/infra/liquidity/config.rs | 11 +- crates/shared/src/sources/swapr.rs | 46 -------- crates/shared/src/sources/uniswap_v2/mod.rs | 110 ++++++++++++++++++-- 5 files changed, 113 insertions(+), 65 deletions(-) diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 08eb307005..674dae6734 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -325,6 +325,12 @@ crate::bindings!( GNOSIS => (address!("0x6093AeBAC87d62b1A5a4cEec91204e35020E38bE")) } ); +crate::bindings!( + HoneyswapRouter, + crate::deployments! { + GNOSIS => (address!("0x1C232F01118CB8B424793ae03F870aa7D0ac7f77")) + } +); pub use alloy::providers::DynProvider as Provider; @@ -503,6 +509,7 @@ mod tests { #[test] fn test_has_address() { assert!(BaoswapRouter::deployment_address(&GNOSIS).is_some()); + assert!(HoneyswapRouter::deployment_address(&GNOSIS).is_some()); } #[test] diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 0771fc71b6..93c76163f9 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -67,7 +67,6 @@ include_contracts! { FlashLoanRouter; GPv2AllowListAuthentication; GPv2Settlement; - HoneyswapRouter; HooksTrampoline; IAavePool; IFlashLoanSolverWrapper; @@ -228,9 +227,6 @@ mod tests { assert_has_deployment_address!(SwaprRouter for *network); } - // only gnosis - assert_has_deployment_address!(HoneyswapRouter for GNOSIS); - // only sepolia assert_has_deployment_address!(TestnetUniswapV2Router02 for SEPOLIA); } diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index 2716267587..fc1173d476 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -1,6 +1,6 @@ use { crate::{ - domain::{eth, eth::ContractAddress}, + domain::eth::{self, ContractAddress}, infra::blockchain::contracts::deployment_address, }, alloy::primitives::Address, @@ -9,7 +9,7 @@ use { ethrpc::alloy::conversions::IntoLegacy, hex_literal::hex, reqwest::Url, - shared::sources::uniswap_v2::BAOSWAP_INIT, + shared::sources::uniswap_v2::{BAOSWAP_INIT, HONEYSWAP_INIT}, std::{collections::HashSet, time::Duration}, }; @@ -78,9 +78,10 @@ impl UniswapV2 { /// Returns the liquidity configuration for Honeyswap. pub fn honeyswap(chain: Chain) -> Option { Some(Self { - router: deployment_address(contracts::HoneyswapRouter::raw_contract(), chain)?, - pool_code: hex!("3f88503e8580ab941773b59034fb4b2a63e86dbc031b3633a925533ad3ed2b93") - .into(), + router: ContractAddress::from( + contracts::alloy::BaoswapRouter::deployment_address(&chain.id())?.into_legacy(), + ), + pool_code: HONEYSWAP_INIT.into(), missing_pool_cache_time: Duration::from_secs(60 * 60), }) } diff --git a/crates/shared/src/sources/swapr.rs b/crates/shared/src/sources/swapr.rs index 0b5b3fbd2f..1543cdea07 100644 --- a/crates/shared/src/sources/swapr.rs +++ b/crates/shared/src/sources/swapr.rs @@ -57,13 +57,8 @@ mod tests { recent_block_cache::Block, sources::{BaselineSource, uniswap_v2}, }, - alloy::{ - primitives::{Address, address}, - providers::Provider, - }, contracts::errors::testing_contract_error, ethcontract::H160, - ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, maplit::hashset, }; @@ -106,47 +101,6 @@ mod tests { ); } - const GNOSIS_CHAIN_WETH: Address = address!("6A023CCd1ff6F2045C3309768eAd9E68F978f6e1"); - const GNOSIS_CHAIN_WXDAI: Address = address!("e91D153E0b41518A2Ce8Dd3D7944Fa863463a97d"); - - #[tokio::test] - #[ignore] - async fn fetch_baoswap_pool() { - let web3 = Web3::new_from_env(); - let version = web3.alloy.get_chain_id().await.unwrap().to_string(); - let pool_fetcher = uniswap_v2::UniV2BaselineSourceParameters::from_baseline_source( - BaselineSource::Baoswap, - &version, - ) - .unwrap() - .into_source(&web3) - .await - .unwrap() - .pool_fetching; - let pool = pool_fetcher - .fetch( - hashset! { - TokenPair::new( - GNOSIS_CHAIN_WETH.into_legacy(), - GNOSIS_CHAIN_WXDAI.into_legacy(), - ) - .unwrap(), - }, - Block::Recent, - ) - .await - .unwrap() - .into_iter() - .next() - .unwrap(); - - println!("WETH <> wxDAI pool: {pool:#?}"); - assert_eq!( - pool.address.into_alloy(), - address!("8c36f7ca02d50bf8e705f582328b873acbe9438d") - ); - } - #[tokio::test] #[ignore] async fn fetch_swapr_pool() { diff --git a/crates/shared/src/sources/uniswap_v2/mod.rs b/crates/shared/src/sources/uniswap_v2/mod.rs index 880b078c33..113d6cdaa0 100644 --- a/crates/shared/src/sources/uniswap_v2/mod.rs +++ b/crates/shared/src/sources/uniswap_v2/mod.rs @@ -65,6 +65,8 @@ impl UniV2BaselineSourceParameters { pub fn from_baseline_source(source: BaselineSource, chain: &str) -> Option { use BaselineSource as BS; + let chain_id = chain.parse::().expect("chain id should be an integer"); + let (contract, init_code_digest, pool_reading) = match source { BS::None | BS::BalancerV2 | BS::ZeroEx | BS::UniswapV3 => None, BS::UniswapV2 => Some(( @@ -72,11 +74,14 @@ impl UniV2BaselineSourceParameters { UNISWAP_INIT, PoolReadingStyle::Default, )), - BS::Honeyswap => Some(( - contracts::HoneyswapRouter::raw_contract(), - HONEYSWAP_INIT, - PoolReadingStyle::Default, - )), + BS::Honeyswap => { + return Some(Self { + router: contracts::alloy::HoneyswapRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: H256(HONEYSWAP_INIT), + pool_reading: PoolReadingStyle::Default, + }); + } BS::SushiSwap => Some(( contracts::SushiSwapRouter::raw_contract(), SUSHISWAP_INIT, @@ -94,10 +99,8 @@ impl UniV2BaselineSourceParameters { )), BS::Baoswap => { return Some(Self { - router: contracts::alloy::BaoswapRouter::deployment_address( - &chain.parse::().expect("chain id should be an integer"), - ) - .map(|address| address.into_legacy())?, + router: contracts::alloy::BaoswapRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, init_code_digest: H256(BAOSWAP_INIT), pool_reading: PoolReadingStyle::Default, }); @@ -173,7 +176,17 @@ impl FromStr for UniV2BaselineSourceParameters { #[cfg(test)] mod tests { - use {super::*, model::TokenPair}; + use { + super::*, + crate::recent_block_cache::Block, + alloy::{ + primitives::{Address, address}, + providers::Provider, + }, + ethrpc::alloy::conversions::IntoAlloy, + maplit::hashset, + model::TokenPair, + }; #[test] fn parse_address_init() { @@ -296,4 +309,81 @@ mod tests { ) .await; } + + const GNOSIS_CHAIN_WETH: Address = address!("6A023CCd1ff6F2045C3309768eAd9E68F978f6e1"); + const GNOSIS_CHAIN_WXDAI: Address = address!("e91D153E0b41518A2Ce8Dd3D7944Fa863463a97d"); + + #[tokio::test] + #[ignore] + async fn fetch_baoswap_pool() { + let web3 = Web3::new_from_env(); + let version = web3.alloy.get_chain_id().await.unwrap().to_string(); + let pool_fetcher = + UniV2BaselineSourceParameters::from_baseline_source(BaselineSource::Baoswap, &version) + .unwrap() + .into_source(&web3) + .await + .unwrap() + .pool_fetching; + let pool = pool_fetcher + .fetch( + hashset! { + TokenPair::new( + GNOSIS_CHAIN_WETH.into_legacy(), + GNOSIS_CHAIN_WXDAI.into_legacy(), + ) + .unwrap(), + }, + Block::Recent, + ) + .await + .unwrap() + .into_iter() + .next() + .unwrap(); + + println!("WETH <> wxDAI pool: {pool:#?}"); + assert_eq!( + pool.address.into_alloy(), + address!("8c36f7ca02d50bf8e705f582328b873acbe9438d") + ); + } + + #[tokio::test] + #[ignore] + async fn fetch_honeyswap_pool() { + let web3 = Web3::new_from_env(); + let version = web3.alloy.get_chain_id().await.unwrap().to_string(); + let pool_fetcher = UniV2BaselineSourceParameters::from_baseline_source( + BaselineSource::Honeyswap, + &version, + ) + .unwrap() + .into_source(&web3) + .await + .unwrap() + .pool_fetching; + let pool = pool_fetcher + .fetch( + hashset! { + TokenPair::new( + GNOSIS_CHAIN_WETH.into_legacy(), + GNOSIS_CHAIN_WXDAI.into_legacy(), + ) + .unwrap(), + }, + Block::Recent, + ) + .await + .unwrap() + .into_iter() + .next() + .unwrap(); + + println!("WETH <> wxDAI pool: {pool:#?}"); + assert_eq!( + pool.address.into_alloy(), + address!("7bea4af5d425f2d4485bdad1859c88617df31a67") + ); + } } From dcacf3ddf30583fb28deece4d4073f76a22686b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 17 Sep 2025 10:46:11 +0100 Subject: [PATCH 034/112] Migrate PancakeswapRouter to alloy (#3665) --- crates/contracts/build.rs | 8 -------- crates/contracts/src/alloy.rs | 17 +++++++++++++++++ crates/contracts/src/lib.rs | 5 ----- crates/driver/src/infra/liquidity/config.rs | 4 +++- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 15ffbf5245..7b83a04cd2 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -589,14 +589,6 @@ fn main() { generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); - generate_contract_with_config("PancakeRouter", |builder| { - builder - .add_network_str(MAINNET, "0xEfF92A263d31888d860bD50809A8D171709b7b1c") - .add_network_str(ARBITRUM_ONE, "0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb") - .add_network_str(BASE, "0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb") - .add_network_str(BNB, "0x10ED43C718714eb63d5aA57B78B54704E256024E") - // Not available on Lens - }); generate_contract_with_config("SushiSwapRouter", |builder| { // builder diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 674dae6734..0d1c5865de 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -331,6 +331,19 @@ crate::bindings!( GNOSIS => (address!("0x1C232F01118CB8B424793ae03F870aa7D0ac7f77")) } ); +crate::bindings!( + PancakeRouter, + crate::deployments! { + // + MAINNET => (address!("0xEfF92A263d31888d860bD50809A8D171709b7b1c")), + // + ARBITRUM_ONE => (address!("0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb")), + // + BASE => (address!("0x8cFe327CEc66d1C090Dd72bd0FF11d690C33a2Eb")), + // + BNB => (address!("0x10ED43C718714eb63d5aA57B78B54704E256024E")) + } +); pub use alloy::providers::DynProvider as Provider; @@ -510,6 +523,10 @@ mod tests { fn test_has_address() { assert!(BaoswapRouter::deployment_address(&GNOSIS).is_some()); assert!(HoneyswapRouter::deployment_address(&GNOSIS).is_some()); + + for chain_id in &[MAINNET, ARBITRUM_ONE, BASE, BNB] { + assert!(PancakeRouter::deployment_address(chain_id).is_some()); + } } #[test] diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 93c76163f9..613a35868f 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -74,7 +74,6 @@ include_contracts! { IUniswapLikePair; IUniswapLikeRouter; IUniswapV3Factory; - PancakeRouter; Permit2; SushiSwapRouter; SwaprRouter; @@ -213,10 +212,6 @@ mod tests { ); } - for network in &[MAINNET, ARBITRUM_ONE] { - assert_has_deployment_address!(PancakeRouter for *network); - } - assert!(alloy::BalancerV2WeightedPoolFactory::deployment_address(&MAINNET).is_some()); for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index fc1173d476..3c03a482cb 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -105,7 +105,9 @@ impl UniswapV2 { } .into(); Some(Self { - router: deployment_address(contracts::PancakeRouter::raw_contract(), chain)?, + router: ContractAddress::from( + contracts::alloy::PancakeRouter::deployment_address(&chain.id())?.into_legacy(), + ), pool_code, missing_pool_cache_time: Duration::from_secs(60 * 60), }) From 230e8c5bec951123caa3ed5083c7fee3e8544b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Wed, 17 Sep 2025 11:25:09 +0100 Subject: [PATCH 035/112] Migrate SushiSwapRouter to alloy (#3666) --- crates/contracts/build.rs | 14 -------- crates/contracts/src/alloy.rs | 36 +++++++++++++++++++++ crates/contracts/src/lib.rs | 2 -- crates/driver/src/infra/liquidity/config.rs | 9 +++--- crates/shared/src/sources/uniswap_v2/mod.rs | 13 +++++--- 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 7b83a04cd2..ee98b59d4a 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -589,20 +589,6 @@ fn main() { generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); - generate_contract_with_config("SushiSwapRouter", |builder| { - // - builder - .add_network_str(MAINNET, "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F") - .add_network_str(GOERLI, "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506") - .add_network_str(GNOSIS, "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506") - .add_network_str(ARBITRUM_ONE, "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506") - .add_network_str(BASE, "0x6BDED42c6DA8FBf0d2bA55B2fa120C5e0c8D7891") - .add_network_str(AVALANCHE, "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506") - .add_network_str(BNB, "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506") - .add_network_str(OPTIMISM, "0x2ABf469074dc0b54d793850807E6eb5Faf2625b1") - .add_network_str(POLYGON, "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506") - // Not available on Lens - }); generate_contract_with_config("SwaprRouter", |builder| { // builder diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 0d1c5865de..62d7751483 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -344,6 +344,29 @@ crate::bindings!( BNB => (address!("0x10ED43C718714eb63d5aA57B78B54704E256024E")) } ); +crate::bindings!( + SushiSwapRouter, + // + crate::deployments! { + // + MAINNET => (address!("0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f")), + // + GNOSIS => (address!("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506")), + // + ARBITRUM_ONE => (address!("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506")), + // + BASE => (address!("0x6bded42c6da8fbf0d2ba55b2fa120c5e0c8d7891")), + // + AVALANCHE => (address!("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506")), + // + BNB => (address!("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506")), + // + OPTIMISM => (address!("0x2abf469074dc0b54d793850807e6eb5faf2625b1")), + // + POLYGON => (address!("0x1b02da8cb0d097eb8d57a175b88c7d8b47997506")), + // Not available on Lens + } +); pub use alloy::providers::DynProvider as Provider; @@ -527,6 +550,19 @@ mod tests { for chain_id in &[MAINNET, ARBITRUM_ONE, BASE, BNB] { assert!(PancakeRouter::deployment_address(chain_id).is_some()); } + + for chain_id in &[ + MAINNET, + GNOSIS, + ARBITRUM_ONE, + BASE, + AVALANCHE, + BNB, + OPTIMISM, + POLYGON, + ] { + assert!(SushiSwapRouter::deployment_address(chain_id).is_some()); + } } #[test] diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 613a35868f..b96112f9d5 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -75,7 +75,6 @@ include_contracts! { IUniswapLikeRouter; IUniswapV3Factory; Permit2; - SushiSwapRouter; SwaprRouter; TestnetUniswapV2Router02; UniswapV2Factory; @@ -185,7 +184,6 @@ mod tests { assert_has_deployment_address!(CowProtocolToken for *network); } for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { - assert_has_deployment_address!(SushiSwapRouter for *network); assert_has_deployment_address!(UniswapV2Factory for *network); assert_has_deployment_address!(UniswapV2Router02 for *network); } diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index 3c03a482cb..27ab1e53f2 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -9,7 +9,7 @@ use { ethrpc::alloy::conversions::IntoLegacy, hex_literal::hex, reqwest::Url, - shared::sources::uniswap_v2::{BAOSWAP_INIT, HONEYSWAP_INIT}, + shared::sources::uniswap_v2::{BAOSWAP_INIT, HONEYSWAP_INIT, SUSHISWAP_INIT}, std::{collections::HashSet, time::Duration}, }; @@ -68,9 +68,10 @@ impl UniswapV2 { /// Returns the liquidity configuration for SushiSwap. pub fn sushi_swap(chain: Chain) -> Option { Some(Self { - router: deployment_address(contracts::SushiSwapRouter::raw_contract(), chain)?, - pool_code: hex!("e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303") - .into(), + router: ContractAddress::from( + contracts::alloy::SushiSwapRouter::deployment_address(&chain.id())?.into_legacy(), + ), + pool_code: SUSHISWAP_INIT.into(), missing_pool_cache_time: Duration::from_secs(60 * 60), }) } diff --git a/crates/shared/src/sources/uniswap_v2/mod.rs b/crates/shared/src/sources/uniswap_v2/mod.rs index 113d6cdaa0..6d1bdac3aa 100644 --- a/crates/shared/src/sources/uniswap_v2/mod.rs +++ b/crates/shared/src/sources/uniswap_v2/mod.rs @@ -82,11 +82,14 @@ impl UniV2BaselineSourceParameters { pool_reading: PoolReadingStyle::Default, }); } - BS::SushiSwap => Some(( - contracts::SushiSwapRouter::raw_contract(), - SUSHISWAP_INIT, - PoolReadingStyle::Default, - )), + BS::SushiSwap => { + return Some(Self { + router: contracts::alloy::SushiSwapRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: SUSHISWAP_INIT.into(), + pool_reading: PoolReadingStyle::Default, + }); + } BS::Swapr => Some(( contracts::SwaprRouter::raw_contract(), SWAPR_INIT, From 8852dd54415a92e987b78244763f8452d0e215f8 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Wed, 17 Sep 2025 12:47:38 +0200 Subject: [PATCH 036/112] Retry mechanism for settlement indexing (#3661) # Description Recently Haris reported that a few settlements were not fully indexed on base. The investigation showed that there were temporary nodes issues that caused the RPC requests to time out. Due to the lack of a retry mechanism we aborted the indexing process of these auctions fully. # Changes - moved logic to aggregate the necessary settlement data into a separate function - replaced `Result` with `Result` to make it clearer in the caller what happened - propagated all temporary errors (DB or RPC issues) as retryable errors ## How to test not clear - the error cases are very tedious to mock --- crates/autopilot/src/domain/settlement/mod.rs | 4 + .../src/domain/settlement/observer.rs | 145 ++++++++++-------- crates/autopilot/src/infra/persistence/mod.rs | 3 +- 3 files changed, 90 insertions(+), 62 deletions(-) diff --git a/crates/autopilot/src/domain/settlement/mod.rs b/crates/autopilot/src/domain/settlement/mod.rs index c7f25fe643..37a73e29b9 100644 --- a/crates/autopilot/src/domain/settlement/mod.rs +++ b/crates/autopilot/src/domain/settlement/mod.rs @@ -58,6 +58,10 @@ pub struct Settlement { } impl Settlement { + pub fn auction_id(&self) -> i64 { + self.auction.id + } + pub fn solver(&self) -> eth::Address { self.solver } diff --git a/crates/autopilot/src/domain/settlement/observer.rs b/crates/autopilot/src/domain/settlement/observer.rs index 2e097abfb4..0cba79ffc1 100644 --- a/crates/autopilot/src/domain/settlement/observer.rs +++ b/crates/autopilot/src/domain/settlement/observer.rs @@ -12,10 +12,14 @@ use { crate::{ - domain::settlement::{self}, + domain::{ + eth, + settlement::{self, Settlement}, + }, infra, }, - anyhow::{Result, anyhow}, + anyhow::{Context, Result, anyhow}, + std::time::Duration, }; #[derive(Clone)] @@ -24,6 +28,12 @@ pub struct Observer { persistence: infra::Persistence, } +enum IndexSuccess { + NothingToDo, + IndexedSettlement, + SkippedInvalidTransaction, +} + impl Observer { /// Creates a new Observer and asynchronously schedules the first update /// run. @@ -35,38 +45,73 @@ impl Observer { /// This needs to get called after indexing a new settlement event /// since this code needs that data to already be present in the DB. pub async fn update(&self) { - loop { + const MAX_RETRIES: usize = 5; + let mut attempts = 0; + while attempts < MAX_RETRIES { match self.single_update().await { - Ok(true) => { + Ok(IndexSuccess::IndexedSettlement) => { tracing::debug!("on settlement event updater ran and processed event"); - // There might be more pending updates, continue immediately. - continue; } - Ok(false) => { + Ok(IndexSuccess::SkippedInvalidTransaction) => { + tracing::warn!("stored default values for unindexable transaction"); + } + Ok(IndexSuccess::NothingToDo) => { tracing::debug!("on settlement event updater ran without update"); - break; + return; } Err(err) => { - tracing::error!(?err, "on settlement event update task failed"); - break; + tracing::debug!(?err, "encountered retryable error"); + // wait a little to give temporary errors a chance to resolve themselves + const TEMP_ERROR_BACK_OFF: Duration = Duration::from_millis(100); + tokio::time::sleep(TEMP_ERROR_BACK_OFF).await; + attempts += 1; + continue; } } + + // everything worked fine -> reset our attempts for the next settlement + attempts = 0; } } /// Update database for settlement events that have not been processed yet. /// /// Returns whether an update was performed. - async fn single_update(&self) -> Result { + async fn single_update(&self) -> Result { // Find a settlement event that has not been processed yet. - let Some(event) = self.persistence.get_settlement_without_auction().await? else { - return Ok(false); + let Some(event) = self + .persistence + .get_settlement_without_auction() + .await + .context("failed to fetch unprocessed tx from DB")? + else { + return Ok(IndexSuccess::NothingToDo); }; - tracing::debug!(tx = ?event.transaction, "updating settlement details"); + tracing::debug!(tx = ?event.transaction, "found unprocessed settlement"); + + let settlement_data = self + .fetch_auction_data_for_transaction(event.transaction) + .await?; + self.persistence + .save_settlement(event, settlement_data.as_ref()) + .await + .context("failed to update settlement")?; + + match settlement_data { + None => Ok(IndexSuccess::SkippedInvalidTransaction), + Some(_) => Ok(IndexSuccess::IndexedSettlement), + } + } - // Reconstruct the settlement transaction based on the transaction hash - let transaction = match self.eth.transaction(event.transaction).await { + /// Inspects the calldata of the transaction, decodes the arguments, and + /// finds off-chain data associated with it based on the attached auction_id + /// bytes. + async fn fetch_auction_data_for_transaction( + &self, + tx: eth::TxId, + ) -> Result> { + let transaction = match self.eth.transaction(tx).await { Ok(transaction) => { let separator = self.eth.contracts().settlement_domain_separator(); let settlement_contract = self.eth.contracts().settlement().address().into(); @@ -79,74 +124,52 @@ impl Observer { .await } Err(err) => { - tracing::warn!(hash = ?event.transaction, ?err, "no tx found"); - return Ok(false); + return Err(anyhow!(format!( + "node could not find the transaction - tx: {tx:?}, err: {err:?}", + ))); } }; - // Build the association - let (auction_id, settlement) = match transaction { + match transaction { Ok(transaction) => { let auction_id = transaction.auction_id; - let settlement = match settlement::Settlement::new( - transaction, - &self.persistence, - self.eth.chain(), - ) - .await + match settlement::Settlement::new(transaction, &self.persistence, self.eth.chain()) + .await { - Ok(settlement) => Some(settlement), - Err(err) if retryable(&err) => return Err(err.into()), + Ok(settlement) => Ok(Some(settlement)), + Err(settlement::Error::Infra(err)) => { + // bubble up retryable error + Err(err) + } Err(err) => { - tracing::warn!(hash = ?event.transaction, ?auction_id, ?err, "invalid settlement"); - None + tracing::warn!(?tx, ?auction_id, ?err, "invalid settlement"); + Ok(None) } - }; - (auction_id, settlement) + } } Err(err) => { match err { settlement::transaction::Error::MissingCalldata => { - tracing::error!(hash = ?event.transaction, ?err, "invalid settlement transaction") + tracing::error!(?tx, ?err, "invalid settlement transaction"); + Ok(None) } settlement::transaction::Error::MissingAuctionId | settlement::transaction::Error::Decoding(_) | settlement::transaction::Error::SignatureRecover(_) | settlement::transaction::Error::OrderUidRecover(_) | settlement::transaction::Error::MissingSolver => { - tracing::warn!(hash = ?event.transaction, ?err, "invalid settlement transaction") + tracing::warn!(?tx, ?err, "invalid settlement transaction"); + Ok(None) } settlement::transaction::Error::Authentication(_) => { - tracing::warn!(hash = ?event.transaction, ?err, "failed authentication") + // This has to be a temporary error because the settlement contract + // guarantees that SOME allow listed contract executed the transaction. + Err(anyhow!(format!( + "could not determing solver address - err: {err:?}" + ))) } } - // default values so we don't get stuck on invalid settlement transactions - (0.into(), None) } - }; - - tracing::debug!(hash = ?event.transaction, ?auction_id, "saving settlement details for tx"); - - if let Err(err) = self - .persistence - .save_settlement(event, auction_id, settlement.as_ref()) - .await - { - return Err(anyhow!( - "save settlement: {:?}, {auction_id}, {err}", - event.transaction - )); } - - Ok(true) - } -} - -/// Whether Observer loop should retry on the given error. -fn retryable(err: &settlement::Error) -> bool { - match err { - settlement::Error::Infra(_) => true, - settlement::Error::InconsistentData(_) => false, - settlement::Error::WrongEnvironment => false, } } diff --git a/crates/autopilot/src/infra/persistence/mod.rs b/crates/autopilot/src/infra/persistence/mod.rs index b8f3ae1dd0..42ea473bc1 100644 --- a/crates/autopilot/src/infra/persistence/mod.rs +++ b/crates/autopilot/src/infra/persistence/mod.rs @@ -637,7 +637,6 @@ impl Persistence { pub async fn save_settlement( &self, event: domain::eth::SettlementEvent, - auction_id: domain::auction::Id, settlement: Option<&domain::settlement::Settlement>, ) -> Result<(), DatabaseError> { let _timer = Metrics::get() @@ -649,6 +648,8 @@ impl Persistence { let block_number = i64::try_from(event.block.0).context("block overflow")?; let log_index = i64::try_from(event.log_index).context("log index overflow")?; + let auction_id = settlement.map(|s| s.auction_id()).unwrap_or_default(); + tracing::debug!(hash = ?event.transaction, ?auction_id, "saving settlement details for tx"); database::settlements::update_settlement_auction( &mut ex, From 692fbd41a4e32c75b148ecbd5661bd51985d7f11 Mon Sep 17 00:00:00 2001 From: Marcin Szymczak Date: Wed, 17 Sep 2025 14:48:56 +0200 Subject: [PATCH 037/112] Add Justfile for common tasks (#3635) # Description `just` is a popular command runner often used throughout Rust projects to automate common tasks. The README.md specifies a couple of maintenance actions that developer can take (format, clippy, various testing, local database setup, etc.). The Justfile automates them. # Changes - Added Justfile ## How to test Run each of the provided commands. Verify that they match what is present in README. --------- Co-authored-by: ilya --- .github/workflows/pull-request.yaml | 42 ++++++++++++++++++++++------- Justfile | 41 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 Justfile diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 0529f2eb6f..afdcdfbacf 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -20,14 +20,17 @@ jobs: profile: minimal toolchain: nightly components: rustfmt + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 - run: | rustup --version rustup show cargo --version cargo +nightly fmt --version cargo clippy --version - - run: cargo +nightly fmt --all -- --check - - run: cargo clippy --locked --workspace --all-features --all-targets -- -D warnings + - run: just fmt --check + - run: just clippy trivy: runs-on: ubuntu-latest @@ -66,9 +69,12 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 - run: rustup toolchain install stable --profile minimal - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - - run: cargo test --doc + - run: just test-doc unit-tests: timeout-minutes: 60 @@ -85,8 +91,11 @@ jobs: - run: rustup toolchain install stable --profile minimal - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - uses: taiki-e/install-action@e4767ccc6762bc4347ef2275c75ea77f5f36e27f # nextest + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 - run: cargo build --tests - - run: cargo nextest run + - run: just test-unit test-db: timeout-minutes: 60 @@ -114,7 +123,10 @@ jobs: with: file: docker-compose.yaml up-opts: migrations --abort-on-container-failure - - run: cargo nextest run postgres -p orderbook -p database -p autopilot --test-threads 1 --run-ignored ignored-only + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 + - run: just test-db test-local-node: timeout-minutes: 60 @@ -144,7 +156,10 @@ jobs: with: file: docker-compose.yaml up-opts: migrations --abort-on-container-failure - - run: cargo nextest run -p e2e local_node --test-threads 1 --failure-output final --run-ignored ignored-only + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 + - run: just test-e2e-local test-forked-node: # Do not run this job on forks since some secrets are required for it. @@ -184,7 +199,10 @@ jobs: with: file: docker-compose.yaml up-opts: migrations --abort-on-container-failure - - run: cargo nextest run -p e2e forked_node --test-threads 1 --run-ignored ignored-only --failure-output final + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 + - run: just test-e2e-forked test-driver: timeout-minutes: 60 @@ -203,10 +221,13 @@ jobs: - uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0 - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 - uses: taiki-e/install-action@e4767ccc6762bc4347ef2275c75ea77f5f36e27f # nextest + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 # Build the driver's tests. - run: cargo build -p driver --tests # Don't spawn any docker containers. The driver's tests will spawn anvil itself. - - run: RUST_MIN_STACK=3145728 cargo nextest run -p driver --test-threads 1 --run-ignored ignored-only + - run: just test-driver openapi: timeout-minutes: 60 @@ -255,12 +276,15 @@ jobs: with: file: docker-compose.yaml up-opts: migrations --abort-on-container-failure + - uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff + with: + just-version: 1.39.0 - name: Run test in a loop run: | attempt=1 while true; do echo "Running test attempt #$attempt" - if ! cargo nextest run -p e2e forked_node_mainnet_repay_debt_with_collateral_of_safe --test-threads 1 --failure-output final --run-ignored ignored-only; then + if ! just test-e2e forked_node_mainnet_repay_debt_with_collateral_of_safe; then exit 1 fi attempt=$((attempt+1)) diff --git a/Justfile b/Justfile new file mode 100644 index 0000000000..46a0a62b69 --- /dev/null +++ b/Justfile @@ -0,0 +1,41 @@ +help: + @just --list + +# Run unit tests +test-unit: + cargo nextest run + +# Run doc tests +test-doc: + cargo test --doc + +# Run database tests +test-db: + cargo nextest run postgres --test-threads 1 --run-ignored ignored-only + +# Run End-to-end tests on local node (this machine) +test-e2e-local: (test-e2e "local_node") + +# Run End-to-end tests on forked node +# +# FORK_URL_MAINNET and FORK_URL_GNOSIS env variables have to be provided. +test-e2e-forked: (test-e2e "forked_node") + +# Run End-to-end tests with custom filters +test-e2e *filters: + cargo nextest run -p e2e {{filters}} --test-threads 1 --failure-output final --run-ignored ignored-only + +test-driver: + RUST_MIN_STACK=3145728 cargo nextest run -p driver --test-threads 1 --run-ignored ignored-only + +# Run clippy +clippy: + cargo clippy --locked --workspace --all-features --all-targets -- -D warnings + +# Format the repository +fmt *extra: + cargo +nightly fmt --all -- {{extra}} + +# Start database for E2E tests +start-db: + docker compose up -d From f4b54491c74872d06ec471fdce4488f30fe4c81d Mon Sep 17 00:00:00 2001 From: Marcin Szymczak Date: Wed, 17 Sep 2025 15:15:10 +0200 Subject: [PATCH 038/112] Mention `just` commands in README (#3669) # Description Updates the README to mention `just` commands that are used throughout the CI instead of the Github Actions source code directly. # Changes CI commands mentioned in README. ## How to test N/A --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 178625ae3b..c64a50d9e7 100644 --- a/README.md +++ b/README.md @@ -63,15 +63,16 @@ You can read more about the services available and their respective ports in the ## Testing The CI (check [`.github/workflows/pull-request.yaml`](.github/workflows/pull-request.yaml)) runs -[doc-tests](https://github.com/cowprotocol/services/tree/main/.github/workflows/pull-request.yaml#L71), -[unit tests](https://github.com/cowprotocol/services/tree/main/.github/workflows/pull-request.yaml#L88-L89), -[DB tests](https://github.com/cowprotocol/services/tree/main/.github/workflows/pull-request.yaml#L117), -[E2E tests with a local node](https://github.com/cowprotocol/services/tree/main/.github/workflows/pull-request.yaml#L147), -[E2E tests with a forked node](https://github.com/cowprotocol/services/tree/main/.github/workflows/pull-request.yaml#L187) and -[driver tests](https://github.com/cowprotocol/services/tree/main/.github/workflows/pull-request.yaml#L206-L209) (and more). -The CI system uses [cargo-nextest](https://nexte.st/) and therefor all tests are getting verified by it. +- doc tests: `just test-doc` +- unit tests: `just test-unit` +- DB tests: `just test-db` +- E2E tests with a local node: `just test-e2e-local` +- E2E tests with a forked node: `just test-e2e-forked` +- driver tests: `just test-driver` + +The CI system uses [cargo-nextest](https://nexte.st/) and therefore all tests are getting verified by it. `cargo-nextest` and `cargo test` handle global state slightly differently which can cause some tests to fail with `cargo test`. -That's why it's recommended to run tests with `cargo nextest run`. +That's why it's recommended to run tests with the provided `just` commands. ### Flaky Tests From 3cc18398d03c05e38d16acac3e72d8bff0bb38b1 Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 18 Sep 2025 11:12:59 +0300 Subject: [PATCH 039/112] Support multiple alloy sc events (#3660) --- crates/e2e/tests/e2e/protocol_fee.rs | 2 +- crates/shared/src/event_handling.rs | 105 +++++++++++++----- .../balancer_v2/pool_fetching/pool_storage.rs | 15 ++- .../balancer_v2/pool_fetching/registry.rs | 22 +++- .../src/sources/balancer_v2/pools/common.rs | 4 +- 5 files changed, 107 insertions(+), 41 deletions(-) diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index 6e332985aa..d498cc48e1 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -367,7 +367,7 @@ async fn combined_protocol_fees(web3: Web3) { .balanceOf(onchain.contracts().gp_settlement.address().into_alloy()) .call() .await - .map(|balance| balance.into_legacy()) + .map(IntoLegacy::into_legacy) }), ) .await diff --git a/crates/shared/src/event_handling.rs b/crates/shared/src/event_handling.rs index c7c58f7eb0..216bf9c5de 100644 --- a/crates/shared/src/event_handling.rs +++ b/crates/shared/src/event_handling.rs @@ -1,6 +1,10 @@ use { crate::maintenance::Maintaining, - alloy::{contract::Event, providers::DynProvider, rpc::types::Log, sol_types::SolEvent}, + alloy::{ + providers::{DynProvider, Provider}, + rpc::types::{Filter, Log}, + sol_types::SolEventInterface, + }, anyhow::{Context, Result}, ethcontract::{EventMetadata, dyns::DynAllEventsBuilder}, ethrpc::{ @@ -8,7 +12,6 @@ use { block_stream::{BlockNumberHash, BlockRetrieving, RangeInclusive}, }, futures::{Stream, StreamExt, TryStreamExt, future}, - itertools::Itertools, primitive_types::H256, std::{pin::Pin, sync::Arc}, tokio::sync::Mutex, @@ -95,9 +98,12 @@ pub trait EthcontractEventRetrieving { } pub trait AlloyEventRetrieving { - type Event: SolEvent + Send + Sync + 'static; + type Event: SolEventInterface + Send + Sync + 'static; + + /// Returns a filter that is used to query for the events of interest. + fn filter(&self) -> Filter; - fn get_events(&self) -> Event<&DynProvider, Self::Event>; + fn provider(&self) -> &DynProvider; } /// A thin wrapper to avoid conflicts with EthcontractEventRetrieving @@ -109,45 +115,86 @@ pub struct AlloyEventRetriever(pub T); #[async_trait::async_trait] impl EventRetrieving for AlloyEventRetriever where - T: AlloyEventRetrieving + Send + Sync + 'static, + T: AlloyEventRetrieving + Send + Sync, { type Event = (T::Event, Log); async fn get_events_by_block_hash(&self, block_hash: H256) -> Result> { - self.0 - .get_events() - .at_block_hash(block_hash.into_alloy()) - .query() + let filter = self.0.filter().at_block_hash(block_hash.into_alloy()); + let events = self + .0 + .provider() + .get_logs(&filter) .await - .map_err(anyhow::Error::from) + .map_err(anyhow::Error::from)? + .into_iter() + .filter_map(|log| { + let event = T::Event::decode_log(&log.inner).ok()?; + Some((event.data, log)) + }) + .collect(); + + Ok(events) } + // Unfortunately, alloy's `watch_logs` does not support pagination yet, so it + // is implemented manually here async fn get_events_by_block_range( &self, block_range: &RangeInclusive, ) -> Result> { - let stream = self - .0 - .get_events() - .from_block(*block_range.start()) - .to_block(*block_range.end()) - .watch() - .await - .map_err(anyhow::Error::from)? - .into_stream() - .map_err(anyhow::Error::from); + const CHUNK_SIZE: usize = 500; + + let start = *block_range.start(); + let end = *block_range.end(); + let provider = self.0.provider().clone(); + let base_filter = self.0.filter(); + + let stream = futures::stream::iter(start..=end) + .chunks(CHUNK_SIZE) + .then(move |range| { + let provider = provider.clone(); + let filter = base_filter.clone(); + + async move { + let Some((start, end)) = range.first().zip(range.last()) else { + return Ok(Vec::new()); + }; + let filter = filter.from_block(*start).to_block(*end); + let logs = provider.get_logs(&filter).await.with_context(|| { + format!("unable to get logs for blocks range {}-{}", start, end) + })?; + + let events: Vec> = logs + .into_iter() + .map(|log| { + T::Event::decode_log(&log.inner) + .with_context(|| format!("unable to parse log: {:?}", log)) + .map(|event| (event.data, log)) + }) + .collect(); + + Ok(events) + } + }) + .flat_map(|chunk_result| { + futures::stream::iter(match chunk_result { + Ok(events) => events, + Err(e) => vec![Err(e)], + }) + }); Ok(Box::pin(stream)) } fn address(&self) -> Vec
{ self.0 - .get_events() - .filter + .filter() .address .iter() - .map(|addr| addr.into_legacy()) - .collect_vec() + .copied() + .map(IntoLegacy::into_legacy) + .collect() } } @@ -204,7 +251,7 @@ pub trait EventRetrieving { block_range: &RangeInclusive, ) -> Result>; - fn address(&self) -> Vec; + fn address(&self) -> Vec
; } #[derive(Debug)] @@ -891,28 +938,28 @@ mod tests { H256::from_str( "0xa21ba3de6ac42185aa2b21e37cd63ff1572b763adff7e828f86590df1d1be118", ) - .unwrap(), + .unwrap(), ), ( 15575560, H256::from_str( "0x5a737331194081e99b73d7a8b7a2ccff84e0aff39fa0e39aca0b660f3d6694c4", ) - .unwrap(), + .unwrap(), ), ( 15575561, H256::from_str( "0xe91ec1a5a795c0739d99a60ac1df37cdf90b6c75c8150ace1cbad5b21f473b75", //WRONG HASH! ) - .unwrap(), + .unwrap(), ), ( 15575562, H256::from_str( "0xac1ca15622f17c62004de1f746728d4051103d8b7e558d39fd9fcec4d3348937", ) - .unwrap(), + .unwrap(), ), ]; let event_handler = EventHandler::new( diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs index 1094d1a605..a18de4f5a6 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/pool_storage.rs @@ -22,7 +22,10 @@ use { }, alloy::rpc::types::Log, anyhow::{Context, Result}, - contracts::alloy::BalancerV2BasePoolFactory::BalancerV2BasePoolFactory::PoolCreated, + contracts::alloy::BalancerV2BasePoolFactory::BalancerV2BasePoolFactory::{ + BalancerV2BasePoolFactoryEvents, + PoolCreated, + }, ethcontract::{H160, H256}, ethrpc::{alloy::conversions::IntoLegacy, block_stream::RangeInclusive}, model::TokenPair, @@ -181,13 +184,13 @@ where } #[async_trait::async_trait] -impl EventStoring<(PoolCreated, Log)> for PoolStorage +impl EventStoring<(BalancerV2BasePoolFactoryEvents, Log)> for PoolStorage where Factory: FactoryIndexing, { async fn replace_events( &mut self, - events: Vec<(PoolCreated, Log)>, + events: Vec<(BalancerV2BasePoolFactoryEvents, Log)>, range: RangeInclusive, ) -> Result<()> { tracing::debug!("replacing {} events for block {:?}", events.len(), range); @@ -196,10 +199,14 @@ where self.append_events(events).await } - async fn append_events(&mut self, events: Vec<(PoolCreated, Log)>) -> Result<()> { + async fn append_events( + &mut self, + events: Vec<(BalancerV2BasePoolFactoryEvents, Log)>, + ) -> Result<()> { tracing::debug!("inserting {} events", events.len()); for (event, log) in events { + let BalancerV2BasePoolFactoryEvents::PoolCreated(event) = event; let block_created = log.block_number.context("event missing block number")?; self.index_pool_creation(event, block_created).await?; diff --git a/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs b/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs index e9b0ee06c6..d599b44168 100644 --- a/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs +++ b/crates/shared/src/sources/balancer_v2/pool_fetching/registry.rs @@ -12,7 +12,12 @@ use { pools::{FactoryIndexing, Pool, PoolStatus, common::PoolInfoFetching}, }, }, - alloy::{contract::Event, providers::DynProvider, rpc::types::Log}, + BalancerV2BasePoolFactory::BalancerV2BasePoolFactory::BalancerV2BasePoolFactoryEvents, + alloy::{ + providers::DynProvider, + rpc::types::{Filter, Log}, + sol_types::SolEvent, + }, anyhow::Result, contracts::{ alloy::{ @@ -24,6 +29,7 @@ use { ethcontract::{BlockId, H256, errors::MethodError}, ethrpc::block_stream::{BlockNumberHash, BlockRetrieving}, futures::future, + maplit::hashset, model::TokenPair, std::{collections::HashSet, sync::Arc}, tokio::sync::Mutex, @@ -33,10 +39,16 @@ pub struct BasePoolFactoryContract(BalancerV2BasePoolFactory::Instance); #[async_trait::async_trait] impl AlloyEventRetrieving for BasePoolFactoryContract { - type Event = PoolCreated; + type Event = BalancerV2BasePoolFactoryEvents; + + fn provider(&self) -> &DynProvider { + self.0.provider() + } - fn get_events(&self) -> Event<&DynProvider, Self::Event> { - self.0.PoolCreated_filter() + fn filter(&self) -> Filter { + Filter::new() + .event_signature(hashset![PoolCreated::SIGNATURE_HASH]) + .address(*self.0.address()) } } @@ -45,7 +57,7 @@ type PoolUpdater = Mutex< EventHandler< AlloyEventRetriever, PoolStorage, - (PoolCreated, Log), + (BalancerV2BasePoolFactoryEvents, Log), >, >; diff --git a/crates/shared/src/sources/balancer_v2/pools/common.rs b/crates/shared/src/sources/balancer_v2/pools/common.rs index 8092ecd103..265aa9468e 100644 --- a/crates/shared/src/sources/balancer_v2/pools/common.rs +++ b/crates/shared/src/sources/balancer_v2/pools/common.rs @@ -96,7 +96,7 @@ impl PoolInfoFetcher { .await? .tokens .into_iter() - .map(|token| token.into_legacy()) + .map(IntoLegacy::into_legacy) .collect::>(); let scaling_factors = self.scaling_factors(&tokens).await?; @@ -158,7 +158,7 @@ impl PoolInfoFetcher { let tokens = pool_tokens .tokens .into_iter() - .map(|t| t.into_legacy()) + .map(IntoLegacy::into_legacy) .collect::>(); ensure!(pool.tokens == tokens, "pool token mismatch"); let tokens = itertools::izip!(&pool.tokens, balances, &pool.scaling_factors) From 8f949c4cf5995a55dd478d070d5738b2ab2ed97b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 18 Sep 2025 09:52:11 +0100 Subject: [PATCH 040/112] Migrate SwaprRouter to alloy (#3667) --- crates/contracts/build.rs | 8 -------- crates/contracts/src/alloy.rs | 17 +++++++++++++++++ crates/contracts/src/lib.rs | 5 ----- crates/driver/src/infra/liquidity/config.rs | 9 +++++---- crates/shared/src/sources/uniswap_v2/mod.rs | 13 ++++++++----- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index ee98b59d4a..4cc2917d8f 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -589,14 +589,6 @@ fn main() { generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); - generate_contract_with_config("SwaprRouter", |builder| { - // - builder - .add_network_str(MAINNET, "0xb9960d9bca016e9748be75dd52f02188b9d0829f") - .add_network_str(GNOSIS, "0xE43e60736b1cb4a75ad25240E2f9a62Bff65c0C0") - .add_network_str(ARBITRUM_ONE, "0x530476d5583724A89c8841eB6Da76E7Af4C0F17E") - // Not available on Base and Lens - }); generate_contract("ISwaprPair"); generate_contract_with_config("UniswapV2Factory", |builder| { // diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 62d7751483..dda61c12f9 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -367,6 +367,19 @@ crate::bindings!( // Not available on Lens } ); +crate::bindings!( + SwaprRouter, + // + crate::deployments! { + // + MAINNET => address!("0xb9960d9bca016e9748be75dd52f02188b9d0829f"), + // + GNOSIS => address!("0xE43e60736b1cb4a75ad25240E2f9a62Bff65c0C0"), + // + ARBITRUM_ONE => address!("0x530476d5583724A89c8841eB6Da76E7Af4C0F17E"), + // Not available on Base and Lens + } +); pub use alloy::providers::DynProvider as Provider; @@ -563,6 +576,10 @@ mod tests { ] { assert!(SushiSwapRouter::deployment_address(chain_id).is_some()); } + + for chain_id in &[MAINNET, GNOSIS, ARBITRUM_ONE] { + assert!(SwaprRouter::deployment_address(chain_id).is_some()); + } } #[test] diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index b96112f9d5..1d618d13fc 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -75,7 +75,6 @@ include_contracts! { IUniswapLikeRouter; IUniswapV3Factory; Permit2; - SwaprRouter; TestnetUniswapV2Router02; UniswapV2Factory; UniswapV2Router02; @@ -216,10 +215,6 @@ mod tests { assert!(alloy::BalancerV2StablePoolFactoryV2::deployment_address(network).is_some()); } - for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { - assert_has_deployment_address!(SwaprRouter for *network); - } - // only sepolia assert_has_deployment_address!(TestnetUniswapV2Router02 for SEPOLIA); } diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index 27ab1e53f2..5382f4ce0c 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -9,7 +9,7 @@ use { ethrpc::alloy::conversions::IntoLegacy, hex_literal::hex, reqwest::Url, - shared::sources::uniswap_v2::{BAOSWAP_INIT, HONEYSWAP_INIT, SUSHISWAP_INIT}, + shared::sources::uniswap_v2::{BAOSWAP_INIT, HONEYSWAP_INIT, SUSHISWAP_INIT, SWAPR_INIT}, std::{collections::HashSet, time::Duration}, }; @@ -144,9 +144,10 @@ impl Swapr { #[allow(clippy::self_named_constructors)] pub fn swapr(chain: Chain) -> Option { Some(Self { - router: deployment_address(contracts::SwaprRouter::raw_contract(), chain)?, - pool_code: hex!("d306a548755b9295ee49cc729e13ca4a45e00199bbd890fa146da43a50571776") - .into(), + router: ContractAddress::from( + contracts::alloy::SwaprRouter::deployment_address(&chain.id())?.into_legacy(), + ), + pool_code: SWAPR_INIT.into(), missing_pool_cache_time: Duration::from_secs(60 * 60), }) } diff --git a/crates/shared/src/sources/uniswap_v2/mod.rs b/crates/shared/src/sources/uniswap_v2/mod.rs index 6d1bdac3aa..fcd9cd1785 100644 --- a/crates/shared/src/sources/uniswap_v2/mod.rs +++ b/crates/shared/src/sources/uniswap_v2/mod.rs @@ -90,11 +90,14 @@ impl UniV2BaselineSourceParameters { pool_reading: PoolReadingStyle::Default, }); } - BS::Swapr => Some(( - contracts::SwaprRouter::raw_contract(), - SWAPR_INIT, - PoolReadingStyle::Swapr, - )), + BS::Swapr => { + return Some(Self { + router: contracts::alloy::SwaprRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: SWAPR_INIT.into(), + pool_reading: PoolReadingStyle::Swapr, + }); + } BS::TestnetUniswapV2 => Some(( contracts::TestnetUniswapV2Router02::raw_contract(), TESTNET_UNISWAP_INIT, From 4d39195f7e79f8617d7220826d1484ae6b90ef75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 18 Sep 2025 10:04:30 +0100 Subject: [PATCH 041/112] Migrate ISwaprPair to alloy (#3670) --- crates/contracts/build.rs | 1 - crates/contracts/src/alloy.rs | 1 + crates/contracts/src/errors.rs | 5 ++++ crates/contracts/src/lib.rs | 1 - crates/shared/src/sources/swapr.rs | 38 +++++++++++++++++++++--------- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 4cc2917d8f..36b685822a 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -589,7 +589,6 @@ fn main() { generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); - generate_contract("ISwaprPair"); generate_contract_with_config("UniswapV2Factory", |builder| { // builder diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index dda61c12f9..7488aa3833 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -380,6 +380,7 @@ crate::bindings!( // Not available on Base and Lens } ); +crate::bindings!(ISwaprPair); pub use alloy::providers::DynProvider as Provider; diff --git a/crates/contracts/src/errors.rs b/crates/contracts/src/errors.rs index 6eb7c907e6..01b33f6e28 100644 --- a/crates/contracts/src/errors.rs +++ b/crates/contracts/src/errors.rs @@ -57,6 +57,11 @@ pub fn testing_contract_error() -> MethodError { } } +/// Create an arbitrary alloy error. Useful for testing. +pub fn testing_alloy_contract_error() -> alloy::contract::Error { + alloy::contract::Error::NotADeploymentTransaction +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 1d618d13fc..141d59fd1c 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -70,7 +70,6 @@ include_contracts! { HooksTrampoline; IAavePool; IFlashLoanSolverWrapper; - ISwaprPair; IUniswapLikePair; IUniswapLikeRouter; IUniswapV3Factory; diff --git a/crates/shared/src/sources/swapr.rs b/crates/shared/src/sources/swapr.rs index 1543cdea07..f1ed9f203e 100644 --- a/crates/shared/src/sources/swapr.rs +++ b/crates/shared/src/sources/swapr.rs @@ -1,10 +1,12 @@ //! A pool state reading implementation specific to Swapr. use { - crate::sources::uniswap_v2::pool_fetching::{self, DefaultPoolReader, Pool, PoolReading}, + crate::sources::uniswap_v2::pool_fetching::{DefaultPoolReader, Pool, PoolReading}, + alloy::sol_types::GenericContractError, anyhow::Result, - contracts::ISwaprPair, - ethcontract::{BlockId, errors::MethodError}, + contracts::alloy::ISwaprPair, + ethcontract::BlockId, + ethrpc::alloy::conversions::IntoAlloy, futures::{FutureExt as _, future::BoxFuture}, model::TokenPair, num::rational::Ratio, @@ -22,13 +24,14 @@ const FEE_BASE: u32 = 10_000; impl PoolReading for SwaprPoolReader { fn read_state(&self, pair: TokenPair, block: BlockId) -> BoxFuture<'_, Result>> { let pair_address = self.0.pair_provider.pair_address(&pair); - let pair_contract = ISwaprPair::at(&self.0.web3, pair_address); - let fetch_pool = self.0.read_state(pair, block); - let fetch_fee = pair_contract.swap_fee().block(block).call(); async move { - let (pool, fee) = futures::join!(fetch_pool, fetch_fee); + let pair_contract = + ISwaprPair::Instance::new(pair_address.into_alloy(), self.0.web3.alloy.clone()); + let fetch_fee = pair_contract.swapFee().block(block.into_alloy()); + + let (pool, fee) = futures::join!(fetch_pool, fetch_fee.call().into_future()); handle_results(pool, fee) } .boxed() @@ -37,9 +40,22 @@ impl PoolReading for SwaprPoolReader { fn handle_results( pool: Result>, - fee: Result, + fee: Result, ) -> Result> { - let fee = pool_fetching::handle_contract_error(fee)?; + use alloy::contract::Error; + let fee = match fee { + Ok(fee) => Some(fee), + // Alloy "hides" the contract execution errors under the transport error + Err(err @ Error::TransportError(_)) => { + // So we need to try to decode the error as a generic contract error, + // we return in case it isn't a contract error + match err.try_decode_into_interface_error::() { + Ok(_) => None, // contract error + Err(err) => return Err(err)?, + } + } + Err(_) => None, + }; Ok(pool?.and_then(|pool| { Some(Pool { fee: Ratio::new(fee?, FEE_BASE), @@ -57,7 +73,7 @@ mod tests { recent_block_cache::Block, sources::{BaselineSource, uniswap_v2}, }, - contracts::errors::testing_contract_error, + contracts::errors::testing_alloy_contract_error, ethcontract::H160, maplit::hashset, }; @@ -94,7 +110,7 @@ mod tests { assert!( handle_results( Ok(Some(Pool::uniswap(address, tokens, (0, 0)))), - Err(testing_contract_error()), + Err(testing_alloy_contract_error()), ) .unwrap() .is_none() From 0d273bef5eb4881af14b6019cf659ccf11bc878b Mon Sep 17 00:00:00 2001 From: ilya Date: Thu, 18 Sep 2025 17:16:15 +0300 Subject: [PATCH 042/112] Filter UniV3 LPs based on raw liquidity (#3677) --- crates/shared/src/sources/uniswap_v3/graph_api.rs | 11 +---------- crates/shared/src/sources/uniswap_v3/pool_fetching.rs | 8 +++----- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/crates/shared/src/sources/uniswap_v3/graph_api.rs b/crates/shared/src/sources/uniswap_v3/graph_api.rs index 52dc383f76..f936f8cde3 100644 --- a/crates/shared/src/sources/uniswap_v3/graph_api.rs +++ b/crates/shared/src/sources/uniswap_v3/graph_api.rs @@ -43,7 +43,6 @@ const ALL_POOLS_QUERY: &str = r#" liquidity sqrtPrice tick - totalValueLockedETH } } "#; @@ -75,7 +74,6 @@ const POOLS_BY_IDS_QUERY: &str = r#" liquidity sqrtPrice tick - totalValueLockedETH } } "#; @@ -125,7 +123,7 @@ impl UniV3SubgraphClient { .paginated_query(query, variables) .await? .into_iter() - .filter(|pool: &PoolData| pool.total_value_locked_eth.is_normal()) + .filter(|pool: &PoolData| pool.liquidity > U256::zero()) .collect()) } @@ -254,9 +252,6 @@ pub struct PoolData { pub sqrt_price: U256, #[serde_as(as = "DisplayFromStr")] pub tick: BigInt, - #[serde_as(as = "DisplayFromStr")] - #[serde(rename = "totalValueLockedETH")] - pub total_value_locked_eth: f64, pub ticks: Option>, } @@ -344,7 +339,6 @@ mod tests { "feeTier": "10000", "liquidity": "303015134493562686441", "tick": "-92110", - "totalValueLockedETH": "1.0", "sqrtPrice": "792216481398733702759960397" }, { @@ -362,7 +356,6 @@ mod tests { "feeTier": "3000", "liquidity": "3125586395511534995", "tick": "-189822", - "totalValueLockedETH": "1.0", "sqrtPrice": "5986323062404391218190509" } ], @@ -387,7 +380,6 @@ mod tests { sqrt_price: U256::from_dec_str("792216481398733702759960397").unwrap(), tick: BigInt::from(-92110), ticks: None, - total_value_locked_eth: 1.0 }, PoolData { id: H160::from_str("0x0002e63328169d7feea121f1e32e4f620abf0352").unwrap(), @@ -406,7 +398,6 @@ mod tests { sqrt_price: U256::from_dec_str("5986323062404391218190509").unwrap(), tick: BigInt::from(-189822), ticks: None, - total_value_locked_eth: 1.0 }, ], } diff --git a/crates/shared/src/sources/uniswap_v3/pool_fetching.rs b/crates/shared/src/sources/uniswap_v3/pool_fetching.rs index 8fa943d8d5..b256a651cb 100644 --- a/crates/shared/src/sources/uniswap_v3/pool_fetching.rs +++ b/crates/shared/src/sources/uniswap_v3/pool_fetching.rs @@ -155,11 +155,9 @@ impl PoolsCheckpointHandler { // can't fetch the state of all pools in constructor for performance reasons, // so let's fetch the top `max_pools_to_initialize_cache` pools with the highest // liquidity - registered_pools.pools.sort_unstable_by(|a, b| { - a.total_value_locked_eth - .partial_cmp(&b.total_value_locked_eth) - .unwrap() - }); + registered_pools + .pools + .sort_unstable_by(|a, b| a.liquidity.partial_cmp(&b.liquidity).unwrap()); let pool_ids = registered_pools .pools .clone() From 3dc5f4f7e8b4dcdaa629bca4fea827ad2ea2b50f Mon Sep 17 00:00:00 2001 From: Marcin Szymczak Date: Thu, 18 Sep 2025 16:45:03 +0200 Subject: [PATCH 043/112] Update swapr.rs to satisfy clippy (#3678) # Description CI was failing because of using modulo == 0 instead of .is_multiple_of(...). # Changes Used .is_multiple_of(...) --- crates/driver/src/boundary/liquidity/swapr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/driver/src/boundary/liquidity/swapr.rs b/crates/driver/src/boundary/liquidity/swapr.rs index e8492b2c32..843404d032 100644 --- a/crates/driver/src/boundary/liquidity/swapr.rs +++ b/crates/driver/src/boundary/liquidity/swapr.rs @@ -19,7 +19,7 @@ const GAS_PER_SWAP: u64 = 90_171; pub fn to_domain(id: liquidity::Id, pool: ConstantProductOrder) -> Result { // invalid Swapr fee ratio; does not have exact BPS representation anyhow::ensure!( - (pool.fee.numer() * BPS_BASE) % pool.fee.denom() == 0, + (pool.fee.numer() * BPS_BASE).is_multiple_of(*pool.fee.denom()), "invalid Swapr fee ratio; does not have exact BPS representation", ); From c18374178347266b5f198714972837b48978f741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Thu, 18 Sep 2025 20:00:15 +0100 Subject: [PATCH 044/112] Migrate TestnetUniswapV2Router02 to alloy (#3673) --- crates/contracts/build.rs | 7 ------- crates/contracts/src/alloy.rs | 9 +++++++++ crates/contracts/src/lib.rs | 4 ---- crates/driver/src/infra/liquidity/config.rs | 16 ++++++++++++---- crates/shared/src/sources/uniswap_v2/mod.rs | 15 ++++++++++----- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 36b685822a..d19e094852 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -688,13 +688,6 @@ fn main() { .add_network_str(BASE, "0xc694a91e6b071bF030A18BD3053A7fE09B6DaE69") // Not available on Lens }); - - // Unofficial Uniswap v2 liquidity on the Sepolia testnet. - generate_contract_with_config("TestnetUniswapV2Router02", |builder| { - // - builder.add_network_str(SEPOLIA, "0x86dcd3293C53Cf8EFd7303B57beb2a3F671dDE98") - }); - generate_contract("CowAmm"); generate_contract_with_config("CowAmmConstantProductFactory", |builder| { builder diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 7488aa3833..3ecdcf36c4 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -381,6 +381,13 @@ crate::bindings!( } ); crate::bindings!(ISwaprPair); +crate::bindings!( + TestnetUniswapV2Router02, + crate::deployments! { + // + SEPOLIA => address!("0x86dcd3293C53Cf8EFd7303B57beb2a3F671dDE98"), + } +); pub use alloy::providers::DynProvider as Provider; @@ -581,6 +588,8 @@ mod tests { for chain_id in &[MAINNET, GNOSIS, ARBITRUM_ONE] { assert!(SwaprRouter::deployment_address(chain_id).is_some()); } + + assert!(TestnetUniswapV2Router02::deployment_address(&SEPOLIA).is_some()); } #[test] diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 141d59fd1c..259272bb75 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -74,7 +74,6 @@ include_contracts! { IUniswapLikeRouter; IUniswapV3Factory; Permit2; - TestnetUniswapV2Router02; UniswapV2Factory; UniswapV2Router02; UniswapV3Pool; @@ -213,9 +212,6 @@ mod tests { for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { assert!(alloy::BalancerV2StablePoolFactoryV2::deployment_address(network).is_some()); } - - // only sepolia - assert_has_deployment_address!(TestnetUniswapV2Router02 for SEPOLIA); } #[test] diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index 5382f4ce0c..e8b1cae32f 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -9,7 +9,13 @@ use { ethrpc::alloy::conversions::IntoLegacy, hex_literal::hex, reqwest::Url, - shared::sources::uniswap_v2::{BAOSWAP_INIT, HONEYSWAP_INIT, SUSHISWAP_INIT, SWAPR_INIT}, + shared::sources::uniswap_v2::{ + BAOSWAP_INIT, + HONEYSWAP_INIT, + SUSHISWAP_INIT, + SWAPR_INIT, + TESTNET_UNISWAP_INIT, + }, std::{collections::HashSet, time::Duration}, }; @@ -118,9 +124,11 @@ impl UniswapV2 { /// test networks. pub fn testnet_uniswapv2(chain: Chain) -> Option { Some(Self { - router: deployment_address(contracts::TestnetUniswapV2Router02::raw_contract(), chain)?, - pool_code: hex!("0efd7612822d579e24a8851501d8c2ad854264a1050e3dfcee8afcca08f80a86") - .into(), + router: ContractAddress::from( + contracts::alloy::TestnetUniswapV2Router02::deployment_address(&chain.id())? + .into_legacy(), + ), + pool_code: TESTNET_UNISWAP_INIT.into(), missing_pool_cache_time: Duration::from_secs(60 * 60), }) } diff --git a/crates/shared/src/sources/uniswap_v2/mod.rs b/crates/shared/src/sources/uniswap_v2/mod.rs index fcd9cd1785..1865d0795f 100644 --- a/crates/shared/src/sources/uniswap_v2/mod.rs +++ b/crates/shared/src/sources/uniswap_v2/mod.rs @@ -98,11 +98,16 @@ impl UniV2BaselineSourceParameters { pool_reading: PoolReadingStyle::Swapr, }); } - BS::TestnetUniswapV2 => Some(( - contracts::TestnetUniswapV2Router02::raw_contract(), - TESTNET_UNISWAP_INIT, - PoolReadingStyle::Default, - )), + BS::TestnetUniswapV2 => { + return Some(Self { + router: contracts::alloy::TestnetUniswapV2Router02::deployment_address( + &chain_id, + ) + .map(IntoLegacy::into_legacy)?, + init_code_digest: TESTNET_UNISWAP_INIT.into(), + pool_reading: PoolReadingStyle::Default, + }); + } BS::Baoswap => { return Some(Self { router: contracts::alloy::BaoswapRouter::deployment_address(&chain_id) From f0ceb39a48d4624640b648d150ac89926a0be092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 19 Sep 2025 09:32:29 +0100 Subject: [PATCH 045/112] Migrate UniswapV2Factory to alloy (#3681) --- crates/contracts/build.rs | 15 ------ crates/contracts/src/alloy.rs | 39 +++++++++++++++ crates/contracts/src/lib.rs | 2 - crates/driver/src/tests/setup/blockchain.rs | 33 ++++++------ crates/e2e/src/setup/deploy.rs | 16 ++++-- .../e2e/src/setup/onchain_components/mod.rs | 50 +++++++++++-------- crates/e2e/tests/e2e/cow_amm.rs | 20 +++----- crates/e2e/tests/e2e/limit_orders.rs | 30 +++++------ .../tests/e2e/partially_fillable_balance.rs | 15 +++--- .../e2e/tests/e2e/partially_fillable_pool.rs | 15 +++--- crates/e2e/tests/e2e/protocol_fee.rs | 15 +++--- crates/e2e/tests/e2e/replace_order.rs | 45 +++++++++-------- .../tests/e2e/solver_participation_guard.rs | 15 +++--- 13 files changed, 175 insertions(+), 135 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index d19e094852..e4e27f2f1f 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -589,21 +589,6 @@ fn main() { generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); - generate_contract_with_config("UniswapV2Factory", |builder| { - // - builder - .add_network_str(MAINNET, "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f") - .add_network_str(GOERLI, "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f") - .add_network_str(GNOSIS, "0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7") - .add_network_str(ARBITRUM_ONE, "0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9") - .add_network_str(BASE, "0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6") - .add_network_str(SEPOLIA, "0xF62c03E08ada871A0bEb309762E260a7a6a880E6") - .add_network_str(AVALANCHE, "0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C") - .add_network_str(BNB, "0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6") - .add_network_str(OPTIMISM, "0x0c3c1c532F1e39EdF36BE9Fe0bE1410313E074Bf") - .add_network_str(POLYGON, "0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C") - // Not available on Lens - }); generate_contract_with_config("UniswapV2Router02", |builder| { // builder diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 3ecdcf36c4..82b0dfa4c1 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -388,6 +388,31 @@ crate::bindings!( SEPOLIA => address!("0x86dcd3293C53Cf8EFd7303B57beb2a3F671dDE98"), } ); +crate::bindings!( + UniswapV2Factory, + // + crate::deployments! { + // + MAINNET => address!("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), + // + GNOSIS => address!("0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7"), + // + ARBITRUM_ONE => address!("0xf1D7CC64Fb4452F05c498126312eBE29f30Fbcf9"), + // + BASE => address!("0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6"), + // + SEPOLIA => address!("0xF62c03E08ada871A0bEb309762E260a7a6a880E6"), + // + AVALANCHE => address!("0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C"), + // + BNB => address!("0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6"), + // + OPTIMISM => address!("0x0c3c1c532F1e39EdF36BE9Fe0bE1410313E074Bf"), + // + POLYGON => address!("0x9e5A52f57b3038F1B8EeE45F28b3C1967e22799C"), + // Not available on Lens + } +); pub use alloy::providers::DynProvider as Provider; @@ -590,6 +615,20 @@ mod tests { } assert!(TestnetUniswapV2Router02::deployment_address(&SEPOLIA).is_some()); + + for chain_id in &[ + MAINNET, + GNOSIS, + ARBITRUM_ONE, + BASE, + SEPOLIA, + AVALANCHE, + BNB, + OPTIMISM, + POLYGON, + ] { + assert!(UniswapV2Factory::deployment_address(chain_id).is_some()); + } } #[test] diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 259272bb75..7e3f751ff5 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -74,7 +74,6 @@ include_contracts! { IUniswapLikeRouter; IUniswapV3Factory; Permit2; - UniswapV2Factory; UniswapV2Router02; UniswapV3Pool; UniswapV3QuoterV2; @@ -181,7 +180,6 @@ mod tests { assert_has_deployment_address!(CowProtocolToken for *network); } for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { - assert_has_deployment_address!(UniswapV2Factory for *network); assert_has_deployment_address!(UniswapV2Router02 for *network); } for network in &[ diff --git a/crates/driver/src/tests/setup/blockchain.rs b/crates/driver/src/tests/setup/blockchain.rs index 7aea616855..5cc3bcf01e 100644 --- a/crates/driver/src/tests/setup/blockchain.rs +++ b/crates/driver/src/tests/setup/blockchain.rs @@ -439,15 +439,16 @@ impl Blockchain { } } // Create the uniswap factory. - let uniswap_factory = wait_for( - &web3, - contracts::UniswapV2Factory::builder(&web3, main_trader_account.address()) - .from(main_trader_account.clone()) - .deploy(), + let contract_address = contracts::alloy::UniswapV2Factory::Instance::deploy_builder( + web3.alloy.clone(), + main_trader_account.address().into_alloy(), ) + .from(main_trader_account.address().into_alloy()) + .deploy() .await .unwrap(); - + let uniswap_factory = + contracts::alloy::UniswapV2Factory::Instance::new(contract_address, web3.alloy.clone()); // Create and fund a uniswap pair for each pool. Fund the settlement contract // with the same liquidity as the pool, to allow for internalized interactions. let mut pairs = Vec::new(); @@ -472,23 +473,21 @@ impl Blockchain { .into_legacy() }; // Create the pair. - wait_for( - &web3, - uniswap_factory - .create_pair(token_a, token_b) - .from(main_trader_account.clone()) - .send(), - ) - .await - .unwrap(); + uniswap_factory + .createPair(token_a.into_alloy(), token_b.into_alloy()) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); // Fund the pair and the settlement contract. let pair = contracts::IUniswapLikePair::at( &web3, uniswap_factory - .get_pair(token_a, token_b) + .getPair(token_a.into_alloy(), token_b.into_alloy()) .call() .await - .unwrap(), + .unwrap() + .into_legacy(), ); pairs.push(Pair { token_a: pool.reserve_a.token, diff --git a/crates/e2e/src/setup/deploy.rs b/crates/e2e/src/setup/deploy.rs index 6e1f79b5b4..50bda43374 100644 --- a/crates/e2e/src/setup/deploy.rs +++ b/crates/e2e/src/setup/deploy.rs @@ -11,12 +11,13 @@ use { GPv2AllowListAuthentication, GPv2Settlement, HooksTrampoline, - UniswapV2Factory, UniswapV2Router02, WETH9, + alloy::{InstanceExt, UniswapV2Factory}, support::{Balances, Signatures}, }, ethcontract::{Address, H256, U256, errors::DeployError}, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, model::DomainSeparator, shared::ethrpc::Web3, }; @@ -34,7 +35,7 @@ pub struct Contracts { pub signatures: Signatures, pub gp_authenticator: GPv2AllowListAuthentication, pub balances: Balances, - pub uniswap_v2_factory: UniswapV2Factory, + pub uniswap_v2_factory: UniswapV2Factory::Instance, pub uniswap_v2_router: UniswapV2Router02, pub weth: WETH9, pub allowance: Address, @@ -94,7 +95,9 @@ impl Contracts { .expect("Couldn't parse network ID to u64"), balancer_vault: BalancerV2Vault::deployed(web3).await.unwrap(), gp_authenticator: GPv2AllowListAuthentication::deployed(web3).await.unwrap(), - uniswap_v2_factory: UniswapV2Factory::deployed(web3).await.unwrap(), + uniswap_v2_factory: UniswapV2Factory::Instance::deployed(&web3.alloy) + .await + .unwrap(), uniswap_v2_router: UniswapV2Router02::deployed(web3).await.unwrap(), weth: WETH9::deployed(web3).await.unwrap(), allowance: gp_settlement @@ -147,10 +150,13 @@ impl Contracts { ) ); - let uniswap_v2_factory = deploy!(web3, UniswapV2Factory(accounts[0])); + let uniswap_v2_factory = + UniswapV2Factory::Instance::deploy(web3.alloy.clone(), accounts[0].into_alloy()) + .await + .unwrap(); let uniswap_v2_router = deploy!( web3, - UniswapV2Router02(uniswap_v2_factory.address(), weth.address()) + UniswapV2Router02(uniswap_v2_factory.address().into_legacy(), weth.address()) ); let gp_authenticator = deploy!(web3, GPv2AllowListAuthentication); diff --git a/crates/e2e/src/setup/onchain_components/mod.rs b/crates/e2e/src/setup/onchain_components/mod.rs index eaa570c715..97f6230819 100644 --- a/crates/e2e/src/setup/onchain_components/mod.rs +++ b/crates/e2e/src/setup/onchain_components/mod.rs @@ -460,13 +460,16 @@ impl OnchainComponents { .unwrap(); tx_value!(minter, weth_amount, self.contracts.weth.deposit()); - tx!( - minter, - self.contracts.uniswap_v2_factory.create_pair( - contract.address().into_legacy(), - self.contracts.weth.address() + self.contracts + .uniswap_v2_factory + .createPair( + *contract.address(), + self.contracts.weth.address().into_alloy(), ) - ); + .from(minter.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); contract .approve( @@ -509,13 +512,13 @@ impl OnchainComponents { asset_a.0.mint(lp.address(), asset_a.1).await; asset_b.0.mint(lp.address(), asset_b.1).await; - tx!( - lp, - self.contracts.uniswap_v2_factory.create_pair( - asset_a.0.address().into_legacy(), - asset_b.0.address().into_legacy() - ) - ); + self.contracts + .uniswap_v2_factory + .createPair(*asset_a.0.address(), *asset_b.0.address()) + .from(lp.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); asset_a .0 @@ -561,10 +564,11 @@ impl OnchainComponents { &self.web3, self.contracts .uniswap_v2_factory - .get_pair(self.contracts.weth.address(), token.address().into_legacy()) + .getPair(self.contracts.weth.address().into_alloy(), *token.address()) .call() .await - .expect("failed to get Uniswap V2 pair"), + .expect("failed to get Uniswap V2 pair") + .into_legacy(), ); assert!(!pair.address().is_zero(), "Uniswap V2 pair is not deployed"); @@ -621,12 +625,16 @@ impl OnchainComponents { tx_value!(holder, weth_amount, self.contracts.weth.deposit()); - tx!( - holder, - self.contracts - .uniswap_v2_factory - .create_pair(cow.address(), self.contracts.weth.address()) - ); + self.contracts + .uniswap_v2_factory + .createPair( + cow.address().into_alloy(), + self.contracts.weth.address().into_alloy(), + ) + .from(holder.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); tx!( holder, cow.approve(self.contracts.uniswap_v2_router.address(), cow_amount) diff --git a/crates/e2e/tests/e2e/cow_amm.rs b/crates/e2e/tests/e2e/cow_amm.rs index 44675828f7..be00292282 100644 --- a/crates/e2e/tests/e2e/cow_amm.rs +++ b/crates/e2e/tests/e2e/cow_amm.rs @@ -112,9 +112,9 @@ async fn cow_amm_jit(web3: Web3) { let pair = onchain .contracts() .uniswap_v2_factory - .get_pair( - onchain.contracts().weth.address(), - dai.address().into_legacy(), + .getPair( + onchain.contracts().weth.address().into_alloy(), + *dai.address(), ) .call() .await @@ -131,9 +131,7 @@ async fn cow_amm_jit(web3: Web3) { .unwrap(); // pad with 12 zeros in the front to end up with 32 bytes - let oracle_data: Vec<_> = std::iter::repeat_n(0u8, 12) - .chain(pair.as_bytes().to_vec()) - .collect(); + let oracle_data: Vec<_> = std::iter::repeat_n(0u8, 12).chain(pair.to_vec()).collect(); const APP_DATA: [u8; 32] = [12u8; 32]; cow_amm_factory @@ -738,9 +736,9 @@ async fn cow_amm_opposite_direction(web3: Web3) { let pair = onchain .contracts() .uniswap_v2_factory - .get_pair( - onchain.contracts().weth.address(), - dai.address().into_legacy(), + .getPair( + onchain.contracts().weth.address().into_alloy(), + *dai.address(), ) .call() .await @@ -757,9 +755,7 @@ async fn cow_amm_opposite_direction(web3: Web3) { .unwrap(); // pad with 12 zeros to end up with 32 bytes - let oracle_data: Vec<_> = std::iter::repeat_n(0u8, 12) - .chain(pair.as_bytes().to_vec()) - .collect(); + let oracle_data: Vec<_> = std::iter::repeat_n(0u8, 12).chain(pair.to_vec()).collect(); const APP_DATA: [u8; 32] = [12u8; 32]; // Create the CoW AMM diff --git a/crates/e2e/tests/e2e/limit_orders.rs b/crates/e2e/tests/e2e/limit_orders.rs index ade6bd6ea3..9daf3b4513 100644 --- a/crates/e2e/tests/e2e/limit_orders.rs +++ b/crates/e2e/tests/e2e/limit_orders.rs @@ -114,13 +114,14 @@ async fn single_limit_order_test(web3: Web3) { // Create and fund Uniswap pool token_a.mint(solver.address(), to_wei(1000)).await; token_b.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( @@ -239,13 +240,14 @@ async fn two_limit_orders_test(web3: Web3) { token_b.mint(solver.address(), to_wei(1_000)).await; // Create and fund Uniswap pool - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( diff --git a/crates/e2e/tests/e2e/partially_fillable_balance.rs b/crates/e2e/tests/e2e/partially_fillable_balance.rs index 3c7723247c..c6b81aa984 100644 --- a/crates/e2e/tests/e2e/partially_fillable_balance.rs +++ b/crates/e2e/tests/e2e/partially_fillable_balance.rs @@ -36,13 +36,14 @@ async fn test(web3: Web3) { token_a.mint(solver.address(), to_wei(1000)).await; token_b.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( diff --git a/crates/e2e/tests/e2e/partially_fillable_pool.rs b/crates/e2e/tests/e2e/partially_fillable_pool.rs index 0b08272bcd..228d79ef7e 100644 --- a/crates/e2e/tests/e2e/partially_fillable_pool.rs +++ b/crates/e2e/tests/e2e/partially_fillable_pool.rs @@ -35,13 +35,14 @@ async fn test(web3: Web3) { token_a.mint(solver.address(), to_wei(1000)).await; token_b.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index d498cc48e1..9900056325 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -644,13 +644,14 @@ async fn volume_fee_buy_order_test(web3: Web3) { // Create and fund Uniswap pool token_gno.mint(solver.address(), to_wei(1000)).await; token_dai.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_gno.address().into_legacy(), - token_dai.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_gno.address(), *token_dai.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_gno .approve( diff --git a/crates/e2e/tests/e2e/replace_order.rs b/crates/e2e/tests/e2e/replace_order.rs index 88eb575cd9..727c01754d 100644 --- a/crates/e2e/tests/e2e/replace_order.rs +++ b/crates/e2e/tests/e2e/replace_order.rs @@ -57,13 +57,14 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { // Create and fund Uniswap pool token_a.mint(solver.address(), to_wei(1000)).await; token_b.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( @@ -239,13 +240,14 @@ async fn try_replace_someone_else_order_test(web3: Web3) { // Create and fund Uniswap pool token_a.mint(solver.address(), to_wei(1000)).await; token_b.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( @@ -378,13 +380,14 @@ async fn single_replace_order_test(web3: Web3) { // Create and fund Uniswap pool token_a.mint(solver.address(), to_wei(1000)).await; token_b.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( diff --git a/crates/e2e/tests/e2e/solver_participation_guard.rs b/crates/e2e/tests/e2e/solver_participation_guard.rs index 2211bd4f4f..6840f87ec8 100644 --- a/crates/e2e/tests/e2e/solver_participation_guard.rs +++ b/crates/e2e/tests/e2e/solver_participation_guard.rs @@ -256,13 +256,14 @@ async fn setup( // Create and fund Uniswap pool token_a.mint(solver.address(), to_wei(1000)).await; token_b.mint(solver.address(), to_wei(1000)).await; - tx!( - solver.account(), - onchain.contracts().uniswap_v2_factory.create_pair( - token_a.address().into_legacy(), - token_b.address().into_legacy() - ) - ); + onchain + .contracts() + .uniswap_v2_factory + .createPair(*token_a.address(), *token_b.address()) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); token_a .approve( From d65c1cee1bf02a865d2ace6c5f202a2aef0c90d5 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 19 Sep 2025 10:49:56 +0200 Subject: [PATCH 046/112] Implement alloy based gas price estimator (#3659) # Description Recently there was an issue where our gas price estimator reported a suggested price that was ~250 times the actual price at the time. Quotes generated during that time effectively had a significantly reduced buy amount (to fit the supposedly very high gas cost into the quote). Market orders created from these incorrectly reduced quoted amounts had a very high surplus (when solvers settled them with the correct gas price of the time). This then led to the protocol capturing an absurd amount of protocol fees from these orders. # Changes Implemented a new `alloy` gas price estimator that just delegates to `alloy`'s gas price estimation logic. At the time of opening the PR it mostly bases the suggested gas price off of the 20th percentile of transaction rewards over the last 10 blocks. ## How to test I was able to reproduce the issue by setting up some temporary test code (not checked in) which just uses the `Native` price estimator connected to a locally running `anvil` node forked off of block `23332697`. When I checked how `alloy`'s gas price estimation would have behaved for that block it reported a value that was appropriate for the time. --- crates/driver/src/infra/blockchain/gas.rs | 2 + crates/driver/src/infra/config/file/mod.rs | 1 + .../shared/src/gas_price_estimation/alloy.rs | 83 +++++++++++++++++++ crates/shared/src/gas_price_estimation/mod.rs | 12 ++- 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 crates/shared/src/gas_price_estimation/alloy.rs diff --git a/crates/driver/src/infra/blockchain/gas.rs b/crates/driver/src/infra/blockchain/gas.rs index 0efca2937d..76ed302dac 100644 --- a/crates/driver/src/infra/blockchain/gas.rs +++ b/crates/driver/src/infra/blockchain/gas.rs @@ -15,6 +15,7 @@ use { GasPriceEstimating, nativegasestimator::{NativeGasEstimator, Params}, }, + shared::gas_price_estimation::alloy::AlloyGasPriceEstimator, std::{sync::Arc, time::Duration}, }; @@ -54,6 +55,7 @@ impl GasPriceEstimator { .map_err(Error::GasPrice)?, ), GasEstimatorType::Web3 => Arc::new(web3.legacy.clone()), + GasEstimatorType::Alloy => Arc::new(AlloyGasPriceEstimator::new(web3.alloy.clone())), }; let additional_tip = mempools .iter() diff --git a/crates/driver/src/infra/config/file/mod.rs b/crates/driver/src/infra/config/file/mod.rs index 2b52c316ac..098ec097db 100644 --- a/crates/driver/src/infra/config/file/mod.rs +++ b/crates/driver/src/infra/config/file/mod.rs @@ -728,6 +728,7 @@ pub enum GasEstimatorType { max_block_percentile: f64, }, Web3, + Alloy, } impl Default for GasEstimatorType { diff --git a/crates/shared/src/gas_price_estimation/alloy.rs b/crates/shared/src/gas_price_estimation/alloy.rs new file mode 100644 index 0000000000..db4e25aa2a --- /dev/null +++ b/crates/shared/src/gas_price_estimation/alloy.rs @@ -0,0 +1,83 @@ +//! Uses `alloy`'s logic for suggesting reasonable EIP-1559 +//! gas price values. It computes the suggested gas price +//! based on the 20th percentile of fee rewards for +//! transactions of the last 10 blocks. +//! See +//! for the implementation details. + +use { + alloy::{consensus::BlockHeader, providers::Provider}, + anyhow::{Context, Result}, + ethrpc::AlloyProvider, + futures::TryFutureExt, + gas_estimation::{GasPrice1559, GasPriceEstimating}, + std::{ops::Mul, time::Duration}, + tracing::instrument, +}; + +pub struct AlloyGasPriceEstimator(AlloyProvider); + +impl AlloyGasPriceEstimator { + pub fn new(provider: AlloyProvider) -> Self { + Self(provider) + } +} + +#[async_trait::async_trait] +impl GasPriceEstimating for AlloyGasPriceEstimator { + #[instrument(skip(self))] + async fn estimate_with_limits( + &self, + _gas_limit: f64, + _time_limit: Duration, + ) -> Result { + let estimate_fees = self + .0 + .estimate_eip1559_fees() + .map_err(|err| anyhow::anyhow!("could not estimate EIP 1559 fees: {err:?}")); + let get_block = self + .0 + .get_block(alloy::eips::BlockId::Number( + alloy::eips::BlockNumberOrTag::Latest, + )) + .into_future() + .map_err(|err| anyhow::anyhow!("could not fetch latest block: {err:?}")); + let (fees, block) = tokio::try_join!(estimate_fees, get_block)?; + + /// `Alloy`'s constant growth factor to estimate the base_fee + /// of the next block. () + const MAX_GAS_PRICE_INCREASE_PER_BLOCK: f64 = 2.; + + let base_fee_per_gas = u64_to_f64( + block + .context("latest block is missing")? + .into_consensus_header() + .base_fee_per_gas() + .context("no base_fee_per_gas")?, + ) + .context("could not convert base_fee_per_gas to f64")? + .mul(MAX_GAS_PRICE_INCREASE_PER_BLOCK); + + Ok(GasPrice1559 { + base_fee_per_gas, + max_fee_per_gas: u128_to_f64(fees.max_fee_per_gas) + .context("could not convert max_fee_per_gas to f64")?, + max_priority_fee_per_gas: u128_to_f64(fees.max_priority_fee_per_gas) + .context("could not convert max_priority_fee_per_gas to f64")?, + }) + } +} + +fn u128_to_f64(val: u128) -> Result { + if val > 2u128.pow(f64::MANTISSA_DIGITS) { + anyhow::bail!(format!("could not convert u128 to f64: {val}")); + } + Ok(val as f64) +} + +fn u64_to_f64(val: u64) -> Result { + if val > 2u64.pow(f64::MANTISSA_DIGITS) { + anyhow::bail!(format!("could not convert u64 to f64: {val}")); + } + Ok(val as f64) +} diff --git a/crates/shared/src/gas_price_estimation/mod.rs b/crates/shared/src/gas_price_estimation/mod.rs index 7e80ce29b3..750c96c5e0 100644 --- a/crates/shared/src/gas_price_estimation/mod.rs +++ b/crates/shared/src/gas_price_estimation/mod.rs @@ -1,8 +1,13 @@ +pub mod alloy; pub mod driver; pub mod fake; use { - crate::{ethrpc::Web3, http_client::HttpClientFactory}, + crate::{ + ethrpc::Web3, + gas_price_estimation::alloy::AlloyGasPriceEstimator, + http_client::HttpClientFactory, + }, anyhow::Result, gas_estimation::{ GasPriceEstimating, @@ -20,6 +25,7 @@ pub enum GasEstimatorType { Web3, Native, Driver(Url), + Alloy, } impl FromStr for GasEstimatorType { @@ -28,6 +34,7 @@ impl FromStr for GasEstimatorType { fn from_str(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { "web3" => Ok(GasEstimatorType::Web3), + "alloy" => Ok(GasEstimatorType::Alloy), "native" => Ok(GasEstimatorType::Native), _ => Url::parse(s).map(GasEstimatorType::Driver).map_err(|e| { format!("expected 'web3', 'native', or a valid driver URL; got {s:?}: {e}") @@ -55,6 +62,9 @@ pub async fn create_priority_estimator( ))); } GasEstimatorType::Web3 => estimators.push(Box::new(web3.legacy.clone())), + GasEstimatorType::Alloy => { + estimators.push(Box::new(AlloyGasPriceEstimator::new(web3.alloy.clone()))) + } GasEstimatorType::Native => { match NativeGasEstimator::new(web3.transport().clone(), None).await { Ok(estimator) => estimators.push(Box::new(estimator)), From 8ab5c5c6cb2c6acbeb5bf2e7c377d2be03c5e8fd Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 19 Sep 2025 12:02:05 +0200 Subject: [PATCH 047/112] Move state overrides into ethcontract (#3671) # Description Adjusts the code base to make use of `.call_with_state_overrides` that was introduced in https://github.com/cowprotocol/ethcontract-rs/pull/983. # Changes - remove `StateOverrides` types from our code base because they are now in ethcontract-rs - replace `Arc` with `Web3` - use `.call_with_state_overrides` instead of weird extension trait on web3 - migrated module declaration from `.rs` to `/mod.rs` to be more consistent with the remaining code base ## How to test tests of balance overriding logic should still work --- Cargo.lock | 10 ++-- Cargo.toml | 6 +-- crates/ethrpc/src/extensions.rs | 47 +++++-------------- crates/shared/src/code_simulation.rs | 8 ++-- crates/shared/src/price_estimation/factory.rs | 2 +- .../balance_overrides/detector.rs | 40 +++++++--------- .../mod.rs} | 8 ++-- .../mod.rs} | 4 +- 8 files changed, 47 insertions(+), 78 deletions(-) rename crates/shared/src/price_estimation/trade_verifier/{balance_overrides.rs => balance_overrides/mod.rs} (98%) rename crates/shared/src/price_estimation/{trade_verifier.rs => trade_verifier/mod.rs} (99%) diff --git a/Cargo.lock b/Cargo.lock index 63fe1cffe5..510dfe6a02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2736,7 +2736,7 @@ dependencies = [ [[package]] name = "ethcontract" version = "0.25.9" -source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=c057a6e78827d762e1827c8d57cb687fc1f0af1a#c057a6e78827d762e1827c8d57cb687fc1f0af1a" dependencies = [ "arrayvec", "aws-config", @@ -2762,7 +2762,7 @@ dependencies = [ [[package]] name = "ethcontract-common" version = "0.25.9" -source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=c057a6e78827d762e1827c8d57cb687fc1f0af1a#c057a6e78827d762e1827c8d57cb687fc1f0af1a" dependencies = [ "ethabi", "hex", @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "ethcontract-derive" version = "0.25.9" -source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=c057a6e78827d762e1827c8d57cb687fc1f0af1a#c057a6e78827d762e1827c8d57cb687fc1f0af1a" dependencies = [ "anyhow", "ethcontract-common", @@ -2790,7 +2790,7 @@ dependencies = [ [[package]] name = "ethcontract-generate" version = "0.25.9" -source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=c057a6e78827d762e1827c8d57cb687fc1f0af1a#c057a6e78827d762e1827c8d57cb687fc1f0af1a" dependencies = [ "Inflector", "anyhow", @@ -2805,7 +2805,7 @@ dependencies = [ [[package]] name = "ethcontract-mock" version = "0.25.9" -source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=8817f4ba868e686f21cda886d6c8b75834eb51f2#8817f4ba868e686f21cda886d6c8b75834eb51f2" +source = "git+https://github.com/cowprotocol/ethcontract-rs?rev=c057a6e78827d762e1827c8d57cb687fc1f0af1a#c057a6e78827d762e1827c8d57cb687fc1f0af1a" dependencies = [ "ethcontract", "hex", diff --git a/Cargo.toml b/Cargo.toml index 144cb6cfef..88d78f3a6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,10 @@ clap = { version = "4.5.6", features = ["derive", "env"] } dashmap = "6.1.0" derivative = "2.2.0" derive_more = { version = "1.0.0", features = ["full"] } -ethcontract = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "8817f4ba868e686f21cda886d6c8b75834eb51f2", default-features = false, features = ["aws-kms"] } +ethcontract = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "c057a6e78827d762e1827c8d57cb687fc1f0af1a", default-features = false, features = ["aws-kms"] } mimalloc = "0.1.43" -ethcontract-generate = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "8817f4ba868e686f21cda886d6c8b75834eb51f2", default-features = false } -ethcontract-mock = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "8817f4ba868e686f21cda886d6c8b75834eb51f2", default-features = false } +ethcontract-generate = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "c057a6e78827d762e1827c8d57cb687fc1f0af1a", default-features = false } +ethcontract-mock = { git = "https://github.com/cowprotocol/ethcontract-rs", rev = "c057a6e78827d762e1827c8d57cb687fc1f0af1a", default-features = false } ethereum-types = "0.14.1" flate2 = "1.0.30" futures = "0.3.30" diff --git a/crates/ethrpc/src/extensions.rs b/crates/ethrpc/src/extensions.rs index 815bc34fd7..6eac4944c7 100644 --- a/crates/ethrpc/src/extensions.rs +++ b/crates/ethrpc/src/extensions.rs @@ -1,15 +1,15 @@ //! Module containing Ethereum RPC extension methods. use { - serde::{Deserialize, Serialize}, - std::collections::HashMap, + ethcontract::state_overrides::StateOverrides, + serde::Deserialize, tracing::{Instrument, instrument::Instrumented}, web3::{ self, Transport, api::Namespace, helpers::{self, CallFuture}, - types::{BlockId, Bytes, CallRequest, H160, H256, U64, U256}, + types::{BlockId, Bytes, CallRequest, H256}, }, }; @@ -22,7 +22,7 @@ where &self, call: CallRequest, block: BlockId, - overrides: HashMap, + overrides: StateOverrides, ) -> Instrumented>; } @@ -48,36 +48,6 @@ where } } -/// State overrides. -pub type StateOverrides = HashMap; - -/// State override object. -#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct StateOverride { - /// Fake balance to set for the account before executing the call. - #[serde(skip_serializing_if = "Option::is_none")] - pub balance: Option, - - /// Fake nonce to set for the account before executing the call. - #[serde(skip_serializing_if = "Option::is_none")] - pub nonce: Option, - - /// Fake EVM bytecode to inject into the account before executing the call. - #[serde(skip_serializing_if = "Option::is_none")] - pub code: Option, - - /// Fake key-value mapping to override **all** slots in the account storage - /// before executing the call. - #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option>, - - /// Fake key-value mapping to override **individual** slots in the account - /// storage before executing the call. - #[serde(skip_serializing_if = "Option::is_none")] - pub state_diff: Option>, -} - /// Debug namespace extension trait. pub trait DebugNamespace where @@ -140,7 +110,14 @@ pub struct CallFrame { #[cfg(test)] mod tests { - use {super::*, crate::Web3, hex_literal::hex, maplit::hashmap, web3::types::BlockNumber}; + use { + super::*, + crate::Web3, + ethcontract::{H160, state_overrides::StateOverride}, + hex_literal::hex, + maplit::hashmap, + web3::types::BlockNumber, + }; #[ignore] #[tokio::test] diff --git a/crates/shared/src/code_simulation.rs b/crates/shared/src/code_simulation.rs index b45fb87ecb..cde349d8eb 100644 --- a/crates/shared/src/code_simulation.rs +++ b/crates/shared/src/code_simulation.rs @@ -4,11 +4,11 @@ use { crate::tenderly_api::{SimulationKind, SimulationRequest, StateObject, TenderlyApi}, anyhow::{Context as _, Result, ensure}, contracts::errors::EthcontractErrorType, - ethcontract::errors::ExecutionError, - ethrpc::{ - Web3, - extensions::{EthExt as _, StateOverride, StateOverrides}, + ethcontract::{ + errors::ExecutionError, + state_overrides::{StateOverride, StateOverrides}, }, + ethrpc::{Web3, extensions::EthExt as _}, std::sync::Arc, thiserror::Error, tracing::instrument, diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 28c95a8d83..c34e491215 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -114,7 +114,7 @@ impl<'a> PriceEstimatorFactory<'a> { None => Arc::new(web3.clone()), }; - let balance_overrides = args.balance_overrides.init(simulator.clone()); + let balance_overrides = args.balance_overrides.init(web3.clone()); let verifier = TradeVerifier::new( web3, diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs index f92cad6003..540ae72bfc 100644 --- a/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/detector.rs @@ -1,9 +1,9 @@ use { super::Strategy, - crate::code_simulation::{CodeSimulating, SimulationError}, - contracts::{ERC20, dummy_contract}, - ethcontract::{Address, H256, U256}, - ethrpc::extensions::StateOverride, + crate::code_simulation::SimulationError, + anyhow::Context, + contracts::ERC20, + ethcontract::{Address, H256, U256, state_overrides::StateOverride}, maplit::hashmap, std::{ collections::HashMap, @@ -11,7 +11,7 @@ use { sync::Arc, }, thiserror::Error, - web3::{signing::keccak256, types::CallRequest}, + web3::signing::keccak256, }; /// A heuristic balance override detector based on `eth_call` simulations. @@ -21,7 +21,7 @@ use { pub struct Detector(Arc); pub struct Inner { - simulator: Arc, + web3: ethrpc::Web3, /// address that we try to override the balances for holder: Address, /// all strategies used to detect successful balance overrides @@ -40,7 +40,7 @@ impl std::ops::Deref for Detector { impl Detector { /// Creates a new balance override detector. - pub fn new(simulator: Arc, probing_depth: u8) -> Self { + pub fn new(web3: ethrpc::Web3, probing_depth: u8) -> Self { let holder = { // On a technical note, Ethereum public addresses are, for the most // part, generated by taking the 20 last bytes of a Keccak-256 hash (for @@ -89,7 +89,7 @@ impl Detector { .collect::>(); Self(Arc::new(Inner { - simulator, + web3, holder, strategies, state_overrides, @@ -100,23 +100,19 @@ impl Detector { /// Returns an `Err` if it cannot detect the strategy or an internal /// simulation fails. pub async fn detect(&self, token: Address) -> Result { - let token = dummy_contract!(ERC20, token); - let call = CallRequest { - to: Some(token.address()), - data: token.methods().balance_of(self.holder).m.tx.data, - ..Default::default() - }; + let token = ERC20::at(&self.web3, token); let overrides = hashmap! { token.address() => StateOverride { state_diff: Some(self.state_overrides.clone()), ..Default::default() }, }; - - let output = self.simulator.simulate(call, overrides, None).await?; - let balance = (output.len() == 32) - .then(|| U256::from_big_endian(&output)) - .ok_or(DetectionError::Decode)?; + let balance = token + .balance_of(self.holder) + .call_with_state_overrides(&overrides) + .await + .context("eth_call with state overrides failed") + .map_err(|e| DetectionError::Simulation(SimulationError::Other(e)))?; self.strategies .iter() @@ -167,8 +163,6 @@ impl Debug for Detector { pub enum DetectionError { #[error("could not detect a balance override strategy")] NotFound, - #[error("unable to decode simulation return data")] - Decode, #[error(transparent)] Simulation(#[from] SimulationError), } @@ -183,7 +177,7 @@ mod tests { #[ignore] #[tokio::test] async fn detects_storage_slots_mainnet() { - let detector = Detector::new(Arc::new(Web3::new_from_env()), 60); + let detector = Detector::new(Web3::new_from_env(), 60); let storage = detector .detect(addr!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2")) @@ -215,7 +209,7 @@ mod tests { #[ignore] #[tokio::test] async fn detects_storage_slots_arbitrum() { - let detector = Detector::new(Arc::new(Web3::new_from_env()), 60); + let detector = Detector::new(Web3::new_from_env(), 60); // all bridged tokens on arbitrum require a ton of probing let storage = detector diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs similarity index 98% rename from crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs rename to crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs index 7c9eb41f65..6a7511d8fd 100644 --- a/crates/shared/src/price_estimation/trade_verifier/balance_overrides.rs +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs @@ -2,11 +2,9 @@ mod detector; use { self::detector::{DetectionError, Detector}, - crate::code_simulation::CodeSimulating, anyhow::Context as _, cached::{Cached, SizedCache}, - ethcontract::{Address, H256, U256}, - ethrpc::extensions::StateOverride, + ethcontract::{Address, H256, U256, state_overrides::StateOverride}, maplit::hashmap, std::{ collections::HashMap, @@ -51,13 +49,13 @@ pub struct Arguments { impl Arguments { /// Creates a balance overrides instance from the current configuration. - pub fn init(&self, simulator: Arc) -> Arc { + pub fn init(&self, web3: ethrpc::Web3) -> Arc { Arc::new(BalanceOverrides { hardcoded: self.quote_token_balance_overrides.0.clone(), detector: self.quote_autodetect_token_balance_overrides.then(|| { ( Detector::new( - simulator, + web3, self.quote_autodetect_token_balance_overrides_probing_depth, ), Mutex::new(SizedCache::with_size( diff --git a/crates/shared/src/price_estimation/trade_verifier.rs b/crates/shared/src/price_estimation/trade_verifier/mod.rs similarity index 99% rename from crates/shared/src/price_estimation/trade_verifier.rs rename to crates/shared/src/price_estimation/trade_verifier/mod.rs index 16ac4d2afb..5a534566db 100644 --- a/crates/shared/src/price_estimation/trade_verifier.rs +++ b/crates/shared/src/price_estimation/trade_verifier/mod.rs @@ -25,8 +25,8 @@ use { dummy_contract, support::{AnyoneAuthenticator, Solver, Spardose, Trader}, }, - ethcontract::{Bytes, H160, U256, tokens::Tokenize}, - ethrpc::{Web3, block_stream::CurrentBlockWatcher, extensions::StateOverride}, + ethcontract::{Bytes, H160, U256, state_overrides::StateOverride, tokens::Tokenize}, + ethrpc::{Web3, block_stream::CurrentBlockWatcher}, model::{ DomainSeparator, order::{BUY_ETH_ADDRESS, OrderData, OrderKind}, From 9679d22bc1d24898a19f902304f5898a67a2af86 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 19 Sep 2025 12:17:01 +0200 Subject: [PATCH 048/112] Populate available app data eagerly (#3683) # Description While playing around with https://github.com/cowprotocol/services/pull/3674 I found a small issue. That PR relies on the appdata already being fully available in the task that fetches the available balances (because we need to know whether we have to use state overrides to fake the effects of a flashloan). However, with the recent changes to how we parallelize the data fetching tasks during pre-processing the balance fetching task only has the appdata hashes and not the deserialized JSON values available. This PR adjust our logic such that we immediately populate all appdata JSONs we have already cached while converting the DTO into the domain representation. Also the appdata fetching task now only creates futures to fetch tasks that we didn't already have in our cache when we parsed the auction. Even though the (now avoided) futures wouldn't have done any network requests simply having tons of futures that tokio has to schedule can result in significant overhead and huge tail latency. This change should also reduce how much data we have to copy because previously all appdata values were inserted per connected solver while we now copy the majority of appdatas only once and only inject the few missing appdatas for each connected solver. # Changes - populate available appdata values while converting auction DTO into domain representation - only create futures to fetch MISSING appdata values ## How to test Deploying to shadow showed no relevant change in latency of either of the changed pre-procssing phases. If at all `fetch_app_data` shows slightly less variance (although this was just measured over ~25m). Screenshot 2025-09-19 at 09 37 20 Screenshot 2025-09-19 at 09 35 52 --- .../src/domain/competition/order/app_data.rs | 15 ++++++++++--- .../src/domain/competition/pre_processing.rs | 21 +++++++++++++++---- .../api/routes/solve/dto/solve_request.rs | 20 +++++++++++++++--- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/crates/driver/src/domain/competition/order/app_data.rs b/crates/driver/src/domain/competition/order/app_data.rs index 5f9f747630..516ca39a47 100644 --- a/crates/driver/src/domain/competition/order/app_data.rs +++ b/crates/driver/src/domain/competition/order/app_data.rs @@ -7,7 +7,7 @@ use { moka::future::Cache, reqwest::StatusCode, shared::request_sharing::BoxRequestSharing, - std::sync::Arc, + std::{collections::HashMap, sync::Arc}, thiserror::Error, url::Url, }; @@ -47,8 +47,17 @@ impl AppDataRetriever { })) } - /// Retrieves the full app-data for the given `app_data` hash, if exists. - pub async fn get( + /// Returns all values that are currently cached. + pub fn get_cached(&self) -> HashMap, Arc> { + self.0 + .cache + .iter() + .flat_map(|(key, value)| Some((key, value?))) + .collect() + } + + /// Retrieves the full app-data for the given `app_data` hash, if it exists. + pub async fn get_cached_or_fetch( &self, app_data: &AppDataHash, ) -> Result>, FetchingError> { diff --git a/crates/driver/src/domain/competition/pre_processing.rs b/crates/driver/src/domain/competition/pre_processing.rs index 028363e9fc..8faefd034f 100644 --- a/crates/driver/src/domain/competition/pre_processing.rs +++ b/crates/driver/src/domain/competition/pre_processing.rs @@ -1,7 +1,11 @@ use { super::{Auction, Order, order}, crate::{ - domain::{competition::order::SellTokenBalance, eth, liquidity}, + domain::{ + competition::order::{SellTokenBalance, app_data::AppData}, + eth, + liquidity, + }, infra::{self, api::routes::solve::dto::SolveRequest, observe::metrics, tokens}, util::Bytes, }, @@ -213,8 +217,13 @@ impl Utilities { let auction_domain = { let _timer = metrics::get().processing_stage_timer("convert_to_domain"); + let app_data = self + .app_data_retriever + .as_ref() + .map(|retriever| retriever.get_cached()) + .unwrap_or_default(); let auction = auction_dto - .into_domain(&self.eth, &self.tokens) + .into_domain(&self.eth, &self.tokens, app_data) .await .context("could not convert auction DTO to domain type")?; Arc::new(auction) @@ -310,13 +319,17 @@ impl Utilities { auction .orders .iter() - .map(|order| order.app_data.hash()) + .flat_map(|order| match order.app_data { + AppData::Full(_) => None, + // only fetch appdata we don't already have in full + AppData::Hash(hash) => Some(hash), + }) .unique() .map(|app_data_hash| { let app_data_retriever = app_data_retriever.clone(); async move { let fetched_app_data = app_data_retriever - .get(&app_data_hash) + .get_cached_or_fetch(&app_data_hash) .await .tap_err(|err| { tracing::warn!(?app_data_hash, ?err, "failed to fetch app data"); diff --git a/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs index 013621ddca..509b9fb4d5 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs @@ -1,7 +1,14 @@ use { crate::{ domain::{ - competition::{self, auction, order}, + competition::{ + self, + auction, + order::{ + self, + app_data::{AppData, AppDataHash}, + }, + }, eth, }, infra::{Ethereum, tokens}, @@ -9,7 +16,10 @@ use { }, serde::Deserialize, serde_with::serde_as, - std::collections::HashSet, + std::{ + collections::{HashMap, HashSet}, + sync::Arc, + }, tracing::instrument, }; @@ -19,6 +29,7 @@ impl SolveRequest { self, eth: &Ethereum, tokens: &tokens::Fetcher, + app_data: HashMap, Arc>, ) -> Result { let token_addresses: Vec<_> = self .tokens @@ -52,7 +63,10 @@ impl SolveRequest { Class::Market => competition::order::Kind::Market, Class::Limit => competition::order::Kind::Limit, }, - app_data: order.app_data.into(), + app_data: match app_data.get(&AppDataHash::from(order.app_data)) { + Some(data) => AppData::Full(data.clone()), + None => AppData::Hash(AppDataHash::from(order.app_data)), + }, partial: if order.partially_fillable { competition::order::Partial::Yes { available: match order.kind { From 9aee79c0a334ad7e1fe1fe352ad70da6d76a96c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Fri, 19 Sep 2025 11:49:23 +0100 Subject: [PATCH 049/112] Migrate UniswapV2Router02 to alloy (#3676) --- crates/contracts/build.rs | 15 --- crates/contracts/src/alloy.rs | 26 ++++ crates/contracts/src/lib.rs | 4 - crates/driver/src/infra/liquidity/config.rs | 8 +- crates/e2e/src/setup/deploy.rs | 22 ++-- .../e2e/src/setup/onchain_components/mod.rs | 106 +++++++++-------- crates/e2e/tests/e2e/limit_orders.rs | 75 ++++++------ .../tests/e2e/partially_fillable_balance.rs | 41 +++---- .../e2e/tests/e2e/partially_fillable_pool.rs | 43 +++---- crates/e2e/tests/e2e/protocol_fee.rs | 80 ++++++------- crates/e2e/tests/e2e/replace_order.rs | 111 ++++++++---------- .../tests/e2e/solver_participation_guard.rs | 69 +++++------ crates/shared/src/sources/uniswap_v2/mod.rs | 90 ++++++-------- 13 files changed, 334 insertions(+), 356 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index e4e27f2f1f..831ec8b28e 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -589,21 +589,6 @@ fn main() { generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); - generate_contract_with_config("UniswapV2Router02", |builder| { - // - builder - .add_network_str(MAINNET, "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D") - .add_network_str(GOERLI, "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D") - .add_network_str(GNOSIS, "0x1C232F01118CB8B424793ae03F870aa7D0ac7f77") - .add_network_str(ARBITRUM_ONE, "0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24") - .add_network_str(BASE, "0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24") - .add_network_str(SEPOLIA, "0xeE567Fe1712Faf6149d80dA1E6934E354124CfE3") - .add_network_str(AVALANCHE, "0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24") - .add_network_str(BNB, "0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24") - .add_network_str(OPTIMISM, "0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2") - .add_network_str(POLYGON, "0xedf6066a2b290C185783862C7F4776A2C8077AD1") - // Not available on Lens - }); generate_contract_with_config("UniswapV3SwapRouterV2", |builder| { // builder diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index 82b0dfa4c1..f8fb20978c 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -413,6 +413,31 @@ crate::bindings!( // Not available on Lens } ); +crate::bindings!( + UniswapV2Router02, + // + crate::deployments! { + // + MAINNET => address!("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), + // + GNOSIS => address!("0x1C232F01118CB8B424793ae03F870aa7D0ac7f77"), + // + ARBITRUM_ONE => address!("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24"), + // + BASE => address!("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24"), + // + SEPOLIA => address!("0xeE567Fe1712Faf6149d80dA1E6934E354124CfE3"), + // + AVALANCHE => address!("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24"), + // + BNB => address!("0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24"), + // + OPTIMISM => address!("0x4A7b5Da61326A6379179b40d00F57E5bbDC962c2"), + // + POLYGON => address!("0xedf6066a2b290C185783862C7F4776A2C8077AD1"), + // Not available on Lens + } +); pub use alloy::providers::DynProvider as Provider; @@ -628,6 +653,7 @@ mod tests { POLYGON, ] { assert!(UniswapV2Factory::deployment_address(chain_id).is_some()); + assert!(UniswapV2Router02::deployment_address(chain_id).is_some()); } } diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 7e3f751ff5..3be1fb9ddf 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -74,7 +74,6 @@ include_contracts! { IUniswapLikeRouter; IUniswapV3Factory; Permit2; - UniswapV2Router02; UniswapV3Pool; UniswapV3QuoterV2; UniswapV3SwapRouterV2; @@ -179,9 +178,6 @@ mod tests { for network in &[MAINNET, GNOSIS, SEPOLIA] { assert_has_deployment_address!(CowProtocolToken for *network); } - for network in &[MAINNET, GNOSIS, ARBITRUM_ONE] { - assert_has_deployment_address!(UniswapV2Router02 for *network); - } for network in &[ MAINNET, ARBITRUM_ONE, diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index e8b1cae32f..85035581a8 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -15,6 +15,7 @@ use { SUSHISWAP_INIT, SWAPR_INIT, TESTNET_UNISWAP_INIT, + UNISWAP_INIT, }, std::{collections::HashSet, time::Duration}, }; @@ -64,9 +65,10 @@ impl UniswapV2 { #[allow(clippy::self_named_constructors)] pub fn uniswap_v2(chain: Chain) -> Option { Some(Self { - router: deployment_address(contracts::UniswapV2Router02::raw_contract(), chain)?, - pool_code: hex!("96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f") - .into(), + router: ContractAddress::from( + contracts::alloy::UniswapV2Router02::deployment_address(&chain.id())?.into_legacy(), + ), + pool_code: UNISWAP_INIT.into(), missing_pool_cache_time: Duration::from_secs(60 * 60), }) } diff --git a/crates/e2e/src/setup/deploy.rs b/crates/e2e/src/setup/deploy.rs index 50bda43374..04b2866908 100644 --- a/crates/e2e/src/setup/deploy.rs +++ b/crates/e2e/src/setup/deploy.rs @@ -11,13 +11,12 @@ use { GPv2AllowListAuthentication, GPv2Settlement, HooksTrampoline, - UniswapV2Router02, WETH9, - alloy::{InstanceExt, UniswapV2Factory}, + alloy::{InstanceExt, UniswapV2Factory, UniswapV2Router02}, support::{Balances, Signatures}, }, ethcontract::{Address, H256, U256, errors::DeployError}, - ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, + ethrpc::alloy::conversions::IntoAlloy, model::DomainSeparator, shared::ethrpc::Web3, }; @@ -36,7 +35,7 @@ pub struct Contracts { pub gp_authenticator: GPv2AllowListAuthentication, pub balances: Balances, pub uniswap_v2_factory: UniswapV2Factory::Instance, - pub uniswap_v2_router: UniswapV2Router02, + pub uniswap_v2_router: UniswapV2Router02::Instance, pub weth: WETH9, pub allowance: Address, pub domain_separator: DomainSeparator, @@ -98,7 +97,9 @@ impl Contracts { uniswap_v2_factory: UniswapV2Factory::Instance::deployed(&web3.alloy) .await .unwrap(), - uniswap_v2_router: UniswapV2Router02::deployed(web3).await.unwrap(), + uniswap_v2_router: UniswapV2Router02::Instance::deployed(&web3.alloy) + .await + .unwrap(), weth: WETH9::deployed(web3).await.unwrap(), allowance: gp_settlement .vault_relayer() @@ -154,10 +155,13 @@ impl Contracts { UniswapV2Factory::Instance::deploy(web3.alloy.clone(), accounts[0].into_alloy()) .await .unwrap(); - let uniswap_v2_router = deploy!( - web3, - UniswapV2Router02(uniswap_v2_factory.address().into_legacy(), weth.address()) - ); + let uniswap_v2_router = UniswapV2Router02::Instance::deploy( + web3.alloy.clone(), + *uniswap_v2_factory.address(), + weth.address().into_alloy(), + ) + .await + .unwrap(); let gp_authenticator = deploy!(web3, GPv2AllowListAuthentication); gp_authenticator diff --git a/crates/e2e/src/setup/onchain_components/mod.rs b/crates/e2e/src/setup/onchain_components/mod.rs index 97f6230819..2677b540b3 100644 --- a/crates/e2e/src/setup/onchain_components/mod.rs +++ b/crates/e2e/src/setup/onchain_components/mod.rs @@ -473,7 +473,7 @@ impl OnchainComponents { contract .approve( - self.contracts.uniswap_v2_router.address().into_alloy(), + *self.contracts.uniswap_v2_router.address(), token_amount.into_alloy(), ) .from(minter.address().into_alloy()) @@ -483,23 +483,27 @@ impl OnchainComponents { tx!( minter, - self.contracts - .weth - .approve(self.contracts.uniswap_v2_router.address(), weth_amount) - ); - tx!( - minter, - self.contracts.uniswap_v2_router.add_liquidity( - contract.address().into_legacy(), - self.contracts.weth.address(), - token_amount, - weth_amount, - 0_u64.into(), - 0_u64.into(), - minter.address(), - U256::max_value(), + self.contracts.weth.approve( + self.contracts.uniswap_v2_router.address().into_legacy(), + weth_amount ) ); + self.contracts + .uniswap_v2_router + .addLiquidity( + *contract.address(), + self.contracts.weth.address().into_alloy(), + token_amount.into_alloy(), + weth_amount.into_alloy(), + ::alloy::primitives::U256::ZERO, + ::alloy::primitives::U256::ZERO, + minter.address().into_alloy(), + ::alloy::primitives::U256::MAX, + ) + .from(minter.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } } @@ -523,7 +527,7 @@ impl OnchainComponents { asset_a .0 .approve( - self.contracts.uniswap_v2_router.address().into_alloy(), + *self.contracts.uniswap_v2_router.address(), asset_a.1.into_alloy(), ) .from(lp.address().into_alloy()) @@ -534,26 +538,29 @@ impl OnchainComponents { asset_b .0 .approve( - self.contracts.uniswap_v2_router.address().into_alloy(), + *self.contracts.uniswap_v2_router.address(), asset_b.1.into_alloy(), ) .from(lp.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - lp, - self.contracts.uniswap_v2_router.add_liquidity( - asset_a.0.address().into_legacy(), - asset_b.0.address().into_legacy(), - asset_a.1, - asset_b.1, - 0_u64.into(), - 0_u64.into(), - lp.address(), - U256::max_value(), + self.contracts + .uniswap_v2_router + .addLiquidity( + *asset_a.0.address(), + *asset_b.0.address(), + asset_a.1.into_alloy(), + asset_b.1.into_alloy(), + ::alloy::primitives::U256::ZERO, + ::alloy::primitives::U256::ZERO, + lp.address().into_alloy(), + ::alloy::primitives::U256::MAX, ) - ); + .from(lp.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); } /// Mints `amount` tokens to its `token`-WETH Uniswap V2 pool. @@ -637,27 +644,34 @@ impl OnchainComponents { .unwrap(); tx!( holder, - cow.approve(self.contracts.uniswap_v2_router.address(), cow_amount) - ); - tx!( - holder, - self.contracts - .weth - .approve(self.contracts.uniswap_v2_router.address(), weth_amount) + cow.approve( + self.contracts.uniswap_v2_router.address().into_legacy(), + cow_amount + ) ); tx!( holder, - self.contracts.uniswap_v2_router.add_liquidity( - cow.address(), - self.contracts.weth.address(), - cow_amount, - weth_amount, - 0_u64.into(), - 0_u64.into(), - holder.address(), - U256::max_value(), + self.contracts.weth.approve( + self.contracts.uniswap_v2_router.address().into_legacy(), + weth_amount ) ); + self.contracts + .uniswap_v2_router + .addLiquidity( + cow.address().into_alloy(), + self.contracts.weth.address().into_alloy(), + cow_amount.into_alloy(), + weth_amount.into_alloy(), + ::alloy::primitives::U256::ZERO, + ::alloy::primitives::U256::ZERO, + holder.address().into_alloy(), + ::alloy::primitives::U256::MAX, + ) + .from(holder.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); cow } diff --git a/crates/e2e/tests/e2e/limit_orders.rs b/crates/e2e/tests/e2e/limit_orders.rs index 9daf3b4513..d85b494cc5 100644 --- a/crates/e2e/tests/e2e/limit_orders.rs +++ b/crates/e2e/tests/e2e/limit_orders.rs @@ -1,5 +1,6 @@ use { crate::database::AuctionTransaction, + ::alloy::primitives::U256, bigdecimal::BigDecimal, contracts::ERC20, database::byte_array::ByteArray, @@ -9,7 +10,7 @@ use { setup::{eth, *}, tx, }, - ethcontract::{H160, prelude::U256}, + ethcontract::H160, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -124,37 +125,35 @@ async fn single_limit_order_test(web3: Web3) { .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), + eth(1000), eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); // Approve GPv2 for trading @@ -250,37 +249,35 @@ async fn two_limit_orders_test(web3: Web3) { .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), + eth(1000), eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); // Approve GPv2 for trading diff --git a/crates/e2e/tests/e2e/partially_fillable_balance.rs b/crates/e2e/tests/e2e/partially_fillable_balance.rs index c6b81aa984..f8148a3b69 100644 --- a/crates/e2e/tests/e2e/partially_fillable_balance.rs +++ b/crates/e2e/tests/e2e/partially_fillable_balance.rs @@ -1,9 +1,6 @@ use { ::alloy::primitives::U256, - e2e::{ - setup::{eth, *}, - tx, - }, + e2e::setup::{eth, *}, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -46,37 +43,35 @@ async fn test(web3: Web3) { .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), eth(1000), + eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::MAX.into_legacy(), - ) - ); token_a .approve(onchain.contracts().allowance.into_alloy(), eth(500)) diff --git a/crates/e2e/tests/e2e/partially_fillable_pool.rs b/crates/e2e/tests/e2e/partially_fillable_pool.rs index 228d79ef7e..6d39aee755 100644 --- a/crates/e2e/tests/e2e/partially_fillable_pool.rs +++ b/crates/e2e/tests/e2e/partially_fillable_pool.rs @@ -1,9 +1,6 @@ use { - e2e::{ - setup::{eth, *}, - tx, - }, - ethcontract::prelude::U256, + ::alloy::primitives::U256, + e2e::setup::{eth, *}, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -45,37 +42,35 @@ async fn test(web3: Web3) { .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), eth(1000), + eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); token_a .approve(onchain.contracts().allowance.into_alloy(), eth(500)) diff --git a/crates/e2e/tests/e2e/protocol_fee.rs b/crates/e2e/tests/e2e/protocol_fee.rs index 9900056325..033ebd8db2 100644 --- a/crates/e2e/tests/e2e/protocol_fee.rs +++ b/crates/e2e/tests/e2e/protocol_fee.rs @@ -96,20 +96,14 @@ async fn combined_protocol_fees(web3: Web3) { token.mint(solver.address(), to_wei(1000)).await; token - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(100), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(100)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -130,10 +124,14 @@ async fn combined_protocol_fees(web3: Web3) { ); tx!( solver.account(), - onchain - .contracts() - .weth - .approve(onchain.contracts().uniswap_v2_router.address(), to_wei(200)) + onchain.contracts().weth.approve( + onchain + .contracts() + .uniswap_v2_router + .address() + .into_legacy(), + to_wei(200) + ) ); let autopilot_config = vec![ @@ -432,20 +430,14 @@ async fn surplus_partner_fee(web3: Web3) { token.mint(solver.address(), to_wei(1000)).await; token - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(100), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(100)) .from(trader.address().into_alloy()) .send_and_watch() .await @@ -464,10 +456,14 @@ async fn surplus_partner_fee(web3: Web3) { ); tx!( solver.account(), - onchain - .contracts() - .weth - .approve(onchain.contracts().uniswap_v2_router.address(), to_wei(200)) + onchain.contracts().weth.approve( + onchain + .contracts() + .uniswap_v2_router + .address() + .into_legacy(), + to_wei(200) + ) ); let services = Services::new(&onchain).await; @@ -654,37 +650,35 @@ async fn volume_fee_buy_order_test(web3: Web3) { .unwrap(); token_gno - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_dai - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_gno.address(), + *token_dai.address(), + eth(1000), eth(1000), + ::alloy::primitives::U256::ZERO, + ::alloy::primitives::U256::ZERO, + solver.address().into_alloy(), + ::alloy::primitives::U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_gno.address().into_legacy(), - token_dai.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); // Approve GPv2 for trading diff --git a/crates/e2e/tests/e2e/replace_order.rs b/crates/e2e/tests/e2e/replace_order.rs index 727c01754d..bdab0cf325 100644 --- a/crates/e2e/tests/e2e/replace_order.rs +++ b/crates/e2e/tests/e2e/replace_order.rs @@ -1,10 +1,9 @@ use { + ::alloy::primitives::U256, e2e::{ nodes::local_node::TestNodeApi, setup::{eth, *}, - tx, }, - ethcontract::prelude::U256, ethrpc::alloy::{ CallBuilderExt, conversions::{IntoAlloy, IntoLegacy}, @@ -67,37 +66,35 @@ async fn try_replace_unreplaceable_order_test(web3: Web3) { .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), + eth(1000), eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); // Approve GPv2 for trading @@ -250,37 +247,35 @@ async fn try_replace_someone_else_order_test(web3: Web3) { .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), eth(1000), + eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); // Approve GPv2 for trading @@ -390,37 +385,35 @@ async fn single_replace_order_test(web3: Web3) { .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), + eth(1000), eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); // Approve GPv2 for trading diff --git a/crates/e2e/tests/e2e/solver_participation_guard.rs b/crates/e2e/tests/e2e/solver_participation_guard.rs index 6840f87ec8..3cd3bf22a8 100644 --- a/crates/e2e/tests/e2e/solver_participation_guard.rs +++ b/crates/e2e/tests/e2e/solver_participation_guard.rs @@ -1,19 +1,17 @@ use { - e2e::{ - setup::{ - Db, - ExtraServiceArgs, - MintableToken, - OnchainComponents, - Services, - TIMEOUT, - TestAccount, - eth, - run_test, - to_wei, - wait_for_condition, - }, - tx, + alloy::primitives::U256, + e2e::setup::{ + Db, + ExtraServiceArgs, + MintableToken, + OnchainComponents, + Services, + TIMEOUT, + TestAccount, + eth, + run_test, + to_wei, + wait_for_condition, }, ethrpc::{ Web3, @@ -29,10 +27,7 @@ use { secp256k1::SecretKey, sqlx::Row, std::time::Instant, - web3::{ - signing::SecretKeyRef, - types::{H160, U256}, - }, + web3::{signing::SecretKeyRef, types::H160}, }; #[tokio::test] @@ -266,37 +261,35 @@ async fn setup( .unwrap(); token_a - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), - eth(1000), - ) + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); token_b - .approve( - onchain.contracts().uniswap_v2_router.address().into_alloy(), + .approve(*onchain.contracts().uniswap_v2_router.address(), eth(1000)) + .from(solver.address().into_alloy()) + .send_and_watch() + .await + .unwrap(); + onchain + .contracts() + .uniswap_v2_router + .addLiquidity( + *token_a.address(), + *token_b.address(), eth(1000), + eth(1000), + U256::ZERO, + U256::ZERO, + solver.address().into_alloy(), + U256::MAX, ) .from(solver.address().into_alloy()) .send_and_watch() .await .unwrap(); - tx!( - solver.account(), - onchain.contracts().uniswap_v2_router.add_liquidity( - token_a.address().into_legacy(), - token_b.address().into_legacy(), - to_wei(1000), - to_wei(1000), - 0_u64.into(), - 0_u64.into(), - solver.address(), - U256::max_value(), - ) - ); // Approve GPv2 for trading diff --git a/crates/shared/src/sources/uniswap_v2/mod.rs b/crates/shared/src/sources/uniswap_v2/mod.rs index 1865d0795f..fc0cd7138b 100644 --- a/crates/shared/src/sources/uniswap_v2/mod.rs +++ b/crates/shared/src/sources/uniswap_v2/mod.rs @@ -67,61 +67,45 @@ impl UniV2BaselineSourceParameters { let chain_id = chain.parse::().expect("chain id should be an integer"); - let (contract, init_code_digest, pool_reading) = match source { + match source { BS::None | BS::BalancerV2 | BS::ZeroEx | BS::UniswapV3 => None, - BS::UniswapV2 => Some(( - contracts::UniswapV2Router02::raw_contract(), - UNISWAP_INIT, - PoolReadingStyle::Default, - )), - BS::Honeyswap => { - return Some(Self { - router: contracts::alloy::HoneyswapRouter::deployment_address(&chain_id) - .map(IntoLegacy::into_legacy)?, - init_code_digest: H256(HONEYSWAP_INIT), - pool_reading: PoolReadingStyle::Default, - }); - } - BS::SushiSwap => { - return Some(Self { - router: contracts::alloy::SushiSwapRouter::deployment_address(&chain_id) - .map(IntoLegacy::into_legacy)?, - init_code_digest: SUSHISWAP_INIT.into(), - pool_reading: PoolReadingStyle::Default, - }); - } - BS::Swapr => { - return Some(Self { - router: contracts::alloy::SwaprRouter::deployment_address(&chain_id) - .map(IntoLegacy::into_legacy)?, - init_code_digest: SWAPR_INIT.into(), - pool_reading: PoolReadingStyle::Swapr, - }); - } - BS::TestnetUniswapV2 => { - return Some(Self { - router: contracts::alloy::TestnetUniswapV2Router02::deployment_address( - &chain_id, - ) + BS::UniswapV2 => Some(Self { + router: contracts::alloy::UniswapV2Router02::deployment_address(&chain_id) .map(IntoLegacy::into_legacy)?, - init_code_digest: TESTNET_UNISWAP_INIT.into(), - pool_reading: PoolReadingStyle::Default, - }); - } - BS::Baoswap => { - return Some(Self { - router: contracts::alloy::BaoswapRouter::deployment_address(&chain_id) - .map(IntoLegacy::into_legacy)?, - init_code_digest: H256(BAOSWAP_INIT), - pool_reading: PoolReadingStyle::Default, - }); - } - }?; - Some(Self { - router: contract.networks.get(chain)?.address, - init_code_digest: H256(init_code_digest), - pool_reading, - }) + init_code_digest: UNISWAP_INIT.into(), + pool_reading: PoolReadingStyle::Default, + }), + BS::Honeyswap => Some(Self { + router: contracts::alloy::HoneyswapRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: HONEYSWAP_INIT.into(), + pool_reading: PoolReadingStyle::Default, + }), + BS::SushiSwap => Some(Self { + router: contracts::alloy::SushiSwapRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: SUSHISWAP_INIT.into(), + pool_reading: PoolReadingStyle::Default, + }), + BS::Swapr => Some(Self { + router: contracts::alloy::SwaprRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: SWAPR_INIT.into(), + pool_reading: PoolReadingStyle::Swapr, + }), + BS::TestnetUniswapV2 => Some(Self { + router: contracts::alloy::TestnetUniswapV2Router02::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: TESTNET_UNISWAP_INIT.into(), + pool_reading: PoolReadingStyle::Default, + }), + BS::Baoswap => Some(Self { + router: contracts::alloy::BaoswapRouter::deployment_address(&chain_id) + .map(IntoLegacy::into_legacy)?, + init_code_digest: BAOSWAP_INIT.into(), + pool_reading: PoolReadingStyle::Default, + }), + } } pub async fn into_source(&self, web3: &Web3) -> Result { From 4083a8068d1d8369d2a49fa3a73012a866bef444 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 19 Sep 2025 14:45:17 +0200 Subject: [PATCH 050/112] fake flashloans in balance checks (#3674) # Description This is the first PR of many to gradually make user balance checks and order signature checks compatible with flashloans. For context when a user wants to place an order we make sure that their account has sufficient allowance and balance for the sell token. When placing a flashloan order it may very well be the case that the user has 0 allowance or balance but their order includes a pre-hook that sets up the balance and allowance correctly. Without this PR the balance simulation logic was completely unaware of the flashloan requirement so placing such orders would be rejected due to missing balances and allowances. To make the balance simulation logic compatible with flashloans we adjust it to fake the effects of a flashloan. Specifically we compute the exact state override that is needed to change the flashloan token such that the flashloan receiver has the required balance. # Changes - adjusted `BalanceOverriding` interface slightly to make it easier to work with - add `Arc` and use it to set the required state overrides in the balance simulation logic - autopilot and orderbook create an instance based on CLI arguments - driver creates instance with sensible defaults to make things simpler for now - removed some balance related dead code from the `Erc20` struct As a follow up we could still optimize how the individual components setup and share the `BalanceOverrides` instance. But that only becomes relevant with tons of flashloan orders for many different tokens. ## How to test all tests should still pass (we just replaced a less versatile hack with a reasonable solution that should work for all reasonable flashloan setups) --- crates/autopilot/src/run.rs | 2 + .../src/domain/competition/pre_processing.rs | 21 ++- crates/driver/src/infra/api/mod.rs | 9 +- crates/driver/src/infra/blockchain/mod.rs | 6 +- crates/driver/src/infra/blockchain/token.rs | 151 +----------------- crates/orderbook/src/run.rs | 1 + crates/shared/src/account_balances/cached.rs | 1 + crates/shared/src/account_balances/mod.rs | 32 +++- .../shared/src/account_balances/simulation.rs | 12 +- crates/shared/src/order_quoting.rs | 2 + crates/shared/src/order_validation.rs | 43 +++-- .../trade_verifier/balance_overrides/mod.rs | 91 ++++++++--- .../price_estimation/trade_verifier/mod.rs | 4 +- 13 files changed, 161 insertions(+), 214 deletions(-) diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index be44569e3b..184a180a17 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -250,6 +250,7 @@ pub async fn run(args: Arguments) { }, ); + let overrider = args.price_estimation.balance_overrides.init(web3.clone()); let balance_fetcher = account_balances::cached( &web3, BalanceSimulator::new( @@ -257,6 +258,7 @@ pub async fn run(args: Arguments) { eth.contracts().balances().clone(), vault_relayer, vault.as_ref().map(|contract| contract.address()), + overrider, ), eth.current_block().clone(), ); diff --git a/crates/driver/src/domain/competition/pre_processing.rs b/crates/driver/src/domain/competition/pre_processing.rs index 8faefd034f..781ca8eced 100644 --- a/crates/driver/src/domain/competition/pre_processing.rs +++ b/crates/driver/src/domain/competition/pre_processing.rs @@ -23,6 +23,7 @@ use { }, shared::{ account_balances::{BalanceFetching, Query}, + price_estimation::trade_verifier::balance_overrides::BalanceOverrideRequest, signature_validator::SignatureValidating, }, std::{collections::HashMap, future::Future, sync::Arc}, @@ -251,8 +252,10 @@ impl Utilities { .map(|((trader, token, source), mut orders)| { let first = orders.next().expect("group contains at least 1 order"); let mut others = orders; - let all_interactions_equal = - others.all(|order| order.pre_interactions == first.pre_interactions); + let all_setups_equal = others.all(|order| { + order.pre_interactions == first.pre_interactions + && order.app_data.flashloan() == first.app_data.flashloan() + }); Query { owner: trader.0.0, token: token.0.0, @@ -261,7 +264,7 @@ impl Utilities { SellTokenBalance::Internal => SellTokenSource::Internal, SellTokenBalance::External => SellTokenSource::External, }, - interactions: if all_interactions_equal { + interactions: if all_setups_equal { first .pre_interactions .iter() @@ -274,6 +277,18 @@ impl Utilities { } else { Vec::default() }, + balance_override: if all_setups_equal { + first + .app_data + .flashloan() + .map(|loan| BalanceOverrideRequest { + token: loan.token, + amount: loan.amount, + holder: loan.borrower.unwrap_or(trader.0.0), + }) + } else { + None + }, } }) .collect::>(); diff --git a/crates/driver/src/infra/api/mod.rs b/crates/driver/src/infra/api/mod.rs index 7fe5ba559d..2e3f44ab5f 100644 --- a/crates/driver/src/infra/api/mod.rs +++ b/crates/driver/src/infra/api/mod.rs @@ -18,7 +18,7 @@ use { error::Error, futures::Future, observe::distributed_tracing::tracing_axum::{make_span, record_trace_id}, - shared::account_balances::{self, BalanceSimulator}, + shared::account_balances, std::{net::SocketAddr, sync::Arc}, tokio::sync::oneshot, }; @@ -55,12 +55,7 @@ impl Api { let balance_fetcher = account_balances::cached( self.eth.web3(), - BalanceSimulator::new( - self.eth.contracts().settlement().clone(), - self.eth.contracts().balance_helper().clone(), - self.eth.contracts().vault_relayer().0, - Some(self.eth.contracts().vault().address()), - ), + self.eth.balance_simulator().clone(), self.eth.current_block().clone(), ); diff --git a/crates/driver/src/infra/blockchain/mod.rs b/crates/driver/src/infra/blockchain/mod.rs index 1216dc59f2..78fd084ed1 100644 --- a/crates/driver/src/infra/blockchain/mod.rs +++ b/crates/driver/src/infra/blockchain/mod.rs @@ -4,7 +4,10 @@ use { chain::Chain, ethcontract::errors::ExecutionError, ethrpc::{Web3, block_stream::CurrentBlockWatcher}, - shared::account_balances::{BalanceSimulator, SimulationError}, + shared::{ + account_balances::{BalanceSimulator, SimulationError}, + price_estimation::trade_verifier::balance_overrides::BalanceOverrides, + }, std::{fmt, sync::Arc, time::Duration}, thiserror::Error, url::Url, @@ -117,6 +120,7 @@ impl Ethereum { contracts.balance_helper().clone(), contracts.vault_relayer().0, Some(contracts.vault().address()), + Arc::new(BalanceOverrides::new(web3.clone())), ); Self { diff --git a/crates/driver/src/infra/blockchain/token.rs b/crates/driver/src/infra/blockchain/token.rs index 106d566bb5..6e930c5840 100644 --- a/crates/driver/src/infra/blockchain/token.rs +++ b/crates/driver/src/infra/blockchain/token.rs @@ -1,9 +1,6 @@ use { super::{Error, Ethereum}, - crate::domain::{competition::order, eth}, - futures::TryFutureExt, - tap::TapFallible, - web3::types::CallRequest, + crate::domain::eth, }; /// An ERC-20 token. @@ -11,14 +8,12 @@ use { /// https://eips.ethereum.org/EIPS/eip-20 pub struct Erc20 { token: contracts::ERC20, - ethereum: Ethereum, } impl Erc20 { pub(super) fn new(eth: &Ethereum, address: eth::TokenAddress) -> Self { Self { token: eth.contract_at(address.into()), - ethereum: eth.clone(), } } @@ -81,150 +76,6 @@ impl Erc20 { .map(Into::into) .map_err(Into::into) } - - /// Fetches the tradable balance for the specified user given an order's - /// pre-interactions. - pub async fn tradable_balance( - &self, - trader: eth::Address, - source: order::SellTokenBalance, - interactions: &[eth::Interaction], - disable_access_list_simulation: bool, - ) -> Result { - if interactions.is_empty() { - self.tradable_balance_simple(trader, source).await - } else { - self.tradable_balance_simulated( - trader, - source, - interactions, - disable_access_list_simulation, - ) - .await - } - } - - /// Uses a custom helper contract to simulate balances while taking - /// pre-interactions into account. This is the most accurate method to - /// compute tradable balances but is very slow. - async fn tradable_balance_simulated( - &self, - trader: eth::Address, - source: order::SellTokenBalance, - interactions: &[eth::Interaction], - disable_access_lists: bool, - ) -> Result { - let interactions: Vec<_> = interactions.iter().map(|i| i.clone().into()).collect(); - let shared::account_balances::Simulation { - token_balance: _, - allowance: _, - effective_balance, - can_transfer, - } = self - .ethereum - .balance_simulator() - .simulate( - trader.0, - self.token.address(), - source.into(), - &interactions, - None, - |mut delegate_call| { - let ethereum = self.ethereum.clone(); - async move { - // Add the access lists to the delegate call if they are enabled - // system-wide. - if disable_access_lists { - return delegate_call; - } - - let access_list_call = CallRequest { - data: delegate_call.tx.data.clone(), - to: delegate_call.tx.to, - from: delegate_call - .tx - .from - .as_ref() - .map(|account| account.address()), - ..Default::default() - }; - let access_list = ethereum - .create_access_list(access_list_call) - .await - .tap_err(|err| { - tracing::debug!( - ?err, - "failed to create access list for balance simulation" - ); - }) - .ok(); - delegate_call.tx.access_list = access_list.map(Into::into); - delegate_call - } - }, - ) - .await?; - - if can_transfer { - Ok(effective_balance.into()) - } else { - Ok(eth::TokenAmount(0.into())) - } - } - - /// Faster balance query that does not take pre-interactions into account. - async fn tradable_balance_simple( - &self, - trader: eth::Address, - source: order::SellTokenBalance, - ) -> Result { - use order::SellTokenBalance; - - let relayer = self.ethereum.contracts().vault_relayer(); - let usable_balance = match source { - SellTokenBalance::Erc20 => { - let balance = self.balance(trader); - let allowance = self.allowance(trader, eth::Address(relayer.into())); - let (balance, allowance) = futures::try_join!(balance, allowance)?; - std::cmp::min(balance.0, allowance.0.amount) - } - SellTokenBalance::External => { - let vault = self.ethereum.contracts().vault(); - let balance = self.balance(trader); - let approved = vault - .methods() - .has_approved_relayer(trader.0, relayer.into()) - .call() - .map_err(Error::from); - let allowance = self.allowance(trader, vault.address().into()); - let (balance, approved, allowance) = - futures::try_join!(balance, approved, allowance)?; - match approved { - true => std::cmp::min(balance.0, allowance.0.amount), - false => 0.into(), - } - } - SellTokenBalance::Internal => { - let vault = self.ethereum.contracts().vault(); - let balance = vault - .methods() - .get_internal_balance(trader.0, vec![self.token.address()]) - .call() - .map_err(Error::from); - let approved = vault - .methods() - .has_approved_relayer(trader.0, relayer.into()) - .call() - .map_err(Error::from); - let (balance, approved) = futures::try_join!(balance, approved)?; - match approved { - true => balance[0], // internal approvals are always U256::MAX - false => 0.into(), - } - } - }; - Ok(eth::TokenAmount(usable_balance)) - } } /// Returns `true` if a [`ethcontract::errors::MethodError`] is the result of diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 8a38d0ec4f..778d68e15d 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -175,6 +175,7 @@ pub async fn run(args: Arguments) { balances_contract.clone(), vault_relayer, vault.as_ref().map(|contract| contract.address()), + args.price_estimation.balance_overrides.init(web3.clone()), ), ); diff --git a/crates/shared/src/account_balances/cached.rs b/crates/shared/src/account_balances/cached.rs index 97992c8a06..4f87854ef0 100644 --- a/crates/shared/src/account_balances/cached.rs +++ b/crates/shared/src/account_balances/cached.rs @@ -213,6 +213,7 @@ mod tests { token: H160([token; 20]), source: SellTokenSource::Erc20, interactions: vec![], + balance_override: None, } } diff --git a/crates/shared/src/account_balances/mod.rs b/crates/shared/src/account_balances/mod.rs index 8cbe75d5a6..339e846754 100644 --- a/crates/shared/src/account_balances/mod.rs +++ b/crates/shared/src/account_balances/mod.rs @@ -1,6 +1,15 @@ use { + crate::price_estimation::trade_verifier::balance_overrides::{ + BalanceOverrideRequest, + BalanceOverriding, + }, alloy::sol_types::{SolType, sol_data}, - ethcontract::{Bytes, contract::MethodBuilder, dyns::DynTransport}, + ethcontract::{ + Bytes, + contract::MethodBuilder, + dyns::DynTransport, + state_overrides::StateOverrides, + }, ethrpc::{Web3, block_stream::CurrentBlockWatcher}, model::{ interaction::InteractionData, @@ -20,6 +29,7 @@ pub struct Query { pub token: H160, pub source: SellTokenSource, pub interactions: Vec, + pub balance_override: Option, } impl Query { @@ -29,6 +39,9 @@ impl Query { token: o.data.sell_token, source: o.data.sell_token_balance, interactions: o.interactions.pre.clone(), + // TODO eventually delete together with the balance + // checks in the autopilot + balance_override: None, } } } @@ -83,11 +96,13 @@ pub fn cached( cached } +#[derive(Clone)] pub struct BalanceSimulator { settlement: contracts::GPv2Settlement, balances: contracts::support::Balances, vault_relayer: H160, vault: H160, + balance_overrider: Arc, } impl BalanceSimulator { @@ -96,12 +111,14 @@ impl BalanceSimulator { balances: contracts::support::Balances, vault_relayer: H160, vault: Option, + balance_overrider: Arc, ) -> Self { Self { settlement, vault_relayer, vault: vault.unwrap_or_default(), balances, + balance_overrider, } } @@ -113,6 +130,7 @@ impl BalanceSimulator { self.vault } + #[allow(clippy::too_many_arguments)] pub async fn simulate( &self, owner: H160, @@ -121,11 +139,21 @@ impl BalanceSimulator { interactions: &[InteractionData], amount: Option, add_access_lists: F, + balance_override: Option, ) -> Result where F: FnOnce(MethodBuilder>>) -> Fut, Fut: Future>>>, { + let overrides: StateOverrides = match balance_override { + Some(overrides) => self + .balance_overrider + .state_override(overrides) + .await + .into_iter() + .collect(), + None => Default::default(), + }; // We simulate the balances from the Settlement contract's context. This // allows us to check: // 1. How the pre-interactions would behave as part of the settlement @@ -155,7 +183,7 @@ impl BalanceSimulator { let delegate_call = add_access_lists(delegate_call).await; - let response = delegate_call.call().await?; + let response = delegate_call.call_with_state_overrides(&overrides).await?; let (token_balance, allowance, effective_balance, can_transfer) = <( sol_data::Uint<256>, diff --git a/crates/shared/src/account_balances/simulation.rs b/crates/shared/src/account_balances/simulation.rs index 583781ab35..661533881c 100644 --- a/crates/shared/src/account_balances/simulation.rs +++ b/crates/shared/src/account_balances/simulation.rs @@ -54,6 +54,7 @@ impl Balances { &query.interactions, None, |delegate_call| async move { delegate_call }, + query.balance_override.clone(), ) .await?; Ok(if simulation.can_transfer { @@ -141,6 +142,7 @@ impl BalanceFetching for Balances { &query.interactions, Some(amount), |delegate_call| async move { delegate_call }, + query.balance_override.clone(), ) .await .map_err(|err| TransferSimulationError::Other(err.into()))?; @@ -161,7 +163,13 @@ impl BalanceFetching for Balances { #[cfg(test)] mod tests { - use {super::*, ethrpc::Web3, model::order::SellTokenSource}; + use { + super::*, + crate::price_estimation::trade_verifier::balance_overrides::DummyOverrider, + ethrpc::Web3, + model::order::SellTokenSource, + std::sync::Arc, + }; #[ignore] #[tokio::test] @@ -180,6 +188,7 @@ mod tests { balances, addr!("C92E8bdf79f0507f65a392b0ab4667716BFE0110"), Some(addr!("BA12222222228d8Ba445958a75a0704d566BF2C8")), + Arc::new(DummyOverrider), ), ); @@ -195,6 +204,7 @@ mod tests { token, source, interactions: vec![], + balance_override: None, }, amount, ) diff --git a/crates/shared/src/order_quoting.rs b/crates/shared/src/order_quoting.rs index 8eee9a42d2..30a70a99a7 100644 --- a/crates/shared/src/order_quoting.rs +++ b/crates/shared/src/order_quoting.rs @@ -572,6 +572,8 @@ impl OrderQuoter { call_data: i.data.clone(), }) .collect(), + // quote verification already tries to auto-fake missing balances + balance_override: None, }; let mut balances = self.balance_fetcher.get_balances(&[query]).await; balances.pop().context("missing balance result")? diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index bd406455c3..13e7025f11 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -10,7 +10,11 @@ use { QuoteParameters, QuoteSearchParameters, }, - price_estimation::{PriceEstimationError, Verification}, + price_estimation::{ + PriceEstimationError, + Verification, + trade_verifier::balance_overrides::BalanceOverrideRequest, + }, signature_validator::{SignatureCheck, SignatureValidating, SignatureValidationError}, trade_finding, }, @@ -375,19 +379,6 @@ impl OrderValidator { ) -> Result<(), ValidationError> { let mut res = Ok(()); - // Check if there's a flashloan hint that could provide the sell token - let has_flashloan_for_sell_token = - app_data - .inner - .protocol - .flashloan - .as_ref() - .is_some_and(|flashloan| { - flashloan.borrower.is_none_or(|b| b == owner) - && flashloan.token == order.data().sell_token - && flashloan.amount >= order.data().sell_amount - }); - // Simulate transferring a small token balance into the settlement contract. // As a spam protection we require that an account must have at least 1 atom // of the sell_token. However, some tokens (e.g. rebasing tokens) actually run @@ -405,6 +396,13 @@ impl OrderValidator { owner, source: order.data().sell_token_balance, interactions: app_data.interactions.pre.clone(), + balance_override: app_data.inner.protocol.flashloan.as_ref().map(|loan| { + BalanceOverrideRequest { + token: loan.token, + holder: loan.borrower.unwrap_or(owner), + amount: loan.amount, + } + }), }, transfer_amount, ) @@ -415,16 +413,13 @@ impl OrderValidator { TransferSimulationError::InsufficientAllowance | TransferSimulationError::InsufficientBalance | TransferSimulationError::TransferFailed, - ) if order.signature == Signature::PreSign || has_flashloan_for_sell_token => { - // We have exceptions for: - // 1. Pre-sign orders where they do not require sufficient balance or allowance. - // The idea is that this allows smart contracts to place orders bundled with - // other transactions that either produce the required balance or set the - // allowance. This would, for example, allow a Gnosis Safe to bundle the - // pre-signature transaction with a WETH wrap and WETH approval to the vault - // relayer contract. - // 2. Orders with flashloan hints that match the sell token, since the flashloan - // will provide the necessary tokens during settlement. + ) if order.signature == Signature::PreSign => { + // Pre-sign orders do not require sufficient balance or allowance. + // The idea is that this allows smart contracts to place orders bundled with + // other transactions that either produce the required balance or set the + // allowance. This would, for example, allow a Gnosis Safe to bundle the + // pre-signature transaction with a WETH wrap and WETH approval to the vault + // relayer contract. return Ok(()); } Err(err) => match err { diff --git a/crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs index 6a7511d8fd..70fdd90c80 100644 --- a/crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs +++ b/crates/shared/src/price_estimation/trade_verifier/balance_overrides/mod.rs @@ -160,10 +160,14 @@ impl FromStr for TokenConfiguration { /// are not available for the quoter. #[async_trait::async_trait] pub trait BalanceOverriding: Send + Sync + 'static { - async fn state_override(&self, request: BalanceOverrideRequest) -> Option; + async fn state_override( + &self, + request: BalanceOverrideRequest, + ) -> Option<(Address, StateOverride)>; } /// Parameters for computing a balance override request. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct BalanceOverrideRequest { /// The token for the override. pub token: Address, @@ -237,6 +241,17 @@ pub struct BalanceOverrides { } impl BalanceOverrides { + /// Creates a new instance with sensible defaults. + pub fn new(web3: ethrpc::Web3) -> Self { + Self { + hardcoded: Default::default(), + detector: Some(( + Detector::new(web3, 60), + Mutex::new(SizedCache::with_size(1000)), + )), + } + } + async fn cached_detection(&self, token: Address) -> Option { let (detector, cache) = self.detector.as_ref()?; tracing::trace!(?token, "attempting to auto-detect"); @@ -272,7 +287,10 @@ impl BalanceOverrides { #[async_trait::async_trait] impl BalanceOverriding for BalanceOverrides { - async fn state_override(&self, request: BalanceOverrideRequest) -> Option { + async fn state_override( + &self, + request: BalanceOverrideRequest, + ) -> Option<(Address, StateOverride)> { let strategy = if let Some(strategy) = self.hardcoded.get(&request.token) { tracing::trace!(token = ?request.token, "using pre-configured balance override strategy"); Some(strategy.clone()) @@ -283,10 +301,27 @@ impl BalanceOverriding for BalanceOverrides { let (key, value) = strategy.state_override(&request.holder, &request.amount); tracing::trace!(?strategy, ?key, ?value, "overriding token balance"); - Some(StateOverride { - state_diff: Some(hashmap! { key => value }), - ..Default::default() - }) + Some(( + request.token, + StateOverride { + state_diff: Some(hashmap! { key => value }), + ..Default::default() + }, + )) + } +} + +/// Balance overrider that always returns `None`. That can be +/// useful for testing. +pub struct DummyOverrider; + +#[async_trait::async_trait] +impl BalanceOverriding for DummyOverrider { + async fn state_override( + &self, + _request: BalanceOverrideRequest, + ) -> Option<(Address, StateOverride)> { + None } } @@ -296,9 +331,10 @@ mod tests { #[tokio::test] async fn balance_override_computation() { + let cow = addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"); let balance_overrides = BalanceOverrides { hardcoded: hashmap! { - addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB") => Strategy::SolidityMapping { + cow => Strategy::SolidityMapping { slot: U256::from(0), }, }, @@ -308,18 +344,21 @@ mod tests { assert_eq!( balance_overrides .state_override(BalanceOverrideRequest { - token: addr!("DEf1CA1fb7FBcDC777520aa7f396b4E015F497aB"), + token: cow, holder: addr!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"), amount: 0x42_u64.into(), }) .await, - Some(StateOverride { - state_diff: Some(hashmap! { - H256(hex!("fca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33")) => - H256(hex!("0000000000000000000000000000000000000000000000000000000000000042")), - }), - ..Default::default() - }), + Some(( + cow, + StateOverride { + state_diff: Some(hashmap! { + H256(hex!("fca351f4d96129454cfc8ef7930b638ac71fea35eb69ee3b8d959496beb04a33")) => + H256(hex!("0000000000000000000000000000000000000000000000000000000000000042")), + }), + ..Default::default() + } + )), ); // You can verify the state override computation is correct by running: @@ -364,9 +403,10 @@ mod tests { #[tokio::test] async fn balance_override_computation_solady() { + let token = addr!("0000000000c5dc95539589fbd24be07c6c14eca4"); let balance_overrides = BalanceOverrides { hardcoded: hashmap! { - addr!("0000000000c5dc95539589fbd24be07c6c14eca4") => Strategy::SoladyMapping, + token => Strategy::SoladyMapping, }, ..Default::default() }; @@ -374,18 +414,21 @@ mod tests { assert_eq!( balance_overrides .state_override(BalanceOverrideRequest { - token: addr!("0000000000c5dc95539589fbd24be07c6c14eca4"), + token, holder: addr!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"), amount: 0x42_u64.into(), }) .await, - Some(StateOverride { - state_diff: Some(hashmap! { - H256(hex!("f6a6656ed2d14bad3cdd3e8871db3f535a136a1b6cd5ae2dced8eb813f3d4e4f")) => - H256(hex!("0000000000000000000000000000000000000000000000000000000000000042")), - }), - ..Default::default() - }), + Some(( + token, + StateOverride { + state_diff: Some(hashmap! { + H256(hex!("f6a6656ed2d14bad3cdd3e8871db3f535a136a1b6cd5ae2dced8eb813f3d4e4f")) => + H256(hex!("0000000000000000000000000000000000000000000000000000000000000042")), + }), + ..Default::default() + } + )), ); // You can verify the state override computation is correct by running: diff --git a/crates/shared/src/price_estimation/trade_verifier/mod.rs b/crates/shared/src/price_estimation/trade_verifier/mod.rs index 5a534566db..e3f1149c21 100644 --- a/crates/shared/src/price_estimation/trade_verifier/mod.rs +++ b/crates/shared/src/price_estimation/trade_verifier/mod.rs @@ -311,9 +311,9 @@ impl TradeVerifier { }) .await { - Some(solver_balance_override) => { + Some((token, solver_balance_override)) => { tracing::trace!(?solver_balance_override, "solver balance override enabled"); - overrides.insert(query.sell_token, solver_balance_override); + overrides.insert(token, solver_balance_override); if verification.from.is_zero() { verification.from = H160::random(); From 02c317f89d53c168bc616ae38887b92495ddcc78 Mon Sep 17 00:00:00 2001 From: Martin Magnus Date: Fri, 19 Sep 2025 15:08:13 +0200 Subject: [PATCH 051/112] fake flashloan in EIP 1271 signature check (#3675) # Description Similar to #3674 but now we fake the effects of a flashloan to fix our EIP-1271 signature verification. # Changes - added `Arc` to compute the necessary state overrides to make EIP1271 signature verification pass when it relies on the flashloan - boiler plate to get the `Arc` to where it needs to be - added `balance_override` to `SignatureCheck` struct ## How to test should be tested with a follow up e2e test --- crates/autopilot/src/run.rs | 5 +- crates/autopilot/src/solvable_orders.rs | 6 +++ crates/cow-amm/src/amm.rs | 1 + .../src/domain/competition/pre_processing.rs | 1 + crates/driver/src/infra/blockchain/mod.rs | 14 +++++- crates/orderbook/src/run.rs | 4 +- crates/shared/src/order_validation.rs | 9 ++++ crates/shared/src/signature_validator/mod.rs | 18 ++++++- .../src/signature_validator/simulation.rs | 50 ++++++++++++------- 9 files changed, 85 insertions(+), 23 deletions(-) diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 184a180a17..08c089f1ae 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -241,6 +241,7 @@ pub async fn run(args: Arguments) { let chain = Chain::try_from(chain_id).expect("incorrect chain ID"); + let balance_overrider = args.price_estimation.balance_overrides.init(web3.clone()); let signature_validator = signature_validator::validator( &web3, signature_validator::Contracts { @@ -248,9 +249,9 @@ pub async fn run(args: Arguments) { signatures: eth.contracts().signatures().clone(), vault_relayer, }, + balance_overrider.clone(), ); - let overrider = args.price_estimation.balance_overrides.init(web3.clone()); let balance_fetcher = account_balances::cached( &web3, BalanceSimulator::new( @@ -258,7 +259,7 @@ pub async fn run(args: Arguments) { eth.contracts().balances().clone(), vault_relayer, vault.as_ref().map(|contract| contract.address()), - overrider, + balance_overrider, ), eth.current_block().clone(), ); diff --git a/crates/autopilot/src/solvable_orders.rs b/crates/autopilot/src/solvable_orders.rs index dae4b4fe07..f63f8d8b91 100644 --- a/crates/autopilot/src/solvable_orders.rs +++ b/crates/autopilot/src/solvable_orders.rs @@ -482,6 +482,9 @@ async fn find_invalid_signature_orders( hash, signature: signature.clone(), interactions: order.interactions.pre.clone(), + // TODO delete balance and signature logic in the autopilot + // altogether + balance_override: None, }) .await { @@ -1251,6 +1254,7 @@ mod tests { value: U256::zero(), call_data: vec![5, 6], }], + balance_override: None, })) .returning(|_| Ok(())); signature_validator @@ -1260,6 +1264,7 @@ mod tests { hash: [4; 32], signature: vec![4, 4, 4, 4], interactions: vec![], + balance_override: None, })) .returning(|_| Err(SignatureValidationError::Invalid)); signature_validator @@ -1269,6 +1274,7 @@ mod tests { hash: [5; 32], signature: vec![5, 5, 5, 5, 5], interactions: vec![], + balance_override: None, })) .returning(|_| Ok(())); diff --git a/crates/cow-amm/src/amm.rs b/crates/cow-amm/src/amm.rs index 1de783615f..41176fabb4 100644 --- a/crates/cow-amm/src/amm.rs +++ b/crates/cow-amm/src/amm.rs @@ -72,6 +72,7 @@ impl Amm { hash, signature: template.signature.to_bytes(), interactions: template.pre_interactions.clone(), + balance_override: None, }) .await .context("invalid signature")?; diff --git a/crates/driver/src/domain/competition/pre_processing.rs b/crates/driver/src/domain/competition/pre_processing.rs index 781ca8eced..34c751614b 100644 --- a/crates/driver/src/domain/competition/pre_processing.rs +++ b/crates/driver/src/domain/competition/pre_processing.rs @@ -131,6 +131,7 @@ impl DataAggregator { vault_relayer: eth.contracts().vault_relayer().0, signatures: eth.contracts().signatures().clone(), }, + eth.balance_overrider(), ); Self { diff --git a/crates/driver/src/infra/blockchain/mod.rs b/crates/driver/src/infra/blockchain/mod.rs index 78fd084ed1..52ce057aae 100644 --- a/crates/driver/src/infra/blockchain/mod.rs +++ b/crates/driver/src/infra/blockchain/mod.rs @@ -6,7 +6,10 @@ use { ethrpc::{Web3, block_stream::CurrentBlockWatcher}, shared::{ account_balances::{BalanceSimulator, SimulationError}, - price_estimation::trade_verifier::balance_overrides::BalanceOverrides, + price_estimation::trade_verifier::balance_overrides::{ + BalanceOverrides, + BalanceOverriding, + }, }, std::{fmt, sync::Arc, time::Duration}, thiserror::Error, @@ -78,6 +81,7 @@ struct Inner { gas: Arc, current_block: CurrentBlockWatcher, balance_simulator: BalanceSimulator, + balance_overrider: Arc, } impl Ethereum { @@ -115,12 +119,13 @@ impl Ethereum { ) .await .expect("could not initialize important smart contracts"); + let balance_overrider = Arc::new(BalanceOverrides::new(web3.clone())); let balance_simulator = BalanceSimulator::new( contracts.settlement().clone(), contracts.balance_helper().clone(), contracts.vault_relayer().0, Some(contracts.vault().address()), - Arc::new(BalanceOverrides::new(web3.clone())), + balance_overrider.clone(), ); Self { @@ -130,6 +135,7 @@ impl Ethereum { contracts, gas, balance_simulator, + balance_overrider, }), web3, } @@ -143,6 +149,10 @@ impl Ethereum { &self.inner.balance_simulator } + pub fn balance_overrider(&self) -> Arc { + Arc::clone(&self.inner.balance_overrider) + } + /// Clones self and returns an instance that captures metrics extended with /// the provided label. pub fn with_metric_label(&self, label: String) -> Self { diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 778d68e15d..0d577f2ecb 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -132,6 +132,7 @@ pub async fn run(args: Arguments) { let chain = Chain::try_from(chain_id).expect("incorrect chain ID"); + let balance_overrider = args.price_estimation.balance_overrides.init(web3.clone()); let signature_validator = signature_validator::validator( &web3, signature_validator::Contracts { @@ -139,6 +140,7 @@ pub async fn run(args: Arguments) { signatures: signatures_contract, vault_relayer, }, + balance_overrider.clone(), ); let vault = match args.shared.balancer_v2_vault_address { @@ -175,7 +177,7 @@ pub async fn run(args: Arguments) { balances_contract.clone(), vault_relayer, vault.as_ref().map(|contract| contract.address()), - args.price_estimation.balance_overrides.init(web3.clone()), + balance_overrider, ), ); diff --git a/crates/shared/src/order_validation.rs b/crates/shared/src/order_validation.rs index 13e7025f11..d0179a910f 100644 --- a/crates/shared/src/order_validation.rs +++ b/crates/shared/src/order_validation.rs @@ -589,6 +589,13 @@ impl OrderValidating for OrderValidator { hash, signature: signature.to_owned(), interactions: app_data.interactions.pre.clone(), + balance_override: app_data.inner.protocol.flashloan.as_ref().map(|loan| { + BalanceOverrideRequest { + token: loan.token, + holder: loan.borrower.unwrap_or(owner), + amount: loan.amount, + } + }), }) .await .map_err(|err| match err { @@ -1400,6 +1407,7 @@ mod tests { hash: order_hash, signature: vec![1, 2, 3], interactions: pre_interactions.clone(), + balance_override: None, })) .returning(|_| Ok(0u64)); @@ -1428,6 +1436,7 @@ mod tests { hash: order_hash, signature: vec![1, 2, 3], interactions: pre_interactions.clone(), + balance_override: None, })) .returning(|_| Err(SignatureValidationError::Invalid)); diff --git a/crates/shared/src/signature_validator/mod.rs b/crates/shared/src/signature_validator/mod.rs index 6a74ff254a..8a829cf5e9 100644 --- a/crates/shared/src/signature_validator/mod.rs +++ b/crates/shared/src/signature_validator/mod.rs @@ -1,4 +1,8 @@ use { + crate::price_estimation::trade_verifier::balance_overrides::{ + BalanceOverrideRequest, + BalanceOverriding, + }, ethcontract::Bytes, ethrpc::Web3, hex_literal::hex, @@ -17,6 +21,13 @@ pub struct SignatureCheck { pub hash: [u8; 32], pub signature: Vec, pub interactions: Vec, + pub balance_override: Option, +} + +impl SignatureCheck { + fn requires_setup(&self) -> bool { + !self.interactions.is_empty() || self.balance_override.is_some() + } } impl std::fmt::Debug for SignatureCheck { @@ -80,11 +91,16 @@ pub struct Contracts { } /// Creates the default [`SignatureValidating`] instance. -pub fn validator(web3: &Web3, contracts: Contracts) -> Arc { +pub fn validator( + web3: &Web3, + contracts: Contracts, + balance_overrider: Arc, +) -> Arc { Arc::new(simulation::Validator::new( web3, contracts.settlement, contracts.signatures, contracts.vault_relayer, + balance_overrider, )) } diff --git a/crates/shared/src/signature_validator/simulation.rs b/crates/shared/src/signature_validator/simulation.rs index 0158639661..60137dfb8f 100644 --- a/crates/shared/src/signature_validator/simulation.rs +++ b/crates/shared/src/signature_validator/simulation.rs @@ -5,11 +5,14 @@ use { super::{SignatureCheck, SignatureValidating, SignatureValidationError}, - anyhow::Result, + crate::price_estimation::trade_verifier::balance_overrides::BalanceOverriding, + alloy::{dyn_abi::SolType, sol_types::sol_data}, + anyhow::{Context, Result}, contracts::{ERC1271SignatureValidator, errors::EthcontractErrorType}, - ethcontract::Bytes, - ethrpc::Web3, + ethcontract::{Bytes, state_overrides::StateOverrides}, + ethrpc::{Web3, alloy::conversions::IntoLegacy}, primitive_types::{H160, U256}, + std::sync::Arc, tracing::instrument, }; @@ -18,6 +21,7 @@ pub struct Validator { settlement: contracts::GPv2Settlement, vault_relayer: H160, web3: Web3, + balance_overrider: Arc, } impl Validator { @@ -29,6 +33,7 @@ impl Validator { settlement: contracts::GPv2Settlement, signatures: contracts::support::Signatures, vault_relayer: H160, + balance_overrider: Arc, ) -> Self { let web3 = ethrpc::instrumented::instrument_with_label(web3, "signatureValidation".into()); Self { @@ -36,12 +41,13 @@ impl Validator { settlement, vault_relayer, web3: web3.clone(), + balance_overrider, } } - /// Simulate isValidSignature for the cases in which the order does not have - /// pre-interactions - async fn simulate_without_pre_interactions( + /// Simulate isValidSignature for the cases in which the order does not + /// require a "setup" in the form of pre-interactions or a flashloan. + async fn simulate_without_setup( &self, check: SignatureCheck, ) -> Result<(), SignatureValidationError> { @@ -69,6 +75,15 @@ impl Validator { &self, check: SignatureCheck, ) -> Result { + let overrides: StateOverrides = match check.balance_override { + Some(overrides) => self + .balance_overrider + .state_override(overrides) + .await + .into_iter() + .collect(), + None => Default::default(), + }; // We simulate the signature verification from the Settlement contract's // context. This allows us to check: // 1. How the pre-interactions would behave as part of the settlement @@ -85,20 +100,21 @@ impl Validator { .map(|i| (i.target, i.value, Bytes(i.call_data))) .collect(), ); - let gas_cost_call = self + let response_bytes = self .settlement .simulate_delegatecall( self.signatures.address(), Bytes(validate_call.tx.data.unwrap_or_default().0), ) - .from(crate::SIMULATION_ACCOUNT.clone()); - let result = gas_cost_call - .tx - .estimate_gas() - .await - .map_err(|_| SignatureValidationError::Invalid); + .from(crate::SIMULATION_ACCOUNT.clone()) + .call_with_state_overrides(&overrides) + .await?; - Ok(Simulation { gas_used: result? }) + let gas_used = >::abi_decode(&response_bytes.0) + .context("could not decode signature check result")? + .into_legacy(); + + Ok(Simulation { gas_used }) } } @@ -109,10 +125,10 @@ impl SignatureValidating for Validator { &self, check: SignatureCheck, ) -> Result<(), SignatureValidationError> { - if check.interactions.is_empty() { - self.simulate_without_pre_interactions(check).await - } else { + if check.requires_setup() { self.simulate(check).await.map(|_| ()) + } else { + self.simulate_without_setup(check).await } } From f86295091e8ee7bac9aba84f04d8aacc3363adea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Mon, 22 Sep 2025 09:42:21 +0100 Subject: [PATCH 052/112] Migrate IUniswapLikeRouter to alloy (#3679) --- crates/contracts/build.rs | 1 - crates/contracts/src/alloy.rs | 1 + crates/contracts/src/lib.rs | 1 - .../src/boundary/liquidity/uniswap/v2.rs | 25 +++++++------ .../driver/src/infra/blockchain/contracts.rs | 6 ---- crates/shared/src/sources/uniswap_v2/mod.rs | 13 ++++--- crates/solver/src/interactions/uniswap_v2.rs | 35 ++++++++++--------- crates/solver/src/liquidity/uniswap_v2.rs | 33 ++++++++++------- 8 files changed, 63 insertions(+), 52 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index 831ec8b28e..d698df00b1 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -585,7 +585,6 @@ fn main() { }); generate_contract("IAavePool"); generate_contract("IFlashLoanSolverWrapper"); - generate_contract("IUniswapLikeRouter"); generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index f8fb20978c..a9d1fa0482 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -438,6 +438,7 @@ crate::bindings!( // Not available on Lens } ); +crate::bindings!(IUniswapLikeRouter); pub use alloy::providers::DynProvider as Provider; diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index 3be1fb9ddf..b04cbe4970 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -71,7 +71,6 @@ include_contracts! { IAavePool; IFlashLoanSolverWrapper; IUniswapLikePair; - IUniswapLikeRouter; IUniswapV3Factory; Permit2; UniswapV3Pool; diff --git a/crates/driver/src/boundary/liquidity/uniswap/v2.rs b/crates/driver/src/boundary/liquidity/uniswap/v2.rs index e6e1caab99..81d8c65058 100644 --- a/crates/driver/src/boundary/liquidity/uniswap/v2.rs +++ b/crates/driver/src/boundary/liquidity/uniswap/v2.rs @@ -8,8 +8,12 @@ use { infra::{self, blockchain::Ethereum}, }, async_trait::async_trait, - contracts::{GPv2Settlement, IUniswapLikeRouter}, - ethrpc::{Web3, block_stream::CurrentBlockWatcher}, + contracts::{GPv2Settlement, alloy::IUniswapLikeRouter}, + ethrpc::{ + Web3, + alloy::conversions::{IntoAlloy, IntoLegacy}, + block_stream::CurrentBlockWatcher, + }, shared::{ http_solver::model::TokenAmount, sources::uniswap_v2::{ @@ -50,13 +54,13 @@ pub fn to_domain(id: liquidity::Id, pool: ConstantProductOrder) -> Result eth::ContractAddress { - pool.settlement_handling + let address = pool + .settlement_handling .as_any() .downcast_ref::() .expect("downcast uniswap settlement handler") - .router() - .address() - .into() + .router(); + eth::ContractAddress::from(address.into_legacy()) } pub(in crate::boundary::liquidity) fn to_domain_pool( @@ -94,7 +98,7 @@ pub fn to_interaction( receiver: ð::Address, ) -> eth::Interaction { let handler = uniswap_v2::Inner::new( - IUniswapLikeRouter::at(ðrpc::dummy::web3(), pool.router.into()), + pool.router.0.into_alloy(), GPv2Settlement::at(ðrpc::dummy::web3(), receiver.0), Mutex::new(Allowances::empty(receiver.0)), ); @@ -132,12 +136,13 @@ where R: PoolReading + Send + Sync + 'static, F: FnOnce(Web3, PairProvider) -> R, { - let router = eth.contract_at::(config.router); + let router = + IUniswapLikeRouter::Instance::new(config.router.0.into_alloy(), eth.web3().alloy.clone()); let settlement = eth.contracts().settlement().clone(); let pool_fetcher = { let factory = router.factory().call().await?; let pair_provider = PairProvider { - factory, + factory: factory.into_legacy(), init_code_digest: config.pool_code.into(), }; @@ -155,7 +160,7 @@ where }; Ok(Box::new(UniswapLikeLiquidity::with_allowances( - router, + *router.address(), settlement, Box::new(NoAllowanceManaging), pool_fetcher, diff --git a/crates/driver/src/infra/blockchain/contracts.rs b/crates/driver/src/infra/blockchain/contracts.rs index f3a767aeae..fb14397956 100644 --- a/crates/driver/src/infra/blockchain/contracts.rs +++ b/crates/driver/src/infra/blockchain/contracts.rs @@ -251,12 +251,6 @@ pub trait ContractAt { fn at(eth: &Ethereum, address: eth::ContractAddress) -> Self; } -impl ContractAt for contracts::IUniswapLikeRouter { - fn at(eth: &Ethereum, address: eth::ContractAddress) -> Self { - Self::at(ð.web3, address.0) - } -} - impl ContractAt for contracts::ERC20 { fn at(eth: &Ethereum, address: eth::ContractAddress) -> Self { Self::at(ð.web3, address.into()) diff --git a/crates/shared/src/sources/uniswap_v2/mod.rs b/crates/shared/src/sources/uniswap_v2/mod.rs index fc0cd7138b..3e4ed07714 100644 --- a/crates/shared/src/sources/uniswap_v2/mod.rs +++ b/crates/shared/src/sources/uniswap_v2/mod.rs @@ -14,9 +14,9 @@ use { sources::{BaselineSource, swapr::SwaprPoolReader}, }, anyhow::{Context, Result}, - contracts::IUniswapLikeRouter, + contracts::alloy::IUniswapLikeRouter, ethcontract::{H160, H256}, - ethrpc::alloy::conversions::IntoLegacy, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, hex_literal::hex, std::{fmt::Display, str::FromStr, sync::Arc}, }; @@ -56,7 +56,7 @@ enum PoolReadingStyle { } pub struct UniV2BaselineSource { - pub router: IUniswapLikeRouter, + pub router: IUniswapLikeRouter::Instance, pub pair_provider: PairProvider, pub pool_fetching: Arc, } @@ -110,10 +110,13 @@ impl UniV2BaselineSourceParameters { pub async fn into_source(&self, web3: &Web3) -> Result { let web3 = ethrpc::instrumented::instrument_with_label(web3, "uniswapV2".into()); - let router = contracts::IUniswapLikeRouter::at(&web3, self.router); + let router = contracts::alloy::IUniswapLikeRouter::Instance::new( + self.router.into_alloy(), + web3.alloy.clone(), + ); let factory = router.factory().call().await.context("factory")?; let pair_provider = pair_provider::PairProvider { - factory, + factory: factory.into_legacy(), init_code_digest: self.init_code_digest.0, }; let pool_reader = DefaultPoolReader::new(web3.clone(), pair_provider); diff --git a/crates/solver/src/interactions/uniswap_v2.rs b/crates/solver/src/interactions/uniswap_v2.rs index b2f0e48538..86f9dbc20a 100644 --- a/crates/solver/src/interactions/uniswap_v2.rs +++ b/crates/solver/src/interactions/uniswap_v2.rs @@ -1,13 +1,15 @@ use { - contracts::{GPv2Settlement, IUniswapLikeRouter}, + alloy::{primitives::Address, sol_types::SolCall}, + contracts::{GPv2Settlement, alloy::IUniswapLikeRouter}, ethcontract::Bytes, + ethrpc::alloy::conversions::{IntoAlloy, IntoLegacy}, primitive_types::{H160, U256}, shared::interaction::{EncodedInteraction, Interaction}, }; #[derive(Debug)] pub struct UniswapInteraction { - pub router: IUniswapLikeRouter, + pub router: Address, pub settlement: GPv2Settlement, pub amount_out: U256, pub amount_in_max: U256, @@ -23,15 +25,15 @@ impl Interaction for UniswapInteraction { impl UniswapInteraction { pub fn encode_swap(&self) -> EncodedInteraction { - let method = self.router.swap_tokens_for_exact_tokens( - self.amount_out, - self.amount_in_max, - vec![self.token_in, self.token_out], - self.settlement.address(), - U256::MAX, - ); - let calldata = method.tx.data.expect("no calldata").0; - (self.router.address(), 0.into(), Bytes(calldata)) + let calldata = IUniswapLikeRouter::IUniswapLikeRouter::swapTokensForExactTokensCall { + amountOut: self.amount_out.into_alloy(), + amountInMax: self.amount_in_max.into_alloy(), + path: vec![self.token_in.into_alloy(), self.token_out.into_alloy()], + to: self.settlement.address().into_alloy(), + deadline: ::alloy::primitives::U256::MAX, + } + .abi_encode(); + (self.router.into_legacy(), 0.into(), Bytes(calldata)) } } @@ -39,8 +41,8 @@ impl UniswapInteraction { mod tests { use { super::*, - contracts::{IUniswapLikeRouter, dummy_contract}, - ethrpc::dummy, + alloy::primitives::Address, + ethrpc::{alloy::conversions::IntoLegacy, dummy}, hex_literal::hex, }; @@ -57,11 +59,12 @@ mod tests { let token_in = H160::from_low_u64_be(7); let token_out = 8; let payout_to = 9u8; - let router = dummy_contract!(IUniswapLikeRouter, H160::from_low_u64_be(4)); + + let router_address = Address::from(&[1u8; 20]); let settlement = GPv2Settlement::at(&dummy::web3(), H160::from_low_u64_be(payout_to as u64)); let interaction = UniswapInteraction { - router: router.clone(), + router: router_address, settlement, amount_out: amount_out.into(), amount_in_max: amount_in_max.into(), @@ -71,7 +74,7 @@ mod tests { let swap_call = interaction.encode(); // Verify Swap - assert_eq!(swap_call.0, router.address()); + assert_eq!(swap_call.0, router_address.into_legacy()); let call = &swap_call.2.0; let swap_signature = hex!("8803dbee"); let path_offset = 160; diff --git a/crates/solver/src/liquidity/uniswap_v2.rs b/crates/solver/src/liquidity/uniswap_v2.rs index dc656096e8..79566bb3af 100644 --- a/crates/solver/src/liquidity/uniswap_v2.rs +++ b/crates/solver/src/liquidity/uniswap_v2.rs @@ -9,8 +9,10 @@ use { liquidity_collector::LiquidityCollecting, settlement::SettlementEncoder, }, + alloy::primitives::Address, anyhow::Result, - contracts::{GPv2Settlement, IUniswapLikeRouter}, + contracts::GPv2Settlement, + ethrpc::alloy::conversions::IntoLegacy, model::TokenPair, primitive_types::H160, shared::{ @@ -33,7 +35,7 @@ pub struct UniswapLikeLiquidity { } pub struct Inner { - router: IUniswapLikeRouter, + router: Address, gpv2_settlement: GPv2Settlement, // Mapping of how much allowance the router has per token to spend on behalf of the settlement // contract @@ -42,7 +44,7 @@ pub struct Inner { impl UniswapLikeLiquidity { pub fn new( - router: IUniswapLikeRouter, + router: Address, gpv2_settlement: GPv2Settlement, web3: Web3, pool_fetcher: Arc, @@ -53,17 +55,16 @@ impl UniswapLikeLiquidity { } pub fn with_allowances( - router: IUniswapLikeRouter, + router: Address, gpv2_settlement: GPv2Settlement, settlement_allowances: Box, pool_fetcher: Arc, ) -> Self { - let router_address = router.address(); Self { inner: Arc::new(Inner { router, gpv2_settlement, - allowances: Mutex::new(Allowances::empty(router_address)), + allowances: Mutex::new(Allowances::empty(router.into_legacy())), }), pool_fetcher, settlement_allowances, @@ -71,7 +72,7 @@ impl UniswapLikeLiquidity { } async fn cache_allowances(&self, tokens: HashSet) -> Result<()> { - let router = self.inner.router.address(); + let router = self.inner.router.into_legacy(); let allowances = self .settlement_allowances .get_allowances(tokens, router) @@ -118,7 +119,7 @@ impl LiquidityCollecting for UniswapLikeLiquidity { impl Inner { pub fn new( - router: IUniswapLikeRouter, + router: Address, gpv2_settlement: GPv2Settlement, allowances: Mutex, ) -> Self { @@ -143,7 +144,7 @@ impl Inner { ( approval, UniswapInteraction { - router: self.router.clone(), + router: self.router, settlement: self.gpv2_settlement.clone(), amount_out: token_amount_out.amount, amount_in_max: token_amount_in_max.amount, @@ -153,8 +154,8 @@ impl Inner { ) } - pub fn router(&self) -> &IUniswapLikeRouter { - &self.router + pub fn router(&self) -> Address { + self.router } } @@ -180,12 +181,18 @@ impl SettlementHandling for Inner { #[cfg(test)] mod tests { - use {super::*, contracts::dummy_contract, primitive_types::U256, std::collections::HashMap}; + use { + super::*, + alloy::primitives::Address, + contracts::dummy_contract, + primitive_types::U256, + std::collections::HashMap, + }; impl Inner { fn new_dummy(allowances: HashMap) -> Self { Self { - router: dummy_contract!(IUniswapLikeRouter, H160::zero()), + router: Address::default(), gpv2_settlement: dummy_contract!(GPv2Settlement, H160::zero()), allowances: Mutex::new(Allowances::new(H160::zero(), allowances)), } From 9e365476b473dbebde70efe90e70f2ad326bd182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= Date: Mon, 22 Sep 2025 10:12:22 +0100 Subject: [PATCH 053/112] Migrate IUniswapLikePair to alloy (#3685) --- crates/contracts/build.rs | 1 - crates/contracts/src/alloy.rs | 1 + crates/contracts/src/errors.rs | 7 +- crates/contracts/src/lib.rs | 1 - crates/driver/src/tests/setup/blockchain.rs | 54 ++++++--------- .../e2e/src/setup/onchain_components/mod.rs | 19 +++--- crates/shared/src/sources/swapr.rs | 23 ++----- .../src/sources/uniswap_v2/pool_fetching.rs | 66 ++++++++++++++----- 8 files changed, 92 insertions(+), 80 deletions(-) diff --git a/crates/contracts/build.rs b/crates/contracts/build.rs index d698df00b1..501e7bbe02 100644 --- a/crates/contracts/build.rs +++ b/crates/contracts/build.rs @@ -585,7 +585,6 @@ fn main() { }); generate_contract("IAavePool"); generate_contract("IFlashLoanSolverWrapper"); - generate_contract("IUniswapLikePair"); // EIP-1271 contract - SignatureValidator generate_contract("ERC1271SignatureValidator"); generate_contract_with_config("UniswapV3SwapRouterV2", |builder| { diff --git a/crates/contracts/src/alloy.rs b/crates/contracts/src/alloy.rs index a9d1fa0482..2cfde3ff46 100644 --- a/crates/contracts/src/alloy.rs +++ b/crates/contracts/src/alloy.rs @@ -439,6 +439,7 @@ crate::bindings!( } ); crate::bindings!(IUniswapLikeRouter); +crate::bindings!(IUniswapLikePair); pub use alloy::providers::DynProvider as Provider; diff --git a/crates/contracts/src/errors.rs b/crates/contracts/src/errors.rs index 01b33f6e28..d7ca2fa8dd 100644 --- a/crates/contracts/src/errors.rs +++ b/crates/contracts/src/errors.rs @@ -57,11 +57,16 @@ pub fn testing_contract_error() -> MethodError { } } -/// Create an arbitrary alloy error. Useful for testing. +/// Create an arbitrary alloy error that will convert into a "contract" error. Useful for testing. pub fn testing_alloy_contract_error() -> alloy::contract::Error { alloy::contract::Error::NotADeploymentTransaction } +/// Create an arbitrary alloy error that will convert into a "node" error. Useful for testing. +pub fn testing_alloy_node_error() -> alloy::contract::Error { + alloy::contract::Error::TransportError(alloy::transports::TransportError::NullResp) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/contracts/src/lib.rs b/crates/contracts/src/lib.rs index b04cbe4970..c43405931b 100644 --- a/crates/contracts/src/lib.rs +++ b/crates/contracts/src/lib.rs @@ -70,7 +70,6 @@ include_contracts! { HooksTrampoline; IAavePool; IFlashLoanSolverWrapper; - IUniswapLikePair; IUniswapV3Factory; Permit2; UniswapV3Pool; diff --git a/crates/driver/src/tests/setup/blockchain.rs b/crates/driver/src/tests/setup/blockchain.rs index 5cc3bcf01e..d55b0c752f 100644 --- a/crates/driver/src/tests/setup/blockchain.rs +++ b/crates/driver/src/tests/setup/blockchain.rs @@ -31,7 +31,7 @@ use { pub struct Pair { token_a: &'static str, token_b: &'static str, - contract: contracts::IUniswapLikePair, + contract: contracts::alloy::IUniswapLikePair::Instance, pool: Pool, } @@ -480,14 +480,13 @@ impl Blockchain { .await .unwrap(); // Fund the pair and the settlement contract. - let pair = contracts::IUniswapLikePair::at( - &web3, + let pair = contracts::alloy::IUniswapLikePair::Instance::new( uniswap_factory .getPair(token_a.into_alloy(), token_b.into_alloy()) .call() .await - .unwrap() - .into_legacy(), + .unwrap(), + web3.alloy.clone(), ); pairs.push(Pair { token_a: pool.reserve_a.token, @@ -498,7 +497,7 @@ impl Blockchain { if pool.reserve_a.token == "WETH" { wait_for( &web3, - weth.transfer(pair.address(), pool.reserve_a.amount) + weth.transfer(pair.address().into_legacy(), pool.reserve_a.amount) .from(primary_account(&web3).await) .send(), ) @@ -539,10 +538,7 @@ impl Blockchain { tokens .get(pool.reserve_a.token) .unwrap() - .mint( - pair.address().into_alloy(), - pool.reserve_a.amount.into_alloy(), - ) + .mint(*pair.address(), pool.reserve_a.amount.into_alloy()) .from(main_trader_account.address().into_alloy()) .send_and_watch() .await @@ -577,7 +573,7 @@ impl Blockchain { if pool.reserve_b.token == "WETH" { wait_for( &web3, - weth.transfer(pair.address(), pool.reserve_b.amount) + weth.transfer(pair.address().into_legacy(), pool.reserve_b.amount) .from(primary_account(&web3).await) .send(), ) @@ -618,10 +614,7 @@ impl Blockchain { tokens .get(pool.reserve_b.token) .unwrap() - .mint( - pair.address().into_alloy(), - pool.reserve_b.amount.into_alloy(), - ) + .mint(*pair.address(), pool.reserve_b.amount.into_alloy()) .from(main_trader_account.address().into_alloy()) .send_and_watch() .await @@ -652,16 +645,11 @@ impl Blockchain { .unwrap(); } } - wait_for( - &web3, - pair.mint( - "0x8270bA71b28CF60859B547A2346aCDE824D6ed40" - .parse() - .unwrap(), - ) - .from(main_trader_account.clone()) - .send(), - ) + pair.mint(::alloy::primitives::address!( + "0x8270bA71b28CF60859B547A2346aCDE824D6ed40" + )) + .from(main_trader_account.address().into_alloy()) + .send_and_watch() .await .unwrap(); } @@ -827,7 +815,7 @@ impl Blockchain { // Create the interactions fulfilling the order. let transfer_interaction = sell_token - .transfer(pair.contract.address(), execution.sell) + .transfer(pair.contract.address().into_legacy(), execution.sell) .tx .data .unwrap() @@ -847,15 +835,13 @@ impl Blockchain { let swap_interaction = pair .contract .swap( - amount_0_out, - amount_1_out, - self.settlement.address(), + amount_0_out.into_alloy(), + amount_1_out.into_alloy(), + self.settlement.address().into_alloy(), Default::default(), ) - .tx - .data - .unwrap() - .0; + .calldata() + .to_vec(); fulfillments.push(Fulfillment { quoted_order: self.quote(order), execution: execution.clone(), @@ -874,7 +860,7 @@ impl Blockchain { internalize: false, }, Interaction { - address: pair.contract.address(), + address: pair.contract.address().into_legacy(), calldata: match solution.calldata { super::Calldata::Valid { .. } => swap_interaction, super::Calldata::Invalid => { diff --git a/crates/e2e/src/setup/onchain_components/mod.rs b/crates/e2e/src/setup/onchain_components/mod.rs index 2677b540b3..92ca3026ad 100644 --- a/crates/e2e/src/setup/onchain_components/mod.rs +++ b/crates/e2e/src/setup/onchain_components/mod.rs @@ -567,21 +567,20 @@ impl OnchainComponents { /// /// This can be used to modify the pool reserves during a test. pub async fn mint_token_to_weth_uni_v2_pool(&self, token: &MintableToken, amount: U256) { - let pair = contracts::IUniswapLikePair::at( - &self.web3, + let pair = contracts::alloy::IUniswapLikePair::Instance::new( self.contracts .uniswap_v2_factory .getPair(self.contracts.weth.address().into_alloy(), *token.address()) .call() .await - .expect("failed to get Uniswap V2 pair") - .into_legacy(), + .expect("failed to get Uniswap V2 pair"), + self.web3.alloy.clone(), ); assert!(!pair.address().is_zero(), "Uniswap V2 pair is not deployed"); // Mint amount + 1 to the pool, and then swap out 1 of the minted token // in order to force it to update its K-value. - token.mint(pair.address(), amount + 1).await; + token.mint(pair.address().into_legacy(), amount + 1).await; let (out0, out1) = if TokenPair::new(self.contracts.weth.address(), token.address().into_legacy()) .unwrap() @@ -594,13 +593,13 @@ impl OnchainComponents { (0, 1) }; pair.swap( - out0.into(), - out1.into(), - token.minter.address(), + ::alloy::primitives::U256::from(out0), + ::alloy::primitives::U256::from(out1), + token.minter.address().into_alloy(), Default::default(), ) - .from(token.minter.clone()) - .send() + .from(token.minter.address().into_alloy()) + .send_and_watch() .await .expect("Uniswap V2 pair couldn't mint"); } diff --git a/crates/shared/src/sources/swapr.rs b/crates/shared/src/sources/swapr.rs index f1ed9f203e..2896817333 100644 --- a/crates/shared/src/sources/swapr.rs +++ b/crates/shared/src/sources/swapr.rs @@ -1,8 +1,12 @@ //! A pool state reading implementation specific to Swapr. use { - crate::sources::uniswap_v2::pool_fetching::{DefaultPoolReader, Pool, PoolReading}, - alloy::sol_types::GenericContractError, + crate::sources::uniswap_v2::pool_fetching::{ + DefaultPoolReader, + Pool, + PoolReading, + handle_alloy_contract_error, + }, anyhow::Result, contracts::alloy::ISwaprPair, ethcontract::BlockId, @@ -42,20 +46,7 @@ fn handle_results( pool: Result>, fee: Result, ) -> Result> { - use alloy::contract::Error; - let fee = match fee { - Ok(fee) => Some(fee), - // Alloy "hides" the contract execution errors under the transport error - Err(err @ Error::TransportError(_)) => { - // So we need to try to decode the error as a generic contract error, - // we return in case it isn't a contract error - match err.try_decode_into_interface_error::() { - Ok(_) => None, // contract error - Err(err) => return Err(err)?, - } - } - Err(_) => None, - }; + let fee = handle_alloy_contract_error(fee)?; Ok(pool?.and_then(|pool| { Some(Pool { fee: Ratio::new(fee?, FEE_BASE), diff --git a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs index f6428fcd27..e2801e2ea9 100644 --- a/crates/shared/src/sources/uniswap_v2/pool_fetching.rs +++ b/crates/shared/src/sources/uniswap_v2/pool_fetching.rs @@ -1,10 +1,16 @@ use { super::pair_provider::PairProvider, crate::{baseline_solver::BaselineSolvable, ethrpc::Web3, recent_block_cache::Block}, + alloy::sol_types::GenericContractError, anyhow::Result, cached::{Cached, TimedCache}, - contracts::{ERC20, IUniswapLikePair, errors::EthcontractErrorType}, + contracts::{ + ERC20, + alloy::IUniswapLikePair::{self, IUniswapLikePair::getReservesReturn}, + errors::EthcontractErrorType, + }, ethcontract::{BlockId, H160, U256, errors::MethodError}, + ethrpc::alloy::conversions::IntoAlloy, futures::{ FutureExt as _, future::{self, BoxFuture}, @@ -267,9 +273,6 @@ impl PoolReading for DefaultPoolReader { fn read_state(&self, pair: TokenPair, block: BlockId) -> BoxFuture<'_, Result>> { let pair_address = self.pair_provider.pair_address(&pair); - let pair_contract = IUniswapLikePair::at(&self.web3, pair_address); - let fetch_reserves = pair_contract.get_reserves().block(block).call(); - // Fetch ERC20 token balances of the pools to sanity check with reserves let token0 = ERC20::at(&self.web3, pair.get().0); let token1 = ERC20::at(&self.web3, pair.get().1); @@ -278,8 +281,16 @@ impl PoolReading for DefaultPoolReader { let fetch_token1_balance = token1.balance_of(pair_address).block(block).call(); async move { - let (reserves, token0_balance, token1_balance) = - futures::join!(fetch_reserves, fetch_token0_balance, fetch_token1_balance); + let pair_contract = + IUniswapLikePair::Instance::new(pair_address.into_alloy(), self.web3.alloy.clone()); + let fetch_reserves = pair_contract.getReserves().block(block.into_alloy()); + + let (reserves, token0_balance, token1_balance) = futures::join!( + fetch_reserves.call().into_future(), + fetch_token0_balance, + fetch_token1_balance + ); + handle_results( FetchedPool { pair, @@ -296,7 +307,7 @@ impl PoolReading for DefaultPoolReader { struct FetchedPool { pair: TokenPair, - reserves: Result<(u128, u128, u32), MethodError>, + reserves: Result, token0_balance: Result, token1_balance: Result, } @@ -313,12 +324,35 @@ pub fn handle_contract_error(result: Result) -> Result