From 23cdf79db169167db00920dbf1ca240a91299dd9 Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Tue, 6 Jan 2026 18:31:23 +0400 Subject: [PATCH 1/5] Migrate to alloy from rust-web3 --- Cargo.lock | 4300 ++++++++++++----- Cargo.toml | 3 + chain/ethereum/Cargo.toml | 1 + chain/ethereum/build.rs | 1 + chain/ethereum/proto/ethereum.proto | 701 ++- chain/ethereum/src/adapter.rs | 267 +- chain/ethereum/src/call_helper.rs | 138 + chain/ethereum/src/chain.rs | 16 +- chain/ethereum/src/codec.rs | 931 +++- chain/ethereum/src/data_source.rs | 236 +- chain/ethereum/src/ethereum_adapter.rs | 1301 +++-- chain/ethereum/src/ingestor.rs | 12 +- chain/ethereum/src/lib.rs | 1 + .../src/protobuf/sf.ethereum.r#type.v2.rs | 780 ++- chain/ethereum/src/runtime/abi.rs | 233 +- chain/ethereum/src/runtime/runtime_adapter.rs | 76 +- chain/ethereum/src/tests.rs | 109 +- chain/ethereum/src/transport.rs | 177 +- chain/ethereum/src/trigger.rs | 244 +- chain/near/src/codec.rs | 13 +- chain/near/src/trigger.rs | 8 +- graph/Cargo.toml | 7 +- graph/src/abi/event_ext.rs | 169 + graph/src/abi/function_ext.rs | 303 ++ graph/src/abi/mod.rs | 20 + graph/src/abi/param.rs | 7 + graph/src/abi/value_ext.rs | 277 ++ graph/src/blockchain/mock.rs | 8 +- graph/src/blockchain/mod.rs | 16 +- graph/src/blockchain/types.rs | 156 +- graph/src/cheap_clone.rs | 2 +- graph/src/components/ethereum/mod.rs | 7 +- graph/src/components/ethereum/types.rs | 197 +- graph/src/components/store/mod.rs | 3 +- graph/src/components/store/traits.rs | 10 +- .../subgraph/proof_of_indexing/mod.rs | 22 +- .../subgraph/proof_of_indexing/online.rs | 4 +- graph/src/components/transaction_receipt.rs | 41 +- graph/src/data/graphql/values.rs | 13 +- graph/src/data/store/ethereum.rs | 22 +- graph/src/data/store/scalar/bigint.rs | 28 +- graph/src/data/store/scalar/bytes.rs | 14 +- graph/src/data/subgraph/mod.rs | 2 +- graph/src/data_source/common.rs | 305 +- graph/src/lib.rs | 6 +- graph/src/runtime/mod.rs | 2 +- graph/src/util/mod.rs | 3 + graph/src/util/test_utils.rs | 57 + graphql/src/store/resolver.rs | 7 +- node/src/manager/commands/chain.rs | 16 +- node/src/manager/commands/check_blocks.rs | 33 +- runtime/test/src/common.rs | 8 +- runtime/test/src/test.rs | 9 +- runtime/test/src/test/abi.rs | 42 +- runtime/wasm/Cargo.toml | 1 - runtime/wasm/src/asc_abi/class.rs | 31 +- runtime/wasm/src/host_exports.rs | 41 +- runtime/wasm/src/module/context.rs | 2 +- runtime/wasm/src/module/instance.rs | 2 +- runtime/wasm/src/to_from/external.rs | 167 +- server/index-node/src/resolver.rs | 4 +- store/postgres/src/chain_store.rs | 64 +- store/postgres/src/deployment.rs | 16 +- store/postgres/src/deployment_store.rs | 4 +- store/postgres/src/detail.rs | 5 +- store/postgres/src/store.rs | 4 +- store/postgres/src/subgraph_store.rs | 12 +- store/postgres/src/transaction_receipt.rs | 27 +- store/test-store/Cargo.toml | 1 + store/test-store/src/block_store.rs | 39 +- store/test-store/src/store.rs | 11 +- .../tests/chain/ethereum/manifest.rs | 12 +- store/test-store/tests/graph/entity_cache.rs | 4 +- store/test-store/tests/postgres/chain_head.rs | 20 +- store/test-store/tests/postgres/relational.rs | 11 +- .../tests/postgres/relational_bytes.rs | 13 +- store/test-store/tests/postgres/store.rs | 42 +- store/test-store/tests/postgres/writable.rs | 4 +- tests/Cargo.toml | 4 + tests/src/contract.rs | 21 +- tests/src/fixture/ethereum.rs | 116 +- tests/src/fixture/mod.rs | 5 +- tests/tests/integration_tests.rs | 2 +- tests/tests/runner_tests.rs | 9 +- 84 files changed, 8348 insertions(+), 3710 deletions(-) create mode 100644 chain/ethereum/src/call_helper.rs create mode 100644 graph/src/abi/event_ext.rs create mode 100644 graph/src/abi/function_ext.rs create mode 100644 graph/src/abi/mod.rs create mode 100644 graph/src/abi/param.rs create mode 100644 graph/src/abi/value_ext.rs create mode 100644 graph/src/util/test_utils.rs diff --git a/Cargo.lock b/Cargo.lock index 3f51a413b25..df4bcc2e62d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,42 +14,805 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f609fb6392508278b276906d6247ea44f5777e448db95444fa39e89b7aee896a" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-json-rpc", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "alloy-trie", +] + +[[package]] +name = "alloy-chains" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b163ff4acf0eac29af05a911397cc418a76e153467b859398adc26cb9335a611" +dependencies = [ + "alloy-primitives", + "num_enum", + "strum 0.27.2", +] + +[[package]] +name = "alloy-consensus" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3dcd2b4e208ce5477de90ccdcbd4bde2c8fb06af49a443974e92bb8f2c5e93f" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "arbitrary", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee5655f234985f5ab1e31bef7e02ed11f0a899468cf3300e061e1b96e9e11de0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "arbitrary", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f01b6d8e5b4f3222aaf7f18613a7292e2fbc9163fe120649cd1b078ca534349" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures 0.3.31", + "futures-util", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-core" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4087016b0896051dd3d03e0bedda2f4d4d1689af8addc8450288c63a9e5f68" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "369f5707b958927176265e8a58627fc6195e5dfa5c55689396e68b241b3a72e6" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "arbitrary", + "itoa", + "proptest", + "serde", + "serde_json", + "winnow", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "crc", + "rand 0.8.5", + "serde", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "borsh", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "borsh", + "k256", + "rand 0.8.5", + "serde", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-eips" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6847d641141b92a1557094aa6c236cbe49c06fb24144d4a21fe6acb970c15888" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "arbitrary", + "auto_impl", + "borsh", + "c-kzg", + "derive_more 2.1.1", + "either", + "serde", + "serde_with", + "sha2", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-genesis" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3192fca2eb0b0c4b122b3c2d8254496b88a4e810558dddd3ea2f30ad9469df" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "borsh", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-json-abi" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e3cf01219c966f95a460c95f1d4c30e12f6c18150c21a30b768af2a2a29142" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ab3330e491053e9608b2a315f147357bb8acb9377a988c1203f2e8e2b296c9" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "http 1.4.0", + "serde", + "serde_json", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e22ff194b1e34b4defd1e257e3fe4dce0eee37451c7757a1510d6b23e7379a" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more 2.1.1", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a6cbb9f431bdad294eebb5af9b293d6979e633bfe5468d1e87c1421a858265" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6a0fb18dd5fb43ec5f0f6a20be1ce0287c79825827de5744afaa6c957737c33" +dependencies = [ + "alloy-rlp", + "arbitrary", + "bytes", + "cfg-if 1.0.4", + "const-hex", + "derive_more 2.1.1", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "indexmap 2.12.1", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "proptest-derive 0.6.0", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash 2.1.1", + "serde", + "sha3", + "tiny-keccak 2.0.2", +] + +[[package]] +name = "alloy-provider" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f5dde1abc3d582e53d139904fcdd8b2103f0bd03e8f2acb4292edbbaeaa7e6e" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-anvil", + "alloy-rpc-types-debug", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-signer", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "either", + "futures 0.3.31", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbfe0a3c553a027f722185fb574124d205147fffb309cae52d0a2094f076887" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "auto_impl", + "bimap", + "futures 0.3.31", + "parking_lot", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "wasmtimer", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +dependencies = [ + "alloy-rlp-derive", + "arrayvec 0.7.6", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "alloy-rpc-client" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a94bdef2710322c6770be08689fee0878c2ad75615b8fc40e05d7f3c9618c0b" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures 0.3.31", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "811a573c8080e1b492d488e6a240ec5dd7677d7167e91ce9cb4d0ec1fcac8027" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-anvil", + "alloy-rpc-types-debug", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-anvil" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "838ca94be532a929f27961851000ec8bbbaeb06e2a2bcca44fac7855a2fe0f6f" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df0b34551ca2eab8ec83b56cb709ee5da991737282180d354a659b907f00dc" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49a3a168a5bf18f1cf7ed5723a650aebe714edf7665b53dacf5707716733d0" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe16cd1dea6089902ec609e04261a9ae6d11ec66005ba24c1f97f0eefbc0fa9" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "arbitrary", + "derive_more 2.1.1", + "rand 0.8.5", + "serde", + "strum 0.27.2", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f9f130511b8632686dfe6f9909b38d7ae4c68de3ce17d28991400646a39b25" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "arbitrary", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cafe859944638c5d57d1a3a0034cdb5d07c98c37de8adce5508f28834acf958f" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-rpc-types-txpool" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afaa06544e36f223b99b1415a12911230fd527994f020736c3c7950d5080208e" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-serde" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "067b718d2e6ac1bb889341fcc7a250cfa49bcd3ba4f23923f1c1eb1f2b10cb7c" dependencies = [ - "gimli 0.29.0", + "alloy-primitives", + "arbitrary", + "serde", + "serde_json", ] [[package]] -name = "addr2line" -version = "0.24.2" +name = "alloy-signer" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "acff6b251740ef473932386d3b71657d3825daebf2217fb41a7ef676229225d4" dependencies = [ - "gimli 0.31.1", + "alloy-primitives", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror 2.0.17", ] [[package]] -name = "adler" -version = "1.0.2" +name = "alloy-signer-local" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "c9129ef31975d987114c27c9930ee817cf3952355834d47f2fdf4596404507e8" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "k256", + "rand 0.8.5", + "thiserror 2.0.17", +] [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "alloy-sol-macro" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "09eb18ce0df92b4277291bbaa0ed70545d78b02948df756bbd3d6214bf39a218" dependencies = [ - "memchr", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] -name = "allocator-api2" -version = "0.2.21" +name = "alloy-sol-macro-expander" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "95d9fa2daf21f59aa546d549943f10b5cce1ae59986774019fbedae834ffe01b" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap 2.12.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.111", + "syn-solidity", + "tiny-keccak 2.0.2", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9396007fe69c26ee118a19f4dee1f5d1d6be186ea75b3881adf16d87f8444686" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck 0.5.0", + "macro-string", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.111", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af67a0b0dcebe14244fc92002cd8d96ecbf65db4639d479f5fcd5805755a4c27" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09aeea64f09a7483bdcd4193634c7e5cf9fd7775ee767585270cd8ce2d69dc95" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec1fb08ee484e615f24867c0b154fff5722bb00176102a16868c6532b7c3623" +dependencies = [ + "alloy-json-rpc", + "auto_impl", + "base64 0.22.1", + "derive_more 2.1.1", + "futures 0.3.31", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b722073c76f2de7e118d546ee1921c50710f97feb32aed50db94cfa5b663e1" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdedcf401aab4b96d8b5e6638b79d04a6afb96c0bfcb50a2324fbadfe65c47b3" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures 0.3.31", + "interprocess", + "pin-project", + "serde", + "serde_json", + "tokio", + "tokio-util 0.7.17", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942210908f0c56941097f5653a5f334546940e6fd9073495b257e52216469feb" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures 0.3.31", + "http 1.4.0", + "serde_json", + "tokio", + "tokio-tungstenite 0.26.2", + "tracing", + "ws_stream_wasm", +] + +[[package]] +name = "alloy-trie" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b77b56af09ead281337d06b1d036c88e2dc8a2e45da512a532476dbee94912b" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "arrayvec 0.7.6", + "derive_arbitrary", + "derive_more 2.1.1", + "nybbles", + "proptest", + "proptest-derive 0.5.1", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04950a13cc4209d8e9b78f306e87782466bad8538c94324702d061ff03e211c9" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.111", +] [[package]] name = "android_system_properties" @@ -62,9 +825,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -77,36 +840,37 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] @@ -116,22 +880,226 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] -name = "arbitrary" -version = "1.4.1" +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object 0.32.2", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec 0.7.6", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec 0.7.6", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] [[package]] -name = "arc-swap" -version = "1.7.1" +name = "ark-std" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -141,9 +1109,12 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "ascii_utils" @@ -180,8 +1151,8 @@ dependencies = [ "futures-timer", "futures-util", "handlebars", - "http 1.3.1", - "indexmap 2.11.4", + "http 1.4.0", + "indexmap 2.12.1", "mime", "multer", "num-traits", @@ -208,7 +1179,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -225,7 +1196,7 @@ dependencies = [ "proc-macro2", "quote", "strum 0.27.2", - "syn 2.0.106", + "syn 2.0.111", "thiserror 2.0.17", ] @@ -248,7 +1219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527a4c6022fc4dac57b4f03f12395e9a391512e85ba98230b93315f8f45f27fc" dependencies = [ "bytes", - "indexmap 2.11.4", + "indexmap 2.12.1", "serde", "serde_json", ] @@ -261,7 +1232,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -283,18 +1254,29 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures 0.3.31", + "pharos", + "rustc_version 0.4.1", ] [[package]] @@ -331,23 +1313,34 @@ dependencies = [ ] [[package]] -name = "autocfg" +name = "auto_impl" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.7.5" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", - "axum-core 0.4.3", + "axum-core 0.4.5", "bytes", "futures-util", - "http 1.3.1", - "http-body 1.0.0", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "itoa", "matchit 0.7.3", @@ -357,8 +1350,8 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper 1.0.1", - "tower 0.4.13", + "sync_wrapper", + "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -374,8 +1367,8 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.3.1", - "http-body 1.0.0", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "hyper 1.8.1", "hyper-util", @@ -390,9 +1383,9 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.1", + "sync_wrapper", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.28.0", "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -401,20 +1394,20 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.3.1", - "http-body 1.0.0", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper", "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -427,12 +1420,12 @@ checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" dependencies = [ "bytes", "futures-core", - "http 1.3.1", - "http-body 1.0.0", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper 1.0.1", + "sync_wrapper", "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", @@ -440,34 +1433,35 @@ dependencies = [ [[package]] name = "backon" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0b50b1b78dbadd44ab18b3c794e496f3a139abb9fbc27d9c94c4eebbb96496" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ "fastrand", ] -[[package]] -name = "backtrace" -version = "0.3.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line 0.22.0", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "base-x" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + [[package]] name = "base64" version = "0.13.1" @@ -486,6 +1480,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + [[package]] name = "beef" version = "0.5.2" @@ -518,6 +1518,56 @@ dependencies = [ "num-traits", ] +[[package]] +name = "bigdecimal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560f42649de9fa436b73517378a147ec21f6c997a546581df4b4b31677828934" +dependencies = [ + "autocfg", + "libm", + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -526,9 +1576,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bitvec" @@ -564,9 +1614,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "cc", - "cfg-if 1.0.0", + "cfg-if 1.0.4", "constant_time_eq 0.3.1", ] @@ -588,6 +1638,41 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "bs58" version = "0.4.0" @@ -605,9 +1690,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "serde", @@ -615,18 +1700,18 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" dependencies = [ "allocator-api2", ] [[package]] name = "byte-slice-cast" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "byteorder" @@ -636,18 +1721,34 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" dependencies = [ "serde", ] +[[package]] +name = "c-kzg" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" +dependencies = [ + "arbitrary", + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "cc" -version = "1.2.43" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -663,9 +1764,15 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" @@ -678,7 +1785,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link 0.2.0", + "windows-link", ] [[package]] @@ -690,14 +1797,14 @@ dependencies = [ "core2", "multibase", "multihash", - "unsigned-varint 0.8.0", + "unsigned-varint", ] [[package]] name = "clap" -version = "4.5.8" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -705,9 +1812,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -718,33 +1825,36 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cobs" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.17", +] [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -757,7 +1867,7 @@ dependencies = [ "memchr", "pin-project-lite", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", ] [[package]] @@ -769,10 +1879,54 @@ dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.0", + "unicode-width 0.2.2", "windows-sys 0.59.0", ] +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if 1.0.4", + "cpufeatures", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -793,9 +1947,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "convert_case" -version = "0.7.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -812,9 +1966,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -837,18 +1991,18 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", ] [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -905,12 +2059,12 @@ dependencies = [ "cranelift-control", "cranelift-entity", "cranelift-isle", - "gimli 0.31.1", - "hashbrown 0.15.2", + "gimli", + "hashbrown 0.15.5", "log", "pulley-interpreter", "regalloc2", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "serde", "smallvec", "target-lexicon", @@ -989,13 +2143,28 @@ version = "0.120.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02e3f4d783a55c64266d17dc67d2708852235732a100fc40dd9f1051adc64d7b" +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", ] [[package]] @@ -1022,9 +2191,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1041,30 +2210,42 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1082,33 +2263,33 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", + "darling_core 0.20.11", + "darling_macro 0.20.11", ] [[package]] @@ -1133,16 +2314,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1155,8 +2336,9 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "serde", "strsim", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1169,18 +2351,18 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.10", + "darling_core 0.20.11", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1191,7 +2373,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1202,20 +2384,34 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.106", + "syn 2.0.111", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if 1.0.4", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "data-encoding-macro" -version = "0.1.15" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" +checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1223,21 +2419,22 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.13" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" +checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.111", ] [[package]] name = "deadpool" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed5957ff93768adf7a65ab167a17835c3d2c3c50d084fe305174c112f468e2f" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" dependencies = [ "deadpool-runtime", + "lazy_static", "num_cpus", "tokio", ] @@ -1266,14 +2463,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "930c7171c8df9fb1782bdf9b918ed9ed2d33d1d22300abb754f9085bc48bf8e8" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -1284,7 +2491,18 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -1302,10 +2520,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.20.10", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1315,52 +2533,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "derive_more" -version = "0.99.19" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.106", + "rustc_version 0.4.1", + "syn 2.0.111", ] [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case 0.7.1", + "convert_case 0.10.0", "proc-macro2", "quote", - "syn 2.0.106", + "rustc_version 0.4.1", + "syn 2.0.111", "unicode-xid", ] [[package]] name = "diesel" -version = "2.3.2" +version = "2.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8496eeb328dce26ee9d9b73275d396d9bddb433fa30106cf6056dd8c3c2764c" +checksum = "e130c806dccc85428c564f2dc5a96e05b6615a27c9a28776bd7761a9af4bb552" dependencies = [ - "bigdecimal 0.3.1", - "bitflags 2.9.0", + "bigdecimal 0.4.9", + "bitflags 2.10.0", "byteorder", "chrono", "diesel_derives", @@ -1376,9 +2595,9 @@ dependencies = [ [[package]] name = "diesel-async" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c69eded9cb72c7e112505caec23da00149d4dd49f4c96b3c83b2b63f0aa3da5f" +checksum = "13096fb8dae53f2d411c4b523bec85f45552ed3044a2ab4d85fb2092d9cb4f34" dependencies = [ "deadpool", "diesel", @@ -1398,7 +2617,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1420,7 +2639,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1440,7 +2659,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c" dependencies = [ - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1471,6 +2690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -1481,39 +2701,29 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "dirs-sys-next", ] [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", - "windows-sys 0.48.0", + "redox_users 0.5.2", + "windows-sys 0.61.2", ] [[package]] @@ -1523,7 +2733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -1535,9 +2745,15 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "downcast-rs" version = "2.0.2" @@ -1555,14 +2771,76 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] [[package]] name = "embedded-io" @@ -1584,18 +2862,38 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if 1.0.4", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ - "cfg-if 1.0.0", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -1631,14 +2929,14 @@ checksum = "452c458dfb890a2fea64e4c894f83daad61689fb9bbe84c382e1ce6d7536710b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" @@ -1651,12 +2949,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1672,7 +2970,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "thiserror 1.0.61", + "thiserror 1.0.69", "uint 0.9.5", ] @@ -1683,7 +2981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" dependencies = [ "crunchy", - "fixed-hash", + "fixed-hash 0.7.0", "impl-rlp", "impl-serde", "tiny-keccak 2.0.2", @@ -1696,10 +2994,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" dependencies = [ "ethbloom", - "fixed-hash", + "fixed-hash 0.7.0", "impl-rlp", "impl-serde", - "primitive-types", + "primitive-types 0.11.1", "uint 0.9.5", ] @@ -1726,15 +3024,47 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec 0.7.6", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec 0.7.6", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "firestorm" @@ -1760,6 +3090,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.5.7" @@ -1768,9 +3110,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.0.30" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -1788,6 +3130,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1889,7 +3237,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -1929,6 +3277,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "fxhash" version = "0.2.1" @@ -1944,7 +3298,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "debugid", "fxhash", "serde", @@ -1959,37 +3313,36 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasip2", + "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - [[package]] name = "gimli" version = "0.31.1" @@ -1997,7 +3350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" dependencies = [ "fallible-iterator 0.3.0", - "indexmap 2.11.4", + "indexmap 2.12.1", "stable_deref_trait", ] @@ -2019,10 +3372,16 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", "time", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "globset" version = "0.4.18" @@ -2062,6 +3421,7 @@ name = "graph" version = "0.36.0" dependencies = [ "Inflector", + "alloy", "anyhow", "async-stream", "async-trait", @@ -2086,14 +3446,14 @@ dependencies = [ "graph_derive", "graphql-parser", "hex", - "hex-literal 1.0.0", + "hex-literal 1.1.0", "http 0.2.12", - "http 1.3.1", + "http 1.4.0", "http-body-util", "humantime", "hyper 1.8.1", "hyper-util", - "itertools", + "itertools 0.14.0", "lazy_static", "lru_time_cache", "maplit", @@ -2111,7 +3471,7 @@ dependencies = [ "redis", "regex", "reqwest", - "semver", + "semver 1.0.27", "serde", "serde_derive", "serde_json", @@ -2132,12 +3492,11 @@ dependencies = [ "tokio", "tokio-retry", "tokio-stream", - "toml 0.9.7", + "toml 0.9.10+spec-1.1.0", "tonic", "tonic-build", "url", "wasmparser 0.118.2", - "web3", "wiremock", ] @@ -2163,17 +3522,18 @@ dependencies = [ "graph-runtime-derive", "graph-runtime-wasm", "hex", - "itertools", + "itertools 0.14.0", "jsonrpc-core", "prost", "prost-types", - "semver", + "semver 1.0.27", "serde", "thiserror 2.0.17", "tiny-keccak 1.5.0", "tokio", "tokio-stream", "tonic-build", + "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2207,7 +3567,7 @@ dependencies = [ "lazy_static", "prost", "prost-types", - "semver", + "semver 1.0.27", "serde", "tokio", "tokio-stream", @@ -2277,7 +3637,7 @@ dependencies = [ "graph-store-postgres", "graphman", "graphman-server", - "itertools", + "itertools 0.14.0", "json-structural-diff", "lazy_static", "notify", @@ -2296,7 +3656,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -2309,7 +3669,7 @@ dependencies = [ "graph-runtime-derive", "graph-runtime-wasm", "rand 0.9.2", - "semver", + "semver 1.0.27", "test-store", "wasmtime", ] @@ -2321,13 +3681,12 @@ dependencies = [ "anyhow", "async-trait", "bs58 0.4.0", - "ethabi", "graph", "graph-runtime-derive", "hex", "never", "parity-wasm", - "semver", + "semver 1.0.27", "serde_yaml", "wasm-instrument", "wasmtime", @@ -2385,7 +3744,7 @@ dependencies = [ "chrono", "clap", "deadpool", - "derive_more 2.0.1", + "derive_more 2.1.1", "diesel", "diesel-async", "diesel-derive-enum", @@ -2398,7 +3757,7 @@ dependencies = [ "graphman-store", "graphql-parser", "hex", - "itertools", + "itertools 0.14.0", "lazy_static", "lru_time_cache", "maybe-owned", @@ -2433,12 +3792,13 @@ dependencies = [ "graph-runtime-wasm", "graph-server-index-node", "graph-store-postgres", - "secp256k1", + "secp256k1 0.21.3", "serde", "serde_yaml", "slog", "tokio", "tokio-stream", + "web3", ] [[package]] @@ -2449,7 +3809,7 @@ dependencies = [ "proc-macro-utils", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -2462,7 +3822,7 @@ dependencies = [ "graph", "graph-store-postgres", "graphman-store", - "itertools", + "itertools 0.14.0", "thiserror 2.0.17", "tokio", ] @@ -2511,7 +3871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a818c0d883d7c0801df27be910917750932be279c7bc82dc541b8769425f409" dependencies = [ "combine", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -2527,11 +3887,22 @@ dependencies = [ "serde_with", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", @@ -2539,29 +3910,29 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.11.4", + "indexmap 2.12.1", "slab", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "tracing", ] [[package]] name = "h2" -version = "0.4.5" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.3.1", - "indexmap 2.11.4", + "http 1.4.0", + "indexmap 2.12.1", "slab", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "tracing", ] @@ -2589,12 +3960,31 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash", + "foldhash 0.2.0", "serde", + "serde_core", ] [[package]] @@ -2609,14 +3999,14 @@ dependencies = [ [[package]] name = "headers" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "headers-core", - "http 1.3.1", + "http 1.4.0", "httpdate", "mime", "sha1", @@ -2628,7 +4018,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -2654,9 +4044,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -2664,6 +4054,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec 0.7.6", +] + [[package]] name = "hex-literal" version = "0.3.4" @@ -2672,9 +4071,9 @@ checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" [[package]] name = "hex-literal" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" +checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" [[package]] name = "hmac" @@ -2687,11 +4086,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2707,12 +4106,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -2729,12 +4127,12 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http 1.4.0", ] [[package]] @@ -2745,16 +4143,16 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", - "http-body 1.0.0", + "http 1.4.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.4" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -2770,22 +4168,22 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", + "h2 0.3.27", "http 0.2.12", "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.5.10", "tokio", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", @@ -2802,9 +4200,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.5", - "http 1.3.1", - "http-body 1.0.0", + "h2 0.4.12", + "http 1.4.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -2817,20 +4215,20 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", - "http 1.3.1", + "http 1.4.0", "hyper 1.8.1", "hyper-util", "rustls", - "rustls-native-certs 0.7.1", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "webpki-roots 1.0.4", ] [[package]] @@ -2864,25 +4262,26 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.3.1", - "http-body 1.0.0", + "http 1.4.0", + "http-body 1.0.1", "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.6.1", "system-configuration", "tokio", + "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", "windows-registry", @@ -2890,14 +4289,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -2917,7 +4317,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1fcc7f316b2c079dde77564a1360639c1a956a23fa96122732e416cb10717bb" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "num-traits", "rand 0.8.5", "static_assertions", @@ -2925,21 +4325,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -2948,99 +4349,61 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "id-arena" version = "2.2.1" @@ -3077,9 +4440,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -3114,13 +4477,13 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.111", ] [[package]] @@ -3136,12 +4499,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.4" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ + "arbitrary", "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -3152,7 +4516,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "inotify-sys", "libc", ] @@ -3167,27 +4531,31 @@ dependencies = [ ] [[package]] -name = "io-uring" -version = "0.7.10" +name = "interprocess" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d" dependencies = [ - "bitflags 2.9.0", - "cfg-if 1.0.0", + "doctest-file", + "futures-core", "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -3195,20 +4563,38 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.5.2", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] [[package]] name = "itertools" @@ -3221,9 +4607,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "ittapi" @@ -3247,42 +4633,43 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -3333,14 +4720,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3dc3e9cf2ba50b7b1d7d76a667619f82846caa39e8e8daa8a4962d74acaddca" dependencies = [ "anyhow", - "arrayvec 0.7.4", + "arrayvec 0.7.6", "async-trait", "beef", "futures-channel", "futures-util", "globset", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.32", "jsonrpsee-types", "lazy_static", "parking_lot", @@ -3348,7 +4735,7 @@ dependencies = [ "rustc-hash 1.1.0", "serde", "serde_json", - "thiserror 1.0.61", + "thiserror 1.0.69", "tokio", "tracing", "unicase", @@ -3362,7 +4749,7 @@ checksum = "03802f0373a38c2420c70b5144742d800b509e2937edc4afb116434f07120117" dependencies = [ "futures-channel", "futures-util", - "hyper 0.14.29", + "hyper 0.14.32", "jsonrpsee-core", "jsonrpsee-types", "serde", @@ -3382,10 +4769,24 @@ dependencies = [ "beef", "serde", "serde_json", - "thiserror 1.0.61", + "thiserror 1.0.69", "tracing", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if 1.0.4", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", +] + [[package]] name = "keccak" version = "0.1.5" @@ -3395,6 +4796,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "kqueue" version = "1.1.1" @@ -3435,9 +4846,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libm" @@ -3447,31 +4858,32 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "libc", + "redox_syscall 0.6.0", ] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "lock_api" @@ -3484,9 +4896,24 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "lru_time_cache" @@ -3496,19 +4923,41 @@ checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd" [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "match-lookup" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "matches" version = "0.1.10" @@ -3539,23 +4988,23 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "digest 0.10.7", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memfd" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" dependencies = [ - "rustix 0.38.34", + "rustix 1.1.3", ] [[package]] @@ -3565,7 +5014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d" dependencies = [ "serde", - "toml 0.9.7", + "toml 0.9.10+spec-1.1.0", ] [[package]] @@ -3597,23 +5046,24 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "adler", + "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -3625,7 +5075,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.3.1", + "http 1.4.0", "httparse", "memchr", "mime", @@ -3635,36 +5085,37 @@ dependencies = [ [[package]] name = "multibase" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" dependencies = [ "base-x", + "base256emoji", "data-encoding", "data-encoding-macro", ] [[package]] name = "multihash" -version = "0.19.1" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076d548d76a0e2a0d4ab471d0b1c36c577786dfc4471242035d97a12a735c492" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" dependencies = [ "core2", - "unsigned-varint 0.7.2", + "unsigned-varint", ] [[package]] name = "multimap" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -3672,7 +5123,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.0", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -3689,7 +5140,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "fsevent-sys", "inotify", "kqueue", @@ -3766,18 +5217,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.5.2", "libc", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "nybbles" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4b5ecbd0beec843101bffe848217f770e8b8da81d8355b7d6e226f2199b3dc" +dependencies = [ + "alloy-rlp", + "arbitrary", + "cfg-if 1.0.4", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "object" version = "0.36.7" @@ -3785,8 +5282,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", - "hashbrown 0.15.2", - "indexmap 2.11.4", + "hashbrown 0.15.5", + "indexmap 2.12.1", "memchr", ] @@ -3802,11 +5299,11 @@ dependencies = [ "chrono", "form_urlencoded", "futures 0.3.31", - "http 1.3.1", + "http 1.4.0", "http-body-util", "humantime", "hyper 1.8.1", - "itertools", + "itertools 0.14.0", "parking_lot", "percent-encoding", "quick-xml", @@ -3828,9 +5325,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "opaque-debug" @@ -3840,12 +5343,12 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" -version = "0.10.73" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.9.0", - "cfg-if 1.0.0", + "bitflags 2.10.0", + "cfg-if 1.0.4", "foreign-types", "libc", "once_cell", @@ -3861,29 +5364,29 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.0+3.5.0" +version = "300.5.4+3.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -3904,33 +5407,35 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" dependencies = [ - "unicode-width 0.1.13", + "unicode-width 0.1.14", ] [[package]] name = "parity-scale-codec" -version = "3.6.12" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "bitvec", "byte-slice-cast", + "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", + "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.111", ] [[package]] @@ -3955,13 +5460,19 @@ version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.0", + "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3970,20 +5481,19 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.7.11" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", - "thiserror 1.0.61", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" dependencies = [ "pest", "pest_generator", @@ -3991,24 +5501,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -4020,7 +5529,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.11.4", + "indexmap 2.12.1", ] [[package]] @@ -4030,8 +5539,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", - "hashbrown 0.15.2", - "indexmap 2.11.4", + "hashbrown 0.15.5", + "indexmap 2.12.1", "serde", ] @@ -4046,6 +5555,16 @@ dependencies = [ "url", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures 0.3.31", + "rustc_version 0.4.1", +] + [[package]] name = "phf" version = "0.13.1" @@ -4067,29 +5586,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -4097,17 +5616,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "portable-atomic-util" @@ -4120,9 +5649,9 @@ dependencies = [ [[package]] name = "postcard" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" dependencies = [ "cobs", "embedded-io 0.4.0", @@ -4132,9 +5661,9 @@ dependencies = [ [[package]] name = "postgres" -version = "0.19.7" +version = "0.19.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7915b33ed60abc46040cbcaa25ffa1c7ec240668e0477c4f3070786f5916d451" +checksum = "e7c48ece1c6cda0db61b058c1721378da76855140e9214339fa1317decacb176" dependencies = [ "bytes", "fallible-iterator 0.2.0", @@ -4185,6 +5714,15 @@ dependencies = [ "postgres-protocol", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -4193,15 +5731,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "pq-src" -version = "0.3.9+libpq-17.5" +version = "0.3.10+libpq-18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ee82a51d19317d15e43b82e496db215ad5bf09a245786e7ac75cb859e5ba46" +checksum = "56ef39ce621f4993d6084fdcd4cbf1e01c84bdba53109cfad095d2cf441b85b9" dependencies = [ "cc", "openssl-sys", @@ -4231,12 +5772,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -4245,13 +5786,24 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" dependencies = [ - "fixed-hash", + "fixed-hash 0.7.0", "impl-codec", "impl-rlp", "impl-serde", "uint 0.9.5", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec", + "uint 0.9.5", +] + [[package]] name = "priority-queue" version = "2.7.0" @@ -4259,17 +5811,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" dependencies = [ "equivalent", - "indexmap 2.11.4", + "indexmap 2.12.1", "serde", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.23.10+spec-1.0.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -4285,9 +5859,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -4298,7 +5872,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "fnv", "lazy_static", "libc", @@ -4309,6 +5883,47 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.10.0", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "proptest-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "proptest-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "prost" version = "0.13.5" @@ -4326,7 +5941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck 0.5.0", - "itertools", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -4335,7 +5950,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.106", + "syn 2.0.111", "tempfile", ] @@ -4346,10 +5961,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools", + "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -4369,7 +5984,7 @@ checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" dependencies = [ "once_cell", "protobuf-support", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -4379,12 +5994,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973" dependencies = [ "anyhow", - "indexmap 2.11.4", + "indexmap 2.12.1", "log", "protobuf", "protobuf-support", "tempfile", - "thiserror 1.0.61", + "thiserror 1.0.69", "which", ] @@ -4394,15 +6009,16 @@ version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" dependencies = [ - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] name = "psm" -version = "0.1.21" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" dependencies = [ + "ar_archive_writer", "cc", ] @@ -4417,11 +6033,17 @@ dependencies = [ "wasmtime-math", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-xml" -version = "0.38.2" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d200a41a7797e6461bd04e4e95c3347053a731c32c87f066f2f0dda22dbdbba8" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", "serde", @@ -4429,49 +6051,57 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.2" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "rustls", - "thiserror 1.0.61", + "socket2 0.6.1", + "thiserror 2.0.17", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "rand 0.8.5", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", "ring", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "rustls", + "rustls-pki-types", "slab", - "thiserror 1.0.61", + "thiserror 2.0.17", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.2" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ + "cfg_aliases", "libc", "once_cell", - "socket2 0.5.7", + "socket2 0.6.1", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -4483,6 +6113,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r2d2" version = "0.8.10" @@ -4509,6 +6145,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -4519,6 +6156,7 @@ checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", + "serde", ] [[package]] @@ -4547,7 +6185,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -4556,14 +6194,33 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "rapidhash" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2988730ee014541157f48ce4dcc603940e00915edc3c7f9a8d78092256bb2493" +dependencies = [ + "rustversion", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -4571,9 +6228,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -4596,9 +6253,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.111", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redis" version = "0.31.0" @@ -4608,7 +6271,7 @@ dependencies = [ "arc-swap", "backon", "bytes", - "cfg-if 1.0.0", + "cfg-if 1.0.4", "combine", "futures-channel", "futures-util", @@ -4618,39 +6281,70 @@ dependencies = [ "pin-project-lite", "ryu", "sha1_smol", - "socket2 0.5.7", + "socket2 0.5.10", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "url", ] [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.10.0", ] [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", - "thiserror 1.0.61", + "thiserror 2.0.17", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", ] [[package]] @@ -4661,17 +6355,17 @@ checksum = "5216b1837de2149f8bc8e6d5f88a9326b63b8c836ed58ce4a0a29ec736a59734" dependencies = [ "allocator-api2", "bumpalo", - "hashbrown 0.15.2", + "hashbrown 0.15.5", "log", - "rustc-hash 2.0.0", + "rustc-hash 2.1.1", "smallvec", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -4681,9 +6375,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -4692,15 +6386,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", @@ -4708,9 +6402,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.5", - "http 1.3.1", - "http-body 1.0.0", + "h2 0.4.12", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "hyper 1.8.1", "hyper-rustls", @@ -4725,16 +6419,16 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs 0.8.1", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper", "tokio", "tokio-native-tls", "tokio-rustls", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "tower-http", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -4743,17 +6437,28 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots 1.0.4", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", ] [[package]] name = "ring" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "cfg-if 1.0.0", - "getrandom 0.2.15", + "cfg-if 1.0.4", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -4769,11 +6474,46 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "ruint" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" +dependencies = [ + "alloy-rlp", + "arbitrary", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types 0.12.2", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" @@ -4783,9 +6523,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc-hex" @@ -4795,44 +6535,53 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.27", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "log", "once_cell", @@ -4845,50 +6594,40 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "rustls-pki-types", - "schannel", - "security-framework 2.11.0", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework 3.5.1", ] [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "web-time", + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -4897,15 +6636,27 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "same-file" @@ -4918,11 +6669,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4934,6 +6685,30 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scoped-futures" version = "0.1.4" @@ -4949,13 +6724,40 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.4.2", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys 0.10.1", + "serde", ] [[package]] @@ -4967,13 +6769,22 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -4982,12 +6793,12 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -4995,14 +6806,23 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.27" @@ -5013,11 +6833,26 @@ dependencies = [ "serde_core", ] +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -5025,43 +6860,46 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.226" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", ] [[package]] @@ -5085,18 +6923,18 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "serde_spanned" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -5115,17 +6953,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.11.4", - "serde", - "serde_derive", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -5133,14 +6972,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ - "darling 0.20.10", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -5149,13 +6988,23 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.1", "itoa", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -5163,7 +7012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if 1.0.4", "cpufeatures", "digest 0.9.0", "opaque-debug", @@ -5175,7 +7024,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "cpufeatures", "digest 0.10.7", ] @@ -5192,7 +7041,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "cpufeatures", "digest 0.10.7", ] @@ -5207,6 +7056,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if 1.0.4", +] + [[package]] name = "shellexpand" version = "3.1.1" @@ -5224,13 +7083,29 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "siphasher" version = "1.0.1" @@ -5239,12 +7114,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slog" @@ -5309,10 +7181,11 @@ dependencies = [ [[package]] name = "slog-term" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" +checksum = "5cb1fc680b38eed6fad4c02b3871c09d2c81db8c96aa4e9c0a34904c830f09b5" dependencies = [ + "chrono", "is-terminal", "slog", "term", @@ -5322,18 +7195,19 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ + "arbitrary", "serde", ] [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5341,12 +7215,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5370,6 +7244,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sptr" version = "0.3.2" @@ -5395,7 +7279,7 @@ checksum = "028e551d5e270b31b9f3ea271778d9d827148d4287a5d96167b6bb9787f5cc38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -5414,23 +7298,23 @@ dependencies = [ [[package]] name = "stable-hash" version = "0.4.4" -source = "git+https://github.com/graphprotocol/stable-hash?branch=main#e50aabef55b8c4de581ca5c4ffa7ed8beed7e998" +source = "git+https://github.com/graphprotocol/stable-hash?branch=main#083b67b949eff48e210c5753eb9c777a73958d9b" dependencies = [ - "blake3 0.3.8", + "blake3 1.8.2", "firestorm 0.5.1", "ibig", "lazy_static", "leb128", "num-traits", - "uint 0.8.5", + "uint 0.10.0", "xxhash-rust", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stacker" @@ -5439,7 +7323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" dependencies = [ "cc", - "cfg-if 1.0.0", + "cfg-if 1.0.4", "libc", "psm", "windows-sys 0.59.0", @@ -5502,7 +7386,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -5514,7 +7398,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -5537,7 +7421,7 @@ dependencies = [ "prost-build", "prost-types", "substreams-macro", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -5558,14 +7442,14 @@ version = "0.36.0" [[package]] name = "substreams-macro" -version = "0.6.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36f36e9da94db29f49daf3ab6b47b529b57c43fc5d58bc35b160aaad1a7233f" +checksum = "21b4fdca34da8c0250425bd798bd843f95b12dea527eeb3ba571e0b0af93d1b7" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "thiserror 1.0.61", + "thiserror 1.0.69", ] [[package]] @@ -5575,7 +7459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01ef8a763c5a5604b16f4898ab75d39494ef785c457aaca1fd7761b299f40fbf" dependencies = [ "bs58 0.4.0", - "getrandom 0.2.15", + "getrandom 0.2.16", "hex", "prost", "prost-build", @@ -5614,9 +7498,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -5624,29 +7508,35 @@ dependencies = [ ] [[package]] -name = "sync_wrapper" -version = "0.1.2" +name = "syn-solidity" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "5f92d01b5de07eaf324f7fca61cc6bd3d82bbc1de5b6c963e6fe79e86f36580d" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.111", +] [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -5655,7 +7545,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5684,31 +7574,30 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ - "cfg-if 1.0.0", "fastrand", - "rustix 0.38.34", - "windows-sys 0.52.0", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", ] [[package]] name = "term" -version = "0.7.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "dirs-next", - "rustversion", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -5722,12 +7611,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 0.38.34", - "windows-sys 0.48.0", + "rustix 1.1.3", + "windows-sys 0.60.2", ] [[package]] @@ -5743,20 +7632,21 @@ dependencies = [ "graph-node", "graph-store-postgres", "hex", - "hex-literal 1.0.0", + "hex-literal 1.1.0", "lazy_static", "pretty_assertions", "prost-types", + "serde_json", "tokio", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.61", + "thiserror-impl 1.0.69", ] [[package]] @@ -5770,13 +7660,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -5787,24 +7677,32 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "cfg-if 1.0.0", - "once_cell", + "cfg-if 1.0.4", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -5817,15 +7715,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -5851,9 +7749,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -5861,9 +7759,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -5876,33 +7774,30 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -5917,11 +7812,10 @@ dependencies = [ [[package]] name = "tokio-openssl" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffab79df67727f6acf57f1ff743091873c24c579b1e2ce4d8f53e47ded4d63d" +checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" dependencies = [ - "futures-util", "openssl", "openssl-sys", "tokio", @@ -5947,9 +7841,9 @@ dependencies = [ "postgres-protocol", "postgres-types", "rand 0.9.2", - "socket2 0.6.0", + "socket2 0.6.1", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "whoami", ] @@ -5966,12 +7860,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -5984,7 +7877,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", ] [[package]] @@ -6000,6 +7893,22 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite 0.26.2", + "webpki-roots 0.26.11", +] + [[package]] name = "tokio-tungstenite" version = "0.28.0" @@ -6009,7 +7918,7 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.28.0", ] [[package]] @@ -6029,9 +7938,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -6043,87 +7952,95 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.15" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned 0.6.6", - "toml_datetime 0.6.6", - "toml_edit 0.22.16", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] name = "toml" -version = "0.9.7" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ - "indexmap 2.11.4", + "indexmap 2.12.1", "serde_core", - "serde_spanned 1.0.2", - "toml_datetime 0.7.2", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.13", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_datetime" -version = "0.7.2" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.4", - "toml_datetime 0.6.6", - "winnow 0.5.40", + "indexmap 2.12.1", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", ] [[package]] name = "toml_edit" -version = "0.22.16" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.11.4", - "serde", - "serde_spanned 0.6.6", - "toml_datetime 0.6.6", - "winnow 0.6.13", + "indexmap 2.12.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow", ] [[package]] name = "toml_parser" -version = "1.0.3" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ - "winnow 0.7.13", + "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" -version = "1.0.3" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" @@ -6133,13 +8050,13 @@ checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", - "axum 0.7.5", + "axum 0.7.9", "base64 0.22.1", "bytes", "flate2", - "h2 0.4.5", - "http 1.3.1", - "http-body 1.0.0", + "h2 0.4.12", + "http 1.4.0", + "http-body 1.0.1", "http-body-util", "hyper 1.8.1", "hyper-timeout", @@ -6147,9 +8064,9 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "rustls-native-certs 0.8.1", + "rustls-native-certs", "rustls-pemfile", - "socket2 0.5.7", + "socket2 0.5.10", "tokio", "tokio-rustls", "tokio-stream", @@ -6170,7 +8087,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -6187,7 +8104,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", @@ -6201,9 +8118,13 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "hdrhistogram", + "indexmap 2.12.1", "pin-project-lite", - "sync_wrapper 1.0.1", + "slab", + "sync_wrapper", "tokio", + "tokio-util 0.7.17", "tower-layer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tower-service 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", @@ -6217,12 +8138,12 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap 2.11.4", + "indexmap 2.12.1", "pin-project-lite", "slab", - "sync_wrapper 1.0.1", + "sync_wrapper", "tokio", - "tokio-util 0.7.11", + "tokio-util 0.7.17", "tower-layer 0.3.3 (git+https://github.com/tower-rs/tower.git)", "tower-service 0.3.3 (git+https://github.com/tower-rs/tower.git)", "tracing", @@ -6234,11 +8155,11 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "bytes", "futures-util", - "http 1.3.1", - "http-body 1.0.0", + "http 1.4.0", + "http-body 1.0.1", "iri-string", "pin-project-lite", "tower 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -6282,9 +8203,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -6294,20 +8215,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -6330,7 +8251,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -6346,6 +8267,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.2", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.17", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.28.0" @@ -6354,7 +8294,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.3.1", + "http 1.4.0", "httparse", "log", "rand 0.9.2", @@ -6365,33 +8305,33 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" -version = "0.8.5" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db035e67dfaf7edd9aebfe8676afcd63eed53c8a4044fed514c8cccf1835177" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", - "rustc-hex", + "hex", "static_assertions", ] [[package]] name = "uint" -version = "0.9.5" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" dependencies = [ "byteorder", "crunchy", @@ -6399,41 +8339,44 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" @@ -6443,21 +8386,21 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-libyaml" @@ -6465,12 +8408,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "unsigned-varint" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" - [[package]] name = "unsigned-varint" version = "0.8.0" @@ -6501,12 +8438,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -6521,9 +8452,19 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.15.1" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -6533,9 +8474,18 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] [[package]] name = "walkdir" @@ -6558,17 +8508,17 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -6579,47 +8529,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6627,22 +8565,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -6659,12 +8597,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.233.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9679ae3cf7cfa2ca3a327f7fab97f27f3294d402fd1a76ca8ab514e17973e4d3" +checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" dependencies = [ "leb128fmt", - "wasmparser 0.233.0", + "wasmparser 0.243.0", ] [[package]] @@ -6678,9 +8616,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -6695,8 +8633,8 @@ version = "0.118.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77f1154f1ab868e2a01d9834a805faca7bf8b50d041b4ca714d005d0dab1c50c" dependencies = [ - "indexmap 2.11.4", - "semver", + "indexmap 2.12.1", + "semver 1.0.27", ] [[package]] @@ -6705,22 +8643,22 @@ version = "0.229.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc3b1f053f5d41aa55640a1fa9b6d1b8a9e4418d118ce308d20e24ff3575a8c" dependencies = [ - "bitflags 2.9.0", - "hashbrown 0.15.2", - "indexmap 2.11.4", - "semver", + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.12.1", + "semver 1.0.27", "serde", ] [[package]] name = "wasmparser" -version = "0.233.0" +version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b51cb03afce7964bbfce46602d6cb358726f36430b6ba084ac6020d8ce5bc102" +checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" dependencies = [ - "bitflags 2.9.0", - "indexmap 2.11.4", - "semver", + "bitflags 2.10.0", + "indexmap 2.12.1", + "semver 1.0.27", ] [[package]] @@ -6740,31 +8678,31 @@ version = "33.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57373e1d8699662fb791270ac5dfac9da5c14f618ecf940cdb29dc3ad9472a3c" dependencies = [ - "addr2line 0.24.2", + "addr2line", "anyhow", "async-trait", - "bitflags 2.9.0", + "bitflags 2.10.0", "bumpalo", "cc", - "cfg-if 1.0.0", + "cfg-if 1.0.4", "encoding_rs", "fxprof-processed-profile", - "gimli 0.31.1", - "hashbrown 0.15.2", - "indexmap 2.11.4", + "gimli", + "hashbrown 0.15.5", + "indexmap 2.12.1", "ittapi", "libc", "log", "mach2", "memfd", - "object", + "object 0.36.7", "once_cell", "postcard", "psm", "pulley-interpreter", "rayon", - "rustix 1.0.7", - "semver", + "rustix 1.1.3", + "semver 1.0.27", "serde", "serde_derive", "serde_json", @@ -6797,7 +8735,7 @@ version = "33.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0fc91372865167a695dc98d0d6771799a388a7541d3f34e939d0539d6583de" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 1.0.4", ] [[package]] @@ -6811,11 +8749,11 @@ dependencies = [ "directories-next", "log", "postcard", - "rustix 1.0.7", + "rustix 1.1.3", "serde", "serde_derive", "sha2", - "toml 0.8.15", + "toml 0.8.23", "windows-sys 0.59.0", "zstd", ] @@ -6829,7 +8767,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -6848,16 +8786,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2bd72f0a6a0ffcc6a184ec86ac35c174e48ea0e97bbae277c8f15f8bf77a566" dependencies = [ "anyhow", - "cfg-if 1.0.0", + "cfg-if 1.0.4", "cranelift-codegen", "cranelift-control", "cranelift-entity", "cranelift-frontend", "cranelift-native", - "gimli 0.31.1", - "itertools", + "gimli", + "itertools 0.14.0", "log", - "object", + "object 0.36.7", "pulley-interpreter", "smallvec", "target-lexicon", @@ -6877,13 +8815,13 @@ dependencies = [ "cpp_demangle", "cranelift-bitset", "cranelift-entity", - "gimli 0.31.1", - "indexmap 2.11.4", + "gimli", + "indexmap 2.12.1", "log", - "object", + "object 0.36.7", "postcard", "rustc-demangle", - "semver", + "semver 1.0.27", "serde", "serde_derive", "smallvec", @@ -6902,8 +8840,8 @@ checksum = "dc8965d2128c012329f390e24b8b2758dd93d01bf67e1a1a0dd3d8fd72f56873" dependencies = [ "anyhow", "cc", - "cfg-if 1.0.0", - "rustix 1.0.7", + "cfg-if 1.0.4", + "rustix 1.1.3", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", "windows-sys 0.59.0", @@ -6916,8 +8854,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5882706a348c266b96dd81f560c1f993c790cf3a019857a9cde5f634191cfbb" dependencies = [ "cc", - "object", - "rustix 1.0.7", + "object 0.36.7", + "rustix 1.1.3", "wasmtime-versioned-export-macros", ] @@ -6928,7 +8866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7af0e940cb062a45c0b3f01a926f77da5947149e99beb4e3dd9846d5b8f11619" dependencies = [ "anyhow", - "cfg-if 1.0.0", + "cfg-if 1.0.4", "libc", "windows-sys 0.59.0", ] @@ -6956,7 +8894,7 @@ checksum = "d0963c1438357a3d8c0efe152b4ef5259846c1cf8b864340270744fe5b3bae5e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] [[package]] @@ -6967,8 +8905,8 @@ checksum = "cbc3b117d03d6eeabfa005a880c5c22c06503bb8820f3aa2e30f0e8d87b6752f" dependencies = [ "anyhow", "cranelift-codegen", - "gimli 0.31.1", - "object", + "gimli", + "object 0.36.7", "target-lexicon", "wasmparser 0.229.0", "wasmtime-cranelift", @@ -6984,37 +8922,51 @@ checksum = "1382f4f09390eab0d75d4994d0c3b0f6279f86a571807ec67a8253c87cf6a145" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.11.4", + "indexmap 2.12.1", "wit-parser", ] +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures 0.3.31", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "wast" -version = "233.0.0" +version = "243.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eaf4099d8d0c922b83bf3c90663f5666f0769db9e525184284ebbbdb1dd2180" +checksum = "df21d01c2d91e46cb7a221d79e58a2d210ea02020d57c092e79255cc2999ca7f" dependencies = [ "bumpalo", "leb128fmt", "memchr", - "unicode-width 0.2.0", - "wasm-encoder 0.233.0", + "unicode-width 0.2.2", + "wasm-encoder 0.243.0", ] [[package]] name = "wat" -version = "1.233.0" +version = "1.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d9bc80f5e4b25ea086ef41b91ccd244adde45d931c384d94a8ff64ab8bd7d87" +checksum = "226a9a91cd80a50449312fef0c75c23478fcecfcc4092bdebe1dc8e760ef521b" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -7035,10 +8987,10 @@ name = "web3" version = "0.19.0-graph" source = "git+https://github.com/graphprotocol/rust-web3?branch=graph-patches-onto-0.18#f9f27f45ce23bf489d8bd010b50b2b207eb316cb" dependencies = [ - "arrayvec 0.7.4", + "arrayvec 0.7.6", "base64 0.13.1", "bytes", - "derive_more 0.99.19", + "derive_more 0.99.20", "ethabi", "ethereum-types", "futures 0.3.31", @@ -7053,7 +9005,7 @@ dependencies = [ "pin-project", "reqwest", "rlp", - "secp256k1", + "secp256k1 0.21.3", "serde", "serde_json", "soketto", @@ -7072,11 +9024,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" dependencies = [ "native-tls", - "thiserror 1.0.61", + "thiserror 1.0.69", "tokio", "url", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.4", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -7086,20 +9056,26 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.34", + "rustix 0.38.44", ] [[package]] name = "whoami" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall 0.4.1", + "libredox", "wasite", "web-sys", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -7118,11 +9094,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7140,7 +9116,7 @@ dependencies = [ "anyhow", "cranelift-assembler-x64", "cranelift-codegen", - "gimli 0.31.1", + "gimli", "regalloc2", "smallvec", "target-lexicon", @@ -7152,61 +9128,72 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-link" -version = "0.1.3" +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-registry" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.48.5", + "windows-link", ] [[package]] @@ -7233,22 +9220,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -7269,27 +9250,21 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -7298,15 +9273,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -7316,15 +9285,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -7334,9 +9297,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -7346,15 +9309,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -7364,15 +9321,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -7382,15 +9333,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -7400,15 +9345,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -7418,34 +9357,19 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" - [[package]] name = "wiremock" version = "0.6.5" @@ -7456,7 +9380,7 @@ dependencies = [ "base64 0.22.1", "deadpool", "futures 0.3.31", - "http 1.3.1", + "http 1.4.0", "http-body-util", "hyper 1.8.1", "hyper-util", @@ -7470,13 +9394,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.33.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.9.0", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wit-parser" @@ -7486,9 +9407,9 @@ checksum = "459c6ba62bf511d6b5f2a845a2a736822e38059c1cfa0b644b467bbbfae4efa6" dependencies = [ "anyhow", "id-arena", - "indexmap 2.11.4", + "indexmap 2.12.1", "log", - "semver", + "semver 1.0.27", "serde", "serde_derive", "serde_json", @@ -7497,16 +9418,29 @@ dependencies = [ ] [[package]] -name = "write16" -version = "1.0.0" +name = "writeable" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] -name = "writeable" -version = "0.5.5" +name = "ws_stream_wasm" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" +dependencies = [ + "async_io_stream", + "futures 0.3.31", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper", + "thiserror 2.0.17", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "wyz" @@ -7519,9 +9453,9 @@ dependencies = [ [[package]] name = "xxhash-rust" -version = "0.8.11" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63658493314859b4dfdf3fb8c1defd61587839def09582db50b8a4e93afca6bb" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yansi" @@ -7531,11 +9465,10 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -7543,16 +9476,36 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "zerofrom" version = "0.1.6" @@ -7570,21 +9523,46 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -7593,15 +9571,21 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.111", ] +[[package]] +name = "zmij" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba" + [[package]] name = "zstd" version = "0.13.3" @@ -7622,9 +9606,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 7100163b303..108577cade3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ repository = "https://github.com/graphprotocol/graph-node" license = "MIT OR Apache-2.0" [workspace.dependencies] +alloy = { version = "1.0.33", features = ["dyn-abi", "json-abi", "full", "arbitrary", "json-rpc"] } +alloy-rpc-types = "1.0.33" anyhow = "1.0" async-graphql = { version = "7.1.0", features = ["chrono"] } async-graphql-axum = "7.0.17" @@ -98,6 +100,7 @@ tokio-retry = "0.3.0" tonic = { version = "0.12.3", features = ["tls-roots", "gzip"] } tonic-build = { version = "0.12.3", features = ["prost"] } tower-http = { version = "0.6.8", features = ["cors"] } +tower = { version = "0.5.1", features = ["full"] } wasmparser = "0.118.1" wasmtime = { version = "33.0.2", features = ["async"] } substreams = "=0.6.0" diff --git a/chain/ethereum/Cargo.toml b/chain/ethereum/Cargo.toml index f3780ddd224..f698bcdfd3a 100644 --- a/chain/ethereum/Cargo.toml +++ b/chain/ethereum/Cargo.toml @@ -18,6 +18,7 @@ semver = "1.0.27" thiserror = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } +tower = { workspace = true } itertools = "0.14.0" diff --git a/chain/ethereum/build.rs b/chain/ethereum/build.rs index 227a50914a6..cb2257bc845 100644 --- a/chain/ethereum/build.rs +++ b/chain/ethereum/build.rs @@ -3,6 +3,7 @@ fn main() { tonic_build::configure() .out_dir("src/protobuf") + .protoc_arg("--experimental_allow_proto3_optional") .compile_protos(&["proto/ethereum.proto"], &["proto"]) .expect("Failed to compile Firehose Ethereum proto(s)"); } diff --git a/chain/ethereum/proto/ethereum.proto b/chain/ethereum/proto/ethereum.proto index 42adbd0ffa6..50c10f921f0 100644 --- a/chain/ethereum/proto/ethereum.proto +++ b/chain/ethereum/proto/ethereum.proto @@ -2,15 +2,62 @@ syntax = "proto3"; package sf.ethereum.type.v2; -option go_package = "github.com/streamingfast/sf-ethereum/types/pb/sf/ethereum/type/v2;pbeth"; +option go_package = "github.com/streamingfast/firehose-ethereum/types/pb/sf/ethereum/type/v2;pbeth"; import "google/protobuf/timestamp.proto"; +// Block is the representation of the tracing of a block in the Ethereum +// blockchain. A block is a collection of [TransactionTrace] that are grouped +// together and processed as an atomic unit. Each [TransactionTrace] is composed +// of a series of [Call] (a.k.a internal transactions) and there is also at +// least one call per transaction a.k.a the root call which essentially has the +// same parameters as the transaction itself (e.g. `from`, `to`, `gas`, `value`, +// etc.). +// +// The exact tracing method used to build the block must be checked against +// [DetailLevel] field. There is two levels of details available, `BASE` and +// `EXTENDED`. The `BASE` level has been extracted using archive node RPC calls +// and will contain only the block header, transaction receipts and event logs. +// Refers to the Firehose service provider to know which blocks are offered on +// each network. +// +// The `EXTENDED` level has been extracted using the Firehose tracer and all +// fields are available in this Protobuf. +// +// The Ethereum block model is used across many chains which means that it +// happen that certain fields are not available in one chain but are available +// in another. Each field should be documented when necesssary if it's available +// on a subset of chains. +// +// One major concept to get about the Block is the concept of 'ordinal'. The +// ordinal is a number that is used to globally order every element of execution +// that happened throughout the processing of the block like +// [TransactionTracer], [Call], [Log], [BalanceChange], [StateChange], etc. +// Element that have a start and end interval, [Transaction] and [Call], will +// have two ordinals: `begin_ordinal` and `end_ordinal`. Element that are +// executed as "point in time" [Log], [BalanceChange], [StateChange], etc. will +// have only one ordinal named `ordinal`. If you take all of the message in the +// Block that have an 'ordinal' field in an array and you sort each element +// against the `ordinal` field, you will get the exact order of execution of +// each element in the block. +// +// All the 'ordinal' fields in a block are globally unique for the given block, +// it is **not** a chain-wide global ordering. Furthermore, caution must be take +// with reverted elements due to execution failure. For anything attached to a +// [Call] that has a `state_reverted` field set to `true`, the `ordinal` field +// is not reliable and should not be used to order the element against other +// elements in the block as those element might have 0 as the ordinal. Only +// successful calls have a reliable `ordinal` field. message Block { - int32 ver = 1; + // Hash is the block's hash. bytes hash = 2; + // Number is the block's height at which this block was mined. uint64 number = 3; + // Size is the size in bytes of the RLP encoding of the block according to Ethereum + // rules. uint64 size = 4; + // Header contain's the block's header information like its parent hash, the merkel root hash + // and all other information the form a block. BlockHeader header = 5; // Uncles represents block produced with a valid solution but were not actually chosen @@ -20,46 +67,79 @@ message Block { // field will actually be always empty. repeated BlockHeader uncles = 6; + // TransactionTraces hold the execute trace of all the transactions that were executed + // in this block. In in there that you will find most of the Ethereum data model. + // + // They are ordered by the order of execution of the transaction in the block. repeated TransactionTrace transaction_traces = 10; + + // BalanceChanges here is the array of ETH transfer that happened at the block level + // outside of the normal transaction flow of a block. The best example of this is mining + // reward for the block mined, the transfer of ETH to the miner happens outside the normal + // transaction flow of the chain and is recorded as a `BalanceChange` here since we cannot + // attached it to any transaction. + // + // Only available in DetailLevel: EXTENDED repeated BalanceChange balance_changes = 11; - repeated CodeChange code_changes = 20; - reserved 40; // bool filtering_applied = 40 [deprecated = true]; - reserved 41; // string filtering_include_filter_expr = 41 [deprecated = true]; - reserved 42; // string filtering_exclude_filter_expr = 42 [deprecated = true]; -} + enum DetailLevel{ + DETAILLEVEL_EXTENDED = 0; + // DETAILLEVEL_TRACE = 1; // TBD + DETAILLEVEL_BASE = 2; + } -// HeaderOnlyBlock is used to optimally unpack the [Block] structure (note the -// corresponding message number for the `header` field) while consuming less -// memory, when only the `header` is desired. -// -// WARN: this is a client-side optimization pattern and should be moved in the -// consuming code. -message HeaderOnlyBlock { - BlockHeader header = 5; -} + // DetailLevel affects the data available in this block. + // + // ## DetailLevel_EXTENDED + // + // Describes the most complete block, with traces, balance changes, storage + // changes. It is extracted during the execution of the block. + // + // ## DetailLevel_BASE + // + // Describes a block that contains only the block header, transaction receipts + // and event logs: everything that can be extracted using the base JSON-RPC + // interface + // (https://ethereum.org/en/developers/docs/apis/json-rpc/#json-rpc-methods) + // Furthermore, the eth_getTransactionReceipt call has been avoided because it + // brings only minimal improvements at the cost of requiring an archive node + // or a full node with complete transaction index. + DetailLevel detail_level = 12; + + // CodeChanges here is the array of smart code change that happened that happened at the block level + // outside of the normal transaction flow of a block. Some Ethereum's fork like BSC and Polygon + // has some capabilities to upgrade internal smart contracts used usually to track the validator + // list. + // + // On hard fork, some procedure runs to upgrade the smart contract code to a new version. In those + // network, a `CodeChange` for each modified smart contract on upgrade would be present here. Note + // that this happen rarely, so the vast majority of block will have an empty list here. + // + // Only available in DetailLevel: EXTENDED + repeated CodeChange code_changes = 20; -// BlockWithRefs is a lightweight block, with traces and transactions -// purged from the `block` within, and only. It is used in transports -// to pass block data around. -message BlockWithRefs { - string id = 1; - Block block = 2; - TransactionRefs transaction_trace_refs = 3; - bool irreversible = 4; -} + // System calls are introduced in Cancun, along with blobs. They are executed outside of transactions but affect the state. + // + // Only available in DetailLevel: EXTENDED + repeated Call system_calls = 21; -message TransactionRefs { - repeated bytes hashes = 1; -} + // Withdrawals represents the list of validator balance withdrawals processed in this block. + // Introduced in the Shanghai hard fork (EIP-4895). + // + // This field has been added because Geth blocks include withdrawals after Shanghai fork, + // but our previous Firehose model didn't capture this data. Currently experimental - + // NOT ready for production use yet as we validate the tracing implementation. + // + // Only available when Shanghai fork is active on the chain. + repeated Withdrawal withdrawals = 22; -message UnclesHeaders { - repeated BlockHeader uncles = 1; -} + reserved 40; // bool filtering_applied = 40 [deprecated = true]; + reserved 41; // string filtering_include_filter_expr = 41 [deprecated = true]; + reserved 42; // string filtering_exclude_filter_expr = 42 [deprecated = true]; -message BlockRef { - bytes hash = 1; - uint64 number = 2; + // Ver represents that data model version of the block, it is used internally by Firehose on Ethereum + // as a validation that we are reading the correct version. + int32 ver = 1; } message BlockHeader { @@ -84,13 +164,10 @@ message BlockHeader { // consensus algorithm, this field will actually be constant and set to `0x00`. BigInt difficulty = 8; - // TotalDifficulty is the sum of all previous blocks difficulty including this block difficulty. + // TotalDifficulty used to be the sum of all previous blocks difficulty including this block difficulty. // - // If the Block containing this `BlockHeader` has been produced using the Proof of Stake - // consensus algorithm, this field will actually be constant and set to the terminal total difficulty - // that was required to transition to Proof of Stake algorithm, which varies per network. It is set to - // 58 750 000 000 000 000 000 000 on Ethereum Mainnet and to 10 790 000 on Ethereum Testnet Goerli. - BigInt total_difficulty = 17; + // It has been deprecated in geth v1.15.0 but was already removed from the JSON-RPC interface for a while + BigInt total_difficulty = 17 [deprecated = true]; uint64 number = 9; uint64 gas_limit = 10; @@ -134,19 +211,84 @@ message BlockHeader { // extra_data, // mix_hash, // nonce, - // base_fee_per_gas + // base_fee_per_gas (to be included only if London fork is active) + // withdrawals_root (to be included only if Shangai fork is active) + // blob_gas_used (to be included only if Cancun fork is active) + // excess_blob_gas (to be included only if Cancun fork is active) + // parent_beacon_root (to be included only if Cancun fork is active) + // requests_hash (to be included only if Prague fork is active) // ])) // bytes hash = 16; // Base fee per gas according to EIP-1559 (e.g. London Fork) rules, only set if London is present/active on the chain. BigInt base_fee_per_gas = 18; + + // Withdrawals root hash according to EIP-4895 (e.g. Shangai Fork) rules, only set if Shangai is present/active on the chain. + // + // Only available in DetailLevel: EXTENDED + bytes withdrawals_root = 19; + + // TxDependency is list of transaction indexes that are dependent on each other in the block + // header. This is metadata only that was used by the internal Polygon parallel execution engine. + // + // This field was available in a few versions on Polygon Mainnet and Polygon Mumbai chains. It was actually + // removed and is not populated anymore. It's now embedded in the `extraData` field, refer to Polygon source + // code to determine how to extract it if you need it. + // + // Only available in DetailLevel: EXTENDED + Uint64NestedArray tx_dependency = 20; + + // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. + optional uint64 blob_gas_used = 22; + + // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. + optional uint64 excess_blob_gas = 23; + + // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. + bytes parent_beacon_root = 24; + + // RequestsHash was added by EIP-7685 and is ignored in legacy headers. + bytes requests_hash = 25; +} + +message Uint64NestedArray { + repeated Uint64Array val = 1; +} + +message Uint64Array { + repeated uint64 val = 1; } message BigInt { bytes bytes = 1; } +// TransactionTrace is full trace of execution of the transaction when the +// it actually executed on chain. +// +// It contains all the transaction details like `from`, `to`, `gas`, etc. +// as well as all the internal calls that were made during the transaction. +// +// The `calls` vector contains Call objects which have balance changes, events +// storage changes, etc. +// +// If ordering is important between elements, almost each message like `Log`, +// `Call`, `StorageChange`, etc. have an ordinal field that is represents "execution" +// order of the said element against all other elements in this block. +// +// Due to how the call tree works doing "naively", looping through all calls then +// through a Call's element like `logs` while not yielding the elements in the order +// they were executed on chain. A log in call could have been done before or after +// another in another call depending on the actual call tree. +// +// The `calls` are ordered by creation order and the call tree can be re-computing +// using fields found in `Call` object (parent/child relationship). +// +// Another important thing to note is that even if a transaction succeed, some calls +// within it could have been reverted internally, if this is important to you, you must +// check the field `state_reverted` on the `Call` to determine if it was fully committed +// to the chain or not. message TransactionTrace { // consensus bytes to = 1; @@ -192,19 +334,47 @@ message TransactionTrace { // All transactions that ever existed prior Berlin fork before EIP-2718 was implemented. TRX_TYPE_LEGACY = 0; - // Field that specifies an access list of contract/storage_keys that is going to be used + // Transaction that specicy an access list of contract/storage_keys that is going to be used // in this transaction. // // Added in Berlin fork (EIP-2930). TRX_TYPE_ACCESS_LIST = 1; - // Transaction that specifies an access list just like TRX_TYPE_ACCESS_LIST but in addition defines the + // Transaction that specifis an access list just like TRX_TYPE_ACCESS_LIST but in addition defines the // max base gas gee and max priority gas fee to pay for this transaction. Transaction's of those type are // executed against EIP-1559 rules which dictates a dynamic gas cost based on the congestion of the network. TRX_TYPE_DYNAMIC_FEE = 2; + + // Transaction which contain a large amount of data that cannot be accessed by EVM execution, but whose commitment + // can be accessed. The format is intended to be fully compatible with the format that will be used in full sharding. + // + // Transaction that defines an access list just like TRX_TYPE_ACCESS_LIST and enables dynamic fee just like + // TRX_TYPE_DYNAMIC_FEE but in addition defines the fields 'max_fee_per_data_gas' of type 'uint256' and the fields + // 'blob_versioned_hashes' which represents a list of hash outputs from 'kzg_to_versioned_hash'. + // + // Activated in Cancun fork (EIP-4844) + TRX_TYPE_BLOB = 3; + + // Transaction that sets code to an EOA (Externally Owned Accounts) + // + // Activated in Prague (EIP-7702) + TRX_TYPE_SET_CODE = 4; + + // Arbitrum-specific transactions + TRX_TYPE_ARBITRUM_DEPOSIT = 100; + TRX_TYPE_ARBITRUM_UNSIGNED = 101; + TRX_TYPE_ARBITRUM_CONTRACT = 102; + TRX_TYPE_ARBITRUM_RETRY = 104; + TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE = 105; + TRX_TYPE_ARBITRUM_INTERNAL = 106; + TRX_TYPE_ARBITRUM_LEGACY = 120; + + // OPTIMISM-specific transactions + TRX_TYPE_OPTIMISM_DEPOSIT = 126; + } - // AcccessList represents the storage access this transaction has agreed to do in which case those storage + // AccessList represents the storage access this transaction has agreed to do in which case those storage // access cost less gas unit per access. // // This will is populated only if `TransactionTrace.Type == TRX_TYPE_ACCESS_LIST || TRX_TYPE_DYNAMIC_FEE` which @@ -215,6 +385,8 @@ message TransactionTrace { // // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only // if London fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED BigInt max_fee_per_gas = 11; // MaxPriorityFeePerGas is priority fee per gas the user to pay in extra to the miner on top of the block's @@ -222,22 +394,106 @@ message TransactionTrace { // // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only // if London fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED BigInt max_priority_fee_per_gas = 13; // meta uint32 index = 20; bytes hash = 21; bytes from = 22; + + // Only available in DetailLevel: EXTENDED + // Known Issues + // - Version 3: + // Field not populated. It will be empty. + // + // Fixed in `Version 4`, see https://docs.substreams.dev/reference-material/chains-and-endpoints/ethereum-data-model for information about block versions. bytes return_data = 23; + + // Only available in DetailLevel: EXTENDED bytes public_key = 24; + + // The block's global ordinal when the transaction started executing, refer to + // [Block] documentation for further information about ordinals and total ordering. uint64 begin_ordinal = 25; + + // The block's global ordinal when the transaction finished executing, refer to + // [Block] documentation for further information about ordinals and total ordering. uint64 end_ordinal = 26; + // TransactionTraceStatus is the status of the transaction execution and will let you know if the transaction + // was successful or not. + // + // ## Explanation relevant only for blocks with `DetailLevel: EXTENDED` + // + // A successful transaction has been recorded to the blockchain's state for calls in it that were successful. + // This means it's possible only a subset of the calls were properly recorded, refer to [calls[].state_reverted] field + // to determine which calls were reverted. + // + // A quirks of the Ethereum protocol is that a transaction `FAILED` or `REVERTED` still affects the blockchain's + // state for **some** of the state changes. Indeed, in those cases, the transactions fees are still paid to the miner + // which means there is a balance change for the transaction's emitter (e.g. `from`) to pay the gas fees, an optional + // balance change for gas refunded to the transaction's emitter (e.g. `from`) and a balance change for the miner who + // received the transaction fees. There is also a nonce change for the transaction's emitter (e.g. `from`). + // + // This means that to properly record the state changes for a transaction, you need to conditionally procees the + // transaction's status. + // + // For a `SUCCEEDED` transaction, you iterate over the `calls` array and record the state changes for each call for + // which `state_reverted == false` (if a transaction succeeded, the call at #0 will always `state_reverted == false` + // because it aligns with the transaction). + // + // For a `FAILED` or `REVERTED` transaction, you iterate over the root call (e.g. at #0, will always exist) for + // balance changes you process those where `reason` is either `REASON_GAS_BUY`, `REASON_GAS_REFUND` or + // `REASON_REWARD_TRANSACTION_FEE` and for nonce change, still on the root call, you pick the nonce change which the + // smallest ordinal (if more than one). TransactionTraceStatus status = 30; + TransactionReceipt receipt = 31; + + // Only available in DetailLevel: EXTENDED repeated Call calls = 32; -} + // BlobGas is the amount of gas the transaction is going to pay for the blobs, this is a computed value + // equivalent to `self.blob_gas_fee_cap * len(self.blob_hashes)` and provided in the model for convenience. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + optional uint64 blob_gas = 33; + + // BlobGasFeeCap is the maximum fee per data gas the user is willing to pay for the data gas used. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + optional BigInt blob_gas_fee_cap = 34; + + // BlobHashes field represents a list of hash outputs from 'kzg_to_versioned_hash' which + // essentially is a version byte + the sha256 hash of the blob commitment (e.g. + // `BLOB_COMMITMENT_VERSION_KZG + sha256(commitment)[1:]`. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + repeated bytes blob_hashes = 35; + + // SetCodeAuthorizations represents the authorizations of a transaction to set code to an EOA (Externally Owned Accounts) + // as defined in EIP-7702. The list will contain all the authorizations as they were specified in the + // transaction itself regardless of their validity. If you need to determined if a given authorization was + // correctly applied on chain's state, refer to [SetCodeAuthorization.discarded] field that records + // if the authorization was discarded or not by the chain due to invalidity. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-7702 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_SET_CODE` which is possible only + // if Prague fork is active on the chain. + repeated SetCodeAuthorization set_code_authorizations = 36; +} // AccessTuple represents a list of storage keys for a given contract's address and is used // for AccessList construction. @@ -246,10 +502,64 @@ message AccessTuple { repeated bytes storage_keys = 2; } -// TransactionTraceWithBlockRef -message TransactionTraceWithBlockRef { - TransactionTrace trace = 1; - BlockRef block_ref = 2; +// SetCodeAuthorization represents the authorization of a transaction to set code of an EOA (Externally Owned Account) +// as defined in EIP-7702. +// +// The 'authority' field is the address that is authorizing the delegation mechanism. The 'authority' value is computed +// from the signature contained in the message using the computation +// `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s)` +// where `MAGIC` is `0x5`, `||` is the bytes concatenation operator, `ecrecover` is the Ethereum signature recovery +// and `y_parity` is the recovery ID value denoted `v` in the message below. Checking the go-ethereum implementation +// at https://github.com/ethereum/go-ethereum/blob/v1.15.0/core/types/tx_setcode.go#L117 might prove easier to "read". +// +// We do extract the 'authority' value from the signature in the message and store it in the 'authority' field for +// convenience so you don't need to perform the computation yourself. +message SetCodeAuthorization { + // Discarded determines if this authorization was skipped due to being invalid. As EIP-7702 states, + // if the authorization is invalid (invalid signature, nonce mismatch, etc.) it must be simply + // discarded and the transaction is processed as if the authorization was not present in the + // authorization list. + // + // This boolean records if the authorization was discarded or not by the chain due to invalidity. + bool discarded = 1; + + // ChainID is the chain ID of the chain where the transaction was executed, used + // to recover the authority from the signature. + bytes chain_id = 2; + + // Address contains the address this account is delegating to. This address usually + // contain code that this account essentially "delegates" to. + // + // Note: This was missing when EIP-7702 was first activated on Holesky, Sepolia, BSC Chapel, + // BSC Mainnet and Arbitrum Sepolia but was ready for Ethereum Mainnet hard fork. We will backfill + // those missing values in the near future at which point we will remove this note. + bytes address = 8; + + // Nonce is the nonce of the account that is authorizing delegation mechanism, EIP-7702 rules + // states that nonce should be verified using this rule: + // + // - Verify the nonce of authority is equal to nonce. In case authority does not exist in the trie, + // verify that nonce is equal to 0. + // + // Read SetCodeAuthorization to know how to recover the `authority` value. + uint64 nonce = 3; + + // V is the recovery ID value for the signature Y point. While it's defined as a + // `uint32`, it's actually bounded by a `uint8` data type withing the Ethereum protocol. + uint32 v = 4; + + // R is the signature's X point on the elliptic curve (32 bytes). + bytes r = 5; + + // S is the signature's Y point on the elliptic curve (32 bytes). + bytes s = 6; + + // Authority is the address of the account that is authorizing delegation mechanism, it + // is computed from the signature contained in the message and stored for convenience. + // + // If the authority cannot be recovered from the signature, this field will be empty and + // the `discarded` field will be set to `true`. + optional bytes authority = 7; } enum TransactionTraceStatus { @@ -263,9 +573,7 @@ message TransactionReceipt { // State root is an intermediate state_root hash, computed in-between transactions to make // **sure** you could build a proof and point to state in the middle of a block. Geth client // uses `PostState + root + PostStateOrStatus`` while Parity used `status_code, root...`` this piles - // hardforks, see (read the EIPs first): - // - https://github.com/eoscanada/go-ethereum-private/blob/deep-mind/core/types/receipt.go#L147 - // - https://github.com/eoscanada/go-ethereum-private/blob/deep-mind/core/types/receipt.go#L50-L86 + // hard forks, see (read the EIPs first): // - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md // // Moreover, the notion of `Outcome`` in parity, which segregates the two concepts, which are @@ -277,6 +585,23 @@ message TransactionReceipt { uint64 cumulative_gas_used = 2; bytes logs_bloom = 3; repeated Log logs = 4; + + // BlobGasUsed is the amount of blob gas that has been used within this transaction. At time + // of writing, this is equal to `self.blob_gas_fee_cap * len(self.blob_hashes)`. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + optional uint64 blob_gas_used = 5; + + // BlobGasPrice is the amount to pay per blob item in the transaction. + // + // This is specified by https://eips.ethereum.org/EIPS/eip-4844 + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + // if Cancun fork is active on the chain. + optional BigInt blob_gas_price = 6; } message Log { @@ -285,8 +610,10 @@ message Log { bytes data = 3; // Index is the index of the log relative to the transaction. This index - // is always populated regardless of the state reversion of the call + // is always populated regardless of the state reversion of the the call // that emitted this log. + // + // Only available in DetailLevel: EXTENDED uint32 index = 4; // BlockIndex represents the index of the log relative to the Block. @@ -294,7 +621,7 @@ message Log { // An **important** notice is that this field will be 0 when the call // that emitted the log has been reverted by the chain. // - // Currently, there are two locations where a Log can be obtained: + // Currently, there is two locations where a Log can be obtained: // - block.transaction_traces[].receipt.logs[] // - block.transaction_traces[].calls[].logs[] // @@ -306,6 +633,8 @@ message Log { // the `blockIndex` value will always be 0. uint32 blockIndex = 6; + // The block's global ordinal when the log was recorded, refer to [Block] + // documentation for further information about ordinals and total ordering. uint64 ordinal = 7; } @@ -316,25 +645,76 @@ message Call { CallType call_type = 4; bytes caller = 5; bytes address = 6; + + // AddressDelegatesTo contains the address from which the actual code to execute will be loaded + // as defined per EIP-7702 rules. If the Call's address value resolves to a code + // that delegates to another address, this field will be populated with the address + // that the call is delegated to. It will be empty in all other situations. + // + // Assumes that a 'SetCode' transaction set address `0xA` to delegates to address `0xB`, + // then when a call is made to `0xA`, the Call object would have: + // + // - caller = + // - address = 0xA + // - address_delegates_to = 0xB + // + // Again, it's important to emphasize that this field relates to EIP-7702, if the call is + // a DELEGATE or CALLCODE type, this field will not be populated and will remain empty. + // + // It will be populated only if EIP-7702 is active on the chain (Prague fork) and if the + // 'address' of the call was pointing to another address at time of execution. + optional bytes address_delegates_to = 34; + BigInt value = 7; uint64 gas_limit = 8; uint64 gas_consumed = 9; bytes return_data = 13; + + // Known Issues + // - Version 3: + // When call is `CREATE` or `CREATE2`, this field is not populated. A couple of suggestions: + // 1. You can get the contract's code in the `code_changes` field. + // 2. In the root `CREATE` call, you can directly use the `TransactionTrace`'s input field. + // + // Fixed in `Version 4`, see https://docs.substreams.dev/reference-material/chains-and-endpoints/ethereum-data-model for information about block versions. bytes input = 14; + + // Indicates whether the call executed code. + // + // Known Issues + // - Version 3: + // This may be incorrectly set to `false` for accounts with code handling native value transfers, + // as well as for certain precompiles with no input. + // The value is initially set based on `call.type != CREATE && len(call.input) > 0` + // and later adjusted if the tracer detects an account without code. + // + // Fixed in `Version 4`, see https://docs.substreams.dev/reference-material/chains-and-endpoints/ethereum-data-model for information about block versions. bool executed_code = 15; bool suicide = 16; /* hex representation of the hash -> preimage */ map keccak_preimages = 20; + + // Known Issues + // - Version 3: + // The data might be not be in order. + // + // Fixed in `Version 4`, see https://docs.substreams.dev/reference-material/chains-and-endpoints/ethereum-data-model for information about block versions. repeated StorageChange storage_changes = 21; repeated BalanceChange balance_changes = 22; repeated NonceChange nonce_changes = 24; repeated Log logs = 25; repeated CodeChange code_changes = 26; - // Deprecated: repeated bytes created_accounts reserved 27; + // Known Issues + // - Version 3: + // Some gas changes are not correctly tracked: + // 1. Gas refunded due to data returned to the chain (occurs at the end of a transaction, before buyback). + // 2. Initial gas allocation (0 -> GasLimit) at the start of a call. + // 3. Final gas deduction (LeftOver -> 0) at the end of a call (if applicable). + // Fixed in `Version 4`, see https://docs.substreams.dev/reference-material/chains-and-endpoints/ethereum-data-model for information about block versions. repeated GasChange gas_changes = 28; // Deprecated: repeated GasEvent gas_events @@ -375,13 +755,44 @@ message Call { // ``` // // In the transaction above, while Call #2 and Call #3 would have the - // status `EXECUTED` + // status `EXECUTED`. + // + // If you check all calls and check only `state_reverted` flag, you might be missing + // some balance changes and nonce changes. This is because when a full transaction fails + // in ethereum (e.g. `calls.all(x.state_reverted == true)`), there is still the transaction + // fee that are recorded to the chain. + // + // Refer to [TransactionTrace#status] field for more details about the handling you must + // perform. bool state_reverted = 30; + // Known Issues + // - Version 3: + // 1. The block's global ordinal when the call started executing, refer to + // [Block] documentation for further information about ordinals and total ordering. + // 2. The transaction root call `begin_ordial` is always `0` (also in the GENESIS block), which can cause issues + // when sorting by this field. To ensure proper execution order, set it as follows: + // `trx.Calls[0].BeginOrdinal = trx.BeginOrdinal`. + // + // Fixed in `Version 4`, see https://docs.substreams.dev/reference-material/chains-and-endpoints/ethereum-data-model for information about block versions. uint64 begin_ordinal = 31; + + // Known Issues + // - Version 3: + // 1. The block's global ordinal when the call finished executing, refer to + // [Block] documentation for further information about ordinals and total ordering. + // 2. The root call of the GENESIS block is always `0`. To fix it, you can set it as follows: + // `rx.Calls[0].EndOrdinal = max.Uint64`. + // + // Fixed in `Version 4`, see https://docs.substreams.dev/reference-material/chains-and-endpoints/ethereum-data-model for information about block versions. uint64 end_ordinal = 32; - repeated AccountCreation account_creations = 33; + // Known Issues + // - Version 4: + // AccountCreations are NOT SUPPORTED anymore. DO NOT rely on them. + repeated AccountCreation account_creations = 33 [deprecated = true]; + + // The identifier 34 is taken by 'address_delegates_to' field above. reserved 50; // repeated ERC20BalanceChange erc20_balance_changes = 50 [deprecated = true]; reserved 51; // repeated ERC20TransferEvent erc20_transfer_events = 51 [deprecated = true]; @@ -403,20 +814,42 @@ message StorageChange { bytes old_value = 3; bytes new_value = 4; + // The block's global ordinal when the storage change was recorded, refer to [Block] + // documentation for further information about ordinals and total ordering. uint64 ordinal = 5; } message BalanceChange { + // Address is the address of the account that has changed balance. bytes address = 1; + + // OldValue is the balance of the address before the change. This value + // can be **nil/null/None** if there was no previous balance for the address. + // It is safe in those case(s) to consider the balance as being 0. + // + // If you consume this from a Substreams, you can safely use: + // + // ```ignore + // let old_value = old_value.unwrap_or_default(); + // ``` BigInt old_value = 2; - BigInt new_value = 3; - Reason reason = 4; - // Obtain all balance change reasons under deep mind repository: + // NewValue is the balance of the address after the change. This value + // can be **nil/null/None** if there was no previous balance for the address + // after the change. It is safe in those case(s) to consider the balance as being + // 0. + // + // If you consume this from a Substreams, you can safely use: // - // ```shell - // ack -ho 'BalanceChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq + // ```ignore + // let new_value = new_value.unwrap_or_default(); // ``` + BigInt new_value = 3; + + // Reason is the reason why the balance has changed. This is useful to determine + // why the balance has changed and what is the context of the change. + Reason reason = 4; + enum Reason { REASON_UNKNOWN = 0; REASON_REWARD_MINE_UNCLE = 1; @@ -435,8 +868,20 @@ message BalanceChange { REASON_CALL_BALANCE_OVERRIDE = 12; // Used on chain(s) where some Ether burning happens REASON_BURN = 15; + REASON_WITHDRAWAL = 16; + + // Rewards for Blob processing on BNB chain added in Tycho hard-fork, refers + // to BNB documentation to check the timestamp at which it was activated. + REASON_REWARD_BLOB_FEE = 17; + + // This reason is used only on Optimism chain. + REASON_INCREASE_MINT = 18; + // This reason is used only on Optimism chain. + REASON_REVERT = 19; } + // The block's global ordinal when the balance change was recorded, refer to [Block] + // documentation for further information about ordinals and total ordering. uint64 ordinal = 5; } @@ -444,11 +889,17 @@ message NonceChange { bytes address = 1; uint64 old_value = 2; uint64 new_value = 3; + + // The block's global ordinal when the nonce change was recorded, refer to [Block] + // documentation for further information about ordinals and total ordering. uint64 ordinal = 4; } message AccountCreation { bytes account = 1; + + // The block's global ordinal when the account creation was recorded, refer to [Block] + // documentation for further information about ordinals and total ordering. uint64 ordinal = 2; } @@ -459,6 +910,8 @@ message CodeChange { bytes new_hash = 4; bytes new_code = 5; + // The block's global ordinal when the code change was recorded, refer to [Block] + // documentation for further information about ordinals and total ordering. uint64 ordinal = 6; } @@ -466,43 +919,155 @@ message CodeChange { // The gas is computed per actual op codes. Doing them completely might prove // overwhelming in most cases. // -// Hence, we only index some of them, those that are costly like all the calls +// Hence, we only index some of them, those that are costy like all the calls // one, log events, return data, etc. message GasChange { uint64 old_value = 1; uint64 new_value = 2; Reason reason = 3; - // Obtain all gas change reasons under deep mind repository: - // - // ```shell - // ack -ho 'GasChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq - // ``` enum Reason { REASON_UNKNOWN = 0; + // REASON_CALL is the amount of gas that will be charged for a 'CALL' opcode executed by the EVM REASON_CALL = 1; + // REASON_CALL_CODE is the amount of gas that will be charged for a 'CALLCODE' opcode executed by the EVM REASON_CALL_CODE = 2; + // REASON_CALL_DATA_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM REASON_CALL_DATA_COPY = 3; + // REASON_CODE_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM REASON_CODE_COPY = 4; + // REASON_CODE_STORAGE is the amount of gas that will be charged for code storage REASON_CODE_STORAGE = 5; + // REASON_CONTRACT_CREATION is the amount of gas that will be charged for a 'CREATE' opcode executed by the EVM and for the gas + // burned for a CREATE, today controlled by EIP150 rules REASON_CONTRACT_CREATION = 6; + // REASON_CONTRACT_CREATION2 is the amount of gas that will be charged for a 'CREATE2' opcode executed by the EVM and for the gas + // burned for a CREATE2, today controlled by EIP150 rules REASON_CONTRACT_CREATION2 = 7; + // REASON_DELEGATE_CALL is the amount of gas that will be charged for a 'DELEGATECALL' opcode executed by the EVM REASON_DELEGATE_CALL = 8; + // REASON_EVENT_LOG is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM REASON_EVENT_LOG = 9; + // REASON_EXT_CODE_COPY is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM REASON_EXT_CODE_COPY = 10; + // REASON_FAILED_EXECUTION is the burning of the remaining gas when the execution failed without a revert REASON_FAILED_EXECUTION = 11; + // REASON_INTRINSIC_GAS is the amount of gas that will be charged for the intrinsic cost of the transaction, there is + // always exactly one of those per transaction REASON_INTRINSIC_GAS = 12; + // GasChangePrecompiledContract is the amount of gas that will be charged for a precompiled contract execution REASON_PRECOMPILED_CONTRACT = 13; + // REASON_REFUND_AFTER_EXECUTION is the amount of gas that will be refunded to the caller after the execution of the call, + // if there is left over at the end of execution REASON_REFUND_AFTER_EXECUTION = 14; + // REASON_RETURN is the amount of gas that will be charged for a 'RETURN' opcode executed by the EVM REASON_RETURN = 15; + // REASON_RETURN_DATA_COPY is the amount of gas that will be charged for a 'RETURNDATACOPY' opcode executed by the EVM REASON_RETURN_DATA_COPY = 16; + // REASON_REVERT is the amount of gas that will be charged for a 'REVERT' opcode executed by the EVM REASON_REVERT = 17; + // REASON_SELF_DESTRUCT is the amount of gas that will be charged for a 'SELFDESTRUCT' opcode executed by the EVM REASON_SELF_DESTRUCT = 18; + // REASON_STATIC_CALL is the amount of gas that will be charged for a 'STATICALL' opcode executed by the EVM REASON_STATIC_CALL = 19; + // REASON_STATE_COLD_ACCESS is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules + // // Added in Berlin fork (Geth 1.10+) REASON_STATE_COLD_ACCESS = 20; + + // REASON_TX_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call + // + // Added as new tracing reason in Geth, available only on some chains + REASON_TX_INITIAL_BALANCE = 21; + // REASON_TX_REFUNDS is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) + // this generates an increase in gas. There is only one such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + REASON_TX_REFUNDS = 22; + // REASON_TX_LEFT_OVER_RETURNED is the amount of gas left over at the end of transaction's execution that will be returned + // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas + // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. + // There is at most one of such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + REASON_TX_LEFT_OVER_RETURNED = 23; + + // REASON_CALL_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per call. + // + // Added as new tracing reason in Geth, available only on some chains + REASON_CALL_INITIAL_BALANCE = 24; + // REASON_CALL_LEFT_OVER_RETURNED is the amount of gas left over that will be returned to the caller, this change will always + // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even + // will be emitted. + REASON_CALL_LEFT_OVER_RETURNED = 25; + + // REASON_WITNESS_CONTRACT_INIT flags the event of adding to the witness during the contract creation initialization step. + REASON_WITNESS_CONTRACT_INIT = 26; + + // REASON_WITNESS_CONTRACT_CREATION flags the event of adding to the witness during the contract creation finalization step. + REASON_WITNESS_CONTRACT_CREATION = 27; + // REASON_WITNESS_CODE_CHUNK flags the event of adding one or more contract code chunks to the witness. + REASON_WITNESS_CODE_CHUNK = 28; + // REASON_WITNESS_CONTRACT_COLLISION_CHECK flags the event of adding to the witness when checking for contract address collision. + REASON_WITNESS_CONTRACT_COLLISION_CHECK = 29; + // REASON_TX_DATA_FLOOR is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the + // transaction data. This change will always be a negative change. + REASON_TX_DATA_FLOOR = 30; } + // The block's global ordinal when the gas change was recorded, refer to [Block] + // documentation for further information about ordinals and total ordering. uint64 ordinal = 4; } + +// HeaderOnlyBlock is used to optimally unpack the [Block] structure (note the +// corresponding message number for the `header` field) while consuming less +// memory, when only the `header` is desired. +// +// WARN: this is a client-side optimization pattern and should be moved in the +// consuming code. +message HeaderOnlyBlock { + BlockHeader header = 5; +} + +// BlockWithRefs is a lightweight block, with traces and transactions +// purged from the `block` within, and only. It is used in transports +// to pass block data around. +message BlockWithRefs { + string id = 1; + Block block = 2; + TransactionRefs transaction_trace_refs = 3; + bool irreversible = 4; +} + +message TransactionTraceWithBlockRef { + TransactionTrace trace = 1; + BlockRef block_ref = 2; +} + +message TransactionRefs { + repeated bytes hashes = 1; +} + +message BlockRef { + bytes hash = 1; + uint64 number = 2; +} + +// Withdrawal represents a validator withdrawal from the beacon chain to the EVM. +// Introduced in EIP-4895 (Shanghai hard fork). +message Withdrawal { + // Index is the monotonically increasing identifier of the withdrawal + uint64 index = 1; + + // ValidatorIndex is the index of the validator that is withdrawing + uint64 validator_index = 2; + + // Address is the Ethereum address receiving the withdrawn funds + bytes address = 3; + + // Amount is the value of the withdrawal in gwei (1 gwei = 1e9 wei) + uint64 amount = 4; +} \ No newline at end of file diff --git a/chain/ethereum/src/adapter.rs b/chain/ethereum/src/adapter.rs index efadb95c089..b9dca9c434c 100644 --- a/chain/ethereum/src/adapter.rs +++ b/chain/ethereum/src/adapter.rs @@ -1,16 +1,17 @@ use anyhow::Error; use async_trait::async_trait; -use ethabi::{Error as ABIError, ParamType, Token}; +use graph::abi; use graph::blockchain::ChainIdentifier; +use graph::components::ethereum::AnyBlock; use graph::components::subgraph::MappingError; use graph::data::store::ethereum::call; use graph::data_source::common::ContractCall; use graph::firehose::CallToFilter; use graph::firehose::CombinedFilter; use graph::firehose::LogFilter; -use graph::prelude::web3::types::Bytes; -use graph::prelude::web3::types::H160; -use graph::prelude::web3::types::U256; +use graph::prelude::alloy::primitives::{Address, B256}; +use graph::prelude::alloy::rpc::types::Log; +use graph::prelude::alloy::transports::{RpcError, TransportErrorKind}; use itertools::Itertools; use prost::Message; use prost_types::Any; @@ -19,7 +20,6 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use thiserror::Error; use tiny_keccak::keccak256; -use web3::types::{Address, Log, H256}; use graph::prelude::*; use graph::{ @@ -28,6 +28,8 @@ use graph::{ petgraph::{self, graphmap::GraphMap}, }; +use graph::blockchain::BlockPtr; + const COMBINED_FILTER_TYPE_URL: &str = "type.googleapis.com/sf.ethereum.transform.v1.CombinedFilter"; @@ -35,7 +37,7 @@ use crate::capabilities::NodeCapabilities; use crate::data_source::{BlockHandlerFilter, DataSource}; use crate::{Chain, Mapping, ENV_VARS}; -pub type EventSignature = H256; +pub type EventSignature = B256; pub type FunctionSelector = [u8; 4]; /// `EventSignatureWithTopics` is used to match events with @@ -44,19 +46,19 @@ pub type FunctionSelector = [u8; 4]; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct EventSignatureWithTopics { pub address: Option
, - pub signature: H256, - pub topic1: Option>, - pub topic2: Option>, - pub topic3: Option>, + pub signature: B256, + pub topic1: Option>, + pub topic2: Option>, + pub topic3: Option>, } impl EventSignatureWithTopics { pub fn new( address: Option
, - signature: H256, - topic1: Option>, - topic2: Option>, - topic3: Option>, + signature: B256, + topic1: Option>, + topic2: Option>, + topic3: Option>, ) -> Self { EventSignatureWithTopics { address, @@ -71,7 +73,7 @@ impl EventSignatureWithTopics { /// If self.address is None, it's considered a wildcard match. /// Otherwise, it must match the provided address. /// It must also match the topics if they are Some - pub fn matches(&self, address: Option<&H160>, sig: H256, topics: &Vec) -> bool { + pub fn matches(&self, address: Option<&Address>, sig: B256, topics: &[B256]) -> bool { // If self.address is None, it's considered a wildcard match. Otherwise, it must match the provided address. let address_matches = match self.address { Some(ref self_addr) => address == Some(self_addr), @@ -95,22 +97,21 @@ impl EventSignatureWithTopics { #[derive(Error, Debug)] pub enum EthereumRpcError { #[error("call error: {0}")] - Web3Error(web3::Error), + AlloyError(RpcError), #[error("ethereum node took too long to perform call")] Timeout, } #[derive(Error, Debug)] pub enum ContractCallError { - #[error("ABI error: {0}")] - ABIError(#[from] ABIError), - /// `Token` is not of expected `ParamType` - #[error("type mismatch, token {0:?} is not of kind {1:?}")] - TypeError(Token, ParamType), - #[error("error encoding input call data: {0}")] - EncodingError(ethabi::Error), + #[error("ABI error: {0:#}")] + ABIError(anyhow::Error), + #[error("type mismatch, decoded value {0:?} is not of kind {1:?}")] + TypeError(abi::DynSolValue, abi::DynSolType), + #[error("error encoding input call data: {0:#}")] + EncodingError(anyhow::Error), #[error("call error: {0}")] - Web3Error(web3::Error), + AlloyError(RpcError), #[error("ethereum node took too long to perform call")] Timeout, #[error("internal error: {0}")] @@ -123,7 +124,7 @@ impl From for MappingError { // Any error reported by the Ethereum node could be due to the block no longer being on // the main chain. This is very unespecific but we don't want to risk failing a // subgraph due to a transient error such as a reorg. - ContractCallError::Web3Error(e) => MappingError::PossibleReorg(anyhow::anyhow!( + ContractCallError::AlloyError(e) => MappingError::PossibleReorg(anyhow::anyhow!( "Ethereum node returned an error for an eth_call: {e}" )), // Also retry on timeouts. @@ -145,13 +146,34 @@ enum LogFilterNode { #[derive(Clone, Debug)] pub struct EthGetLogsFilter { pub contracts: Vec
, - pub event_signatures: Vec, - pub topic1: Option>, - pub topic2: Option>, - pub topic3: Option>, + pub event_signatures: Vec, + pub topic1: Option>, + pub topic2: Option>, + pub topic3: Option>, } impl EthGetLogsFilter { + /// Convert to alloy Filter for the given block range + pub fn to_alloy_filter(&self, from: BlockNumber, to: BlockNumber) -> alloy::rpc::types::Filter { + let mut filter_builder = alloy::rpc::types::Filter::new() + .from_block(alloy::rpc::types::BlockNumberOrTag::Number(from as u64)) + .to_block(alloy::rpc::types::BlockNumberOrTag::Number(to as u64)) + .address(self.contracts.clone()) + .event_signature(self.event_signatures.clone()); + + if let Some(ref topic1) = self.topic1 { + filter_builder = filter_builder.topic1(topic1.clone()); + } + if let Some(ref topic2) = self.topic2 { + filter_builder = filter_builder.topic2(topic2.clone()); + } + if let Some(ref topic3) = self.topic3 { + filter_builder = filter_builder.topic3(topic3.clone()); + } + + filter_builder + } + fn from_contract(address: Address) -> Self { EthGetLogsFilter { contracts: vec![address], @@ -162,7 +184,7 @@ impl EthGetLogsFilter { } } - fn from_event(event: EventSignature) -> Self { + fn from_event(event: B256) -> Self { EthGetLogsFilter { contracts: vec![], event_signatures: vec![event], @@ -202,7 +224,7 @@ impl fmt::Display for EthGetLogsFilter { }; // Helper to format topics as strings - let format_topics = |topics: &Option>| -> String { + let format_topics = |topics: &Option>| -> String { topics.as_ref().map_or_else( || "None".to_string(), |ts| { @@ -348,11 +370,11 @@ impl From for Vec { }| LogFilter { addresses: contracts .iter() - .map(|addr| addr.to_fixed_bytes().to_vec()) + .map(|addr| addr.to_vec()) .collect_vec(), event_signatures: event_signatures .iter() - .map(|sig| sig.to_fixed_bytes().to_vec()) + .map(|sig| sig.to_vec()) .collect_vec(), }, ) @@ -364,14 +386,14 @@ impl EthereumLogFilter { /// Check if this filter matches the specified `Log`. pub fn matches(&self, log: &Log) -> bool { // First topic should be event sig - match log.topics.first() { + match log.topics().first() { None => false, Some(sig) => { // The `Log` matches the filter either if the filter contains // a (contract address, event signature) pair that matches the // `Log`, or if the filter contains wildcard event that matches. - let contract = LogFilterNode::Contract(log.address); + let contract = LogFilterNode::Contract(log.address()); let event = LogFilterNode::Event(*sig); self.contracts_and_events_graph .all_edges() @@ -380,7 +402,7 @@ impl EthereumLogFilter { || self .events_with_topic_filters .iter() - .any(|(e, _)| e.matches(Some(&log.address), *sig, &log.topics)) + .any(|(e, _)| e.matches(Some(&log.address()), *sig, &log.topics())) } } } @@ -388,9 +410,9 @@ impl EthereumLogFilter { /// Similar to [`matches`], checks if a transaction receipt is required for this log filter. pub fn requires_transaction_receipt( &self, - event_signature: &H256, + event_signature: &B256, contract_address: Option<&Address>, - topics: &Vec, + topics: &[B256], ) -> bool { // Check for wildcard events first. if self.wildcard_events.get(event_signature) == Some(&true) { @@ -628,7 +650,7 @@ impl Into> for EthereumCallFilter { let mut filters: Vec = contract_addresses_function_signatures .into_iter() .map(|(addr, (_, sigs))| CallToFilter { - addresses: vec![addr.to_fixed_bytes().to_vec()], + addresses: vec![addr.to_vec()], signatures: sigs.into_iter().map(|x| x.to_vec()).collect_vec(), }) .collect(); @@ -818,7 +840,7 @@ impl Into> for EthereumBlockFilter { .sorted() .dedup_by(|x, y| x == y) .map(|addr| CallToFilter { - addresses: vec![addr.to_fixed_bytes().to_vec()], + addresses: vec![addr.to_vec()], signatures: vec![], }) .collect_vec() @@ -1080,20 +1102,8 @@ pub trait EthereumAdapter: Send + Sync + 'static { /// connected to. async fn net_identifiers(&self) -> Result; - /// Get the latest block, including full transactions. - async fn latest_block(&self, logger: &Logger) -> Result; - /// Get the latest block, with only the header and transaction hashes. - async fn latest_block_header( - &self, - logger: &Logger, - ) -> Result, bc::IngestorError>; - - async fn load_block( - &self, - logger: &Logger, - block_hash: H256, - ) -> Result; + async fn latest_block_ptr(&self, logger: &Logger) -> Result; /// Load Ethereum blocks in bulk, returning results as they come back as a Stream. /// May use the `chain_store` as a cache. @@ -1101,44 +1111,29 @@ pub trait EthereumAdapter: Send + Sync + 'static { &self, logger: Logger, chain_store: Arc, - block_hashes: HashSet, + block_hashes: HashSet, ) -> Result>, Error>; /// Find a block by its hash. async fn block_by_hash( &self, logger: &Logger, - block_hash: H256, - ) -> Result, Error>; + block_hash: B256, + ) -> Result, Error>; async fn block_by_number( &self, logger: &Logger, block_number: BlockNumber, - ) -> Result, Error>; + ) -> Result, Error>; /// Load full information for the specified `block` (in particular, transaction receipts). async fn load_full_block( &self, logger: &Logger, - block: LightEthereumBlock, + block: AnyBlock, ) -> Result; - /// Find a block by its number, according to the Ethereum node. - /// - /// Careful: don't use this function without considering race conditions. - /// Chain reorgs could happen at any time, and could affect the answer received. - /// Generally, it is only safe to use this function with blocks that have received enough - /// confirmations to guarantee no further reorgs, **and** where the Ethereum node is aware of - /// those confirmations. - /// If the Ethereum node is far behind in processing blocks, even old blocks can be subject to - /// reorgs. - async fn block_hash_by_block_number( - &self, - logger: &Logger, - block_number: BlockNumber, - ) -> Result, Error>; - /// Finds the hash and number of the lowest non-null block with height greater than or equal to /// the given number. /// @@ -1158,7 +1153,7 @@ pub trait EthereumAdapter: Send + Sync + 'static { logger: &Logger, call: &ContractCall, cache: Arc, - ) -> Result<(Option>, call::Source), ContractCallError>; + ) -> Result<(Option>, call::Source), ContractCallError>; /// Make multiple contract calls in a single batch. The returned `Vec` /// has results in the same order as the calls in `calls` on input. The @@ -1168,22 +1163,22 @@ pub trait EthereumAdapter: Send + Sync + 'static { logger: &Logger, calls: &[&ContractCall], cache: Arc, - ) -> Result>, call::Source)>, ContractCallError>; + ) -> Result>, call::Source)>, ContractCallError>; async fn get_balance( &self, logger: &Logger, - address: H160, + address: Address, block_ptr: BlockPtr, - ) -> Result; + ) -> Result; // Returns the compiled bytecode of a smart contract async fn get_code( &self, logger: &Logger, - address: H160, + address: Address, block_ptr: BlockPtr, - ) -> Result; + ) -> Result; } #[cfg(test)] @@ -1197,9 +1192,7 @@ mod tests { use graph::blockchain::TriggerFilter as _; use graph::firehose::{CallToFilter, CombinedFilter, LogFilter, MultiLogFilter}; use graph::petgraph::graphmap::GraphMap; - use graph::prelude::ethabi::ethereum_types::H256; - use graph::prelude::web3::types::Address; - use graph::prelude::web3::types::Bytes; + use graph::prelude::alloy::primitives::{Address, Bytes, B256, U256}; use graph::prelude::EthereumCall; use hex::ToHex; use itertools::Itertools; @@ -1226,7 +1219,7 @@ mod tests { .map(|addr| { format!( "0x{}", - H256::from_str(addr) + B256::from_str(addr) .expect("unable to parse addr") .encode_hex::() ) @@ -1237,16 +1230,11 @@ mod tests { let sigs = event_sigs .iter() - .map(|addr| { - H256::from_str(addr) - .expect("unable to parse addr") - .to_fixed_bytes() - .to_vec() - }) + .map(|addr| B256::from_str(addr).expect("unable to parse addr").to_vec()) .collect_vec(); let filter = LogFilter { - addresses: vec![address.to_fixed_bytes().to_vec()], + addresses: vec![address.to_vec()], event_signatures: sigs, }; // This base64 was provided by Streamingfast as a binding example of the expected encoded for the @@ -1275,7 +1263,6 @@ mod tests { let filter = LogFilter { addresses: vec![Address::from_str(hex_addr) .expect("failed to parse address") - .to_fixed_bytes() .to_vec()], event_signatures: vec![fs.to_vec()], }; @@ -1290,8 +1277,7 @@ mod tests { #[test] fn ethereum_trigger_filter_to_firehose() { - let address = Address::from_low_u64_be; - let sig = H256::from_low_u64_le; + let sig = |value: u64| B256::from(U256::from(value)); let mut filter = TriggerFilter { log: EthereumLogFilter { contracts_and_events_graph: GraphMap::new(), @@ -1321,27 +1307,27 @@ mod tests { let expected_call_filters = vec![ CallToFilter { - addresses: vec![address(0).to_fixed_bytes().to_vec()], + addresses: vec![address(0).to_vec()], signatures: vec![[0u8; 4].to_vec()], }, CallToFilter { - addresses: vec![address(1).to_fixed_bytes().to_vec()], + addresses: vec![address(1).to_vec()], signatures: vec![[1u8; 4].to_vec()], }, CallToFilter { - addresses: vec![address(2).to_fixed_bytes().to_vec()], + addresses: vec![address(2).to_vec()], signatures: vec![], }, CallToFilter { - addresses: vec![address(1000).to_fixed_bytes().to_vec()], + addresses: vec![address(1000).to_vec()], signatures: vec![], }, CallToFilter { - addresses: vec![address(2000).to_fixed_bytes().to_vec()], + addresses: vec![address(2000).to_vec()], signatures: vec![], }, CallToFilter { - addresses: vec![address(3000).to_fixed_bytes().to_vec()], + addresses: vec![address(3000).to_vec()], signatures: vec![], }, ]; @@ -1364,15 +1350,12 @@ mod tests { let expected_log_filters = vec![ LogFilter { - addresses: vec![address(10).to_fixed_bytes().to_vec()], - event_signatures: vec![sig(101).to_fixed_bytes().to_vec()], + addresses: vec![address(10).to_vec()], + event_signatures: vec![sig(101).to_vec()], }, LogFilter { - addresses: vec![ - address(10).to_fixed_bytes().to_vec(), - address(20).to_fixed_bytes().to_vec(), - ], - event_signatures: vec![sig(100).to_fixed_bytes().to_vec()], + addresses: vec![address(10).to_vec(), address(20).to_vec()], + event_signatures: vec![sig(100).to_vec()], }, ]; @@ -1416,8 +1399,8 @@ mod tests { #[test] fn ethereum_trigger_filter_to_firehose_every_block_plus_logfilter() { - let address = Address::from_low_u64_be; - let sig = H256::from_low_u64_le; + let address = |value: u64| Address::left_padding_from(&value.to_le_bytes()); + let sig = |value: u64| B256::left_padding_from(&value.to_le_bytes()); let mut filter = TriggerFilter { log: EthereumLogFilter { contracts_and_events_graph: GraphMap::new(), @@ -1442,8 +1425,8 @@ mod tests { ); let expected_log_filters = vec![LogFilter { - addresses: vec![address(10).to_fixed_bytes().to_vec()], - event_signatures: vec![sig(101).to_fixed_bytes().to_vec()], + addresses: vec![address(10).to_vec()], + event_signatures: vec![sig(101).to_vec()], }]; let firehose_filter = filter.clone().to_firehose_filter(); @@ -1733,51 +1716,36 @@ mod tests { fn extending_ethereum_call_filter() { let mut base = EthereumCallFilter { contract_addresses_function_signatures: HashMap::from_iter(vec![ - ( - Address::from_low_u64_be(0), - (0, HashSet::from_iter(vec![[0u8; 4]])), - ), - ( - Address::from_low_u64_be(1), - (1, HashSet::from_iter(vec![[1u8; 4]])), - ), + (address(0), (0, HashSet::from_iter(vec![[0u8; 4]]))), + (address(1), (1, HashSet::from_iter(vec![[1u8; 4]]))), ]), wildcard_signatures: HashSet::new(), }; let extension = EthereumCallFilter { contract_addresses_function_signatures: HashMap::from_iter(vec![ - ( - Address::from_low_u64_be(0), - (2, HashSet::from_iter(vec![[2u8; 4]])), - ), - ( - Address::from_low_u64_be(3), - (3, HashSet::from_iter(vec![[3u8; 4]])), - ), + (address(0), (2, HashSet::from_iter(vec![[2u8; 4]]))), + (address(3), (3, HashSet::from_iter(vec![[3u8; 4]]))), ]), wildcard_signatures: HashSet::new(), }; base.extend(extension); assert_eq!( - base.contract_addresses_function_signatures - .get(&Address::from_low_u64_be(0)), + base.contract_addresses_function_signatures.get(&address(0)), Some(&(0, HashSet::from_iter(vec![[0u8; 4], [2u8; 4]]))) ); assert_eq!( - base.contract_addresses_function_signatures - .get(&Address::from_low_u64_be(3)), + base.contract_addresses_function_signatures.get(&address(3)), Some(&(3, HashSet::from_iter(vec![[3u8; 4]]))) ); assert_eq!( - base.contract_addresses_function_signatures - .get(&Address::from_low_u64_be(1)), + base.contract_addresses_function_signatures.get(&address(1)), Some(&(1, HashSet::from_iter(vec![[1u8; 4]]))) ); } - fn address(id: u64) -> Address { - Address::from_low_u64_be(id) + fn address(value: u64) -> Address { + Address::left_padding_from(&value.to_be_bytes()) } fn bytes(value: Vec) -> Bytes { @@ -1793,10 +1761,10 @@ fn complete_log_filter() { // Test a few combinations of complete graphs. for i in [1, 2] { - let events: BTreeSet<_> = (0..i).map(H256::from_low_u64_le).collect(); + let events: BTreeSet<_> = (0..i).map(|n| B256::from([n as u8; 32])).collect(); for j in [1, 1000, 2000, 3000] { - let contracts: BTreeSet<_> = (0..j).map(Address::from_low_u64_le).collect(); + let contracts: BTreeSet<_> = (0..j).map(|n| Address::from([n as u8; 20])).collect(); // Construct the complete bipartite graph with i events and j contracts. let mut contracts_and_events_graph = GraphMap::new(); @@ -1847,16 +1815,19 @@ fn complete_log_filter() { #[test] fn log_filter_require_transacion_receipt_method() { + let address = |value: u64| Address::left_padding_from(&value.to_be_bytes()); + let b256 = |value: u64| B256::left_padding_from(&value.to_be_bytes()); + // test data - let event_signature_a = H256::zero(); - let event_signature_b = H256::from_low_u64_be(1); - let event_signature_c = H256::from_low_u64_be(2); - let contract_a = Address::from_low_u64_be(3); - let contract_b = Address::from_low_u64_be(4); - let contract_c = Address::from_low_u64_be(5); - - let wildcard_event_with_receipt = H256::from_low_u64_be(6); - let wildcard_event_without_receipt = H256::from_low_u64_be(7); + let event_signature_a = b256(0); + let event_signature_b = b256(1); + let event_signature_c = b256(2); + let contract_a = address(3); + let contract_b = address(4); + let contract_c = address(5); + + let wildcard_event_with_receipt = b256(6); + let wildcard_event_without_receipt = b256(7); let wildcard_events = [ (wildcard_event_with_receipt, true), (wildcard_event_without_receipt, false), @@ -1866,8 +1837,8 @@ fn log_filter_require_transacion_receipt_method() { let events_with_topic_filters = HashMap::new(); // TODO(krishna): Test events with topic filters - let alien_event_signature = H256::from_low_u64_be(8); // those will not be inserted in the graph - let alien_contract_address = Address::from_low_u64_be(9); + let alien_event_signature = b256(8); // those will not be inserted in the graph + let alien_contract_address = address(9); // test graph nodes let event_a_node = LogFilterNode::Event(event_signature_a); @@ -1912,7 +1883,7 @@ fn log_filter_require_transacion_receipt_method() { events_with_topic_filters, }; - let empty_vec: Vec = vec![]; + let empty_vec: Vec = vec![]; // connected contracts and events graph assert!(filter.requires_transaction_receipt(&event_signature_a, Some(&contract_a), &empty_vec)); diff --git a/chain/ethereum/src/call_helper.rs b/chain/ethereum/src/call_helper.rs new file mode 100644 index 00000000000..ca45b6fb09d --- /dev/null +++ b/chain/ethereum/src/call_helper.rs @@ -0,0 +1,138 @@ +use crate::{ContractCallError, ENV_VARS}; +use graph::{ + abi, + data::store::ethereum::call, + prelude::{ + alloy::transports::{RpcError, TransportErrorKind}, + Logger, + }, + slog::info, +}; + +// ------------------------------------------------------------------ +// Constants and helper utilities used across eth_call handling +// ------------------------------------------------------------------ + +// Try to check if the call was reverted. The JSON-RPC response for reverts is +// not standardized, so we have ad-hoc checks for each Ethereum client. + +// 0xfe is the "designated bad instruction" of the EVM, and Solidity uses it for +// asserts. +const PARITY_BAD_INSTRUCTION_FE: &str = "Bad instruction fe"; + +// 0xfd is REVERT, but on some contracts, and only on older blocks, +// this happens. Makes sense to consider it a revert as well. +const PARITY_BAD_INSTRUCTION_FD: &str = "Bad instruction fd"; + +const PARITY_BAD_JUMP_PREFIX: &str = "Bad jump"; +const PARITY_STACK_LIMIT_PREFIX: &str = "Out of stack"; + +// See f0af4ab0-6b7c-4b68-9141-5b79346a5f61. +const PARITY_OUT_OF_GAS: &str = "Out of gas"; + +// Also covers Nethermind reverts +const PARITY_VM_EXECUTION_ERROR: i64 = -32015; +const PARITY_REVERT_PREFIX: &str = "revert"; + +const XDAI_REVERT: &str = "revert"; + +// Deterministic Geth execution errors. We might need to expand this as +// subgraphs come across other errors. See +// https://github.com/ethereum/go-ethereum/blob/cd57d5cd38ef692de8fbedaa56598b4e9fbfbabc/core/vm/errors.go +const GETH_EXECUTION_ERRORS: &[&str] = &[ + // The "revert" substring covers a few known error messages, including: + // Hardhat: "error: transaction reverted", + // Ganache and Moonbeam: "vm exception while processing transaction: revert", + // Geth: "execution reverted" + // And others. + "revert", + "invalid jump destination", + "invalid opcode", + // Ethereum says 1024 is the stack sizes limit, so this is deterministic. + "stack limit reached 1024", + // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61 for why the gas limit is considered deterministic. + "out of gas", + "stack underflow", +]; + +/// Helper that checks if a geth style RPC error message corresponds to a revert. +fn is_geth_revert_message(message: &str) -> bool { + let env_geth_call_errors = ENV_VARS.geth_eth_call_errors.iter(); + let mut execution_errors = GETH_EXECUTION_ERRORS + .iter() + .copied() + .chain(env_geth_call_errors.map(|s| s.as_str())); + execution_errors.any(|e| message.to_lowercase().contains(e)) +} + +/// Decode a Solidity revert(reason) payload, returning the reason string when possible. +fn as_solidity_revert_reason(bytes: &[u8]) -> Option { + let selector = &tiny_keccak::keccak256(b"Error(string)")[..4]; + if bytes.len() >= 4 && &bytes[..4] == selector { + abi::DynSolType::String + .abi_decode(&bytes[4..]) + .ok() + .and_then(|val| val.clone().as_str().map(ToOwned::to_owned)) + } else { + None + } +} + +/// Interpret the error returned by `eth_call`, distinguishing genuine failures from +/// EVM reverts. Returns `Ok(Null)` for reverts or a proper error otherwise. +pub fn interpret_eth_call_error( + logger: &Logger, + err: RpcError, +) -> Result { + fn reverted(logger: &Logger, reason: &str) -> Result { + info!(logger, "Contract call reverted"; "reason" => reason); + Ok(call::Retval::Null) + } + + if let RpcError::ErrorResp(rpc_error) = &err { + if is_geth_revert_message(&rpc_error.message) { + return reverted(logger, &rpc_error.message); + } + } + + if let RpcError::ErrorResp(rpc_error) = &err { + let code = rpc_error.code; + let data = rpc_error.data.as_ref().map(|d| d.to_string()); + + if code == PARITY_VM_EXECUTION_ERROR { + if let Some(data) = data { + if is_parity_revert(&data) { + return reverted(logger, &parity_revert_reason(&data)); + } + } + } + } + + Err(ContractCallError::AlloyError(err)) +} + +fn is_parity_revert(data: &str) -> bool { + data.to_lowercase().starts_with(PARITY_REVERT_PREFIX) + || data.starts_with(PARITY_BAD_JUMP_PREFIX) + || data.starts_with(PARITY_STACK_LIMIT_PREFIX) + || data == PARITY_BAD_INSTRUCTION_FE + || data == PARITY_BAD_INSTRUCTION_FD + || data == PARITY_OUT_OF_GAS + || data == XDAI_REVERT +} + +/// Checks if the given data corresponds to a Parity / Nethermind style EVM +/// revert and, if so, tries to extract a human-readable revert reason. Returns `Some` +/// with the reason when the error is identified as a revert, otherwise `None`. +fn parity_revert_reason(data: &str) -> String { + if data == PARITY_BAD_INSTRUCTION_FE { + return PARITY_BAD_INSTRUCTION_FE.to_owned(); + } + + // Otherwise try to decode a Solidity revert reason payload. + let payload = data.trim_start_matches(PARITY_REVERT_PREFIX); + hex::decode(payload) + .ok() + .and_then(|decoded| as_solidity_revert_reason(&decoded)) + .unwrap_or_else(|| "no reason".to_owned()) +} diff --git a/chain/ethereum/src/chain.rs b/chain/ethereum/src/chain.rs index 11ca025e0e2..3f827220b93 100644 --- a/chain/ethereum/src/chain.rs +++ b/chain/ethereum/src/chain.rs @@ -660,16 +660,6 @@ impl BlockFinality { } } -impl<'a> From<&'a BlockFinality> for BlockPtr { - fn from(block: &'a BlockFinality) -> BlockPtr { - match block { - BlockFinality::Final(b) => BlockPtr::from(&**b), - BlockFinality::NonFinal(b) => BlockPtr::from(&b.ethereum_block), - BlockFinality::Ptr(b) => BlockPtr::new(b.hash.clone(), b.number), - } - } -} - impl Block for BlockFinality { fn ptr(&self) -> BlockPtr { match self { @@ -725,11 +715,11 @@ impl Block for BlockFinality { fn timestamp(&self) -> BlockTime { match self { BlockFinality::Final(block) => { - let ts = i64::try_from(block.timestamp.as_u64()).unwrap(); + let ts = i64::try_from(block.timestamp_u64()).unwrap(); BlockTime::since_epoch(ts, 0) } BlockFinality::NonFinal(block) => { - let ts = i64::try_from(block.ethereum_block.block.timestamp.as_u64()).unwrap(); + let ts = i64::try_from(block.ethereum_block.block.timestamp_u64()).unwrap(); BlockTime::since_epoch(ts, 0) } BlockFinality::Ptr(block) => block.timestamp, @@ -1111,7 +1101,7 @@ impl TriggersAdapterTrait for TriggersAdapter { .load_blocks( self.logger.cheap_clone(), self.chain_store.cheap_clone(), - HashSet::from_iter(Some(block.hash_as_h256())), + HashSet::from_iter(Some(block.hash.as_b256())), ) .await?; assert_eq!(blocks.len(), 1); diff --git a/chain/ethereum/src/codec.rs b/chain/ethereum/src/codec.rs index 114982607ec..2a402d4f5b2 100644 --- a/chain/ethereum/src/codec.rs +++ b/chain/ethereum/src/codec.rs @@ -7,9 +7,16 @@ use graph::{ blockchain::{ self, Block as BlockchainBlock, BlockPtr, BlockTime, ChainStoreBlock, ChainStoreData, }, + components::ethereum::{AnyBlock, AnyHeader, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope}, prelude::{ - web3, - web3::types::{Bytes, H160, H2048, H256, H64, U256, U64}, + alloy::{ + self, + consensus::{ReceiptWithBloom, TxEnvelope, TxType}, + network::AnyReceiptEnvelope, + primitives::{aliases::B2048, Address, Bloom, Bytes, LogData, B256, U256}, + rpc::types::{self as alloy_rpc_types, AccessList, AccessListItem, Transaction}, + serde::WithOtherFields, + }, BlockNumber, Error, EthereumBlock, EthereumBlockWithCalls, EthereumCall, LightEthereumBlock, }, @@ -34,13 +41,13 @@ where } } -impl TryDecodeProto<[u8; 256], H2048> for &[u8] {} -impl TryDecodeProto<[u8; 32], H256> for &[u8] {} -impl TryDecodeProto<[u8; 20], H160> for &[u8] {} +impl TryDecodeProto<[u8; 32], B256> for &[u8] {} +impl TryDecodeProto<[u8; 256], B2048> for &[u8] {} +impl TryDecodeProto<[u8; 20], Address> for &[u8] {} -impl From<&BigInt> for web3::types::U256 { +impl From<&BigInt> for U256 { fn from(val: &BigInt) -> Self { - web3::types::U256::from_big_endian(&val.bytes) + U256::from_be_slice(&val.bytes) } } @@ -68,9 +75,9 @@ impl<'a> TryInto for CallAt<'a> { .value .as_ref() .map_or_else(|| U256::from(0), |v| v.into()), - gas_used: U256::from(self.call.gas_consumed), - input: Bytes(self.call.input.clone()), - output: Bytes(self.call.return_data.clone()), + gas_used: self.call.gas_consumed, + input: Bytes::from(self.call.input.clone()), + output: Bytes::from(self.call.return_data.clone()), block_hash: self.block.hash.try_decode_proto("call block hash")?, block_number: self.block.number as i32, transaction_hash: Some(self.trace.hash.try_decode_proto("call transaction hash")?), @@ -79,41 +86,6 @@ impl<'a> TryInto for CallAt<'a> { } } -impl TryInto for Call { - type Error = Error; - - fn try_into(self) -> Result { - Ok(web3::types::Call { - from: self.caller.try_decode_proto("call from address")?, - to: self.address.try_decode_proto("call to address")?, - value: self - .value - .as_ref() - .map_or_else(|| U256::from(0), |v| v.into()), - gas: U256::from(self.gas_limit), - input: Bytes::from(self.input.clone()), - call_type: CallType::try_from(self.call_type) - .map_err(|_| graph::anyhow::anyhow!("invalid call type: {}", self.call_type))? - .into(), - }) - } -} - -impl From for web3::types::CallType { - fn from(val: CallType) -> Self { - match val { - CallType::Unspecified => web3::types::CallType::None, - CallType::Call => web3::types::CallType::Call, - CallType::Callcode => web3::types::CallType::CallCode, - CallType::Delegate => web3::types::CallType::DelegateCall, - CallType::Static => web3::types::CallType::StaticCall, - - // FIXME (SF): Really not sure what this should map to, we are using None for now, need to revisit - CallType::Create => web3::types::CallType::None, - } - } -} - pub struct LogAt<'a> { log: &'a Log, block: &'a Block, @@ -126,46 +98,39 @@ impl<'a> LogAt<'a> { } } -impl<'a> TryInto for LogAt<'a> { +impl<'a> TryInto for LogAt<'a> { type Error = Error; - fn try_into(self) -> Result { - Ok(web3::types::Log { - address: self.log.address.try_decode_proto("log address")?, - topics: self - .log - .topics - .iter() - .map(|t| t.try_decode_proto("topic")) - .collect::, Error>>()?, - data: Bytes::from(self.log.data.clone()), + fn try_into(self) -> Result { + let topics = self + .log + .topics + .iter() + .map(|t| t.try_decode_proto("topic")) + .collect::, Error>>()?; + + Ok(alloy::rpc::types::Log { + inner: alloy::primitives::Log { + address: self.log.address.try_decode_proto("log address")?, + data: LogData::new(topics, self.log.data.clone().into()) + .ok_or_else(|| format_err!("invalid log data"))?, + }, block_hash: Some(self.block.hash.try_decode_proto("log block hash")?), - block_number: Some(U64::from(self.block.number)), + block_number: Some(self.block.number), transaction_hash: Some(self.trace.hash.try_decode_proto("log transaction hash")?), - transaction_index: Some(U64::from(self.trace.index as u64)), - log_index: Some(U256::from(self.log.block_index)), - transaction_log_index: Some(U256::from(self.log.index)), - log_type: None, - removed: None, + transaction_index: Some(self.trace.index as u64), + log_index: Some(self.log.block_index as u64), + removed: false, + block_timestamp: self + .block + .header + .as_ref() + .map(|h| h.timestamp.as_ref().map(|t| t.seconds as u64)) + .flatten(), }) } } -impl TryFrom for Option { - type Error = Error; - - fn try_from(val: TransactionTraceStatus) -> Result { - match val { - TransactionTraceStatus::Unknown => Err(format_err!( - "Got a transaction trace with status UNKNOWN, datasource is broken" - )), - TransactionTraceStatus::Succeeded => Ok(Some(web3::types::U64::from(1))), - TransactionTraceStatus::Failed => Ok(Some(web3::types::U64::from(0))), - TransactionTraceStatus::Reverted => Ok(Some(web3::types::U64::from(0))), - } - } -} - pub struct TransactionTraceAt<'a> { trace: &'a TransactionTrace, block: &'a Block, @@ -177,34 +142,314 @@ impl<'a> TransactionTraceAt<'a> { } } -impl<'a> TryInto for TransactionTraceAt<'a> { +impl<'a> TryInto> for TransactionTraceAt<'a> { type Error = Error; - fn try_into(self) -> Result { - Ok(web3::types::Transaction { - hash: self.trace.hash.try_decode_proto("transaction hash")?, - nonce: U256::from(self.trace.nonce), - block_hash: Some(self.block.hash.try_decode_proto("transaction block hash")?), - block_number: Some(U64::from(self.block.number)), - transaction_index: Some(U64::from(self.trace.index as u64)), - from: Some( - self.trace - .from - .try_decode_proto("transaction from address")?, - ), - to: get_to_address(self.trace)?, - value: self.trace.value.as_ref().map_or(U256::zero(), |x| x.into()), - gas_price: self.trace.gas_price.as_ref().map(|x| x.into()), - gas: U256::from(self.trace.gas_limit), - input: Bytes::from(self.trace.input.clone()), - v: None, - r: None, - s: None, - raw: None, - access_list: None, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - transaction_type: None, + fn try_into(self) -> Result, Self::Error> { + use alloy::{ + consensus::transaction::Recovered, + consensus::{ + Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy, + }, + network::{AnyTxEnvelope, AnyTxType, UnknownTxEnvelope, UnknownTypedTransaction}, + primitives::{Bytes, TxKind, U256}, + rpc::types::Transaction as AlloyTransaction, + serde::OtherFields, + }; + use std::collections::BTreeMap; + + // Extract data from trace and block + let block_hash = self.block.hash.try_decode_proto("transaction block hash")?; + let block_number = self.block.number; + let transaction_index = Some(self.trace.index as u64); + let from_address = self + .trace + .from + .try_decode_proto("transaction from address")?; + let to = get_to_address(self.trace)?; + let value = self.trace.value.as_ref().map_or(U256::ZERO, |x| x.into()); + let gas_price = self.trace.gas_price.as_ref().map_or(0u128, |x| { + let val: U256 = x.into(); + val.to::() + }); + let gas_limit = self.trace.gas_limit; + let input = Bytes::from(self.trace.input.clone()); + + let tx_type_u64 = u64::try_from(self.trace.r#type).map_err(|_| { + format_err!( + "Invalid transaction type value {} in transaction trace. Transaction type must be a valid u64.", + self.trace.r#type + ) + })?; + + // Try to convert to known Ethereum transaction type + let tx_type_result = TxType::try_from(tx_type_u64); + + // If this is an unknown transaction type, create an UnknownTxEnvelope + if tx_type_result.is_err() { + let mut fields_map = BTreeMap::new(); + + fields_map.insert( + "nonce".to_string(), + jsonrpc_core::serde_json::json!(format!("0x{:x}", self.trace.nonce)), + ); + fields_map.insert( + "from".to_string(), + jsonrpc_core::serde_json::json!(format!("{:?}", from_address)), + ); + if let Some(to_addr) = to { + fields_map.insert( + "to".to_string(), + jsonrpc_core::serde_json::json!(format!("{:?}", to_addr)), + ); + } + fields_map.insert( + "value".to_string(), + jsonrpc_core::serde_json::json!(format!("0x{:x}", value)), + ); + fields_map.insert( + "gas".to_string(), + jsonrpc_core::serde_json::json!(format!("0x{:x}", gas_limit)), + ); + fields_map.insert( + "gasPrice".to_string(), + jsonrpc_core::serde_json::json!(format!("0x{:x}", gas_price)), + ); + fields_map.insert( + "input".to_string(), + jsonrpc_core::serde_json::json!(format!("0x{}", hex::encode(&input))), + ); + + let fields = OtherFields::new(fields_map); + let unknown_tx = UnknownTypedTransaction { + ty: AnyTxType(tx_type_u64 as u8), + fields, + memo: Default::default(), + }; + + let tx_hash = self.trace.hash.try_decode_proto("transaction hash")?; + let unknown_envelope = UnknownTxEnvelope { + hash: tx_hash, + inner: unknown_tx, + }; + + let any_envelope = AnyTxEnvelope::Unknown(unknown_envelope); + let recovered = Recovered::new_unchecked(any_envelope, from_address); + + return Ok(AlloyTransaction { + inner: recovered, + block_hash: Some(block_hash), + block_number: Some(block_number), + transaction_index, + effective_gas_price: if gas_price > 0 { Some(gas_price) } else { None }, + }); + } + + let tx_type = tx_type_result.unwrap(); + let nonce = self.trace.nonce; + + // Extract EIP-1559 fee fields from trace + let max_fee_per_gas_u128 = self.trace.max_fee_per_gas.as_ref().map_or(gas_price, |x| { + let val: U256 = x.into(); + val.to::() + }); + + let max_priority_fee_per_gas_u128 = + self.trace + .max_priority_fee_per_gas + .as_ref() + .map_or(0u128, |x| { + let val: U256 = x.into(); + val.to::() + }); + + // Extract access list from trace + let access_list: AccessList = self + .trace + .access_list + .iter() + .map(|access_tuple| { + let address = Address::from_slice(&access_tuple.address); + let storage_keys = access_tuple + .storage_keys + .iter() + .map(|key| B256::from_slice(key)) + .collect(); + AccessListItem { + address, + storage_keys, + } + }) + .collect::>() + .into(); + + // Extract actual signature components from trace + let signature = extract_signature_from_trace(self.trace, tx_type)?; + + let to_kind = match to { + Some(addr) => TxKind::Call(addr), + None => TxKind::Create, + }; + + let envelope = match tx_type { + TxType::Legacy => { + let tx = TxLegacy { + chain_id: None, + nonce, + gas_price, + gas_limit, + to: to_kind, + value, + input: input.clone(), + }; + let signed_tx = Signed::new_unchecked( + tx, + signature, + self.trace.hash.try_decode_proto("transaction hash")?, + ); + TxEnvelope::Legacy(signed_tx) + } + TxType::Eip2930 => { + let tx = TxEip2930 { + // Firehose protobuf doesn't provide chain_id for transactions. + // Using 0 as placeholder since the transaction has already been validated on-chain. + chain_id: 0, + nonce, + gas_price, + gas_limit, + to: to_kind, + value, + access_list: access_list.clone(), // Use actual access list from trace + input: input.clone(), + }; + let signed_tx = Signed::new_unchecked( + tx, + signature, + self.trace.hash.try_decode_proto("transaction hash")?, + ); + TxEnvelope::Eip2930(signed_tx) + } + TxType::Eip1559 => { + let tx = TxEip1559 { + // Firehose protobuf doesn't provide chain_id for transactions. + // Using 0 as placeholder since the transaction has already been validated on-chain. + chain_id: 0, + nonce, + gas_limit, + max_fee_per_gas: max_fee_per_gas_u128, + max_priority_fee_per_gas: max_priority_fee_per_gas_u128, + to: to_kind, + value, + access_list: access_list.clone(), // Use actual access list from trace + input: input.clone(), + }; + let signed_tx = Signed::new_unchecked( + tx, + signature, + self.trace.hash.try_decode_proto("transaction hash")?, + ); + TxEnvelope::Eip1559(signed_tx) + } + TxType::Eip4844 => { + let to_address = to.ok_or_else(|| { + format_err!("EIP-4844 transactions cannot be contract creation transactions. The 'to' field must contain a valid address.") + })?; + + let blob_versioned_hashes: Vec = self + .trace + .blob_hashes + .iter() + .map(|hash| B256::from_slice(hash)) + .collect(); + + let max_fee_per_blob_gas_u128 = + self.trace.blob_gas_fee_cap.as_ref().map_or(0u128, |x| { + let val: U256 = x.into(); + val.to::() + }); + + let tx_eip4844 = TxEip4844 { + // Firehose protobuf doesn't provide chain_id for transactions. + // Using 0 as placeholder since the transaction has already been validated on-chain. + chain_id: 0, + nonce, + gas_limit, + max_fee_per_gas: max_fee_per_gas_u128, + max_priority_fee_per_gas: max_priority_fee_per_gas_u128, + to: to_address, + value, + access_list: access_list.clone(), // Use actual access list from trace + blob_versioned_hashes, + max_fee_per_blob_gas: max_fee_per_blob_gas_u128, + input: input.clone(), + }; + let tx = TxEip4844Variant::TxEip4844(tx_eip4844); + let signed_tx = Signed::new_unchecked( + tx, + signature, + self.trace.hash.try_decode_proto("transaction hash")?, + ); + TxEnvelope::Eip4844(signed_tx) + } + TxType::Eip7702 => { + let to_address = to.ok_or_else(|| { + format_err!("EIP-7702 transactions cannot be contract creation transactions. The 'to' field must contain a valid address.") + })?; + + // Convert set_code_authorizations to alloy authorization list + let authorization_list: Vec = self + .trace + .set_code_authorizations + .iter() + .map(|auth| { + let inner = alloy::eips::eip7702::Authorization { + chain_id: U256::from_be_slice(&auth.chain_id), + address: Address::from_slice(&auth.address), + nonce: auth.nonce, + }; + + let r = U256::from_be_slice(&auth.r); + let s = U256::from_be_slice(&auth.s); + let y_parity = auth.v as u8; + + alloy::eips::eip7702::SignedAuthorization::new_unchecked( + inner, y_parity, r, s, + ) + }) + .collect(); + + let tx = TxEip7702 { + // Firehose protobuf doesn't provide chain_id for transactions. + // Using 0 as placeholder since the transaction has already been validated on-chain. + chain_id: 0, + nonce, + gas_limit, + max_fee_per_gas: max_fee_per_gas_u128, + max_priority_fee_per_gas: max_priority_fee_per_gas_u128, + to: to_address, + value, + access_list: access_list.clone(), // Use actual access list from trace + authorization_list, + input: input.clone(), + }; + let signed_tx = Signed::new_unchecked( + tx, + signature, + self.trace.hash.try_decode_proto("transaction hash")?, + ); + TxEnvelope::Eip7702(signed_tx) + } + }; + + let any_envelope = AnyTxEnvelope::Ethereum(envelope); + let recovered = Recovered::new_unchecked(any_envelope, from_address); + + Ok(AlloyTransaction { + inner: recovered, + block_hash: Some(block_hash), + block_number: Some(block_number), + transaction_index, + effective_gas_price: if gas_price > 0 { Some(gas_price) } else { None }, // gas_price already contains effective gas price per protobuf spec }) } } @@ -217,143 +462,136 @@ impl TryInto for &Block { } } +impl TryInto for &Block { + type Error = Error; + + fn try_into(self) -> Result { + let header = self.header(); + + let block_hash = self.hash.try_decode_proto("block hash")?; + let consensus_header = alloy::consensus::Header { + number: header.number, + beneficiary: header.coinbase.try_decode_proto("author / coinbase")?, + parent_hash: header.parent_hash.try_decode_proto("parent hash")?, + ommers_hash: header.uncle_hash.try_decode_proto("uncle hash")?, + state_root: header.state_root.try_decode_proto("state root")?, + transactions_root: header + .transactions_root + .try_decode_proto("transactions root")?, + receipts_root: header.receipt_root.try_decode_proto("receipt root")?, + gas_used: header.gas_used, + gas_limit: header.gas_limit, + base_fee_per_gas: header.base_fee_per_gas.as_ref().map(|v| { + let val: U256 = v.into(); + val.to::() + }), + extra_data: Bytes::from(header.extra_data.clone()), + logs_bloom: if header.logs_bloom.is_empty() { + Bloom::ZERO + } else { + Bloom::try_from(header.logs_bloom.as_slice())? + }, + timestamp: header.timestamp.as_ref().map_or(0, |v| v.seconds as u64), + difficulty: header + .difficulty + .as_ref() + .map_or_else(|| U256::ZERO, |v| v.into()), + + mix_hash: header.mix_hash.try_decode_proto("mix hash")?, + nonce: header.nonce.into(), + + withdrawals_root: if header.withdrawals_root.is_empty() { + None + } else { + Some( + header + .withdrawals_root + .try_decode_proto("withdrawals root")?, + ) + }, + blob_gas_used: header.blob_gas_used, + excess_blob_gas: header.excess_blob_gas, + parent_beacon_block_root: if header.parent_beacon_root.is_empty() { + None + } else { + Some( + header + .parent_beacon_root + .try_decode_proto("parent beacon root")?, + ) + }, + requests_hash: if header.requests_hash.is_empty() { + None + } else { + Some(header.requests_hash.try_decode_proto("requests hash")?) + }, + }; + + let rpc_header = alloy::rpc::types::Header { + hash: block_hash, + inner: consensus_header, + total_difficulty: { + #[allow(deprecated)] + let total_difficulty = &header.total_difficulty; + total_difficulty.as_ref().map(|v| v.into()) + }, + size: Some(U256::from(self.size)), + }; + + let transactions = self + .transaction_traces + .iter() + .map(|t| TransactionTraceAt::new(t, self).try_into()) + .collect::>, Error>>()?; + + let uncles = self + .uncles + .iter() + .map(|u| u.hash.try_decode_proto("uncle hash")) + .collect::, _>>()?; + + use alloy::rpc::types::Block; + + let any_header: AnyRpcHeader = rpc_header.map(AnyHeader::from); + + let any_transactions: Vec = transactions + .into_iter() + .map(|tx| AnyRpcTransaction::new(WithOtherFields::new(tx))) + .collect(); + + let any_block = Block { + header: any_header, + transactions: alloy::rpc::types::BlockTransactions::Full(any_transactions), + uncles, + withdrawals: None, + }; + + Ok(AnyBlock::new(WithOtherFields::new(any_block))) + } +} + impl TryInto for &Block { type Error = Error; fn try_into(self) -> Result { - let header = self.header.as_ref().ok_or_else(|| { - format_err!("block header should always be present from gRPC Firehose") - })?; - + let alloy_block: AnyBlock = self.try_into()?; + + let transaction_receipts = self + .transaction_traces + .iter() + .filter_map(|t| transaction_trace_to_alloy_txn_reciept(t, self).transpose()) + .collect::, Error>>()? + .into_iter() + // Transaction receipts will be shared along the code, so we put them into an + // Arc here to avoid excessive cloning. + .map(Arc::new) + .collect(); + + #[allow(unreachable_code)] let block = EthereumBlockWithCalls { ethereum_block: EthereumBlock { - block: Arc::new(LightEthereumBlock { - hash: Some(self.hash.try_decode_proto("block hash")?), - number: Some(U64::from(self.number)), - author: header.coinbase.try_decode_proto("author / coinbase")?, - parent_hash: header.parent_hash.try_decode_proto("parent hash")?, - uncles_hash: header.uncle_hash.try_decode_proto("uncle hash")?, - state_root: header.state_root.try_decode_proto("state root")?, - transactions_root: header - .transactions_root - .try_decode_proto("transactions root")?, - receipts_root: header.receipt_root.try_decode_proto("receipt root")?, - gas_used: U256::from(header.gas_used), - gas_limit: U256::from(header.gas_limit), - base_fee_per_gas: Some( - header - .base_fee_per_gas - .as_ref() - .map_or_else(U256::default, |v| v.into()), - ), - extra_data: Bytes::from(header.extra_data.clone()), - logs_bloom: match &header.logs_bloom.len() { - 0 => None, - _ => Some(header.logs_bloom.try_decode_proto("logs bloom")?), - }, - timestamp: header - .timestamp - .as_ref() - .map_or_else(U256::default, |v| U256::from(v.seconds)), - difficulty: header - .difficulty - .as_ref() - .map_or_else(U256::default, |v| v.into()), - total_difficulty: Some( - header - .total_difficulty - .as_ref() - .map_or_else(U256::default, |v| v.into()), - ), - // FIXME (SF): Firehose does not have seal fields, are they really used? Might be required for POA chains only also, I've seen that stuff on xDai (is this important?) - seal_fields: vec![], - uncles: self - .uncles - .iter() - .map(|u| u.hash.try_decode_proto("uncle hash")) - .collect::, _>>()?, - transactions: self - .transaction_traces - .iter() - .map(|t| TransactionTraceAt::new(t, self).try_into()) - .collect::, Error>>()?, - size: Some(U256::from(self.size)), - mix_hash: Some(header.mix_hash.try_decode_proto("mix hash")?), - nonce: Some(H64::from_low_u64_be(header.nonce)), - }), - transaction_receipts: self - .transaction_traces - .iter() - .filter_map(|t| { - t.receipt.as_ref().map(|r| { - Ok(web3::types::TransactionReceipt { - transaction_hash: t.hash.try_decode_proto("transaction hash")?, - transaction_index: U64::from(t.index), - block_hash: Some( - self.hash.try_decode_proto("transaction block hash")?, - ), - block_number: Some(U64::from(self.number)), - cumulative_gas_used: U256::from(r.cumulative_gas_used), - // FIXME (SF): What is the rule here about gas_used being None, when it's 0? - gas_used: Some(U256::from(t.gas_used)), - contract_address: { - match t.calls.len() { - 0 => None, - _ => { - match CallType::try_from(t.calls[0].call_type).map_err( - |_| { - graph::anyhow::anyhow!( - "invalid call type: {}", - t.calls[0].call_type, - ) - }, - )? { - CallType::Create => { - Some(t.calls[0].address.try_decode_proto( - "transaction contract address", - )?) - } - _ => None, - } - } - } - }, - logs: r - .logs - .iter() - .map(|l| LogAt::new(l, self, t).try_into()) - .collect::, Error>>()?, - status: TransactionTraceStatus::try_from(t.status) - .map_err(|_| { - graph::anyhow::anyhow!( - "invalid transaction trace status: {}", - t.status - ) - })? - .try_into()?, - root: match r.state_root.len() { - 0 => None, // FIXME (SF): should this instead map to [0;32]? - // FIXME (SF): if len < 32, what do we do? - _ => Some( - r.state_root.try_decode_proto("transaction state root")?, - ), - }, - logs_bloom: r - .logs_bloom - .try_decode_proto("transaction logs bloom")?, - from: t.from.try_decode_proto("transaction from")?, - to: get_to_address(t)?, - transaction_type: None, - effective_gas_price: None, - }) - }) - }) - .collect::, Error>>()? - .into_iter() - // Transaction receipts will be shared along the code, so we put them into an - // Arc here to avoid excessive cloning. - .map(Arc::new) - .collect(), + block: Arc::new(LightEthereumBlock::new(alloy_block)), + transaction_receipts, }, // Comment (437a9f17-67cc-478f-80a3-804fe554b227): This Some() will avoid calls in the triggers_in_block // TODO: Refactor in a way that this is no longer needed. @@ -376,12 +614,118 @@ impl TryInto for &Block { } } +fn transaction_trace_to_alloy_txn_reciept( + t: &TransactionTrace, + block: &Block, +) -> Result, Error> { + use alloy::consensus::{Eip658Value, Receipt}; + let r = t.receipt.as_ref(); + + if r.is_none() { + return Ok(None); + } + + let r = r.unwrap(); + + let contract_address = match t.calls.len() { + 0 => None, + _ => { + match CallType::try_from(t.calls[0].call_type).map_err(|_| { + graph::anyhow::anyhow!("invalid call type: {}", t.calls[0].call_type) + })? { + CallType::Create => Some( + t.calls[0] + .address + .try_decode_proto("transaction contract address")?, + ), + _ => None, + } + } + }; + + let state_root = match &r.state_root { + b if b.is_empty() => None, + _ => Some(r.state_root.try_decode_proto("transaction state root")?), + }; + + let status = match TransactionTraceStatus::try_from(t.status) + .map_err(|_| format_err!("invalid transaction trace status: {}", t.status))? + { + TransactionTraceStatus::Unknown => { + return Err(format_err!( + "Transaction trace has UNKNOWN status; datasource is broken" + )) + } + TransactionTraceStatus::Succeeded => true, + TransactionTraceStatus::Failed | TransactionTraceStatus::Reverted => false, + }; + + // [EIP-658]: https://eips.ethereum.org/EIPS/eip-658 + // Before EIP-658, the state root field was used to indicate the status of the transaction. + // After EIP-658, the status field is used to indicate the status of the transaction. + let status = match state_root { + Some(root) => Eip658Value::PostState(root), + None => Eip658Value::Eip658(status), + }; + + let logs: Vec = r + .logs + .iter() + .map(|l| LogAt::new(l, block, t).try_into()) + .collect::, Error>>()?; + + let core_receipt = Receipt { + status, + cumulative_gas_used: r.cumulative_gas_used, + logs, + }; + + let logs_bloom = Bloom::try_from(r.logs_bloom.as_slice())?; + + let receipt_with_bloom = ReceiptWithBloom::new(core_receipt, logs_bloom); + + let tx_type_u64 = u64::try_from(t.r#type).map_err(|_| { + format_err!( + "Invalid transaction type value {} in transaction receipt. Transaction type must be a valid u64.", + t.r#type + ) + })?; + + let any_envelope = AnyReceiptEnvelope { + inner: receipt_with_bloom, + r#type: tx_type_u64 as u8, + }; + + let receipt = alloy_rpc_types::TransactionReceipt { + transaction_hash: t.hash.try_decode_proto("transaction hash")?, + transaction_index: Some(t.index as u64), + block_hash: Some(block.hash.try_decode_proto("transaction block hash")?), + block_number: Some(block.number), + gas_used: t.gas_used, + contract_address, + from: t.from.try_decode_proto("transaction from")?, + to: get_to_address(t)?, + effective_gas_price: t.gas_price.as_ref().map_or(0u128, |x| { + let val: U256 = x.into(); + val.to::() + }), // gas_price already contains effective gas price per protobuf spec + blob_gas_used: r.blob_gas_used, + blob_gas_price: r.blob_gas_price.as_ref().map(|x| { + let val: U256 = x.into(); + val.to::() + }), + inner: any_envelope, + }; + + Ok(Some(WithOtherFields::new(receipt))) +} + impl BlockHeader { pub fn parent_ptr(&self) -> Option { match self.parent_hash.len() { 0 => None, _ => Some(BlockPtr::from(( - H256::from_slice(self.parent_hash.as_ref()), + B256::from_slice(self.parent_hash.as_ref()), self.number - 1, ))), } @@ -390,13 +734,13 @@ impl BlockHeader { impl<'a> From<&'a BlockHeader> for BlockPtr { fn from(b: &'a BlockHeader) -> BlockPtr { - BlockPtr::from((H256::from_slice(b.hash.as_ref()), b.number)) + BlockPtr::from((B256::from_slice(b.hash.as_ref()), b.number)) } } impl<'a> From<&'a Block> for BlockPtr { fn from(b: &'a Block) -> BlockPtr { - BlockPtr::from((H256::from_slice(b.hash.as_ref()), b.number)) + BlockPtr::from((B256::from_slice(b.hash.as_ref()), b.number)) } } @@ -528,9 +872,120 @@ mod test { format!(r#"{{"block":{{"data":null,"timestamp":"{}"}}}}"#, now) ); } + + #[test] + fn test_unknown_transaction_type_conversion() { + use super::TransactionTraceAt; + use crate::codec::TransactionTrace; + use graph::prelude::alloy::network::AnyTxEnvelope; + use graph::prelude::alloy::primitives::B256; + + let mut block = Block::default(); + let mut header = BlockHeader::default(); + header.number = 123456; + header.timestamp = Some(Timestamp { + seconds: 1234567890, + nanos: 0, + }); + block.header = Some(header); + block.number = 123456; + block.hash = vec![0u8; 32]; + + let mut trace = TransactionTrace::default(); + trace.r#type = 126; // 0x7e Optimism deposit transaction + trace.hash = vec![1u8; 32]; + trace.from = vec![2u8; 20]; + trace.to = vec![3u8; 20]; + trace.nonce = 42; + trace.gas_limit = 21000; + trace.index = 0; + + let trace_at = TransactionTraceAt::new(&trace, &block); + let result: Result< + graph::prelude::alloy::rpc::types::Transaction, + graph::prelude::Error, + > = trace_at.try_into(); + + assert!( + result.is_ok(), + "Should successfully convert unknown transaction type" + ); + + let tx = result.unwrap(); + + match tx.inner.inner() { + AnyTxEnvelope::Unknown(unknown_envelope) => { + assert_eq!(unknown_envelope.inner.ty.0, 126); + assert_eq!(unknown_envelope.hash, B256::from_slice(&trace.hash)); + assert!( + !unknown_envelope.inner.fields.is_empty(), + "OtherFields should contain transaction data" + ); + } + _ => panic!("Expected AnyTxEnvelope::Unknown, got Ethereum variant"), + } + + assert_eq!(tx.block_number, Some(123456)); + assert_eq!(tx.transaction_index, Some(0)); + assert_eq!(tx.block_hash, Some(B256::from_slice(&block.hash))); + } + + #[test] + fn test_unknown_receipt_type_conversion() { + use super::transaction_trace_to_alloy_txn_reciept; + use crate::codec::TransactionTrace; + + let mut block = Block::default(); + let mut header = BlockHeader::default(); + header.number = 123456; + block.header = Some(header); + block.hash = vec![0u8; 32]; + + let mut trace = TransactionTrace::default(); + trace.r#type = 126; // 0x7e Optimism deposit transaction + trace.hash = vec![1u8; 32]; + trace.from = vec![2u8; 20]; + trace.to = vec![3u8; 20]; + trace.index = 0; + trace.gas_used = 21000; + trace.status = 1; + + let mut receipt = super::TransactionReceipt::default(); + receipt.cumulative_gas_used = 21000; + receipt.logs_bloom = vec![0u8; 256]; + trace.receipt = Some(receipt); + + let result = transaction_trace_to_alloy_txn_reciept(&trace, &block); + + assert!( + result.is_ok(), + "Should successfully convert receipt with unknown transaction type" + ); + + let receipt_opt = result.unwrap(); + assert!(receipt_opt.is_some(), "Receipt should be present"); + + let receipt = receipt_opt.unwrap(); + + assert_eq!(receipt.inner.inner.r#type, 126); + assert_eq!(receipt.gas_used, 21000); + assert_eq!(receipt.transaction_index, Some(0)); + } +} + +fn extract_signature_from_trace( + _trace: &TransactionTrace, + _tx_type: TxType, +) -> Result { + use alloy::primitives::{Signature as PrimitiveSignature, U256}; + + // Create a dummy signature with r = 0, s = 0 and even y-parity (false) + let dummy = PrimitiveSignature::new(U256::ZERO, U256::ZERO, false); + + Ok(dummy.into()) } -fn get_to_address(trace: &TransactionTrace) -> Result, Error> { +fn get_to_address(trace: &TransactionTrace) -> Result, Error> { // Try to detect contract creation transactions, which have no 'to' address let is_contract_creation = trace.to.len() == 0 || trace.calls.get(0).map_or(false, |call| { diff --git a/chain/ethereum/src/data_source.rs b/chain/ethereum/src/data_source.rs index e314b5a158f..b266bbab659 100644 --- a/chain/ethereum/src/data_source.rs +++ b/chain/ethereum/src/data_source.rs @@ -1,7 +1,11 @@ use anyhow::{anyhow, Error}; use anyhow::{ensure, Context}; use async_trait::async_trait; +use graph::abi; +use graph::abi::EventExt; +use graph::abi::FunctionExt; use graph::blockchain::{BlockPtr, TriggerWithHandler}; +use graph::components::ethereum::AnyTransaction; use graph::components::link_resolver::LinkResolverContext; use graph::components::metrics::subgraph::SubgraphInstanceMetrics; use graph::components::store::{EthereumCallCache, StoredDynamicDataSource}; @@ -17,9 +21,13 @@ use graph::env::ENV_VARS; use graph::futures03::future::try_join; use graph::futures03::stream::FuturesOrdered; use graph::futures03::TryStreamExt; -use graph::prelude::ethabi::ethereum_types::H160; -use graph::prelude::ethabi::StateMutability; -use graph::prelude::{Link, SubgraphManifestValidationError}; +use graph::prelude::alloy::{ + consensus::{TxEnvelope, TxLegacy}, + network::TransactionResponse, + primitives::{Address, B256, U256}, + rpc::types::Log, +}; +use graph::prelude::{alloy, Link, SubgraphManifestValidationError}; use graph::slog::{debug, error, o, trace}; use itertools::Itertools; use serde::de::Error as ErrorD; @@ -34,11 +42,8 @@ use tiny_keccak::{keccak256, Keccak}; use graph::{ blockchain::{self, Blockchain}, prelude::{ - ethabi::{Address, Event, Function, LogParam, ParamType, RawLog}, - serde_json, warn, - web3::types::{Log, Transaction, H256}, - BlockNumber, CheapClone, EthereumCall, LightEthereumBlock, LightEthereumBlockExt, - LinkResolver, Logger, + serde_json, warn, BlockNumber, CheapClone, EthereumCall, LightEthereumBlock, + LightEthereumBlockExt, LinkResolver, Logger, }, }; @@ -134,7 +139,7 @@ impl blockchain::DataSource for DataSource { } fn address(&self) -> Option<&[u8]> { - self.address.as_ref().map(|x| x.as_bytes()) + self.address.as_ref().map(|x| x.as_slice()) } fn has_declared_calls(&self) -> bool { @@ -238,7 +243,7 @@ impl blockchain::DataSource for DataSource { } fn as_stored_dynamic_data_source(&self) -> StoredDynamicDataSource { - let param = self.address.map(|addr| addr.0.into()); + let param = self.address.map(|addr| addr.as_slice().into()); StoredDynamicDataSource { manifest_idx: self.manifest_idx, param, @@ -277,7 +282,7 @@ impl blockchain::DataSource for DataSource { let contract_abi = template.mapping.find_abi(&template.source.abi)?; - let address = param.map(|x| H160::from_slice(&x)); + let address = param.map(|x| Address::from_slice(&x)); Ok(DataSource { kind: template.kind.to_string(), network: template.network.as_ref().map(|s| s.to_string()), @@ -432,6 +437,45 @@ impl blockchain::DataSource for DataSource { } } +/// Generic function that creates a mock legacy Transaction from ANY log +fn create_dummy_transaction( + block_number: u64, + block_hash: B256, + transaction_index: Option, + transaction_hash: Option, +) -> Result { + use alloy::serde::WithOtherFields; + use graph::components::ethereum::AnyTxEnvelope; + use graph::prelude::alloy::{ + consensus::transaction::Recovered, consensus::Signed, primitives::Signature, + rpc::types::Transaction, + }; + + let tx = TxLegacy::default(); + + // Create a dummy signature + let signature = Signature::new(U256::ZERO, U256::ZERO, false); + + let tx_hash = transaction_hash.ok_or(anyhow!("Log has no transaction hash"))?; + let signed_tx = Signed::new_unchecked(tx, signature, tx_hash); + let eth_envelope = TxEnvelope::Legacy(signed_tx); + + // Wrap in AnyTxEnvelope + let any_envelope = AnyTxEnvelope::Ethereum(eth_envelope); + + let recovered = Recovered::new_unchecked(any_envelope, Address::ZERO); + + let inner_tx = Transaction { + inner: recovered, + block_hash: Some(block_hash), + block_number: Some(block_number), + transaction_index, + effective_gas_price: None, + }; + + Ok(AnyTransaction::new(WithOtherFields::new(inner_tx))) +} + impl DataSource { fn from_manifest( kind: String, @@ -463,7 +507,7 @@ impl DataSource { }) } - fn handlers_for_log(&self, log: &Log) -> Vec { + fn handlers_for_log(&self, log: &alloy::rpc::types::Log) -> Vec { self.mapping .event_handlers .iter() @@ -528,28 +572,28 @@ impl DataSource { } } - /// Returns the contract event with the given signature, if it exists. A an event from the ABI + /// Returns the contract event with the given signature, if it exists. An event from the ABI /// will be matched if: /// 1. An event signature is equal to `signature`. /// 2. There are no equal matches, but there is exactly one event that equals `signature` if all /// `indexed` modifiers are removed from the parameters. - fn contract_event_with_signature(&self, signature: &str) -> Option<&Event> { + fn contract_event_with_signature(&self, signature: &str) -> Option<&abi::Event> { // Returns an `Event(uint256,address)` signature for an event, without `indexed` hints. - fn ambiguous_event_signature(event: &Event) -> String { + fn ambiguous_event_signature(event: &abi::Event) -> String { format!( "{}({})", event.name, event .inputs .iter() - .map(|input| event_param_type_signature(&input.kind)) + .map(|input| input.selector_type().into_owned()) .collect::>() .join(",") ) } // Returns an `Event(indexed uint256,address)` type signature for an event. - fn event_signature(event: &Event) -> String { + fn event_signature(event: &abi::Event) -> String { format!( "{}({})", event.name, @@ -559,40 +603,13 @@ impl DataSource { .map(|input| format!( "{}{}", if input.indexed { "indexed " } else { "" }, - event_param_type_signature(&input.kind) + input.selector_type() )) .collect::>() .join(",") ) } - // Returns the signature of an event parameter type (e.g. `uint256`). - fn event_param_type_signature(kind: &ParamType) -> String { - use ParamType::*; - - match kind { - Address => "address".into(), - Bytes => "bytes".into(), - Int(size) => format!("int{}", size), - Uint(size) => format!("uint{}", size), - Bool => "bool".into(), - String => "string".into(), - Array(inner) => format!("{}[]", event_param_type_signature(inner)), - FixedBytes(size) => format!("bytes{}", size), - FixedArray(inner, size) => { - format!("{}[{}]", event_param_type_signature(inner), size) - } - Tuple(components) => format!( - "({})", - components - .iter() - .map(event_param_type_signature) - .collect::>() - .join(",") - ), - } - } - self.contract_abi .contract .events() @@ -631,7 +648,9 @@ impl DataSource { }) } - fn contract_function_with_signature(&self, target_signature: &str) -> Option<&Function> { + fn contract_function_with_signature(&self, target_signature: &str) -> Option<&abi::Function> { + use abi::StateMutability; + self.contract_abi .contract .functions() @@ -645,7 +664,7 @@ impl DataSource { let mut arguments = function .inputs .iter() - .map(|input| format!("{}", input.kind)) + .map(|input| input.selector_type().into_owned()) .collect::>() .join(","); // `address,uint256,bool) @@ -735,11 +754,7 @@ impl DataSource { .into_iter() .filter_map(|(event_handler, event_abi)| { event_abi - .parse_log(RawLog { - topics: log.topics.clone(), - data: log.data.clone().0, - }) - .map(|log| log.params) + .decode_log(&log) .map_err(|e| { trace!( logger, @@ -778,17 +793,15 @@ impl DataSource { // See also ca0edc58-0ec5-4c89-a7dd-2241797f5e50. // There is another special case in zkSync-era, where the transaction hash in this case would be zero // See https://docs.zksync.io/zk-stack/concepts/blocks.html#fictive-l2-block-finalizing-the-batch - let transaction = if log.transaction_hash == block.hash - || log.transaction_hash == Some(H256::zero()) + let transaction = if log.transaction_hash == Some(block.hash()) + || log.transaction_hash == Some(B256::ZERO) { - Transaction { - hash: log.transaction_hash.unwrap(), - block_hash: block.hash, - block_number: block.number, - transaction_index: log.transaction_index, - from: Some(H160::zero()), - ..Transaction::default() - } + create_dummy_transaction( + block.number_u64(), + block.hash(), + log.transaction_index, + log.transaction_hash, + )? } else { // This is the general case where the log's transaction hash does not match the block's hash // and is not a special zero hash, implying a real transaction associated with this log. @@ -799,8 +812,8 @@ impl DataSource { let logging_extras = Arc::new(o! { "signature" => event_handler.event.to_string(), - "address" => format!("{}", &log.address), - "transaction" => format!("{}", &transaction.hash), + "address" => format!("{}", &log.address()), + "transaction" => format!("{}", &transaction.inner.tx_hash()), }); let handler = event_handler.handler.clone(); let calls = DeclaredCall::from_log_trigger_with_event( @@ -844,20 +857,15 @@ impl DataSource { ) })?; - // Parse the inputs - // - // Take the input for the call, chop off the first 4 bytes, then call - // `function.decode_input` to get a vector of `Token`s. Match the `Token`s - // with the `Param`s in `function.inputs` to create a `Vec`. - let tokens = match function_abi.decode_input(&call.input.0[4..]).with_context( - || { + let values = match function_abi + .abi_decode_input(&call.input.0[4..]) + .with_context(|| { format!( "Generating function inputs for the call {:?} failed, raw input: {}", &function_abi, hex::encode(&call.input.0) ) - }, - ) { + }) { Ok(val) => val, // See also 280b0108-a96e-4738-bb37-60ce11eeb5bf Err(err) => { @@ -867,27 +875,22 @@ impl DataSource { }; ensure!( - tokens.len() == function_abi.inputs.len(), + values.len() == function_abi.inputs.len(), "Number of arguments in call does not match \ number of inputs in function signature." ); - let inputs = tokens + let inputs = values .into_iter() .enumerate() - .map(|(i, token)| LogParam { + .map(|(i, value)| abi::DynSolParam { name: function_abi.inputs[i].name.clone(), - value: token, + value, }) .collect::>(); - // Parse the outputs - // - // Take the output for the call, then call `function.decode_output` to - // get a vector of `Token`s. Match the `Token`s with the `Param`s in - // `function.outputs` to create a `Vec`. - let tokens = function_abi - .decode_output(&call.output.0) + let values = function_abi + .abi_decode_output(&call.output.0) .with_context(|| { format!( "Decoding function outputs for the call {:?} failed, raw output: {}", @@ -897,17 +900,17 @@ impl DataSource { })?; ensure!( - tokens.len() == function_abi.outputs.len(), + values.len() == function_abi.outputs.len(), "Number of parameters in the call output does not match \ number of outputs in the function signature." ); - let outputs = tokens + let outputs = values .into_iter() .enumerate() - .map(|(i, token)| LogParam { + .map(|(i, value)| abi::DynSolParam { name: function_abi.outputs[i].name.clone(), - value: token, + value, }) .collect::>(); @@ -919,7 +922,7 @@ impl DataSource { let logging_extras = Arc::new(o! { "function" => handler.function.to_string(), "to" => format!("{}", &call.to), - "transaction" => format!("{}", &transaction.hash), + "transaction" => format!("{}", &transaction.inner.tx_hash()), }); Ok(Some(TriggerWithHandler::::new_with_logging_extras( MappingTrigger::Call { @@ -1478,13 +1481,13 @@ pub struct MappingCallHandler { #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] pub struct UnresolvedMappingEventHandler { pub event: String, - pub topic0: Option, - #[serde(deserialize_with = "deserialize_h256_vec", default)] - pub topic1: Option>, - #[serde(deserialize_with = "deserialize_h256_vec", default)] - pub topic2: Option>, - #[serde(deserialize_with = "deserialize_h256_vec", default)] - pub topic3: Option>, + pub topic0: Option, + #[serde(deserialize_with = "deserialize_b256_vec", default)] + pub topic1: Option>, + #[serde(deserialize_with = "deserialize_b256_vec", default)] + pub topic2: Option>, + #[serde(deserialize_with = "deserialize_b256_vec", default)] + pub topic3: Option>, pub handler: String, #[serde(default)] pub receipt: bool, @@ -1518,17 +1521,17 @@ impl UnresolvedMappingEventHandler { #[derive(Clone, Debug, Hash, Eq, PartialEq)] pub struct MappingEventHandler { pub event: String, - pub topic0: Option, - pub topic1: Option>, - pub topic2: Option>, - pub topic3: Option>, + pub topic0: Option, + pub topic1: Option>, + pub topic2: Option>, + pub topic3: Option>, pub handler: String, pub receipt: bool, pub calls: CallDecls, } -// Custom deserializer for H256 fields that removes the '0x' prefix before parsing -fn deserialize_h256_vec<'de, D>(deserializer: D) -> Result>, D::Error> +// Custom deserializer for B256 fields that removes the '0x' prefix before parsing +fn deserialize_b256_vec<'de, D>(deserializer: D) -> Result>, D::Error> where D: Deserializer<'de>, { @@ -1536,40 +1539,40 @@ where match s { Some(vec) => { - let mut h256_vec = Vec::new(); + let mut b256_vec = Vec::new(); for hex_str in vec { // Remove '0x' prefix if present let clean_hex_str = hex_str.trim_start_matches("0x"); // Ensure the hex string is 64 characters long, after removing '0x' let padded_hex_str = format!("{:0>64}", clean_hex_str); // Parse the padded string into H256, handling potential errors - h256_vec.push( - H256::from_str(&padded_hex_str) - .map_err(|e| D::Error::custom(format!("Failed to parse H256: {}", e)))?, + b256_vec.push( + B256::from_str(&padded_hex_str) + .map_err(|e| D::Error::custom(format!("Failed to parse B256: {}", e)))?, ); } - Ok(Some(h256_vec)) + Ok(Some(b256_vec)) } None => Ok(None), } } impl MappingEventHandler { - pub fn topic0(&self) -> H256 { + pub fn topic0(&self) -> B256 { self.topic0 - .unwrap_or_else(|| string_to_h256(&self.event.replace("indexed ", ""))) + .unwrap_or_else(|| string_to_b256(&self.event.replace("indexed ", ""))) } pub fn matches(&self, log: &Log) -> bool { - let matches_topic = |index: usize, topic_opt: &Option>| -> bool { + let matches_topic = |index: usize, topic_opt: &Option>| -> bool { topic_opt.as_ref().map_or(true, |topic_vec| { - log.topics + log.topics() .get(index) .map_or(false, |log_topic| topic_vec.contains(log_topic)) }) }; - if let Some(topic0) = log.topics.get(0) { + if let Some(topic0) = log.topics().get(0) { return self.topic0() == *topic0 && matches_topic(1, &self.topic1) && matches_topic(2, &self.topic2) @@ -1587,18 +1590,15 @@ impl MappingEventHandler { } } -/// Hashes a string to a H256 hash. -fn string_to_h256(s: &str) -> H256 { +/// Hashes a string to a B256 hash. +fn string_to_b256(s: &str) -> B256 { let mut result = [0u8; 32]; let data = s.replace(' ', "").into_bytes(); let mut sponge = Keccak::new_keccak256(); sponge.update(&data); sponge.finalize(&mut result); - // This was deprecated but the replacement seems to not be available in the - // version web3 uses. - #[allow(deprecated)] - H256::from_slice(&result) + B256::from_slice(&result) } #[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Deserialize)] diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index c4dd377fa58..bd0febbf56c 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1,13 +1,13 @@ use async_trait::async_trait; use futures03::{future::BoxFuture, stream::FuturesUnordered}; -use tokio::sync::RwLock; -use tokio::time::timeout; - +use graph::abi; +use graph::abi::DynSolValueExt; +use graph::abi::FunctionExt; use graph::blockchain::client::ChainClient; use graph::blockchain::BlockHash; use graph::blockchain::ChainIdentifier; use graph::blockchain::ExtendedBlockPtr; - +use graph::components::ethereum::*; use graph::components::transaction_receipt::LightTransactionReceipt; use graph::data::store::ethereum::call; use graph::data::store::scalar; @@ -21,33 +21,35 @@ use graph::futures03::future::try_join_all; use graph::futures03::{ self, compat::Future01CompatExt, FutureExt, StreamExt, TryFutureExt, TryStreamExt, }; -use graph::prelude::ethabi::ParamType; -use graph::prelude::ethabi::Token; -use graph::prelude::tokio::try_join; -use graph::prelude::web3::types::U256; +use graph::prelude::{ + alloy::{ + self, + network::{AnyNetwork, TransactionResponse}, + primitives::{Address, B256}, + providers::{ + ext::TraceApi, + fillers::{ + BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, + }, + Identity, Provider, RootProvider, + }, + rpc::types::{ + trace::{filter::TraceFilter as AlloyTraceFilter, parity::LocalizedTransactionTrace}, + TransactionInput, TransactionRequest, + }, + transports::{RpcError, TransportErrorKind}, + }, + tokio::try_join, +}; use graph::slog::o; use graph::{ blockchain::{block_stream::BlockWithTriggers, BlockPtr, IngestorError}, prelude::{ anyhow::{self, anyhow, bail, ensure, Context}, - debug, error, ethabi, hex, info, retry, serde_json as json, tiny_keccak, trace, warn, - web3::{ - self, - types::{ - Address, BlockId, BlockNumber as Web3BlockNumber, Bytes, CallRequest, Filter, - FilterBuilder, Log, Transaction, TransactionReceipt, H256, - }, - }, - BlockNumber, ChainStore, CheapClone, DynTryFuture, Error, EthereumCallCache, Logger, - TimeoutError, + debug, error, hex, info, retry, serde_json as json, trace, warn, BlockNumber, ChainStore, + CheapClone, DynTryFuture, Error, EthereumCallCache, Logger, TimeoutError, }, }; -use graph::{ - components::ethereum::*, - prelude::web3::api::Web3, - prelude::web3::transports::Batch, - prelude::web3::types::{Trace, TraceFilter, TraceFilterBuilder, H160}, -}; use itertools::Itertools; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::convert::TryFrom; @@ -55,9 +57,14 @@ use std::iter::FromIterator; use std::pin::Pin; use std::sync::Arc; use std::time::Instant; +use tokio::sync::RwLock; +use tokio::time::timeout; +use tokio_stream; +use crate::adapter::EthGetLogsFilter; use crate::adapter::EthereumRpcError; use crate::adapter::ProviderStatus; +use crate::call_helper::interpret_eth_call_error; use crate::chain::BlockFinality; use crate::trigger::{LogPosition, LogRef}; use crate::Chain; @@ -65,32 +72,54 @@ use crate::NodeCapabilities; use crate::TriggerFilter; use crate::{ adapter::{ - ContractCallError, EthGetLogsFilter, EthereumAdapter as EthereumAdapterTrait, - EthereumBlockFilter, EthereumCallFilter, EthereumLogFilter, ProviderEthRpcMetrics, - SubgraphEthRpcMetrics, + ContractCallError, EthereumAdapter as EthereumAdapterTrait, EthereumBlockFilter, + EthereumCallFilter, EthereumLogFilter, ProviderEthRpcMetrics, SubgraphEthRpcMetrics, }, transport::Transport, trigger::{EthereumBlockTriggerType, EthereumTrigger}, ENV_VARS, }; -#[derive(Debug, Clone)] +type AlloyProvider = FillProvider< + JoinFill< + Identity, + JoinFill>>, + >, + RootProvider, + AnyNetwork, +>; + +#[derive(Clone)] pub struct EthereumAdapter { logger: Logger, provider: String, - web3: Arc>, + alloy: Arc, metrics: Arc, supports_eip_1898: bool, call_only: bool, supports_block_receipts: Arc>>, } +impl std::fmt::Debug for EthereumAdapter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EthereumAdapter") + .field("logger", &self.logger) + .field("provider", &self.provider) + .field("alloy", &"") + .field("metrics", &self.metrics) + .field("supports_eip_1898", &self.supports_eip_1898) + .field("call_only", &self.call_only) + .field("supports_block_receipts", &self.supports_block_receipts) + .finish() + } +} + impl CheapClone for EthereumAdapter { fn cheap_clone(&self) -> Self { Self { logger: self.logger.clone(), provider: self.provider.clone(), - web3: self.web3.cheap_clone(), + alloy: self.alloy.clone(), metrics: self.metrics.cheap_clone(), supports_eip_1898: self.supports_eip_1898, call_only: self.call_only, @@ -112,12 +141,35 @@ impl EthereumAdapter { supports_eip_1898: bool, call_only: bool, ) -> Self { - let web3 = Arc::new(Web3::new(transport)); + let alloy = match &transport { + Transport::RPC { client, .. } => Arc::new( + alloy::providers::ProviderBuilder::<_, _, AnyNetwork>::default() + .network::() + .with_recommended_fillers() + .connect_client(client.clone()), + ), + Transport::IPC(ipc_connect) => Arc::new( + alloy::providers::ProviderBuilder::<_, _, AnyNetwork>::default() + .network::() + .with_recommended_fillers() + .connect_ipc(ipc_connect.clone()) + .await + .expect("Failed to connect to Ethereum IPC"), + ), + Transport::WS(ws_connect) => Arc::new( + alloy::providers::ProviderBuilder::<_, _, AnyNetwork>::default() + .network::() + .with_recommended_fillers() + .connect_ws(ws_connect.clone()) + .await + .expect("Failed to connect to Ethereum WS"), + ), + }; EthereumAdapter { logger, provider, - web3, + alloy, metrics: provider_metrics, supports_eip_1898, call_only, @@ -131,79 +183,26 @@ impl EthereumAdapter { subgraph_metrics: Arc, from: BlockNumber, to: BlockNumber, - addresses: Vec, - ) -> Result, Error> { + addresses: Vec
, + ) -> Result, Error> { assert!(!self.call_only); - let eth = self.clone(); let retry_log_message = format!("trace_filter RPC call for block range: [{}..{}]", from, to); + let eth = self.clone(); + retry(retry_log_message, &logger) .redact_log_urls(true) .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let trace_filter: TraceFilter = match addresses.len() { - 0 => TraceFilterBuilder::default() - .from_block(from.into()) - .to_block(to.into()) - .build(), - _ => TraceFilterBuilder::default() - .from_block(from.into()) - .to_block(to.into()) - .to_address(addresses.clone()) - .build(), - }; - - let eth = eth.cheap_clone(); - let logger_for_triggers = logger.clone(); - let logger_for_error = logger.clone(); - let start = Instant::now(); + let eth = eth.clone(); + let logger = logger.clone(); let subgraph_metrics = subgraph_metrics.clone(); - let provider_metrics = eth.metrics.clone(); - let provider = self.provider.clone(); - + let addresses = addresses.clone(); async move { - let result = eth - .web3 - .trace() - .filter(trace_filter) + eth.execute_trace_filter_request(logger, subgraph_metrics, from, to, addresses) .await - .map(move |traces| { - if !traces.is_empty() { - if to == from { - debug!( - logger_for_triggers, - "Received {} traces for block {}", - traces.len(), - to - ); - } else { - debug!( - logger_for_triggers, - "Received {} traces for blocks [{}, {}]", - traces.len(), - from, - to - ); - } - } - traces - }) - .map_err(Error::from); - - let elapsed = start.elapsed().as_secs_f64(); - provider_metrics.observe_request(elapsed, "trace_filter", &provider); - subgraph_metrics.observe_request(elapsed, "trace_filter", &provider); - if let Err(e) = &result { - provider_metrics.add_error("trace_filter", &provider); - subgraph_metrics.add_error("trace_filter", &provider); - debug!( - logger_for_error, - "Error querying traces error = {:#} from = {} to = {}", e, from, to - ); - } - result } }) .map_err(move |e| { @@ -219,12 +218,99 @@ impl EthereumAdapter { .await } + async fn execute_trace_filter_request( + &self, + logger: Logger, + subgraph_metrics: Arc, + from: BlockNumber, + to: BlockNumber, + addresses: Vec
, + ) -> Result, Error> { + let alloy_trace_filter = Self::build_trace_filter(from, to, &addresses); + let start = Instant::now(); + + let result = self.alloy.trace_filter(&alloy_trace_filter).await; + + if let Ok(traces) = &result { + self.log_trace_results(&logger, from, to, traces.len()); + } + + self.record_trace_metrics( + &subgraph_metrics, + start.elapsed().as_secs_f64(), + &result, + from, + to, + &logger, + ); + + result.map_err(Error::from) + } + + fn build_trace_filter( + from: BlockNumber, + to: BlockNumber, + addresses: &[Address], + ) -> AlloyTraceFilter { + let filter = AlloyTraceFilter::default() + .from_block(from as u64) + .to_block(to as u64); + + if !addresses.is_empty() { + filter.to_address(addresses.to_vec()) + } else { + filter + } + } + + fn log_trace_results( + &self, + logger: &Logger, + from: BlockNumber, + to: BlockNumber, + trace_len: usize, + ) { + if trace_len > 0 { + if to == from { + debug!(logger, "Received {} traces for block {}", trace_len, to); + } else { + debug!( + logger, + "Received {} traces for blocks [{}, {}]", trace_len, from, to + ); + } + } + } + + fn record_trace_metrics( + &self, + subgraph_metrics: &Arc, + elapsed: f64, + result: &Result, RpcError>, + from: BlockNumber, + to: BlockNumber, + logger: &Logger, + ) { + self.metrics + .observe_request(elapsed, "trace_filter", &self.provider); + subgraph_metrics.observe_request(elapsed, "trace_filter", &self.provider); + + if let Err(e) = result { + self.metrics.add_error("trace_filter", &self.provider); + subgraph_metrics.add_error("trace_filter", &self.provider); + debug!( + logger, + "Error querying traces error = {:#} from = {} to = {}", e, from, to + ); + } + } + // This is a lazy check for block receipt support. It is only called once and then the result is // cached. The result is not used for anything critical, so it is fine to be lazy. async fn check_block_receipt_support_and_update_cache( &self, - web3: Arc>, - block_hash: H256, + alloy: Arc, + block_hash: B256, supports_eip_1898: bool, call_only: bool, logger: Logger, @@ -241,7 +327,7 @@ impl EthereumAdapter { info!(logger, "Checking eth_getBlockReceipts support"); let result = timeout( ENV_VARS.block_receipts_check_timeout, - check_block_receipt_support(web3, block_hash, supports_eip_1898, call_only), + check_block_receipt_support(alloy, block_hash, supports_eip_1898, call_only), ) .await; @@ -274,6 +360,7 @@ impl EthereumAdapter { result } + /// Alloy-exclusive version of logs_with_sigs using alloy types and methods async fn logs_with_sigs( &self, logger: Logger, @@ -282,19 +369,24 @@ impl EthereumAdapter { to: BlockNumber, filter: Arc, too_many_logs_fingerprints: &'static [&'static str], - ) -> Result, TimeoutError> { + ) -> Result< + Vec, + TimeoutError>, + > { assert!(!self.call_only); let eth_adapter = self.clone(); let retry_log_message = format!("eth_getLogs RPC call for block range: [{}..{}]", from, to); retry(retry_log_message, &logger) .redact_log_urls(true) - .when(move |res: &Result<_, web3::error::Error>| match res { - Ok(_) => false, - Err(e) => !too_many_logs_fingerprints - .iter() - .any(|f| e.to_string().contains(f)), - }) + .when( + move |res: &Result<_, RpcError>| match res { + Ok(_) => false, + Err(e) => !too_many_logs_fingerprints + .iter() + .any(|f| e.to_string().contains(f)), + }, + ) .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { @@ -306,21 +398,10 @@ impl EthereumAdapter { async move { let start = Instant::now(); - // Create a log filter - let log_filter: Filter = FilterBuilder::default() - .from_block(from.into()) - .to_block(to.into()) - .address(filter.contracts.clone()) - .topics( - Some(filter.event_signatures.clone()), - filter.topic1.clone(), - filter.topic2.clone(), - filter.topic3.clone(), - ) - .build(); - // Request logs from client - let result = eth_adapter.web3.eth().logs(log_filter).boxed().await; + let alloy_filter = filter.to_alloy_filter(from, to); + + let result = eth_adapter.alloy.get_logs(&alloy_filter).await; let elapsed = start.elapsed().as_secs_f64(); provider_metrics.observe_request(elapsed, "eth_getLogs", &provider); subgraph_metrics.observe_request(elapsed, "eth_getLogs", &provider); @@ -340,8 +421,8 @@ impl EthereumAdapter { subgraph_metrics: Arc, from: BlockNumber, to: BlockNumber, - addresses: Vec, - ) -> impl Stream + Send { + addresses: Vec
, + ) -> impl futures03::Stream> + Send { if from > to { panic!( "Can not produce a call stream on a backwards block range: from = {}, to = {}", @@ -355,36 +436,40 @@ impl EthereumAdapter { true => 1, }; + let ranges: Vec<(BlockNumber, BlockNumber)> = { + let mut ranges = Vec::new(); + let mut start = from; + while start <= to { + let end = (start + step_size - 1).min(to); + ranges.push((start, end)); + start = end + 1; + } + ranges + }; + let eth = self; let logger = logger.clone(); - stream::unfold(from, move |start| { - if start > to { - return None; - } - let end = (start + step_size - 1).min(to); - let new_start = end + 1; - if start == end { - debug!(logger, "Requesting traces for block {}", start); - } else { - debug!(logger, "Requesting traces for blocks [{}, {}]", start, end); + + futures03::stream::iter(ranges.into_iter().map(move |(start, end)| { + let eth = eth.clone(); + let logger = logger.clone(); + let subgraph_metrics = subgraph_metrics.clone(); + let addresses = addresses.clone(); + + async move { + if start == end { + debug!(logger, "Requesting traces for block {}", start); + } else { + debug!(logger, "Requesting traces for blocks [{}, {}]", start, end); + } + + eth.traces(logger, subgraph_metrics, start, end, addresses) + .await } - Some(graph::futures01::future::ok(( - eth.clone() - .traces( - logger.cheap_clone(), - subgraph_metrics.clone(), - start, - end, - addresses.clone(), - ) - .boxed() - .compat(), - new_start, - ))) - }) + })) .buffered(ENV_VARS.block_batch_size) - .map(stream::iter_ok) - .flatten() + .map_ok(|traces| futures03::stream::iter(traces.into_iter().map(Ok))) + .try_flatten() } fn log_stream( @@ -394,7 +479,7 @@ impl EthereumAdapter { from: BlockNumber, to: BlockNumber, filter: EthGetLogsFilter, - ) -> DynTryFuture<'static, Vec, Error> { + ) -> DynTryFuture<'static, Vec, Error> { // Codes returned by Ethereum node providers if an eth_getLogs request is too heavy. const TOO_MANY_LOGS_FINGERPRINTS: &[&str] = &[ "ServerError(-32005)", // Infura @@ -481,14 +566,11 @@ impl EthereumAdapter { .boxed() } - // Method to determine block_id based on support for EIP-1898 - fn block_ptr_to_id(&self, block_ptr: &BlockPtr) -> BlockId { - // Ganache does not support calls by block hash. - // See https://github.com/trufflesuite/ganache-cli/issues/973 + fn block_ptr_to_id(&self, block_ptr: &BlockPtr) -> alloy::rpc::types::BlockId { if !self.supports_eip_1898 { - BlockId::Number(block_ptr.number.into()) + alloy::rpc::types::BlockId::number(block_ptr.number as u64) } else { - BlockId::Hash(block_ptr.hash_as_h256()) + alloy::rpc::types::BlockId::hash(block_ptr.hash.as_b256()) } } @@ -497,8 +579,8 @@ impl EthereumAdapter { logger: &Logger, address: Address, block_ptr: BlockPtr, - ) -> Result { - let web3 = self.web3.clone(); + ) -> Result { + let alloy = self.alloy.clone(); let logger = Logger::new(&logger, o!("provider" => self.provider.clone())); let block_id = self.block_ptr_to_id(&block_ptr); @@ -513,13 +595,12 @@ impl EthereumAdapter { .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); async move { - let result: Result = - web3.eth().code(address, Some(block_id)).boxed().await; + let result = alloy.get_code_at(address).block_id(block_id).await; match result { Ok(code) => Ok(code), - Err(err) => Err(EthereumRpcError::Web3Error(err)), + Err(err) => Err(EthereumRpcError::AlloyError(err)), } } }) @@ -532,8 +613,8 @@ impl EthereumAdapter { logger: &Logger, address: Address, block_ptr: BlockPtr, - ) -> Result { - let web3 = self.web3.clone(); + ) -> Result { + let alloy = self.alloy.clone(); let logger = Logger::new(&logger, o!("provider" => self.provider.clone())); let block_id = self.block_ptr_to_id(&block_ptr); @@ -548,13 +629,12 @@ impl EthereumAdapter { .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); async move { - let result: Result = - web3.eth().balance(address, Some(block_id)).boxed().await; + let result = alloy.get_balance(address).block_id(block_id).await; match result { Ok(balance) => Ok(balance), - Err(err) => Err(EthereumRpcError::Web3Error(err)), + Err(err) => Err(EthereumRpcError::AlloyError(err)), } } }) @@ -569,15 +649,10 @@ impl EthereumAdapter { block_ptr: BlockPtr, gas: Option, ) -> Result { - fn reverted(logger: &Logger, reason: &str) -> Result { - info!(logger, "Contract call reverted"; "reason" => reason); - Ok(call::Retval::Null) - } - - let web3 = self.web3.clone(); + let alloy = self.alloy.clone(); let logger = Logger::new(&logger, o!("provider" => self.provider.clone())); - let block_id = self.block_ptr_to_id(&block_ptr); + let alloy_block_id = self.block_ptr_to_id(&block_ptr); let retry_log_message = format!("eth_call RPC call for block {}", block_ptr); retry(retry_log_message, &logger) .redact_log_urls(true) @@ -585,138 +660,29 @@ impl EthereumAdapter { .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { let call_data = call_data.clone(); - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); let logger = logger.cheap_clone(); async move { - let req = CallRequest { - to: Some(call_data.address), - gas: gas.map(|val| web3::types::U256::from(val)), - data: Some(Bytes::from(call_data.encoded_call.to_vec())), - from: None, - gas_price: None, - value: None, - access_list: None, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - transaction_type: None, - }; - let result = web3.eth().call(req, Some(block_id)).boxed().await; - - // Try to check if the call was reverted. The JSON-RPC response for reverts is - // not standardized, so we have ad-hoc checks for each Ethereum client. - - // 0xfe is the "designated bad instruction" of the EVM, and Solidity uses it for - // asserts. - const PARITY_BAD_INSTRUCTION_FE: &str = "Bad instruction fe"; - - // 0xfd is REVERT, but on some contracts, and only on older blocks, - // this happens. Makes sense to consider it a revert as well. - const PARITY_BAD_INSTRUCTION_FD: &str = "Bad instruction fd"; - - const PARITY_BAD_JUMP_PREFIX: &str = "Bad jump"; - const PARITY_STACK_LIMIT_PREFIX: &str = "Out of stack"; - - // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61. - const PARITY_OUT_OF_GAS: &str = "Out of gas"; - - // Also covers Nethermind reverts - const PARITY_VM_EXECUTION_ERROR: i64 = -32015; - const PARITY_REVERT_PREFIX: &str = "revert"; - - const XDAI_REVERT: &str = "revert"; - - // Deterministic Geth execution errors. We might need to expand this as - // subgraphs come across other errors. See - // https://github.com/ethereum/go-ethereum/blob/cd57d5cd38ef692de8fbedaa56598b4e9fbfbabc/core/vm/errors.go - const GETH_EXECUTION_ERRORS: &[&str] = &[ - // The "revert" substring covers a few known error messages, including: - // Hardhat: "error: transaction reverted", - // Ganache and Moonbeam: "vm exception while processing transaction: revert", - // Geth: "execution reverted" - // And others. - "revert", - "invalid jump destination", - "invalid opcode", - // Ethereum says 1024 is the stack sizes limit, so this is deterministic. - "stack limit reached 1024", - // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61 for why the gas limit is considered deterministic. - "out of gas", - "stack underflow", - ]; - - let env_geth_call_errors = ENV_VARS.geth_eth_call_errors.iter(); - let mut geth_execution_errors = GETH_EXECUTION_ERRORS - .iter() - .copied() - .chain(env_geth_call_errors.map(|s| s.as_str())); - - let as_solidity_revert_with_reason = |bytes: &[u8]| { - let solidity_revert_function_selector = - &tiny_keccak::keccak256(b"Error(string)")[..4]; - - match bytes.len() >= 4 && &bytes[..4] == solidity_revert_function_selector { - false => None, - true => ethabi::decode(&[ParamType::String], &bytes[4..]) - .ok() - .and_then(|tokens| tokens[0].clone().into_string()), - } - }; - - match result { - // A successful response. - Ok(bytes) => Ok(call::Retval::Value(scalar::Bytes::from(bytes))), - - // Check for Geth revert. - Err(web3::Error::Rpc(rpc_error)) - if geth_execution_errors - .any(|e| rpc_error.message.to_lowercase().contains(e)) => - { - reverted(&logger, &rpc_error.message) - } + let mut req = TransactionRequest::default() + .input(TransactionInput::both(alloy::primitives::Bytes::from( + call_data.encoded_call.to_vec(), + ))) + .to(call_data.address); - // Check for Parity revert. - Err(web3::Error::Rpc(ref rpc_error)) - if rpc_error.code.code() == PARITY_VM_EXECUTION_ERROR => - { - match rpc_error.data.as_ref().and_then(|d| d.as_str()) { - Some(data) - if data.to_lowercase().starts_with(PARITY_REVERT_PREFIX) - || data.starts_with(PARITY_BAD_JUMP_PREFIX) - || data.starts_with(PARITY_STACK_LIMIT_PREFIX) - || data == PARITY_BAD_INSTRUCTION_FE - || data == PARITY_BAD_INSTRUCTION_FD - || data == PARITY_OUT_OF_GAS - || data == XDAI_REVERT => - { - let reason = if data == PARITY_BAD_INSTRUCTION_FE { - PARITY_BAD_INSTRUCTION_FE.to_owned() - } else { - let payload = data.trim_start_matches(PARITY_REVERT_PREFIX); - hex::decode(payload) - .ok() - .and_then(|payload| { - as_solidity_revert_with_reason(&payload) - }) - .unwrap_or("no reason".to_owned()) - }; - reverted(&logger, &reason) - } + if let Some(gas) = gas { + req = req.gas_limit(gas as u64); + } - // The VM execution error was not identified as a revert. - _ => Err(ContractCallError::Web3Error(web3::Error::Rpc( - rpc_error.clone(), - ))), - } - } + let result = alloy.call(req.into()).block(alloy_block_id).await; - // The error was not identified as a revert. - Err(err) => Err(ContractCallError::Web3Error(err)), + match result { + Ok(bytes) => Ok(call::Retval::Value(scalar::Bytes::from(bytes))), + Err(err) => interpret_eth_call_error(&logger, err), } } }) - .map_err(|e| e.into_inner().unwrap_or(ContractCallError::Timeout)) - .boxed() .await + .map_err(|e| e.into_inner().unwrap_or(ContractCallError::Timeout)) } async fn call_and_cache( @@ -754,30 +720,46 @@ impl EthereumAdapter { fn load_blocks_rpc( &self, logger: Logger, - ids: Vec, - ) -> impl Stream, Error = Error> + Send { - let web3 = self.web3.clone(); + ids: Vec, + ) -> impl futures03::Stream, Error>> + Send { + let alloy = self.alloy.clone(); - stream::iter_ok::<_, Error>(ids.into_iter().map(move |hash| { - let web3 = web3.clone(); - retry(format!("load block {}", hash), &logger) - .redact_log_urls(true) - .limit(ENV_VARS.request_retries) - .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) - .run(move || { - Box::pin(web3.eth().block_with_txs(BlockId::Hash(hash))) - .compat() - .from_err::() - .and_then(move |block| { - block.map(Arc::new).ok_or_else(|| { - anyhow::anyhow!("Ethereum node did not find block {:?}", hash) - }) + futures03::stream::iter(ids.into_iter().map(move |hash| { + let alloy = alloy.clone(); + let logger = logger.clone(); + + async move { + retry(format!("load block {}", hash), &logger) + .redact_log_urls(true) + .limit(ENV_VARS.request_retries) + .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) + .run(move || { + let alloy = alloy.cheap_clone(); + async move { + alloy + .get_block_by_hash(hash) + .full() + .await + .map_err(Error::from) + .and_then(|block| { + block + .map(|b| Arc::new(LightEthereumBlock::new(b))) + .ok_or_else(|| { + anyhow::anyhow!( + "Ethereum node did not find block {:?}", + hash + ) + }) + }) + } + }) + .await + .map_err(|e| { + e.into_inner().unwrap_or_else(|| { + anyhow::anyhow!("Ethereum node took too long to return block {}", hash) }) - .compat() - }) - .boxed() - .compat() - .from_err() + }) + } })) .buffered(ENV_VARS.block_batch_size) } @@ -788,10 +770,10 @@ impl EthereumAdapter { logger: Logger, numbers: Vec, ) -> impl futures03::Stream, Error>> + Send { - let web3 = self.web3.clone(); + let alloy = self.alloy.clone(); futures03::stream::iter(numbers.into_iter().map(move |number| { - let web3 = web3.clone(); + let alloy = alloy.clone(); let logger = logger.clone(); async move { @@ -800,21 +782,22 @@ impl EthereumAdapter { .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.clone(); + let alloy = alloy.cheap_clone(); async move { - let block_result = web3 - .eth() - .block(BlockId::Number(Web3BlockNumber::Number(number.into()))) + let block_result = alloy + .get_block_by_number(alloy::rpc::types::BlockNumberOrTag::Number( + number as u64, + )) .await; match block_result { Ok(Some(block)) => { let ptr = ExtendedBlockPtr::try_from(( - block.hash, - block.number, - block.parent_hash, - block.timestamp, + block.header.hash, + block.header.number as i32, + block.header.parent_hash, + block.header.timestamp, )) .map_err(|e| { anyhow::anyhow!("Failed to convert block: {}", e) @@ -849,22 +832,22 @@ impl EthereumAdapter { logger: Logger, block_nums: Vec, ) -> impl Stream + Send { - let web3 = self.web3.clone(); + let alloy = self.alloy.clone(); stream::iter_ok::<_, Error>(block_nums.into_iter().map(move |block_num| { - let web3 = web3.clone(); + let alloy = alloy.clone(); retry(format!("load block ptr {}", block_num), &logger) .redact_log_urls(true) .when(|res| !res.is_ok() && !detect_null_block(res)) .no_limit() .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.clone(); + let alloy = alloy.cheap_clone(); async move { - let block = web3 - .eth() - .block(BlockId::Number(Web3BlockNumber::Number(block_num.into()))) - .boxed() + let block = alloy + .get_block_by_number(alloy::rpc::types::BlockNumberOrTag::Number( + block_num as u64, + )) .await?; block.ok_or_else(|| { @@ -885,7 +868,7 @@ impl EthereumAdapter { })) .buffered(ENV_VARS.block_batch_size) .filter_map(|b| b) - .map(|b| b.into()) + .map(|b| BlockPtr::from((b.header.hash, b.header.number as i32))) } /// Check if `block_ptr` refers to a block that is on the main chain, according to the Ethereum @@ -918,7 +901,7 @@ impl EthereumAdapter { from: BlockNumber, to: BlockNumber, log_filter: EthereumLogFilter, - ) -> DynTryFuture<'static, Vec, Error> { + ) -> DynTryFuture<'static, Vec, Error> { let eth: Self = self.cheap_clone(); let logger = logger.clone(); @@ -952,13 +935,13 @@ impl EthereumAdapter { wildcard_signatures, } = call_filter; - let mut addresses: Vec = contract_addresses_function_signatures + let mut addresses: Vec
= contract_addresses_function_signatures .iter() .filter(|(_addr, (start_block, _fsigs))| start_block <= &to) .map(|(addr, (_start_block, _fsigs))| *addr) - .collect::>() + .collect::>() .into_iter() - .collect::>(); + .collect::>(); if addresses.is_empty() && wildcard_signatures.is_empty() { // The filter has no started data sources in the requested range, nothing to do. @@ -975,14 +958,13 @@ impl EthereumAdapter { Box::new( eth.trace_stream(logger, subgraph_metrics, from, to, addresses) - .filter_map(|trace| EthereumCall::try_from_trace(&trace)) - .filter(move |call| { - // `trace_filter` can only filter by calls `to` an address and - // a block range. Since subgraphs are subscribing to calls - // for a specific contract function an additional filter needs - // to be applied - call_filter.matches(call) - }), + .try_filter_map(move |trace| { + let maybe_call = EthereumCall::try_from_trace(&trace) + .filter(|call| call_filter.matches(call)); + futures03::future::ready(Ok(maybe_call)) + }) + .boxed() + .compat(), ) } @@ -1061,11 +1043,11 @@ impl EthereumAdapter { logger: &Logger, subgraph_metrics: Arc, block_number: BlockNumber, - block_hash: H256, + block_hash: alloy::primitives::B256, ) -> Result, Error> { let eth = self.clone(); let addresses = Vec::new(); - let traces = eth + let traces: Vec = eth .trace_stream( logger, subgraph_metrics.clone(), @@ -1073,8 +1055,7 @@ impl EthereumAdapter { block_number, addresses, ) - .collect() - .compat() + .try_collect() .await?; // `trace_stream` returns all of the traces for the block, and this @@ -1092,7 +1073,7 @@ impl EthereumAdapter { // all the traces for the block, we need to ensure that the // block hash for the traces is equal to the desired block hash. // Assume all traces are for the same block. - if traces.iter().nth(0).unwrap().block_hash != block_hash { + if traces.iter().nth(0).unwrap().block_hash != Some(block_hash) { return Err(anyhow!( "Trace stream returned traces for an unexpected block: \ number = `{}`, hash = `{}`", @@ -1136,19 +1117,17 @@ impl EthereumAdapter { pub async fn chain_id(&self) -> Result { let logger = self.logger.clone(); - let web3 = self.web3.clone(); - u64::try_from( - retry("chain_id RPC call", &logger) - .redact_log_urls(true) - .no_limit() - .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) - .run(move || { - let web3 = web3.cheap_clone(); - async move { web3.eth().chain_id().await } - }) - .await?, - ) - .map_err(Error::msg) + let alloy = self.alloy.clone(); + retry("chain_id RPC call", &logger) + .redact_log_urls(true) + .no_limit() + .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) + .run(move || { + let alloy = alloy.cheap_clone(); + async move { alloy.get_chain_id().await.map_err(Error::from) } + }) + .await + .map_err(|e| e.into_inner().unwrap_or(EthereumRpcError::Timeout.into())) } } @@ -1171,7 +1150,7 @@ impl EthereumAdapterTrait for EthereumAdapter { async fn net_identifiers(&self) -> Result { let logger = self.logger.clone(); - let web3 = self.web3.clone(); + let alloy = self.alloy.clone(); let metrics = self.metrics.clone(); let provider = self.provider().to_string(); let net_version_future = retry("net_version RPC call", &logger) @@ -1179,11 +1158,11 @@ impl EthereumAdapterTrait for EthereumAdapter { .no_limit() .timeout_secs(20) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); let metrics = metrics.cheap_clone(); let provider = provider.clone(); async move { - web3.net().version().await.map_err(|e| { + alloy.get_net_version().await.map_err(|e| { metrics.set_status(ProviderStatus::VersionFail, &provider); e.into() }) @@ -1196,7 +1175,7 @@ impl EthereumAdapterTrait for EthereumAdapter { }) .boxed(); - let web3 = self.web3.clone(); + let alloy_provider = self.alloy.clone(); let metrics = self.metrics.clone(); let provider = self.provider().to_string(); let retry_log_message = format!( @@ -1208,20 +1187,20 @@ impl EthereumAdapterTrait for EthereumAdapter { .no_limit() .timeout_secs(30) .run(move || { - let web3 = web3.cheap_clone(); + let alloy_genesis = alloy_provider.cheap_clone(); let metrics = metrics.cheap_clone(); let provider = provider.clone(); async move { - web3.eth() - .block(BlockId::Number(Web3BlockNumber::Number( - ENV_VARS.genesis_block_number.into(), - ))) + alloy_genesis + .get_block_by_number(alloy::rpc::types::BlockNumberOrTag::Number( + ENV_VARS.genesis_block_number as u64, + )) .await .map_err(|e| { metrics.set_status(ProviderStatus::GenesisFail, &provider); e })? - .and_then(|gen_block| gen_block.hash.map(BlockHash::from)) + .map(|gen_block| BlockHash::from(gen_block.header.hash)) .ok_or_else(|| anyhow!("Ethereum node could not find genesis block")) } }) @@ -1240,7 +1219,7 @@ impl EthereumAdapterTrait for EthereumAdapter { })?; let ident = ChainIdentifier { - net_version, + net_version: net_version.to_string(), genesis_block_hash, }; @@ -1249,52 +1228,27 @@ impl EthereumAdapterTrait for EthereumAdapter { Ok(ident) } - async fn latest_block_header( - &self, - logger: &Logger, - ) -> Result, IngestorError> { - let web3 = self.web3.clone(); + async fn latest_block_ptr(&self, logger: &Logger) -> Result { + let alloy = self.alloy.clone(); retry("eth_getBlockByNumber(latest) no txs RPC call", logger) .redact_log_urls(true) .no_limit() .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); async move { - let block_opt = web3 - .eth() - .block(Web3BlockNumber::Latest.into()) + let block_opt = alloy + .get_block_by_number(alloy::rpc::types::BlockNumberOrTag::Latest) .await .map_err(|e| anyhow!("could not get latest block from Ethereum: {}", e))?; - block_opt - .ok_or_else(|| anyhow!("no latest block returned from Ethereum").into()) - } - }) - .map_err(move |e| { - e.into_inner().unwrap_or_else(move || { - anyhow!("Ethereum node took too long to return latest block").into() - }) - }) - .await - } + let block = block_opt + .ok_or_else(|| anyhow!("no latest block returned from Ethereum"))?; - async fn latest_block(&self, logger: &Logger) -> Result { - let web3 = self.web3.clone(); - retry("eth_getBlockByNumber(latest) with txs RPC call", logger) - .redact_log_urls(true) - .no_limit() - .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) - .run(move || { - let web3 = web3.cheap_clone(); - async move { - let block_opt = web3 - .eth() - .block_with_txs(Web3BlockNumber::Latest.into()) - .await - .map_err(|e| anyhow!("could not get latest block from Ethereum: {}", e))?; - block_opt - .ok_or_else(|| anyhow!("no latest block returned from Ethereum").into()) + Ok(BlockPtr::from(( + block.header.hash, + block.header.number as i32, + ))) } }) .map_err(move |e| { @@ -1305,27 +1259,12 @@ impl EthereumAdapterTrait for EthereumAdapter { .await } - async fn load_block( - &self, - logger: &Logger, - block_hash: H256, - ) -> Result { - self.block_by_hash(logger, block_hash) - .await? - .ok_or_else(move || { - anyhow!( - "Ethereum node could not find block with hash {}", - block_hash - ) - }) - } - async fn block_by_hash( &self, logger: &Logger, - block_hash: H256, - ) -> Result, Error> { - let web3 = self.web3.clone(); + block_hash: B256, + ) -> Result, Error> { + let alloy = self.alloy.clone(); let logger = logger.clone(); let retry_log_message = format!( "eth_getBlockByHash RPC call for block hash {:?}", @@ -1337,10 +1276,11 @@ impl EthereumAdapterTrait for EthereumAdapter { .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); async move { - web3.eth() - .block_with_txs(BlockId::Hash(block_hash)) + alloy + .get_block_by_hash(block_hash) + .full() .await .map_err(Error::from) } @@ -1357,8 +1297,8 @@ impl EthereumAdapterTrait for EthereumAdapter { &self, logger: &Logger, block_number: BlockNumber, - ) -> Result, Error> { - let web3 = self.web3.clone(); + ) -> Result, Error> { + let alloy = self.alloy.clone(); let logger = logger.clone(); let retry_log_message = format!( "eth_getBlockByNumber RPC call for block number {}", @@ -1369,10 +1309,13 @@ impl EthereumAdapterTrait for EthereumAdapter { .no_limit() .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.clone(); async move { - web3.eth() - .block_with_txs(BlockId::Number(block_number.into())) + alloy + .get_block_by_number(alloy::rpc::types::BlockNumberOrTag::Number( + block_number as u64, + )) + .full() .await .map_err(Error::from) } @@ -1391,26 +1334,26 @@ impl EthereumAdapterTrait for EthereumAdapter { async fn load_full_block( &self, logger: &Logger, - block: LightEthereumBlock, + block: AnyBlock, ) -> Result { - let web3 = Arc::clone(&self.web3); + let alloy = self.alloy.clone(); let logger = logger.clone(); - let block_hash = block.hash.expect("block is missing block hash"); + let block_hash = block.header.hash; // The early return is necessary for correctness, otherwise we'll // request an empty batch which is not valid in JSON-RPC. if block.transactions.is_empty() { trace!(logger, "Block {} contains no transactions", block_hash); return Ok(EthereumBlock { - block: Arc::new(block), + block: Arc::new(LightEthereumBlock::new(block)), transaction_receipts: Vec::new(), }); } - let hashes: Vec<_> = block.transactions.iter().map(|txn| txn.hash).collect(); + let hashes: Vec<_> = block.transactions.hashes().collect(); let supports_block_receipts = self .check_block_receipt_support_and_update_cache( - web3.clone(), + alloy.clone(), block_hash, self.supports_eip_1898, self.call_only, @@ -1418,55 +1361,23 @@ impl EthereumAdapterTrait for EthereumAdapter { ) .await; - fetch_receipts_with_retry(web3, hashes, block_hash, logger, supports_block_receipts) + fetch_receipts_with_retry(alloy, hashes, block_hash, logger, supports_block_receipts) .await .map(|transaction_receipts| EthereumBlock { - block: Arc::new(block), - transaction_receipts, - }) - } - - async fn block_hash_by_block_number( - &self, - logger: &Logger, - block_number: BlockNumber, - ) -> Result, Error> { - let web3 = self.web3.clone(); - let retry_log_message = format!( - "eth_getBlockByNumber RPC call for block number {}", - block_number - ); - retry(retry_log_message, logger) - .redact_log_urls(true) - .no_limit() - .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) - .run(move || { - let web3 = web3.cheap_clone(); - async move { - web3.eth() - .block(BlockId::Number(block_number.into())) - .await - .map(|block_opt| block_opt.and_then(|block| block.hash)) - .map_err(Error::from) - } - }) - .await - .map_err(move |e| { - e.into_inner().unwrap_or_else(move || { - anyhow!( - "Ethereum node took too long to return data for block #{}", - block_number - ) - }) + block: Arc::new(LightEthereumBlock::new(block)), + transaction_receipts: transaction_receipts + .into_iter() + .map(|receipt| receipt) + .collect(), }) } async fn get_balance( &self, logger: &Logger, - address: H160, + address: Address, block_ptr: BlockPtr, - ) -> Result { + ) -> Result { debug!( logger, "eth_getBalance"; "address" => format!("{}", address), @@ -1478,9 +1389,9 @@ impl EthereumAdapterTrait for EthereumAdapter { async fn get_code( &self, logger: &Logger, - address: H160, + address: Address, block_ptr: BlockPtr, - ) -> Result { + ) -> Result { debug!( logger, "eth_getCode"; "address" => format!("{}", address), @@ -1500,7 +1411,7 @@ impl EthereumAdapterTrait for EthereumAdapter { "eth_getBlockByNumber RPC call for block number {}", next_number ); - let web3 = self.web3.clone(); + let alloy = self.alloy.clone(); let logger = logger.clone(); let res = retry(retry_log_message, &logger) .redact_log_urls(true) @@ -1508,12 +1419,16 @@ impl EthereumAdapterTrait for EthereumAdapter { .no_limit() .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); async move { - web3.eth() - .block(BlockId::Number(next_number.into())) + alloy + .get_block_by_number(alloy::rpc::types::BlockNumberOrTag::Number( + next_number as u64, + )) .await - .map(|block_opt| block_opt.and_then(|block| block.hash)) + .map(|block_opt| { + block_opt.map(|block| BlockHash::from(block.header.hash.0.to_vec())) + }) .map_err(Error::from) } }) @@ -1531,7 +1446,7 @@ impl EthereumAdapterTrait for EthereumAdapter { continue; } return match res { - Ok(Some(hash)) => Ok(BlockPtr::new(hash.into(), next_number)), + Ok(Some(hash)) => Ok(BlockPtr::new(hash, next_number)), Ok(None) => Err(anyhow!("Block {} does not contain hash", next_number)), Err(e) => Err(e), }; @@ -1543,7 +1458,7 @@ impl EthereumAdapterTrait for EthereumAdapter { logger: &Logger, inp_call: &ContractCall, cache: Arc, - ) -> Result<(Option>, call::Source), ContractCallError> { + ) -> Result<(Option>, call::Source), ContractCallError> { let mut result = self.contract_calls(logger, &[inp_call], cache).await?; // unwrap: self.contract_calls returns as many results as there were calls Ok(result.pop().unwrap()) @@ -1554,20 +1469,26 @@ impl EthereumAdapterTrait for EthereumAdapter { logger: &Logger, calls: &[&ContractCall], cache: Arc, - ) -> Result>, call::Source)>, ContractCallError> { + ) -> Result>, call::Source)>, ContractCallError> { fn as_req( logger: &Logger, call: &ContractCall, index: u32, ) -> Result { // Emit custom error for type mismatches. - for (token, kind) in call + for (val, kind) in call .args .iter() - .zip(call.function.inputs.iter().map(|p| &p.kind)) + .zip(call.function.inputs.iter().map(|p| p.selector_type())) { - if !token.type_check(kind) { - return Err(ContractCallError::TypeError(token.clone(), kind.clone())); + let kind: abi::DynSolType = kind.parse().map_err(|err| { + ContractCallError::ABIError(anyhow!( + "failed to parse function input type '{kind}': {err}" + )) + })?; + + if !val.type_check(&kind) { + return Err(ContractCallError::TypeError(val.clone(), kind.clone())); } } @@ -1575,8 +1496,8 @@ impl EthereumAdapterTrait for EthereumAdapter { let req = { let encoded_call = call .function - .encode_input(&call.args) - .map_err(ContractCallError::EncodingError)?; + .abi_encode_input(&call.args) + .map_err(|err| ContractCallError::EncodingError(err.into()))?; call::Request::new(call.address, encoded_call, index) }; @@ -1594,15 +1515,14 @@ impl EthereumAdapterTrait for EthereumAdapter { logger: &Logger, resp: call::Response, call: &ContractCall, - ) -> (Option>, call::Source) { + ) -> (Option>, call::Source) { let call::Response { retval, source, req: _, } = resp; - use call::Retval::*; match retval { - Value(output) => match call.function.decode_output(&output) { + call::Retval::Value(output) => match call.function.abi_decode_output(&output) { Ok(tokens) => (Some(tokens), source), Err(e) => { // Decode failures are reverts. The reasoning is that if Solidity fails to @@ -1612,7 +1532,7 @@ impl EthereumAdapterTrait for EthereumAdapter { (None, call::Source::Rpc) } }, - Null => { + call::Retval::Null => { // We got a `0x` response. For old Geth, this can mean a revert. It can also be // that the contract actually returned an empty response. A view call is meant // to return something, so we treat empty responses the same as reverts. @@ -1624,7 +1544,7 @@ impl EthereumAdapterTrait for EthereumAdapter { fn log_call_error(logger: &Logger, e: &ContractCallError, call: &ContractCall) { match e { - ContractCallError::Web3Error(e) => error!(logger, + ContractCallError::AlloyError(e) => error!(logger, "Ethereum node returned an error when calling function \"{}\" of contract \"{}\": {}", call.function.name, call.contract_name, e), ContractCallError::Timeout => error!(logger, @@ -1696,11 +1616,11 @@ impl EthereumAdapterTrait for EthereumAdapter { &self, logger: Logger, chain_store: Arc, - block_hashes: HashSet, + block_hashes: HashSet, ) -> Result>, Error> { let block_hashes: Vec<_> = block_hashes.iter().cloned().collect(); // Search for the block in the store first then use json-rpc as a backup. - let mut blocks: Vec> = chain_store + let mut blocks: Vec<_> = chain_store .cheap_clone() .blocks(block_hashes.iter().map(|&b| b.into()).collect::>()) .await @@ -1708,21 +1628,20 @@ impl EthereumAdapterTrait for EthereumAdapter { .unwrap_or_default() .into_iter() .filter_map(|value| json::from_value(value).ok()) - .map(Arc::new) + .map(|b| Arc::new(LightEthereumBlock::new(b))) .collect(); let missing_blocks = Vec::from_iter( block_hashes .into_iter() - .filter(|hash| !blocks.iter().any(|b| b.hash == Some(*hash))), + .filter(|hash| !blocks.iter().any(|b| b.hash() == *hash)), ); // Return a stream that lazily loads batches of blocks. debug!(logger, "Requesting {} block(s)", missing_blocks.len()); - let new_blocks = self + let new_blocks: Vec<_> = self .load_blocks_rpc(logger.clone(), missing_blocks) - .collect() - .compat() + .try_collect() .await?; let upsert_blocks: Vec<_> = new_blocks .iter() @@ -1736,7 +1655,7 @@ impl EthereumAdapterTrait for EthereumAdapter { error!(logger, "Error writing to block cache {}", e); } blocks.extend(new_blocks); - blocks.sort_by_key(|block| block.number); + blocks.sort_by_key(|block| block.number()); Ok(blocks) } } @@ -1779,7 +1698,7 @@ pub(crate) async fn blocks_with_triggers( debug!(logger, "Finding nearest valid `to` block to {}", to); let to_ptr = eth.next_existing_ptr_to_number(&logger, to).await?; - let to_hash = to_ptr.hash_as_h256(); + let to_hash = to_ptr.hash.as_b256(); let to = to_ptr.block_number(); // This is for `start` triggers which can be initialization handlers which needs to be run @@ -1856,8 +1775,10 @@ pub(crate) async fn blocks_with_triggers( .await .with_context(|| format!("Failed to obtain triggers for block {}", to))?; - let mut block_hashes: HashSet = - triggers.iter().map(EthereumTrigger::block_hash).collect(); + let mut block_hashes: HashSet = triggers + .iter() + .map(|trigger| trigger.block_hash()) + .collect(); let mut triggers_by_block: HashMap> = triggers.into_iter().fold(HashMap::new(), |mut map, t| { map.entry(t.block_number()).or_default().push(t); @@ -1877,7 +1798,7 @@ pub(crate) async fn blocks_with_triggers( .await? .into_iter() .map( - move |block| match triggers_by_block.remove(&(block.number() as BlockNumber)) { + move |block| match triggers_by_block.remove(&(block.number())) { Some(triggers) => Ok(BlockWithTriggers::new( BlockFinality::Final(block), triggers, @@ -1952,9 +1873,8 @@ pub(crate) async fn get_calls( .calls_in_block( &logger, subgraph_metrics.clone(), - BlockNumber::try_from(ethereum_block.block.number.unwrap().as_u64()) - .unwrap(), - ethereum_block.block.hash.unwrap(), + ethereum_block.block.number(), + ethereum_block.block.hash(), ) .await? }; @@ -1981,15 +1901,15 @@ pub(crate) fn parse_log_triggers( .transaction_receipts .iter() .flat_map(move |receipt| { - receipt.logs.iter().enumerate().map(move |(index, log)| { + receipt.logs().iter().enumerate().map(move |(index, log)| { let requires_transaction_receipt = log - .topics + .topics() .first() .map(|signature| { log_filter.requires_transaction_receipt( signature, - Some(&log.address), - &log.topics, + Some(&log.address()), + &log.topics(), ) }) .unwrap_or(false); @@ -2040,7 +1960,7 @@ pub(crate) fn parse_block_triggers( return vec![]; } - let block_ptr = BlockPtr::from(&block.ethereum_block); + let block_ptr = block.ethereum_block.block.block_ptr(); let trigger_every_block = block_filter.trigger_every_block; let call_filter = EthereumCallFilter::from(block_filter); let block_ptr2 = block_ptr.cheap_clone(); @@ -2110,9 +2030,9 @@ pub(crate) fn parse_block_triggers( async fn fetch_receipt_from_ethereum_client( eth: &EthereumAdapter, - transaction_hash: &H256, -) -> anyhow::Result { - match eth.web3.eth().transaction_receipt(*transaction_hash).await { + transaction_hash: B256, +) -> anyhow::Result { + match eth.alloy.get_transaction_receipt(transaction_hash).await { Ok(Some(receipt)) => Ok(receipt), Ok(None) => bail!("Could not find transaction receipt"), Err(error) => bail!("Failed to fetch transaction receipt: {}", error), @@ -2133,14 +2053,14 @@ async fn filter_call_triggers_from_unsuccessful_transactions( let initial_number_of_triggers = block.trigger_data.len(); // Get the transaction hash from each call trigger - let transaction_hashes: BTreeSet = block + let transaction_hashes: BTreeSet = block .trigger_data .iter() .filter_map(|trigger| match trigger.as_chain() { Some(EthereumTrigger::Call(call_trigger)) => Some(call_trigger.transaction_hash), _ => None, }) - .collect::>>() + .collect::>>() .ok_or(anyhow!( "failed to obtain transaction hash from call triggers" ))?; @@ -2151,12 +2071,13 @@ async fn filter_call_triggers_from_unsuccessful_transactions( } // And obtain all Transaction values for the calls in this block. - let transactions: Vec<&Transaction> = { + let transactions: Vec<&AnyTransaction> = { match &block.block { BlockFinality::Final(ref block) => block - .transactions + .transactions() + .ok_or_else(|| anyhow!("Block transactions not available"))? .iter() - .filter(|transaction| transaction_hashes.contains(&transaction.hash)) + .filter(|transaction| transaction_hashes.contains(&transaction.tx_hash())) .collect(), BlockFinality::NonFinal(_block_with_calls) => { unreachable!( @@ -2178,21 +2099,21 @@ async fn filter_call_triggers_from_unsuccessful_transactions( // We'll also need the receipts for those transactions. In this step we collect all receipts // we have in store for the current block. - let mut receipts = chain_store - .transaction_receipts_in_block(&block.ptr().hash_as_h256()) + let mut receipts: BTreeMap = chain_store + .transaction_receipts_in_block(&block.ptr().hash.as_b256()) .await? .into_iter() .map(|receipt| (receipt.transaction_hash, receipt)) - .collect::>(); + .collect::>(); // Do we have a receipt for each transaction under analysis? - let mut receipts_and_transactions: Vec<(&Transaction, LightTransactionReceipt)> = Vec::new(); - let mut transactions_without_receipt: Vec<&Transaction> = Vec::new(); + let mut receipts_and_transactions: Vec<(&AnyTransaction, LightTransactionReceipt)> = Vec::new(); + let mut transactions_without_receipt: Vec<&AnyTransaction> = Vec::new(); for transaction in transactions.iter() { - if let Some(receipt) = receipts.remove(&transaction.hash) { - receipts_and_transactions.push((transaction, receipt)); + if let Some(receipt) = receipts.remove(&transaction.tx_hash()) { + receipts_and_transactions.push((*transaction, receipt)); } else { - transactions_without_receipt.push(transaction); + transactions_without_receipt.push(*transaction); } } @@ -2200,7 +2121,7 @@ async fn filter_call_triggers_from_unsuccessful_transactions( let futures = transactions_without_receipt .iter() .map(|transaction| async move { - fetch_receipt_from_ethereum_client(eth, &transaction.hash) + fetch_receipt_from_ethereum_client(eth, transaction.tx_hash()) .await .map(|receipt| (transaction, receipt)) }); @@ -2215,12 +2136,9 @@ async fn filter_call_triggers_from_unsuccessful_transactions( // additional Ethereum API calls for future scans on this block. // With all transactions and receipts in hand, we can evaluate the success of each transaction - let mut transaction_success: BTreeMap<&H256, bool> = BTreeMap::new(); + let mut transaction_success: BTreeMap = BTreeMap::new(); for (transaction, receipt) in receipts_and_transactions.into_iter() { - transaction_success.insert( - &transaction.hash, - evaluate_transaction_status(receipt.status), - ); + transaction_success.insert(transaction.tx_hash(), receipt.status); } // Confidence check: Did we inspect the status of all transactions? @@ -2262,11 +2180,11 @@ async fn filter_call_triggers_from_unsuccessful_transactions( /// Deprecated. Wraps the [`fetch_transaction_receipts_in_batch`] in a retry loop. async fn fetch_transaction_receipts_in_batch_with_retry( - web3: Arc>, - hashes: Vec, - block_hash: H256, + alloy: Arc, + hashes: Vec, + block_hash: B256, logger: Logger, -) -> Result>, IngestorError> { +) -> Result>, IngestorError> { let retry_log_message = format!( "batch eth_getTransactionReceipt RPC call for block {:?}", block_hash @@ -2277,51 +2195,84 @@ async fn fetch_transaction_receipts_in_batch_with_retry( .no_logging() .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) .run(move || { - let web3 = web3.cheap_clone(); + let alloy = alloy.cheap_clone(); let hashes = hashes.clone(); let logger = logger.cheap_clone(); - fetch_transaction_receipts_in_batch(web3, hashes, block_hash, logger).boxed() + fetch_transaction_receipts_in_batch(alloy, hashes, block_hash, logger).boxed() }) .await .map_err(|_timeout| anyhow!(block_hash).into()) } -/// Deprecated. Attempts to fetch multiple transaction receipts in a batching contex. +/// Deprecated. Attempts to fetch multiple transaction receipts in a batching context. async fn fetch_transaction_receipts_in_batch( - web3: Arc>, - hashes: Vec, - block_hash: H256, + alloy: Arc, + hashes: Vec, + block_hash: B256, logger: Logger, -) -> Result>, IngestorError> { - let batching_web3 = Web3::new(Batch::new(web3.transport().clone())); - let eth = batching_web3.eth(); - let receipt_futures = hashes - .into_iter() - .map(move |hash| { - let logger = logger.cheap_clone(); - eth.transaction_receipt(hash) - .map_err(IngestorError::from) - .and_then(move |some_receipt| async move { - resolve_transaction_receipt(some_receipt, hash, block_hash, logger) - }) - }) - .collect::>(); +) -> Result>, IngestorError> { + // Use the batch method to get all receipts at once + let receipts = batch_get_transaction_receipts(alloy, hashes.clone()) + .await + .map_err(|e| { + IngestorError::Unknown(anyhow::anyhow!("Batch receipt fetch failed: {}", e)) + })?; + + let mut result = Vec::new(); + for (receipt, hash) in receipts.into_iter().zip(hashes.iter()) { + if let Some(receipt) = receipt { + let validated_receipt = resolve_transaction_receipt( + Some(receipt), + *hash, + block_hash, + logger.cheap_clone(), + )?; + result.push(Arc::new(validated_receipt)); + } else { + return Err(IngestorError::ReceiptUnavailable(block_hash, *hash)); + } + } - batching_web3.transport().submit_batch().await?; + Ok(result) +} + +async fn batch_get_transaction_receipts( + provider: Arc, + tx_hashes: Vec, +) -> Result>, Box> { + let mut batch = alloy::rpc::client::BatchRequest::new(provider.client()); + let mut receipt_futures = Vec::new(); + + // Add all receipt requests to batch + for tx_hash in &tx_hashes { + let receipt_future = batch + .add_call::<(B256,), Option>( + "eth_getTransactionReceipt", + &(*tx_hash,), + )?; + receipt_futures.push(receipt_future); + } - let mut collected = vec![]; - for receipt in receipt_futures.into_iter() { - collected.push(Arc::new(receipt.await?)) + // Execute batch + batch.send().await?; + + // Collect results in order + let mut results = Vec::new(); + for receipt_future in receipt_futures { + let receipt = receipt_future.await?; + results.push(receipt); } - Ok(collected) + + Ok(results) } pub(crate) async fn check_block_receipt_support( - web3: Arc>, - block_hash: H256, + alloy: Arc, + block_hash: B256, supports_eip_1898: bool, call_only: bool, ) -> Result<(), Error> { + use alloy::rpc::types::BlockId; if call_only { return Err(anyhow!("Provider is call-only")); } @@ -2331,7 +2282,7 @@ pub(crate) async fn check_block_receipt_support( } // Fetch block receipts from the provider for the latest block. - let block_receipts_result = web3.eth().block_receipts(BlockId::Hash(block_hash)).await; + let block_receipts_result = alloy.get_block_receipts(BlockId::from(block_hash)).await; // Determine if the provider supports block receipts based on the fetched result. match block_receipts_result { @@ -2345,27 +2296,27 @@ pub(crate) async fn check_block_receipt_support( // based on whether block receipts are supported or individual transaction receipts // need to be fetched. async fn fetch_receipts_with_retry( - web3: Arc>, - hashes: Vec, - block_hash: H256, + alloy: Arc, + hashes: Vec, + block_hash: B256, logger: Logger, supports_block_receipts: bool, -) -> Result>, IngestorError> { +) -> Result>, IngestorError> { if supports_block_receipts { - return fetch_block_receipts_with_retry(web3, hashes, block_hash, logger).await; + return fetch_block_receipts_with_retry(alloy, hashes, block_hash, logger).await; } - fetch_individual_receipts_with_retry(web3, hashes, block_hash, logger).await + fetch_individual_receipts_with_retry(alloy, hashes, block_hash, logger).await } // Fetches receipts for each transaction in the block individually. async fn fetch_individual_receipts_with_retry( - web3: Arc>, - hashes: Vec, - block_hash: H256, + alloy: Arc, + hashes: Vec, + block_hash: B256, logger: Logger, -) -> Result>, IngestorError> { +) -> Result>, IngestorError> { if ENV_VARS.fetch_receipts_in_batches { - return fetch_transaction_receipts_in_batch_with_retry(web3, hashes, block_hash, logger) + return fetch_transaction_receipts_in_batch_with_retry(alloy, hashes, block_hash, logger) .await; } @@ -2374,7 +2325,7 @@ async fn fetch_individual_receipts_with_retry( let receipt_stream = hash_stream .map(move |tx_hash| { fetch_transaction_receipt_with_retry( - web3.cheap_clone(), + alloy.cheap_clone(), tx_hash, block_hash, logger.cheap_clone(), @@ -2382,19 +2333,20 @@ async fn fetch_individual_receipts_with_retry( }) .buffered(ENV_VARS.block_ingestor_max_concurrent_json_rpc_calls); - tokio_stream::StreamExt::collect::>, IngestorError>>( - receipt_stream, - ) + tokio_stream::StreamExt::collect::< + Result>, IngestorError>, + >(receipt_stream) .await } /// Fetches transaction receipts of all transactions in a block with `eth_getBlockReceipts` call. async fn fetch_block_receipts_with_retry( - web3: Arc>, - hashes: Vec, - block_hash: H256, + alloy: Arc, + hashes: Vec, + block_hash: B256, logger: Logger, -) -> Result>, IngestorError> { +) -> Result>, IngestorError> { + use graph::prelude::alloy::rpc::types::BlockId; let logger = logger.cheap_clone(); let retry_log_message = format!("eth_getBlockReceipts RPC call for block {:?}", block_hash); @@ -2403,7 +2355,7 @@ async fn fetch_block_receipts_with_retry( .redact_log_urls(true) .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) - .run(move || web3.eth().block_receipts(BlockId::Hash(block_hash)).boxed()) + .run(move || alloy.get_block_receipts(BlockId::from(block_hash)).boxed()) .await .map_err(|_timeout| -> IngestorError { anyhow!(block_hash).into() })?; @@ -2432,23 +2384,27 @@ async fn fetch_block_receipts_with_retry( } } -/// Retries fetching a single transaction receipt. +/// Retries fetching a single transaction receipt using alloy, then converts to web3 format. async fn fetch_transaction_receipt_with_retry( - web3: Arc>, - transaction_hash: H256, - block_hash: H256, + alloy: Arc, + transaction_hash: B256, + block_hash: B256, logger: Logger, -) -> Result, IngestorError> { +) -> Result, IngestorError> { let logger = logger.cheap_clone(); let retry_log_message = format!( "eth_getTransactionReceipt RPC call for transaction {:?}", transaction_hash ); + retry(retry_log_message, &logger) .redact_log_urls(true) .limit(ENV_VARS.request_retries) .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) - .run(move || web3.eth().transaction_receipt(transaction_hash).boxed()) + .run(move || { + let alloy_clone = alloy.clone(); + async move { alloy_clone.get_transaction_receipt(transaction_hash).await }.boxed() + }) .await .map_err(|_timeout| anyhow!(block_hash).into()) .and_then(move |some_receipt| { @@ -2458,11 +2414,11 @@ async fn fetch_transaction_receipt_with_retry( } fn resolve_transaction_receipt( - transaction_receipt: Option, - transaction_hash: H256, - block_hash: H256, + transaction_receipt: Option, + transaction_hash: B256, + block_hash: B256, logger: Logger, -) -> Result { +) -> Result { match transaction_receipt { // A receipt might be missing because the block was uncled, and the transaction never // made it back into the main chain. @@ -2535,12 +2491,16 @@ async fn get_logs_and_transactions( // Not all logs have associated transaction hashes, nor do all triggers require them. // We also restrict receipts retrieval for some api versions. - let transaction_hashes_by_block: HashMap> = logs + let transaction_hashes_by_block: HashMap> = logs .iter() .filter(|_| unified_api_version.equal_or_greater_than(&API_VERSION_0_0_7)) .filter(|log| { - if let Some(signature) = log.topics.first() { - log_filter.requires_transaction_receipt(signature, Some(&log.address), &log.topics) + if let Some(signature) = log.topics().first() { + log_filter.requires_transaction_receipt( + signature, + Some(&log.address()), + &log.topics(), + ) } else { false } @@ -2555,7 +2515,7 @@ async fn get_logs_and_transactions( } }) .fold( - HashMap::>::new(), + HashMap::>::new(), |mut acc, (block_hash, txn_hash)| { acc.entry(block_hash).or_default().insert(txn_hash); acc @@ -2577,6 +2537,7 @@ async fn get_logs_and_transactions( let optional_receipt = log .transaction_hash .and_then(|txn| transaction_receipts_by_hash.get(&txn).cloned()); + let value = EthereumTrigger::Log(LogRef::FullLog(Arc::new(log), optional_receipt)); log_triggers.push(value); } @@ -2587,13 +2548,14 @@ async fn get_logs_and_transactions( /// Tries to retrive all transaction receipts for a set of transaction hashes. async fn get_transaction_receipts_for_transaction_hashes( adapter: &EthereumAdapter, - transaction_hashes_by_block: &HashMap>, + transaction_hashes_by_block: &HashMap>, subgraph_metrics: Arc, logger: Logger, -) -> Result>, anyhow::Error> { +) -> Result>, anyhow::Error> { use std::collections::hash_map::Entry::Vacant; - let mut receipts_by_hash: HashMap> = HashMap::new(); + let mut receipts_by_hash: HashMap> = + HashMap::new(); // Return early if input set is empty if transaction_hashes_by_block.is_empty() { @@ -2602,17 +2564,17 @@ async fn get_transaction_receipts_for_transaction_hashes( // Keep a record of all unique transaction hashes for which we'll request receipts. We will // later use this to check if we have collected the receipts from all required transactions. - let mut unique_transaction_hashes: HashSet<&H256> = HashSet::new(); + let mut unique_transaction_hashes: HashSet<&B256> = HashSet::new(); // Request transaction receipts concurrently let receipt_futures = FuturesUnordered::new(); - let web3 = Arc::clone(&adapter.web3); + let alloy = Arc::clone(&adapter.alloy); for (block_hash, transaction_hashes) in transaction_hashes_by_block { for transaction_hash in transaction_hashes { unique_transaction_hashes.insert(transaction_hash); let receipt_future = fetch_transaction_receipt_with_retry( - web3.cheap_clone(), + alloy.cheap_clone(), *transaction_hash, *block_hash, logger.cheap_clone(), @@ -2674,11 +2636,12 @@ mod tests { EthereumBlockWithCalls, }; use graph::blockchain::BlockPtr; - use graph::prelude::ethabi::ethereum_types::U64; - use graph::prelude::web3::transports::test::TestTransport; - use graph::prelude::web3::types::{Address, Block, Bytes, H256}; - use graph::prelude::web3::Web3; - use graph::prelude::EthereumCall; + use graph::components::ethereum::AnyBlock; + use graph::prelude::alloy::network::AnyNetwork; + use graph::prelude::alloy::primitives::{Address, Bytes, B256}; + use graph::prelude::alloy::providers::mock::Asserter; + use graph::prelude::alloy::providers::ProviderBuilder; + use graph::prelude::{create_minimal_block_for_test, EthereumCall, LightEthereumBlock}; use jsonrpc_core::serde_json::{self, Value}; use std::collections::HashSet; use std::iter::FromIterator; @@ -2686,13 +2649,11 @@ mod tests { #[test] fn parse_block_triggers_every_block() { + let block = create_minimal_block_for_test(2, hash(2)); + let block = EthereumBlockWithCalls { ethereum_block: EthereumBlock { - block: Arc::new(Block { - hash: Some(hash(2)), - number: Some(U64::from(2)), - ..Default::default() - }), + block: Arc::new(LightEthereumBlock::new(AnyBlock::from(block))), ..Default::default() }, calls: Some(vec![EthereumCall { @@ -2724,8 +2685,6 @@ mod tests { #[graph::test] async fn test_check_block_receipts_support() { - let mut transport = TestTransport::default(); - let json_receipts = r#"[{ "blockHash": "0x23f785604642e91613881fc3c9d16740ee416e340fd36f3fa2239f203d68fd33", "blockNumber": "0x12f7f81", @@ -2747,22 +2706,24 @@ mod tests { // Helper function to run a single test case async fn run_test_case( - transport: &mut TestTransport, json_response: &str, expected_err: Option<&str>, supports_eip_1898: bool, call_only: bool, ) -> Result<(), anyhow::Error> { let json_value: Value = serde_json::from_str(json_response).unwrap(); - // let block_json: Value = serde_json::from_str(block).unwrap(); - transport.set_response(json_value); - // transport.set_response(block_json); - // transport.add_response(json_value); - let web3 = Arc::new(Web3::new(transport.clone())); + let asserter = Asserter::new(); + let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + .network::() + .with_recommended_fillers() + .connect_mocked_client(asserter.clone()); + + asserter.push_success(&json_value); + let result = check_block_receipt_support( - web3.clone(), - H256::zero(), + Arc::new(provider), + B256::ZERO, supports_eip_1898, call_only, ) @@ -2787,38 +2748,25 @@ mod tests { } // Test case 1: Valid block receipts - run_test_case(&mut transport, json_receipts, None, true, false) + run_test_case(json_receipts, None, true, false) .await .unwrap(); // Test case 2: Empty block receipts - run_test_case( - &mut transport, - json_empty, - Some("Block receipts are empty"), - true, - false, - ) - .await - .unwrap(); + run_test_case(json_empty, Some("Block receipts are empty"), true, false) + .await + .unwrap(); // Test case 3: Null response - run_test_case( - &mut transport, - "null", - Some("Block receipts are empty"), - true, - false, - ) - .await - .unwrap(); + run_test_case("null", Some("Block receipts are empty"), true, false) + .await + .unwrap(); // Test case 3: Simulating an RPC error // Note: In the context of this test, we cannot directly simulate an RPC error. // Instead, we simulate a response that would cause a decoding error, such as an unexpected key("error"). // The function should handle this as an error case. run_test_case( - &mut transport, r#"{"error":"RPC Error"}"#, Some("Error fetching block receipts:"), true, @@ -2829,7 +2777,6 @@ mod tests { // Test case 5: Does not support EIP-1898 run_test_case( - &mut transport, json_receipts, Some("Provider does not support EIP 1898"), false, @@ -2839,26 +2786,19 @@ mod tests { .unwrap(); // Test case 5: Does not support Call only adapters - run_test_case( - &mut transport, - json_receipts, - Some("Provider is call-only"), - true, - true, - ) - .await - .unwrap(); + run_test_case(json_receipts, Some("Provider is call-only"), true, true) + .await + .unwrap(); } #[test] fn parse_block_triggers_specific_call_not_found() { + let block = create_minimal_block_for_test(2, hash(2)); + + #[allow(unreachable_code)] let block = EthereumBlockWithCalls { ethereum_block: EthereumBlock { - block: Arc::new(Block { - hash: Some(hash(2)), - number: Some(U64::from(2)), - ..Default::default() - }), + block: Arc::new(LightEthereumBlock::new(AnyBlock::from(block))), ..Default::default() }, calls: Some(vec![EthereumCall { @@ -2884,13 +2824,12 @@ mod tests { #[test] fn parse_block_triggers_specific_call_found() { + let block = create_minimal_block_for_test(2, hash(2)); + + #[allow(unreachable_code)] let block = EthereumBlockWithCalls { ethereum_block: EthereumBlock { - block: Arc::new(Block { - hash: Some(hash(2)), - number: Some(U64::from(2)), - ..Default::default() - }), + block: Arc::new(LightEthereumBlock::new(AnyBlock::from(block))), ..Default::default() }, calls: Some(vec![EthereumCall { @@ -2918,11 +2857,11 @@ mod tests { } fn address(id: u64) -> Address { - Address::from_low_u64_be(id) + Address::left_padding_from(&id.to_be_bytes()) } - fn hash(id: u8) -> H256 { - H256::from([id; 32]) + fn hash(id: u8) -> B256 { + B256::from_slice(&[id; 32]) } fn bytes(value: Vec) -> Bytes { diff --git a/chain/ethereum/src/ingestor.rs b/chain/ethereum/src/ingestor.rs index 47cae0b93c5..35676c8c90f 100644 --- a/chain/ethereum/src/ingestor.rs +++ b/chain/ethereum/src/ingestor.rs @@ -4,14 +4,14 @@ use async_trait::async_trait; use graph::blockchain::client::ChainClient; use graph::blockchain::BlockchainKind; use graph::components::network_provider::ChainName; +use graph::prelude::alloy::primitives::B256; use graph::slog::o; use graph::util::backoff::ExponentialBackoff; use graph::{ blockchain::{BlockHash, BlockIngestor, BlockPtr, IngestorError}, cheap_clone::CheapClone, prelude::{ - error, ethabi::ethereum_types::H256, info, tokio, trace, warn, ChainStore, Error, - EthereumBlockWithCalls, LogCode, Logger, + error, info, tokio, trace, warn, ChainStore, Error, EthereumBlockWithCalls, LogCode, Logger, }, }; use std::{sync::Arc, time::Duration}; @@ -173,8 +173,7 @@ impl PollingBlockIngestor { eth_adapter: &Arc, block_hash: &BlockHash, ) -> Result, IngestorError> { - // TODO: H256::from_slice can panic - let block_hash = H256::from_slice(block_hash.as_slice()); + let block_hash = B256::from_slice(block_hash.as_slice()); // Get the fully populated block let block = eth_adapter @@ -211,10 +210,7 @@ impl PollingBlockIngestor { logger: &Logger, eth_adapter: &Arc, ) -> Result { - eth_adapter - .latest_block_header(&logger) - .await - .map(|block| block.into()) + eth_adapter.latest_block_ptr(&logger).await } async fn eth_adapter(&self) -> anyhow::Result> { diff --git a/chain/ethereum/src/lib.rs b/chain/ethereum/src/lib.rs index fa76f70d799..8850764d63b 100644 --- a/chain/ethereum/src/lib.rs +++ b/chain/ethereum/src/lib.rs @@ -1,5 +1,6 @@ mod adapter; mod buffered_call_cache; +mod call_helper; mod capabilities; pub mod codec; mod data_source; diff --git a/chain/ethereum/src/protobuf/sf.ethereum.r#type.v2.rs b/chain/ethereum/src/protobuf/sf.ethereum.r#type.v2.rs index 4ab8d0a1324..bcb068083df 100644 --- a/chain/ethereum/src/protobuf/sf.ethereum.r#type.v2.rs +++ b/chain/ethereum/src/protobuf/sf.ethereum.r#type.v2.rs @@ -1,14 +1,60 @@ // This file is @generated by prost-build. +/// Block is the representation of the tracing of a block in the Ethereum +/// blockchain. A block is a collection of \[TransactionTrace\] that are grouped +/// together and processed as an atomic unit. Each \[TransactionTrace\] is composed +/// of a series of \[Call\] (a.k.a internal transactions) and there is also at +/// least one call per transaction a.k.a the root call which essentially has the +/// same parameters as the transaction itself (e.g. `from`, `to`, `gas`, `value`, +/// etc.). +/// +/// The exact tracing method used to build the block must be checked against +/// \[DetailLevel\] field. There is two levels of details available, `BASE` and +/// `EXTENDED`. The `BASE` level has been extracted using archive node RPC calls +/// and will contain only the block header, transaction receipts and event logs. +/// Refers to the Firehose service provider to know which blocks are offered on +/// each network. +/// +/// The `EXTENDED` level has been extracted using the Firehose tracer and all +/// fields are available in this Protobuf. +/// +/// The Ethereum block model is used across many chains which means that it +/// happen that certain fields are not available in one chain but are available +/// in another. Each field should be documented when necesssary if it's available +/// on a subset of chains. +/// +/// One major concept to get about the Block is the concept of 'ordinal'. The +/// ordinal is a number that is used to globally order every element of execution +/// that happened throughout the processing of the block like +/// \[TransactionTracer\], \[Call\], \[Log\], \[BalanceChange\], \[StateChange\], etc. +/// Element that have a start and end interval, \[Transaction\] and \[Call\], will +/// have two ordinals: `begin_ordinal` and `end_ordinal`. Element that are +/// executed as "point in time" \[Log\], \[BalanceChange\], \[StateChange\], etc. will +/// have only one ordinal named `ordinal`. If you take all of the message in the +/// Block that have an 'ordinal' field in an array and you sort each element +/// against the `ordinal` field, you will get the exact order of execution of +/// each element in the block. +/// +/// All the 'ordinal' fields in a block are globally unique for the given block, +/// it is **not** a chain-wide global ordering. Furthermore, caution must be take +/// with reverted elements due to execution failure. For anything attached to a +/// \[Call\] that has a `state_reverted` field set to `true`, the `ordinal` field +/// is not reliable and should not be used to order the element against other +/// elements in the block as those element might have 0 as the ordinal. Only +/// successful calls have a reliable `ordinal` field. #[derive(Clone, PartialEq, ::prost::Message)] pub struct Block { - #[prost(int32, tag = "1")] - pub ver: i32, + /// Hash is the block's hash. #[prost(bytes = "vec", tag = "2")] pub hash: ::prost::alloc::vec::Vec, + /// Number is the block's height at which this block was mined. #[prost(uint64, tag = "3")] pub number: u64, + /// Size is the size in bytes of the RLP encoding of the block according to Ethereum + /// rules. #[prost(uint64, tag = "4")] pub size: u64, + /// Header contain's the block's header information like its parent hash, the merkel root hash + /// and all other information the form a block. #[prost(message, optional, tag = "5")] pub header: ::core::option::Option, /// Uncles represents block produced with a valid solution but were not actually chosen @@ -18,54 +64,110 @@ pub struct Block { /// field will actually be always empty. #[prost(message, repeated, tag = "6")] pub uncles: ::prost::alloc::vec::Vec, + /// TransactionTraces hold the execute trace of all the transactions that were executed + /// in this block. In in there that you will find most of the Ethereum data model. + /// + /// They are ordered by the order of execution of the transaction in the block. #[prost(message, repeated, tag = "10")] pub transaction_traces: ::prost::alloc::vec::Vec, + /// BalanceChanges here is the array of ETH transfer that happened at the block level + /// outside of the normal transaction flow of a block. The best example of this is mining + /// reward for the block mined, the transfer of ETH to the miner happens outside the normal + /// transaction flow of the chain and is recorded as a `BalanceChange` here since we cannot + /// attached it to any transaction. + /// + /// Only available in DetailLevel: EXTENDED #[prost(message, repeated, tag = "11")] pub balance_changes: ::prost::alloc::vec::Vec, + /// DetailLevel affects the data available in this block. + /// + /// ## DetailLevel_EXTENDED + /// + /// Describes the most complete block, with traces, balance changes, storage + /// changes. It is extracted during the execution of the block. + /// + /// ## DetailLevel_BASE + /// + /// Describes a block that contains only the block header, transaction receipts + /// and event logs: everything that can be extracted using the base JSON-RPC + /// interface + /// () + /// Furthermore, the eth_getTransactionReceipt call has been avoided because it + /// brings only minimal improvements at the cost of requiring an archive node + /// or a full node with complete transaction index. + #[prost(enumeration = "block::DetailLevel", tag = "12")] + pub detail_level: i32, + /// CodeChanges here is the array of smart code change that happened that happened at the block level + /// outside of the normal transaction flow of a block. Some Ethereum's fork like BSC and Polygon + /// has some capabilities to upgrade internal smart contracts used usually to track the validator + /// list. + /// + /// On hard fork, some procedure runs to upgrade the smart contract code to a new version. In those + /// network, a `CodeChange` for each modified smart contract on upgrade would be present here. Note + /// that this happen rarely, so the vast majority of block will have an empty list here. + /// + /// Only available in DetailLevel: EXTENDED #[prost(message, repeated, tag = "20")] pub code_changes: ::prost::alloc::vec::Vec, + /// System calls are introduced in Cancun, along with blobs. They are executed outside of transactions but affect the state. + /// + /// Only available in DetailLevel: EXTENDED + #[prost(message, repeated, tag = "21")] + pub system_calls: ::prost::alloc::vec::Vec, + /// Withdrawals represents the list of validator balance withdrawals processed in this block. + /// Introduced in the Shanghai hard fork (EIP-4895). + /// + /// This field has been added because Geth blocks include withdrawals after Shanghai fork, + /// but our previous Firehose model didn't capture this data. Currently experimental - + /// NOT ready for production use yet as we validate the tracing implementation. + /// + /// Only available when Shanghai fork is active on the chain. + #[prost(message, repeated, tag = "22")] + pub withdrawals: ::prost::alloc::vec::Vec, + /// Ver represents that data model version of the block, it is used internally by Firehose on Ethereum + /// as a validation that we are reading the correct version. + #[prost(int32, tag = "1")] + pub ver: i32, } -/// HeaderOnlyBlock is used to optimally unpack the \[Block\] structure (note the -/// corresponding message number for the `header` field) while consuming less -/// memory, when only the `header` is desired. -/// -/// WARN: this is a client-side optimization pattern and should be moved in the -/// consuming code. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct HeaderOnlyBlock { - #[prost(message, optional, tag = "5")] - pub header: ::core::option::Option, -} -/// BlockWithRefs is a lightweight block, with traces and transactions -/// purged from the `block` within, and only. It is used in transports -/// to pass block data around. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BlockWithRefs { - #[prost(string, tag = "1")] - pub id: ::prost::alloc::string::String, - #[prost(message, optional, tag = "2")] - pub block: ::core::option::Option, - #[prost(message, optional, tag = "3")] - pub transaction_trace_refs: ::core::option::Option, - #[prost(bool, tag = "4")] - pub irreversible: bool, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct TransactionRefs { - #[prost(bytes = "vec", repeated, tag = "1")] - pub hashes: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct UnclesHeaders { - #[prost(message, repeated, tag = "1")] - pub uncles: ::prost::alloc::vec::Vec, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BlockRef { - #[prost(bytes = "vec", tag = "1")] - pub hash: ::prost::alloc::vec::Vec, - #[prost(uint64, tag = "2")] - pub number: u64, +/// Nested message and enum types in `Block`. +pub mod block { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum DetailLevel { + DetaillevelExtended = 0, + /// DETAILLEVEL_TRACE = 1; // TBD + DetaillevelBase = 2, + } + impl DetailLevel { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::DetaillevelExtended => "DETAILLEVEL_EXTENDED", + Self::DetaillevelBase => "DETAILLEVEL_BASE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DETAILLEVEL_EXTENDED" => Some(Self::DetaillevelExtended), + "DETAILLEVEL_BASE" => Some(Self::DetaillevelBase), + _ => None, + } + } + } } #[derive(Clone, PartialEq, ::prost::Message)] pub struct BlockHeader { @@ -94,12 +196,10 @@ pub struct BlockHeader { /// consensus algorithm, this field will actually be constant and set to `0x00`. #[prost(message, optional, tag = "8")] pub difficulty: ::core::option::Option, - /// TotalDifficulty is the sum of all previous blocks difficulty including this block difficulty. + /// TotalDifficulty used to be the sum of all previous blocks difficulty including this block difficulty. /// - /// If the Block containing this `BlockHeader` has been produced using the Proof of Stake - /// consensus algorithm, this field will actually be constant and set to the terminal total difficulty - /// that was required to transition to Proof of Stake algorithm, which varies per network. It is set to - /// 58 750 000 000 000 000 000 000 on Ethereum Mainnet and to 10 790 000 on Ethereum Testnet Goerli. + /// It has been deprecated in geth v1.15.0 but was already removed from the JSON-RPC interface for a while + #[deprecated] #[prost(message, optional, tag = "17")] pub total_difficulty: ::core::option::Option, #[prost(uint64, tag = "9")] @@ -147,7 +247,12 @@ pub struct BlockHeader { /// extra_data, /// mix_hash, /// nonce, - /// base_fee_per_gas + /// base_fee_per_gas (to be included only if London fork is active) + /// withdrawals_root (to be included only if Shangai fork is active) + /// blob_gas_used (to be included only if Cancun fork is active) + /// excess_blob_gas (to be included only if Cancun fork is active) + /// parent_beacon_root (to be included only if Cancun fork is active) + /// requests_hash (to be included only if Prague fork is active) /// ])) /// #[prost(bytes = "vec", tag = "16")] @@ -155,12 +260,74 @@ pub struct BlockHeader { /// Base fee per gas according to EIP-1559 (e.g. London Fork) rules, only set if London is present/active on the chain. #[prost(message, optional, tag = "18")] pub base_fee_per_gas: ::core::option::Option, + /// Withdrawals root hash according to EIP-4895 (e.g. Shangai Fork) rules, only set if Shangai is present/active on the chain. + /// + /// Only available in DetailLevel: EXTENDED + #[prost(bytes = "vec", tag = "19")] + pub withdrawals_root: ::prost::alloc::vec::Vec, + /// TxDependency is list of transaction indexes that are dependent on each other in the block + /// header. This is metadata only that was used by the internal Polygon parallel execution engine. + /// + /// This field was available in a few versions on Polygon Mainnet and Polygon Mumbai chains. It was actually + /// removed and is not populated anymore. It's now embedded in the `extraData` field, refer to Polygon source + /// code to determine how to extract it if you need it. + /// + /// Only available in DetailLevel: EXTENDED + #[prost(message, optional, tag = "20")] + pub tx_dependency: ::core::option::Option, + /// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. + #[prost(uint64, optional, tag = "22")] + pub blob_gas_used: ::core::option::Option, + /// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. + #[prost(uint64, optional, tag = "23")] + pub excess_blob_gas: ::core::option::Option, + /// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. + #[prost(bytes = "vec", tag = "24")] + pub parent_beacon_root: ::prost::alloc::vec::Vec, + /// RequestsHash was added by EIP-7685 and is ignored in legacy headers. + #[prost(bytes = "vec", tag = "25")] + pub requests_hash: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Uint64NestedArray { + #[prost(message, repeated, tag = "1")] + pub val: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Uint64Array { + #[prost(uint64, repeated, tag = "1")] + pub val: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct BigInt { #[prost(bytes = "vec", tag = "1")] pub bytes: ::prost::alloc::vec::Vec, } +/// TransactionTrace is full trace of execution of the transaction when the +/// it actually executed on chain. +/// +/// It contains all the transaction details like `from`, `to`, `gas`, etc. +/// as well as all the internal calls that were made during the transaction. +/// +/// The `calls` vector contains Call objects which have balance changes, events +/// storage changes, etc. +/// +/// If ordering is important between elements, almost each message like `Log`, +/// `Call`, `StorageChange`, etc. have an ordinal field that is represents "execution" +/// order of the said element against all other elements in this block. +/// +/// Due to how the call tree works doing "naively", looping through all calls then +/// through a Call's element like `logs` while not yielding the elements in the order +/// they were executed on chain. A log in call could have been done before or after +/// another in another call depending on the actual call tree. +/// +/// The `calls` are ordered by creation order and the call tree can be re-computing +/// using fields found in `Call` object (parent/child relationship). +/// +/// Another important thing to note is that even if a transaction succeed, some calls +/// within it could have been reverted internally, if this is important to you, you must +/// check the field `state_reverted` on the `Call` to determine if it was fully committed +/// to the chain or not. #[derive(Clone, PartialEq, ::prost::Message)] pub struct TransactionTrace { /// consensus @@ -205,7 +372,7 @@ pub struct TransactionTrace { /// The value is always set even for transaction before Berlin fork because those before the fork are still legacy transactions. #[prost(enumeration = "transaction_trace::Type", tag = "12")] pub r#type: i32, - /// AcccessList represents the storage access this transaction has agreed to do in which case those storage + /// AccessList represents the storage access this transaction has agreed to do in which case those storage /// access cost less gas unit per access. /// /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_ACCESS_LIST || TRX_TYPE_DYNAMIC_FEE` which @@ -216,6 +383,8 @@ pub struct TransactionTrace { /// /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only /// if London fork is active on the chain. + /// + /// Only available in DetailLevel: EXTENDED #[prost(message, optional, tag = "11")] pub max_fee_per_gas: ::core::option::Option, /// MaxPriorityFeePerGas is priority fee per gas the user to pay in extra to the miner on top of the block's @@ -223,6 +392,8 @@ pub struct TransactionTrace { /// /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only /// if London fork is active on the chain. + /// + /// Only available in DetailLevel: EXTENDED #[prost(message, optional, tag = "13")] pub max_priority_fee_per_gas: ::core::option::Option, /// meta @@ -232,20 +403,97 @@ pub struct TransactionTrace { pub hash: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "22")] pub from: ::prost::alloc::vec::Vec, + /// Only available in DetailLevel: EXTENDED + /// Known Issues + /// - Version 3: + /// Field not populated. It will be empty. + /// + /// Fixed in `Version 4`, see for information about block versions. #[prost(bytes = "vec", tag = "23")] pub return_data: ::prost::alloc::vec::Vec, + /// Only available in DetailLevel: EXTENDED #[prost(bytes = "vec", tag = "24")] pub public_key: ::prost::alloc::vec::Vec, + /// The block's global ordinal when the transaction started executing, refer to + /// \[Block\] documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "25")] pub begin_ordinal: u64, + /// The block's global ordinal when the transaction finished executing, refer to + /// \[Block\] documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "26")] pub end_ordinal: u64, + /// TransactionTraceStatus is the status of the transaction execution and will let you know if the transaction + /// was successful or not. + /// + /// ## Explanation relevant only for blocks with `DetailLevel: EXTENDED` + /// + /// A successful transaction has been recorded to the blockchain's state for calls in it that were successful. + /// This means it's possible only a subset of the calls were properly recorded, refer to \[calls[\].state_reverted] field + /// to determine which calls were reverted. + /// + /// A quirks of the Ethereum protocol is that a transaction `FAILED` or `REVERTED` still affects the blockchain's + /// state for **some** of the state changes. Indeed, in those cases, the transactions fees are still paid to the miner + /// which means there is a balance change for the transaction's emitter (e.g. `from`) to pay the gas fees, an optional + /// balance change for gas refunded to the transaction's emitter (e.g. `from`) and a balance change for the miner who + /// received the transaction fees. There is also a nonce change for the transaction's emitter (e.g. `from`). + /// + /// This means that to properly record the state changes for a transaction, you need to conditionally procees the + /// transaction's status. + /// + /// For a `SUCCEEDED` transaction, you iterate over the `calls` array and record the state changes for each call for + /// which `state_reverted == false` (if a transaction succeeded, the call at #0 will always `state_reverted == false` + /// because it aligns with the transaction). + /// + /// For a `FAILED` or `REVERTED` transaction, you iterate over the root call (e.g. at #0, will always exist) for + /// balance changes you process those where `reason` is either `REASON_GAS_BUY`, `REASON_GAS_REFUND` or + /// `REASON_REWARD_TRANSACTION_FEE` and for nonce change, still on the root call, you pick the nonce change which the + /// smallest ordinal (if more than one). #[prost(enumeration = "TransactionTraceStatus", tag = "30")] pub status: i32, #[prost(message, optional, tag = "31")] pub receipt: ::core::option::Option, + /// Only available in DetailLevel: EXTENDED #[prost(message, repeated, tag = "32")] pub calls: ::prost::alloc::vec::Vec, + /// BlobGas is the amount of gas the transaction is going to pay for the blobs, this is a computed value + /// equivalent to `self.blob_gas_fee_cap * len(self.blob_hashes)` and provided in the model for convenience. + /// + /// This is specified by + /// + /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + /// if Cancun fork is active on the chain. + #[prost(uint64, optional, tag = "33")] + pub blob_gas: ::core::option::Option, + /// BlobGasFeeCap is the maximum fee per data gas the user is willing to pay for the data gas used. + /// + /// This is specified by + /// + /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + /// if Cancun fork is active on the chain. + #[prost(message, optional, tag = "34")] + pub blob_gas_fee_cap: ::core::option::Option, + /// BlobHashes field represents a list of hash outputs from 'kzg_to_versioned_hash' which + /// essentially is a version byte + the sha256 hash of the blob commitment (e.g. + /// `BLOB_COMMITMENT_VERSION_KZG + sha256(commitment)\[1:\]`. + /// + /// This is specified by + /// + /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + /// if Cancun fork is active on the chain. + #[prost(bytes = "vec", repeated, tag = "35")] + pub blob_hashes: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, + /// SetCodeAuthorizations represents the authorizations of a transaction to set code to an EOA (Externally Owned Accounts) + /// as defined in EIP-7702. The list will contain all the authorizations as they were specified in the + /// transaction itself regardless of their validity. If you need to determined if a given authorization was + /// correctly applied on chain's state, refer to \[SetCodeAuthorization.discarded\] field that records + /// if the authorization was discarded or not by the chain due to invalidity. + /// + /// This is specified by + /// + /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_SET_CODE` which is possible only + /// if Prague fork is active on the chain. + #[prost(message, repeated, tag = "36")] + pub set_code_authorizations: ::prost::alloc::vec::Vec, } /// Nested message and enum types in `TransactionTrace`. pub mod transaction_trace { @@ -264,15 +512,38 @@ pub mod transaction_trace { pub enum Type { /// All transactions that ever existed prior Berlin fork before EIP-2718 was implemented. TrxTypeLegacy = 0, - /// Field that specifies an access list of contract/storage_keys that is going to be used + /// Transaction that specicy an access list of contract/storage_keys that is going to be used /// in this transaction. /// /// Added in Berlin fork (EIP-2930). TrxTypeAccessList = 1, - /// Transaction that specifies an access list just like TRX_TYPE_ACCESS_LIST but in addition defines the + /// Transaction that specifis an access list just like TRX_TYPE_ACCESS_LIST but in addition defines the /// max base gas gee and max priority gas fee to pay for this transaction. Transaction's of those type are /// executed against EIP-1559 rules which dictates a dynamic gas cost based on the congestion of the network. TrxTypeDynamicFee = 2, + /// Transaction which contain a large amount of data that cannot be accessed by EVM execution, but whose commitment + /// can be accessed. The format is intended to be fully compatible with the format that will be used in full sharding. + /// + /// Transaction that defines an access list just like TRX_TYPE_ACCESS_LIST and enables dynamic fee just like + /// TRX_TYPE_DYNAMIC_FEE but in addition defines the fields 'max_fee_per_data_gas' of type 'uint256' and the fields + /// 'blob_versioned_hashes' which represents a list of hash outputs from 'kzg_to_versioned_hash'. + /// + /// Activated in Cancun fork (EIP-4844) + TrxTypeBlob = 3, + /// Transaction that sets code to an EOA (Externally Owned Accounts) + /// + /// Activated in Prague (EIP-7702) + TrxTypeSetCode = 4, + /// Arbitrum-specific transactions + TrxTypeArbitrumDeposit = 100, + TrxTypeArbitrumUnsigned = 101, + TrxTypeArbitrumContract = 102, + TrxTypeArbitrumRetry = 104, + TrxTypeArbitrumSubmitRetryable = 105, + TrxTypeArbitrumInternal = 106, + TrxTypeArbitrumLegacy = 120, + /// OPTIMISM-specific transactions + TrxTypeOptimismDeposit = 126, } impl Type { /// String value of the enum field names used in the ProtoBuf definition. @@ -284,6 +555,18 @@ pub mod transaction_trace { Self::TrxTypeLegacy => "TRX_TYPE_LEGACY", Self::TrxTypeAccessList => "TRX_TYPE_ACCESS_LIST", Self::TrxTypeDynamicFee => "TRX_TYPE_DYNAMIC_FEE", + Self::TrxTypeBlob => "TRX_TYPE_BLOB", + Self::TrxTypeSetCode => "TRX_TYPE_SET_CODE", + Self::TrxTypeArbitrumDeposit => "TRX_TYPE_ARBITRUM_DEPOSIT", + Self::TrxTypeArbitrumUnsigned => "TRX_TYPE_ARBITRUM_UNSIGNED", + Self::TrxTypeArbitrumContract => "TRX_TYPE_ARBITRUM_CONTRACT", + Self::TrxTypeArbitrumRetry => "TRX_TYPE_ARBITRUM_RETRY", + Self::TrxTypeArbitrumSubmitRetryable => { + "TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE" + } + Self::TrxTypeArbitrumInternal => "TRX_TYPE_ARBITRUM_INTERNAL", + Self::TrxTypeArbitrumLegacy => "TRX_TYPE_ARBITRUM_LEGACY", + Self::TrxTypeOptimismDeposit => "TRX_TYPE_OPTIMISM_DEPOSIT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -292,6 +575,18 @@ pub mod transaction_trace { "TRX_TYPE_LEGACY" => Some(Self::TrxTypeLegacy), "TRX_TYPE_ACCESS_LIST" => Some(Self::TrxTypeAccessList), "TRX_TYPE_DYNAMIC_FEE" => Some(Self::TrxTypeDynamicFee), + "TRX_TYPE_BLOB" => Some(Self::TrxTypeBlob), + "TRX_TYPE_SET_CODE" => Some(Self::TrxTypeSetCode), + "TRX_TYPE_ARBITRUM_DEPOSIT" => Some(Self::TrxTypeArbitrumDeposit), + "TRX_TYPE_ARBITRUM_UNSIGNED" => Some(Self::TrxTypeArbitrumUnsigned), + "TRX_TYPE_ARBITRUM_CONTRACT" => Some(Self::TrxTypeArbitrumContract), + "TRX_TYPE_ARBITRUM_RETRY" => Some(Self::TrxTypeArbitrumRetry), + "TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE" => { + Some(Self::TrxTypeArbitrumSubmitRetryable) + } + "TRX_TYPE_ARBITRUM_INTERNAL" => Some(Self::TrxTypeArbitrumInternal), + "TRX_TYPE_ARBITRUM_LEGACY" => Some(Self::TrxTypeArbitrumLegacy), + "TRX_TYPE_OPTIMISM_DEPOSIT" => Some(Self::TrxTypeOptimismDeposit), _ => None, } } @@ -306,22 +601,73 @@ pub struct AccessTuple { #[prost(bytes = "vec", repeated, tag = "2")] pub storage_keys: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, } -/// TransactionTraceWithBlockRef +/// SetCodeAuthorization represents the authorization of a transaction to set code of an EOA (Externally Owned Account) +/// as defined in EIP-7702. +/// +/// The 'authority' field is the address that is authorizing the delegation mechanism. The 'authority' value is computed +/// from the signature contained in the message using the computation +/// `authority = ecrecover(keccak(MAGIC || rlp(\[chain_id, address, nonce\])), y_parity, r, s)` +/// where `MAGIC` is `0x5`, `||` is the bytes concatenation operator, `ecrecover` is the Ethereum signature recovery +/// and `y_parity` is the recovery ID value denoted `v` in the message below. Checking the go-ethereum implementation +/// at might prove easier to "read". +/// +/// We do extract the 'authority' value from the signature in the message and store it in the 'authority' field for +/// convenience so you don't need to perform the computation yourself. #[derive(Clone, PartialEq, ::prost::Message)] -pub struct TransactionTraceWithBlockRef { - #[prost(message, optional, tag = "1")] - pub trace: ::core::option::Option, - #[prost(message, optional, tag = "2")] - pub block_ref: ::core::option::Option, +pub struct SetCodeAuthorization { + /// Discarded determines if this authorization was skipped due to being invalid. As EIP-7702 states, + /// if the authorization is invalid (invalid signature, nonce mismatch, etc.) it must be simply + /// discarded and the transaction is processed as if the authorization was not present in the + /// authorization list. + /// + /// This boolean records if the authorization was discarded or not by the chain due to invalidity. + #[prost(bool, tag = "1")] + pub discarded: bool, + /// ChainID is the chain ID of the chain where the transaction was executed, used + /// to recover the authority from the signature. + #[prost(bytes = "vec", tag = "2")] + pub chain_id: ::prost::alloc::vec::Vec, + /// Address contains the address this account is delegating to. This address usually + /// contain code that this account essentially "delegates" to. + /// + /// Note: This was missing when EIP-7702 was first activated on Holesky, Sepolia, BSC Chapel, + /// BSC Mainnet and Arbitrum Sepolia but was ready for Ethereum Mainnet hard fork. We will backfill + /// those missing values in the near future at which point we will remove this note. + #[prost(bytes = "vec", tag = "8")] + pub address: ::prost::alloc::vec::Vec, + /// Nonce is the nonce of the account that is authorizing delegation mechanism, EIP-7702 rules + /// states that nonce should be verified using this rule: + /// + /// - Verify the nonce of authority is equal to nonce. In case authority does not exist in the trie, + /// verify that nonce is equal to 0. + /// + /// Read SetCodeAuthorization to know how to recover the `authority` value. + #[prost(uint64, tag = "3")] + pub nonce: u64, + /// V is the recovery ID value for the signature Y point. While it's defined as a + /// `uint32`, it's actually bounded by a `uint8` data type withing the Ethereum protocol. + #[prost(uint32, tag = "4")] + pub v: u32, + /// R is the signature's X point on the elliptic curve (32 bytes). + #[prost(bytes = "vec", tag = "5")] + pub r: ::prost::alloc::vec::Vec, + /// S is the signature's Y point on the elliptic curve (32 bytes). + #[prost(bytes = "vec", tag = "6")] + pub s: ::prost::alloc::vec::Vec, + /// Authority is the address of the account that is authorizing delegation mechanism, it + /// is computed from the signature contained in the message and stored for convenience. + /// + /// If the authority cannot be recovered from the signature, this field will be empty and + /// the `discarded` field will be set to `true`. + #[prost(bytes = "vec", optional, tag = "7")] + pub authority: ::core::option::Option<::prost::alloc::vec::Vec>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct TransactionReceipt { /// State root is an intermediate state_root hash, computed in-between transactions to make /// **sure** you could build a proof and point to state in the middle of a block. Geth client /// uses `PostState + root + PostStateOrStatus`` while Parity used `status_code, root...`` this piles - /// hardforks, see (read the EIPs first): - /// - - /// - + /// hard forks, see (read the EIPs first): /// - /// /// Moreover, the notion of `Outcome`` in parity, which segregates the two concepts, which are @@ -337,6 +683,23 @@ pub struct TransactionReceipt { pub logs_bloom: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag = "4")] pub logs: ::prost::alloc::vec::Vec, + /// BlobGasUsed is the amount of blob gas that has been used within this transaction. At time + /// of writing, this is equal to `self.blob_gas_fee_cap * len(self.blob_hashes)`. + /// + /// This is specified by + /// + /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + /// if Cancun fork is active on the chain. + #[prost(uint64, optional, tag = "5")] + pub blob_gas_used: ::core::option::Option, + /// BlobGasPrice is the amount to pay per blob item in the transaction. + /// + /// This is specified by + /// + /// This will is populated only if `TransactionTrace.Type == TRX_TYPE_BLOB` which is possible only + /// if Cancun fork is active on the chain. + #[prost(message, optional, tag = "6")] + pub blob_gas_price: ::core::option::Option, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Log { @@ -347,8 +710,10 @@ pub struct Log { #[prost(bytes = "vec", tag = "3")] pub data: ::prost::alloc::vec::Vec, /// Index is the index of the log relative to the transaction. This index - /// is always populated regardless of the state reversion of the call + /// is always populated regardless of the state reversion of the the call /// that emitted this log. + /// + /// Only available in DetailLevel: EXTENDED #[prost(uint32, tag = "4")] pub index: u32, /// BlockIndex represents the index of the log relative to the Block. @@ -356,7 +721,7 @@ pub struct Log { /// An **important** notice is that this field will be 0 when the call /// that emitted the log has been reverted by the chain. /// - /// Currently, there are two locations where a Log can be obtained: + /// Currently, there is two locations where a Log can be obtained: /// - block.transaction_traces\[\].receipt.logs\[\] /// - block.transaction_traces\[\].calls\[\].logs\[\] /// @@ -368,6 +733,8 @@ pub struct Log { /// the `blockIndex` value will always be 0. #[prost(uint32, tag = "6")] pub block_index: u32, + /// The block's global ordinal when the log was recorded, refer to \[Block\] + /// documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "7")] pub ordinal: u64, } @@ -385,6 +752,25 @@ pub struct Call { pub caller: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "6")] pub address: ::prost::alloc::vec::Vec, + /// AddressDelegatesTo contains the address from which the actual code to execute will be loaded + /// as defined per EIP-7702 rules. If the Call's address value resolves to a code + /// that delegates to another address, this field will be populated with the address + /// that the call is delegated to. It will be empty in all other situations. + /// + /// Assumes that a 'SetCode' transaction set address `0xA` to delegates to address `0xB`, + /// then when a call is made to `0xA`, the Call object would have: + /// + /// - caller = + /// - address = 0xA + /// - address_delegates_to = 0xB + /// + /// Again, it's important to emphasize that this field relates to EIP-7702, if the call is + /// a DELEGATE or CALLCODE type, this field will not be populated and will remain empty. + /// + /// It will be populated only if EIP-7702 is active on the chain (Prague fork) and if the + /// 'address' of the call was pointing to another address at time of execution. + #[prost(bytes = "vec", optional, tag = "34")] + pub address_delegates_to: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(message, optional, tag = "7")] pub value: ::core::option::Option, #[prost(uint64, tag = "8")] @@ -393,8 +779,25 @@ pub struct Call { pub gas_consumed: u64, #[prost(bytes = "vec", tag = "13")] pub return_data: ::prost::alloc::vec::Vec, + /// Known Issues + /// - Version 3: + /// When call is `CREATE` or `CREATE2`, this field is not populated. A couple of suggestions: + /// 1. You can get the contract's code in the `code_changes` field. + /// 2. In the root `CREATE` call, you can directly use the `TransactionTrace`'s input field. + /// + /// Fixed in `Version 4`, see for information about block versions. #[prost(bytes = "vec", tag = "14")] pub input: ::prost::alloc::vec::Vec, + /// Indicates whether the call executed code. + /// + /// Known Issues + /// - Version 3: + /// This may be incorrectly set to `false` for accounts with code handling native value transfers, + /// as well as for certain precompiles with no input. + /// The value is initially set based on `call.type != CREATE && len(call.input) > 0` + /// and later adjusted if the tracer detects an account without code. + /// + /// Fixed in `Version 4`, see for information about block versions. #[prost(bool, tag = "15")] pub executed_code: bool, #[prost(bool, tag = "16")] @@ -405,6 +808,11 @@ pub struct Call { ::prost::alloc::string::String, ::prost::alloc::string::String, >, + /// Known Issues + /// - Version 3: + /// The data might be not be in order. + /// + /// Fixed in `Version 4`, see for information about block versions. #[prost(message, repeated, tag = "21")] pub storage_changes: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag = "22")] @@ -415,6 +823,13 @@ pub struct Call { pub logs: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag = "26")] pub code_changes: ::prost::alloc::vec::Vec, + /// Known Issues + /// - Version 3: + /// Some gas changes are not correctly tracked: + /// 1. Gas refunded due to data returned to the chain (occurs at the end of a transaction, before buyback). + /// 2. Initial gas allocation (0 -> GasLimit) at the start of a call. + /// 3. Final gas deduction (LeftOver -> 0) at the end of a call (if applicable). + /// Fixed in `Version 4`, see for information about block versions. #[prost(message, repeated, tag = "28")] pub gas_changes: ::prost::alloc::vec::Vec, /// In Ethereum, a call can be either: @@ -453,13 +868,42 @@ pub struct Call { /// ``` /// /// In the transaction above, while Call #2 and Call #3 would have the - /// status `EXECUTED` + /// status `EXECUTED`. + /// + /// If you check all calls and check only `state_reverted` flag, you might be missing + /// some balance changes and nonce changes. This is because when a full transaction fails + /// in ethereum (e.g. `calls.all(x.state_reverted == true)`), there is still the transaction + /// fee that are recorded to the chain. + /// + /// Refer to \[TransactionTrace#status\] field for more details about the handling you must + /// perform. #[prost(bool, tag = "30")] pub state_reverted: bool, + /// Known Issues + /// - Version 3: + /// 1. The block's global ordinal when the call started executing, refer to + /// \[Block\] documentation for further information about ordinals and total ordering. + /// 2. The transaction root call `begin_ordial` is always `0` (also in the GENESIS block), which can cause issues + /// when sorting by this field. To ensure proper execution order, set it as follows: + /// `trx.Calls\[0\].BeginOrdinal = trx.BeginOrdinal`. + /// + /// Fixed in `Version 4`, see for information about block versions. #[prost(uint64, tag = "31")] pub begin_ordinal: u64, + /// Known Issues + /// - Version 3: + /// 1. The block's global ordinal when the call finished executing, refer to + /// \[Block\] documentation for further information about ordinals and total ordering. + /// 2. The root call of the GENESIS block is always `0`. To fix it, you can set it as follows: + /// `rx.Calls\[0\].EndOrdinal = max.Uint64`. + /// + /// Fixed in `Version 4`, see for information about block versions. #[prost(uint64, tag = "32")] pub end_ordinal: u64, + /// Known Issues + /// - Version 4: + /// AccountCreations are NOT SUPPORTED anymore. DO NOT rely on them. + #[deprecated] #[prost(message, repeated, tag = "33")] pub account_creations: ::prost::alloc::vec::Vec, } @@ -473,29 +917,50 @@ pub struct StorageChange { pub old_value: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "4")] pub new_value: ::prost::alloc::vec::Vec, + /// The block's global ordinal when the storage change was recorded, refer to \[Block\] + /// documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "5")] pub ordinal: u64, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct BalanceChange { + /// Address is the address of the account that has changed balance. #[prost(bytes = "vec", tag = "1")] pub address: ::prost::alloc::vec::Vec, + /// OldValue is the balance of the address before the change. This value + /// can be **nil/null/None** if there was no previous balance for the address. + /// It is safe in those case(s) to consider the balance as being 0. + /// + /// If you consume this from a Substreams, you can safely use: + /// + /// ```ignore + /// let old_value = old_value.unwrap_or_default(); + /// ``` #[prost(message, optional, tag = "2")] pub old_value: ::core::option::Option, + /// NewValue is the balance of the address after the change. This value + /// can be **nil/null/None** if there was no previous balance for the address + /// after the change. It is safe in those case(s) to consider the balance as being + /// 0. + /// + /// If you consume this from a Substreams, you can safely use: + /// + /// ```ignore + /// let new_value = new_value.unwrap_or_default(); + /// ``` #[prost(message, optional, tag = "3")] pub new_value: ::core::option::Option, + /// Reason is the reason why the balance has changed. This is useful to determine + /// why the balance has changed and what is the context of the change. #[prost(enumeration = "balance_change::Reason", tag = "4")] pub reason: i32, + /// The block's global ordinal when the balance change was recorded, refer to \[Block\] + /// documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "5")] pub ordinal: u64, } /// Nested message and enum types in `BalanceChange`. pub mod balance_change { - /// Obtain all balance change reasons under deep mind repository: - /// - /// ```shell - /// ack -ho 'BalanceChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq - /// ``` #[derive( Clone, Copy, @@ -526,6 +991,14 @@ pub mod balance_change { CallBalanceOverride = 12, /// Used on chain(s) where some Ether burning happens Burn = 15, + Withdrawal = 16, + /// Rewards for Blob processing on BNB chain added in Tycho hard-fork, refers + /// to BNB documentation to check the timestamp at which it was activated. + RewardBlobFee = 17, + /// This reason is used only on Optimism chain. + IncreaseMint = 18, + /// This reason is used only on Optimism chain. + Revert = 19, } impl Reason { /// String value of the enum field names used in the ProtoBuf definition. @@ -550,6 +1023,10 @@ pub mod balance_change { Self::SuicideWithdraw => "REASON_SUICIDE_WITHDRAW", Self::CallBalanceOverride => "REASON_CALL_BALANCE_OVERRIDE", Self::Burn => "REASON_BURN", + Self::Withdrawal => "REASON_WITHDRAWAL", + Self::RewardBlobFee => "REASON_REWARD_BLOB_FEE", + Self::IncreaseMint => "REASON_INCREASE_MINT", + Self::Revert => "REASON_REVERT", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -571,6 +1048,10 @@ pub mod balance_change { "REASON_SUICIDE_WITHDRAW" => Some(Self::SuicideWithdraw), "REASON_CALL_BALANCE_OVERRIDE" => Some(Self::CallBalanceOverride), "REASON_BURN" => Some(Self::Burn), + "REASON_WITHDRAWAL" => Some(Self::Withdrawal), + "REASON_REWARD_BLOB_FEE" => Some(Self::RewardBlobFee), + "REASON_INCREASE_MINT" => Some(Self::IncreaseMint), + "REASON_REVERT" => Some(Self::Revert), _ => None, } } @@ -584,6 +1065,8 @@ pub struct NonceChange { pub old_value: u64, #[prost(uint64, tag = "3")] pub new_value: u64, + /// The block's global ordinal when the nonce change was recorded, refer to \[Block\] + /// documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "4")] pub ordinal: u64, } @@ -591,6 +1074,8 @@ pub struct NonceChange { pub struct AccountCreation { #[prost(bytes = "vec", tag = "1")] pub account: ::prost::alloc::vec::Vec, + /// The block's global ordinal when the account creation was recorded, refer to \[Block\] + /// documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "2")] pub ordinal: u64, } @@ -606,6 +1091,8 @@ pub struct CodeChange { pub new_hash: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "5")] pub new_code: ::prost::alloc::vec::Vec, + /// The block's global ordinal when the code change was recorded, refer to \[Block\] + /// documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "6")] pub ordinal: u64, } @@ -613,7 +1100,7 @@ pub struct CodeChange { /// The gas is computed per actual op codes. Doing them completely might prove /// overwhelming in most cases. /// -/// Hence, we only index some of them, those that are costly like all the calls +/// Hence, we only index some of them, those that are costy like all the calls /// one, log events, return data, etc. #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct GasChange { @@ -623,16 +1110,13 @@ pub struct GasChange { pub new_value: u64, #[prost(enumeration = "gas_change::Reason", tag = "3")] pub reason: i32, + /// The block's global ordinal when the gas change was recorded, refer to \[Block\] + /// documentation for further information about ordinals and total ordering. #[prost(uint64, tag = "4")] pub ordinal: u64, } /// Nested message and enum types in `GasChange`. pub mod gas_change { - /// Obtain all gas change reasons under deep mind repository: - /// - /// ```shell - /// ack -ho 'GasChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq - /// ``` #[derive( Clone, Copy, @@ -647,27 +1131,88 @@ pub mod gas_change { #[repr(i32)] pub enum Reason { Unknown = 0, + /// REASON_CALL is the amount of gas that will be charged for a 'CALL' opcode executed by the EVM Call = 1, + /// REASON_CALL_CODE is the amount of gas that will be charged for a 'CALLCODE' opcode executed by the EVM CallCode = 2, + /// REASON_CALL_DATA_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM CallDataCopy = 3, + /// REASON_CODE_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM CodeCopy = 4, + /// REASON_CODE_STORAGE is the amount of gas that will be charged for code storage CodeStorage = 5, + /// REASON_CONTRACT_CREATION is the amount of gas that will be charged for a 'CREATE' opcode executed by the EVM and for the gas + /// burned for a CREATE, today controlled by EIP150 rules ContractCreation = 6, + /// REASON_CONTRACT_CREATION2 is the amount of gas that will be charged for a 'CREATE2' opcode executed by the EVM and for the gas + /// burned for a CREATE2, today controlled by EIP150 rules ContractCreation2 = 7, + /// REASON_DELEGATE_CALL is the amount of gas that will be charged for a 'DELEGATECALL' opcode executed by the EVM DelegateCall = 8, + /// REASON_EVENT_LOG is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM EventLog = 9, + /// REASON_EXT_CODE_COPY is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM ExtCodeCopy = 10, + /// REASON_FAILED_EXECUTION is the burning of the remaining gas when the execution failed without a revert FailedExecution = 11, + /// REASON_INTRINSIC_GAS is the amount of gas that will be charged for the intrinsic cost of the transaction, there is + /// always exactly one of those per transaction IntrinsicGas = 12, + /// GasChangePrecompiledContract is the amount of gas that will be charged for a precompiled contract execution PrecompiledContract = 13, + /// REASON_REFUND_AFTER_EXECUTION is the amount of gas that will be refunded to the caller after the execution of the call, + /// if there is left over at the end of execution RefundAfterExecution = 14, + /// REASON_RETURN is the amount of gas that will be charged for a 'RETURN' opcode executed by the EVM Return = 15, + /// REASON_RETURN_DATA_COPY is the amount of gas that will be charged for a 'RETURNDATACOPY' opcode executed by the EVM ReturnDataCopy = 16, + /// REASON_REVERT is the amount of gas that will be charged for a 'REVERT' opcode executed by the EVM Revert = 17, + /// REASON_SELF_DESTRUCT is the amount of gas that will be charged for a 'SELFDESTRUCT' opcode executed by the EVM SelfDestruct = 18, + /// REASON_STATIC_CALL is the amount of gas that will be charged for a 'STATICALL' opcode executed by the EVM StaticCall = 19, + /// REASON_STATE_COLD_ACCESS is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules + /// /// Added in Berlin fork (Geth 1.10+) StateColdAccess = 20, + /// REASON_TX_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call + /// + /// Added as new tracing reason in Geth, available only on some chains + TxInitialBalance = 21, + /// REASON_TX_REFUNDS is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) + /// this generates an increase in gas. There is only one such gas change per transaction. + /// + /// Added as new tracing reason in Geth, available only on some chains + TxRefunds = 22, + /// REASON_TX_LEFT_OVER_RETURNED is the amount of gas left over at the end of transaction's execution that will be returned + /// to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas + /// left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. + /// There is at most one of such gas change per transaction. + /// + /// Added as new tracing reason in Geth, available only on some chains + TxLeftOverReturned = 23, + /// REASON_CALL_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call. There is only + /// one such gas change per call. + /// + /// Added as new tracing reason in Geth, available only on some chains + CallInitialBalance = 24, + /// REASON_CALL_LEFT_OVER_RETURNED is the amount of gas left over that will be returned to the caller, this change will always + /// be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even + /// will be emitted. + CallLeftOverReturned = 25, + /// REASON_WITNESS_CONTRACT_INIT flags the event of adding to the witness during the contract creation initialization step. + WitnessContractInit = 26, + /// REASON_WITNESS_CONTRACT_CREATION flags the event of adding to the witness during the contract creation finalization step. + WitnessContractCreation = 27, + /// REASON_WITNESS_CODE_CHUNK flags the event of adding one or more contract code chunks to the witness. + WitnessCodeChunk = 28, + /// REASON_WITNESS_CONTRACT_COLLISION_CHECK flags the event of adding to the witness when checking for contract address collision. + WitnessContractCollisionCheck = 29, + /// REASON_TX_DATA_FLOOR is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the + /// transaction data. This change will always be a negative change. + TxDataFloor = 30, } impl Reason { /// String value of the enum field names used in the ProtoBuf definition. @@ -697,6 +1242,18 @@ pub mod gas_change { Self::SelfDestruct => "REASON_SELF_DESTRUCT", Self::StaticCall => "REASON_STATIC_CALL", Self::StateColdAccess => "REASON_STATE_COLD_ACCESS", + Self::TxInitialBalance => "REASON_TX_INITIAL_BALANCE", + Self::TxRefunds => "REASON_TX_REFUNDS", + Self::TxLeftOverReturned => "REASON_TX_LEFT_OVER_RETURNED", + Self::CallInitialBalance => "REASON_CALL_INITIAL_BALANCE", + Self::CallLeftOverReturned => "REASON_CALL_LEFT_OVER_RETURNED", + Self::WitnessContractInit => "REASON_WITNESS_CONTRACT_INIT", + Self::WitnessContractCreation => "REASON_WITNESS_CONTRACT_CREATION", + Self::WitnessCodeChunk => "REASON_WITNESS_CODE_CHUNK", + Self::WitnessContractCollisionCheck => { + "REASON_WITNESS_CONTRACT_COLLISION_CHECK" + } + Self::TxDataFloor => "REASON_TX_DATA_FLOOR", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -723,11 +1280,84 @@ pub mod gas_change { "REASON_SELF_DESTRUCT" => Some(Self::SelfDestruct), "REASON_STATIC_CALL" => Some(Self::StaticCall), "REASON_STATE_COLD_ACCESS" => Some(Self::StateColdAccess), + "REASON_TX_INITIAL_BALANCE" => Some(Self::TxInitialBalance), + "REASON_TX_REFUNDS" => Some(Self::TxRefunds), + "REASON_TX_LEFT_OVER_RETURNED" => Some(Self::TxLeftOverReturned), + "REASON_CALL_INITIAL_BALANCE" => Some(Self::CallInitialBalance), + "REASON_CALL_LEFT_OVER_RETURNED" => Some(Self::CallLeftOverReturned), + "REASON_WITNESS_CONTRACT_INIT" => Some(Self::WitnessContractInit), + "REASON_WITNESS_CONTRACT_CREATION" => Some(Self::WitnessContractCreation), + "REASON_WITNESS_CODE_CHUNK" => Some(Self::WitnessCodeChunk), + "REASON_WITNESS_CONTRACT_COLLISION_CHECK" => { + Some(Self::WitnessContractCollisionCheck) + } + "REASON_TX_DATA_FLOOR" => Some(Self::TxDataFloor), _ => None, } } } } +/// HeaderOnlyBlock is used to optimally unpack the \[Block\] structure (note the +/// corresponding message number for the `header` field) while consuming less +/// memory, when only the `header` is desired. +/// +/// WARN: this is a client-side optimization pattern and should be moved in the +/// consuming code. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HeaderOnlyBlock { + #[prost(message, optional, tag = "5")] + pub header: ::core::option::Option, +} +/// BlockWithRefs is a lightweight block, with traces and transactions +/// purged from the `block` within, and only. It is used in transports +/// to pass block data around. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockWithRefs { + #[prost(string, tag = "1")] + pub id: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub block: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub transaction_trace_refs: ::core::option::Option, + #[prost(bool, tag = "4")] + pub irreversible: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionTraceWithBlockRef { + #[prost(message, optional, tag = "1")] + pub trace: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub block_ref: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionRefs { + #[prost(bytes = "vec", repeated, tag = "1")] + pub hashes: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockRef { + #[prost(bytes = "vec", tag = "1")] + pub hash: ::prost::alloc::vec::Vec, + #[prost(uint64, tag = "2")] + pub number: u64, +} +/// Withdrawal represents a validator withdrawal from the beacon chain to the EVM. +/// Introduced in EIP-4895 (Shanghai hard fork). +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Withdrawal { + /// Index is the monotonically increasing identifier of the withdrawal + #[prost(uint64, tag = "1")] + pub index: u64, + /// ValidatorIndex is the index of the validator that is withdrawing + #[prost(uint64, tag = "2")] + pub validator_index: u64, + /// Address is the Ethereum address receiving the withdrawn funds + #[prost(bytes = "vec", tag = "3")] + pub address: ::prost::alloc::vec::Vec, + /// Amount is the value of the withdrawal in gwei (1 gwei = 1e9 wei) + #[prost(uint64, tag = "4")] + pub amount: u64, +} #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum TransactionTraceStatus { diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index a716c4ea3a8..5641a501a6e 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -2,16 +2,15 @@ use super::runtime_adapter::UnresolvedContractCall; use crate::trigger::{ EthereumBlockData, EthereumCallData, EthereumEventData, EthereumTransactionData, }; +use anyhow::anyhow; use async_trait::async_trait; +use graph::abi; +use graph::prelude::alloy; +use graph::prelude::alloy::network::ReceiptResponse; +use graph::prelude::alloy::rpc::types::{Log, TransactionReceipt}; +use graph::prelude::alloy::serde::WithOtherFields; use graph::{ - prelude::{ - ethabi, - web3::{ - self, - types::{Log, TransactionReceipt, H256}, - }, - BigInt, - }, + prelude::BigInt, runtime::{ asc_get, asc_new, asc_new_or_null, gas::GasCounter, AscHeap, AscIndexId, AscPtr, AscType, DeterministicHostError, FromAscObj, HostExportError, IndexForAscTypeId, ToAscObj, @@ -24,7 +23,7 @@ use graph_runtime_wasm::asc_abi::class::{ }; use semver::Version; -type AscH256 = Uint8Array; +type AscB256 = Uint8Array; type AscH2048 = Uint8Array; pub struct AscLogParamArray(Array>); @@ -42,7 +41,7 @@ impl AscType for AscLogParamArray { } #[async_trait] -impl ToAscObj for &[ethabi::LogParam] { +impl ToAscObj for &[abi::DynSolParam] { async fn to_asc_obj( &self, heap: &mut H, @@ -60,7 +59,7 @@ impl AscIndexId for AscLogParamArray { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayEventParam; } -pub struct AscTopicArray(Array>); +pub struct AscTopicArray(Array>); impl AscType for AscTopicArray { fn to_asc_bytes(&self) -> Result, DeterministicHostError> { @@ -76,14 +75,14 @@ impl AscType for AscTopicArray { } #[async_trait] -impl ToAscObj for Vec { +impl ToAscObj for &[alloy::primitives::B256] { async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { let mut topics = Vec::with_capacity(self.len()); - for topic in self { + for topic in *self { topics.push(asc_new(heap, topic, gas).await?); } Ok(AscTopicArray(Array::new(&topics, heap, gas).await?)) @@ -91,7 +90,7 @@ impl ToAscObj for Vec { } impl AscIndexId for AscTopicArray { - const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayH256; + const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::ArrayB256; } pub struct AscLogArray(Array>); @@ -110,14 +109,14 @@ impl AscType for AscLogArray { } #[async_trait] -impl ToAscObj for Vec { +impl ToAscObj for &[Log] { async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { let mut logs = Vec::with_capacity(self.len()); - for log in self { + for log in *self { logs.push(asc_new(heap, log, gas).await?); } @@ -190,13 +189,13 @@ impl FromAscObj for UnresolvedContractCall { #[repr(C)] #[derive(AscType)] pub(crate) struct AscEthereumBlock { - pub hash: AscPtr, - pub parent_hash: AscPtr, - pub uncles_hash: AscPtr, + pub hash: AscPtr, + pub parent_hash: AscPtr, + pub uncles_hash: AscPtr, pub author: AscPtr, - pub state_root: AscPtr, - pub transactions_root: AscPtr, - pub receipts_root: AscPtr, + pub state_root: AscPtr, + pub transactions_root: AscPtr, + pub receipts_root: AscPtr, pub number: AscPtr, pub gas_used: AscPtr, pub gas_limit: AscPtr, @@ -214,13 +213,13 @@ impl AscIndexId for AscEthereumBlock { #[derive(AscType)] #[allow(non_camel_case_types)] pub(crate) struct AscEthereumBlock_0_0_6 { - pub hash: AscPtr, - pub parent_hash: AscPtr, - pub uncles_hash: AscPtr, + pub hash: AscPtr, + pub parent_hash: AscPtr, + pub uncles_hash: AscPtr, pub author: AscPtr, - pub state_root: AscPtr, - pub transactions_root: AscPtr, - pub receipts_root: AscPtr, + pub state_root: AscPtr, + pub transactions_root: AscPtr, + pub receipts_root: AscPtr, pub number: AscPtr, pub gas_used: AscPtr, pub gas_limit: AscPtr, @@ -239,7 +238,7 @@ impl AscIndexId for AscEthereumBlock_0_0_6 { #[derive(AscType)] #[allow(non_camel_case_types)] pub(crate) struct AscEthereumTransaction_0_0_1 { - pub hash: AscPtr, + pub hash: AscPtr, pub index: AscPtr, pub from: AscPtr, pub to: AscPtr, @@ -256,7 +255,7 @@ impl AscIndexId for AscEthereumTransaction_0_0_1 { #[derive(AscType)] #[allow(non_camel_case_types)] pub(crate) struct AscEthereumTransaction_0_0_2 { - pub hash: AscPtr, + pub hash: AscPtr, pub index: AscPtr, pub from: AscPtr, pub to: AscPtr, @@ -274,7 +273,7 @@ impl AscIndexId for AscEthereumTransaction_0_0_2 { #[derive(AscType)] #[allow(non_camel_case_types)] pub(crate) struct AscEthereumTransaction_0_0_6 { - pub hash: AscPtr, + pub hash: AscPtr, pub index: AscPtr, pub from: AscPtr, pub to: AscPtr, @@ -323,9 +322,9 @@ pub(crate) struct AscEthereumLog { pub address: AscPtr, pub topics: AscPtr, pub data: AscPtr, - pub block_hash: AscPtr, - pub block_number: AscPtr, - pub transaction_hash: AscPtr, + pub block_hash: AscPtr, + pub block_number: AscPtr, + pub transaction_hash: AscPtr, pub transaction_index: AscPtr, pub log_index: AscPtr, pub transaction_log_index: AscPtr, @@ -340,16 +339,16 @@ impl AscIndexId for AscEthereumLog { #[repr(C)] #[derive(AscType)] pub(crate) struct AscEthereumTransactionReceipt { - pub transaction_hash: AscPtr, + pub transaction_hash: AscPtr, pub transaction_index: AscPtr, - pub block_hash: AscPtr, + pub block_hash: AscPtr, pub block_number: AscPtr, pub cumulative_gas_used: AscPtr, pub gas_used: AscPtr, pub contract_address: AscPtr, pub logs: AscPtr, pub status: AscPtr, - pub root: AscPtr, + pub root: AscPtr, pub logs_bloom: AscPtr, } @@ -437,10 +436,7 @@ impl<'a> ToAscObj for EthereumBlockData<'a> { heap: &mut H, gas: &GasCounter, ) -> Result { - let size = match self.size() { - Some(size) => asc_new(heap, &BigInt::from_unsigned_u256(&size), gas).await?, - None => AscPtr::null(), - }; + let size = asc_new_or_null_u256(heap, &self.size(), gas).await?; Ok(AscEthereumBlock { hash: asc_new(heap, self.hash(), gas).await?, @@ -451,9 +447,9 @@ impl<'a> ToAscObj for EthereumBlockData<'a> { transactions_root: asc_new(heap, self.transactions_root(), gas).await?, receipts_root: asc_new(heap, self.receipts_root(), gas).await?, number: asc_new(heap, &BigInt::from(self.number()), gas).await?, - gas_used: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_used()), gas).await?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, - timestamp: asc_new(heap, &BigInt::from_unsigned_u256(self.timestamp()), gas).await?, + gas_used: asc_new(heap, &BigInt::from(self.gas_used()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from(self.gas_limit()), gas).await?, + timestamp: asc_new(heap, &BigInt::from(self.timestamp()), gas).await?, difficulty: asc_new(heap, &BigInt::from_unsigned_u256(self.difficulty()), gas).await?, total_difficulty: asc_new( heap, @@ -473,14 +469,8 @@ impl<'a> ToAscObj for EthereumBlockData<'a> { heap: &mut H, gas: &GasCounter, ) -> Result { - let size = match self.size() { - Some(size) => asc_new(heap, &BigInt::from_unsigned_u256(&size), gas).await?, - None => AscPtr::null(), - }; - let base_fee_per_block = match self.base_fee_per_gas() { - Some(base_fee) => asc_new(heap, &BigInt::from_unsigned_u256(&base_fee), gas).await?, - None => AscPtr::null(), - }; + let size = asc_new_or_null_u256(heap, &self.size(), gas).await?; + let base_fee_per_block = asc_new_or_null_u64(heap, &self.base_fee_per_gas(), gas).await?; Ok(AscEthereumBlock_0_0_6 { hash: asc_new(heap, self.hash(), gas).await?, @@ -491,9 +481,9 @@ impl<'a> ToAscObj for EthereumBlockData<'a> { transactions_root: asc_new(heap, self.transactions_root(), gas).await?, receipts_root: asc_new(heap, self.receipts_root(), gas).await?, number: asc_new(heap, &BigInt::from(self.number()), gas).await?, - gas_used: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_used()), gas).await?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, - timestamp: asc_new(heap, &BigInt::from_unsigned_u256(self.timestamp()), gas).await?, + gas_used: asc_new(heap, &BigInt::from(self.gas_used()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from(self.gas_limit()), gas).await?, + timestamp: asc_new(heap, &BigInt::from(self.timestamp()), gas).await?, difficulty: asc_new(heap, &BigInt::from_unsigned_u256(self.difficulty()), gas).await?, total_difficulty: asc_new( heap, @@ -515,13 +505,16 @@ impl<'a> ToAscObj for EthereumTransactionData<'a> gas: &GasCounter, ) -> Result { Ok(AscEthereumTransaction_0_0_1 { - hash: asc_new(heap, self.hash(), gas).await?, - index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas).await?, - from: asc_new(heap, self.from(), gas).await?, - to: asc_new_or_null(heap, self.to(), gas).await?, - value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas).await?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, - gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas).await?, + hash: asc_new(heap, &self.hash(), gas).await?, + index: asc_new(heap, &BigInt::from(self.index()), gas).await?, + from: asc_new(heap, &self.from(), gas).await?, + to: match self.to() { + Some(to) => asc_new(heap, &to, gas).await?, + None => AscPtr::null(), + }, + value: asc_new(heap, &BigInt::from_unsigned_u256(&self.value()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from(self.gas_limit()), gas).await?, + gas_price: asc_new(heap, &BigInt::from(self.gas_price()), gas).await?, }) } } @@ -534,13 +527,16 @@ impl<'a> ToAscObj for EthereumTransactionData<'a> gas: &GasCounter, ) -> Result { Ok(AscEthereumTransaction_0_0_2 { - hash: asc_new(heap, self.hash(), gas).await?, - index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas).await?, - from: asc_new(heap, self.from(), gas).await?, - to: asc_new_or_null(heap, self.to(), gas).await?, - value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas).await?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, - gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas).await?, + hash: asc_new(heap, &self.hash(), gas).await?, + index: asc_new(heap, &BigInt::from(self.index()), gas).await?, + from: asc_new(heap, &self.from(), gas).await?, + to: match self.to() { + Some(to) => asc_new(heap, &to, gas).await?, + None => AscPtr::null(), + }, + value: asc_new(heap, &BigInt::from_unsigned_u256(&self.value()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from(self.gas_limit()), gas).await?, + gas_price: asc_new(heap, &BigInt::from(self.gas_price()), gas).await?, input: asc_new(heap, self.input(), gas).await?, }) } @@ -554,15 +550,15 @@ impl<'a> ToAscObj for EthereumTransactionData<'a> gas: &GasCounter, ) -> Result { Ok(AscEthereumTransaction_0_0_6 { - hash: asc_new(heap, self.hash(), gas).await?, - index: asc_new(heap, &BigInt::from_unsigned_u128(self.index()), gas).await?, - from: asc_new(heap, self.from(), gas).await?, - to: asc_new_or_null(heap, self.to(), gas).await?, - value: asc_new(heap, &BigInt::from_unsigned_u256(self.value()), gas).await?, - gas_limit: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_limit()), gas).await?, - gas_price: asc_new(heap, &BigInt::from_unsigned_u256(self.gas_price()), gas).await?, + hash: asc_new(heap, &self.hash(), gas).await?, + index: asc_new(heap, &BigInt::from(self.index()), gas).await?, + from: asc_new(heap, &self.from(), gas).await?, + to: asc_new_or_null(heap, &self.to(), gas).await?, + value: asc_new(heap, &BigInt::from_unsigned_u256(&self.value()), gas).await?, + gas_limit: asc_new(heap, &BigInt::from(self.gas_limit()), gas).await?, + gas_price: asc_new(heap, &BigInt::from(self.gas_price()), gas).await?, input: asc_new(heap, self.input(), gas).await?, - nonce: asc_new(heap, &BigInt::from_unsigned_u256(self.nonce()), gas).await?, + nonce: asc_new(heap, &BigInt::from(self.nonce()), gas).await?, }) } } @@ -582,14 +578,10 @@ where ) -> Result, HostExportError> { Ok(AscEthereumEvent { address: asc_new(heap, self.address(), gas).await?, - log_index: asc_new(heap, &BigInt::from_unsigned_u256(self.log_index()), gas).await?, - transaction_log_index: asc_new( - heap, - &BigInt::from_unsigned_u256(self.transaction_log_index()), - gas, - ) - .await?, - log_type: asc_new_or_null(heap, self.log_type(), gas).await?, + log_index: asc_new(heap, &BigInt::from(self.log_index()), gas).await?, + transaction_log_index: asc_new(heap, &BigInt::from(self.transaction_log_index()), gas) + .await?, + log_type: asc_new_or_null(heap, &self.log_type().as_ref(), gas).await?, block: asc_new::(heap, &self.block, gas).await?, transaction: asc_new::(heap, &self.transaction, gas) .await?, @@ -599,13 +591,18 @@ where } #[async_trait] -impl<'a, T, B> ToAscObj> - for (EthereumEventData<'a>, Option<&TransactionReceipt>) +impl<'a, T, B, Inner> ToAscObj> + for ( + EthereumEventData<'a>, + Option<&WithOtherFields>>, + ) where T: AscType + AscIndexId + Send, B: AscType + AscIndexId + Send, EthereumTransactionData<'a>: ToAscObj, EthereumBlockData<'a>: ToAscObj, + Inner: Send + Sync, + TransactionReceipt: ToAscObj, { async fn to_asc_obj( &self, @@ -623,7 +620,7 @@ where params, } = event_data.to_asc_obj(heap, gas).await?; let receipt = if let Some(receipt_data) = optional_receipt { - asc_new(heap, receipt_data, gas).await? + asc_new(heap, &receipt_data.inner(), gas).await? } else { AscPtr::null() }; @@ -642,7 +639,7 @@ where async fn asc_new_or_null_u256( heap: &mut H, - value: &Option, + value: &Option, gas: &GasCounter, ) -> Result, HostExportError> { match value { @@ -653,7 +650,7 @@ async fn asc_new_or_null_u256( async fn asc_new_or_null_u64( heap: &mut H, - value: &Option, + value: &Option, gas: &GasCounter, ) -> Result, HostExportError> { match value { @@ -669,51 +666,55 @@ impl ToAscObj for Log { heap: &mut H, gas: &GasCounter, ) -> Result { - let removed = match self.removed { - Some(removed) => asc_new(heap, &AscWrapped { inner: removed }, gas).await?, - None => AscPtr::null(), - }; Ok(AscEthereumLog { - address: asc_new(heap, &self.address, gas).await?, - topics: asc_new(heap, &self.topics, gas).await?, - data: asc_new(heap, self.data.0.as_slice(), gas).await?, + address: asc_new(heap, &self.address(), gas).await?, + topics: asc_new(heap, &self.topics(), gas).await?, + data: asc_new(heap, self.data().data.as_ref(), gas).await?, block_hash: asc_new_or_null(heap, &self.block_hash, gas).await?, block_number: asc_new_or_null_u64(heap, &self.block_number, gas).await?, transaction_hash: asc_new_or_null(heap, &self.transaction_hash, gas).await?, transaction_index: asc_new_or_null_u64(heap, &self.transaction_index, gas).await?, - log_index: asc_new_or_null_u256(heap, &self.log_index, gas).await?, - transaction_log_index: asc_new_or_null_u256(heap, &self.transaction_log_index, gas) - .await?, - log_type: asc_new_or_null(heap, &self.log_type, gas).await?, - removed, + log_index: asc_new_or_null_u64(heap, &self.log_index, gas).await?, + transaction_log_index: AscPtr::null(), // TODO(alloy): figure out how to get transaction log index + log_type: AscPtr::null(), // TODO(alloy): figure out how to get log type + removed: asc_new( + heap, + &AscWrapped { + inner: self.removed, + }, + gas, + ) + .await?, }) } } #[async_trait] -impl ToAscObj for &TransactionReceipt { +impl ToAscObj + for TransactionReceipt> +{ async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { + let transaction_index = self + .transaction_index + .ok_or(HostExportError::Unknown(anyhow!( + "Transaction index is missing" + )))?; Ok(AscEthereumTransactionReceipt { transaction_hash: asc_new(heap, &self.transaction_hash, gas).await?, - transaction_index: asc_new(heap, &BigInt::from(self.transaction_index), gas).await?, + transaction_index: asc_new(heap, &BigInt::from(transaction_index), gas).await?, block_hash: asc_new_or_null(heap, &self.block_hash, gas).await?, block_number: asc_new_or_null_u64(heap, &self.block_number, gas).await?, - cumulative_gas_used: asc_new( - heap, - &BigInt::from_unsigned_u256(&self.cumulative_gas_used), - gas, - ) - .await?, - gas_used: asc_new_or_null_u256(heap, &self.gas_used, gas).await?, + cumulative_gas_used: asc_new(heap, &BigInt::from(self.gas_used), gas).await?, + gas_used: asc_new(heap, &BigInt::from(self.gas_used), gas).await?, contract_address: asc_new_or_null(heap, &self.contract_address, gas).await?, - logs: asc_new(heap, &self.logs, gas).await?, - status: asc_new_or_null_u64(heap, &self.status, gas).await?, - root: asc_new_or_null(heap, &self.root, gas).await?, - logs_bloom: asc_new(heap, self.logs_bloom.as_bytes(), gas).await?, + logs: asc_new(heap, &self.logs(), gas).await?, + status: asc_new(heap, &BigInt::from(self.status() as u64), gas).await?, + root: asc_new_or_null(heap, &self.state_root(), gas).await?, + logs_bloom: asc_new(heap, self.inner.bloom().as_slice(), gas).await?, }) } } @@ -782,7 +783,7 @@ impl<'a> ToAscObj for ethabi::LogParam { +impl ToAscObj for abi::DynSolParam { async fn to_asc_obj( &self, heap: &mut H, diff --git a/chain/ethereum/src/runtime/runtime_adapter.rs b/chain/ethereum/src/runtime/runtime_adapter.rs index 8b11ada37cc..f74790146d1 100644 --- a/chain/ethereum/src/runtime/runtime_adapter.rs +++ b/chain/ethereum/src/runtime/runtime_adapter.rs @@ -7,6 +7,8 @@ use crate::{ }; use anyhow::{anyhow, Context, Error}; use blockchain::HostFn; +use graph::abi; +use graph::abi::DynSolValueExt; use graph::blockchain::ChainIdentifier; use graph::components::subgraph::HostMetrics; use graph::data::store::ethereum::call; @@ -14,18 +16,14 @@ use graph::data::store::scalar::BigInt; use graph::data::subgraph::{API_VERSION_0_0_4, API_VERSION_0_0_9}; use graph::data_source; use graph::data_source::common::{ContractCall, MappingABI}; -use graph::futures03::FutureExt as _; -use graph::prelude::web3::types::H160; use graph::runtime::gas::Gas; use graph::runtime::{AscIndexId, IndexForAscTypeId}; use graph::slog::debug; use graph::{ blockchain::{self, BlockPtr, HostFnCtx}, cheap_clone::CheapClone, - prelude::{ - ethabi::{self, Address, Token}, - EthereumCallCache, - }, + futures03::FutureExt, + prelude::{alloy::primitives::Address, EthereumCallCache}, runtime::{asc_get, asc_new, AscPtr, HostExportError}, slog::Logger, }; @@ -243,7 +241,7 @@ async fn eth_get_balance( let logger = &ctx.logger; let block_ptr = &ctx.block_ptr; - let address: H160 = asc_get(ctx.heap, wasm_ptr.into(), &ctx.gas, 0)?; + let address: Address = asc_get(ctx.heap, wasm_ptr.into(), &ctx.gas, 0)?; let result = eth_adapter .get_balance(logger, address, block_ptr.clone()) @@ -255,7 +253,7 @@ async fn eth_get_balance( Ok(asc_new(ctx.heap, &bigint, &ctx.gas).await?) } // Retry on any kind of error - Err(EthereumRpcError::Web3Error(e)) => Err(HostExportError::PossibleReorg(e.into())), + Err(EthereumRpcError::AlloyError(e)) => Err(HostExportError::PossibleReorg(e.into())), Err(EthereumRpcError::Timeout) => Err(HostExportError::PossibleReorg( EthereumRpcError::Timeout.into(), )), @@ -279,7 +277,7 @@ async fn eth_has_code( let logger = &ctx.logger; let block_ptr = &ctx.block_ptr; - let address: H160 = asc_get(ctx.heap, wasm_ptr.into(), &ctx.gas, 0)?; + let address: Address = asc_get(ctx.heap, wasm_ptr.into(), &ctx.gas, 0)?; let result = eth_adapter .get_code(logger, address, block_ptr.clone()) @@ -289,7 +287,7 @@ async fn eth_has_code( match result { Ok(v) => Ok(asc_new(ctx.heap, &AscWrapped { inner: v }, &ctx.gas).await?), // Retry on any kind of error - Err(EthereumRpcError::Web3Error(e)) => Err(HostExportError::PossibleReorg(e.into())), + Err(EthereumRpcError::AlloyError(e)) => Err(HostExportError::PossibleReorg(e.into())), Err(EthereumRpcError::Timeout) => Err(HostExportError::PossibleReorg( EthereumRpcError::Timeout.into(), )), @@ -306,20 +304,7 @@ async fn eth_call( abis: &[Arc], eth_call_gas: Option, metrics: Arc, -) -> Result>, HostExportError> { - // Helpers to log the result of the call at the end - fn tokens_as_string(tokens: &[Token]) -> String { - tokens.iter().map(|arg| arg.to_string()).join(", ") - } - - fn result_as_string(result: &Result>, HostExportError>) -> String { - match result { - Ok(Some(tokens)) => format!("({})", tokens_as_string(&tokens)), - Ok(None) => "none".to_string(), - Err(_) => "error".to_string(), - } - } - +) -> Result>, HostExportError> { let start_time = Instant::now(); // Obtain the path to the contract ABI @@ -365,7 +350,7 @@ async fn eth_call( // Any error reported by the Ethereum node could be due to the block no longer being on // the main chain. This is very unespecific but we don't want to risk failing a // subgraph due to a transient error such as a reorg. - Err(ContractCallError::Web3Error(e)) => Err(HostExportError::PossibleReorg(anyhow::anyhow!( + Err(ContractCallError::AlloyError(e)) => Err(HostExportError::PossibleReorg(anyhow::anyhow!( "Ethereum node returned an error when calling function \"{}\" of contract \"{}\": {}", unresolved_call.function_name, unresolved_call.contract_name, @@ -397,16 +382,26 @@ async fn eth_call( ); } - debug!(logger, "Contract call finished"; - "address" => format!("0x{:x}", &unresolved_call.contract_address), - "contract" => &unresolved_call.contract_name, - "signature" => &unresolved_call.function_signature, - "args" => format!("[{}]", tokens_as_string(&unresolved_call.function_args)), - "time_ms" => format!("{}ms", elapsed.as_millis()), - "result" => result_as_string(&result), - "block_hash" => block_ptr.hash_hex(), - "block_number" => block_ptr.block_number(), - "source" => source.to_string()); + let args_as_string = format!("[{}]", values_to_string(&unresolved_call.function_args)); + + let result_as_string = match &result { + Ok(Some(values)) => format!("({})", values_to_string(values)), + Ok(None) => "none".to_owned(), + Err(_err) => "error".to_owned(), + }; + + debug!( + logger, "Contract call finished"; + "address" => format!("0x{:x}", &unresolved_call.contract_address), + "contract" => &unresolved_call.contract_name, + "signature" => &unresolved_call.function_signature, + "args" => args_as_string, + "time_ms" => format!("{}ms", elapsed.as_millis()), + "result" => result_as_string, + "block_hash" => block_ptr.hash_hex(), + "block_number" => block_ptr.block_number(), + "source" => source.to_string(), + ); result } @@ -417,9 +412,18 @@ pub struct UnresolvedContractCall { pub contract_address: Address, pub function_name: String, pub function_signature: Option, - pub function_args: Vec, + pub function_args: Vec, } impl AscIndexId for AscUnresolvedContractCall { const INDEX_ASC_TYPE_ID: IndexForAscTypeId = IndexForAscTypeId::SmartContractCall; } + +#[inline] +fn values_to_string(values: &[abi::DynSolValue]) -> String { + values + .iter() + .map(|x| x.to_string()) + .collect_vec() + .join(", ") +} diff --git a/chain/ethereum/src/tests.rs b/chain/ethereum/src/tests.rs index 00873f8ea87..5dd8266a588 100644 --- a/chain/ethereum/src/tests.rs +++ b/chain/ethereum/src/tests.rs @@ -3,7 +3,12 @@ use std::sync::Arc; use graph::{ blockchain::{block_stream::BlockWithTriggers, BlockPtr, Trigger}, prelude::{ - web3::types::{Address, Bytes, Log, H160, H256, U64}, + alloy::{ + self, + primitives::{Address, Bytes, LogData, B256}, + rpc::types::{Block, Log}, + }, + rand::{self, Rng}, EthereumCall, LightEthereumBlock, }, slog::{self, o, Logger}, @@ -14,15 +19,55 @@ use crate::{ trigger::{EthereumBlockTriggerType, EthereumTrigger, LogRef}, }; +pub trait Random { + fn random() -> Self; +} + +impl Random for B256 { + fn random() -> Self { + let mut rng = rand::rng(); + let mut bytes = [0u8; 32]; + rng.fill(&mut bytes); + Self::from(bytes) + } +} + +impl Random for Address { + fn random() -> Self { + let mut rng = rand::rng(); + let mut bytes = [0u8; 20]; + rng.fill(&mut bytes); + Self::from(bytes) + } +} + +fn create_log(tx_index: u64, log_index: u64) -> Arc { + let log = Log { + inner: alloy::primitives::Log { + address: Address::default(), + data: LogData::new_unchecked(vec![], Bytes::from(vec![])), + }, + block_hash: Some(B256::ZERO), + block_number: Some(0), + block_timestamp: Some(0), + transaction_hash: Some(B256::ZERO), + transaction_index: Some(tx_index), + log_index: Some(log_index), + removed: false, + }; + + Arc::new(log) +} + #[test] fn test_trigger_ordering() { let block1 = EthereumTrigger::Block( - BlockPtr::from((H256::random(), 1u64)), + BlockPtr::from((B256::random(), 1u64)), EthereumBlockTriggerType::End, ); let block2 = EthereumTrigger::Block( - BlockPtr::from((H256::random(), 0u64)), + BlockPtr::from((B256::random(), 0u64)), EthereumBlockTriggerType::WithCallTo(Address::random()), ); @@ -32,7 +77,7 @@ fn test_trigger_ordering() { let mut call2 = EthereumCall::default(); call2.transaction_index = 2; - call2.input = Bytes(vec![0]); + call2.input = Bytes::from(vec![0]); let call2 = EthereumTrigger::Call(Arc::new(call2)); let mut call3 = EthereumCall::default(); @@ -43,25 +88,9 @@ fn test_trigger_ordering() { let mut call4 = EthereumCall::default(); call4.transaction_index = 2; // different than call2 so they don't get mistaken as the same - call4.input = Bytes(vec![1]); + call4.input = Bytes::from(vec![1]); let call4 = EthereumTrigger::Call(Arc::new(call4)); - fn create_log(tx_index: u64, log_index: u64) -> Arc { - Arc::new(Log { - address: H160::default(), - topics: vec![], - data: Bytes::default(), - block_hash: Some(H256::zero()), - block_number: Some(U64::zero()), - transaction_hash: Some(H256::zero()), - transaction_index: Some(tx_index.into()), - log_index: Some(log_index.into()), - transaction_log_index: Some(log_index.into()), - log_type: Some("".into()), - removed: Some(false), - }) - } - // Event with transaction_index 1 and log_index 0; // should be the first element after sorting let log1 = EthereumTrigger::Log(LogRef::FullLog(create_log(1, 0), None)); @@ -92,13 +121,9 @@ fn test_trigger_ordering() { let logger = Logger::root(slog::Discard, o!()); - let mut b: LightEthereumBlock = Default::default(); + let b = Block::default(); - // This is necessary because inside of BlockWithTriggers::new - // there's a log for both fields. So just using Default above - // gives None on them. - b.number = Some(Default::default()); - b.hash = Some(Default::default()); + let b = LightEthereumBlock::new(graph::components::ethereum::AnyBlock::from(b)); // Test that `BlockWithTriggers` sorts the triggers. let block_with_triggers = BlockWithTriggers::::new( @@ -118,12 +143,12 @@ fn test_trigger_ordering() { #[test] fn test_trigger_dedup() { let block1 = EthereumTrigger::Block( - BlockPtr::from((H256::random(), 1u64)), + BlockPtr::from((B256::random(), 1u64)), EthereumBlockTriggerType::End, ); let block2 = EthereumTrigger::Block( - BlockPtr::from((H256::random(), 0u64)), + BlockPtr::from((B256::random(), 0u64)), EthereumBlockTriggerType::WithCallTo(Address::random()), ); @@ -147,22 +172,6 @@ fn test_trigger_dedup() { call4.transaction_index = 2; let call4 = EthereumTrigger::Call(Arc::new(call4)); - fn create_log(tx_index: u64, log_index: u64) -> Arc { - Arc::new(Log { - address: H160::default(), - topics: vec![], - data: Bytes::default(), - block_hash: Some(H256::zero()), - block_number: Some(U64::zero()), - transaction_hash: Some(H256::zero()), - transaction_index: Some(tx_index.into()), - log_index: Some(log_index.into()), - transaction_log_index: Some(log_index.into()), - log_type: Some("".into()), - removed: Some(false), - }) - } - let log1 = EthereumTrigger::Log(LogRef::FullLog(create_log(1, 0), None)); let log2 = EthereumTrigger::Log(LogRef::FullLog(create_log(1, 1), None)); let log3 = EthereumTrigger::Log(LogRef::FullLog(create_log(2, 5), None)); @@ -190,13 +199,11 @@ fn test_trigger_dedup() { let logger = Logger::root(slog::Discard, o!()); - let mut b: LightEthereumBlock = Default::default(); + #[allow(unused_variables)] + let b = Block::default(); - // This is necessary because inside of BlockWithTriggers::new - // there's a log for both fields. So just using Default above - // gives None on them. - b.number = Some(Default::default()); - b.hash = Some(Default::default()); + #[allow(unreachable_code)] + let b = LightEthereumBlock::new(graph::components::ethereum::AnyBlock::from(b)); // Test that `BlockWithTriggers` sorts the triggers. let block_with_triggers = BlockWithTriggers::::new( diff --git a/chain/ethereum/src/transport.rs b/chain/ethereum/src/transport.rs index ef571efacb8..16f62c8a4dc 100644 --- a/chain/ethereum/src/transport.rs +++ b/chain/ethereum/src/transport.rs @@ -1,35 +1,36 @@ use graph::components::network_provider::ProviderName; -use graph::endpoint::{EndpointMetrics, RequestLabels}; -use jsonrpc_core::types::Call; -use jsonrpc_core::Value; - -use web3::transports::{http, ipc, ws}; -use web3::RequestId; - +use graph::endpoint::{ConnectionType, EndpointMetrics, RequestLabels}; +use graph::prelude::alloy::rpc::json_rpc::{RequestPacket, ResponsePacket}; use graph::prelude::*; use graph::url::Url; -use std::future::Future; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tower::Service; + +use alloy::transports::{TransportError, TransportFut}; -/// Abstraction over the different web3 transports. +use graph::prelude::alloy::transports::{http::Http, ipc::IpcConnect, ws::WsConnect}; + +/// Abstraction over different transport types for Alloy providers. #[derive(Clone, Debug)] pub enum Transport { RPC { - client: http::Http, + client: alloy::rpc::client::RpcClient, metrics: Arc, provider: ProviderName, + url: String, }, - IPC(ipc::Ipc), - WS(ws::WebSocket), + IPC(IpcConnect), + WS(WsConnect), } impl Transport { /// Creates an IPC transport. #[cfg(unix)] pub async fn new_ipc(ipc: &str) -> Self { - ipc::Ipc::new(ipc) - .await - .map(Transport::IPC) - .expect("Failed to connect to Ethereum IPC") + let transport = IpcConnect::new(ipc.to_string()); + + Transport::IPC(transport) } #[cfg(not(unix))] @@ -39,16 +40,12 @@ impl Transport { /// Creates a WebSocket transport. pub async fn new_ws(ws: &str) -> Self { - ws::WebSocket::new(ws) - .await - .map(Transport::WS) - .expect("Failed to connect to Ethereum WS") + let transport = WsConnect::new(ws.to_string()); + + Transport::WS(transport) } /// Creates a JSON-RPC over HTTP transport. - /// - /// Note: JSON-RPC over HTTP doesn't always support subscribing to new - /// blocks (one such example is Infura's HTTP endpoint). pub fn new_rpc( rpc: Url, headers: graph::http::HeaderMap, @@ -61,85 +58,85 @@ impl Transport { .build() .unwrap(); + let rpc_url = rpc.to_string(); + + // Create HTTP transport with metrics collection + let http_transport = Http::with_client(client, rpc); + let metrics_transport = + MetricsHttp::new(http_transport, metrics.clone(), provider.as_ref().into()); + let rpc_client = alloy::rpc::client::RpcClient::new(metrics_transport, false); + Transport::RPC { - client: http::Http::with_client(client, rpc), + client: rpc_client, metrics, provider: provider.as_ref().into(), + url: rpc_url, } } } -impl web3::Transport for Transport { - type Out = Pin> + Send + 'static>>; - - fn prepare(&self, method: &str, params: Vec) -> (RequestId, Call) { - match self { - Transport::RPC { - client, - metrics: _, - provider: _, - } => client.prepare(method, params), - Transport::IPC(ipc) => ipc.prepare(method, params), - Transport::WS(ws) => ws.prepare(method, params), +/// Custom HTTP transport wrapper that collects metrics +#[derive(Clone)] +pub struct MetricsHttp { + inner: Http, + metrics: Arc, + provider: ProviderName, +} + +impl MetricsHttp { + pub fn new( + inner: Http, + metrics: Arc, + provider: ProviderName, + ) -> Self { + Self { + inner, + metrics, + provider, } } +} + +// Implement tower::Service trait for MetricsHttp to intercept RPC calls +impl Service for MetricsHttp { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } - fn send(&self, id: RequestId, request: Call) -> Self::Out { - match self { - Transport::RPC { - client, - metrics, + fn call(&mut self, request: RequestPacket) -> Self::Future { + let metrics = self.metrics.clone(); + let provider = self.provider.clone(); + let mut inner = self.inner.clone(); + + Box::pin(async move { + // Extract method name from request + let method = match &request { + RequestPacket::Single(req) => req.method().to_string(), + RequestPacket::Batch(reqs) => reqs + .first() + .map(|r| r.method().to_string()) + .unwrap_or_else(|| "batch".to_string()), + }; + + let labels = RequestLabels { provider, - } => { - let metrics = metrics.cheap_clone(); - let client = client.clone(); - let method = match request { - Call::MethodCall(ref m) => m.method.as_str(), - _ => "unknown", - }; - - let labels = RequestLabels { - provider: provider.clone(), - req_type: method.into(), - conn_type: graph::endpoint::ConnectionType::Rpc, - }; - let out = async move { - let out = client.send(id, request).await; - match out { - Ok(_) => metrics.success(&labels), - Err(_) => metrics.failure(&labels), - } - - out - }; - - Box::pin(out) + req_type: method.into(), + conn_type: ConnectionType::Rpc, + }; + + // Call inner transport and track metrics + let result = inner.call(request).await; + + match &result { + Ok(_) => metrics.success(&labels), + Err(_) => metrics.failure(&labels), } - Transport::IPC(ipc) => Box::pin(ipc.send(id, request)), - Transport::WS(ws) => Box::pin(ws.send(id, request)), - } - } -} -impl web3::BatchTransport for Transport { - type Batch = Box< - dyn Future>, web3::error::Error>> - + Send - + Unpin, - >; - - fn send_batch(&self, requests: T) -> Self::Batch - where - T: IntoIterator, - { - match self { - Transport::RPC { - client, - metrics: _, - provider: _, - } => Box::new(client.send_batch(requests)), - Transport::IPC(ipc) => Box::new(ipc.send_batch(requests)), - Transport::WS(ws) => Box::new(ws.send_batch(requests)), - } + result + }) } } diff --git a/chain/ethereum/src/trigger.rs b/chain/ethereum/src/trigger.rs index bbbaa69a8d2..f969fa7063c 100644 --- a/chain/ethereum/src/trigger.rs +++ b/chain/ethereum/src/trigger.rs @@ -1,23 +1,20 @@ use async_trait::async_trait; +use graph::abi; use graph::blockchain::MappingTriggerTrait; use graph::blockchain::TriggerData; +use graph::components::ethereum::AnyTransaction; use graph::data::subgraph::API_VERSION_0_0_2; use graph::data::subgraph::API_VERSION_0_0_6; use graph::data::subgraph::API_VERSION_0_0_7; use graph::data_source::common::DeclaredCall; -use graph::prelude::ethabi::ethereum_types::H160; -use graph::prelude::ethabi::ethereum_types::H256; -use graph::prelude::ethabi::ethereum_types::U128; -use graph::prelude::ethabi::ethereum_types::U256; -use graph::prelude::ethabi::ethereum_types::U64; -use graph::prelude::ethabi::Address; -use graph::prelude::ethabi::LogParam; -use graph::prelude::web3::types::Block; -use graph::prelude::web3::types::Log; -use graph::prelude::web3::types::Transaction; -use graph::prelude::web3::types::TransactionReceipt; +use graph::prelude::alloy::consensus::Transaction as TransactionTrait; +use graph::prelude::alloy::network::AnyTransactionReceipt as AlloyTransactionReceipt; +use graph::prelude::alloy::network::TransactionResponse; +use graph::prelude::alloy::primitives::{Address, B256, U256}; +use graph::prelude::alloy::rpc::types::Log; use graph::prelude::BlockNumber; use graph::prelude::BlockPtr; +use graph::prelude::LightEthereumBlock; use graph::prelude::{CheapClone, EthereumCall}; use graph::runtime::asc_new; use graph::runtime::gas::GasCounter; @@ -38,26 +35,23 @@ use crate::runtime::abi::AscEthereumTransaction_0_0_1; use crate::runtime::abi::AscEthereumTransaction_0_0_2; use crate::runtime::abi::AscEthereumTransaction_0_0_6; -// ETHDEP: This should be defined in only one place. -type LightEthereumBlock = Block; - -static U256_DEFAULT: U256 = U256::zero(); +static U256_DEFAULT: U256 = U256::ZERO; pub enum MappingTrigger { Log { block: Arc, - transaction: Arc, + transaction: Arc, log: Arc, - params: Vec, - receipt: Option>, + params: Vec, + receipt: Option>, calls: Vec, }, Call { block: Arc, - transaction: Arc, + transaction: Arc, call: Arc, - inputs: Vec, - outputs: Vec, + inputs: Vec, + outputs: Vec, }, Block { block: Arc, @@ -65,7 +59,7 @@ pub enum MappingTrigger { } impl MappingTriggerTrait for MappingTrigger { - fn error_context(&self) -> std::string::String { + fn error_context(&self) -> String { let transaction_id = match self { MappingTrigger::Log { log, .. } => log.transaction_hash, MappingTrigger::Call { call, .. } => call.transaction_hash, @@ -85,15 +79,15 @@ impl std::fmt::Debug for MappingTrigger { #[derive(Debug)] enum MappingTriggerWithoutBlock { Log { - _transaction: Arc, + _transaction: Arc, _log: Arc, - _params: Vec, + _params: Vec, }, Call { - _transaction: Arc, + _transaction: Arc, _call: Arc, - _inputs: Vec, - _outputs: Vec, + _inputs: Vec, + _outputs: Vec, }, Block, } @@ -238,13 +232,13 @@ impl ToAscPtr for MappingTrigger { #[derive(Clone, Debug)] pub struct LogPosition { pub index: usize, - pub receipt: Arc, + pub receipt: Arc, pub requires_transaction_receipt: bool, } #[derive(Clone, Debug)] pub enum LogRef { - FullLog(Arc, Option>), + FullLog(Arc, Option>), LogPosition(LogPosition), } @@ -252,7 +246,7 @@ impl LogRef { pub fn log(&self) -> &Log { match self { LogRef::FullLog(log, _) => log.as_ref(), - LogRef::LogPosition(pos) => pos.receipt.logs.get(pos.index).unwrap(), + LogRef::LogPosition(pos) => pos.receipt.logs().get(pos.index).unwrap(), } } @@ -262,7 +256,7 @@ impl LogRef { /// For `LogPosition` variants, only returns the receipt if the /// `requires_transaction_receipt` flag is true, otherwise returns None /// even though the receipt is stored internally. - pub fn receipt(&self) -> Option<&Arc> { + pub fn receipt(&self) -> Option<&Arc> { match self { LogRef::FullLog(_, receipt) => receipt.as_ref(), LogRef::LogPosition(pos) => { @@ -275,28 +269,28 @@ impl LogRef { } } - pub fn log_index(&self) -> Option { + pub fn log_index(&self) -> Option { self.log().log_index } - pub fn transaction_index(&self) -> Option { + pub fn transaction_index(&self) -> Option { self.log().transaction_index } - fn transaction_hash(&self) -> Option { + fn transaction_hash(&self) -> Option { self.log().transaction_hash } - pub fn block_hash(&self) -> Option { + pub fn block_hash(&self) -> Option { self.log().block_hash } - pub fn block_number(&self) -> Option { + pub fn block_number(&self) -> Option { self.log().block_number } - pub fn address(&self) -> &H160 { - &self.log().address + pub fn address(&self) -> &Address { + &self.log().inner.address } } @@ -339,14 +333,14 @@ impl EthereumTrigger { EthereumTrigger::Block(block_ptr, _) => block_ptr.number, EthereumTrigger::Call(call) => call.block_number, EthereumTrigger::Log(log_ref) => { - i32::try_from(log_ref.block_number().unwrap().as_u64()).unwrap() + i32::try_from(log_ref.block_number().unwrap()).unwrap() } } } - pub fn block_hash(&self) -> H256 { + pub fn block_hash(&self) -> B256 { match self { - EthereumTrigger::Block(block_ptr, _) => block_ptr.hash_as_h256(), + EthereumTrigger::Block(block_ptr, _) => block_ptr.hash.as_b256(), EthereumTrigger::Call(call) => call.block_hash, EthereumTrigger::Log(log_ref) => log_ref.block_hash().unwrap(), } @@ -359,7 +353,7 @@ impl EthereumTrigger { Some(address) } EthereumTrigger::Call(call) => Some(&call.to), - EthereumTrigger::Log(log_ref) => Some(&log_ref.address()), + EthereumTrigger::Log(log_ref) => Some(log_ref.address()), // Unfiltered block triggers match any data source address. EthereumTrigger::Block(_, EthereumBlockTriggerType::End) => None, EthereumTrigger::Block(_, EthereumBlockTriggerType::Start) => None, @@ -390,23 +384,21 @@ impl Ord for EthereumTrigger { // Calls vs. events are logged by their tx index; // if they are from the same transaction, events come first (Self::Call(a), Self::Log(b)) - if a.transaction_index == b.transaction_index().unwrap().as_u64() => + if a.transaction_index == b.transaction_index().unwrap() => { Ordering::Greater } (Self::Log(a), Self::Call(b)) - if a.transaction_index().unwrap().as_u64() == b.transaction_index => + if a.transaction_index().unwrap() == b.transaction_index => { Ordering::Less } - (Self::Call(a), Self::Log(b)) => a - .transaction_index - .cmp(&b.transaction_index().unwrap().as_u64()), - (Self::Log(a), Self::Call(b)) => a - .transaction_index() - .unwrap() - .as_u64() - .cmp(&b.transaction_index), + (Self::Call(a), Self::Log(b)) => { + a.transaction_index.cmp(&b.transaction_index().unwrap()) + } + (Self::Log(a), Self::Call(b)) => { + a.transaction_index().unwrap().cmp(&b.transaction_index) + } } } } @@ -437,137 +429,140 @@ impl TriggerData for EthereumTrigger { } fn address_match(&self) -> Option<&[u8]> { - self.address().map(|address| address.as_bytes()) + self.address().map(|address| address.as_slice()) } } /// Ethereum block data. #[derive(Clone, Debug)] pub struct EthereumBlockData<'a> { - block: &'a Block, + block: &'a LightEthereumBlock, } -impl<'a> From<&'a Block> for EthereumBlockData<'a> { - fn from(block: &'a Block) -> EthereumBlockData<'a> { +impl<'a> From<&'a LightEthereumBlock> for EthereumBlockData<'a> { + fn from(block: &'a LightEthereumBlock) -> EthereumBlockData<'a> { EthereumBlockData { block } } } impl<'a> EthereumBlockData<'a> { - pub fn hash(&self) -> &H256 { - self.block.hash.as_ref().unwrap() + pub fn hash(&self) -> &B256 { + &self.block.inner().header.hash } - pub fn parent_hash(&self) -> &H256 { - &self.block.parent_hash + pub fn parent_hash(&self) -> &B256 { + &self.block.inner().header.parent_hash } - pub fn uncles_hash(&self) -> &H256 { - &self.block.uncles_hash + pub fn uncles_hash(&self) -> &B256 { + &self.block.inner().header.ommers_hash } - pub fn author(&self) -> &H160 { - &self.block.author + pub fn author(&self) -> &Address { + &self.block.inner().header.beneficiary } - pub fn state_root(&self) -> &H256 { - &self.block.state_root + pub fn state_root(&self) -> &B256 { + &self.block.inner().header.state_root } - pub fn transactions_root(&self) -> &H256 { - &self.block.transactions_root + pub fn transactions_root(&self) -> &B256 { + &self.block.inner().header.transactions_root } - pub fn receipts_root(&self) -> &H256 { - &self.block.receipts_root + pub fn receipts_root(&self) -> &B256 { + &self.block.inner().header.receipts_root } - pub fn number(&self) -> U64 { - self.block.number.unwrap() + pub fn number(&self) -> u64 { + self.block.number_u64() } - pub fn gas_used(&self) -> &U256 { - &self.block.gas_used + pub fn gas_used(&self) -> u64 { + self.block.inner().header.gas_used } - pub fn gas_limit(&self) -> &U256 { - &self.block.gas_limit + pub fn gas_limit(&self) -> u64 { + self.block.inner().header.gas_limit } - pub fn timestamp(&self) -> &U256 { - &self.block.timestamp + pub fn timestamp(&self) -> u64 { + self.block.inner().header.timestamp } pub fn difficulty(&self) -> &U256 { - &self.block.difficulty + &self.block.inner().header.difficulty } pub fn total_difficulty(&self) -> &U256 { self.block + .inner() + .header .total_difficulty .as_ref() .unwrap_or(&U256_DEFAULT) } pub fn size(&self) -> &Option { - &self.block.size + &self.block.inner().header.size } - pub fn base_fee_per_gas(&self) -> &Option { - &self.block.base_fee_per_gas + pub fn base_fee_per_gas(&self) -> &Option { + &self.block.inner().header.base_fee_per_gas } } /// Ethereum transaction data. #[derive(Clone, Debug)] pub struct EthereumTransactionData<'a> { - tx: &'a Transaction, + tx: &'a AnyTransaction, + base_fee_per_gas: Option, } impl<'a> EthereumTransactionData<'a> { // We don't implement `From` because it causes confusion with the `from` // accessor method - fn new(tx: &'a Transaction) -> EthereumTransactionData<'a> { - EthereumTransactionData { tx } + fn new(tx: &'a AnyTransaction, base_fee_per_gas: Option) -> EthereumTransactionData<'a> { + EthereumTransactionData { + tx, + base_fee_per_gas, + } } - pub fn hash(&self) -> &H256 { - &self.tx.hash + pub fn hash(&self) -> B256 { + self.tx.tx_hash() } - pub fn index(&self) -> U128 { - self.tx.transaction_index.unwrap().as_u64().into() + pub fn index(&self) -> u64 { + self.tx.transaction_index.unwrap() } - pub fn from(&self) -> &H160 { - // unwrap: this is always `Some` for txns that have been mined - // (see https://github.com/tomusdrw/rust-web3/pull/407) - self.tx.from.as_ref().unwrap() + pub fn from(&self) -> Address { + self.tx.from() } - pub fn to(&self) -> &Option { - &self.tx.to + pub fn to(&self) -> Option
{ + self.tx.to() } - pub fn value(&self) -> &U256 { - &self.tx.value + pub fn value(&self) -> U256 { + self.tx.value() } - pub fn gas_limit(&self) -> &U256 { - &self.tx.gas + pub fn gas_limit(&self) -> u64 { + self.tx.gas_limit() } - pub fn gas_price(&self) -> &U256 { - // EIP-1559 made this optional. - self.tx.gas_price.as_ref().unwrap_or(&U256_DEFAULT) + pub fn gas_price(&self) -> u128 { + self.tx.effective_gas_price(self.base_fee_per_gas) } pub fn input(&self) -> &[u8] { - &self.tx.input.0 + self.tx.input() } - pub fn nonce(&self) -> &U256 { - &self.tx.nonce + pub fn nonce(&self) -> u64 { + self.tx.nonce() } } @@ -576,45 +571,46 @@ impl<'a> EthereumTransactionData<'a> { pub struct EthereumEventData<'a> { pub block: EthereumBlockData<'a>, pub transaction: EthereumTransactionData<'a>, - pub params: &'a [LogParam], + pub params: &'a [abi::DynSolParam], log: &'a Log, } impl<'a> EthereumEventData<'a> { pub fn new( - block: &'a Block, - tx: &'a Transaction, + block: &'a LightEthereumBlock, + tx: &'a AnyTransaction, log: &'a Log, - params: &'a [LogParam], + params: &'a [abi::DynSolParam], ) -> Self { EthereumEventData { block: EthereumBlockData::from(block), - transaction: EthereumTransactionData::new(tx), + transaction: EthereumTransactionData::new(tx, block.base_fee_per_gas()), log, params, } } pub fn address(&self) -> &Address { - &self.log.address + &self.log.inner.address } - pub fn log_index(&self) -> &U256 { - self.log.log_index.as_ref().unwrap_or(&U256_DEFAULT) + pub fn log_index(&self) -> u64 { + self.log.log_index.unwrap_or(0) } - pub fn transaction_log_index(&self) -> &U256 { + pub fn transaction_log_index(&self) -> u64 { // We purposely use the `log_index` here. Geth does not support // `transaction_log_index`, and subgraphs that use it only care that // it identifies the log, the specific value is not important. Still // this will change the output of subgraphs that use this field. // // This was initially changed in commit b95c6953 - self.log.log_index.as_ref().unwrap_or(&U256_DEFAULT) + self.log.log_index.unwrap_or(0) } - pub fn log_type(&self) -> &Option { - &self.log.log_type + pub fn log_type(&self) -> Option { + // This field was present in old rust-web3 Block, but alloy doesn't have it. + None } } @@ -623,22 +619,22 @@ impl<'a> EthereumEventData<'a> { pub struct EthereumCallData<'a> { pub block: EthereumBlockData<'a>, pub transaction: EthereumTransactionData<'a>, - pub inputs: &'a [LogParam], - pub outputs: &'a [LogParam], + pub inputs: &'a [abi::DynSolParam], + pub outputs: &'a [abi::DynSolParam], call: &'a EthereumCall, } impl<'a> EthereumCallData<'a> { fn new( - block: &'a Block, - transaction: &'a Transaction, + block: &'a LightEthereumBlock, + transaction: &'a AnyTransaction, call: &'a EthereumCall, - inputs: &'a [LogParam], - outputs: &'a [LogParam], + inputs: &'a [abi::DynSolParam], + outputs: &'a [abi::DynSolParam], ) -> EthereumCallData<'a> { EthereumCallData { block: EthereumBlockData::from(block), - transaction: EthereumTransactionData::new(transaction), + transaction: EthereumTransactionData::new(transaction, block.base_fee_per_gas()), inputs, outputs, call, diff --git a/chain/near/src/codec.rs b/chain/near/src/codec.rs index 6f0f2f7af4d..905495e7ae3 100644 --- a/chain/near/src/codec.rs +++ b/chain/near/src/codec.rs @@ -7,18 +7,17 @@ pub mod pbcodec; pub mod substreams_triggers; use graph::{ - blockchain::Block as BlockchainBlock, - blockchain::{BlockPtr, BlockTime}, - prelude::{hex, web3::types::H256, BlockNumber}, + blockchain::{Block as BlockchainBlock, BlockPtr, BlockTime}, + prelude::{alloy::primitives::B256, hex, BlockNumber}, }; use std::convert::TryFrom; use std::fmt::LowerHex; pub use pbcodec::*; -impl From<&CryptoHash> for H256 { +impl From<&CryptoHash> for B256 { fn from(input: &CryptoHash) -> Self { - H256::from_slice(&input.bytes) + B256::from_slice(&input.bytes) } } @@ -31,7 +30,7 @@ impl LowerHex for &CryptoHash { impl BlockHeader { pub fn parent_ptr(&self) -> Option { match (self.prev_hash.as_ref(), self.prev_height) { - (Some(hash), number) => Some(BlockPtr::from((H256::from(hash), number))), + (Some(hash), number) => Some(BlockPtr::from((B256::from(hash), number))), _ => None, } } @@ -39,7 +38,7 @@ impl BlockHeader { impl<'a> From<&'a BlockHeader> for BlockPtr { fn from(b: &'a BlockHeader) -> BlockPtr { - BlockPtr::from((H256::from(b.hash.as_ref().unwrap()), b.height)) + BlockPtr::from((B256::from(b.hash.as_ref().unwrap()), b.height)) } } diff --git a/chain/near/src/trigger.rs b/chain/near/src/trigger.rs index d604f97bc14..a36e640b7c1 100644 --- a/chain/near/src/trigger.rs +++ b/chain/near/src/trigger.rs @@ -3,8 +3,8 @@ use graph::blockchain::Block; use graph::blockchain::MappingTriggerTrait; use graph::blockchain::TriggerData; use graph::derive::CheapClone; +use graph::prelude::alloy::primitives::B256; use graph::prelude::hex; -use graph::prelude::web3::types::H256; use graph::prelude::BlockNumber; use graph::runtime::HostExportError; use graph::runtime::{asc_new, gas::GasCounter, AscHeap, AscPtr}; @@ -80,10 +80,10 @@ impl NearTrigger { } } - pub fn block_hash(&self) -> H256 { + pub fn block_hash(&self) -> B256 { match self { - NearTrigger::Block(block) => block.ptr().hash_as_h256(), - NearTrigger::Receipt(receipt) => receipt.block.ptr().hash_as_h256(), + NearTrigger::Block(block) => block.ptr().hash.as_b256(), + NearTrigger::Receipt(receipt) => receipt.block.ptr().hash.as_b256(), } } diff --git a/graph/Cargo.toml b/graph/Cargo.toml index 1cec1c48f25..796b3dee87e 100644 --- a/graph/Cargo.toml +++ b/graph/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] +alloy = { workspace = true } base64 = "=0.21.7" anyhow = "1.0" async-trait = { workspace = true } @@ -83,12 +84,6 @@ parking_lot = "0.12.5" itertools = "0.14.0" defer = "0.2" -# Our fork contains patches to make some fields optional for Celo and Fantom compatibility. -# Without the "arbitrary_precision" feature, we get the error `data did not match any variant of untagged enum Response`. -web3 = { git = "https://github.com/graphprotocol/rust-web3", branch = "graph-patches-onto-0.18", features = [ - "arbitrary_precision", - "test", -] } serde_plain = "1.0.2" csv = "1.3.1" object_store = { version = "0.12.4", features = ["gcp"] } diff --git a/graph/src/abi/event_ext.rs b/graph/src/abi/event_ext.rs new file mode 100644 index 00000000000..94088dfcaae --- /dev/null +++ b/graph/src/abi/event_ext.rs @@ -0,0 +1,169 @@ +use std::collections::VecDeque; + +use alloy::json_abi::Event; +use alloy::rpc::types::Log; +use anyhow::anyhow; +use anyhow::Result; + +use crate::abi::{DynSolParam, DynSolValue}; + +pub trait EventExt { + fn decode_log(&self, log: &Log) -> Result>; +} + +impl EventExt for Event { + fn decode_log(&self, log: &Log) -> Result> { + let log_data = log.data(); + let decoded_event = alloy::dyn_abi::EventExt::decode_log(self, &log_data)?; + let mut indexed: VecDeque = decoded_event.indexed.into(); + let mut body: VecDeque = decoded_event.body.into(); + + if self.inputs.len() != indexed.len() + body.len() { + return Err(anyhow!( + "unexpected number of decoded event inputs; expected {}, got {}", + self.inputs.len(), + indexed.len() + body.len(), + )); + } + + let mut decoded_params = Vec::with_capacity(self.inputs.len()); + + for input in &self.inputs { + decoded_params.push(DynSolParam { + name: input.name.clone(), + value: { + if input.indexed { + indexed.pop_front().unwrap() + } else { + body.pop_front().unwrap() + } + }, + }); + } + + Ok(decoded_params) + } +} + +#[cfg(test)] +mod tests { + use alloy::dyn_abi::DynSolValue; + use alloy::primitives::{LogData, U256}; + + use super::*; + + fn make_log(topics: &[[u8; 32]], data: Vec) -> Log { + Log { + inner: alloy::primitives::Log { + address: [1; 20].into(), + data: LogData::new_unchecked(topics.iter().map(Into::into).collect(), data.into()), + }, + block_hash: None, + block_number: None, + block_timestamp: None, + transaction_hash: None, + transaction_index: None, + log_index: None, + removed: false, + } + } + + #[test] + fn decode_log_no_topic_0() { + let event = Event::parse("event X(uint256 indexed a, bytes32 b)").unwrap(); + let a = U256::from(10).to_be_bytes::<32>(); + let b = DynSolValue::FixedBytes([10; 32].into(), 32).abi_encode(); + + let log = make_log(&[a], b); + let err = event.decode_log(&log).unwrap_err(); + + assert_eq!( + err.to_string(), + "invalid log topic list length: expected 2 topics, got 1", + ); + } + + #[test] + fn decode_log_invalid_topic_0() { + let event = Event::parse("event X(uint256 indexed a, bytes32 b)").unwrap(); + let a = U256::from(10).to_be_bytes::<32>(); + let b = DynSolValue::FixedBytes([10; 32].into(), 32).abi_encode(); + + let log = make_log(&[[0; 32], a], b); + let err = event.decode_log(&log).unwrap_err(); + + assert!(err.to_string().starts_with("invalid event signature:")); + } + + #[test] + fn decode_log_success() { + let event = Event::parse("event X(uint256 indexed a, bytes32 b)").unwrap(); + let topic_0 = event.selector().0; + let a = U256::from(10).to_be_bytes::<32>(); + let b = DynSolValue::FixedBytes([10; 32].into(), 32).abi_encode(); + + let log = make_log(&[topic_0, a], b); + let resp = event.decode_log(&log).unwrap(); + + assert_eq!( + resp, + vec![ + DynSolParam { + name: "a".to_owned(), + value: DynSolValue::Uint(U256::from(10), 256), + }, + DynSolParam { + name: "b".to_owned(), + value: DynSolValue::FixedBytes([10; 32].into(), 32), + } + ], + ); + } + + #[test] + fn decode_log_too_many_topics() { + let event = Event::parse("event X(uint256 indexed a, bytes32 b)").unwrap(); + let topic_0 = event.selector().0; + let a = U256::from(10).to_be_bytes::<32>(); + let b = DynSolValue::FixedBytes([10; 32].into(), 32).abi_encode(); + + let log = make_log(&[topic_0, a, a, a, a], b); + let err = event.decode_log(&log).unwrap_err(); + + assert_eq!( + err.to_string(), + "invalid log topic list length: expected 2 topics, got 5" + ); + } + + #[test] + fn decode_log_when_indexed_param_is_not_the_first() { + let event = Event::parse("event X(uint256 a, uint256 indexed b, bytes32 c)").unwrap(); + let topic_0 = event.selector().0; + let a = DynSolValue::Uint(U256::from(10), 32); + let b = U256::from(20).to_be_bytes::<32>(); + let c = DynSolValue::FixedBytes([30; 32].into(), 32); + let data = DynSolValue::Tuple(vec![a, c]).abi_encode(); + + let log = make_log(&[topic_0, b], data); + let resp = event.decode_log(&log).unwrap(); + + assert_eq!( + resp, + vec![ + DynSolParam { + name: "a".to_owned(), + value: DynSolValue::Uint(U256::from(10), 256), + }, + DynSolParam { + name: "b".to_owned(), + value: DynSolValue::Uint(U256::from(20), 256), + }, + DynSolParam { + name: "c".to_owned(), + value: DynSolValue::FixedBytes([30; 32].into(), 32), + } + ], + ); + } +} diff --git a/graph/src/abi/function_ext.rs b/graph/src/abi/function_ext.rs new file mode 100644 index 00000000000..3264dd10a35 --- /dev/null +++ b/graph/src/abi/function_ext.rs @@ -0,0 +1,303 @@ +use std::borrow::Cow; + +use alloy::dyn_abi::DynSolType; +use alloy::dyn_abi::DynSolValue; +use alloy::dyn_abi::Specifier; +use alloy::json_abi::Function; +use alloy::json_abi::Param; +use anyhow::anyhow; +use anyhow::Result; +use itertools::Itertools; + +use crate::abi::DynSolValueExt; + +pub trait FunctionExt { + /// Returns the signature of this function in the following formats: + /// - if the function has no outputs: `$name($($inputs),*)` + /// - if the function has outputs: `$name($($inputs),*):($(outputs),*)` + /// + /// Examples: + /// - `functionName()` + /// - `functionName():(uint256)` + /// - `functionName(bool):(uint256,string)` + /// - `functionName(uint256,bytes32):(string,uint256)` + fn signature_compat(&self) -> String; + + /// ABI-decodes the given data according to the function's input types. + fn abi_decode_input(&self, data: &[u8]) -> Result>; + + /// ABI-decodes the given data according to the function's output types. + fn abi_decode_output(&self, data: &[u8]) -> Result>; + + /// ABI-encodes the given values, prefixed by the function's selector, if any. + /// + /// This behaviour is to ensure consistency with `ethabi`. + fn abi_encode_input(&self, values: &[DynSolValue]) -> Result>; +} + +impl FunctionExt for Function { + fn signature_compat(&self) -> String { + let name = &self.name; + let inputs = &self.inputs; + let outputs = &self.outputs; + + // This is what `alloy` uses internally when creating signatures. + const MAX_SOL_TYPE_LEN: usize = 32; + + let mut sig_cap = name.len() + 1 + inputs.len() * MAX_SOL_TYPE_LEN + 1; + + if !outputs.is_empty() { + sig_cap = sig_cap + 2 + outputs.len() * MAX_SOL_TYPE_LEN + 1; + } + + let mut sig = String::with_capacity(sig_cap); + + sig.push_str(&name); + signature_part(&inputs, &mut sig); + + if !outputs.is_empty() { + sig.push(':'); + signature_part(&outputs, &mut sig); + } + + sig + } + + fn abi_decode_input(&self, data: &[u8]) -> Result> { + (self as &dyn alloy::dyn_abi::FunctionExt) + .abi_decode_input(data) + .map_err(Into::into) + } + + fn abi_decode_output(&self, data: &[u8]) -> Result> { + (self as &dyn alloy::dyn_abi::FunctionExt) + .abi_decode_output(data) + .map_err(Into::into) + } + + fn abi_encode_input(&self, values: &[DynSolValue]) -> Result> { + let inputs = &self.inputs; + + if inputs.len() != values.len() { + return Err(anyhow!( + "unexpected number of values; expected {}, got {}", + inputs.len(), + values.len(), + )); + } + + let mut fixed_values = Vec::with_capacity(values.len()); + + for (i, input) in inputs.iter().enumerate() { + let ty = input.resolve()?; + let val = &values[i]; + + fixed_values.push(fix_type_size(&ty, val)?); + } + + if fixed_values.iter().all(|x| matches!(x, Cow::Borrowed(_))) { + return (self as &dyn alloy::dyn_abi::JsonAbiExt) + .abi_encode_input(values) + .map_err(Into::into); + } + + // Required because of `alloy::dyn_abi::JsonAbiExt::abi_encode_input` API; + let owned_fixed_values = fixed_values + .into_iter() + .map(|x| x.into_owned()) + .collect_vec(); + + (self as &dyn alloy::dyn_abi::JsonAbiExt) + .abi_encode_input(&owned_fixed_values) + .map_err(Into::into) + } +} + +// An efficient way to compute a part of the signature without new allocations. +fn signature_part(params: &[Param], out: &mut String) { + out.push('('); + + match params.len() { + 0 => {} + 1 => { + params[0].selector_type_raw(out); + } + n => { + params[0].selector_type_raw(out); + + for i in 1..n { + out.push(','); + params[i].selector_type_raw(out); + } + } + } + + out.push(')'); +} + +// Alloy is stricter in type checking than `ehtabi` and requires that the decoded values have +// exactly the same number of bits / bytes as the type used for checking. +// +// This is a problem because in some ASC conversions we lose the original number of bits / bytes +// if the actual data takes less memory. +// +// This method fixes that in a simple but not very cheap way, by encoding the value and trying +// to decode it again using the given type. The result fixes the number of bits / bytes in the +// decoded values, so we can use `alloy` methods that have strict type checking internally. +fn fix_type_size<'a>(ty: &DynSolType, val: &'a DynSolValue) -> Result> { + if val.matches(ty) { + return Ok(Cow::Borrowed(val)); + } + + if !val.type_check(ty) { + return Err(anyhow!( + "invalid value type; expected '{}', got '{:?}'", + ty.sol_type_name(), + val.sol_type_name(), + )); + } + + let bytes = val.abi_encode(); + let new_val = ty.abi_decode(&bytes)?; + + Ok(Cow::Owned(new_val)) +} + +#[cfg(test)] +mod tests { + use alloy::primitives::I256; + use alloy::primitives::U256; + + use super::*; + + fn s(f: &str) -> String { + Function::parse(f).unwrap().signature_compat() + } + + fn u256(u: u64) -> U256 { + U256::from(u) + } + + fn i256(i: i32) -> I256 { + I256::try_from(i).unwrap() + } + + #[test] + fn signature_compat_no_inputs_no_outputs() { + assert_eq!(s("x()"), "x()"); + } + + #[test] + fn signature_compat_one_input_no_outputs() { + assert_eq!(s("x(uint256 a)"), "x(uint256)"); + } + + #[test] + fn signature_compat_multiple_inputs_no_outputs() { + assert_eq!(s("x(uint256 a, bytes32 b)"), "x(uint256,bytes32)"); + } + + #[test] + fn signature_compat_no_inputs_one_output() { + assert_eq!(s("x() returns (uint256)"), "x():(uint256)"); + } + + #[test] + fn signature_compat_no_inputs_multiple_outputs() { + assert_eq!(s("x() returns (uint256, bytes32)"), "x():(uint256,bytes32)"); + } + + #[test] + fn signature_compat_multiple_inputs_multiple_outputs() { + assert_eq!( + s("x(bytes32 a, uint256 b) returns (uint256, bytes32)"), + "x(bytes32,uint256):(uint256,bytes32)", + ); + } + + #[test] + fn abi_decode_input() { + use DynSolValue::{Int, Tuple, Uint}; + + let f = Function::parse("x(uint256 a, int256 b)").unwrap(); + let data = Tuple(vec![Uint(u256(10), 256), Int(i256(-10), 256)]).abi_encode_params(); + let inputs = f.abi_decode_input(&data).unwrap(); + + assert_eq!(inputs, vec![Uint(u256(10), 256), Int(i256(-10), 256)]); + } + + #[test] + fn abi_decode_output() { + use DynSolValue::{Int, Tuple, Uint}; + + let f = Function::parse("x() returns (uint256 a, int256 b)").unwrap(); + let data = Tuple(vec![Uint(u256(10), 256), Int(i256(-10), 256)]).abi_encode_params(); + let outputs = f.abi_decode_output(&data).unwrap(); + + assert_eq!(outputs, vec![Uint(u256(10), 256), Int(i256(-10), 256)]); + } + + #[test] + fn abi_encode_input_no_values() { + let f = Function::parse("x(uint256 a, int256 b)").unwrap(); + let err = f.abi_encode_input(&[]).unwrap_err(); + + assert_eq!( + err.to_string(), + "unexpected number of values; expected 2, got 0", + ); + } + + #[test] + fn abi_encode_input_too_many_values() { + use DynSolValue::Bool; + + let f = Function::parse("x(uint256 a, int256 b)").unwrap(); + + let err = f + .abi_encode_input(&[Bool(true), Bool(false), Bool(true)]) + .unwrap_err(); + + assert_eq!( + err.to_string(), + "unexpected number of values; expected 2, got 3", + ); + } + + #[test] + fn abi_encode_input_invalid_types() { + use DynSolValue::Bool; + + let f = Function::parse("x(uint256 a, int256 b)").unwrap(); + let err = f.abi_encode_input(&[Bool(true), Bool(false)]).unwrap_err(); + assert!(err.to_string().starts_with("invalid value type;")); + } + + #[test] + fn abi_encode_success() { + use DynSolValue::{Bool, Uint}; + + let f = Function::parse("x(uint256 a, bool b)").unwrap(); + let a = Uint(u256(10), 256); + let b = Bool(true); + + let data = f.abi_encode_input(&[a.clone(), b.clone()]).unwrap(); + let inputs = f.abi_decode_input(&data[4..]).unwrap(); + + assert_eq!(inputs, vec![a, b]); + } + + #[test] + fn abi_encode_success_with_size_fix() { + use DynSolValue::{Int, Uint}; + + let f = Function::parse("x(uint256 a, int256 b)").unwrap(); + let a = Uint(u256(10), 32); + let b = Int(i256(-10), 32); + + let data = f.abi_encode_input(&[a, b]).unwrap(); + let inputs = f.abi_decode_input(&data[4..]).unwrap(); + + assert_eq!(inputs, vec![Uint(u256(10), 256), Int(i256(-10), 256)]); + } +} diff --git a/graph/src/abi/mod.rs b/graph/src/abi/mod.rs new file mode 100644 index 00000000000..9bedeb0e3b2 --- /dev/null +++ b/graph/src/abi/mod.rs @@ -0,0 +1,20 @@ +mod event_ext; +mod function_ext; +mod param; +mod value_ext; + +pub use alloy::dyn_abi::DynSolType; +pub use alloy::dyn_abi::DynSolValue; + +pub use alloy::json_abi::Event; +pub use alloy::json_abi::Function; +pub use alloy::json_abi::JsonAbi; +pub use alloy::json_abi::StateMutability; + +pub use alloy::primitives::I256; +pub use alloy::primitives::U256 as AlloyU256; + +pub use self::event_ext::EventExt; +pub use self::function_ext::FunctionExt; +pub use self::param::DynSolParam; +pub use self::value_ext::DynSolValueExt; diff --git a/graph/src/abi/param.rs b/graph/src/abi/param.rs new file mode 100644 index 00000000000..49e0f0878ea --- /dev/null +++ b/graph/src/abi/param.rs @@ -0,0 +1,7 @@ +use alloy::dyn_abi::DynSolValue; + +#[derive(Clone, Debug, PartialEq)] +pub struct DynSolParam { + pub name: String, + pub value: DynSolValue, +} diff --git a/graph/src/abi/value_ext.rs b/graph/src/abi/value_ext.rs new file mode 100644 index 00000000000..cb0f220e036 --- /dev/null +++ b/graph/src/abi/value_ext.rs @@ -0,0 +1,277 @@ +use alloy::dyn_abi::DynSolType; +use alloy::dyn_abi::DynSolValue; +use anyhow::anyhow; +use anyhow::Result; +use itertools::Itertools; + +pub trait DynSolValueExt { + /// Creates a fixed-byte decoded value from a slice. + /// + /// Fails if the source slice exceeds 32 bytes. + fn fixed_bytes_from_slice(s: &[u8]) -> Result; + + /// Returns the decoded value as a string. + /// + /// The resulting string contains no type information. + fn to_string(&self) -> String; + + /// Checks whether the value is of the specified type. + /// + /// For types with additional size information, returns true if the size of the value is less + /// than or equal to the size of the specified type. + #[must_use] + fn type_check(&self, ty: &DynSolType) -> bool; +} + +impl DynSolValueExt for DynSolValue { + fn fixed_bytes_from_slice(s: &[u8]) -> Result { + let num_bytes = s.len(); + + if num_bytes > 32 { + return Err(anyhow!( + "input slice must contain a maximum of 32 bytes, got {num_bytes}" + )); + } + + let mut bytes = [0u8; 32]; + + // Access: If `x` is of type `bytesI`, then `x[k]` for `0 <= k < I` returns the `k`th byte. + // Ref: + bytes[..num_bytes].copy_from_slice(s); + + Ok(Self::FixedBytes(bytes.into(), num_bytes)) + } + + fn to_string(&self) -> String { + let s = |v: &[Self]| v.iter().map(|x| x.to_string()).collect_vec().join(","); + + // Output format is taken from `ethabi`; + // See: + match self { + Self::Bool(v) => v.to_string(), + Self::Int(v, _) => format!("{v:x}"), + Self::Uint(v, _) => format!("{v:x}"), + Self::FixedBytes(v, _) => hex::encode(v), + Self::Address(v) => format!("{v:x}"), + Self::Function(v) => format!("{v:x}"), + Self::Bytes(v) => hex::encode(v), + Self::String(v) => v.to_owned(), + Self::Array(v) => format!("[{}]", s(v)), + Self::FixedArray(v) => format!("[{}]", s(v)), + Self::Tuple(v) => format!("({})", s(v)), + } + } + + fn type_check(&self, ty: &DynSolType) -> bool { + match self { + Self::Bool(_) => *ty == DynSolType::Bool, + Self::Int(_, a) => { + if let DynSolType::Int(b) = ty { + b >= a + } else { + false + } + } + Self::Uint(_, a) => { + if let DynSolType::Uint(b) = ty { + b >= a + } else { + false + } + } + Self::FixedBytes(_, a) => { + if let DynSolType::FixedBytes(b) = ty { + b >= a + } else { + false + } + } + Self::Address(_) => *ty == DynSolType::Address, + Self::Function(_) => *ty == DynSolType::Function, + Self::Bytes(_) => *ty == DynSolType::Bytes, + Self::String(_) => *ty == DynSolType::String, + Self::Array(values) => { + if let DynSolType::Array(ty) = ty { + values.iter().all(|x| x.type_check(ty)) + } else { + false + } + } + Self::FixedArray(values) => { + if let DynSolType::FixedArray(ty, size) = ty { + *size == values.len() && values.iter().all(|x| x.type_check(ty)) + } else { + false + } + } + Self::Tuple(values) => { + if let DynSolType::Tuple(types) = ty { + types.len() == values.len() + && values + .iter() + .enumerate() + .all(|(i, x)| x.type_check(&types[i])) + } else { + false + } + } + } + } +} + +#[cfg(test)] +mod tests { + use alloy::primitives::I256; + use alloy::primitives::U256; + + use super::*; + + #[test] + fn fixed_bytes_from_slice_empty_slice() { + let val = DynSolValue::fixed_bytes_from_slice(&[]).unwrap(); + let bytes = [0; 32]; + + assert_eq!(val, DynSolValue::FixedBytes(bytes.into(), 0)); + } + + #[test] + fn fixed_bytes_from_slice_one_byte() { + let val = DynSolValue::fixed_bytes_from_slice(&[10]).unwrap(); + let mut bytes = [0; 32]; + bytes[0] = 10; + + assert_eq!(val, DynSolValue::FixedBytes(bytes.into(), 1)); + } + + #[test] + fn fixed_bytes_from_slice_multiple_bytes() { + let val = DynSolValue::fixed_bytes_from_slice(&[10, 20, 30]).unwrap(); + let mut bytes = [0; 32]; + bytes[0] = 10; + bytes[1] = 20; + bytes[2] = 30; + + assert_eq!(val, DynSolValue::FixedBytes(bytes.into(), 3)); + } + + #[test] + fn fixed_bytes_from_slice_max_bytes() { + let val = DynSolValue::fixed_bytes_from_slice(&[10; 32]).unwrap(); + let bytes = [10; 32]; + + assert_eq!(val, DynSolValue::FixedBytes(bytes.into(), 32)); + } + + #[test] + fn fixed_bytes_from_slice_too_many_bytes() { + DynSolValue::fixed_bytes_from_slice(&[10; 33]).unwrap_err(); + } + + #[test] + fn to_string() { + use DynSolValue::*; + + assert_eq!(Bool(false).to_string(), "false"); + assert_eq!(Bool(true).to_string(), "true"); + + assert_eq!( + Int(I256::try_from(-10).unwrap(), 256).to_string(), + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6", + ); + + assert_eq!(Uint(U256::from(10), 256).to_string(), "a"); + + assert_eq!( + FixedBytes([10; 32].into(), 32).to_string(), + "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", + ); + + assert_eq!( + Address([10; 20].into()).to_string(), + "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", + ); + + assert_eq!( + Function([10; 24].into()).to_string(), + "0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", + ); + + assert_eq!(Bytes(vec![10, 20, 30]).to_string(), "0a141e"); + + assert_eq!( + String("one two three".to_owned()).to_string(), + "one two three" + ); + + assert_eq!( + Array(vec![String("one".to_owned()), String("two".to_owned())]).to_string(), + "[one,two]", + ); + + assert_eq!( + FixedArray(vec![String("one".to_owned()), String("two".to_owned())]).to_string(), + "[one,two]" + ); + + assert_eq!( + Tuple(vec![String("one".to_owned()), String("two".to_owned())]).to_string(), + "(one,two)" + ); + } + + #[test] + fn type_check() { + use DynSolType as T; + use DynSolValue::*; + + assert!(Bool(true).type_check(&T::Bool)); + assert!(!Bool(true).type_check(&T::Int(256))); + + assert!(!Int(I256::try_from(-10).unwrap(), 32).type_check(&T::Int(24))); + assert!(Int(I256::try_from(-10).unwrap(), 32).type_check(&T::Int(32))); + assert!(Int(I256::try_from(-10).unwrap(), 32).type_check(&T::Int(256))); + assert!(!Int(I256::try_from(-10).unwrap(), 32).type_check(&T::Uint(256))); + + assert!(!Uint(U256::from(10), 32).type_check(&T::Uint(24))); + assert!(Uint(U256::from(10), 32).type_check(&T::Uint(32))); + assert!(Uint(U256::from(10), 32).type_check(&T::Uint(256))); + assert!(!Uint(U256::from(10), 32).type_check(&T::FixedBytes(32))); + + assert!(!FixedBytes([0; 32].into(), 16).type_check(&T::FixedBytes(8))); + assert!(FixedBytes([0; 32].into(), 16).type_check(&T::FixedBytes(16))); + assert!(FixedBytes([0; 32].into(), 16).type_check(&T::FixedBytes(32))); + assert!(!FixedBytes([0; 32].into(), 32).type_check(&T::Address)); + + assert!(Address([0; 20].into()).type_check(&T::Address)); + assert!(!Address([0; 20].into()).type_check(&T::Function)); + + assert!(Function([0; 24].into()).type_check(&T::Function)); + assert!(!Function([0; 24].into()).type_check(&T::Bytes)); + + assert!(Bytes(vec![0, 0, 0]).type_check(&T::Bytes)); + assert!(!Bytes(vec![0, 0, 0]).type_check(&T::String)); + + assert!(String("".to_owned()).type_check(&T::String)); + assert!(!String("".to_owned()).type_check(&T::Array(Box::new(T::Bool)))); + + assert!(Array(vec![Bool(true)]).type_check(&T::Array(Box::new(T::Bool)))); + assert!(!Array(vec![Bool(true)]).type_check(&T::Array(Box::new(T::String)))); + assert!(!Array(vec![Bool(true)]).type_check(&T::FixedArray(Box::new(T::Bool), 1))); + + assert!(!FixedArray(vec![String("".to_owned())]) + .type_check(&T::FixedArray(Box::new(T::Bool), 1))); + assert!(!FixedArray(vec![Bool(true), Bool(false)]) + .type_check(&T::FixedArray(Box::new(T::Bool), 1))); + assert!(FixedArray(vec![Bool(true), Bool(false)]) + .type_check(&T::FixedArray(Box::new(T::Bool), 2))); + assert!(!FixedArray(vec![Bool(true), Bool(false)]) + .type_check(&T::FixedArray(Box::new(T::Bool), 3))); + assert!(!FixedArray(vec![Bool(true), Bool(false)]) + .type_check(&T::Tuple(vec![T::Bool, T::Bool]))); + + assert!(!Tuple(vec![Bool(true), Bool(false)]).type_check(&T::Tuple(vec![T::Bool]))); + assert!(Tuple(vec![Bool(true), Bool(false)]).type_check(&T::Tuple(vec![T::Bool, T::Bool]))); + assert!(!Tuple(vec![Bool(true)]).type_check(&T::Tuple(vec![T::Bool, T::Bool]))); + assert!(!Tuple(vec![Bool(true)]).type_check(&T::Bool)); + } +} diff --git a/graph/src/blockchain/mock.rs b/graph/src/blockchain/mock.rs index 577b5fbc816..d35958d80ad 100644 --- a/graph/src/blockchain/mock.rs +++ b/graph/src/blockchain/mock.rs @@ -16,6 +16,7 @@ use crate::{ DataSourceTemplateInfo, StoreError, }, }; +use alloy::primitives::{B256, U256}; use anyhow::{Error, Result}; use async_trait::async_trait; use serde::Deserialize; @@ -26,7 +27,6 @@ use std::{ convert::TryFrom, sync::Arc, }; -use web3::types::H256; use super::{ block_stream::{self, BlockStream, FirehoseCursor}, @@ -73,7 +73,7 @@ pub fn test_ptr(n: BlockNumber) -> BlockPtr { } pub fn test_ptr_reorged(n: BlockNumber, reorg_n: u32) -> BlockPtr { - let mut hash = H256::from_low_u64_be(n as u64); + let mut hash = B256::from(U256::from(n as u64)); hash[0..4].copy_from_slice(&reorg_n.to_be_bytes()); BlockPtr { hash: hash.into(), @@ -524,7 +524,7 @@ impl ChainStore for MockChainStore { async fn attempt_chain_head_update( self: Arc, _ancestor_count: BlockNumber, - ) -> Result, Error> { + ) -> Result, Error> { unimplemented!() } async fn blocks(self: Arc, _hashes: Vec) -> Result, Error> { @@ -571,7 +571,7 @@ impl ChainStore for MockChainStore { } async fn transaction_receipts_in_block( &self, - _block_ptr: &H256, + _block_ptr: &B256, ) -> Result, StoreError> { unimplemented!() } diff --git a/graph/src/blockchain/mod.rs b/graph/src/blockchain/mod.rs index 0b86f416c3f..10742213c04 100644 --- a/graph/src/blockchain/mod.rs +++ b/graph/src/blockchain/mod.rs @@ -33,6 +33,7 @@ use crate::{ components::store::BlockNumber, prelude::{thiserror::Error, LinkResolver}, }; +use alloy::primitives::B256; use anyhow::{anyhow, Context, Error}; use async_trait::async_trait; use futures03::future::BoxFuture; @@ -47,7 +48,6 @@ use std::{ str::FromStr, sync::Arc, }; -use web3::types::H256; pub use block_stream::{ChainHeadUpdateListener, ChainHeadUpdateStream, TriggersAdapter}; pub use builder::{BasicBlockchainBuilder, BlockchainBuilder}; @@ -225,32 +225,26 @@ pub enum IngestorError { /// The Ethereum node does not know about this block for some reason, probably because it /// disappeared in a chain reorg. #[error("Block data unavailable, block was likely uncled (block hash = {0:?})")] - BlockUnavailable(H256), + BlockUnavailable(B256), /// The Ethereum node does not know about this block for some reason, probably because it /// disappeared in a chain reorg. #[error("Receipt for tx {1:?} unavailable, block was likely uncled (block hash = {0:?})")] - ReceiptUnavailable(H256, H256), + ReceiptUnavailable(B256, B256), /// The Ethereum node does not know about this block for some reason #[error("Transaction receipts for block (block hash = {0:?}) is unavailable")] - BlockReceiptsUnavailable(H256), + BlockReceiptsUnavailable(B256), /// The Ethereum node does not know about this block for some reason #[error("Received confliciting block receipts for block (block hash = {0:?})")] - BlockReceiptsMismatched(H256), + BlockReceiptsMismatched(B256), /// An unexpected error occurred. #[error("Ingestor error: {0:#}")] Unknown(#[from] Error), } -impl From for IngestorError { - fn from(e: web3::Error) -> Self { - IngestorError::Unknown(anyhow::anyhow!(e)) - } -} - /// The `TriggerFilterWrapper` is a higher-level wrapper around the chain-specific `TriggerFilter`, /// enabling subgraph-based trigger filtering for subgraph datasources. This abstraction is necessary /// because subgraph filtering operates at a higher level than chain-based filtering. By using this wrapper, diff --git a/graph/src/blockchain/types.rs b/graph/src/blockchain/types.rs index 081fff4eea5..290fb6bb1aa 100644 --- a/graph/src/blockchain/types.rs +++ b/graph/src/blockchain/types.rs @@ -1,3 +1,4 @@ +use alloy::primitives::B256; use anyhow::anyhow; use diesel::deserialize::FromSql; use diesel::pg::Pg; @@ -9,7 +10,8 @@ use serde::{Deserialize, Deserializer}; use std::convert::TryFrom; use std::time::Duration; use std::{fmt, str::FromStr}; -use web3::types::{Block, H256, U256, U64}; + +use crate::components::ethereum::BlockWrapper; use crate::cheap_clone::CheapClone; use crate::components::store::BlockNumber; @@ -32,8 +34,8 @@ impl BlockHash { &self.0 } - pub fn as_h256(&self) -> H256 { - H256::from_slice(self.as_slice()) + pub fn as_b256(&self) -> B256 { + B256::from_slice(self.as_slice()) } /// Encodes the block hash into a hexadecimal string **without** a "0x" @@ -45,7 +47,7 @@ impl BlockHash { } pub fn zero() -> Self { - Self::from(H256::zero()) + Self::from(B256::ZERO) } } @@ -83,18 +85,18 @@ impl fmt::LowerHex for BlockHash { } } -impl From for BlockHash { - fn from(hash: H256) -> Self { - BlockHash(hash.as_bytes().into()) - } -} - impl From> for BlockHash { fn from(bytes: Vec) -> Self { BlockHash(bytes.as_slice().into()) } } +impl From for BlockHash { + fn from(hash: B256) -> Self { + BlockHash(hash.as_slice().into()) + } +} + impl TryFrom<&str> for BlockHash { type Error = anyhow::Error; @@ -170,13 +172,6 @@ impl BlockPtr { self.number } - // FIXME: - // - // workaround for arweave - pub fn hash_as_h256(&self) -> H256 { - H256::from_slice(&self.hash_slice()[..32]) - } - pub fn hash_slice(&self) -> &[u8] { self.hash.0.as_ref() } @@ -205,15 +200,15 @@ impl slog::Value for BlockPtr { } } -impl From> for BlockPtr { - fn from(b: Block) -> BlockPtr { - BlockPtr::from((b.hash.unwrap(), b.number.unwrap().as_u64())) +impl From for BlockPtr { + fn from(b: BlockWrapper) -> BlockPtr { + BlockPtr::from((b.hash(), b.number_u64() as i32)) } } -impl<'a, T> From<&'a Block> for BlockPtr { - fn from(b: &'a Block) -> BlockPtr { - BlockPtr::from((b.hash.unwrap(), b.number.unwrap().as_u64())) +impl From<&BlockWrapper> for BlockPtr { + fn from(b: &BlockWrapper) -> BlockPtr { + BlockPtr::from((b.hash(), b.number_u64() as i32)) } } @@ -226,50 +221,51 @@ impl From<(Vec, i32)> for BlockPtr { } } -impl From<(H256, i32)> for BlockPtr { - fn from((hash, number): (H256, i32)) -> BlockPtr { +impl From<(B256, i32)> for BlockPtr { + fn from((hash, number): (B256, i32)) -> BlockPtr { BlockPtr { hash: hash.into(), number, } } } - -impl From<(Vec, u64)> for BlockPtr { - fn from((bytes, number): (Vec, u64)) -> Self { +impl From<(B256, u64)> for BlockPtr { + fn from((hash, number): (B256, u64)) -> BlockPtr { let number = i32::try_from(number).unwrap(); BlockPtr { - hash: BlockHash::from(bytes), + hash: hash.into(), number, } } } -impl From<(Vec, i64)> for BlockPtr { - fn from((bytes, number): (Vec, i64)) -> Self { +impl From<(B256, i64)> for BlockPtr { + fn from((hash, number): (B256, i64)) -> BlockPtr { let number = i32::try_from(number).unwrap(); BlockPtr { - hash: BlockHash::from(bytes), + hash: hash.into(), number, } } } -impl From<(H256, u64)> for BlockPtr { - fn from((hash, number): (H256, u64)) -> BlockPtr { +impl From<(Vec, u64)> for BlockPtr { + fn from((bytes, number): (Vec, u64)) -> Self { let number = i32::try_from(number).unwrap(); - - BlockPtr::from((hash, number)) + BlockPtr { + hash: BlockHash::from(bytes), + number, + } } } -impl From<(H256, i64)> for BlockPtr { - fn from((hash, number): (H256, i64)) -> BlockPtr { - if number < 0 { - panic!("block number out of range: {}", number); +impl From<(Vec, i64)> for BlockPtr { + fn from((bytes, number): (Vec, i64)) -> Self { + let number = i32::try_from(number).unwrap(); + BlockPtr { + hash: BlockHash::from(bytes), + number, } - - BlockPtr::from((hash, number as u64)) } } @@ -288,14 +284,14 @@ impl TryFrom<(&[u8], i64)> for BlockPtr { type Error = anyhow::Error; fn try_from((bytes, number): (&[u8], i64)) -> Result { - let hash = if bytes.len() == H256::len_bytes() { - H256::from_slice(bytes) + let hash = if bytes.len() == B256::len_bytes() { + B256::from_slice(bytes) } else { return Err(anyhow!( - "invalid H256 value `{}` has {} bytes instead of {}", + "invalid B256 value `{}` has {} bytes instead of {}", hex::encode(bytes), bytes.len(), - H256::len_bytes() + B256::len_bytes() )); }; Ok(BlockPtr::from((hash, number))) @@ -312,9 +308,9 @@ impl IntoValue for BlockPtr { } } -impl From for H256 { +impl From for B256 { fn from(ptr: BlockPtr) -> Self { - ptr.hash_as_h256() + ptr.hash.as_b256() } } @@ -398,22 +394,6 @@ impl ExtendedBlockPtr { pub fn block_number(&self) -> BlockNumber { self.number } - - pub fn hash_as_h256(&self) -> H256 { - H256::from_slice(&self.hash_slice()[..32]) - } - - pub fn parent_hash_as_h256(&self) -> H256 { - H256::from_slice(&self.parent_hash_slice()[..32]) - } - - pub fn hash_slice(&self) -> &[u8] { - self.hash.0.as_ref() - } - - pub fn parent_hash_slice(&self) -> &[u8] { - self.parent_hash.0.as_ref() - } } impl fmt::Display for ExtendedBlockPtr { @@ -463,44 +443,15 @@ impl IntoValue for ExtendedBlockPtr { } } -impl TryFrom<(Option, Option, H256, U256)> for ExtendedBlockPtr { - type Error = anyhow::Error; - - fn try_from(tuple: (Option, Option, H256, U256)) -> Result { - let (hash_opt, number_opt, parent_hash, timestamp_u256) = tuple; - - let hash = hash_opt.ok_or_else(|| anyhow!("Block hash is missing"))?; - let number = number_opt - .ok_or_else(|| anyhow!("Block number is missing"))? - .as_u64(); - - let block_number = - i32::try_from(number).map_err(|_| anyhow!("Block number out of range"))?; - - // Convert `U256` to `BlockTime` - let secs = - i64::try_from(timestamp_u256).map_err(|_| anyhow!("Timestamp out of range for i64"))?; - let block_time = BlockTime::since_epoch(secs, 0); - - Ok(ExtendedBlockPtr { - hash: hash.into(), - number: block_number, - parent_hash: parent_hash.into(), - timestamp: block_time, - }) - } -} - -impl TryFrom<(H256, i32, H256, U256)> for ExtendedBlockPtr { +impl TryFrom<(B256, i32, B256, u64)> for ExtendedBlockPtr { type Error = anyhow::Error; - fn try_from(tuple: (H256, i32, H256, U256)) -> Result { - let (hash, block_number, parent_hash, timestamp_u256) = tuple; + fn try_from(tuple: (B256, i32, B256, u64)) -> Result { + let (hash, block_number, parent_hash, timestamp) = tuple; - // Convert `U256` to `BlockTime` - let secs = - i64::try_from(timestamp_u256).map_err(|_| anyhow!("Timestamp out of range for i64"))?; - let block_time = BlockTime::since_epoch(secs, 0); + // Convert timestamp to `BlockTime` + let secs = timestamp; + let block_time = BlockTime::since_epoch(secs as i64, 0); Ok(ExtendedBlockPtr { hash: hash.into(), @@ -510,11 +461,6 @@ impl TryFrom<(H256, i32, H256, U256)> for ExtendedBlockPtr { }) } } -impl From for H256 { - fn from(ptr: ExtendedBlockPtr) -> Self { - ptr.hash_as_h256() - } -} impl From for BlockNumber { fn from(ptr: ExtendedBlockPtr) -> Self { @@ -539,7 +485,7 @@ impl Default for ChainIdentifier { fn default() -> Self { Self { net_version: String::default(), - genesis_block_hash: BlockHash::from(H256::zero()), + genesis_block_hash: BlockHash::from(B256::ZERO), } } } diff --git a/graph/src/cheap_clone.rs b/graph/src/cheap_clone.rs index b8863d3918e..c56e621cb4c 100644 --- a/graph/src/cheap_clone.rs +++ b/graph/src/cheap_clone.rs @@ -118,4 +118,4 @@ cheap_clone_is_copy!( &'static str, std::time::Duration ); -cheap_clone_is_copy!(ethabi::Address); +cheap_clone_is_copy!(alloy::primitives::Address); diff --git a/graph/src/components/ethereum/mod.rs b/graph/src/components/ethereum/mod.rs index 45f1f5d98ad..5fc0baf10cd 100644 --- a/graph/src/components/ethereum/mod.rs +++ b/graph/src/components/ethereum/mod.rs @@ -1,6 +1,9 @@ mod types; pub use self::types::{ - evaluate_transaction_status, EthereumBlock, EthereumBlockWithCalls, EthereumCall, - LightEthereumBlock, LightEthereumBlockExt, + evaluate_transaction_status, AnyBlock, AnyTransaction, BlockWrapper, EthereumBlock, + EthereumBlockWithCalls, EthereumCall, LightEthereumBlock, LightEthereumBlockExt, }; + +// Re-export Alloy network types for convenience +pub use alloy::network::{AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope}; diff --git a/graph/src/components/ethereum/types.rs b/graph/src/components/ethereum/types.rs index b43730590d4..931ca02b44e 100644 --- a/graph/src/components/ethereum/types.rs +++ b/graph/src/components/ethereum/types.rs @@ -1,68 +1,160 @@ -use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, sync::Arc}; -use web3::types::{ - Action, Address, Block, Bytes, Log, Res, Trace, Transaction, TransactionReceipt, H256, U256, - U64, +use alloy::{ + network::{AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, ReceiptResponse, TransactionResponse}, + primitives::{Address, Bytes, B256, U256, U64}, + rpc::types::{ + trace::parity::{Action, LocalizedTransactionTrace, TraceOutput}, + Log, + }, }; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; use crate::{ blockchain::{BlockPtr, BlockTime}, prelude::BlockNumber, }; -pub type LightEthereumBlock = Block; +// Use Alloy's official types for handling any transaction type +pub type AnyTransaction = AnyRpcTransaction; +pub type AnyBlock = AnyRpcBlock; + +// BlockWrapper wraps AnyBlock +#[allow(dead_code)] +#[derive(Debug, Deserialize, Serialize)] +pub struct BlockWrapper(AnyBlock); + +impl Default for BlockWrapper { + fn default() -> Self { + use alloy::rpc::types::{Block, BlockTransactions}; + use alloy::serde::WithOtherFields; + + let default_block = Block { + header: AnyRpcHeader::default(), + transactions: BlockTransactions::Full(vec![]), + uncles: vec![], + withdrawals: None, + }; + Self(AnyBlock::new(WithOtherFields::new(default_block))) + } +} + +impl BlockWrapper { + pub fn new(block: AnyBlock) -> Self { + Self(block) + } + + pub fn hash(&self) -> B256 { + self.0.header.hash + } + + pub fn number_u64(&self) -> u64 { + self.0.header.number + } + + pub fn timestamp_u64(&self) -> u64 { + self.0.header.timestamp + } + + pub fn transactions(&self) -> Option<&[AnyTransaction]> { + self.0.transactions.as_transactions() + } + + pub fn inner(&self) -> &AnyBlock { + &self.0 + } + + pub fn base_fee_per_gas(&self) -> Option { + self.0.header.base_fee_per_gas + } +} + +pub type LightEthereumBlock = BlockWrapper; pub trait LightEthereumBlockExt { fn number(&self) -> BlockNumber; - fn transaction_for_log(&self, log: &Log) -> Option; - fn transaction_for_call(&self, call: &EthereumCall) -> Option; + fn transaction_for_log(&self, log: &Log) -> Option; + fn transaction_for_call(&self, call: &EthereumCall) -> Option; fn parent_ptr(&self) -> Option; fn format(&self) -> String; fn block_ptr(&self) -> BlockPtr; fn timestamp(&self) -> BlockTime; } -impl LightEthereumBlockExt for LightEthereumBlock { +impl LightEthereumBlockExt for AnyBlock { fn number(&self) -> BlockNumber { - BlockNumber::try_from(self.number.unwrap().as_u64()).unwrap() + self.header.number as BlockNumber + } + + fn timestamp(&self) -> BlockTime { + let time = self.header.timestamp; + let time = i64::try_from(time).unwrap(); + BlockTime::since_epoch(time, 0) } - fn transaction_for_log(&self, log: &Log) -> Option { - log.transaction_hash - .and_then(|hash| self.transactions.iter().find(|tx| tx.hash == hash)) - .cloned() + fn transaction_for_log(&self, log: &Log) -> Option { + log.transaction_hash.and_then(|hash| { + self.transactions + .txns() + .find(|tx| &tx.tx_hash() == &hash) + .cloned() + }) } - fn transaction_for_call(&self, call: &EthereumCall) -> Option { - call.transaction_hash - .and_then(|hash| self.transactions.iter().find(|tx| tx.hash == hash)) - .cloned() + fn transaction_for_call(&self, call: &EthereumCall) -> Option { + call.transaction_hash.and_then(|hash| { + self.transactions + .txns() + .find(|tx| &tx.tx_hash() == &hash) + .cloned() + }) } fn parent_ptr(&self) -> Option { - match self.number() { + match self.header.number { 0 => None, - n => Some(BlockPtr::from((self.parent_hash, n - 1))), + n => { + let number = i32::try_from(n - 1).unwrap(); + Some(BlockPtr::new(self.header.parent_hash.into(), number)) + } } } fn format(&self) -> String { - format!( - "{} ({})", - self.number - .map_or(String::from("none"), |number| format!("#{}", number)), - self.hash - .map_or(String::from("-"), |hash| format!("{:x}", hash)) - ) + format!("{} ({})", self.header.number, self.header.hash) + } + + fn block_ptr(&self) -> BlockPtr { + BlockPtr::new(self.header.hash.into(), self.header.number as i32) + } +} + +impl LightEthereumBlockExt for LightEthereumBlock { + fn number(&self) -> BlockNumber { + self.0.header.number.try_into().unwrap() + } + + fn transaction_for_log(&self, log: &alloy::rpc::types::Log) -> Option { + self.0.transaction_for_log(log) + } + + fn transaction_for_call(&self, call: &EthereumCall) -> Option { + self.0.transaction_for_call(call) + } + + fn parent_ptr(&self) -> Option { + self.0.parent_ptr() + } + + fn format(&self) -> String { + self.0.format() } fn block_ptr(&self) -> BlockPtr { - BlockPtr::from((self.hash.unwrap(), self.number.unwrap().as_u64())) + self.0.block_ptr() } fn timestamp(&self) -> BlockTime { - let ts = i64::try_from(self.timestamp.as_u64()).unwrap(); - BlockTime::since_epoch(ts, 0) + self.0.timestamp() } } @@ -90,7 +182,7 @@ impl EthereumBlockWithCalls { "failed to find the receipt for this transaction" ))?; - Ok(evaluate_transaction_status(receipt.status)) + Ok(receipt.status()) } } @@ -104,10 +196,10 @@ pub fn evaluate_transaction_status(receipt_status: Option) -> bool { .unwrap_or(true) } -#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct EthereumBlock { pub block: Arc, - pub transaction_receipts: Vec>, + pub transaction_receipts: Vec>, } #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -115,31 +207,34 @@ pub struct EthereumCall { pub from: Address, pub to: Address, pub value: U256, - pub gas_used: U256, + pub gas_used: u64, pub input: Bytes, pub output: Bytes, pub block_number: BlockNumber, - pub block_hash: H256, - pub transaction_hash: Option, + pub block_hash: B256, + pub transaction_hash: Option, pub transaction_index: u64, } impl EthereumCall { - pub fn try_from_trace(trace: &Trace) -> Option { + pub fn try_from_trace(trace: &LocalizedTransactionTrace) -> Option { // The parity-ethereum tracing api returns traces for operations which had execution errors. // Filter errorful traces out, since call handlers should only run on successful CALLs. - if trace.error.is_some() { + + let tx_trace = &trace.trace; + + if tx_trace.error.is_some() { return None; } // We are only interested in traces from CALLs - let call = match &trace.action { + let call = match &tx_trace.action { // Contract to contract value transfers compile to the CALL opcode // and have no input. Call handlers are for triggering on explicit method calls right now. Action::Call(call) if call.input.0.len() >= 4 => call, _ => return None, }; - let (output, gas_used) = match &trace.result { - Some(Res::Call(result)) => (result.output.clone(), result.gas_used), + let (output, gas_used) = match &tx_trace.result { + Some(TraceOutput::Call(result)) => (result.output.clone(), result.gas_used), _ => return None, }; @@ -151,29 +246,17 @@ impl EthereumCall { from: call.from, to: call.to, value: call.value, - gas_used, + gas_used: gas_used, input: call.input.clone(), - output, - block_number: trace.block_number as BlockNumber, - block_hash: trace.block_hash, + output: output, + block_number: trace.block_number? as BlockNumber, + block_hash: trace.block_hash?, transaction_hash: trace.transaction_hash, transaction_index, }) } } -impl From for BlockPtr { - fn from(b: EthereumBlock) -> BlockPtr { - BlockPtr::from((b.block.hash.unwrap(), b.block.number.unwrap().as_u64())) - } -} - -impl<'a> From<&'a EthereumBlock> for BlockPtr { - fn from(b: &'a EthereumBlock) -> BlockPtr { - BlockPtr::from((b.block.hash.unwrap(), b.block.number.unwrap().as_u64())) - } -} - impl<'a> From<&'a EthereumCall> for BlockPtr { fn from(call: &'a EthereumCall) -> BlockPtr { BlockPtr::from((call.block_hash, call.block_number)) diff --git a/graph/src/components/store/mod.rs b/graph/src/components/store/mod.rs index 818718a5f74..c5ac4002224 100644 --- a/graph/src/components/store/mod.rs +++ b/graph/src/components/store/mod.rs @@ -3,6 +3,7 @@ mod err; mod traits; pub mod write; +use alloy::primitives::Address; use diesel::deserialize::FromSql; use diesel::pg::Pg; use diesel::serialize::{Output, ToSql}; @@ -1140,7 +1141,7 @@ pub struct CachedEthereumCall { pub block_ptr: BlockPtr, /// The address to the called contract. - pub contract_address: ethabi::Address, + pub contract_address: Address, /// The encoded return value of this call. pub return_value: Vec, diff --git a/graph/src/components/store/traits.rs b/graph/src/components/store/traits.rs index 2e26a3c96ee..61347a1e8db 100644 --- a/graph/src/components/store/traits.rs +++ b/graph/src/components/store/traits.rs @@ -3,7 +3,6 @@ use std::ops::Range; use anyhow::Error; use async_trait::async_trait; -use web3::types::{Address, H256}; use super::*; use crate::blockchain::block_stream::{EntitySourceOperation, FirehoseCursor}; @@ -19,7 +18,10 @@ use crate::data::store::ethereum::call; use crate::data::store::{QueryObject, SqlQueryObject}; use crate::data::subgraph::{status, DeploymentFeatures}; use crate::data::{query::QueryTarget, subgraph::schema::*}; -use crate::prelude::{DeploymentState, NodeId, QueryExecutionError, SubgraphName}; +use crate::prelude::{ + alloy::primitives::{Address, B256}, + DeploymentState, NodeId, QueryExecutionError, SubgraphName, +}; use crate::schema::{ApiSchema, InputSchema}; pub trait SubscriptionManager: Send + Sync + 'static { @@ -539,7 +541,7 @@ pub trait ChainStore: ChainHeadStore { async fn attempt_chain_head_update( self: Arc, ancestor_count: BlockNumber, - ) -> Result, Error>; + ) -> Result, Error>; /// Returns the blocks present in the store. async fn blocks( @@ -616,7 +618,7 @@ pub trait ChainStore: ChainHeadStore { /// Tries to retrieve all transactions receipts for a given block. async fn transaction_receipts_in_block( &self, - block_ptr: &H256, + block_ptr: &B256, ) -> Result, StoreError>; /// Clears call cache of the chain for the given `from` and `to` block number. diff --git a/graph/src/components/subgraph/proof_of_indexing/mod.rs b/graph/src/components/subgraph/proof_of_indexing/mod.rs index 718a3a5cecd..6121eccf60e 100644 --- a/graph/src/components/subgraph/proof_of_indexing/mod.rs +++ b/graph/src/components/subgraph/proof_of_indexing/mod.rs @@ -87,6 +87,7 @@ mod tests { prelude::{BlockPtr, DeploymentHash, Value}, schema::InputSchema, }; + use alloy::primitives::{Address, B256}; use maplit::hashmap; use online::ProofOfIndexingFinisher; use reference::*; @@ -96,7 +97,6 @@ mod tests { use stable_hash_legacy::utils::stable_hash as stable_hash_legacy; use std::collections::HashMap; use std::convert::TryInto; - use web3::types::{Address, H256}; /// The PoI is the StableHash of this struct. This reference implementation is /// mostly here just to make sure that the online implementation is @@ -106,22 +106,22 @@ mod tests { pub struct PoI<'a> { pub causality_regions: HashMap>, pub subgraph_id: DeploymentHash, - pub block_hash: H256, + pub block_hash: B256, pub indexer: Option
, } - fn h256_as_bytes(val: &H256) -> AsBytes<&[u8]> { - AsBytes(val.as_bytes()) + fn b256_as_bytes(val: &B256) -> AsBytes<&[u8]> { + AsBytes(val.as_slice()) } fn indexer_opt_as_bytes(val: &Option
) -> Option> { - val.as_ref().map(|v| AsBytes(v.as_bytes())) + val.as_ref().map(|v| AsBytes(v.as_slice())) } impl_stable_hash!(PoI<'_> { causality_regions, subgraph_id, - block_hash: h256_as_bytes, + block_hash: b256_as_bytes, indexer: indexer_opt_as_bytes }); @@ -247,7 +247,7 @@ mod tests { fast: "dced49c45eac68e8b3d8f857928e7be6c270f2db8b56b0d7f27ce725100bae01", data: PoI { subgraph_id: DeploymentHash::new("test").unwrap(), - block_hash: H256::repeat_byte(1), + block_hash: B256::repeat_byte(1), causality_regions: HashMap::new(), indexer: None, }, @@ -259,7 +259,7 @@ mod tests { fast: "8bb3373fb55e02bde3202bac0eeecf1bd9a676856a4dd6667bd809aceda41885", data: PoI { subgraph_id: DeploymentHash::new("test").unwrap(), - block_hash: H256::repeat_byte(1), + block_hash: B256::repeat_byte(1), causality_regions: hashmap! { "eth".to_owned() => PoICausalityRegion { blocks: vec! [ @@ -286,7 +286,7 @@ mod tests { fast: "8b0097ad96b21f7e4bd8dcc41985e6e5506b808f1185016ab1073dd8745238ce", data: PoI { subgraph_id: DeploymentHash::new("b").unwrap(), - block_hash: H256::repeat_byte(3), + block_hash: B256::repeat_byte(3), causality_regions: hashmap! { "eth".to_owned() => PoICausalityRegion { blocks: vec! [ @@ -324,7 +324,7 @@ mod tests { fast: "2041af28678e68406247a5cfb5fe336947da75256c79b35c2f61fc7985091c0e", data: PoI { subgraph_id: DeploymentHash::new("b").unwrap(), - block_hash: H256::repeat_byte(3), + block_hash: B256::repeat_byte(3), causality_regions: hashmap! { "eth".to_owned() => PoICausalityRegion { blocks: vec! [ @@ -386,7 +386,7 @@ mod tests { fast: "421ef30a03be64014b9eef2b999795dcabfc601368040df855635e7886eb3822", data: PoI { subgraph_id: DeploymentHash::new("test").unwrap(), - block_hash: H256::repeat_byte(1), + block_hash: B256::repeat_byte(1), causality_regions: hashmap! { "eth".to_owned() => PoICausalityRegion { blocks: vec! [ diff --git a/graph/src/components/subgraph/proof_of_indexing/online.rs b/graph/src/components/subgraph/proof_of_indexing/online.rs index ebf7a65e2f9..f9bd8b5f18d 100644 --- a/graph/src/components/subgraph/proof_of_indexing/online.rs +++ b/graph/src/components/subgraph/proof_of_indexing/online.rs @@ -9,6 +9,7 @@ use crate::{ prelude::{debug, BlockNumber, DeploymentHash, Logger, ENV_VARS}, util::stable_hash_glue::AsBytes, }; +use alloy::primitives::Address; use sha2::{Digest, Sha256}; use stable_hash::{fast::FastStableHasher, FieldAddress, StableHash, StableHasher}; use stable_hash_legacy::crypto::{Blake3SeqNo, SetHasher}; @@ -18,7 +19,6 @@ use stable_hash_legacy::prelude::{ use std::collections::HashMap; use std::convert::TryInto; use std::fmt; -use web3::types::Address; pub struct BlockEventStream { vec_length: u64, @@ -278,7 +278,7 @@ impl ProofOfIndexingFinisher { state.write(&AsBytes(block.hash_slice()), &[2]); // Add PoI.indexer - state.write(&indexer.as_ref().map(|i| AsBytes(i.as_bytes())), &[3]); + state.write(&indexer.as_ref().map(|i| AsBytes(i.as_slice())), &[3]); ProofOfIndexingFinisher { block_number: block.number, diff --git a/graph/src/components/transaction_receipt.rs b/graph/src/components/transaction_receipt.rs index dc8eaf6a730..526a0487180 100644 --- a/graph/src/components/transaction_receipt.rs +++ b/graph/src/components/transaction_receipt.rs @@ -3,37 +3,28 @@ //! This module exposes the [`LightTransactionReceipt`] type, which holds basic information about //! the retrieved transaction receipts. -use web3::types::{TransactionReceipt, H256, U256, U64}; +use alloy::network::ReceiptResponse; +use alloy::primitives::B256; -/// Like web3::types::Receipt, but with fewer fields. #[derive(Debug, PartialEq, Eq)] pub struct LightTransactionReceipt { - pub transaction_hash: H256, - pub transaction_index: U64, - pub block_hash: Option, - pub block_number: Option, - pub gas_used: Option, - pub status: Option, + pub transaction_hash: B256, + pub transaction_index: u64, + pub block_hash: Option, + pub block_number: Option, + pub gas_used: u64, + pub status: bool, } -impl From for LightTransactionReceipt { - fn from(receipt: TransactionReceipt) -> Self { - let TransactionReceipt { - transaction_hash, - transaction_index, - block_hash, - block_number, - gas_used, - status, - .. - } = receipt; +impl From for LightTransactionReceipt { + fn from(receipt: alloy::network::AnyTransactionReceipt) -> Self { LightTransactionReceipt { - transaction_hash, - transaction_index, - block_hash, - block_number, - gas_used, - status, + transaction_hash: receipt.transaction_hash, + transaction_index: receipt.transaction_index.unwrap(), // unwrap is safe because its None only for pending transactions, graph-node does not ingest pending transactions + block_hash: receipt.block_hash, + block_number: receipt.block_number, + gas_used: receipt.gas_used, + status: receipt.status(), } } } diff --git a/graph/src/data/graphql/values.rs b/graph/src/data/graphql/values.rs index 7f15d26dc98..b4923d6ce26 100644 --- a/graph/src/data/graphql/values.rs +++ b/graph/src/data/graphql/values.rs @@ -1,3 +1,4 @@ +use alloy::primitives::Address; use anyhow::{anyhow, Error}; use std::collections::HashMap; use std::convert::TryFrom; @@ -6,7 +7,6 @@ use std::str::FromStr; use crate::blockchain::BlockHash; use crate::data::value::Object; use crate::prelude::{r, BigInt}; -use web3::types::H160; pub trait TryFromValue: Sized { fn try_from_value(value: &r::Value) -> Result; @@ -74,16 +74,11 @@ impl TryFromValue for i32 { } } -impl TryFromValue for H160 { +impl TryFromValue for Address { fn try_from_value(value: &r::Value) -> Result { match value { - r::Value::String(s) => { - // `H160::from_str` takes a hex string with no leading `0x`. - let string = s.trim_start_matches("0x"); - H160::from_str(string).map_err(|e| { - anyhow!("Cannot parse Address/H160 value from string `{}`: {}", s, e) - }) - } + r::Value::String(s) => Address::from_str(s) + .map_err(|e| anyhow!("Cannot parse Address/H160 value from string `{}`: {}", s, e)), _ => Err(anyhow!( "Cannot parse value into an Address/H160: {:?}", value diff --git a/graph/src/data/store/ethereum.rs b/graph/src/data/store/ethereum.rs index 12d48f992df..469b4b14551 100644 --- a/graph/src/data/store/ethereum.rs +++ b/graph/src/data/store/ethereum.rs @@ -1,7 +1,7 @@ use super::scalar; use crate::derive::CheapClone; use crate::prelude::*; -use web3::types::{Address, Bytes, H2048, H256, H64, U64}; +use alloy::primitives::{aliases::B2048, Address, Bytes, B256, B64, U64}; impl From
for Value { fn from(address: Address) -> Value { @@ -9,27 +9,27 @@ impl From
for Value { } } -impl From for Value { - fn from(hash: H64) -> Value { +impl From for Value { + fn from(hash: B64) -> Value { Value::Bytes(scalar::Bytes::from(hash.as_ref())) } } -impl From for Value { - fn from(hash: H256) -> Value { +impl From for Value { + fn from(hash: B256) -> Value { Value::Bytes(scalar::Bytes::from(hash.as_ref())) } } -impl From for Value { - fn from(hash: H2048) -> Value { +impl From for Value { + fn from(hash: B2048) -> Value { Value::Bytes(scalar::Bytes::from(hash.as_ref())) } } impl From for Value { fn from(bytes: Bytes) -> Value { - Value::Bytes(scalar::Bytes::from(bytes.0.as_slice())) + Value::Bytes(scalar::Bytes::from(bytes.as_ref())) } } @@ -43,6 +43,8 @@ impl From for Value { pub mod call { use std::sync::Arc; + use alloy::primitives::Address; + use crate::data::store::scalar::Bytes; use super::CheapClone; @@ -104,7 +106,7 @@ pub mod call { /// on the call's return value #[derive(Debug, Clone, CheapClone)] pub struct Request { - pub address: ethabi::Address, + pub address: Address, pub encoded_call: Arc, /// The index is set by the caller and is used to identify the /// request in related data structures that the caller might have @@ -112,7 +114,7 @@ pub mod call { } impl Request { - pub fn new(address: ethabi::Address, encoded_call: Vec, index: u32) -> Self { + pub fn new(address: Address, encoded_call: Vec, index: u32) -> Self { Request { address, encoded_call: Arc::new(Bytes::from(encoded_call)), diff --git a/graph/src/data/store/scalar/bigint.rs b/graph/src/data/store/scalar/bigint.rs index 526ca5ba390..a69391e7fc1 100644 --- a/graph/src/data/store/scalar/bigint.rs +++ b/graph/src/data/store/scalar/bigint.rs @@ -4,8 +4,8 @@ use serde::{self, Deserialize, Serialize}; use stable_hash::utils::AsInt; use stable_hash::StableHash; use thiserror::Error; -use web3::types::*; +use crate::prelude::alloy::primitives::{U128, U256, U64}; use std::convert::{TryFrom, TryInto}; use std::fmt; use std::ops::{Add, BitAnd, BitOr, Div, Mul, Rem, Shl, Shr, Sub}; @@ -174,22 +174,19 @@ impl BigInt { } pub fn from_unsigned_u128(n: U128) -> Self { - let mut bytes: [u8; 16] = [0; 16]; - n.to_little_endian(&mut bytes); + let bytes: [u8; U128::BYTES] = n.to_le_bytes(); // Unwrap: 128 bits is much less than BigInt::MAX_BITS BigInt::from_unsigned_bytes_le(&bytes).unwrap() } pub fn from_unsigned_u256(n: &U256) -> Self { - let mut bytes: [u8; 32] = [0; 32]; - n.to_little_endian(&mut bytes); + let bytes: [u8; U256::BYTES] = n.to_le_bytes(); // Unwrap: 256 bits is much less than BigInt::MAX_BITS BigInt::from_unsigned_bytes_le(&bytes).unwrap() } pub fn from_signed_u256(n: &U256) -> Self { - let mut bytes: [u8; 32] = [0; 32]; - n.to_little_endian(&mut bytes); + let bytes: [u8; U256::BYTES] = n.to_le_bytes(); BigInt::from_signed_bytes_le(&bytes).unwrap() } @@ -202,9 +199,9 @@ impl BigInt { ); let mut i_bytes: [u8; 32] = [255; 32]; i_bytes[..bytes.len()].copy_from_slice(&bytes); - U256::from_little_endian(&i_bytes) + U256::from_le_slice(&i_bytes) } else { - U256::from_little_endian(&bytes) + U256::from_le_slice(&bytes) } } @@ -216,7 +213,7 @@ impl BigInt { self ); } - Ok(U256::from_little_endian(&bytes)) + Ok(U256::from_le_slice(&bytes)) } pub fn pow(self, exponent: u8) -> Result { @@ -238,6 +235,12 @@ impl From for BigInt { } } +impl From for BigInt { + fn from(i: u128) -> BigInt { + BigInt::unchecked_new(i.into()) + } +} + impl From for BigInt { fn from(i: i64) -> BigInt { BigInt::unchecked_new(i.into()) @@ -251,7 +254,7 @@ impl From for BigInt { /// handle signed U64s, we should add the same /// `{to,from}_{signed,unsigned}_u64` methods that we have for U64. fn from(n: U64) -> BigInt { - BigInt::from(n.as_u64()) + BigInt::from(n.to::()) } } @@ -367,8 +370,9 @@ impl GasSizeOf for BigInt { #[cfg(test)] mod test { + use alloy::primitives::U64; + use super::{super::test::same_stable_hash, BigInt}; - use web3::types::U64; #[test] fn bigint_to_from_u64() { diff --git a/graph/src/data/store/scalar/bytes.rs b/graph/src/data/store/scalar/bytes.rs index 585b548f931..c7742f412a2 100644 --- a/graph/src/data/store/scalar/bytes.rs +++ b/graph/src/data/store/scalar/bytes.rs @@ -1,9 +1,9 @@ +use alloy::primitives::Address; use diesel::deserialize::FromSql; use diesel::pg::PgValue; use diesel::serialize::ToSql; use hex; use serde::{self, Deserialize, Serialize}; -use web3::types::*; use std::fmt::{self, Display, Formatter}; use std::ops::Deref; @@ -64,12 +64,6 @@ impl From
for Bytes { } } -impl From for Bytes { - fn from(bytes: web3::types::Bytes) -> Bytes { - Bytes::from(bytes.0.as_slice()) - } -} - impl From for Bytes { fn from(hash: BlockHash) -> Self { Bytes(hash.0) @@ -123,3 +117,9 @@ impl FromSql for Bytes { as FromSql>::from_sql(value).map(Bytes::from) } } + +impl From for Bytes { + fn from(bytes: alloy::primitives::Bytes) -> Bytes { + Bytes::from(bytes.as_ref()) + } +} diff --git a/graph/src/data/subgraph/mod.rs b/graph/src/data/subgraph/mod.rs index 25287a94e95..c19926bd813 100644 --- a/graph/src/data/subgraph/mod.rs +++ b/graph/src/data/subgraph/mod.rs @@ -3,6 +3,7 @@ pub mod schema; /// API version and spec version. pub mod api_version; +use alloy::primitives::Address; pub use api_version::*; pub mod features; @@ -29,7 +30,6 @@ use std::{ }; use thiserror::Error; use wasmparser; -use web3::types::Address; use crate::{ bail, diff --git a/graph/src/data_source/common.rs b/graph/src/data_source/common.rs index f89739c6ab7..8090f9334b7 100644 --- a/graph/src/data_source/common.rs +++ b/graph/src/data_source/common.rs @@ -1,3 +1,6 @@ +use crate::abi; +use crate::abi::DynSolValueExt; +use crate::abi::FunctionExt; use crate::blockchain::block_stream::EntitySourceOperation; use crate::data::subgraph::SPEC_VERSION_1_4_0; use crate::prelude::{BlockPtr, Value}; @@ -7,8 +10,9 @@ use crate::{ data::value::Word, prelude::Link, }; +use alloy::primitives::{Address, U256}; +use alloy::rpc::types::Log; use anyhow::{anyhow, Context, Error}; -use ethabi::{Address, Contract, Function, LogParam, ParamType, Token}; use graph_derive::CheapClone; use lazy_static::lazy_static; use num_bigint::Sign; @@ -19,12 +23,11 @@ use serde_json; use slog::Logger; use std::collections::HashMap; use std::{str::FromStr, sync::Arc}; -use web3::types::{Log, H160}; #[derive(Clone, Debug, PartialEq)] pub struct MappingABI { pub name: String, - pub contract: Contract, + pub contract: abi::JsonAbi, } impl MappingABI { @@ -33,24 +36,27 @@ impl MappingABI { contract_name: &str, name: &str, signature: Option<&str>, - ) -> Result<&Function, Error> { + ) -> Result<&abi::Function, Error> { let contract = &self.contract; let function = match signature { // Behavior for apiVersion < 0.0.4: look up function by name; for overloaded // functions this always picks the same overloaded variant, which is incorrect // and may lead to encoding/decoding errors - None => contract.function(name).with_context(|| { - format!( - "Unknown function \"{}::{}\" called from WASM runtime", - contract_name, name - ) - })?, + None => contract + .function(name) + .and_then(|matches| matches.first()) + .with_context(|| { + format!( + "Unknown function \"{}::{}\" called from WASM runtime", + contract_name, name + ) + })?, // Behavior for apiVersion >= 0.0.04: look up function by signature of // the form `functionName(uint256,string) returns (bytes32,string)`; this // correctly picks the correct variant of an overloaded function Some(ref signature) => contract - .functions_by_name(name) + .function(name) .with_context(|| { format!( "Unknown function \"{}::{}\" called from WASM runtime", @@ -58,7 +64,7 @@ impl MappingABI { ) })? .iter() - .find(|f| signature == &f.signature()) + .find(|f| signature == &f.signature_compat()) .with_context(|| { format!( "Unknown function \"{}::{}\" with signature `{}` \ @@ -120,7 +126,7 @@ impl AbiJson { if param_type == "tuple" { if let Some(components) = input.get("components") { // Parse the ParamType from the JSON (simplified for now) - let param_type = ParamType::Tuple(vec![]); + let param_type = abi::DynSolType::Tuple(vec![]); return StructFieldInfo::from_components( param_name.to_string(), param_type, @@ -358,7 +364,7 @@ impl UnresolvedMappingABI { self.name, self.file.link ) })?; - let contract = Contract::load(&*contract_bytes) + let contract = serde_json::from_slice(&*contract_bytes) .with_context(|| format!("failed to load ABI {}", self.name))?; // Parse ABI JSON for on-demand struct field extraction @@ -408,17 +414,25 @@ impl CallDecl { self.expr.validate_args() } - pub fn address_for_log(&self, log: &Log, params: &[LogParam]) -> Result { + pub fn address_for_log( + &self, + log: &Log, + params: &[abi::DynSolParam], + ) -> Result { self.address_for_log_with_abi(log, params) } - pub fn address_for_log_with_abi(&self, log: &Log, params: &[LogParam]) -> Result { + pub fn address_for_log_with_abi( + &self, + log: &Log, + params: &[abi::DynSolParam], + ) -> Result { let address = match &self.expr.address { CallArg::HexAddress(address) => *address, CallArg::Ethereum(arg) => match arg { - EthereumArg::Address => log.address, + EthereumArg::Address => log.address(), EthereumArg::Param(name) => { - let value = params + let value = ¶ms .iter() .find(|param| ¶m.name == name.as_str()) .ok_or_else(|| { @@ -428,15 +442,17 @@ impl CallDecl { name ) })? - .value - .clone(); - value.into_address().ok_or_else(|| { + .value; + + let address = value.as_address().ok_or_else(|| { anyhow!( "In declarative call '{}': param {} is not an address", self.label, name ) - })? + })?; + + Address::from(address.into_array()) } EthereumArg::StructField(param_name, field_accesses) => { let param = params @@ -467,22 +483,27 @@ impl CallDecl { Ok(address) } - pub fn args_for_log(&self, log: &Log, params: &[LogParam]) -> Result, Error> { + pub fn args_for_log( + &self, + log: &Log, + params: &[abi::DynSolParam], + ) -> Result, Error> { self.args_for_log_with_abi(log, params) } pub fn args_for_log_with_abi( &self, log: &Log, - params: &[LogParam], - ) -> Result, Error> { + params: &[abi::DynSolParam], + ) -> Result, Error> { + use abi::DynSolValue; self.expr .args .iter() .map(|arg| match arg { - CallArg::HexAddress(address) => Ok(Token::Address(*address)), + CallArg::HexAddress(address) => Ok(DynSolValue::Address(*address)), CallArg::Ethereum(arg) => match arg { - EthereumArg::Address => Ok(Token::Address(log.address)), + EthereumArg::Address => Ok(DynSolValue::Address(log.address())), EthereumArg::Param(name) => { let value = params .iter() @@ -513,7 +534,10 @@ impl CallDecl { .collect() } - pub fn get_function(&self, mapping: &dyn FindMappingABI) -> Result { + pub fn get_function( + &self, + mapping: &dyn FindMappingABI, + ) -> Result { let contract_name = self.expr.abi.to_string(); let function_name = self.expr.func.as_str(); let abi = mapping.find_abi(&contract_name)?; @@ -524,6 +548,7 @@ impl CallDecl { // and may lead to encoding/decoding errors abi.contract .function(function_name) + .and_then(|matches| matches.first()) .cloned() .with_context(|| { format!( @@ -536,7 +561,7 @@ impl CallDecl { pub fn address_for_entity_handler( &self, entity: &EntitySourceOperation, - ) -> Result { + ) -> Result { match &self.expr.address { // Static hex address - just return it directly CallArg::HexAddress(address) => Ok(*address), @@ -557,7 +582,7 @@ impl CallDecl { // Make sure it's a bytes value and convert to address match value { Value::Bytes(bytes) => { - let address = H160::from_slice(bytes.as_slice()); + let address = Address::from_slice(bytes.as_slice()); Ok(address) } _ => Err(anyhow!("param '{name}' must be an address")), @@ -571,8 +596,8 @@ impl CallDecl { pub fn args_for_entity_handler( &self, entity: &EntitySourceOperation, - param_types: Vec, - ) -> Result, Error> { + param_types: Vec, + ) -> Result, Error> { self.validate_entity_handler_args(¶m_types)?; self.expr @@ -586,7 +611,7 @@ impl CallDecl { } /// Validates that the number of provided arguments matches the expected parameter types. - fn validate_entity_handler_args(&self, param_types: &[ParamType]) -> Result<(), Error> { + fn validate_entity_handler_args(&self, param_types: &[abi::DynSolType]) -> Result<(), Error> { if self.expr.args.len() != param_types.len() { return Err(anyhow!( "mismatched number of arguments: expected {}, got {}", @@ -602,9 +627,9 @@ impl CallDecl { fn process_entity_handler_arg( &self, arg: &CallArg, - expected_type: &ParamType, + expected_type: &abi::DynSolType, entity: &EntitySourceOperation, - ) -> Result { + ) -> Result { match arg { CallArg::HexAddress(address) => self.process_hex_address(*address, expected_type), CallArg::Ethereum(_) => Err(anyhow!( @@ -619,11 +644,11 @@ impl CallDecl { /// Converts a hex address to a token, ensuring it matches the expected parameter type. fn process_hex_address( &self, - address: H160, - expected_type: &ParamType, - ) -> Result { + address: Address, + expected_type: &abi::DynSolType, + ) -> Result { match expected_type { - ParamType::Address => Ok(Token::Address(address)), + abi::DynSolType::Address => Ok(abi::DynSolValue::Address(address)), _ => Err(anyhow!( "type mismatch: hex address provided for non-address parameter" )), @@ -634,9 +659,9 @@ impl CallDecl { fn process_entity_param( &self, name: &str, - expected_type: &ParamType, + expected_type: &abi::DynSolType, entity: &EntitySourceOperation, - ) -> Result { + ) -> Result { let value = entity .entity .get(name) @@ -650,27 +675,44 @@ impl CallDecl { fn convert_entity_value_to_token( &self, value: &Value, - expected_type: &ParamType, + expected_type: &abi::DynSolType, param_name: &str, - ) -> Result { + ) -> Result { + use abi::DynSolType; + use abi::DynSolValue; + match (expected_type, value) { - (ParamType::Address, Value::Bytes(b)) => { - Ok(Token::Address(H160::from_slice(b.as_slice()))) + (DynSolType::Address, Value::Bytes(b)) => { + Ok(DynSolValue::Address(b.as_slice().try_into()?)) + } + (DynSolType::Bytes, Value::Bytes(b)) => Ok(DynSolValue::Bytes(b.as_ref().to_vec())), + (DynSolType::FixedBytes(size), Value::Bytes(b)) if b.len() == *size => { + DynSolValue::fixed_bytes_from_slice(b.as_ref()) + } + (DynSolType::String, Value::String(s)) => Ok(DynSolValue::String(s.to_string())), + (DynSolType::Bool, Value::Bool(b)) => Ok(DynSolValue::Bool(*b)), + (DynSolType::Int(_), Value::Int(i)) => { + let x = abi::I256::try_from(*i)?; + Ok(DynSolValue::Int(x, x.bits() as usize)) + } + (DynSolType::Int(_), Value::Int8(i)) => { + let x = abi::I256::try_from(*i)?; + Ok(DynSolValue::Int(x, x.bits() as usize)) } - (ParamType::Bytes, Value::Bytes(b)) => Ok(Token::Bytes(b.as_ref().to_vec())), - (ParamType::FixedBytes(size), Value::Bytes(b)) if b.len() == *size => { - Ok(Token::FixedBytes(b.as_ref().to_vec())) + (DynSolType::Int(_), Value::BigInt(i)) => { + let x = + abi::I256::from_le_bytes(i.to_signed_u256().to_le_bytes::<{ U256::BYTES }>()); + Ok(DynSolValue::Int(x, x.bits() as usize)) } - (ParamType::String, Value::String(s)) => Ok(Token::String(s.to_string())), - (ParamType::Bool, Value::Bool(b)) => Ok(Token::Bool(*b)), - (ParamType::Int(_), Value::Int(i)) => Ok(Token::Int((*i).into())), - (ParamType::Int(_), Value::Int8(i)) => Ok(Token::Int((*i).into())), - (ParamType::Int(_), Value::BigInt(i)) => Ok(Token::Int(i.to_signed_u256())), - (ParamType::Uint(_), Value::Int(i)) if *i >= 0 => Ok(Token::Uint((*i).into())), - (ParamType::Uint(_), Value::BigInt(i)) if i.sign() == Sign::Plus => { - Ok(Token::Uint(i.to_unsigned_u256()?)) + (DynSolType::Uint(_), Value::Int(i)) if *i >= 0 => { + let x = U256::try_from(*i)?; + Ok(DynSolValue::Uint(x, x.bit_len())) } - (ParamType::Array(inner_type), Value::List(values)) => { + (DynSolType::Uint(_), Value::BigInt(i)) if i.sign() == Sign::Plus => { + let x = i.to_unsigned_u256()?; + Ok(DynSolValue::Uint(x, x.bit_len())) + } + (DynSolType::Array(inner_type), Value::List(values)) => { self.process_entity_array_values(values, inner_type.as_ref(), param_name) } _ => Err(anyhow!( @@ -684,41 +726,42 @@ impl CallDecl { fn process_entity_array_values( &self, values: &[Value], - inner_type: &ParamType, + inner_type: &abi::DynSolType, param_name: &str, - ) -> Result { - let tokens: Result, Error> = values + ) -> Result { + let tokens: Result, Error> = values .iter() .enumerate() .map(|(idx, v)| { self.convert_entity_value_to_token(v, inner_type, &format!("{param_name}[{idx}]")) }) .collect(); - Ok(Token::Array(tokens?)) + Ok(abi::DynSolValue::Array(tokens?)) } /// Extracts a nested field value from a struct parameter with mixed numeric/named access fn extract_nested_struct_field_as_address( - struct_token: &Token, + struct_token: &abi::DynSolValue, field_accesses: &[usize], call_label: &str, - ) -> Result { + ) -> Result { let field_token = Self::extract_nested_struct_field(struct_token, field_accesses, call_label)?; - field_token.into_address().ok_or_else(|| { + let address = field_token.as_address().ok_or_else(|| { anyhow!( "In declarative call '{}': nested struct field is not an address", call_label ) - }) + })?; + Ok(address) } /// Extracts a nested field value from a struct parameter using numeric indices fn extract_nested_struct_field( - struct_token: &Token, + struct_token: &abi::DynSolValue, field_accesses: &[usize], call_label: &str, - ) -> Result { + ) -> Result { assert!( !field_accesses.is_empty(), "Internal error: empty field access path should be caught at parse time" @@ -728,7 +771,7 @@ impl CallDecl { for (index, &field_index) in field_accesses.iter().enumerate() { match current_token { - Token::Tuple(fields) => { + abi::DynSolValue::Tuple(fields) => { let field_token = fields .get(field_index) .ok_or_else(|| { @@ -998,15 +1041,15 @@ pub struct StructFieldInfo { pub param_name: String, /// Mapping from field names to their indices in the tuple pub field_mappings: HashMap, - /// The ethabi ParamType for type validation - pub param_type: ParamType, + /// The alloy DynSolType for type validation + pub param_type: abi::DynSolType, } impl StructFieldInfo { /// Create a new StructFieldInfo from ABI JSON components pub fn from_components( param_name: String, - param_type: ParamType, + param_type: abi::DynSolType, components: &serde_json::Value, ) -> Result { let mut field_mappings = HashMap::new(); @@ -1189,16 +1232,16 @@ pub struct DeclaredCall { label: String, contract_name: String, address: Address, - function: Function, - args: Vec, + function: abi::Function, + args: Vec, } impl DeclaredCall { pub fn from_log_trigger( mapping: &dyn FindMappingABI, call_decls: &CallDecls, - log: &Log, - params: &[LogParam], + log: &alloy::rpc::types::Log, + params: &[abi::DynSolParam], ) -> Result, anyhow::Error> { Self::from_log_trigger_with_event(mapping, call_decls, log, params) } @@ -1207,7 +1250,7 @@ impl DeclaredCall { mapping: &dyn FindMappingABI, call_decls: &CallDecls, log: &Log, - params: &[LogParam], + params: &[abi::DynSolParam], ) -> Result, anyhow::Error> { Self::create_calls(mapping, call_decls, |decl, _| { Ok(( @@ -1221,13 +1264,13 @@ impl DeclaredCall { mapping: &dyn FindMappingABI, call_decls: &CallDecls, entity: &EntitySourceOperation, - ) -> Result, anyhow::Error> { + ) -> Result, Error> { Self::create_calls(mapping, call_decls, |decl, function| { let param_types = function .inputs .iter() - .map(|param| param.kind.clone()) - .collect::>(); + .map(|param| param.selector_type().parse()) + .collect::, _>>()?; Ok(( decl.address_for_entity_handler(entity)?, @@ -1247,7 +1290,7 @@ impl DeclaredCall { get_address_and_args: F, ) -> Result, anyhow::Error> where - F: Fn(&CallDecl, &Function) -> Result<(Address, Vec), anyhow::Error>, + F: Fn(&CallDecl, &abi::Function) -> Result<(Address, Vec), anyhow::Error>, { let mut calls = Vec::new(); for decl in call_decls.decls.iter() { @@ -1285,13 +1328,15 @@ pub struct ContractCall { pub contract_name: String, pub address: Address, pub block_ptr: BlockPtr, - pub function: Function, - pub args: Vec, + pub function: abi::Function, + pub args: Vec, pub gas: Option, } #[cfg(test)] mod tests { + use alloy::primitives::B256; + use crate::data::subgraph::SPEC_VERSION_1_3_0; use super::*; @@ -1530,7 +1575,7 @@ mod tests { let parser = ExprParser::new(); let addr = "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"; - let hex_address = CallArg::HexAddress(web3::types::H160::from_str(addr).unwrap()); + let hex_address = CallArg::HexAddress(Address::from_str(addr).unwrap()); // Test HexAddress in address position let expr: CallExpr = parser.ok(&format!("Pool[{}].growth()", addr)); @@ -1597,18 +1642,19 @@ mod tests { #[test] fn test_struct_field_access_functions() { - use ethabi::Token; + use crate::abi::DynSolValue; + use alloy::primitives::{Address, U256}; let parser = ExprParser::new(); let tuple_fields = vec![ - Token::Uint(ethabi::Uint::from(8u8)), // index 0: uint8 - Token::Address([1u8; 20].into()), // index 1: address - Token::Uint(ethabi::Uint::from(1000u64)), // index 2: uint256 + DynSolValue::Uint(U256::from(8u8), 8), // index 0: uint8 + DynSolValue::Address(Address::from([1u8; 20])), // index 1: address + DynSolValue::Uint(U256::from(1000u64), 256), // index 2: uint256 ]; // Test extract_struct_field with numeric indices - let struct_token = Token::Tuple(tuple_fields.clone()); + let struct_token = DynSolValue::Tuple(tuple_fields.clone()); // Test accessing index 0 (uint8) let result = @@ -1646,8 +1692,9 @@ mod tests { #[test] fn test_declarative_call_error_context() { - use crate::prelude::web3::types::{Log, H160, H256}; - use ethabi::{LogParam, Token}; + use crate::abi::{DynSolParam, DynSolValue}; + use alloy::primitives::U256; + use alloy::rpc::types::Log; let parser = ExprParser::new(); @@ -1659,18 +1706,19 @@ mod tests { }; // Test scenario 1: Unknown parameter + let inner_log = alloy::primitives::Log { + address: Address::ZERO, + data: alloy::primitives::LogData::new_unchecked(vec![].into(), vec![].into()), + }; let log = Log { - address: H160::zero(), - topics: vec![], - data: vec![].into(), - block_hash: Some(H256::zero()), - block_number: Some(1.into()), - transaction_hash: Some(H256::zero()), - transaction_index: Some(0.into()), - log_index: Some(0.into()), - transaction_log_index: Some(0.into()), - log_type: None, - removed: Some(false), + inner: inner_log, + block_hash: Some(B256::ZERO), + block_number: Some(1), + block_timestamp: None, + transaction_hash: Some(B256::ZERO), + transaction_index: Some(0), + log_index: Some(0), + removed: false, }; let params = vec![]; // Empty params - 'asset' param is missing @@ -1681,9 +1729,9 @@ mod tests { assert!(error_msg.contains("unknown param asset")); // Test scenario 2: Struct field access error - let params = vec![LogParam { + let params = vec![DynSolParam { name: "asset".to_string(), - value: Token::Tuple(vec![Token::Uint(ethabi::Uint::from(1u8))]), // Only 1 field, but trying to access index 1 + value: DynSolValue::Tuple(vec![DynSolValue::Uint(U256::from(1u8), 8)]), // Only 1 field, but trying to access index 1 }]; let result = call_decl.address_for_log(&log, ¶ms); @@ -1694,11 +1742,11 @@ mod tests { assert!(error_msg.contains("struct has 1 fields")); // Test scenario 3: Non-address field access - let params = vec![LogParam { + let params = vec![DynSolParam { name: "asset".to_string(), - value: Token::Tuple(vec![ - Token::Uint(ethabi::Uint::from(1u8)), - Token::Uint(ethabi::Uint::from(2u8)), // Index 1 is uint, not address + value: DynSolValue::Tuple(vec![ + DynSolValue::Uint(U256::from(1u8), 8), + DynSolValue::Uint(U256::from(2u8), 8), // Index 1 is uint, not address ]), }]; @@ -1724,18 +1772,18 @@ mod tests { // Create a structure where base has only 2 fields instead of 3 // The parser thinks there should be 3 fields based on ABI, but at runtime we provide only 2 - let base_struct = Token::Tuple(vec![ - Token::Address([1u8; 20].into()), // addr at index 0 - Token::Uint(ethabi::Uint::from(100u64)), // amount at index 1 - // Missing the active field at index 2! + let base_struct = DynSolValue::Tuple(vec![ + DynSolValue::Address(Address::from([1u8; 20])), // addr at index 0 + DynSolValue::Uint(U256::from(100u64), 256), // amount at index 1 + // Missing the active field at index 2! ]); - let params = vec![LogParam { + let params = vec![DynSolParam { name: "complexAsset".to_string(), - value: Token::Tuple(vec![ - base_struct, // base with only 2 fields - Token::String("metadata".to_string()), // metadata at index 1 - Token::Array(vec![]), // values at index 2 + value: DynSolValue::Tuple(vec![ + base_struct, // base with only 2 fields + DynSolValue::String("metadata".to_string()), // metadata at index 1 + DynSolValue::Array(vec![]), // values at index 2 ]), }]; @@ -1749,7 +1797,8 @@ mod tests { #[test] fn test_struct_field_extraction_comprehensive() { - use ethabi::Token; + use crate::abi::DynSolValue; + use alloy::primitives::{Address, U256}; // Create a complex nested structure for comprehensive testing: // struct Asset { @@ -1761,37 +1810,37 @@ mod tests { // address addr; // index 0 // string name; // index 1 // } - let inner_struct = Token::Tuple(vec![ - Token::Address([0x42; 20].into()), // token.addr - Token::String("TokenName".to_string()), // token.name + let inner_struct = DynSolValue::Tuple(vec![ + DynSolValue::Address(Address::from([0x42; 20])), // token.addr + DynSolValue::String("TokenName".to_string()), // token.name ]); - let outer_struct = Token::Tuple(vec![ - Token::Uint(ethabi::Uint::from(1u8)), // asset.kind - inner_struct, // asset.token - Token::Uint(ethabi::Uint::from(1000u64)), // asset.amount + let outer_struct = DynSolValue::Tuple(vec![ + DynSolValue::Uint(U256::from(1u8), 8), // asset.kind + inner_struct, // asset.token + DynSolValue::Uint(U256::from(1000u64), 256), // asset.amount ]); // Test cases: (path, expected_value, description) let test_cases = vec![ ( vec![0], - Token::Uint(ethabi::Uint::from(1u8)), + DynSolValue::Uint(U256::from(1u8), 8), "Simple field access", ), ( vec![1, 0], - Token::Address([0x42; 20].into()), + DynSolValue::Address(Address::from([0x42; 20])), "Nested field access", ), ( vec![1, 1], - Token::String("TokenName".to_string()), + DynSolValue::String("TokenName".to_string()), "Nested string field", ), ( vec![2], - Token::Uint(ethabi::Uint::from(1000u64)), + DynSolValue::Uint(U256::from(1000u64), 256), "Last field access", ), ]; diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 3166db5971b..639cced50a0 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -37,6 +37,8 @@ pub mod env; pub mod ipfs; +pub mod abi; + /// Wrapper for spawning tasks that abort on panic, which is our default. mod tokio; #[cfg(debug_assertions)] @@ -76,12 +78,12 @@ pub use url; /// ``` pub mod prelude { pub use ::anyhow; + pub use alloy; pub use anyhow::{anyhow, Context as _, Error}; pub use atty; pub use chrono; pub use diesel; pub use envconfig; - pub use ethabi; pub use hex; pub use lazy_static::lazy_static; pub use prost; @@ -105,7 +107,6 @@ pub mod prelude { pub use tokio; pub use toml; pub use tonic; - pub use web3; pub type DynTryFuture<'a, Ok = (), Err = Error> = Pin> + Send + 'a>>; @@ -174,6 +175,7 @@ pub mod prelude { pub use crate::util::cache_weight::CacheWeight; pub use crate::util::futures::{retry, TimeoutError}; pub use crate::util::stats::MovingStats; + pub use crate::util::test_utils::*; macro_rules! static_graphql { ($m:ident, $m2:ident, {$($n:ident,)*}) => { diff --git a/graph/src/runtime/mod.rs b/graph/src/runtime/mod.rs index 7958b991598..ac94d33c32f 100644 --- a/graph/src/runtime/mod.rs +++ b/graph/src/runtime/mod.rs @@ -260,7 +260,7 @@ pub enum IndexForAscTypeId { // Reserved discriminant space for more Ethereum type IDs: [1000, 1499] TransactionReceipt = 1000, Log = 1001, - ArrayH256 = 1002, + ArrayB256 = 1002, ArrayLog = 1003, ArrayTypedMapStringStoreValue = 1004, // Continue to add more Ethereum type IDs here. diff --git a/graph/src/util/mod.rs b/graph/src/util/mod.rs index 4cdf52a82a5..7a67c984a63 100644 --- a/graph/src/util/mod.rs +++ b/graph/src/util/mod.rs @@ -35,3 +35,6 @@ pub mod monitored; pub mod intern; pub mod herd_cache; + +/// Test utilities for creating mock blockchain data structures +pub mod test_utils; diff --git a/graph/src/util/test_utils.rs b/graph/src/util/test_utils.rs new file mode 100644 index 00000000000..38b618560c4 --- /dev/null +++ b/graph/src/util/test_utils.rs @@ -0,0 +1,57 @@ +use alloy::consensus::{TxEnvelope, TxLegacy}; +use alloy::primitives::Address; +use alloy::rpc::types::Transaction; + +use crate::prelude::alloy::consensus::Header as ConsensusHeader; +use crate::prelude::alloy::primitives::B256; +use crate::prelude::alloy::rpc::types::{Block, Header}; + +/// Creates a minimal Alloy Block for testing purposes. +pub fn create_minimal_block_for_test(block_number: u64, block_hash: B256) -> Block { + // Create consensus header with defaults, but set the specific number + let mut consensus_header = ConsensusHeader::default(); + consensus_header.number = block_number; + + // Create RPC header with the specific hash + let rpc_header = Header { + hash: block_hash, + inner: consensus_header, + total_difficulty: None, + size: None, + }; + + // Create an empty block with this header + Block::empty(rpc_header) +} + +/// Generic function that creates a mock legacy Transaction from ANY log +pub fn create_dummy_transaction( + block_number: u64, + block_hash: B256, + transaction_index: Option, + transaction_hash: B256, +) -> Transaction { + use alloy::{ + consensus::transaction::Recovered, + consensus::Signed, + primitives::{Signature, U256}, + }; + + let tx = TxLegacy::default(); + + // Create a dummy signature + let signature = Signature::new(U256::from(0x1111), U256::from(0x2222), false); + + let signed_tx = Signed::new_unchecked(tx, signature, transaction_hash); + let envelope = TxEnvelope::Legacy(signed_tx); + + let recovered = Recovered::new_unchecked(envelope, Address::ZERO); + + Transaction { + inner: recovered, + block_hash: Some(block_hash), + block_number: Some(block_number), + transaction_index: transaction_index, + effective_gas_price: None, + } +} diff --git a/graphql/src/store/resolver.rs b/graphql/src/store/resolver.rs index 500964ea7a2..f2a42c1c824 100644 --- a/graphql/src/store/resolver.rs +++ b/graphql/src/store/resolver.rs @@ -10,6 +10,7 @@ use graph::data::query::{CacheStatus, QueryResults, Trace}; use graph::data::store::ID; use graph::data::value::{Object, Word}; use graph::derive::CheapClone; +use graph::prelude::alloy::primitives::B256; use graph::prelude::*; use graph::schema::{ ast as sast, INTROSPECTION_SCHEMA_FIELD_NAME, INTROSPECTION_TYPE_FIELD_NAME, META_FIELD_NAME, @@ -204,11 +205,11 @@ impl StoreResolver { // locate_block indicates that we do not have a block hash // by setting the hash to `zero` // See 7a7b9708-adb7-4fc2-acec-88680cb07ec1 - let hash_h256 = ptr.hash_as_h256(); - if hash_h256 == web3::types::H256::zero() { + let hash_b256 = ptr.hash.as_b256(); + if hash_b256 == B256::ZERO { None } else { - Some(r::Value::String(format!("0x{:x}", hash_h256))) + Some(r::Value::String(format!("0x{:x}", hash_b256))) } }) .unwrap_or(r::Value::Null); diff --git a/node/src/manager/commands/chain.rs b/node/src/manager/commands/chain.rs index 12b69d0bf4e..ad6d7bdd543 100644 --- a/node/src/manager/commands/chain.rs +++ b/node/src/manager/commands/chain.rs @@ -12,7 +12,7 @@ use graph::components::store::ChainIdStore; use graph::components::store::StoreError; use graph::prelude::BlockNumber; use graph::prelude::ChainStore as _; -use graph::prelude::LightEthereumBlockExt; +use graph::prelude::LightEthereumBlock; use graph::prelude::{anyhow, anyhow::bail}; use graph::slog::Logger; use graph::{ @@ -300,16 +300,18 @@ pub async fn ingest( else { bail!("block number {number} not found"); }; - let ptr = block.block_ptr(); + let hash = block.header.hash; + let number = block.header.number; // For inserting the block, it doesn't matter whether the block is final or not. - let block = Arc::new(BlockFinality::Final(Arc::new(block))); + let block = Arc::new(BlockFinality::Final(Arc::new(LightEthereumBlock::new( + block, + )))); chain_store.upsert_block(block).await?; - let rows = chain_store - .confirm_block_hash(ptr.number, &ptr.hash) - .await?; + let hash = hash.into(); + let rows = chain_store.confirm_block_hash(number as i32, &hash).await?; - println!("Inserted block {}", ptr); + println!("Inserted block {}", hash); if rows > 0 { println!(" (also deleted {rows} duplicate row(s) with that number)"); } diff --git a/node/src/manager/commands/check_blocks.rs b/node/src/manager/commands/check_blocks.rs index 15314067a49..f6a4506a2f8 100644 --- a/node/src/manager/commands/check_blocks.rs +++ b/node/src/manager/commands/check_blocks.rs @@ -4,8 +4,8 @@ use graph::{ cheap_clone::CheapClone, components::store::ChainStore as ChainStoreTrait, prelude::{ + alloy::primitives::B256, anyhow::{self, anyhow, Context}, - web3::types::H256, }, slog::Logger, }; @@ -105,7 +105,7 @@ pub async fn truncate(chain_store: Arc, skip_confirmation: bool) -> } async fn run( - block_hash: &H256, + block_hash: &B256, chain_store: Arc, ethereum_adapter: &EthereumAdapter, logger: &Logger, @@ -124,7 +124,7 @@ async fn run( async fn handle_multiple_block_hashes( block_number: i32, - block_hashes: &[H256], + block_hashes: &[B256], chain_store: &ChainStore, delete_duplicates: bool, ) -> anyhow::Result<()> { @@ -157,7 +157,10 @@ mod steps { use graph::{ anyhow::bail, - prelude::serde_json::{self, Value}, + prelude::{ + alloy::primitives::B256, + serde_json::{self, Value}, + }, }; use json_structural_diff::{colorize as diff_to_string, JsonDiff}; @@ -169,11 +172,11 @@ mod steps { pub(super) async fn resolve_block_hash_from_block_number( number: i32, chain_store: &ChainStore, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let block_hashes = chain_store.block_hashes_by_block_number(number).await?; Ok(block_hashes .into_iter() - .map(|x| H256::from_slice(&x.as_slice()[..32])) + .map(|x| B256::from_slice(&x.as_slice()[..32])) .collect()) } @@ -181,7 +184,7 @@ mod steps { /// /// Errors on a non-unary result. pub(super) async fn fetch_single_cached_block( - block_hash: H256, + block_hash: B256, chain_store: Arc, ) -> anyhow::Result { let blocks = chain_store.blocks(vec![block_hash.into()]).await?; @@ -199,7 +202,7 @@ mod steps { /// Errors on provider failure or if the returned block has a different hash than the one /// requested. pub(super) async fn fetch_single_provider_block( - block_hash: &H256, + block_hash: &B256, ethereum_adapter: &EthereumAdapter, logger: &Logger, ) -> anyhow::Result { @@ -209,7 +212,7 @@ mod steps { .with_context(|| format!("failed to fetch block {block_hash}"))? .ok_or_else(|| anyhow!("JRPC provider found no block with hash {block_hash:?}"))?; ensure!( - provider_block.hash == Some(*block_hash), + provider_block.header.hash == *block_hash, "Provider responded with a different block hash" ); serde_json::to_value(provider_block) @@ -237,7 +240,7 @@ mod steps { } /// Prints the difference between two [`serde_json::Value`] values to the user. - pub(super) fn report_difference(difference: Option<&str>, hash: &H256) { + pub(super) fn report_difference(difference: Option<&str>, hash: &B256) { if let Some(diff) = difference { eprintln!("block {hash} diverges from cache:"); eprintln!("{diff}"); @@ -247,7 +250,7 @@ mod steps { } /// Attempts to delete a block from the block cache. - pub(super) async fn delete_block(hash: &H256, chain_store: &ChainStore) -> anyhow::Result<()> { + pub(super) async fn delete_block(hash: &B256, chain_store: &ChainStore) -> anyhow::Result<()> { println!("Deleting block {hash} from cache."); chain_store.delete_blocks(&[hash]).await?; println!("Done."); @@ -263,13 +266,13 @@ mod steps { mod helpers { use super::*; - use graph::prelude::hex; + use graph::prelude::{alloy::primitives::B256, hex}; - /// Tries to parse a [`H256`] from a hex string. - pub(super) fn parse_block_hash(hash: &str) -> anyhow::Result { + /// Tries to parse a [`B256`] from a hex string. + pub(super) fn parse_block_hash(hash: &str) -> anyhow::Result { let hash = hash.trim_start_matches("0x"); let hash = hex::decode(hash)?; - Ok(H256::from_slice(&hash)) + Ok(B256::from_slice(&hash)) } } diff --git a/runtime/test/src/common.rs b/runtime/test/src/common.rs index b0ec8018db2..dec0d7046ce 100644 --- a/runtime/test/src/common.rs +++ b/runtime/test/src/common.rs @@ -1,4 +1,3 @@ -use ethabi::Contract; use graph::blockchain::BlockTime; use graph::components::store::DeploymentLocator; use graph::components::subgraph::SharedProofOfIndexing; @@ -8,6 +7,7 @@ use graph::data_source::common::MappingABI; use graph::env::EnvVars; use graph::ipfs::{IpfsMetrics, IpfsRpcClient, ServerAddress}; use graph::log; +use graph::prelude::alloy::primitives::Address; use graph::prelude::*; use graph_chain_ethereum::{Chain, DataSource, DataSourceTemplate, Mapping, TemplateSource}; use graph_runtime_wasm::host_exports::DataSourceDetails; @@ -15,7 +15,6 @@ use graph_runtime_wasm::{HostExports, MappingContext}; use semver::Version; use std::env; use std::str::FromStr; -use web3::types::Address; lazy_static! { pub static ref LOGGER: Logger = match env::var_os("GRAPH_LOG") { @@ -83,7 +82,7 @@ fn mock_host_exports( fn mock_abi() -> MappingABI { MappingABI { name: "mock_abi".to_string(), - contract: Contract::load( + contract: serde_json::from_str( r#"[ { "inputs": [ @@ -94,8 +93,7 @@ fn mock_abi() -> MappingABI { ], "type": "constructor" } - ]"# - .as_bytes(), + ]"#, ) .unwrap(), } diff --git a/runtime/test/src/test.rs b/runtime/test/src/test.rs index 4e65c236500..2e3f671cfe6 100644 --- a/runtime/test/src/test.rs +++ b/runtime/test/src/test.rs @@ -6,7 +6,7 @@ use graph::data::store::{scalar, Id, IdType}; use graph::data::subgraph::*; use graph::data::value::Word; use graph::ipfs::test_utils::add_files_to_local_ipfs_node_for_testing; -use graph::prelude::web3::types::U256; +use graph::prelude::alloy::primitives::U256; use graph::runtime::gas::GasCounter; use graph::runtime::{AscIndexId, AscType, HostExportError}; use graph::runtime::{AscPtr, ToAscObj}; @@ -22,7 +22,6 @@ use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; use test_store::{LOGGER, STORE}; use wasmtime::{AsContext, AsContextMut}; -use web3::types::H160; use crate::common::{mock_context, mock_data_source}; @@ -715,19 +714,19 @@ async fn test_big_int_to_hex(api_version: Version, gas_used: u64) { .await; // Convert zero to hex - let zero = BigInt::from_unsigned_u256(&U256::zero()); + let zero = BigInt::from_unsigned_u256(&U256::ZERO); let zero_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &zero).await; let zero_hex_str: String = instance.asc_get(zero_hex_ptr).unwrap(); assert_eq!(zero_hex_str, "0x0"); // Convert 1 to hex - let one = BigInt::from_unsigned_u256(&U256::one()); + let one = BigInt::from_unsigned_u256(&U256::ONE); let one_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &one).await; let one_hex_str: String = instance.asc_get(one_hex_ptr).unwrap(); assert_eq!(one_hex_str, "0x1"); // Convert U256::max_value() to hex - let u256_max = BigInt::from_unsigned_u256(&U256::max_value()); + let u256_max = BigInt::from_unsigned_u256(&U256::MAX); let u256_max_hex_ptr: AscPtr = instance.invoke_export1("big_int_to_hex", &u256_max).await; let u256_max_hex_str: String = instance.asc_get(u256_max_hex_ptr).unwrap(); diff --git a/runtime/test/src/test/abi.rs b/runtime/test/src/test/abi.rs index 886626a2871..5aa7df7b132 100644 --- a/runtime/test/src/test/abi.rs +++ b/runtime/test/src/test/abi.rs @@ -1,4 +1,4 @@ -use graph::prelude::{ethabi::Token, web3::types::U256}; +use graph::{abi, prelude::alloy::primitives::Address}; use graph_runtime_wasm::asc_abi::class::{ ArrayBuffer, AscAddress, AscEnum, AscEnumArray, EthereumValueKind, StoreValueKind, TypedArray, }; @@ -182,9 +182,9 @@ async fn abi_bytes_and_fixed_bytes_v0_0_5() { test_abi_bytes_and_fixed_bytes(API_VERSION_0_0_5).await; } -async fn test_abi_ethabi_token_identity(api_version: Version) { +async fn test_abi_alloy_token_identity(api_version: Version) { let mut instance = test_module( - "abiEthabiTokenIdentity", + "abiAlloyTokenIdentity", mock_data_source( &wasm_file_path("abi_token.wasm", api_version.clone()), api_version.clone(), @@ -194,8 +194,8 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { .await; // Token::Address - let address = H160([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); - let token_address = Token::Address(address); + let address = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; + let token_address = abi::DynSolValue::Address(address.into()); let new_address_obj: AscPtr = instance .invoke_export1("token_to_address", &token_address) @@ -209,7 +209,7 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { assert_eq!(token_address, new_token); // Token::Bytes - let token_bytes = Token::Bytes(vec![42, 45, 7, 245, 45]); + let token_bytes = abi::DynSolValue::Bytes(vec![42, 45, 7, 245, 45]); let new_bytes_obj: AscPtr = instance .invoke_export1("token_to_bytes", &token_bytes) .await; @@ -221,7 +221,8 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { assert_eq!(token_bytes, new_token); // Token::Int - let int_token = Token::Int(U256([256, 453452345, 0, 42])); + let int = abi::I256::from_limbs([256, 453452345, 0, 42]); + let int_token = abi::DynSolValue::Int(int, int.bits() as usize); let new_int_obj: AscPtr = instance.invoke_export1("token_to_int", &int_token).await; @@ -233,7 +234,8 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { assert_eq!(int_token, new_token); // Token::Uint - let uint_token = Token::Uint(U256([256, 453452345, 0, 42])); + let uint = U256::from_limbs([256, 453452345, 0, 42]); + let uint_token = abi::DynSolValue::Uint(uint, uint.bit_len()); let new_uint_obj: AscPtr = instance.invoke_export1("token_to_uint", &uint_token).await; @@ -246,7 +248,7 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { assert_ne!(uint_token, int_token); // Token::Bool - let token_bool = Token::Bool(true); + let token_bool = abi::DynSolValue::Bool(true); let token_bool_ptr = instance.asc_new(&token_bool).await.unwrap(); let func = instance @@ -270,7 +272,7 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { assert_eq!(token_bool, new_token); // Token::String - let token_string = Token::String("漢字Go🇧🇷".into()); + let token_string = abi::DynSolValue::String("漢字Go🇧🇷".into()); let new_string_obj: AscPtr = instance .invoke_export1("token_to_string", &token_string) .await; @@ -282,8 +284,8 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { assert_eq!(token_string, new_token); // Token::Array - let token_array = Token::Array(vec![token_address, token_bytes, token_bool]); - let token_array_nested = Token::Array(vec![token_string, token_array]); + let token_array = abi::DynSolValue::Array(vec![token_address, token_bytes, token_bool]); + let token_array_nested = abi::DynSolValue::Array(vec![token_string, token_array]); let new_array_obj: AscEnumArray = instance .invoke_export1("token_to_array", &token_array_nested) .await; @@ -291,7 +293,7 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { let new_token_ptr = instance .takes_ptr_returns_ptr("token_from_array", new_array_obj) .await; - let new_token: Token = instance.asc_get(new_token_ptr).unwrap(); + let new_token: abi::DynSolValue = instance.asc_get(new_token_ptr).unwrap(); assert_eq!(new_token, token_array_nested); } @@ -300,14 +302,14 @@ async fn test_abi_ethabi_token_identity(api_version: Version) { /// and assert the final token is the same as the starting one. #[graph::test] async fn abi_ethabi_token_identity_v0_0_4() { - test_abi_ethabi_token_identity(API_VERSION_0_0_4).await; + test_abi_alloy_token_identity(API_VERSION_0_0_4).await; } /// Test a roundtrip Token -> Payload -> Token identity conversion through asc, /// and assert the final token is the same as the starting one. #[graph::test] async fn abi_ethabi_token_identity_v0_0_5() { - test_abi_ethabi_token_identity(API_VERSION_0_0_5).await; + test_abi_alloy_token_identity(API_VERSION_0_0_5).await; } async fn test_abi_store_value(api_version: Version) { @@ -447,17 +449,17 @@ async fn test_abi_h160(api_version: Version) { api_version, ) .await; - let address = H160::zero(); + let address = Address::ZERO; // As an `Uint8Array` let new_address_obj: AscPtr = module.invoke_export1("test_address", &address).await; // This should have 1 added to the first and last byte. - let new_address: H160 = module.asc_get(new_address_obj).unwrap(); + let new_address: Address = module.asc_get(new_address_obj).unwrap(); assert_eq!( new_address, - H160([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) + Address::from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) ) } @@ -509,14 +511,14 @@ async fn test_abi_big_int(api_version: Version) { .await; // Test passing in 0 and increment it by 1 - let old_uint = U256::zero(); + let old_uint = U256::ZERO; let new_uint_obj: AscPtr = module .invoke_export1("test_uint", &BigInt::from_unsigned_u256(&old_uint)) .await; let new_uint: BigInt = module.asc_get(new_uint_obj).unwrap(); assert_eq!(new_uint, BigInt::from(1_i32)); let new_uint = new_uint.to_unsigned_u256().unwrap(); - assert_eq!(new_uint, U256([1, 0, 0, 0])); + assert_eq!(new_uint, U256::from_limbs([1, 0, 0, 0])); // Test passing in -50 and increment it by 1 let old_uint = BigInt::from(-50); diff --git a/runtime/wasm/Cargo.toml b/runtime/wasm/Cargo.toml index e2260a7bb59..d446bd0fbe1 100644 --- a/runtime/wasm/Cargo.toml +++ b/runtime/wasm/Cargo.toml @@ -5,7 +5,6 @@ edition.workspace = true [dependencies] async-trait = { workspace = true } -ethabi = "17.2" hex = "0.4.3" graph = { path = "../../graph" } bs58 = "0.4.0" diff --git a/runtime/wasm/src/asc_abi/class.rs b/runtime/wasm/src/asc_abi/class.rs index 4fe5b3192cd..805e30de895 100644 --- a/runtime/wasm/src/asc_abi/class.rs +++ b/runtime/wasm/src/asc_abi/class.rs @@ -1,6 +1,5 @@ use async_trait::async_trait; -use ethabi; - +use graph::abi; use graph::{ data::{ store::{self, scalar::Timestamp}, @@ -540,21 +539,25 @@ pub enum EthereumValueKind { FixedArray, Array, Tuple, + Function, } impl EthereumValueKind { - pub(crate) fn get_kind(token: ðabi::Token) -> Self { - match token { - ethabi::Token::Address(_) => EthereumValueKind::Address, - ethabi::Token::FixedBytes(_) => EthereumValueKind::FixedBytes, - ethabi::Token::Bytes(_) => EthereumValueKind::Bytes, - ethabi::Token::Int(_) => EthereumValueKind::Int, - ethabi::Token::Uint(_) => EthereumValueKind::Uint, - ethabi::Token::Bool(_) => EthereumValueKind::Bool, - ethabi::Token::String(_) => EthereumValueKind::String, - ethabi::Token::FixedArray(_) => EthereumValueKind::FixedArray, - ethabi::Token::Array(_) => EthereumValueKind::Array, - ethabi::Token::Tuple(_) => EthereumValueKind::Tuple, + pub(crate) fn get_kind(value: &abi::DynSolValue) -> Self { + use graph::abi::DynSolValue; + + match value { + DynSolValue::Bool(_) => Self::Bool, + DynSolValue::Int(_, _) => Self::Int, + DynSolValue::Uint(_, _) => Self::Uint, + DynSolValue::FixedBytes(_, _) => Self::FixedBytes, + DynSolValue::Address(_) => Self::Address, + DynSolValue::Function(_) => Self::Function, + DynSolValue::Bytes(_) => Self::Bytes, + DynSolValue::String(_) => Self::String, + DynSolValue::Array(_) => Self::Array, + DynSolValue::FixedArray(_) => Self::FixedArray, + DynSolValue::Tuple(_) => Self::Tuple, } } } diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index 43e235c6299..4054766f026 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -5,12 +5,13 @@ use std::time::{Duration, Instant}; use graph::data::subgraph::API_VERSION_0_0_8; use graph::data::value::Word; -use graph::futures03::StreamExt; +use graph::futures03::stream::StreamExt; +use graph::prelude::alloy::primitives::Address; use graph::schema::EntityType; use never::Never; use semver::Version; -use web3::types::H160; +use graph::abi; use graph::blockchain::BlockTime; use graph::blockchain::Blockchain; use graph::components::link_resolver::LinkResolverContext; @@ -21,8 +22,6 @@ use graph::components::subgraph::{ use graph::data::store::{self}; use graph::data_source::{CausalityRegion, DataSource, EntityTypeAccess}; use graph::ensure; -use graph::prelude::ethabi::param_type::Reader; -use graph::prelude::ethabi::{decode, encode, Token}; use graph::prelude::serde_json; use graph::prelude::{slog::b, slog::record_static, *}; use graph::runtime::gas::{self, complexity, Gas, GasCounter}; @@ -1173,19 +1172,19 @@ impl HostExports { .map_err(|e| DeterministicHostError::from(Error::from(e))) } - pub(crate) fn string_to_h160( + pub(crate) fn string_to_address( &self, string: &str, gas: &GasCounter, state: &mut BlockState, - ) -> Result { + ) -> Result { Self::track_gas_and_ops( gas, state, gas::DEFAULT_GAS_OP.with_args(complexity::Size, &string), "string_to_h160", )?; - string_to_h160(string) + string_to_address(string) } pub(crate) fn bytes_to_string( @@ -1207,11 +1206,11 @@ impl HostExports { pub(crate) fn ethereum_encode( &self, - token: Token, + value: abi::DynSolValue, gas: &GasCounter, state: &mut BlockState, ) -> Result, DeterministicHostError> { - let encoded = encode(&[token]); + let encoded = value.abi_encode(); Self::track_gas_and_ops( gas, @@ -1229,7 +1228,7 @@ impl HostExports { data: Vec, gas: &GasCounter, state: &mut BlockState, - ) -> Result { + ) -> Result { Self::track_gas_and_ops( gas, state, @@ -1237,15 +1236,9 @@ impl HostExports { "ethereum_decode", )?; - let param_types = - Reader::read(&types).map_err(|e| anyhow::anyhow!("Failed to read types: {}", e))?; + let ty: abi::DynSolType = types.parse().context("Failed to read types")?; - decode(&[param_types], &data) - // The `.pop().unwrap()` here is ok because we're always only passing one - // `param_types` to `decode`, so the returned `Vec` has always size of one. - // We can't do `tokens[0]` because the value can't be moved out of the `Vec`. - .map(|mut tokens| tokens.pop().unwrap()) - .context("Failed to decode") + ty.abi_decode(&data).context("Failed to decode") } pub(crate) fn yaml_from_bytes( @@ -1279,11 +1272,9 @@ impl HostExports { } } -fn string_to_h160(string: &str) -> Result { - // `H160::from_str` takes a hex string with no leading `0x`. - let s = string.trim_start_matches("0x"); - H160::from_str(s) - .with_context(|| format!("Failed to convert string to Address/H160: '{}'", s)) +fn string_to_address(string: &str) -> Result { + Address::from_str(string) + .with_context(|| format!("Failed to convert string to Address: '{}'", string)) .map_err(DeterministicHostError::from) } @@ -1382,8 +1373,8 @@ pub mod test_support { #[test] fn test_string_to_h160_with_0x() { assert_eq!( - H160::from_str("A16081F360e3847006dB660bae1c6d1b2e17eC2A").unwrap(), - string_to_h160("0xA16081F360e3847006dB660bae1c6d1b2e17eC2A").unwrap() + Address::from_str("A16081F360e3847006dB660bae1c6d1b2e17eC2A").unwrap(), + string_to_address("0xA16081F360e3847006dB660bae1c6d1b2e17eC2A").unwrap() ) } diff --git a/runtime/wasm/src/module/context.rs b/runtime/wasm/src/module/context.rs index 9ecb04782ef..ce5a7c1109f 100644 --- a/runtime/wasm/src/module/context.rs +++ b/runtime/wasm/src/module/context.rs @@ -455,7 +455,7 @@ impl WasmInstanceContext<'_> { let s: String = asc_get(self, str_ptr, gas)?; let host_exports = self.as_ref().ctx.host_exports.cheap_clone(); let ctx = &mut self.as_mut().ctx; - let h160 = host_exports.string_to_h160(&s, gas, &mut ctx.state)?; + let h160 = host_exports.string_to_address(&s, gas, &mut ctx.state)?; asc_new(self, &h160, gas).await } diff --git a/runtime/wasm/src/module/instance.rs b/runtime/wasm/src/module/instance.rs index 21560bb4fe5..c57ec0e9da9 100644 --- a/runtime/wasm/src/module/instance.rs +++ b/runtime/wasm/src/module/instance.rs @@ -2,8 +2,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Instant; use anyhow::Error; +use graph::futures03::future::BoxFuture; use graph::futures03::FutureExt as _; -use graph::prelude::web3::futures::future::BoxFuture; use graph::slog::SendSyncRefUnwindSafeKV; use semver::Version; diff --git a/runtime/wasm/src/to_from/external.rs b/runtime/wasm/src/to_from/external.rs index 022c07b0bc8..1ae4e6426fe 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -1,54 +1,58 @@ use async_trait::async_trait; -use ethabi; - +use graph::abi::DynSolValueExt; +use graph::abi::{self}; use graph::data::store::scalar::Timestamp; use graph::data::value::Word; +use graph::prelude::alloy::primitives::{Address, B256}; use graph::prelude::{BigDecimal, BigInt}; use graph::runtime::gas::GasCounter; +use graph::runtime::AscHeap; use graph::runtime::{ asc_get, asc_new, AscIndexId, AscPtr, AscType, AscValue, HostExportError, ToAscObj, }; use graph::{data::store, runtime::DeterministicHostError}; -use graph::{prelude::serde_json, runtime::FromAscObj}; -use graph::{prelude::web3::types as web3, runtime::AscHeap}; +use graph::{ + prelude::{alloy::primitives::U256, serde_json}, + runtime::FromAscObj, +}; use crate::asc_abi::class::*; +impl FromAscObj for Address { + fn from_asc_obj( + typed_array: Uint8Array, + heap: &H, + gas: &GasCounter, + depth: usize, + ) -> Result { + let data = <[u8; 20]>::from_asc_obj(typed_array, heap, gas, depth)?; + Ok(Self::from(data)) + } +} + #[async_trait] -impl ToAscObj for web3::H160 { +impl ToAscObj for Address { async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.0.to_asc_obj(heap, gas).await + self.as_slice().to_asc_obj(heap, gas).await } } #[async_trait] -impl ToAscObj for web3::Bytes { +impl ToAscObj for B256 { async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result { - self.0.to_asc_obj(heap, gas).await + self.as_slice().to_asc_obj(heap, gas).await } } -impl FromAscObj for web3::H160 { - fn from_asc_obj( - typed_array: Uint8Array, - heap: &H, - gas: &GasCounter, - depth: usize, - ) -> Result { - let data = <[u8; 20]>::from_asc_obj(typed_array, heap, gas, depth)?; - Ok(Self(data)) - } -} - -impl FromAscObj for web3::H256 { +impl FromAscObj for B256 { fn from_asc_obj( typed_array: Uint8Array, heap: &H, @@ -60,30 +64,6 @@ impl FromAscObj for web3::H256 { } } -#[async_trait] -impl ToAscObj for web3::H256 { - async fn to_asc_obj( - &self, - heap: &mut H, - gas: &GasCounter, - ) -> Result { - self.0.to_asc_obj(heap, gas).await - } -} - -#[async_trait] -impl ToAscObj for web3::U128 { - async fn to_asc_obj( - &self, - heap: &mut H, - gas: &GasCounter, - ) -> Result { - let mut bytes: [u8; 16] = [0; 16]; - self.to_little_endian(&mut bytes); - bytes.to_asc_obj(heap, gas).await - } -} - #[async_trait] impl ToAscObj for BigInt { async fn to_asc_obj( @@ -173,34 +153,47 @@ impl ToAscObj>> for Vec { } #[async_trait] -impl ToAscObj> for ethabi::Token { +impl ToAscObj> for abi::DynSolValue { async fn to_asc_obj( &self, heap: &mut H, gas: &GasCounter, ) -> Result, HostExportError> { - use ethabi::Token::*; - let kind = EthereumValueKind::get_kind(self); + let payload = match self { - Address(address) => asc_new::(heap, address, gas) - .await? - .to_payload(), - FixedBytes(bytes) | Bytes(bytes) => asc_new::(heap, &**bytes, gas) - .await? - .to_payload(), - Int(uint) => { - let n = BigInt::from_signed_u256(uint); + Self::Bool(val) => *val as u64, + Self::Int(val, _) => { + let bytes = val.to_le_bytes::<32>(); + let n = BigInt::from_signed_bytes_le(&bytes)?; + asc_new(heap, &n, gas).await?.to_payload() } - Uint(uint) => { - let n = BigInt::from_unsigned_u256(uint); + Self::Uint(val, _) => { + let bytes = val.to_le_bytes::<32>(); + let n = BigInt::from_unsigned_bytes_le(&bytes)?; + asc_new(heap, &n, gas).await?.to_payload() } - Bool(b) => *b as u64, - String(string) => asc_new(heap, &**string, gas).await?.to_payload(), - FixedArray(tokens) | Array(tokens) => asc_new(heap, &**tokens, gas).await?.to_payload(), - Tuple(tokens) => asc_new(heap, &**tokens, gas).await?.to_payload(), + Self::FixedBytes(val, size) => { + // FixedBytes stores the value in a 32-byte word, but we only want the first `size` bytes + asc_new::(heap, &val.as_slice()[..*size], gas) + .await? + .to_payload() + } + Self::Address(val) => asc_new::(heap, val.as_slice(), gas) + .await? + .to_payload(), + Self::Function(val) => asc_new::(heap, val.as_slice(), gas) + .await? + .to_payload(), + Self::Bytes(val) => asc_new::(heap, &**val, gas) + .await? + .to_payload(), + Self::String(val) => asc_new(heap, &**val, gas).await?.to_payload(), + Self::Array(values) => asc_new(heap, &**values, gas).await?.to_payload(), + Self::FixedArray(values) => asc_new(heap, &**values, gas).await?.to_payload(), + Self::Tuple(values) => asc_new(heap, &**values, gas).await?.to_payload(), }; Ok(AscEnum { @@ -211,34 +204,41 @@ impl ToAscObj> for ethabi::Token { } } -impl FromAscObj> for ethabi::Token { +impl FromAscObj> for abi::DynSolValue { fn from_asc_obj( asc_enum: AscEnum, heap: &H, gas: &GasCounter, depth: usize, ) -> Result { - use ethabi::Token; - let payload = asc_enum.payload; - Ok(match asc_enum.kind { - EthereumValueKind::Bool => Token::Bool(bool::from(payload)), + + let value = match asc_enum.kind { EthereumValueKind::Address => { let ptr: AscPtr = AscPtr::from(payload); - Token::Address(asc_get(heap, ptr, gas, depth)?) + let bytes: [u8; 20] = asc_get(heap, ptr, gas, depth)?; + + Self::Address(bytes.into()) } EthereumValueKind::FixedBytes => { let ptr: AscPtr = AscPtr::from(payload); - Token::FixedBytes(asc_get(heap, ptr, gas, depth)?) + let bytes: Vec = asc_get(heap, ptr, gas, depth)?; + + Self::fixed_bytes_from_slice(&bytes)? } EthereumValueKind::Bytes => { let ptr: AscPtr = AscPtr::from(payload); - Token::Bytes(asc_get(heap, ptr, gas, depth)?) + let bytes: Vec = asc_get(heap, ptr, gas, depth)?; + + Self::Bytes(bytes) } EthereumValueKind::Int => { let ptr: AscPtr = AscPtr::from(payload); let n: BigInt = asc_get(heap, ptr, gas, depth)?; - Token::Int(n.to_signed_u256()) + let x = + abi::I256::from_le_bytes(n.to_signed_u256().to_le_bytes::<{ U256::BYTES }>()); + + Self::Int(x, x.bits() as usize) } EthereumValueKind::Uint => { let ptr: AscPtr = AscPtr::from(payload); @@ -246,25 +246,38 @@ impl FromAscObj> for ethabi::Token { let uint = n .to_unsigned_u256() .map_err(DeterministicHostError::Other)?; - Token::Uint(uint) + Self::Uint(uint, uint.bit_len()) } + EthereumValueKind::Bool => Self::Bool(bool::from(payload)), EthereumValueKind::String => { let ptr: AscPtr = AscPtr::from(payload); - Token::String(asc_get(heap, ptr, gas, depth)?) + + Self::String(asc_get(heap, ptr, gas, depth)?) } EthereumValueKind::FixedArray => { let ptr: AscEnumArray = AscPtr::from(payload); - Token::FixedArray(asc_get(heap, ptr, gas, depth)?) + + Self::FixedArray(asc_get(heap, ptr, gas, depth)?) } EthereumValueKind::Array => { let ptr: AscEnumArray = AscPtr::from(payload); - Token::Array(asc_get(heap, ptr, gas, depth)?) + + Self::Array(asc_get(heap, ptr, gas, depth)?) } EthereumValueKind::Tuple => { let ptr: AscEnumArray = AscPtr::from(payload); - Token::Tuple(asc_get(heap, ptr, gas, depth)?) + + Self::Tuple(asc_get(heap, ptr, gas, depth)?) } - }) + EthereumValueKind::Function => { + let ptr: AscPtr = AscPtr::from(payload); + let bytes: [u8; 24] = asc_get(heap, ptr, gas, depth)?; + + Self::Function(bytes.into()) + } + }; + + Ok(value) } } diff --git a/server/index-node/src/resolver.rs b/server/index-node/src/resolver.rs index e07e0dc62da..ff0d25d4e38 100644 --- a/server/index-node/src/resolver.rs +++ b/server/index-node/src/resolver.rs @@ -4,8 +4,8 @@ use std::convert::TryInto; use async_trait::async_trait; use graph::data::query::Trace; use graph::data::store::Id; +use graph::prelude::alloy::primitives::Address; use graph::schema::EntityType; -use web3::types::Address; use git_testament::{git_testament, CommitKind}; use graph::blockchain::{Blockchain, BlockchainKind, BlockchainMap}; @@ -370,7 +370,7 @@ impl IndexNodeResolver { if !poi_protection.validate_access_token(self.bearer_token.as_deref()) { // Let's sign the POI with a zero'd address when the access token is // invalid. - indexer = Some(Address::zero()); + indexer = Some(Address::ZERO); } let poi_fut = self diff --git a/store/postgres/src/chain_store.rs b/store/postgres/src/chain_store.rs index eadde677f96..7d49d0750b2 100644 --- a/store/postgres/src/chain_store.rs +++ b/store/postgres/src/chain_store.rs @@ -10,6 +10,7 @@ use graph::data::store::ethereum::call; use graph::derive::CheapClone; use graph::env::ENV_VARS; use graph::parking_lot::RwLock; +use graph::prelude::alloy::primitives::B256; use graph::prelude::MetricsRegistry; use graph::prometheus::{CounterVec, GaugeVec}; use graph::slog::Logger; @@ -26,7 +27,6 @@ use std::{ use graph::blockchain::{Block, BlockHash, ChainIdentifier, ExtendedBlockPtr}; use graph::cheap_clone::CheapClone; -use graph::prelude::web3::types::{H256, U256}; use graph::prelude::{ serde_json as json, transaction_receipt::LightTransactionReceipt, BlockNumber, BlockPtr, CachedEthereumCall, ChainStore as ChainStoreTrait, Error, EthereumCallCache, StoreError, @@ -56,12 +56,12 @@ impl JsonBlock { } } - fn timestamp(&self) -> Option { + fn timestamp(&self) -> Option { self.data .as_ref() .and_then(|data| data.get("timestamp")) .and_then(|ts| ts.as_str()) - .and_then(|ts| U256::from_dec_str(ts).ok()) + .and_then(|ts| ts.parse::().ok()) } } @@ -103,9 +103,8 @@ mod data { use graph::blockchain::{Block, BlockHash}; use graph::data::store::scalar::Bytes; use graph::internal_error; - use graph::prelude::ethabi::ethereum_types::H160; + use graph::prelude::alloy::primitives::{Address, B256}; use graph::prelude::transaction_receipt::LightTransactionReceipt; - use graph::prelude::web3::types::H256; use graph::prelude::{ info, serde_json as json, BlockNumber, BlockPtr, CachedEthereumCall, Error, Logger, StoreError, @@ -175,17 +174,17 @@ mod data { hash: Vec, } - // Like H256::from_slice, but returns an error instead of panicking + // Like B256::from_slice, but returns an error instead of panicking // when `bytes` does not have the right length - fn h256_from_bytes(bytes: &[u8]) -> Result { - if bytes.len() == H256::len_bytes() { - Ok(H256::from_slice(bytes)) + fn b256_from_bytes(bytes: &[u8]) -> Result { + if bytes.len() == B256::len_bytes() { + Ok(B256::from_slice(bytes)) } else { Err(internal_error!( "invalid H256 value `{}` has {} bytes instead of {}", graph::prelude::hex::encode(bytes), bytes.len(), - H256::len_bytes() + B256::len_bytes() )) } } @@ -892,9 +891,9 @@ mod data { conn: &mut AsyncPgConnection, chain: &str, first_block: i64, - hash: H256, - genesis: H256, - ) -> Result, Error> { + hash: B256, + genesis: B256, + ) -> Result, Error> { match self { Storage::Shared => { // We recursively build a temp table 'chain' containing the hash and @@ -978,15 +977,15 @@ mod data { ); let missing = sql_query(query) - .bind::(hash.as_bytes()) - .bind::(genesis.as_bytes()) + .bind::(hash.as_slice()) + .bind::(genesis.as_slice()) .bind::(first_block) .load::(conn) .await?; let missing = match missing.len() { 0 => None, - 1 => Some(h256_from_bytes(&missing[0].hash)?), + 1 => Some(b256_from_bytes(&missing[0].hash)?), _ => { unreachable!("the query can only return no or one row") } @@ -1226,7 +1225,7 @@ mod data { &self, conn: &mut AsyncPgConnection, chain: &str, - block_hashes: &[&H256], + block_hashes: &[&B256], ) -> Result { match self { Storage::Shared => { @@ -1252,7 +1251,7 @@ mod data { ); let hashes: Vec<&[u8]> = - block_hashes.iter().map(|hash| hash.as_bytes()).collect(); + block_hashes.iter().map(|hash| hash.as_slice()).collect(); sql_query(query) .bind::, _>(hashes) @@ -1404,7 +1403,7 @@ mod data { .map(|row| CachedEthereumCall { blake3_id: row.0, block_ptr: block_ptr.clone(), - contract_address: H160::from_slice(&row.2[..]), + contract_address: Address::from_slice(&row.2[..]), return_value: row.1, }) .collect()) @@ -1819,7 +1818,7 @@ mod data { pub(crate) async fn find_transaction_receipts_in_block( &self, conn: &mut AsyncPgConnection, - block_hash: H256, + block_hash: B256, ) -> anyhow::Result> { let query = sql_query(format!( " @@ -1853,7 +1852,7 @@ from ( } Storage::Private(_) => { query - .bind::(block_hash.as_bytes()) + .bind::(block_hash.as_slice()) .get_results(conn) .await } @@ -2195,7 +2194,7 @@ impl ChainStore { self.recent_blocks_cache.blocks() } - pub async fn delete_blocks(&self, block_hashes: &[&H256]) -> Result { + pub async fn delete_blocks(&self, block_hashes: &[&B256]) -> Result { let mut conn = self.pool.get_permitted().await?; self.storage .delete_blocks_by_hash(&mut conn, &self.chain, block_hashes) @@ -2257,10 +2256,10 @@ impl ChainStore { async fn attempt_chain_head_update_inner( &self, ancestor_count: BlockNumber, - ) -> Result<(Option, Option<(String, i64)>), StoreError> { + ) -> Result<(Option, Option<(String, i64)>), StoreError> { use public::ethereum_networks as n; - let genesis_block_ptr = self.genesis_block_ptr().await?.hash_as_h256(); + let genesis_block_ptr = self.genesis_block_ptr().await?.hash.as_b256(); let mut conn = self.pool.get_permitted().await?; let candidate = self @@ -2278,7 +2277,7 @@ impl ChainStore { &mut conn, &self.chain, first_block as i64, - ptr.hash_as_h256(), + ptr.hash.as_b256(), genesis_block_ptr, ) .await? @@ -2291,7 +2290,7 @@ impl ChainStore { let hash = ptr.hash_hex(); let number = ptr.number as i64; - conn.transaction::<(Option, Option<(String, i64)>), StoreError, _>(|conn| { + conn.transaction::<(Option, Option<(String, i64)>), StoreError, _>(|conn| { async move { update(n::table.filter(n::name.eq(&self.chain))) .set(( @@ -2326,7 +2325,7 @@ fn json_block_to_block_ptr_ext(json_block: &JsonBlock) -> Result, Option)>(&mut conn) @@ -2352,7 +2351,7 @@ impl ChainHeadStore for ChainStore { // FIXME: // // workaround for arweave - H256::from_slice(&hex::decode(hash).unwrap()[..32]), + B256::from_slice(&hex::decode(hash).unwrap()[..32]), *number, ) .into(), @@ -2361,7 +2360,8 @@ impl ChainHeadStore for ChainStore { _ => unreachable!(), }) .and_then(|opt: Option| opt) - })?) + }) + .map_err(Error::from) } async fn chain_head_cursor(&self) -> Result, Error> { @@ -2461,7 +2461,7 @@ impl ChainStoreTrait for ChainStore { async fn attempt_chain_head_update( self: Arc, ancestor_count: BlockNumber, - ) -> Result, Error> { + ) -> Result, Error> { let (missing, ptr) = self.attempt_chain_head_update_inner(ancestor_count).await?; if let Some((hash, number)) = ptr { @@ -2847,7 +2847,7 @@ impl ChainStoreTrait for ChainStore { async fn transaction_receipts_in_block( &self, - block_hash: &H256, + block_hash: &B256, ) -> Result, StoreError> { let mut conn = self.pool.get_permitted().await?; self.storage diff --git a/store/postgres/src/deployment.rs b/store/postgres/src/deployment.rs index 1a8cf9586c8..70c67c1ed43 100644 --- a/store/postgres/src/deployment.rs +++ b/store/postgres/src/deployment.rs @@ -12,6 +12,7 @@ use diesel::{ sql_types::{Nullable, Text}, }; use diesel_async::{RunQueryDsl, SimpleAsyncConnection}; +use graph::prelude::alloy::primitives::B256; use graph::{ blockchain::block_stream::FirehoseCursor, data::subgraph::schema::SubgraphError, @@ -20,18 +21,14 @@ use graph::{ slog::{debug, Logger}, }; use graph::{components::store::StoreResult, semver::Version}; -use graph::{ - data::store::scalar::ToPrimitive, - prelude::{ - anyhow, hex, web3::types::H256, BlockNumber, BlockPtr, DeploymentHash, DeploymentState, - StoreError, - }, - schema::InputSchema, -}; use graph::{ data::subgraph::schema::{DeploymentCreate, SubgraphManifestEntity}, util::backoff::ExponentialBackoff, }; +use graph::{ + prelude::{anyhow, hex, BlockNumber, BlockPtr, DeploymentHash, DeploymentState, StoreError}, + schema::InputSchema, +}; use stable_hash_legacy::crypto::SetHasher; use std::sync::Arc; use std::{convert::TryFrom, ops::Bound, time::Duration}; @@ -263,8 +260,7 @@ async fn graft( // FIXME: // // workaround for arweave - let hash = H256::from_slice(&hash.as_slice()[..32]); - let block = block.to_u64().expect("block numbers fit into a u64"); + let hash = B256::from_slice(&hash.as_slice()[..32]); let subgraph = DeploymentHash::new(subgraph.clone()).map_err(|_| { StoreError::Unknown(anyhow!( "the base subgraph for a graft must be a valid subgraph id but is `{}`", diff --git a/store/postgres/src/deployment_store.rs b/store/postgres/src/deployment_store.rs index 1df16c155b5..3c1e98ff1d2 100644 --- a/store/postgres/src/deployment_store.rs +++ b/store/postgres/src/deployment_store.rs @@ -20,6 +20,7 @@ use graph::data::subgraph::{status, SPEC_VERSION_0_0_6}; use graph::data_source::CausalityRegion; use graph::derive::CheapClone; use graph::futures03::FutureExt; +use graph::prelude::alloy::primitives::Address; use graph::prelude::{ApiVersion, EntityOperation, PoolWaitStats, SubgraphDeploymentEntity}; use graph::semver::Version; use itertools::Itertools; @@ -38,12 +39,11 @@ use graph::components::subgraph::{ProofOfIndexingFinisher, ProofOfIndexingVersio use graph::data::subgraph::schema::{DeploymentCreate, SubgraphError}; use graph::internal_error; use graph::prelude::{ - anyhow, debug, info, o, warn, web3, AttributeNames, BlockNumber, BlockPtr, CheapClone, + anyhow, debug, info, o, warn, AttributeNames, BlockNumber, BlockPtr, CheapClone, DeploymentHash, DeploymentState, Entity, EntityQuery, Error, Logger, QueryExecutionError, StopwatchMetrics, StoreError, UnfailOutcome, Value, ENV_VARS, }; use graph::schema::{ApiSchema, EntityKey, EntityType, InputSchema}; -use web3::types::Address; use crate::block_range::{BLOCK_COLUMN, BLOCK_RANGE_COLUMN}; use crate::deployment::{self, OnSync}; diff --git a/store/postgres/src/detail.rs b/store/postgres/src/detail.rs index 7dcac2f3bd8..8eff3ecb2d2 100644 --- a/store/postgres/src/detail.rs +++ b/store/postgres/src/detail.rs @@ -14,13 +14,14 @@ use git_testament::{git_testament, git_testament_macros}; use graph::blockchain::BlockHash; use graph::data::store::scalar::ToPrimitive; use graph::data::subgraph::schema::{SubgraphError, SubgraphManifestEntity}; +use graph::prelude::alloy::primitives::B256; use graph::prelude::BlockNumber; use graph::prelude::{ chrono::{DateTime, Utc}, BlockPtr, DeploymentHash, StoreError, SubgraphDeploymentEntity, }; use graph::schema::InputSchema; -use graph::{data::subgraph::status, internal_error, prelude::web3::types::H256}; +use graph::{data::subgraph::status, internal_error}; use itertools::Itertools; use std::collections::HashMap; use std::convert::TryFrom; @@ -191,7 +192,7 @@ impl TryFrom for SubgraphError { // FIXME: // // workaround for arweave - let block_hash = block_hash.map(|hash| H256::from_slice(&hash.as_slice()[..32])); + let block_hash = block_hash.map(|hash| B256::from_slice(&hash.as_slice()[..32])); // In existing databases, we have errors that have a `block_range` of // `UNVERSIONED_RANGE`, which leads to `None` as the block number, but // has a hash. Conversely, it is also possible for an error to not have a diff --git a/store/postgres/src/store.rs b/store/postgres/src/store.rs index 5acac2691b2..4adec80ab5b 100644 --- a/store/postgres/src/store.rs +++ b/store/postgres/src/store.rs @@ -12,8 +12,8 @@ use graph::{ data::subgraph::status, internal_error, prelude::{ - web3::types::Address, BlockNumber, BlockPtr, CheapClone, DeploymentHash, PartialBlockPtr, - QueryExecutionError, StoreError, + alloy::primitives::Address, BlockNumber, BlockPtr, CheapClone, DeploymentHash, + PartialBlockPtr, QueryExecutionError, StoreError, }, }; diff --git a/store/postgres/src/subgraph_store.rs b/store/postgres/src/subgraph_store.rs index 1cc89a69233..28d9580a17e 100644 --- a/store/postgres/src/subgraph_store.rs +++ b/store/postgres/src/subgraph_store.rs @@ -27,16 +27,16 @@ use graph::{ internal_error, prelude::StoreEvent, prelude::{ - anyhow, lazy_static, o, web3::types::Address, ApiVersion, BlockNumber, BlockPtr, - ChainStore, DeploymentHash, EntityOperation, Logger, MetricsRegistry, NodeId, - PartialBlockPtr, StoreError, SubgraphDeploymentEntity, SubgraphName, - SubgraphStore as SubgraphStoreTrait, SubgraphVersionSwitchingMode, + anyhow, lazy_static, o, ApiVersion, BlockNumber, BlockPtr, ChainStore, DeploymentHash, + EntityOperation, Logger, MetricsRegistry, NodeId, PartialBlockPtr, StoreError, + SubgraphDeploymentEntity, SubgraphName, SubgraphStore as SubgraphStoreTrait, + SubgraphVersionSwitchingMode, }, schema::{ApiSchema, InputSchema}, url::Url, util::timed_cache::TimedCache, }; -use graph::{derive::CheapClone, futures03::future::join_all}; +use graph::{derive::CheapClone, futures03::future::join_all, prelude::alloy::primitives::Address}; use crate::{ deployment::{OnSync, SubgraphHealth}, @@ -1153,7 +1153,7 @@ impl Inner { }; let block_for_poi_query = BlockPtr::new(block_hash.clone(), block_number); - let indexer = Some(Address::zero()); + let indexer = Some(Address::ZERO); let poi = store .get_proof_of_indexing(site, &indexer, block_for_poi_query) .await?; diff --git a/store/postgres/src/transaction_receipt.rs b/store/postgres/src/transaction_receipt.rs index 115a32f1cc2..73b11c9c400 100644 --- a/store/postgres/src/transaction_receipt.rs +++ b/store/postgres/src/transaction_receipt.rs @@ -39,15 +39,32 @@ impl TryFrom for LightTransactionReceipt { let block_hash = block_hash.map(drain_vector).transpose()?; let block_number = block_number.map(drain_vector).transpose()?; let gas_used = gas_used.map(drain_vector).transpose()?; - let status = status.map(drain_vector).transpose()?; + + // Convert big-endian bytes to numbers + let transaction_index = u64::from_be_bytes(transaction_index); + let block_number = block_number.map(u64::from_be_bytes); + let gas_used = gas_used.map(u64::from_be_bytes).unwrap_or(0); + + // Handle both old U64 format and new boolean format + let status = status + .map(|bytes| { + match bytes.len() { + 1 => bytes[0] != 0, // New format: single byte + 8 => { + u64::from_be_bytes(drain_vector::<8>(bytes.to_vec()).unwrap_or([0; 8])) != 0 + } // Old format: U64 + _ => false, // Fallback + } + }) + .unwrap_or(false); Ok(LightTransactionReceipt { transaction_hash: transaction_hash.into(), - transaction_index: transaction_index.into(), + transaction_index, block_hash: block_hash.map(Into::into), - block_number: block_number.map(Into::into), - gas_used: gas_used.map(Into::into), - status: status.map(Into::into), + block_number, + gas_used, + status, }) } } diff --git a/store/test-store/Cargo.toml b/store/test-store/Cargo.toml index fd6f9ba0566..9cf617c3083 100644 --- a/store/test-store/Cargo.toml +++ b/store/test-store/Cargo.toml @@ -22,3 +22,4 @@ tokio = { workspace = true } [dev-dependencies] hex = "0.4.3" pretty_assertions = "1.4.1" +serde_json = { workspace = true } diff --git a/store/test-store/src/block_store.rs b/store/test-store/src/block_store.rs index f085e2dbd9d..ed3c091ad07 100644 --- a/store/test-store/src/block_store.rs +++ b/store/test-store/src/block_store.rs @@ -1,17 +1,18 @@ use std::{convert::TryFrom, str::FromStr, sync::Arc}; use graph::blockchain::{BlockTime, ChainIdentifier}; +use graph::prelude::alloy::consensus::Header as ConsensusHeader; +use graph::prelude::alloy::primitives::{Bloom, B256, U256}; +use graph::prelude::alloy::rpc::types::{Block, Header}; +use graph::prelude::LightEthereumBlock; use lazy_static::lazy_static; use graph::components::store::BlockStore; use graph::{ blockchain::Block as BlockchainBlock, - prelude::{ - serde_json, web3::types::H256, web3::types::U256, BlockHash, BlockNumber, BlockPtr, - EthereumBlock, LightEthereumBlock, - }, + prelude::{serde_json, BlockHash, BlockNumber, BlockPtr, EthereumBlock}, }; -use graph_chain_ethereum::codec::{Block, BlockHeader}; +use graph_chain_ethereum::codec::{Block as FirehoseBlock, BlockHeader}; use prost_types::Timestamp; use crate::{GENESIS_PTR, NETWORK_VERSION}; @@ -103,24 +104,34 @@ impl FakeBlock { } pub fn as_ethereum_block(&self) -> EthereumBlock { - let parent_hash = H256::from_str(self.parent_hash.as_str()).expect("invalid parent hash"); + let parent_hash = B256::from_str(self.parent_hash.as_str()).expect("invalid parent hash"); + let block_hash = B256::from_str(self.hash.as_str()).expect("invalid block hash"); - let mut block = LightEthereumBlock::default(); - block.number = Some(self.number.into()); - block.parent_hash = parent_hash; - block.hash = Some(H256(self.block_hash().as_slice().try_into().unwrap())); + let mut consensus_header = ConsensusHeader::default(); + consensus_header.number = self.number as u64; + consensus_header.parent_hash = parent_hash; + consensus_header.logs_bloom = Bloom::default(); // Empty bloom filter for test blocks if let Some(ts) = self.timestamp { - block.timestamp = ts; + consensus_header.timestamp = ts.to::(); } + let rpc_header = Header { + hash: block_hash, + inner: consensus_header, + total_difficulty: None, + size: None, + }; + + let block = Block::empty(rpc_header); + EthereumBlock { - block: Arc::new(block), + block: Arc::new(LightEthereumBlock::new(block.into())), transaction_receipts: Vec::new(), } } - pub fn as_firehose_block(&self) -> Block { - let mut block = Block::default(); + pub fn as_firehose_block(&self) -> FirehoseBlock { + let mut block = FirehoseBlock::default(); block.hash = self.hash.clone().into_bytes(); block.number = self.number as u64; diff --git a/store/test-store/src/store.rs b/store/test-store/src/store.rs index a671e770a6f..1778bf46813 100644 --- a/store/test-store/src/store.rs +++ b/store/test-store/src/store.rs @@ -10,6 +10,7 @@ use graph::data::subgraph::schema::{DeploymentCreate, SubgraphError}; use graph::data::subgraph::SubgraphFeature; use graph::data_source::DataSource; use graph::log; +use graph::prelude::alloy::primitives::B256; use graph::prelude::{QueryStoreManager as _, SubgraphStore as _, *}; use graph::schema::EntityType; use graph::schema::InputSchema; @@ -37,7 +38,6 @@ use std::collections::BTreeSet; use std::collections::HashMap; use std::time::Instant; use std::{marker::PhantomData, sync::Mutex}; -use web3::types::H256; pub const NETWORK_NAME: &str = "fake_network"; pub const DATA_SOURCE_KIND: &str = "mock/kind"; @@ -69,14 +69,14 @@ lazy_static! { pub static ref SUBGRAPH_STORE: Arc = STORE.subgraph_store(); static ref BLOCK_STORE: DieselBlockStore = STORE.block_store(); pub static ref GENESIS_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "bd34884280958002c51d3f7b5f853e6febeba33de0f40d15b0363006533c924f" )), 0u64 ) .into(); pub static ref BLOCK_ONE: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "8511fa04b64657581e3f00e14543c1d522d5d7e771b54aa3060b662ade47da13" )), 1u64 @@ -84,14 +84,15 @@ lazy_static! { .into(); pub static ref BLOCKS: [BlockPtr; 4] = { let two: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "b98fb783b49de5652097a989414c767824dff7e7fd765a63b493772511db81c1" )), 2u64, ) .into(); + let three: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "977c084229c72a0fa377cae304eda9099b6a2cb5d83b25cdf0f0969b69874255" )), 3u64, diff --git a/store/test-store/tests/chain/ethereum/manifest.rs b/store/test-store/tests/chain/ethereum/manifest.rs index 172068add9c..e001dcb5696 100644 --- a/store/test-store/tests/chain/ethereum/manifest.rs +++ b/store/test-store/tests/chain/ethereum/manifest.rs @@ -18,7 +18,7 @@ use graph::data_source::offchain::OffchainDataSourceKind; use graph::data_source::{DataSourceEnum, DataSourceTemplate}; use graph::entity; use graph::env::ENV_VARS; -use graph::prelude::web3::types::H256; +use graph::prelude::alloy::primitives::B256; use graph::prelude::{ anyhow, serde_yaml, BigDecimal, BigInt, DeploymentHash, Link, SubgraphManifest, SubgraphManifestResolveError, SubgraphManifestValidationError, SubgraphStore, @@ -796,18 +796,18 @@ specVersion: 1.2.0 assert_eq!( Some(vec![ - H256::from_str("0000000000000000000000000000000000000000000000000000000000000000") + B256::from_str("0000000000000000000000000000000000000000000000000000000000000000") .unwrap(), - H256::from_str("0000000000000000000000000000000000000000000000000000000000000001") + B256::from_str("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(), - H256::from_str("0000000000000000000000000000000000000000000000000000000000000002") + B256::from_str("0000000000000000000000000000000000000000000000000000000000000002") .unwrap() ]), topic1.clone() ); assert_eq!( - Some(vec![H256::from_str( + Some(vec![B256::from_str( "0000000000000000000000000000000000000000000000000000000000000001" ) .unwrap()]), @@ -815,7 +815,7 @@ specVersion: 1.2.0 ); assert_eq!( - Some(vec![H256::from_str( + Some(vec![B256::from_str( "0000000000000000000000000000000000000000000000000000000000000002" ) .unwrap()]), diff --git a/store/test-store/tests/graph/entity_cache.rs b/store/test-store/tests/graph/entity_cache.rs index be27d111fa8..37c183d91c2 100644 --- a/store/test-store/tests/graph/entity_cache.rs +++ b/store/test-store/tests/graph/entity_cache.rs @@ -8,6 +8,7 @@ use graph::components::store::{ use graph::data::store::Id; use graph::data::subgraph::schema::{DeploymentCreate, SubgraphError, SubgraphHealth}; use graph::data_source::CausalityRegion; +use graph::prelude::alloy::primitives::B256; use graph::schema::{EntityKey, EntityType, InputSchema}; use graph::{ components::store::{DeploymentId, DeploymentLocator}, @@ -22,7 +23,6 @@ use slog::Logger; use std::collections::{BTreeMap, BTreeSet}; use std::marker::PhantomData; use std::sync::Arc; -use web3::types::H256; use graph_store_postgres::SubgraphStore as DieselSubgraphStore; use test_store::*; @@ -399,7 +399,7 @@ lazy_static! { InputSchema::parse_latest(ACCOUNT_GQL, LOAD_RELATED_ID.clone()) .expect("Failed to parse user schema"); static ref TEST_BLOCK_1_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "8511fa04b64657581e3f00e14543c1d522d5d7e771b54aa3060b662ade47da13" )), 1u64 diff --git a/store/test-store/tests/postgres/chain_head.rs b/store/test-store/tests/postgres/chain_head.rs index 98f1045de7a..f41446df6a1 100644 --- a/store/test-store/tests/postgres/chain_head.rs +++ b/store/test-store/tests/postgres/chain_head.rs @@ -7,14 +7,14 @@ use graph::data::store::ethereum::call; use graph::data::store::scalar::Bytes; use graph::env::ENV_VARS; use graph::futures03::executor; +use graph::prelude::alloy::primitives::B256; use std::future::Future; use std::sync::Arc; -use graph::prelude::web3::types::H256; +use graph::cheap_clone::CheapClone; +use graph::prelude::{alloy, serde_json as json, EthereumBlock}; use graph::prelude::{anyhow::anyhow, anyhow::Error}; -use graph::prelude::{serde_json as json, EthereumBlock}; use graph::prelude::{BlockNumber, QueryStoreManager, QueryTarget}; -use graph::{cheap_clone::CheapClone, prelude::web3::types::H160}; use graph::{components::store::BlockStore as _, prelude::DeploymentHash}; use graph::{ components::store::ChainHeadStore as _, components::store::ChainStore as _, @@ -329,7 +329,7 @@ fn check_ancestor( } let act_block = json::from_value::(act.0)?; - let act_hash = format!("{:x}", act_block.block.hash.unwrap()); + let act_hash = format!("{:x}", act_block.block.hash()); let exp_hash = &exp.hash; if &act_hash != exp_hash { @@ -441,7 +441,9 @@ fn eth_call_cache() { call::Retval::Value(Bytes::from(value)) } - let address = H160([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + let address = alloy::primitives::Address::from_slice(&[ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]); let call: [u8; 6] = [1, 2, 3, 4, 5, 6]; let return_value: [u8; 3] = [7, 8, 9]; @@ -542,7 +544,9 @@ fn test_clear_stale_call_cache() { run_test_async(chain, |chain_store, _, _| async move { let logger = LOGGER.cheap_clone(); - let address = H160([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3]); + let address = alloy::primitives::Address::from([ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, + ]); let call: [u8; 6] = [1, 2, 3, 4, 5, 6]; let return_value: [u8; 3] = [7, 8, 9]; @@ -593,7 +597,7 @@ fn test_clear_stale_call_cache() { diesel::sql_query(format!( "UPDATE {meta_table} SET accessed_at = NOW() - INTERVAL '8 days' WHERE contract_address = $1" )) - .bind::(address.as_bytes()) + .bind::(address.as_slice()) .execute(&mut conn) .await .unwrap(); @@ -616,7 +620,7 @@ fn test_transaction_receipts_in_block_function() { let chain = vec![]; run_test_async(chain, move |store, _, _| async move { let receipts = store - .transaction_receipts_in_block(&H256::zero()) + .transaction_receipts_in_block(&B256::ZERO) .await .unwrap(); assert!(receipts.is_empty()) diff --git a/store/test-store/tests/postgres/relational.rs b/store/test-store/tests/postgres/relational.rs index 483be514504..a2fda3e3f1e 100644 --- a/store/test-store/tests/postgres/relational.rs +++ b/store/test-store/tests/postgres/relational.rs @@ -3,9 +3,10 @@ use diesel_async::SimpleAsyncConnection; use graph::components::store::write::{EntityModification, RowGroup}; use graph::data::store::scalar; use graph::entity; +use graph::prelude::alloy::primitives::B256; use graph::prelude::{ - o, slog, web3::types::H256, DeploymentHash, Entity, EntityCollection, EntityFilter, - EntityOrder, EntityQuery, Logger, StopwatchMetrics, Value, ValueType, BLOCK_NUMBER_MAX, + o, slog, DeploymentHash, Entity, EntityCollection, EntityFilter, EntityOrder, EntityQuery, + Logger, StopwatchMetrics, Value, ValueType, BLOCK_NUMBER_MAX, }; use graph::prelude::{BlockNumber, MetricsRegistry}; use graph::schema::{EntityKey, EntityType, InputSchema}; @@ -185,13 +186,13 @@ lazy_static! { static ref LARGE_INT: BigInt = BigInt::from(std::i64::MAX).pow(17).unwrap(); static ref LARGE_DECIMAL: BigDecimal = BigDecimal::from(1) / BigDecimal::new(LARGE_INT.clone(), 1); - static ref BYTES_VALUE: H256 = H256::from(hex!( + static ref BYTES_VALUE: B256 = B256::from(hex!( "e8b3b02b936c4a4a331ac691ac9a86e197fb7731f14e3108602c87d4dac55160" )); - static ref BYTES_VALUE2: H256 = H256::from(hex!( + static ref BYTES_VALUE2: B256 = B256::from(hex!( "b98fb783b49de5652097a989414c767824dff7e7fd765a63b493772511db81c1" )); - static ref BYTES_VALUE3: H256 = H256::from(hex!( + static ref BYTES_VALUE3: B256 = B256::from(hex!( "977c084229c72a0fa377cae304eda9099b6a2cb5d83b25cdf0f0969b69874255" )); static ref SCALAR_ENTITY: Entity = { diff --git a/store/test-store/tests/postgres/relational_bytes.rs b/store/test-store/tests/postgres/relational_bytes.rs index c42bdc2eef4..eb449dc3021 100644 --- a/store/test-store/tests/postgres/relational_bytes.rs +++ b/store/test-store/tests/postgres/relational_bytes.rs @@ -5,6 +5,7 @@ use graph::components::store::write::RowGroup; use graph::data::store::scalar; use graph::data_source::CausalityRegion; use graph::entity; +use graph::prelude::alloy::primitives::B256; use graph::prelude::{BlockNumber, EntityModification, EntityQuery, MetricsRegistry, StoreError}; use graph::schema::{EntityKey, EntityType, InputSchema}; use graph_store_postgres::AsyncPgConnection; @@ -17,9 +18,9 @@ use std::{collections::BTreeMap, sync::Arc}; use graph::data::store::scalar::{BigDecimal, BigInt}; use graph::data::store::IdList; use graph::prelude::{ - o, slog, web3::types::H256, AttributeNames, ChildMultiplicity, DeploymentHash, Entity, - EntityCollection, EntityLink, EntityWindow, Logger, ParentLink, StopwatchMetrics, - WindowAttribute, BLOCK_NUMBER_MAX, + o, slog, AttributeNames, ChildMultiplicity, DeploymentHash, Entity, EntityCollection, + EntityLink, EntityWindow, Logger, ParentLink, StopwatchMetrics, WindowAttribute, + BLOCK_NUMBER_MAX, }; use graph_store_postgres::{ layout_for_tests::make_dummy_site, @@ -46,13 +47,13 @@ lazy_static! { static ref LARGE_INT: BigInt = BigInt::from(std::i64::MAX).pow(17).unwrap(); static ref LARGE_DECIMAL: BigDecimal = BigDecimal::from(1) / BigDecimal::new(LARGE_INT.clone(), 1); - static ref BYTES_VALUE: H256 = H256::from(hex!( + static ref BYTES_VALUE: B256 = B256::from(hex!( "e8b3b02b936c4a4a331ac691ac9a86e197fb7731f14e3108602c87d4dac55160" )); - static ref BYTES_VALUE2: H256 = H256::from(hex!( + static ref BYTES_VALUE2: B256 = B256::from(hex!( "b98fb783b49de5652097a989414c767824dff7e7fd765a63b493772511db81c1" )); - static ref BYTES_VALUE3: H256 = H256::from(hex!( + static ref BYTES_VALUE3: B256 = B256::from(hex!( "977c084229c72a0fa377cae304eda9099b6a2cb5d83b25cdf0f0969b69874255" )); static ref BEEF_ENTITY: Entity = entity! { THINGS_SCHEMA => diff --git a/store/test-store/tests/postgres/store.rs b/store/test-store/tests/postgres/store.rs index 60fb746fbe8..81c277a8854 100644 --- a/store/test-store/tests/postgres/store.rs +++ b/store/test-store/tests/postgres/store.rs @@ -3,6 +3,7 @@ use graph::blockchain::BlockTime; use graph::data::graphql::ext::TypeDefinitionExt; use graph::data::subgraph::schema::DeploymentCreate; use graph::data_source::common::MappingABI; +use graph::prelude::alloy::primitives::{Address, B256}; use graph::schema::{EntityType, InputSchema}; use graph_chain_ethereum::Mapping; use hex_literal::hex; @@ -15,13 +16,11 @@ use graph::data::subgraph::*; use graph::{ blockchain::DataSource, components::store::{BlockStore as _, EntityFilter, EntityOrder, EntityQuery, StatusStore}, - prelude::ethabi::Contract, }; use graph::{data::store::scalar, semver::Version}; use graph::{entity, prelude::*}; use graph_store_postgres::layout_for_tests::STRING_PREFIX_SIZE; use graph_store_postgres::{Store as DieselStore, SubgraphStore as DieselSubgraphStore}; -use web3::types::{Address, H256}; const USER_GQL: &str = " interface ColorAndAge { @@ -65,56 +64,56 @@ lazy_static! { InputSchema::parse_latest(USER_GQL, TEST_SUBGRAPH_ID.clone()) .expect("Failed to parse user schema"); static ref TEST_BLOCK_0_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "bd34884280958002c51d3f7b5f853e6febeba33de0f40d15b0363006533c924f" )), 0u64 ) .into(); static ref TEST_BLOCK_1_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "8511fa04b64657581e3f00e14543c1d522d5d7e771b54aa3060b662ade47da13" )), 1u64 ) .into(); static ref TEST_BLOCK_2_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "b98fb783b49de5652097a989414c767824dff7e7fd765a63b493772511db81c1" )), 2u64 ) .into(); static ref TEST_BLOCK_3_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "977c084229c72a0fa377cae304eda9099b6a2cb5d83b25cdf0f0969b69874255" )), 3u64 ) .into(); static ref TEST_BLOCK_3A_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "d163aec0592c7cb00c2700ab65dcaac93289f5d250b3b889b39198b07e1fbe4a" )), 3u64 ) .into(); static ref TEST_BLOCK_4_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "007a03cdf635ebb66f5e79ae66cc90ca23d98031665649db056ff9c6aac2d74d" )), 4u64 ) .into(); static ref TEST_BLOCK_4A_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "8fab27e9e9285b0a39110f4d9877f05d0f43d2effa157e55f4dcc49c3cf8cbd7" )), 4u64 ) .into(); static ref TEST_BLOCK_5_PTR: BlockPtr = ( - H256::from(hex!( + B256::from(hex!( "e8b3b02b936c4a4a331ac691ac9a86e197fb7731f14e3108602c87d4dac55160" )), 5u64 @@ -1133,19 +1132,18 @@ fn mock_data_source() -> graph_chain_ethereum::DataSource { fn mock_abi() -> MappingABI { MappingABI { name: "mock_abi".to_string(), - contract: Contract::load( + contract: serde_json::from_str( r#"[ - { - "inputs": [ - { - "name": "a", - "type": "address" - } - ], - "type": "constructor" - } - ]"# - .as_bytes(), + { + "inputs": [ + { + "name": "a", + "type": "address" + } + ], + "type": "constructor" + } + ]"#, ) .unwrap(), } diff --git a/store/test-store/tests/postgres/writable.rs b/store/test-store/tests/postgres/writable.rs index da828e8784f..feaae3e5c4e 100644 --- a/store/test-store/tests/postgres/writable.rs +++ b/store/test-store/tests/postgres/writable.rs @@ -2,6 +2,7 @@ use graph::blockchain::block_stream::{EntitySourceOperation, FirehoseCursor}; use graph::data::subgraph::schema::DeploymentCreate; use graph::data::value::Word; use graph::data_source::CausalityRegion; +use graph::prelude::alloy::primitives::B256; use graph::schema::{EntityKey, EntityType, InputSchema}; use lazy_static::lazy_static; use std::collections::{BTreeMap, BTreeSet}; @@ -18,7 +19,6 @@ use graph::semver::Version; use graph::{entity, prelude::*}; use graph_store_postgres::layout_for_tests::writable; use graph_store_postgres::{Store as DieselStore, SubgraphStore as DieselSubgraphStore}; -use web3::types::H256; const SCHEMA_GQL: &str = " type Counter @entity { @@ -145,7 +145,7 @@ where } fn block_pointer(number: u8) -> BlockPtr { - let hash = H256::from([number; 32]); + let hash = B256::from([number; 32]); BlockPtr::from((hash, number as BlockNumber)) } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ed44ad4888c..0a00813d2c5 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -25,6 +25,10 @@ tokio = { version = "1.45.1", features = ["rt", "macros", "process"] } # here needs to be kept in sync with the web3 version that the graph crate # uses until then secp256k1 = { version = "0.21", features = ["recovery"] } +web3 = { git = "https://github.com/graphprotocol/rust-web3", branch = "graph-patches-onto-0.18", features = [ + "arbitrary_precision", + "test", +] } [dev-dependencies] anyhow = "1.0.100" diff --git a/tests/src/contract.rs b/tests/src/contract.rs index 2d3d72216f3..80a9ba57031 100644 --- a/tests/src/contract.rs +++ b/tests/src/contract.rs @@ -3,12 +3,13 @@ use std::str::FromStr; use graph::prelude::{ lazy_static, serde_json::{self, Value}, - web3::{ - api::{Eth, Namespace}, - contract::{tokens::Tokenize, Contract as Web3Contract, Options}, - transports::Http, - types::{Address, Block, BlockId, BlockNumber, Bytes, TransactionReceipt, H256}, - }, +}; + +use web3::{ + api::{Eth, Namespace}, + contract::{tokens::Tokenize, Contract as Web3Contract, Options}, + transports::Http, + types::{Address, Block, BlockId, BlockNumber, Bytes, TransactionReceipt, H256}, }; // web3 version 0.18 does not expose this; once the graph crate updates to // version 0.19, we can use web3::signing::SecretKey from the graph crate @@ -161,16 +162,16 @@ impl Contract { if contract.name == "DeclaredCallsContract" { status!("contracts", "Emitting transfers from DeclaredCallsContract"); let addr1 = "0x1111111111111111111111111111111111111111" - .parse::() + .parse::() .unwrap(); let addr2 = "0x2222222222222222222222222222222222222222" - .parse::() + .parse::() .unwrap(); let addr3 = "0x3333333333333333333333333333333333333333" - .parse::() + .parse::() .unwrap(); let addr4 = "0x4444444444444444444444444444444444444444" - .parse::() + .parse::() .unwrap(); contract diff --git a/tests/src/fixture/ethereum.rs b/tests/src/fixture/ethereum.rs index ddf950bd273..11470ca52d4 100644 --- a/tests/src/fixture/ethereum.rs +++ b/tests/src/fixture/ethereum.rs @@ -6,16 +6,20 @@ use super::{ test_ptr, CommonChainConfig, MutexBlockStreamBuilder, NoopAdapterSelector, NoopRuntimeAdapterBuilder, StaticBlockRefetcher, StaticStreamBuilder, Stores, TestChain, }; +use graph::abi; +use graph::blockchain::block_stream::BlockWithTriggers; use graph::blockchain::block_stream::{EntityOperationKind, EntitySourceOperation}; use graph::blockchain::client::ChainClient; use graph::blockchain::{BlockPtr, Trigger, TriggersAdapterSelector}; use graph::cheap_clone::CheapClone; use graph::data_source::subgraph; -use graph::prelude::ethabi::ethereum_types::H256; -use graph::prelude::web3::types::{Address, Log, Transaction, H160}; -use graph::prelude::{ethabi, tiny_keccak, DeploymentHash, Entity, LightEthereumBlock, ENV_VARS}; +use graph::prelude::alloy::primitives::{Address, B256, U256}; +use graph::prelude::alloy::rpc::types::BlockTransactions; +use graph::prelude::{ + create_dummy_transaction, create_minimal_block_for_test, tiny_keccak, DeploymentHash, Entity, + LightEthereumBlock, ENV_VARS, +}; use graph::schema::EntityType; -use graph::{blockchain::block_stream::BlockWithTriggers, prelude::ethabi::ethereum_types::U64}; use graph_chain_ethereum::network::EthereumNetworkAdapters; use graph_chain_ethereum::trigger::LogRef; use graph_chain_ethereum::Chain; @@ -76,12 +80,11 @@ pub async fn chain( pub fn genesis() -> BlockWithTriggers { let ptr = test_ptr(0); + + let block = create_minimal_block_for_test(ptr.number as u64, ptr.hash.as_b256()); + BlockWithTriggers:: { - block: BlockFinality::Final(Arc::new(LightEthereumBlock { - hash: Some(H256::from_slice(ptr.hash.as_slice())), - number: Some(U64::from(ptr.number)), - ..Default::default() - })), + block: BlockFinality::Final(Arc::new(LightEthereumBlock::new(block.into()))), trigger_data: vec![Trigger::Chain(EthereumTrigger::Block( ptr, EthereumBlockTriggerType::End, @@ -101,7 +104,7 @@ pub fn generate_empty_blocks_for_range( let parent_ptr = blocks.last().map(|b| b.ptr()).unwrap_or(parent_ptr.clone()); let ptr = BlockPtr { number: i, - hash: H256::from_low_u64_be(i as u64 + add_to_hash).into(), + hash: B256::from(U256::from(i as u64 + add_to_hash)).into(), }; blocks.push(empty_block(parent_ptr, ptr)); } @@ -113,25 +116,19 @@ pub fn empty_block(parent_ptr: BlockPtr, ptr: BlockPtr) -> BlockWithTriggers parent_ptr.number); - // A 0x000.. transaction is used so `push_test_log` can use it - let transactions = vec![Transaction { - hash: H256::zero(), - block_hash: Some(H256::from_slice(ptr.hash.as_slice())), - block_number: Some(ptr.number.into()), - transaction_index: Some(0.into()), - from: Some(H160::zero()), - to: Some(H160::zero()), - ..Default::default() - }]; + let dummy_txn = + create_dummy_transaction(ptr.number as u64, ptr.hash.as_b256(), Some(0), B256::ZERO); + let transactions = BlockTransactions::Full(vec![dummy_txn]); + let alloy_block = create_minimal_block_for_test(ptr.number as u64, ptr.hash.as_b256()) + .map_header(|mut header| { + // Ensure the parent hash matches the given parent_ptr so that parent_ptr() lookups succeed + header.inner.parent_hash = parent_ptr.hash.as_b256(); + header + }) + .with_transactions(transactions); BlockWithTriggers:: { - block: BlockFinality::Final(Arc::new(LightEthereumBlock { - hash: Some(H256::from_slice(ptr.hash.as_slice())), - number: Some(U64::from(ptr.number)), - parent_hash: H256::from_slice(parent_ptr.hash.as_slice()), - transactions, - ..Default::default() - })), + block: BlockFinality::Final(Arc::new(LightEthereumBlock::new(alloy_block.into()))), trigger_data: vec![Trigger::Chain(EthereumTrigger::Block( ptr, EthereumBlockTriggerType::End, @@ -140,19 +137,25 @@ pub fn empty_block(parent_ptr: BlockPtr, ptr: BlockPtr) -> BlockWithTriggers, payload: impl Into) { + use graph::prelude::alloy::{self, primitives::LogData, rpc::types::Log}; + let log = Arc::new(Log { - address: Address::zero(), - topics: vec![tiny_keccak::keccak256(b"TestEvent(string)").into()], - data: ethabi::encode(&[ethabi::Token::String(payload.into())]).into(), - block_hash: Some(H256::from_slice(block.ptr().hash.as_slice())), - block_number: Some(block.ptr().number.into()), - transaction_hash: Some(H256::from_low_u64_be(0)), - transaction_index: Some(0.into()), - log_index: Some(0.into()), - transaction_log_index: Some(0.into()), - log_type: None, - removed: None, + inner: alloy::primitives::Log { + address: Address::ZERO, + data: LogData::new_unchecked( + vec![tiny_keccak::keccak256(b"TestEvent(string)").into()], + abi::DynSolValue::String(payload.into()).abi_encode().into(), + ), + }, + block_hash: Some(B256::from_slice(block.ptr().hash.as_slice())), + block_number: Some(block.ptr().number as u64), + transaction_hash: Some(B256::from(U256::from(0))), + transaction_index: Some(0), + log_index: Some(0), + block_timestamp: None, + removed: false, }); + block .trigger_data .push(Trigger::Chain(EthereumTrigger::Log(LogRef::FullLog( @@ -190,23 +193,30 @@ pub fn push_test_command( test_command: impl Into, data: impl Into, ) { + use graph::prelude::alloy::{self, primitives::LogData, rpc::types::Log}; + let log = Arc::new(Log { - address: Address::zero(), - topics: vec![tiny_keccak::keccak256(b"TestEvent(string,string)").into()], - data: ethabi::encode(&[ - ethabi::Token::String(test_command.into()), - ethabi::Token::String(data.into()), - ]) - .into(), - block_hash: Some(H256::from_slice(block.ptr().hash.as_slice())), - block_number: Some(block.ptr().number.into()), - transaction_hash: Some(H256::from_low_u64_be(0)), - transaction_index: Some(0.into()), - log_index: Some(0.into()), - transaction_log_index: Some(0.into()), - log_type: None, - removed: None, + inner: alloy::primitives::Log { + address: Address::ZERO, + data: LogData::new_unchecked( + vec![tiny_keccak::keccak256(b"TestEvent(string,string)").into()], + abi::DynSolValue::Tuple(vec![ + abi::DynSolValue::String(test_command.into()), + abi::DynSolValue::String(data.into()), + ]) + .abi_encode_params() + .into(), + ), + }, + block_hash: Some(block.ptr().hash.as_b256()), + block_number: Some(block.ptr().number as u64), + transaction_hash: Some(B256::from(U256::from(0))), + transaction_index: Some(0), + log_index: Some(0), + block_timestamp: None, + removed: false, }); + block .trigger_data .push(Trigger::Chain(EthereumTrigger::Log(LogRef::FullLog( diff --git a/tests/src/fixture/mod.rs b/tests/src/fixture/mod.rs index 3554624d477..2a71436e2b9 100644 --- a/tests/src/fixture/mod.rs +++ b/tests/src/fixture/mod.rs @@ -38,7 +38,8 @@ use graph::http_body_util::Full; use graph::hyper::body::Bytes; use graph::hyper::Request; use graph::ipfs::{IpfsClient, IpfsMetrics}; -use graph::prelude::ethabi::ethereum_types::H256; +use graph::prelude::alloy::primitives::B256; +use graph::prelude::alloy::primitives::U256; use graph::prelude::serde_json::{self, json}; use graph::prelude::{ lazy_static, q, r, ApiVersion, BigInt, BlockNumber, DeploymentHash, GraphQlRunner as _, @@ -76,7 +77,7 @@ pub fn test_ptr(n: BlockNumber) -> BlockPtr { // Set n as the low bits and `reorg_n` as the high bits of the hash. pub fn test_ptr_reorged(n: BlockNumber, reorg_n: u32) -> BlockPtr { - let mut hash = H256::from_low_u64_be(n as u64); + let mut hash = B256::from(U256::from(n as u64)); hash[0..4].copy_from_slice(&reorg_n.to_be_bytes()); BlockPtr { hash: hash.into(), diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index db459972bc3..5505771e79d 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -17,13 +17,13 @@ use anyhow::{anyhow, bail, Context, Result}; use graph::futures03::StreamExt; use graph::itertools::Itertools; use graph::prelude::serde_json::{json, Value}; -use graph::prelude::web3::types::U256; use graph_tests::contract::Contract; use graph_tests::subgraph::Subgraph; use graph_tests::{error, status, CONFIG}; use tokio::process::Child; use tokio::task::JoinError; use tokio::time::sleep; +use web3::types::U256; const SUBGRAPH_LAST_GRAFTING_BLOCK: i32 = 3; diff --git a/tests/tests/runner_tests.rs b/tests/tests/runner_tests.rs index 83d9625ae8a..2d1f9de1ce7 100644 --- a/tests/tests/runner_tests.rs +++ b/tests/tests/runner_tests.rs @@ -14,8 +14,7 @@ use graph::data_source::CausalityRegion; use graph::env::{EnvVars, TEST_WITH_NO_REORG}; use graph::ipfs::test_utils::add_files_to_local_ipfs_node_for_testing; use graph::object; -use graph::prelude::ethabi::ethereum_types::H256; -use graph::prelude::web3::types::Address; +use graph::prelude::alloy::primitives::{Address, B256, U256}; use graph::prelude::{hex, CheapClone, SubgraphAssignmentProvider, SubgraphName, SubgraphStore}; use graph_tests::fixture::ethereum::{ chain, empty_block, generate_empty_blocks_for_range, genesis, push_test_command, push_test_log, @@ -67,7 +66,7 @@ async fn data_source_revert() -> anyhow::Result<()> { let block1 = empty_block(block0.ptr(), test_ptr(1)); let block1_reorged_ptr = BlockPtr { number: 1, - hash: H256::from_low_u64_be(12).into(), + hash: B256::from(U256::from(12)).into(), }; let block1_reorged = empty_block(block0.ptr(), block1_reorged_ptr.clone()); let block2 = empty_block(block1_reorged_ptr, test_ptr(2)); @@ -184,7 +183,7 @@ async fn typename() -> anyhow::Result<()> { let block_1 = empty_block(block_0.ptr(), test_ptr(1)); let block_1_reorged_ptr = BlockPtr { number: 1, - hash: H256::from_low_u64_be(12).into(), + hash: B256::from(U256::from(12)).into(), }; let block_1_reorged = empty_block(block_0.ptr(), block_1_reorged_ptr); let block_2 = empty_block(block_1_reorged.ptr(), test_ptr(2)); @@ -1049,7 +1048,7 @@ async fn retry_create_ds() { let block1 = empty_block(block0.ptr(), test_ptr(1)); let block1_reorged_ptr = BlockPtr { number: 1, - hash: H256::from_low_u64_be(12).into(), + hash: B256::from(U256::from(12)).into(), }; let block1_reorged = empty_block(block0.ptr(), block1_reorged_ptr); let block2 = empty_block(block1_reorged.ptr(), test_ptr(2)); From e2a4704a974c09722cc6d1ef2109f18b38664cd5 Mon Sep 17 00:00:00 2001 From: Krishnanand V P <44740264+incrypto32@users.noreply.github.com> Date: Wed, 14 Jan 2026 14:06:01 +0400 Subject: [PATCH 2/5] Fix compatibility issues between rust-web3 and alloy, Fallback to firehose or RPC on deserialization errors (#6240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add warning logs on block deserialization failures * add Firehose/RPC fallback for stale block cache in ancestor_block * chain/ethereum: Fix ABI conversion issues in alloy migration * store, chain/ethereum: Handle pre-Byzantium receipt status correctly * graph: Normalize ABI JSON to handle undefined stateMutability Some ABIs contain "undefined" as a stateMutability value, which is not part of the official Solidity ABI specification. The spec only defines four valid values: pure, view, nonpayable, and payable. Alloy's StateMutability enum strictly follows the spec and rejects "undefined" during deserialization, causing subgraph deployment failures with: unknown variant `undefined`, expected one of `pure`, `view`, `nonpayable`, `payable` This adds a normalize_abi_json() function that preprocesses ABI JSON before deserialization, replacing "undefined" with "nonpayable" (the default state mutability). This handles non-compliant ABIs gracefully while maintaining spec compliance. Also adds a unit test to verify the normalization works correctly. 🤖 Generated with Claude Code Co-Authored-By: Claude * graph: Handle duplicate constructors in ABIs Extends normalize_abi_json() to also remove duplicate constructors from ABIs. Some non-compliant ABIs contain multiple constructor entries (e.g., DolomiteMargin ABI has two constructors, likely from incorrectly merged contract ABIs). Alloy's JsonAbi only allows one constructor and fails with 'duplicate field self.constructor' when encountering duplicates. ethabi's Contract type silently handled this by only storing one constructor (the last one encountered during deserialization). The fix keeps only the first constructor and removes any subsequent ones, matching the Solidity spec that a contract can only have one constructor. Fixes subgraph QmacPbft3reGGGL4VBzrZCKHeLpRgU9X2wUJjvPBVweyRV deployment. * extend normalizer to handle all alloy parsing incompatibilities * Add fallback for ancestor_block to handle cache truncation (#6262) * ethereum: Add RPC fallback for ancestor_block to handle cache truncation * ethereum: Extract walk_back_ancestor logic and add tests * ethereum: Fetch full blocks with receipts in ancestor_block RPC fallback When ancestor_block falls back to RPC due to cache deserialization failures, fetch full blocks with receipts instead of light blocks. Empty receipts caused missing block handler call triggers and log triggers. --------- Co-authored-by: Claude --- chain/ethereum/src/chain.rs | 389 +++++++++++++++++++--- chain/ethereum/src/codec.rs | 30 +- chain/ethereum/src/ethereum_adapter.rs | 14 +- chain/ethereum/src/runtime/abi.rs | 24 +- graph/src/data_source/common.rs | 223 ++++++++++++- graph/src/ipfs/server_address.rs | 2 +- store/postgres/src/transaction_receipt.rs | 15 +- 7 files changed, 603 insertions(+), 94 deletions(-) diff --git a/chain/ethereum/src/chain.rs b/chain/ethereum/src/chain.rs index 3f827220b93..9114e136950 100644 --- a/chain/ethereum/src/chain.rs +++ b/chain/ethereum/src/chain.rs @@ -10,7 +10,7 @@ use graph::blockchain::{ use graph::components::network_provider::ChainName; use graph::components::store::{DeploymentCursorTracker, SourceableStore}; use graph::data::subgraph::UnifiedMappingApiVersion; -use graph::firehose::{FirehoseEndpoint, ForkStep}; +use graph::firehose::{FirehoseEndpoint, FirehoseEndpoints, ForkStep}; use graph::futures03::TryStreamExt; use graph::prelude::{ retry, BlockHash, ComponentLoggerConfig, ElasticComponentLoggerConfig, EthereumBlock, @@ -355,6 +355,38 @@ impl std::fmt::Debug for Chain { } } +/// Walk back from a block pointer by following parent pointers. +/// This is the core logic used as a fallback when the cache doesn't have ancestor block. +/// +async fn walk_back_ancestor( + start_ptr: BlockPtr, + offset: BlockNumber, + root: Option, + mut parent_getter: F, +) -> Result, E> +where + F: FnMut(BlockPtr) -> Fut, + Fut: std::future::Future, E>>, +{ + let mut current_ptr = start_ptr; + + for _ in 0..offset { + match parent_getter(current_ptr.clone()).await? { + Some(parent) => { + if let Some(root_hash) = &root { + if parent.hash == *root_hash { + break; + } + } + current_ptr = parent; + } + None => return Ok(None), + } + } + + Ok(Some(current_ptr)) +} + impl Chain { /// Creates a new Ethereum [`Chain`]. pub fn new( @@ -1030,26 +1062,110 @@ impl TriggersAdapterTrait for TriggersAdapter { } } + // Find an ancestor block at the specified offset from the given block pointer. + // Primarily used for reorg detection to verify if the indexed position remains + // on the main chain. + // + // Parameters: + // - ptr: Starting block pointer from which to walk backwards (typically the chain head) + // - offset: Number of blocks to traverse backwards (0 returns ptr, 1 returns parent, etc.) + // - root: Optional block hash that serves as a boundary for traversal. This is ESSENTIAL + // for chains with skipped blocks (e.g., Filecoin EVM) where block numbers are not + // consecutive. When provided, traversal stops upon reaching the child of root, + // ensuring correct ancestor relationships even with gaps in block numbers. + // + // The function attempts to use the database cache first for performance, + // with RPC fallback implemented to handle cases where the cache is unavailable. async fn ancestor_block( &self, ptr: BlockPtr, offset: BlockNumber, root: Option, ) -> Result, Error> { - let block: Option = self + let ptr_for_log = ptr.clone(); + let cached = self .chain_store .cheap_clone() - .ancestor_block(ptr, offset, root) - .await? - .map(|x| x.0) - .map(json::from_value) - .transpose()?; - Ok(block.map(|block| { - BlockFinality::NonFinal(EthereumBlockWithCalls { - ethereum_block: block, - calls: None, - }) - })) + .ancestor_block(ptr.clone(), offset, root.clone()) + .await?; + + // First check if we have the ancestor in cache and can deserialize it + let block_ptr = match cached { + Some((json, ptr)) => { + // Try to deserialize the cached block + match json::from_value::(json.clone()) { + Ok(block) => { + // Successfully cached and deserialized + return Ok(Some(BlockFinality::NonFinal(EthereumBlockWithCalls { + ethereum_block: block, + calls: None, + }))); + } + Err(e) => { + // Cache hit but deserialization failed + warn!( + self.logger, + "Failed to deserialize cached ancestor block {} (offset {} from {}): {}. \ + This may indicate stale cache data from a previous version. \ + Falling back to Firehose/RPC.", + ptr.hash_hex(), + offset, + ptr_for_log.hash_hex(), + e + ); + ptr + } + } + } + None => { + // Cache miss - fall back to walking the chain via parent_ptr() calls. + // This provides resilience when the block cache is empty (e.g., after truncation). + debug!( + self.logger, + "ancestor_block cache miss for {} at offset {}, walking back via parent_ptr", + ptr_for_log.hash_hex(), + offset + ); + + match walk_back_ancestor( + ptr.clone(), + offset, + root.clone(), + |block_ptr| async move { self.parent_ptr(&block_ptr).await }, + ) + .await? + { + Some(ptr) => ptr, + None => return Ok(None), + } + } + }; + + // Fetch the actual block data for the identified block pointer. + // This path is taken for both cache misses and deserialization failures. + match self.chain_client.as_ref() { + ChainClient::Firehose(endpoints) => { + let block = self + .fetch_block_with_firehose(endpoints, &block_ptr) + .await?; + let ethereum_block: EthereumBlockWithCalls = (&block).try_into()?; + Ok(Some(BlockFinality::NonFinal(ethereum_block))) + } + ChainClient::Rpc(adapters) => { + match self + .fetch_full_block_with_rpc(adapters, &block_ptr) + .await? + { + Some(ethereum_block) => { + Ok(Some(BlockFinality::NonFinal(EthereumBlockWithCalls { + ethereum_block, + calls: None, + }))) + } + None => Ok(None), + } + } + } } async fn parent_ptr(&self, block: &BlockPtr) -> Result, Error> { @@ -1060,60 +1176,113 @@ impl TriggersAdapterTrait for TriggersAdapter { let chain_store = self.chain_store.cheap_clone(); // First try to get the block from the store if let Ok(blocks) = chain_store.blocks(vec![block.hash.clone()]).await { - if let Some(block) = blocks.first() { - if let Ok(block) = json::from_value::(block.clone()) { - return Ok(block.parent_ptr()); + if let Some(cached_json) = blocks.first() { + match json::from_value::(cached_json.clone()) { + Ok(block) => { + return Ok(block.parent_ptr()); + } + Err(e) => { + warn!( + self.logger, + "Failed to deserialize cached block {}: {}. \ + This may indicate stale cache data from a previous version. \ + Falling back to Firehose.", + block.hash_hex(), + e + ); + } } } } // If not in store, fetch from Firehose - let endpoint = endpoints.endpoint().await?; - let logger = self.logger.clone(); - let retry_log_message = - format!("get_block_by_ptr for block {} with firehose", block); - let block = block.clone(); - - retry(retry_log_message, &logger) - .limit(ENV_VARS.request_retries) - .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) - .run(move || { - let endpoint = endpoint.cheap_clone(); - let logger = logger.cheap_clone(); - let block = block.clone(); - async move { - endpoint - .get_block_by_ptr::(&block, &logger) - .await - .context(format!( - "Failed to fetch block by ptr {} from firehose", - block - )) - } - }) + self.fetch_block_with_firehose(endpoints, block) .await? .parent_ptr() } - ChainClient::Rpc(adapters) => { - let blocks = adapters - .cheapest_with(&self.capabilities) - .await? - .load_blocks( - self.logger.cheap_clone(), - self.chain_store.cheap_clone(), - HashSet::from_iter(Some(block.hash.as_b256())), - ) - .await?; - assert_eq!(blocks.len(), 1); - - blocks[0].parent_ptr() - } + ChainClient::Rpc(adapters) => self + .fetch_light_block_with_rpc(adapters, block) + .await? + .expect("block must exist for parent_ptr") + .parent_ptr(), }; Ok(block) } } +impl TriggersAdapter { + async fn fetch_block_with_firehose( + &self, + endpoints: &FirehoseEndpoints, + block_ptr: &BlockPtr, + ) -> Result { + let endpoint = endpoints.endpoint().await?; + let logger = self.logger.clone(); + let retry_log_message = format!("fetch_block_with_firehose {}", block_ptr); + let block_ptr = block_ptr.clone(); + + let block = retry(retry_log_message, &logger) + .limit(ENV_VARS.request_retries) + .timeout_secs(ENV_VARS.json_rpc_timeout.as_secs()) + .run(move || { + let endpoint = endpoint.cheap_clone(); + let logger = logger.cheap_clone(); + let block_ptr = block_ptr.clone(); + async move { + endpoint + .get_block_by_ptr::(&block_ptr, &logger) + .await + .context(format!("Failed to fetch block {} from firehose", block_ptr)) + } + }) + .await?; + + Ok(block) + } + + async fn fetch_light_block_with_rpc( + &self, + adapters: &EthereumNetworkAdapters, + block_ptr: &BlockPtr, + ) -> Result>, Error> { + let blocks = adapters + .cheapest_with(&self.capabilities) + .await? + .load_blocks( + self.logger.cheap_clone(), + self.chain_store.cheap_clone(), + HashSet::from_iter(Some(block_ptr.hash.as_b256())), + ) + .await?; + + Ok(blocks.into_iter().next()) + } + + async fn fetch_full_block_with_rpc( + &self, + adapters: &EthereumNetworkAdapters, + block_ptr: &BlockPtr, + ) -> Result, Error> { + let adapter = adapters.cheapest_with(&self.capabilities).await?; + + let block = adapter + .block_by_hash(&self.logger, block_ptr.hash.as_b256()) + .await?; + + match block { + Some(block) => { + let ethereum_block = adapter + .load_full_block(&self.logger, block) + .await + .map_err(|e| anyhow!("Failed to load full block: {}", e))?; + Ok(Some(ethereum_block)) + } + None => Ok(None), + } + } +} + pub struct FirehoseMapper { adapter: Arc>, filter: Arc, @@ -1388,4 +1557,118 @@ mod tests { assert!(missing.contains(&2)); assert!(missing.contains(&3)); } + + #[tokio::test] + async fn test_walk_back_ancestor() { + use std::collections::HashMap; + + let block_100_hash = BlockHash("block100".as_bytes().to_vec().into_boxed_slice()); + let block_101_hash = BlockHash("block101".as_bytes().to_vec().into_boxed_slice()); + let block_102_hash = BlockHash("block102".as_bytes().to_vec().into_boxed_slice()); + let block_103_hash = BlockHash("block103".as_bytes().to_vec().into_boxed_slice()); + let block_104_hash = BlockHash("block104".as_bytes().to_vec().into_boxed_slice()); + let block_105_hash = BlockHash("block105".as_bytes().to_vec().into_boxed_slice()); + + let block_105 = BlockPtr::new(block_105_hash.clone(), 105); + let block_104 = BlockPtr::new(block_104_hash.clone(), 104); + let block_103 = BlockPtr::new(block_103_hash.clone(), 103); + let block_102 = BlockPtr::new(block_102_hash.clone(), 102); + let block_101 = BlockPtr::new(block_101_hash.clone(), 101); + let block_100 = BlockPtr::new(block_100_hash.clone(), 100); + + let mut parent_map = HashMap::new(); + parent_map.insert(block_105_hash.clone(), block_104.clone()); + parent_map.insert(block_104_hash.clone(), block_103.clone()); + parent_map.insert(block_103_hash.clone(), block_102.clone()); + parent_map.insert(block_102_hash.clone(), block_101.clone()); + parent_map.insert(block_101_hash.clone(), block_100.clone()); + + let result = super::walk_back_ancestor(block_105.clone(), 2, None, |block_ptr| { + let parent = parent_map.get(&block_ptr.hash).cloned(); + async move { Ok::<_, std::convert::Infallible>(parent) } + }) + .await + .unwrap(); + assert_eq!(result, Some(block_103.clone())); + + let result = super::walk_back_ancestor( + block_105.clone(), + 10, + Some(block_102_hash.clone()), + |block_ptr| { + let parent = parent_map.get(&block_ptr.hash).cloned(); + async move { Ok::<_, std::convert::Infallible>(parent) } + }, + ) + .await + .unwrap(); + assert_eq!( + result, + Some(block_103.clone()), + "Should stop at child of root" + ); + } + + #[tokio::test] + async fn test_walk_back_ancestor_skipped_blocks_with_root() { + use std::collections::HashMap; + + let block_100_hash = BlockHash("block100".as_bytes().to_vec().into_boxed_slice()); + let block_101_hash = BlockHash("block101".as_bytes().to_vec().into_boxed_slice()); + let block_102_hash = BlockHash("block102".as_bytes().to_vec().into_boxed_slice()); + let block_110_hash = BlockHash("block110".as_bytes().to_vec().into_boxed_slice()); + let block_111_hash = BlockHash("block111".as_bytes().to_vec().into_boxed_slice()); + let block_112_hash = BlockHash("block112".as_bytes().to_vec().into_boxed_slice()); + let block_120_hash = BlockHash("block120".as_bytes().to_vec().into_boxed_slice()); + + let block_120 = BlockPtr::new(block_120_hash.clone(), 120); + let block_112 = BlockPtr::new(block_112_hash.clone(), 112); + let block_111 = BlockPtr::new(block_111_hash.clone(), 111); + let block_110 = BlockPtr::new(block_110_hash.clone(), 110); + let block_102 = BlockPtr::new(block_102_hash.clone(), 102); + let block_101 = BlockPtr::new(block_101_hash.clone(), 101); + let block_100 = BlockPtr::new(block_100_hash.clone(), 100); + + let mut parent_map = HashMap::new(); + parent_map.insert(block_120_hash.clone(), block_112.clone()); + parent_map.insert(block_112_hash.clone(), block_111.clone()); + parent_map.insert(block_111_hash.clone(), block_110.clone()); + parent_map.insert(block_110_hash.clone(), block_102.clone()); + parent_map.insert(block_102_hash.clone(), block_101.clone()); + parent_map.insert(block_101_hash.clone(), block_100.clone()); + + let result = super::walk_back_ancestor( + block_120.clone(), + 10, + Some(block_110_hash.clone()), + |block_ptr| { + let parent = parent_map.get(&block_ptr.hash).cloned(); + async move { Ok::<_, std::convert::Infallible>(parent) } + }, + ) + .await + .unwrap(); + assert_eq!( + result, + Some(block_111.clone()), + "root=110: should stop at 111 (child of root)" + ); + + let result = super::walk_back_ancestor( + block_120.clone(), + 10, + Some(block_101_hash.clone()), + |block_ptr| { + let parent = parent_map.get(&block_ptr.hash).cloned(); + async move { Ok::<_, std::convert::Infallible>(parent) } + }, + ) + .await + .unwrap(); + assert_eq!( + result, + Some(block_102.clone()), + "root=101: should stop at 102 (child of root, across skip)" + ); + } } diff --git a/chain/ethereum/src/codec.rs b/chain/ethereum/src/codec.rs index 2a402d4f5b2..f7d1af103bc 100644 --- a/chain/ethereum/src/codec.rs +++ b/chain/ethereum/src/codec.rs @@ -268,19 +268,21 @@ impl<'a> TryInto> for TransactionTraceAt<'a> { .trace .access_list .iter() - .map(|access_tuple| { - let address = Address::from_slice(&access_tuple.address); + .map(|access_tuple| -> Result<_, Error> { + let address = access_tuple + .address + .try_decode_proto("access tuple address")?; let storage_keys = access_tuple .storage_keys .iter() - .map(|key| B256::from_slice(key)) - .collect(); - AccessListItem { + .map(|key| key.try_decode_proto("storage key")) + .collect::, _>>()?; + Ok(AccessListItem { address, storage_keys, - } + }) }) - .collect::>() + .collect::, Error>>()? .into(); // Extract actual signature components from trace @@ -359,8 +361,8 @@ impl<'a> TryInto> for TransactionTraceAt<'a> { .trace .blob_hashes .iter() - .map(|hash| B256::from_slice(hash)) - .collect(); + .map(|hash| hash.try_decode_proto("blob hash")) + .collect::, _>>()?; let max_fee_per_blob_gas_u128 = self.trace.blob_gas_fee_cap.as_ref().map_or(0u128, |x| { @@ -401,10 +403,10 @@ impl<'a> TryInto> for TransactionTraceAt<'a> { .trace .set_code_authorizations .iter() - .map(|auth| { + .map(|auth| -> Result<_, Error> { let inner = alloy::eips::eip7702::Authorization { chain_id: U256::from_be_slice(&auth.chain_id), - address: Address::from_slice(&auth.address), + address: auth.address.try_decode_proto("authorization address")?, nonce: auth.nonce, }; @@ -412,11 +414,11 @@ impl<'a> TryInto> for TransactionTraceAt<'a> { let s = U256::from_be_slice(&auth.s); let y_parity = auth.v as u8; - alloy::eips::eip7702::SignedAuthorization::new_unchecked( + Ok(alloy::eips::eip7702::SignedAuthorization::new_unchecked( inner, y_parity, r, s, - ) + )) }) - .collect(); + .collect::, Error>>()?; let tx = TxEip7702 { // Firehose protobuf doesn't provide chain_id for transactions. diff --git a/chain/ethereum/src/ethereum_adapter.rs b/chain/ethereum/src/ethereum_adapter.rs index bd0febbf56c..6cc2ec674d8 100644 --- a/chain/ethereum/src/ethereum_adapter.rs +++ b/chain/ethereum/src/ethereum_adapter.rs @@ -1627,7 +1627,19 @@ impl EthereumAdapterTrait for EthereumAdapter { .map_err(|e| error!(&logger, "Error accessing block cache {}", e)) .unwrap_or_default() .into_iter() - .filter_map(|value| json::from_value(value).ok()) + .filter_map(|value| { + json::from_value(value.clone()) + .map_err(|e| { + warn!( + &logger, + "Failed to deserialize cached block: {}. \ + This may indicate stale cache data from a previous version. \ + Block will be re-fetched from RPC.", + e + ); + }) + .ok() + }) .map(|b| Arc::new(LightEthereumBlock::new(b))) .collect(); diff --git a/chain/ethereum/src/runtime/abi.rs b/chain/ethereum/src/runtime/abi.rs index 5641a501a6e..75717106ac6 100644 --- a/chain/ethereum/src/runtime/abi.rs +++ b/chain/ethereum/src/runtime/abi.rs @@ -6,6 +6,7 @@ use anyhow::anyhow; use async_trait::async_trait; use graph::abi; use graph::prelude::alloy; +use graph::prelude::alloy::consensus::TxReceipt; use graph::prelude::alloy::network::ReceiptResponse; use graph::prelude::alloy::rpc::types::{Log, TransactionReceipt}; use graph::prelude::alloy::serde::WithOtherFields; @@ -508,10 +509,7 @@ impl<'a> ToAscObj for EthereumTransactionData<'a> hash: asc_new(heap, &self.hash(), gas).await?, index: asc_new(heap, &BigInt::from(self.index()), gas).await?, from: asc_new(heap, &self.from(), gas).await?, - to: match self.to() { - Some(to) => asc_new(heap, &to, gas).await?, - None => AscPtr::null(), - }, + to: asc_new_or_null(heap, &self.to(), gas).await?, value: asc_new(heap, &BigInt::from_unsigned_u256(&self.value()), gas).await?, gas_limit: asc_new(heap, &BigInt::from(self.gas_limit()), gas).await?, gas_price: asc_new(heap, &BigInt::from(self.gas_price()), gas).await?, @@ -530,10 +528,7 @@ impl<'a> ToAscObj for EthereumTransactionData<'a> hash: asc_new(heap, &self.hash(), gas).await?, index: asc_new(heap, &BigInt::from(self.index()), gas).await?, from: asc_new(heap, &self.from(), gas).await?, - to: match self.to() { - Some(to) => asc_new(heap, &to, gas).await?, - None => AscPtr::null(), - }, + to: asc_new_or_null(heap, &self.to(), gas).await?, value: asc_new(heap, &BigInt::from_unsigned_u256(&self.value()), gas).await?, gas_limit: asc_new(heap, &BigInt::from(self.gas_limit()), gas).await?, gas_price: asc_new(heap, &BigInt::from(self.gas_price()), gas).await?, @@ -675,8 +670,8 @@ impl ToAscObj for Log { transaction_hash: asc_new_or_null(heap, &self.transaction_hash, gas).await?, transaction_index: asc_new_or_null_u64(heap, &self.transaction_index, gas).await?, log_index: asc_new_or_null_u64(heap, &self.log_index, gas).await?, - transaction_log_index: AscPtr::null(), // TODO(alloy): figure out how to get transaction log index - log_type: AscPtr::null(), // TODO(alloy): figure out how to get log type + transaction_log_index: AscPtr::null(), // Non-standard field, not available in alloy + log_type: AscPtr::null(), // Non-standard field, not available in alloy removed: asc_new( heap, &AscWrapped { @@ -703,16 +698,21 @@ impl ToAscObj .ok_or(HostExportError::Unknown(anyhow!( "Transaction index is missing" )))?; + let status = match self.inner.status_or_post_state().as_eip658() { + Some(success) => asc_new(heap, &BigInt::from(success as u64), gas).await?, + None => AscPtr::null(), // Pre-EIP-658 (pre-Byzantium) receipt + }; Ok(AscEthereumTransactionReceipt { transaction_hash: asc_new(heap, &self.transaction_hash, gas).await?, transaction_index: asc_new(heap, &BigInt::from(transaction_index), gas).await?, block_hash: asc_new_or_null(heap, &self.block_hash, gas).await?, block_number: asc_new_or_null_u64(heap, &self.block_number, gas).await?, - cumulative_gas_used: asc_new(heap, &BigInt::from(self.gas_used), gas).await?, + cumulative_gas_used: asc_new(heap, &BigInt::from(self.cumulative_gas_used()), gas) + .await?, gas_used: asc_new(heap, &BigInt::from(self.gas_used), gas).await?, contract_address: asc_new_or_null(heap, &self.contract_address, gas).await?, logs: asc_new(heap, &self.logs(), gas).await?, - status: asc_new(heap, &BigInt::from(self.status() as u64), gas).await?, + status, root: asc_new_or_null(heap, &self.state_root(), gas).await?, logs_bloom: asc_new(heap, self.inner.bloom().as_slice(), gas).await?, }) diff --git a/graph/src/data_source/common.rs b/graph/src/data_source/common.rs index 8090f9334b7..6149742c3ee 100644 --- a/graph/src/data_source/common.rs +++ b/graph/src/data_source/common.rs @@ -24,6 +24,80 @@ use slog::Logger; use std::collections::HashMap; use std::{str::FromStr, sync::Arc}; +/// Normalizes ABI JSON to handle compatibility issues between the legacy `ethabi`/`rust-web3` +/// parser and the stricter `alloy` parser. +/// +/// Some deployed subgraph ABIs contain non-standard constructs that `ethabi` accepted but +/// `alloy` rejects. This function patches these issues to maintain backward compatibility: +/// +/// 1. **`stateMutability: "undefined"`** - Some ABIs use "undefined" which is not a valid +/// Solidity state mutability. We replace it with "nonpayable". +/// +/// 2. **Duplicate constructors** - Some ABIs contain multiple constructor definitions. +/// We keep only the first one. +/// +/// 3. **Duplicate fallback functions** - Similar to constructors, some ABIs have multiple +/// fallback definitions. We keep only the first one. +/// +/// 4. **`indexed` field in non-event params** - The `indexed` field is only valid for event +/// parameters, but some ABIs include it on function inputs/outputs. We strip it from +/// non-event items. +/// +/// These issues were identified by validating ABIs across deployed subgraphs in production +/// before the migration to alloy. +fn normalize_abi_json(json_bytes: &[u8]) -> Result, anyhow::Error> { + let mut value: serde_json::Value = serde_json::from_slice(json_bytes)?; + + if let Some(array) = value.as_array_mut() { + let mut found_constructor = false; + let mut found_fallback = false; + let mut indices_to_remove = Vec::new(); + + for (index, item) in array.iter_mut().enumerate() { + if let Some(obj) = item.as_object_mut() { + if let Some(state_mutability) = obj.get_mut("stateMutability") { + if let Some(s) = state_mutability.as_str() { + if s == "undefined" { + *state_mutability = serde_json::Value::String("nonpayable".to_string()); + } + } + } + + let item_type = obj.get("type").and_then(|t| t.as_str()); + + match item_type { + Some("constructor") if found_constructor => indices_to_remove.push(index), + Some("constructor") => found_constructor = true, + Some("fallback") if found_fallback => indices_to_remove.push(index), + Some("fallback") => found_fallback = true, + _ => {} + } + + if item_type != Some("event") { + strip_indexed_from_params(obj.get_mut("inputs")); + strip_indexed_from_params(obj.get_mut("outputs")); + } + } + } + + for index in indices_to_remove.iter().rev() { + array.remove(*index); + } + } + + Ok(serde_json::to_vec(&value)?) +} + +fn strip_indexed_from_params(params: Option<&mut serde_json::Value>) { + if let Some(serde_json::Value::Array(arr)) = params { + for param in arr.iter_mut() { + if let Some(obj) = param.as_object_mut() { + obj.remove("indexed"); + } + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct MappingABI { pub name: String, @@ -364,11 +438,16 @@ impl UnresolvedMappingABI { self.name, self.file.link ) })?; - let contract = serde_json::from_slice(&*contract_bytes) + // Normalize the ABI to handle compatibility issues between ethabi and alloy parsers. + // See `normalize_abi_json` for details on the specific issues being addressed. + let normalized_bytes = normalize_abi_json(&contract_bytes) + .with_context(|| format!("failed to normalize ABI JSON for {}", self.name))?; + + let contract = serde_json::from_slice(&normalized_bytes) .with_context(|| format!("failed to load ABI {}", self.name))?; // Parse ABI JSON for on-demand struct field extraction - let abi_json = AbiJson::new(&contract_bytes) + let abi_json = AbiJson::new(&normalized_bytes) .with_context(|| format!("Failed to parse ABI JSON for {}", self.name))?; Ok(( @@ -2103,6 +2182,146 @@ mod tests { assert!(error_msg.contains("is not a struct")); } + #[test] + fn test_normalize_abi_json_with_undefined_state_mutability() { + let abi_with_undefined = r#"[ + { + "type": "function", + "name": "testFunction", + "inputs": [], + "outputs": [], + "stateMutability": "undefined" + }, + { + "type": "function", + "name": "normalFunction", + "inputs": [], + "outputs": [], + "stateMutability": "view" + } + ]"#; + + let normalized = normalize_abi_json(abi_with_undefined.as_bytes()).unwrap(); + let result: serde_json::Value = serde_json::from_slice(&normalized).unwrap(); + + if let Some(array) = result.as_array() { + assert_eq!(array[0]["stateMutability"], "nonpayable"); + assert_eq!(array[1]["stateMutability"], "view"); + } else { + panic!("Expected JSON array"); + } + + let json_abi: abi::JsonAbi = serde_json::from_slice(&normalized).unwrap(); + assert_eq!(json_abi.len(), 2); + } + + #[test] + fn test_normalize_abi_json_with_duplicate_constructors() { + let abi_with_duplicate_constructors = r#"[ + { + "type": "constructor", + "inputs": [{"name": "param1", "type": "address"}], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "someFunction", + "inputs": [], + "outputs": [], + "stateMutability": "view" + }, + { + "type": "constructor", + "inputs": [{"name": "param2", "type": "uint256"}], + "stateMutability": "nonpayable" + } + ]"#; + + let normalized = normalize_abi_json(abi_with_duplicate_constructors.as_bytes()).unwrap(); + let result: serde_json::Value = serde_json::from_slice(&normalized).unwrap(); + + if let Some(array) = result.as_array() { + assert_eq!(array.len(), 2); + assert_eq!(array[0]["type"], "constructor"); + assert_eq!(array[0]["inputs"][0]["name"], "param1"); + assert_eq!(array[1]["type"], "function"); + } else { + panic!("Expected JSON array"); + } + + let json_abi: abi::JsonAbi = serde_json::from_slice(&normalized).unwrap(); + assert_eq!(json_abi.len(), 2); + } + + #[test] + fn test_normalize_abi_json_with_duplicate_fallbacks() { + let abi_with_duplicate_fallbacks = r#"[ + { + "type": "fallback", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "someFunction", + "inputs": [], + "outputs": [], + "stateMutability": "view" + }, + { + "type": "fallback", + "stateMutability": "nonpayable" + } + ]"#; + + let normalized = normalize_abi_json(abi_with_duplicate_fallbacks.as_bytes()).unwrap(); + let result: serde_json::Value = serde_json::from_slice(&normalized).unwrap(); + + if let Some(array) = result.as_array() { + assert_eq!(array.len(), 2); + assert_eq!(array[0]["type"], "fallback"); + assert_eq!(array[0]["stateMutability"], "payable"); + assert_eq!(array[1]["type"], "function"); + } else { + panic!("Expected JSON array"); + } + + let json_abi: abi::JsonAbi = serde_json::from_slice(&normalized).unwrap(); + assert_eq!(json_abi.len(), 2); + } + + #[test] + fn test_normalize_abi_json_strips_indexed_from_non_events() { + let abi_with_indexed_in_function = r#"[ + { + "type": "function", + "name": "testFunction", + "inputs": [{"name": "x", "type": "uint256", "indexed": true}], + "outputs": [{"name": "y", "type": "address", "indexed": false}], + "stateMutability": "view" + }, + { + "type": "event", + "name": "TestEvent", + "anonymous": false, + "inputs": [{"name": "from", "type": "address", "indexed": true}] + } + ]"#; + + let normalized = normalize_abi_json(abi_with_indexed_in_function.as_bytes()).unwrap(); + let result: serde_json::Value = serde_json::from_slice(&normalized).unwrap(); + + if let Some(array) = result.as_array() { + assert!(array[0]["inputs"][0].get("indexed").is_none()); + assert!(array[0]["outputs"][0].get("indexed").is_none()); + assert_eq!(array[1]["inputs"][0]["indexed"], true); + } else { + panic!("Expected JSON array"); + } + + let json_abi: abi::JsonAbi = serde_json::from_slice(&normalized).unwrap(); + assert_eq!(json_abi.len(), 2); + } + // Helper function to create consistent test ABI fn create_test_mapping_abi() -> AbiJson { const ABI_JSON: &str = r#"[ diff --git a/graph/src/ipfs/server_address.rs b/graph/src/ipfs/server_address.rs index c7c8bc109f6..556997406ef 100644 --- a/graph/src/ipfs/server_address.rs +++ b/graph/src/ipfs/server_address.rs @@ -119,7 +119,7 @@ mod tests { assert_eq!( err.to_string(), - "'https://' is not a valid IPFS server address: invalid format", + "'https://' is not a valid IPFS server address: empty string", ); } diff --git a/store/postgres/src/transaction_receipt.rs b/store/postgres/src/transaction_receipt.rs index 73b11c9c400..1177422f42b 100644 --- a/store/postgres/src/transaction_receipt.rs +++ b/store/postgres/src/transaction_receipt.rs @@ -45,18 +45,11 @@ impl TryFrom for LightTransactionReceipt { let block_number = block_number.map(u64::from_be_bytes); let gas_used = gas_used.map(u64::from_be_bytes).unwrap_or(0); - // Handle both old U64 format and new boolean format + // Status is non-zero for success, zero for failure. Works for any byte length. + // Defaults to true for pre-Byzantium receipts (no status field), consistent with alloy. let status = status - .map(|bytes| { - match bytes.len() { - 1 => bytes[0] != 0, // New format: single byte - 8 => { - u64::from_be_bytes(drain_vector::<8>(bytes.to_vec()).unwrap_or([0; 8])) != 0 - } // Old format: U64 - _ => false, // Fallback - } - }) - .unwrap_or(false); + .map(|bytes| bytes.iter().any(|&b| b != 0)) + .unwrap_or(true); Ok(LightTransactionReceipt { transaction_hash: transaction_hash.into(), From 4f1d41573e0782c42a9952d1b2fcbb651e9180c4 Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Wed, 14 Jan 2026 14:24:30 +0400 Subject: [PATCH 3/5] graph: Fix silent truncation in BlockPtr::from(BlockWrapper) Use From<(B256, u64)> which has proper i32::try_from().unwrap() overflow checking instead of direct `as i32` cast that silently truncates. --- chain/ethereum/src/chain.rs | 5 +---- graph/src/blockchain/types.rs | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/chain/ethereum/src/chain.rs b/chain/ethereum/src/chain.rs index 9114e136950..06f2b1a63da 100644 --- a/chain/ethereum/src/chain.rs +++ b/chain/ethereum/src/chain.rs @@ -1152,10 +1152,7 @@ impl TriggersAdapterTrait for TriggersAdapter { Ok(Some(BlockFinality::NonFinal(ethereum_block))) } ChainClient::Rpc(adapters) => { - match self - .fetch_full_block_with_rpc(adapters, &block_ptr) - .await? - { + match self.fetch_full_block_with_rpc(adapters, &block_ptr).await? { Some(ethereum_block) => { Ok(Some(BlockFinality::NonFinal(EthereumBlockWithCalls { ethereum_block, diff --git a/graph/src/blockchain/types.rs b/graph/src/blockchain/types.rs index 290fb6bb1aa..8b50f0931c4 100644 --- a/graph/src/blockchain/types.rs +++ b/graph/src/blockchain/types.rs @@ -202,13 +202,13 @@ impl slog::Value for BlockPtr { impl From for BlockPtr { fn from(b: BlockWrapper) -> BlockPtr { - BlockPtr::from((b.hash(), b.number_u64() as i32)) + BlockPtr::from((b.hash(), b.number_u64())) } } impl From<&BlockWrapper> for BlockPtr { fn from(b: &BlockWrapper) -> BlockPtr { - BlockPtr::from((b.hash(), b.number_u64() as i32)) + BlockPtr::from((b.hash(), b.number_u64())) } } From 75ae18a7495cfaa370e10e13761f7ba927f926a1 Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Wed, 14 Jan 2026 15:34:46 +0400 Subject: [PATCH 4/5] graph: Rename BlockWrapper to LightEthereumBlock and fix silent truncations - Rename BlockWrapper struct to LightEthereumBlock directly, removing the unnecessary type alias indirection - Fix silent truncation in AnyBlock::number() - use try_from().unwrap() instead of `as BlockNumber` - Fix silent truncation in AnyBlock::block_ptr() - route through From<(B256, u64)> instead of direct `as i32` cast --- graph/src/blockchain/types.rs | 10 +++++----- graph/src/components/ethereum/mod.rs | 4 ++-- graph/src/components/ethereum/types.rs | 13 +++++-------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/graph/src/blockchain/types.rs b/graph/src/blockchain/types.rs index 8b50f0931c4..bd598f954a6 100644 --- a/graph/src/blockchain/types.rs +++ b/graph/src/blockchain/types.rs @@ -11,7 +11,7 @@ use std::convert::TryFrom; use std::time::Duration; use std::{fmt, str::FromStr}; -use crate::components::ethereum::BlockWrapper; +use crate::components::ethereum::LightEthereumBlock; use crate::cheap_clone::CheapClone; use crate::components::store::BlockNumber; @@ -200,14 +200,14 @@ impl slog::Value for BlockPtr { } } -impl From for BlockPtr { - fn from(b: BlockWrapper) -> BlockPtr { +impl From for BlockPtr { + fn from(b: LightEthereumBlock) -> BlockPtr { BlockPtr::from((b.hash(), b.number_u64())) } } -impl From<&BlockWrapper> for BlockPtr { - fn from(b: &BlockWrapper) -> BlockPtr { +impl From<&LightEthereumBlock> for BlockPtr { + fn from(b: &LightEthereumBlock) -> BlockPtr { BlockPtr::from((b.hash(), b.number_u64())) } } diff --git a/graph/src/components/ethereum/mod.rs b/graph/src/components/ethereum/mod.rs index 5fc0baf10cd..0630ddc4feb 100644 --- a/graph/src/components/ethereum/mod.rs +++ b/graph/src/components/ethereum/mod.rs @@ -1,8 +1,8 @@ mod types; pub use self::types::{ - evaluate_transaction_status, AnyBlock, AnyTransaction, BlockWrapper, EthereumBlock, - EthereumBlockWithCalls, EthereumCall, LightEthereumBlock, LightEthereumBlockExt, + evaluate_transaction_status, AnyBlock, AnyTransaction, EthereumBlock, EthereumBlockWithCalls, + EthereumCall, LightEthereumBlock, LightEthereumBlockExt, }; // Re-export Alloy network types for convenience diff --git a/graph/src/components/ethereum/types.rs b/graph/src/components/ethereum/types.rs index 931ca02b44e..0f407b6ce3b 100644 --- a/graph/src/components/ethereum/types.rs +++ b/graph/src/components/ethereum/types.rs @@ -18,12 +18,11 @@ use crate::{ pub type AnyTransaction = AnyRpcTransaction; pub type AnyBlock = AnyRpcBlock; -// BlockWrapper wraps AnyBlock #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] -pub struct BlockWrapper(AnyBlock); +pub struct LightEthereumBlock(AnyBlock); -impl Default for BlockWrapper { +impl Default for LightEthereumBlock { fn default() -> Self { use alloy::rpc::types::{Block, BlockTransactions}; use alloy::serde::WithOtherFields; @@ -38,7 +37,7 @@ impl Default for BlockWrapper { } } -impl BlockWrapper { +impl LightEthereumBlock { pub fn new(block: AnyBlock) -> Self { Self(block) } @@ -68,8 +67,6 @@ impl BlockWrapper { } } -pub type LightEthereumBlock = BlockWrapper; - pub trait LightEthereumBlockExt { fn number(&self) -> BlockNumber; fn transaction_for_log(&self, log: &Log) -> Option; @@ -82,7 +79,7 @@ pub trait LightEthereumBlockExt { impl LightEthereumBlockExt for AnyBlock { fn number(&self) -> BlockNumber { - self.header.number as BlockNumber + BlockNumber::try_from(self.header.number).unwrap() } fn timestamp(&self) -> BlockTime { @@ -124,7 +121,7 @@ impl LightEthereumBlockExt for AnyBlock { } fn block_ptr(&self) -> BlockPtr { - BlockPtr::new(self.header.hash.into(), self.header.number as i32) + BlockPtr::from((self.header.hash, self.header.number)) } } From 825a10a0dcdd11539aef55930589bcc5097eda19 Mon Sep 17 00:00:00 2001 From: incrypto32 Date: Wed, 14 Jan 2026 15:40:25 +0400 Subject: [PATCH 5/5] graph: Use expect() for required trace fields instead of silent skip Localized traces from finalized blocks must have block_number and block_hash. Using ? would silently skip traces missing these fields, potentially hiding data integrity issues. Use expect() to fail fast. Also fixes silent truncation on block_number by using try_from().unwrap(). --- graph/src/components/ethereum/types.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/graph/src/components/ethereum/types.rs b/graph/src/components/ethereum/types.rs index 0f407b6ce3b..3a18af30b7d 100644 --- a/graph/src/components/ethereum/types.rs +++ b/graph/src/components/ethereum/types.rs @@ -246,8 +246,15 @@ impl EthereumCall { gas_used: gas_used, input: call.input.clone(), output: output, - block_number: trace.block_number? as BlockNumber, - block_hash: trace.block_hash?, + block_number: BlockNumber::try_from( + trace + .block_number + .expect("localized trace must have block_number"), + ) + .unwrap(), + block_hash: trace + .block_hash + .expect("localized trace must have block_hash"), transaction_hash: trace.transaction_hash, transaction_index, })