From af8643ecb4ede28004b0a525c7c5630f290c6599 Mon Sep 17 00:00:00 2001 From: tvinagre Date: Wed, 5 Feb 2025 10:19:09 -0300 Subject: [PATCH] feat: Implement UniswapV4 Native integration (#125) * feat: initial data structure + ABI & buf models * feat: initial modules: identify new pools and events * feat: map liquidity and balance changes, introduce balance delta calculation from liquidity * feat: map tick deltas * chore: undo weird formatting * chore: undo weird formatting * feat: map fee changes and aggregate data to final block structure * feat: reimplement math natively, remove alloy and univ3sdk dependencies * chore: make clippy happy + misc improvements * chore: add rust generated files from buf & ABI * chore: make clippy happy * feat: add all modules to ethereum-uniswap-v4.yaml * chore: update yaml file to skip unnecessary buf paths * fix: update pb mod.rs * fix: fix hex / utf-8 encoding. working version * fix: simplify error handling, rename yaml file to match chain & formatting * fix: fix ChangeType for sqrt_price_x96 on Initialization * fix: make fee a non-static attribute * fix: add balance_owner to univ4 pool * feat: add uniswap-v4 mainnet yaml * fix(uniswap-v4): miscellaneous fixes for UniswapV4 (#147) * fix(uniswap-v4): correctly decode swap event deltas. Deltas are given from a user debt perspective by the event (negative if received by the pool, positive if sent by the pool). In our usecase we need the opposite of this. This commit uses `.neg()` on the amount to apply them correctly. * fix(uniswap-v4): correctly compute token amounts. We were using `get_sqrt_ratio_at_tick` at the current tick to compute the amounts. This was incorrect because it provides the price at the tick boundary, but we might be mid-tick, which could lead to erroneous balances. We now track the current price (updated by `initialization` and `swap` events) and use it when calculating the amounts. * fix(uniswapv4): do not account for fees in swaps. Previously we were adding fees as balances. But they are actually not part of the TVL and we aren't accounting for fees in the position changes (withdrawals). This commit addresses this and remove the fees from the token balances. * refactor: fix pb mod.rs * refactor: bump version and update sepolia config * ci: make clippy happy * refactor: clean unimath tests * refactor: make logic clearer and improve docstrings --------- Co-authored-by: zizou <111426680+flopell@users.noreply.github.com> --------- Co-authored-by: Zizou <111426680+zizou0x@users.noreply.github.com> Co-authored-by: zizou <111426680+flopell@users.noreply.github.com> --- substreams/Cargo.lock | 495 +- substreams/Cargo.toml | 1 + substreams/ethereum-uniswap-v4/Cargo.toml | 33 + .../ethereum-uniswap-v4/abi/PoolManager.json | 1355 ++++ substreams/ethereum-uniswap-v4/buf.gen.yaml | 12 + substreams/ethereum-uniswap-v4/build.rs | 9 + .../ethereum-uniswap-v4.yaml | 138 + .../ethereum-uniswap-v4/proto/uniswap.proto | 124 + .../sepolia-uniswap-v4.yaml | 138 + substreams/ethereum-uniswap-v4/src/abi/mod.rs | 3 + .../src/abi/pool_manager.rs | 5656 +++++++++++++++++ substreams/ethereum-uniswap-v4/src/lib.rs | 4 + .../src/modules/1_map_pool_created.rs | 146 + .../src/modules/2_store_pools.rs | 24 + .../src/modules/3_map_events.rs | 138 + .../src/modules/4_store_current_sqrtprice.rs | 39 + .../src/modules/4_store_current_tick.rs | 28 + .../modules/5_map_store_balance_changes.rs | 185 + .../src/modules/5_map_store_liquidity.rs | 101 + .../src/modules/5_map_store_ticks.rs | 73 + .../src/modules/6_map_protocol_changes.rs | 240 + .../ethereum-uniswap-v4/src/modules/mod.rs | 43 + .../src/modules/uni_math.rs | 469 ++ substreams/ethereum-uniswap-v4/src/pb/mod.rs | 8 + .../ethereum-uniswap-v4/src/pb/uniswap.v4.rs | 228 + substreams/rust-toolchain.toml | 4 + substreams/rustfmt.toml | 1 + 27 files changed, 9544 insertions(+), 151 deletions(-) create mode 100644 substreams/ethereum-uniswap-v4/Cargo.toml create mode 100644 substreams/ethereum-uniswap-v4/abi/PoolManager.json create mode 100644 substreams/ethereum-uniswap-v4/buf.gen.yaml create mode 100644 substreams/ethereum-uniswap-v4/build.rs create mode 100644 substreams/ethereum-uniswap-v4/ethereum-uniswap-v4.yaml create mode 100644 substreams/ethereum-uniswap-v4/proto/uniswap.proto create mode 100644 substreams/ethereum-uniswap-v4/sepolia-uniswap-v4.yaml create mode 100644 substreams/ethereum-uniswap-v4/src/abi/mod.rs create mode 100644 substreams/ethereum-uniswap-v4/src/abi/pool_manager.rs create mode 100644 substreams/ethereum-uniswap-v4/src/lib.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/1_map_pool_created.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/2_store_pools.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/3_map_events.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/4_store_current_sqrtprice.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/4_store_current_tick.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/5_map_store_balance_changes.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/5_map_store_liquidity.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/5_map_store_ticks.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/6_map_protocol_changes.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/mod.rs create mode 100644 substreams/ethereum-uniswap-v4/src/modules/uni_math.rs create mode 100644 substreams/ethereum-uniswap-v4/src/pb/mod.rs create mode 100644 substreams/ethereum-uniswap-v4/src/pb/uniswap.v4.rs create mode 100644 substreams/rust-toolchain.toml diff --git a/substreams/Cargo.lock b/substreams/Cargo.lock index 63e0b59..ba774ea 100644 --- a/substreams/Cargo.lock +++ b/substreams/Cargo.lock @@ -4,30 +4,30 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[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" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -48,9 +48,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -87,9 +87,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cfg-if" @@ -99,9 +99,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -140,9 +140,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "equivalent" @@ -152,9 +152,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys", @@ -173,7 +173,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "thiserror", + "thiserror 1.0.69", "uint", ] @@ -190,7 +190,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "thiserror", + "thiserror 1.0.69", "uint", ] @@ -367,11 +367,32 @@ dependencies = [ "tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)", ] +[[package]] +name = "ethereum-uniswap-v4" +version = "0.1.1" +dependencies = [ + "anyhow", + "ethabi 18.0.0", + "getrandom", + "hex", + "hex-literal 0.4.1", + "itertools 0.13.0", + "num-bigint", + "prost 0.11.9", + "rstest", + "substreams", + "substreams-entity-change", + "substreams-ethereum", + "substreams-helper 0.0.2 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)", + "tiny-keccak", + "tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)", +] + [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fixed-hash" @@ -409,6 +430,49 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -421,9 +485,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -431,10 +495,16 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.14.3" +name = "glob" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -462,9 +532,9 @@ checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys", ] @@ -507,20 +577,20 @@ 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.90", ] [[package]] name = "indexmap" -version = "2.2.5" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -555,9 +625,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "keccak" @@ -570,33 +640,33 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "multimap" @@ -606,11 +676,10 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -626,18 +695,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pad" @@ -650,9 +719,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", "bitvec", @@ -664,9 +733,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -682,20 +751,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.7", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -703,22 +772,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -727,19 +796,34 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", ] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "pin-project-lite" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "prettyplease" @@ -779,19 +863,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_datetime", "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -861,7 +944,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] @@ -884,9 +967,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -929,9 +1012,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -941,9 +1024,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -952,9 +1035,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rlp" @@ -966,6 +1055,36 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rstest" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e905296805ab93e13c1ec3a03f4b6c4f35e9498a3d5fa96dc626d22c03cd89" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0053bbffce09062bee4bcc499b0fbe7a57b879f1efe088d6d8d4c7adcdef9b" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.90", + "unicode-ident", +] + [[package]] name = "rustc-hex" version = "2.1.0" @@ -973,10 +1092,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] -name = "rustix" -version = "0.38.31" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags", "errno", @@ -987,37 +1115,44 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" -version = "1.0.204" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1030,7 +1165,7 @@ checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1054,6 +1189,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1080,7 +1224,7 @@ dependencies = [ "prost-build", "prost-types 0.11.9", "substreams-macro", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1097,9 +1241,9 @@ dependencies = [ [[package]] name = "substreams-ethereum" -version = "0.9.9" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f45dc04be50b7ca08d6d5c4560ee3eeba16ccaa1c124d0361bb30b5b84e28b" +checksum = "3d033738be190ad9c9a63712cf121e4f33ae9c57845c64021e421c45aac688b5" dependencies = [ "getrandom", "num-bigint", @@ -1111,9 +1255,9 @@ dependencies = [ [[package]] name = "substreams-ethereum-abigen" -version = "0.9.9" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c04307913a355aaf2a1bb7186d4bc7e36875f3d4aff77b47e83f1b63b24da55" +checksum = "27ef0adda971fbbd085545e73ccd7efbc12e31139cd301252f478e6b9743ff65" dependencies = [ "anyhow", "ethabi 17.2.0", @@ -1146,9 +1290,9 @@ dependencies = [ [[package]] name = "substreams-ethereum-core" -version = "0.9.9" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db9048cc9a66873ab7069ef958c2684994e6ee323da49c186b19156fdb4ca131" +checksum = "0a38e740018de8c2453f08621bab84ac55b0f1bfd42d56cf8b43ad340e670862" dependencies = [ "bigdecimal", "ethabi 17.2.0", @@ -1162,9 +1306,9 @@ dependencies = [ [[package]] name = "substreams-ethereum-derive" -version = "0.9.9" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e862928bee8653f5c9291ac619c8dc0da14ca61d8cd8d89b3acdbbde4d0bf304" +checksum = "55c934c70e6ab1fe11a89f401f36e7d9be3fe087687395767a4c13ab3d6bd055" dependencies = [ "ethabi 17.2.0", "heck", @@ -1213,7 +1357,7 @@ dependencies = [ "substreams", "substreams-entity-change", "substreams-ethereum", - "thiserror", + "thiserror 1.0.69", "tiny-keccak", ] @@ -1236,7 +1380,7 @@ dependencies = [ "substreams", "substreams-entity-change", "substreams-ethereum", - "thiserror", + "thiserror 1.0.69", "tiny-keccak", ] @@ -1249,7 +1393,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1265,9 +1409,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1282,34 +1426,55 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" +dependencies = [ + "thiserror-impl 2.0.7", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.52", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -1323,15 +1488,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", @@ -1411,21 +1576,21 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -1447,22 +1612,23 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -1471,51 +1637,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -1528,3 +1700,24 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] diff --git a/substreams/Cargo.toml b/substreams/Cargo.toml index 0b4d12c..cbe5d9d 100644 --- a/substreams/Cargo.toml +++ b/substreams/Cargo.toml @@ -10,6 +10,7 @@ members = [ "ethereum-sfrax", "ethereum-sfraxeth", "ethereum-uniswap-v3-logs-only", + "ethereum-uniswap-v4" ] resolver = "2" diff --git a/substreams/ethereum-uniswap-v4/Cargo.toml b/substreams/ethereum-uniswap-v4/Cargo.toml new file mode 100644 index 0000000..8dba988 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "ethereum-uniswap-v4" +version = "0.1.1" +edition = "2021" + +[lib] +name = "ethereum_uniswap_v4" +crate-type = ["cdylib"] + +[dependencies] +substreams = "0.5.22" +substreams-ethereum = "0.9.9" +prost = "0.11" +ethabi = "18.0.0" +anyhow = "1.0.75" +hex-literal = "0.4.1" +substreams-helper = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "b8aeaa3" } +tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "b8aeaa3" } +num-bigint = "0.4.4" +hex = "0.4.3" +tiny-keccak = "2.0" +substreams-entity-change = "1.3" +itertools = "0.13.0" + +[dev-dependencies] +rstest = "0.24.0" + +[target.wasm32-unknown-unknown.dependencies] +getrandom = { version = "0.2", features = ["custom"] } + +[build-dependencies] +anyhow = "1.0.75" +substreams-ethereum = "0.9.9" diff --git a/substreams/ethereum-uniswap-v4/abi/PoolManager.json b/substreams/ethereum-uniswap-v4/abi/PoolManager.json new file mode 100644 index 0000000..7b1dee8 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/abi/PoolManager.json @@ -0,0 +1,1355 @@ +[ + { + "inputs": [], + "name": "AlreadyUnlocked", + "type": "error" + }, + { + "inputs": [], + "name": "ContractUnlocked", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "currency0", + "type": "address" + }, + { + "internalType": "address", + "name": "currency1", + "type": "address" + } + ], + "name": "CurrenciesOutOfOrderOrEqual", + "type": "error" + }, + { + "inputs": [], + "name": "CurrencyNotSettled", + "type": "error" + }, + { + "inputs": [], + "name": "DelegateCallNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCaller", + "type": "error" + }, + { + "inputs": [], + "name": "ManagerLocked", + "type": "error" + }, + { + "inputs": [], + "name": "MustClearExactPositiveDelta", + "type": "error" + }, + { + "inputs": [], + "name": "NonzeroNativeValue", + "type": "error" + }, + { + "inputs": [], + "name": "PoolNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "ProtocolFeeTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "SwapAmountCannotBeZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "TickSpacingTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "TickSpacingTooSmall", + "type": "error" + }, + { + "inputs": [], + "name": "UnauthorizedDynamicLPFeeUpdate", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Donate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "indexed": true, + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "indexed": false, + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "int256", + "name": "liquidityDelta", + "type": "int256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "ModifyLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "OperatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "protocolFeeController", + "type": "address" + } + ], + "name": "ProtocolFeeControllerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "protocolFee", + "type": "uint24" + } + ], + "name": "ProtocolFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "int128", + "name": "amount0", + "type": "int128" + }, + { + "indexed": false, + "internalType": "int128", + "name": "amount1", + "type": "int128" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "clear", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "collectProtocolFees", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCollected", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "donate", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "delta", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "startSlot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nSlots", + "type": "uint256" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "slots", + "type": "bytes32[]" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "slots", + "type": "bytes32[]" + } + ], + "name": "exttload", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "exttload", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [ + { + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "isOperator", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "int256", + "name": "liquidityDelta", + "type": "int256" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "internalType": "struct IPoolManager.ModifyLiquidityParams", + "name": "params", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "modifyLiquidity", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "callerDelta", + "type": "int256" + }, + { + "internalType": "BalanceDelta", + "name": "feesAccrued", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFeeController", + "outputs": [ + { + "internalType": "contract IProtocolFeeController", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "protocolFeesAccrued", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint24", + "name": "newProtocolFee", + "type": "uint24" + } + ], + "name": "setProtocolFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IProtocolFeeController", + "name": "controller", + "type": "address" + } + ], + "name": "setProtocolFeeController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "settle", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "settleFor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IPoolManager.SwapParams", + "name": "params", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "swapDelta", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "sync", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "take", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "unlock", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint24", + "name": "newDynamicLPFee", + "type": "uint24" + } + ], + "name": "updateDynamicLPFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substreams/ethereum-uniswap-v4/buf.gen.yaml b/substreams/ethereum-uniswap-v4/buf.gen.yaml new file mode 100644 index 0000000..cde865e --- /dev/null +++ b/substreams/ethereum-uniswap-v4/buf.gen.yaml @@ -0,0 +1,12 @@ + +version: v1 +plugins: + - plugin: buf.build/community/neoeinstein-prost:v0.2.2 + out: src/pb + opt: + - file_descriptor_set=false + + - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 + out: src/pb + opt: + - no_features diff --git a/substreams/ethereum-uniswap-v4/build.rs b/substreams/ethereum-uniswap-v4/build.rs new file mode 100644 index 0000000..dcbc0b8 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/build.rs @@ -0,0 +1,9 @@ +use anyhow::{Ok, Result}; +use substreams_ethereum::Abigen; + +fn main() -> Result<(), anyhow::Error> { + Abigen::new("Factory", "abi/PoolManager.json")? + .generate()? + .write_to_file("src/abi/pool_manager.rs")?; + Ok(()) +} diff --git a/substreams/ethereum-uniswap-v4/ethereum-uniswap-v4.yaml b/substreams/ethereum-uniswap-v4/ethereum-uniswap-v4.yaml new file mode 100644 index 0000000..69633bf --- /dev/null +++ b/substreams/ethereum-uniswap-v4/ethereum-uniswap-v4.yaml @@ -0,0 +1,138 @@ +specVersion: v0.1.0 +package: + name: "ethereum_uniswap_v4" + version: v0.1.1 + +protobuf: + files: + - tycho/evm/v1/entity.proto + - tycho/evm/v1/common.proto + - tycho/evm/v1/utils.proto + - uniswap.proto + importPaths: + - ../../proto + - ./proto + excludePaths: + - sf/ethereum + - sf/substreams + - google + +binaries: + default: + type: wasm/rust-v1 + file: ../target/wasm32-unknown-unknown/release/ethereum_uniswap_v4.wasm + +modules: + - name: map_pools_created + kind: map + initialBlock: 21688329 + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:tycho.evm.v1.BlockEntityChanges + + - name: store_pools + kind: store + initialBlock: 21688329 + updatePolicy: set_if_not_exists + valueType: proto:uniswap.v4.Pool + inputs: + - map: map_pools_created + + - name: map_events + kind: map + initialBlock: 21688329 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_pools + output: + type: proto:uniswap.v4.Events + + - name: store_pool_current_tick + kind: store + initialBlock: 21688329 + updatePolicy: set + valueType: int64 + inputs: + - map: map_events + + - name: store_pool_current_sqrt_price + kind: store + initialBlock: 21688329 + updatePolicy: set + valueType: bigint + inputs: + - map: map_events + + - name: map_balance_changes + kind: map + initialBlock: 21688329 + inputs: + - map: map_events + - store: store_pool_current_sqrt_price + output: + type: proto:tycho.evm.v1.BlockBalanceDeltas + + - name: store_pools_balances + kind: store + initialBlock: 21688329 + updatePolicy: add + valueType: bigint + inputs: + - map: map_balance_changes + + - name: map_ticks_changes + kind: map + initialBlock: 21688329 + inputs: + - map: map_events + output: + type: proto:uniswap.v4.TickDeltas + + - name: store_ticks_liquidity + kind: store + initialBlock: 21688329 + updatePolicy: add + valueType: bigint + inputs: + - map: map_ticks_changes + + - name: map_liquidity_changes + kind: map + initialBlock: 21688329 + inputs: + - map: map_events + - store: store_pool_current_tick + output: + type: proto:uniswap.v4.LiquidityChanges + + - name: store_liquidity + kind: store + initialBlock: 21688329 + updatePolicy: set_sum + valueType: bigint + inputs: + - map: map_liquidity_changes + + - name: map_protocol_changes + kind: map + initialBlock: 21688329 + inputs: + - source: sf.ethereum.type.v2.Block + - map: map_pools_created + - map: map_events + - map: map_balance_changes + - store: store_pools_balances + mode: deltas + - map: map_ticks_changes + - store: store_ticks_liquidity + mode: deltas + - map: map_liquidity_changes + - store: store_liquidity + mode: deltas + output: + type: proto:tycho.evm.v1.BlockChanges + +params: + map_pools_created: "000000000004444c5dc75cB358380D2e3dE08A90" diff --git a/substreams/ethereum-uniswap-v4/proto/uniswap.proto b/substreams/ethereum-uniswap-v4/proto/uniswap.proto new file mode 100644 index 0000000..5041bd3 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/proto/uniswap.proto @@ -0,0 +1,124 @@ +syntax = "proto3"; + +package uniswap.v4; + +message Pool { + // // The pool address. + bytes id = 1; + // The token0 address. + bytes currency0 = 2; + // The token1 address. + bytes currency1 = 3; + // The transaction where the pool was created. + bytes created_tx_hash = 4; +} + +// A struct describing a transaction. +message Transaction { + // The transaction hash. + bytes hash = 1; + // The sender of the transaction. + bytes from = 2; + // The receiver of the transaction. + bytes to = 3; + // The transactions index within the block. + uint64 index = 4; +} + +// A change to a pool's tick. +message TickDelta { + // The address of the pool. + bytes pool_address = 1; + // The index of the tick. + int32 tick_index = 2; + // The liquidity net delta of this tick. Bigint encoded as signed little endian bytes. + bytes liquidity_net_delta = 3; + // Used to determine the order of the balance changes. Necessary for the balance store. + uint64 ordinal = 4; + Transaction transaction = 5; +} + +// A group of TickDelta +message TickDeltas { + repeated TickDelta deltas = 1; +} + +// A change to a pool's liquidity. +message LiquidityChange { + // The address of the pool. + bytes pool_address = 1; + // The liquidity changed amount. Bigint encoded as signed little endian bytes. + bytes value = 2; + // The type of update, can be absolute or delta. + LiquidityChangeType change_type = 3; + // Used to determine the order of the balance changes. Necessary for the balance store. + uint64 ordinal = 4; + Transaction transaction = 5; +} + +// A group of LiquidityChange +message LiquidityChanges { + repeated LiquidityChange changes = 1; +} + + +enum LiquidityChangeType { + DELTA = 0; + ABSOLUTE = 1; +} + +message Events { + repeated PoolEvent pool_events = 3; + + message PoolEvent { + oneof type { + Initialize initialize = 1; + ModifyLiquidity modify_liquidity = 2; + Swap swap = 3; + Donate donate = 4; + ProtocolFeeUpdated protocol_fee_updated = 5; + } + uint64 log_ordinal = 100; + string pool_id = 102; // Changed from pool_address to pool_id as V4 uses PoolId + string currency0 = 103; // Changed from token0 to currency0 + string currency1 = 104; // Changed from token1 to currency1 + Transaction transaction = 105; + + message Initialize { + string sqrt_price_x96 = 1; + int32 tick = 2; + uint32 fee = 3; + int32 tick_spacing = 4; + string hooks = 5; // Address of the hooks contract + } + + message ModifyLiquidity { + string sender = 1; + int32 tick_lower = 2; + int32 tick_upper = 3; + string liquidity_delta = 4; // Changed to support signed integers + string salt = 5; // Added salt field + } + + message Swap { + string sender = 1; + string amount0 = 2; // Signed int128 + string amount1 = 3; // Signed int128 + string sqrt_price_x96 = 4; + string liquidity = 5; + int32 tick = 6; + uint32 fee = 7; // Added fee field + } + + message Donate { + string sender = 1; + string amount0 = 2; + string amount1 = 3; + } + + message ProtocolFeeUpdated { + string pool_id = 1; + uint32 protocol_fee = 2; + } + } +} \ No newline at end of file diff --git a/substreams/ethereum-uniswap-v4/sepolia-uniswap-v4.yaml b/substreams/ethereum-uniswap-v4/sepolia-uniswap-v4.yaml new file mode 100644 index 0000000..29dd0c4 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/sepolia-uniswap-v4.yaml @@ -0,0 +1,138 @@ +specVersion: v0.1.0 +package: + name: "ethereum_uniswap_v4" + version: v0.1.1 + +protobuf: + files: + - tycho/evm/v1/entity.proto + - tycho/evm/v1/common.proto + - tycho/evm/v1/utils.proto + - uniswap.proto + importPaths: + - ../../proto + - ./proto + excludePaths: + - sf/ethereum + - sf/substreams + - google + +binaries: + default: + type: wasm/rust-v1 + file: ../target/wasm32-unknown-unknown/release/ethereum_uniswap_v4.wasm + +modules: + - name: map_pools_created + kind: map + initialBlock: 6894393 + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:tycho.evm.v1.BlockEntityChanges + + - name: store_pools + kind: store + initialBlock: 6894393 + updatePolicy: set_if_not_exists + valueType: proto:uniswap.v4.Pool + inputs: + - map: map_pools_created + + - name: map_events + kind: map + initialBlock: 6894393 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_pools + output: + type: proto:uniswap.v4.Events + + - name: store_pool_current_tick + kind: store + initialBlock: 6894393 + updatePolicy: set + valueType: int64 + inputs: + - map: map_events + + - name: store_pool_current_sqrt_price + kind: store + initialBlock: 6894393 + updatePolicy: set + valueType: bigint + inputs: + - map: map_events + + - name: map_balance_changes + kind: map + initialBlock: 6894393 + inputs: + - map: map_events + - store: store_pool_current_sqrt_price + output: + type: proto:tycho.evm.v1.BlockBalanceDeltas + + - name: store_pools_balances + kind: store + initialBlock: 6894393 + updatePolicy: add + valueType: bigint + inputs: + - map: map_balance_changes + + - name: map_ticks_changes + kind: map + initialBlock: 6894393 + inputs: + - map: map_events + output: + type: proto:uniswap.v4.TickDeltas + + - name: store_ticks_liquidity + kind: store + initialBlock: 6894393 + updatePolicy: add + valueType: bigint + inputs: + - map: map_ticks_changes + + - name: map_liquidity_changes + kind: map + initialBlock: 6894393 + inputs: + - map: map_events + - store: store_pool_current_tick + output: + type: proto:uniswap.v4.LiquidityChanges + + - name: store_liquidity + kind: store + initialBlock: 6894393 + updatePolicy: set_sum + valueType: bigint + inputs: + - map: map_liquidity_changes + + - name: map_protocol_changes + kind: map + initialBlock: 6894393 + inputs: + - source: sf.ethereum.type.v2.Block + - map: map_pools_created + - map: map_events + - map: map_balance_changes + - store: store_pools_balances + mode: deltas + - map: map_ticks_changes + - store: store_ticks_liquidity + mode: deltas + - map: map_liquidity_changes + - store: store_liquidity + mode: deltas + output: + type: proto:tycho.evm.v1.BlockChanges + +params: + map_pools_created: "8c4bcbe6b9ef47855f97e675296fa3f6fafa5f1a" diff --git a/substreams/ethereum-uniswap-v4/src/abi/mod.rs b/substreams/ethereum-uniswap-v4/src/abi/mod.rs new file mode 100644 index 0000000..578e458 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/abi/mod.rs @@ -0,0 +1,3 @@ +#![allow(clippy::all, clippy::pedantic, clippy::nursery)] + +pub mod pool_manager; \ No newline at end of file diff --git a/substreams/ethereum-uniswap-v4/src/abi/pool_manager.rs b/substreams/ethereum-uniswap-v4/src/abi/pool_manager.rs new file mode 100644 index 0000000..2c9f8e0 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/abi/pool_manager.rs @@ -0,0 +1,5656 @@ +const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; +/// Contract's functions. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Allowance { + pub owner: Vec, + pub spender: Vec, + pub id: substreams::scalar::BigInt, + } + impl Allowance { + const METHOD_ID: [u8; 4] = [89u8, 138u8, 249u8, 231u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + spender: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.spender)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Allowance { + const NAME: &'static str = "allowance"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for Allowance { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Approve { + pub spender: Vec, + pub id: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Approve { + const METHOD_ID: [u8; 4] = [66u8, 106u8, 132u8, 147u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + spender: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.spender)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Bool], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Approve { + const NAME: &'static str = "approve"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Approve { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BalanceOf { + pub owner: Vec, + pub id: substreams::scalar::BigInt, + } + impl BalanceOf { + const METHOD_ID: [u8; 4] = [0u8, 253u8, 213u8, 142u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BalanceOf { + const NAME: &'static str = "balanceOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for BalanceOf { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Burn { + pub from: Vec, + pub id: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Burn { + const METHOD_ID: [u8; 4] = [245u8, 41u8, 138u8, 202u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Burn { + const NAME: &'static str = "burn"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Clear { + pub currency: Vec, + pub amount: substreams::scalar::BigInt, + } + impl Clear { + const METHOD_ID: [u8; 4] = [128u8, 240u8, 180u8, 76u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + currency: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.currency)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Clear { + const NAME: &'static str = "clear"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct CollectProtocolFees { + pub recipient: Vec, + pub currency: Vec, + pub amount: substreams::scalar::BigInt, + } + impl CollectProtocolFees { + const METHOD_ID: [u8; 4] = [129u8, 97u8, 184u8, 116u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + currency: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.currency)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for CollectProtocolFees { + const NAME: &'static str = "collectProtocolFees"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for CollectProtocolFees { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Donate { + pub key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + Vec, + ), + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub hook_data: Vec, + } + impl Donate { + const METHOD_ID: [u8; 4] = [35u8, 66u8, 102u8, 215u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Address, ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), ethabi::ParamType::Address + ], + ), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tuple_elements[4usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + }, + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + hook_data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Tuple( + vec![ + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .0)), ethabi::Token::Address(ethabi::Address::from_slice(& + self.key.1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .key.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), { let non_full_signed_bytes = self.key.3 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .4)) + ], + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount1.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Bytes(self.hook_data.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Donate { + const NAME: &'static str = "donate"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Donate { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Extsload1 { + pub slot: [u8; 32usize], + } + impl Extsload1 { + const METHOD_ID: [u8; 4] = [30u8, 46u8, 174u8, 175u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + slot: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.slot.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<[u8; 32usize], String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut result = [0u8; 32]; + let v = values + .pop() + .expect("one output data should have existed") + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option<[u8; 32usize]> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Extsload1 { + const NAME: &'static str = "extsload"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable<[u8; 32usize]> for Extsload1 { + fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Extsload2 { + pub start_slot: [u8; 32usize], + pub n_slots: substreams::scalar::BigInt, + } + impl Extsload2 { + const METHOD_ID: [u8; 4] = [53u8, 253u8, 99u8, 26u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + start_slot: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + n_slots: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::FixedBytes(self.start_slot.as_ref().to_vec()), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.n_slots.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::FixedBytes(32usize)), + ), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut result = [0u8; 32]; + let v = inner.into_fixed_bytes().expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + .collect(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Extsload2 { + const NAME: &'static str = "extsload"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Extsload2 { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Extsload3 { + pub slots: Vec<[u8; 32usize]>, + } + impl Extsload3 { + const METHOD_ID: [u8; 4] = [219u8, 208u8, 53u8, 255u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::FixedBytes(32usize)), + ), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + slots: values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut result = [0u8; 32]; + let v = inner.into_fixed_bytes().expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + .collect(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + { + let v = self + .slots + .iter() + .map(|inner| ethabi::Token::FixedBytes( + inner.as_ref().to_vec(), + )) + .collect(); + ethabi::Token::Array(v) + }, + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::FixedBytes(32usize)), + ), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut result = [0u8; 32]; + let v = inner.into_fixed_bytes().expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + .collect(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Extsload3 { + const NAME: &'static str = "extsload"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Extsload3 { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Exttload1 { + pub slots: Vec<[u8; 32usize]>, + } + impl Exttload1 { + const METHOD_ID: [u8; 4] = [155u8, 246u8, 100u8, 95u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::FixedBytes(32usize)), + ), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + slots: values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut result = [0u8; 32]; + let v = inner.into_fixed_bytes().expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + .collect(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + { + let v = self + .slots + .iter() + .map(|inner| ethabi::Token::FixedBytes( + inner.as_ref().to_vec(), + )) + .collect(); + ethabi::Token::Array(v) + }, + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::FixedBytes(32usize)), + ), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut result = [0u8; 32]; + let v = inner.into_fixed_bytes().expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + .collect(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Exttload1 { + const NAME: &'static str = "exttload"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Exttload1 { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Exttload2 { + pub slot: [u8; 32usize], + } + impl Exttload2 { + const METHOD_ID: [u8; 4] = [241u8, 53u8, 186u8, 170u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + slot: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.slot.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<[u8; 32usize], String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut result = [0u8; 32]; + let v = values + .pop() + .expect("one output data should have existed") + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option<[u8; 32usize]> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Exttload2 { + const NAME: &'static str = "exttload"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable<[u8; 32usize]> for Exttload2 { + fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Initialize { + pub key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + Vec, + ), + pub sqrt_price_x96: substreams::scalar::BigInt, + } + impl Initialize { + const METHOD_ID: [u8; 4] = [98u8, 118u8, 203u8, 190u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Address, ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), ethabi::ParamType::Address + ], + ), + ethabi::ParamType::Uint(160usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tuple_elements[4usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + }, + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Tuple( + vec![ + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .0)), ethabi::Token::Address(ethabi::Address::from_slice(& + self.key.1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .key.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), { let non_full_signed_bytes = self.key.3 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .4)) + ], + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.sqrt_price_x96.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(24usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Initialize { + const NAME: &'static str = "initialize"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for Initialize { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IsOperator { + pub owner: Vec, + pub operator: Vec, + } + impl IsOperator { + const METHOD_ID: [u8; 4] = [182u8, 54u8, 60u8, 242u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.operator)), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Bool], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for IsOperator { + const NAME: &'static str = "isOperator"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for IsOperator { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Mint { + pub to: Vec, + pub id: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Mint { + const METHOD_ID: [u8; 4] = [21u8, 110u8, 41u8, 246u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Mint { + const NAME: &'static str = "mint"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ModifyLiquidity { + pub key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + Vec, + ), + pub params: ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + [u8; 32usize], + ), + pub hook_data: Vec, + } + impl ModifyLiquidity { + const METHOD_ID: [u8; 4] = [90u8, 107u8, 207u8, 218u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Address, ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), ethabi::ParamType::Address + ], + ), + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(256usize), + ethabi::ParamType::FixedBytes(32usize) + ], + ), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tuple_elements[4usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + }, + params: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + { + let mut v = [0 as u8; 32]; + tuple_elements[0usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[1usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut result = [0u8; 32]; + let v = tuple_elements[3usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + hook_data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Tuple( + vec![ + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .0)), ethabi::Token::Address(ethabi::Address::from_slice(& + self.key.1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .key.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), { let non_full_signed_bytes = self.key.3 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .4)) + ], + ), + ethabi::Token::Tuple( + vec![ + { let non_full_signed_bytes = self.params.0 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, { let non_full_signed_bytes = self.params.1 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, { let non_full_signed_bytes = self.params.2 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, ethabi::Token::FixedBytes(self.params.3 + .as_ref().to_vec()) + ], + ), + ethabi::Token::Bytes(self.hook_data.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Int(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ModifyLiquidity { + const NAME: &'static str = "modifyLiquidity"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable< + (substreams::scalar::BigInt, substreams::scalar::BigInt), + > for ModifyLiquidity { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Owner {} + impl Owner { + const METHOD_ID: [u8; 4] = [141u8, 165u8, 203u8, 91u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Owner { + const NAME: &'static str = "owner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Owner { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProtocolFeeController {} + impl ProtocolFeeController { + const METHOD_ID: [u8; 4] = [240u8, 45u8, 227u8, 178u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ProtocolFeeController { + const NAME: &'static str = "protocolFeeController"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for ProtocolFeeController { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProtocolFeesAccrued { + pub currency: Vec, + } + impl ProtocolFeesAccrued { + const METHOD_ID: [u8; 4] = [151u8, 232u8, 205u8, 78u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + currency: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.currency))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ProtocolFeesAccrued { + const NAME: &'static str = "protocolFeesAccrued"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for ProtocolFeesAccrued { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetOperator { + pub operator: Vec, + pub approved: bool, + } + impl SetOperator { + const METHOD_ID: [u8; 4] = [85u8, 138u8, 114u8, 151u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Bool], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.operator)), + ethabi::Token::Bool(self.approved.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Bool], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SetOperator { + const NAME: &'static str = "setOperator"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for SetOperator { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetProtocolFee { + pub key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + Vec, + ), + pub new_protocol_fee: substreams::scalar::BigInt, + } + impl SetProtocolFee { + const METHOD_ID: [u8; 4] = [126u8, 135u8, 206u8, 125u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Address, ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), ethabi::ParamType::Address + ], + ), + ethabi::ParamType::Uint(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tuple_elements[4usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + }, + new_protocol_fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Tuple( + vec![ + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .0)), ethabi::Token::Address(ethabi::Address::from_slice(& + self.key.1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .key.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), { let non_full_signed_bytes = self.key.3 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .4)) + ], + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.new_protocol_fee.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetProtocolFee { + const NAME: &'static str = "setProtocolFee"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetProtocolFeeController { + pub controller: Vec, + } + impl SetProtocolFeeController { + const METHOD_ID: [u8; 4] = [45u8, 119u8, 19u8, 137u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + controller: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.controller))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetProtocolFeeController { + const NAME: &'static str = "setProtocolFeeController"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Settle {} + impl Settle { + const METHOD_ID: [u8; 4] = [17u8, 218u8, 96u8, 180u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Settle { + const NAME: &'static str = "settle"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Settle { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SettleFor { + pub recipient: Vec, + } + impl SettleFor { + const METHOD_ID: [u8; 4] = [61u8, 212u8, 90u8, 219u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SettleFor { + const NAME: &'static str = "settleFor"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for SettleFor { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SupportsInterface { + pub interface_id: [u8; 4usize], + } + impl SupportsInterface { + const METHOD_ID: [u8; 4] = [1u8, 255u8, 201u8, 167u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(4usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + interface_id: { + let mut result = [0u8; 4]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.interface_id.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Bool], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SupportsInterface { + const NAME: &'static str = "supportsInterface"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for SupportsInterface { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Swap { + pub key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + Vec, + ), + pub params: (bool, substreams::scalar::BigInt, substreams::scalar::BigInt), + pub hook_data: Vec, + } + impl Swap { + const METHOD_ID: [u8; 4] = [243u8, 205u8, 145u8, 76u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Address, ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), ethabi::ParamType::Address + ], + ), + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Bool, ethabi::ParamType::Int(256usize), + ethabi::ParamType::Uint(160usize) + ], + ), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tuple_elements[4usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + }, + params: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize].clone().into_bool().expect(INTERNAL_ERR), + { + let mut v = [0 as u8; 32]; + tuple_elements[1usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + ) + }, + hook_data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Tuple( + vec![ + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .0)), ethabi::Token::Address(ethabi::Address::from_slice(& + self.key.1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .key.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), { let non_full_signed_bytes = self.key.3 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .4)) + ], + ), + ethabi::Token::Tuple( + vec![ + ethabi::Token::Bool(self.params.0.clone()), { let + non_full_signed_bytes = self.params.1.to_signed_bytes_be(); + let full_signed_bytes_init = if non_full_signed_bytes[0] & + 0x80 == 0x80 { 0xff } else { 0x00 }; let mut + full_signed_bytes = [full_signed_bytes_init as u8; 32]; + non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .params.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),) + ], + ), + ethabi::Token::Bytes(self.hook_data.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Swap { + const NAME: &'static str = "swap"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Swap { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Sync { + pub currency: Vec, + } + impl Sync { + const METHOD_ID: [u8; 4] = [165u8, 132u8, 17u8, 148u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + currency: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.currency))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Sync { + const NAME: &'static str = "sync"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Take { + pub currency: Vec, + pub to: Vec, + pub amount: substreams::scalar::BigInt, + } + impl Take { + const METHOD_ID: [u8; 4] = [11u8, 13u8, 156u8, 9u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + currency: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.currency)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Take { + const NAME: &'static str = "take"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Transfer { + pub receiver: Vec, + pub id: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Transfer { + const METHOD_ID: [u8; 4] = [9u8, 91u8, 205u8, 182u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + receiver: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.receiver)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Bool], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Transfer { + const NAME: &'static str = "transfer"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Transfer { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferFrom { + pub sender: Vec, + pub receiver: Vec, + pub id: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl TransferFrom { + const METHOD_ID: [u8; 4] = [254u8, 153u8, 4u8, 154u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + sender: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + receiver: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.sender)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.receiver)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Bool], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TransferFrom { + const NAME: &'static str = "transferFrom"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TransferFrom { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferOwnership { + pub new_owner: Vec, + } + impl TransferOwnership { + const METHOD_ID: [u8; 4] = [242u8, 253u8, 227u8, 139u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + new_owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.new_owner))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for TransferOwnership { + const NAME: &'static str = "transferOwnership"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Unlock { + pub data: Vec, + } + impl Unlock { + const METHOD_ID: [u8; 4] = [72u8, 200u8, 148u8, 145u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Bytes], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + data: values.pop().expect(INTERNAL_ERR).into_bytes().expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Bytes(self.data.clone())]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Bytes], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bytes() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { to_addr : address, data : self.encode(), }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Unlock { + const NAME: &'static str = "unlock"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Unlock { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct UpdateDynamicLpFee { + pub key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + Vec, + ), + pub new_dynamic_lp_fee: substreams::scalar::BigInt, + } + impl UpdateDynamicLpFee { + const METHOD_ID: [u8; 4] = [82u8, 117u8, 150u8, 81u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Address, ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), ethabi::ParamType::Address + ], + ), + ethabi::ParamType::Uint(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tuple_elements[4usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + }, + new_dynamic_lp_fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Tuple( + vec![ + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .0)), ethabi::Token::Address(ethabi::Address::from_slice(& + self.key.1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .key.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), { let non_full_signed_bytes = self.key.3 + .to_signed_bytes_be(); let full_signed_bytes_init = if + non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 + }; let mut full_signed_bytes = [full_signed_bytes_init as u8; + 32]; non_full_signed_bytes.into_iter().rev().enumerate() + .for_each(| (i, byte) | full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes + .as_ref())) }, + ethabi::Token::Address(ethabi::Address::from_slice(& self.key + .4)) + ], + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.new_dynamic_lp_fee.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for UpdateDynamicLpFee { + const NAME: &'static str = "updateDynamicLPFee"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } +} +/// Contract's events. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Approval { + pub owner: Vec, + pub spender: Vec, + pub id: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Approval { + const TOPIC_ID: [u8; 32] = [ + 179u8, + 253u8, + 80u8, + 113u8, + 131u8, + 88u8, + 135u8, + 86u8, + 122u8, + 6u8, + 113u8, + 21u8, + 17u8, + 33u8, + 137u8, + 77u8, + 220u8, + 204u8, + 40u8, + 66u8, + 241u8, + 209u8, + 11u8, + 237u8, + 173u8, + 19u8, + 224u8, + 209u8, + 124u8, + 172u8, + 233u8, + 167u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + spender: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'spender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Approval { + const NAME: &'static str = "Approval"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Donate { + pub id: [u8; 32usize], + pub sender: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl Donate { + const TOPIC_ID: [u8; 32] = [ + 41u8, + 239u8, + 5u8, + 202u8, + 175u8, + 249u8, + 64u8, + 75u8, + 124u8, + 182u8, + 209u8, + 192u8, + 233u8, + 187u8, + 174u8, + 158u8, + 170u8, + 122u8, + 178u8, + 84u8, + 31u8, + 235u8, + 161u8, + 169u8, + 196u8, + 36u8, + 133u8, + 148u8, + 192u8, + 129u8, + 86u8, + 203u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + id: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'id' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + sender: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Donate { + const NAME: &'static str = "Donate"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Initialize { + pub id: [u8; 32usize], + pub currency0: Vec, + pub currency1: Vec, + pub fee: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + pub hooks: Vec, + pub sqrt_price_x96: substreams::scalar::BigInt, + pub tick: substreams::scalar::BigInt, + } + impl Initialize { + const TOPIC_ID: [u8; 32] = [ + 221u8, + 70u8, + 110u8, + 103u8, + 78u8, + 165u8, + 87u8, + 245u8, + 98u8, + 149u8, + 226u8, + 208u8, + 33u8, + 138u8, + 18u8, + 94u8, + 164u8, + 180u8, + 240u8, + 246u8, + 243u8, + 48u8, + 123u8, + 149u8, + 248u8, + 94u8, + 97u8, + 16u8, + 131u8, + 141u8, + 100u8, + 56u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 160usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Address, + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Int(24usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + id: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'id' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + currency0: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'currency0' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + currency1: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'currency1' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick_spacing: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + hooks: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Initialize { + const NAME: &'static str = "Initialize"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ModifyLiquidity { + pub id: [u8; 32usize], + pub sender: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub liquidity_delta: substreams::scalar::BigInt, + pub salt: [u8; 32usize], + } + impl ModifyLiquidity { + const TOPIC_ID: [u8; 32] = [ + 242u8, + 8u8, + 244u8, + 145u8, + 39u8, + 130u8, + 253u8, + 37u8, + 199u8, + 241u8, + 20u8, + 202u8, + 55u8, + 35u8, + 162u8, + 213u8, + 221u8, + 111u8, + 59u8, + 204u8, + 58u8, + 200u8, + 219u8, + 90u8, + 246u8, + 59u8, + 170u8, + 133u8, + 247u8, + 17u8, + 213u8, + 236u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 128usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(256usize), + ethabi::ParamType::FixedBytes(32usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + id: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'id' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + sender: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + liquidity_delta: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + salt: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + } + impl substreams_ethereum::Event for ModifyLiquidity { + const NAME: &'static str = "ModifyLiquidity"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OperatorSet { + pub owner: Vec, + pub operator: Vec, + pub approved: bool, + } + impl OperatorSet { + const TOPIC_ID: [u8; 32] = [ + 206u8, + 181u8, + 118u8, + 217u8, + 241u8, + 94u8, + 78u8, + 32u8, + 15u8, + 219u8, + 80u8, + 150u8, + 214u8, + 77u8, + 93u8, + 253u8, + 102u8, + 126u8, + 22u8, + 222u8, + 242u8, + 12u8, + 30u8, + 239u8, + 209u8, + 66u8, + 86u8, + 216u8, + 227u8, + 250u8, + 162u8, + 103u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'operator' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + } + impl substreams_ethereum::Event for OperatorSet { + const NAME: &'static str = "OperatorSet"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnershipTransferred { + pub user: Vec, + pub new_owner: Vec, + } + impl OwnershipTransferred { + const TOPIC_ID: [u8; 32] = [ + 139u8, + 224u8, + 7u8, + 156u8, + 83u8, + 22u8, + 89u8, + 20u8, + 19u8, + 68u8, + 205u8, + 31u8, + 208u8, + 164u8, + 242u8, + 132u8, + 25u8, + 73u8, + 127u8, + 151u8, + 34u8, + 163u8, + 218u8, + 175u8, + 227u8, + 180u8, + 24u8, + 111u8, + 107u8, + 100u8, + 87u8, + 224u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + user: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'user' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + new_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'new_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for OwnershipTransferred { + const NAME: &'static str = "OwnershipTransferred"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProtocolFeeControllerUpdated { + pub protocol_fee_controller: Vec, + } + impl ProtocolFeeControllerUpdated { + const TOPIC_ID: [u8; 32] = [ + 180u8, + 189u8, + 142u8, + 245u8, + 61u8, + 246u8, + 144u8, + 185u8, + 148u8, + 61u8, + 51u8, + 24u8, + 153u8, + 96u8, + 6u8, + 219u8, + 184u8, + 42u8, + 37u8, + 245u8, + 71u8, + 25u8, + 216u8, + 200u8, + 3u8, + 91u8, + 81u8, + 106u8, + 42u8, + 91u8, + 138u8, + 204u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 2usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + protocol_fee_controller: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'protocol_fee_controller' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for ProtocolFeeControllerUpdated { + const NAME: &'static str = "ProtocolFeeControllerUpdated"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProtocolFeeUpdated { + pub id: [u8; 32usize], + pub protocol_fee: substreams::scalar::BigInt, + } + impl ProtocolFeeUpdated { + const TOPIC_ID: [u8; 32] = [ + 233u8, + 196u8, + 37u8, + 147u8, + 231u8, + 31u8, + 132u8, + 64u8, + 59u8, + 132u8, + 53u8, + 44u8, + 209u8, + 104u8, + 214u8, + 147u8, + 226u8, + 201u8, + 252u8, + 209u8, + 253u8, + 188u8, + 195u8, + 254u8, + 178u8, + 29u8, + 146u8, + 180u8, + 62u8, + 102u8, + 150u8, + 249u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 2usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(24usize)], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + id: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'id' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + protocol_fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for ProtocolFeeUpdated { + const NAME: &'static str = "ProtocolFeeUpdated"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Swap { + pub id: [u8; 32usize], + pub sender: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub sqrt_price_x96: substreams::scalar::BigInt, + pub liquidity: substreams::scalar::BigInt, + pub tick: substreams::scalar::BigInt, + pub fee: substreams::scalar::BigInt, + } + impl Swap { + const TOPIC_ID: [u8; 32] = [ + 64u8, + 233u8, + 206u8, + 203u8, + 159u8, + 95u8, + 31u8, + 28u8, + 91u8, + 156u8, + 151u8, + 222u8, + 194u8, + 145u8, + 123u8, + 126u8, + 233u8, + 46u8, + 87u8, + 186u8, + 85u8, + 99u8, + 112u8, + 141u8, + 172u8, + 169u8, + 77u8, + 216u8, + 74u8, + 215u8, + 17u8, + 47u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 192usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(24usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + id: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'id' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + sender: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + liquidity: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Swap { + const NAME: &'static str = "Swap"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Transfer { + pub caller: Vec, + pub from: Vec, + pub to: Vec, + pub id: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Transfer { + const TOPIC_ID: [u8; 32] = [ + 27u8, + 61u8, + 126u8, + 219u8, + 46u8, + 156u8, + 11u8, + 14u8, + 124u8, + 82u8, + 91u8, + 32u8, + 170u8, + 174u8, + 240u8, + 245u8, + 148u8, + 13u8, + 46u8, + 215u8, + 22u8, + 99u8, + 199u8, + 211u8, + 146u8, + 102u8, + 236u8, + 175u8, + 172u8, + 114u8, + 136u8, + 89u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + from: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'from' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'to' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + caller: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Transfer { + const NAME: &'static str = "Transfer"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } +} \ No newline at end of file diff --git a/substreams/ethereum-uniswap-v4/src/lib.rs b/substreams/ethereum-uniswap-v4/src/lib.rs new file mode 100644 index 0000000..c8d3443 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/lib.rs @@ -0,0 +1,4 @@ +#![allow(clippy::not_unsafe_ptr_arg_deref)] +mod abi; +mod modules; +mod pb; diff --git a/substreams/ethereum-uniswap-v4/src/modules/1_map_pool_created.rs b/substreams/ethereum-uniswap-v4/src/modules/1_map_pool_created.rs new file mode 100644 index 0000000..eb1904a --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/1_map_pool_created.rs @@ -0,0 +1,146 @@ +use std::str::FromStr; + +use ethabi::ethereum_types::Address; +use substreams::scalar::BigInt; +use substreams_ethereum::pb::eth::v2::{self as eth}; + +use substreams_helper::{event_handler::EventHandler, hex::Hexable}; + +use crate::abi::pool_manager::events::Initialize; + +use tycho_substreams::prelude::*; +#[substreams::handlers::map] +pub fn map_pools_created( + params: String, + block: eth::Block, +) -> Result { + let mut new_pools: Vec = vec![]; + let pool_manager = params.as_str(); + + get_new_pools(&block, &mut new_pools, pool_manager); + + Ok(BlockEntityChanges { block: None, changes: new_pools }) +} + +// Extract new pools initialized on the pool manager contract +fn get_new_pools( + block: ð::Block, + new_pools: &mut Vec, + pool_manager_address: &str, +) { + // Extract new pools from Initialize events + let mut on_pool_created = |event: Initialize, _tx: ð::TransactionTrace, _log: ð::Log| { + let tycho_tx: Transaction = _tx.into(); + + new_pools.push(TransactionEntityChanges { + tx: Some(tycho_tx.clone()), + entity_changes: vec![EntityChanges { + component_id: event.id.to_vec().to_hex(), + attributes: vec![ + // Represents the pool's LP Fee. The fee is either static or dynamic. + // Static fees are represented in hundredths of a bip, and can be set to a + // value between 0 and 1000000 (100%) and are immutable. If + // the value is set to 0x800000 (1 in the most significant bit of uint24) then + // the pool has a dynamic fee. This pool is initialized + // with 0 fee which can be changed later via hooks (dynamic fee). + Attribute { + name: "balance_owner".to_string(), + value: hex::decode(pool_manager_address).unwrap(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "fee".to_string(), + value: event.fee.to_signed_bytes_be(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "liquidity".to_string(), + value: BigInt::from(0).to_signed_bytes_be(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "tick".to_string(), + value: event.tick.to_signed_bytes_be(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "sqrt_price_x96".to_string(), + value: event + .sqrt_price_x96 + .to_signed_bytes_be(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "protocol_fees/zero2one".to_string(), + value: BigInt::from(0).to_signed_bytes_be(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "protocol_fees/one2zero".to_string(), + value: BigInt::from(0).to_signed_bytes_be(), + change: ChangeType::Creation.into(), + }, + ], + }], + component_changes: vec![ProtocolComponent { + id: event.id.to_vec().to_hex(), + tokens: vec![event.currency0.clone(), event.currency1.clone()], + contracts: vec![], + static_att: vec![ + Attribute { + name: "tick_spacing".to_string(), + value: event.tick_spacing.to_signed_bytes_be(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "pool_id".to_string(), + value: event.id.to_vec(), + change: ChangeType::Creation.into(), + }, + Attribute { + name: "hooks".to_string(), + value: event.hooks.to_vec(), + change: ChangeType::Creation.into(), + }, + ], + change: i32::from(ChangeType::Creation), + protocol_type: Some(ProtocolType { + name: "uniswap_v4_pool".to_string(), + financial_type: FinancialType::Swap.into(), + attribute_schema: vec![], + implementation_type: ImplementationType::Custom.into(), + }), + tx: Some(tycho_tx), + }], + balance_changes: vec![ + BalanceChange { + token: event.currency0, + balance: BigInt::from(0).to_signed_bytes_be(), + component_id: event + .id + .to_vec() + .to_hex() + .as_bytes() + .to_vec(), + }, + BalanceChange { + token: event.currency1, + balance: BigInt::from(0).to_signed_bytes_be(), + component_id: event + .id + .to_vec() + .to_hex() + .as_bytes() + .to_vec(), + }, + ], + }) + }; + + let mut eh = EventHandler::new(block); + + eh.filter_by_address(vec![Address::from_str(pool_manager_address).unwrap()]); + + eh.on::(&mut on_pool_created); + eh.handle_events(); +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/2_store_pools.rs b/substreams/ethereum-uniswap-v4/src/modules/2_store_pools.rs new file mode 100644 index 0000000..27c972b --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/2_store_pools.rs @@ -0,0 +1,24 @@ +use std::str; + +use substreams::store::{StoreNew, StoreSetIfNotExists, StoreSetIfNotExistsProto}; +use tycho_substreams::models::BlockEntityChanges; + +use crate::pb::uniswap::v4::Pool; + +#[substreams::handlers::store] +pub fn store_pools(pools_created: BlockEntityChanges, store: StoreSetIfNotExistsProto) { + // Store pools. Required so the next maps can match any event to a known pool by their address + + for change in pools_created.changes { + for component_change in &change.component_changes { + let pool_address: &str = &component_change.id; + let pool: Pool = Pool { + id: hex::decode(pool_address.trim_start_matches("0x")).unwrap(), + currency0: component_change.tokens[0].clone(), + currency1: component_change.tokens[1].clone(), + created_tx_hash: change.tx.as_ref().unwrap().hash.clone(), + }; + store.set_if_not_exists(0, format!("{}:{}", "pool", pool_address), &pool); + } + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/3_map_events.rs b/substreams/ethereum-uniswap-v4/src/modules/3_map_events.rs new file mode 100644 index 0000000..6a7e0ad --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/3_map_events.rs @@ -0,0 +1,138 @@ +use crate::{ + abi::pool_manager::events::{Initialize, ModifyLiquidity, ProtocolFeeUpdated, Swap}, + pb::uniswap::v4::{ + events::{pool_event, pool_event::Type, PoolEvent}, + Events, Pool, + }, +}; +use anyhow::Ok; +use substreams::store::{StoreGet, StoreGetProto}; +use substreams_ethereum::{ + pb::eth::v2::{self as eth, Log, TransactionTrace}, + Event, +}; +use substreams_helper::hex::Hexable; + +#[substreams::handlers::map] +pub fn map_events( + block: eth::Block, + pools_store: StoreGetProto, +) -> Result { + let mut pool_manager_events = block + .transaction_traces + .into_iter() + .filter(|tx| tx.status == 1) + .flat_map(|tx| { + tx.clone() + .receipt + .into_iter() + .flat_map(|receipt| receipt.logs) + .filter_map(|log| log_to_event(&log, tx.clone(), &pools_store)) + .collect::>() + }) + .collect::>(); + + pool_manager_events.sort_unstable_by_key(|e| e.log_ordinal); + + Ok(Events { pool_events: pool_manager_events }) +} + +fn log_to_event( + event: &Log, + tx: TransactionTrace, + pools_store: &StoreGetProto, +) -> Option { + if let Some(init) = Initialize::match_and_decode(event) { + // We need to track initialization again to keep track of pool current tick, which is set on + // initialization and changed on swaps. + let pool_id = init.id.to_vec().to_hex(); + let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?; + Some(PoolEvent { + log_ordinal: event.ordinal, + pool_id, + currency0: pool.currency0.to_hex(), + currency1: pool.currency1.to_hex(), + transaction: Some(tx.into()), + r#type: Some(Type::Initialize(pool_event::Initialize { + sqrt_price_x96: init.sqrt_price_x96.to_string(), + tick: init.tick.into(), + fee: init.fee.into(), + tick_spacing: init.tick_spacing.into(), + hooks: init.hooks.to_vec().to_hex(), + })), + }) + } else if let Some(swap) = Swap::match_and_decode(event) { + let pool_id = swap.id.to_vec().to_hex(); + let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?; + Some(PoolEvent { + log_ordinal: event.ordinal, + pool_id, + currency0: pool.currency0.to_hex(), + currency1: pool.currency1.to_hex(), + transaction: Some(tx.into()), + r#type: Some(Type::Swap(pool_event::Swap { + sender: swap.sender.to_hex(), + amount0: swap.amount0.to_string(), + amount1: swap.amount1.to_string(), + sqrt_price_x96: swap.sqrt_price_x96.to_string(), + liquidity: swap.liquidity.to_string(), + tick: swap.tick.into(), + fee: swap.fee.into(), + })), + }) + // Skipped because Donate doesn't seem to affect pool liquidity? + // } else if let Some(flash) = Donate::match_and_decode(event) { + // let pool_id = flash.id.to_vec().to_hex(); + // let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?; + // Some(PoolEvent { + // log_ordinal: event.ordinal, + // pool_id, + // currency0: pool.currency0.to_hex(), + // currency1: pool.currency1.to_hex(), + // transaction: Some(tx.into()), + // r#type: Some(Type::Donate(pool_event::Donate { + // sender: flash.sender.to_hex(), + // amount0: flash.amount0.to_string(), + // amount1: flash.amount1.to_string(), + // })), + // }) + } else if let Some(modify_liquidity) = ModifyLiquidity::match_and_decode(event) { + let pool_id = modify_liquidity.id.to_vec().to_hex(); + let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?; + Some(PoolEvent { + log_ordinal: event.ordinal, + pool_id, + currency0: pool.currency0.to_hex(), + currency1: pool.currency1.to_hex(), + transaction: Some(tx.into()), + r#type: Some(Type::ModifyLiquidity(pool_event::ModifyLiquidity { + sender: modify_liquidity.sender.to_hex(), + tick_lower: modify_liquidity.tick_lower.into(), + tick_upper: modify_liquidity.tick_upper.into(), + liquidity_delta: modify_liquidity + .liquidity_delta + .to_string(), + salt: modify_liquidity.salt.to_vec().to_hex(), + })), + }) + } else if let Some(protocol_fee_updated) = ProtocolFeeUpdated::match_and_decode(event) { + let pool_id = protocol_fee_updated + .id + .to_vec() + .to_hex(); + let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?; + Some(PoolEvent { + log_ordinal: event.ordinal, + pool_id: pool_id.clone(), + currency0: pool.currency0.to_hex(), + currency1: pool.currency1.to_hex(), + transaction: Some(tx.into()), + r#type: Some(Type::ProtocolFeeUpdated(pool_event::ProtocolFeeUpdated { + pool_id, + protocol_fee: protocol_fee_updated.protocol_fee.into(), + })), + }) + } else { + None + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/4_store_current_sqrtprice.rs b/substreams/ethereum-uniswap-v4/src/modules/4_store_current_sqrtprice.rs new file mode 100644 index 0000000..9add61c --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/4_store_current_sqrtprice.rs @@ -0,0 +1,39 @@ +use substreams::{ + scalar::BigInt, + store::{StoreSet, StoreSetBigInt}, +}; + +use crate::pb::uniswap::v4::{ + events::{pool_event, PoolEvent}, + Events, +}; + +use substreams::store::StoreNew; + +#[substreams::handlers::store] +pub fn store_pool_current_sqrt_price(events: Events, store: StoreSetBigInt) { + events + .pool_events + .into_iter() + .filter_map(event_to_current_sqrt_price) + .for_each(|(pool, ordinal, new_tick_index)| { + store.set(ordinal, format!("pool:{0}", pool), &new_tick_index) + }); +} + +fn event_to_current_sqrt_price(event: PoolEvent) -> Option<(String, u64, BigInt)> { + match event.r#type.as_ref().unwrap() { + pool_event::Type::Initialize(initialize) => Some(( + event.pool_id, + event.log_ordinal, + BigInt::try_from(&initialize.sqrt_price_x96) + .expect("cannot convert sqrt_price to bigint"), + )), + pool_event::Type::Swap(swap) => Some(( + event.pool_id, + event.log_ordinal, + BigInt::try_from(&swap.sqrt_price_x96).expect("cannot convert sqrt_price to bigint"), + )), + _ => None, + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/4_store_current_tick.rs b/substreams/ethereum-uniswap-v4/src/modules/4_store_current_tick.rs new file mode 100644 index 0000000..d81c055 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/4_store_current_tick.rs @@ -0,0 +1,28 @@ +use substreams::store::{StoreSet, StoreSetInt64}; + +use crate::pb::uniswap::v4::{ + events::{pool_event, PoolEvent}, + Events, +}; + +use substreams::store::StoreNew; + +#[substreams::handlers::store] +pub fn store_pool_current_tick(events: Events, store: StoreSetInt64) { + events + .pool_events + .into_iter() + .filter_map(event_to_current_tick) + .for_each(|(pool, ordinal, new_tick_index)| { + store.set(ordinal, format!("pool:{0}", pool), &new_tick_index.into()) + }); +} +fn event_to_current_tick(event: PoolEvent) -> Option<(String, u64, i32)> { + match event.r#type.as_ref().unwrap() { + pool_event::Type::Initialize(initialize) => { + Some((event.pool_id, event.log_ordinal, initialize.tick)) + } + pool_event::Type::Swap(swap) => Some((event.pool_id, event.log_ordinal, swap.tick)), + _ => None, + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/5_map_store_balance_changes.rs b/substreams/ethereum-uniswap-v4/src/modules/5_map_store_balance_changes.rs new file mode 100644 index 0000000..1e2e497 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/5_map_store_balance_changes.rs @@ -0,0 +1,185 @@ +use std::str::FromStr; + +use anyhow::Ok; +use tycho_substreams::models::{BalanceDelta, BlockBalanceDeltas}; + +use crate::{ + modules::uni_math::calculate_token_amounts, + pb::uniswap::v4::{ + events::{pool_event, PoolEvent}, + Events, + }, +}; +use substreams::{ + prelude::StoreGet, + scalar::BigInt, + store::{StoreAddBigInt, StoreGetBigInt, StoreNew}, +}; + +#[substreams::handlers::map] +pub fn map_balance_changes( + events: Events, + pools_current_sqrt_price_store: StoreGetBigInt, +) -> Result { + let balance_deltas = events + .pool_events + .into_iter() + .filter(PoolEvent::can_introduce_balance_changes) + .map(|e| { + ( + pools_current_sqrt_price_store + .get_at(e.log_ordinal, format!("pool:{0}", &e.pool_id)) + .unwrap_or(BigInt::zero()), + e, + ) + }) + .filter_map(|(current_sqrt_price, event)| { + event_to_balance_deltas(current_sqrt_price, event) + }) + .flatten() + .collect(); + + Ok(BlockBalanceDeltas { balance_deltas }) +} + +#[substreams::handlers::store] +pub fn store_pools_balances(balances_deltas: BlockBalanceDeltas, store: StoreAddBigInt) { + tycho_substreams::balances::store_balance_changes(balances_deltas, store); +} + +fn event_to_balance_deltas( + current_sqrt_price: BigInt, + event: PoolEvent, +) -> Option> { + let address = event.pool_id.as_bytes().to_vec(); + match event.r#type.unwrap() { + pool_event::Type::ModifyLiquidity(e) => { + let (delta0, delta1) = + get_amount_delta(current_sqrt_price, e.tick_lower, e.tick_upper, e.liquidity_delta); + Some(vec![ + BalanceDelta { + token: hex::decode( + event + .currency0 + .clone() + .trim_start_matches("0x"), + ) + .unwrap(), + delta: delta0.to_signed_bytes_be(), + component_id: address.clone(), + ord: event.log_ordinal, + tx: event + .transaction + .clone() + .map(Into::into), + }, + BalanceDelta { + token: hex::decode( + event + .currency1 + .clone() + .trim_start_matches("0x"), + ) + .unwrap(), + delta: delta1.to_signed_bytes_be(), + component_id: address, + ord: event.log_ordinal, + tx: event.transaction.map(Into::into), + }, + ]) + } + + pool_event::Type::Swap(e) => { + let mut delta0 = BigInt::from_str(&e.amount0) + .unwrap() + .neg(); + let mut delta1 = BigInt::from_str(&e.amount1) + .unwrap() + .neg(); + + // Adjusting deltas to exclude collected fees. + // Collected fees are not considered part of the component balance since they aren't + // accounted for during swaps. Thus, they should be excluded from the Total Value Locked + // (TVL). + // To achieve this, we subtract the fee portion from each delta. + + // We perform integer division and round to the nearest integer based on the + // remainder. If the remainder is at least half of the divisor, we + // round up. + let bips_divisor = BigInt::from(1_000_000); + let half_divisor = BigInt::from(500_000); + if delta0 > BigInt::zero() { + let fee_part = delta0.clone() * e.fee; + let (quotient, remainder) = fee_part.div_rem(&bips_divisor); + delta0 = + delta0 - if remainder >= half_divisor { quotient + 1u32 } else { quotient }; + } + if delta1 > BigInt::zero() { + let fee_part = delta1.clone() * e.fee; + let (quotient, remainder) = fee_part.div_rem(&bips_divisor); + delta1 = + delta1 - if remainder >= half_divisor { quotient + 1u32 } else { quotient }; + } + + Some(vec![ + BalanceDelta { + token: hex::decode( + event + .currency0 + .clone() + .trim_start_matches("0x"), + ) + .unwrap(), + delta: delta0.to_signed_bytes_be(), + component_id: address.clone(), + ord: event.log_ordinal, + tx: event + .transaction + .clone() + .map(Into::into), + }, + BalanceDelta { + token: hex::decode( + event + .currency1 + .clone() + .trim_start_matches("0x"), + ) + .unwrap(), + delta: delta1.to_signed_bytes_be(), + component_id: address.clone(), + ord: event.log_ordinal, + tx: event.transaction.map(Into::into), + }, + ]) + } + _ => None, + } +} + +impl PoolEvent { + fn can_introduce_balance_changes(&self) -> bool { + matches!( + self.r#type.as_ref().unwrap(), + pool_event::Type::ModifyLiquidity(_) | pool_event::Type::Swap(_) + ) + } +} + +fn get_amount_delta( + current_sqrt_price: BigInt, + tick_lower: i32, + tick_upper: i32, + liquidity_delta: String, +) -> (BigInt, BigInt) { + // This should never fail because the liquidity delta is a string encoded signed int128 (from + // the contract) + let liquidity_delta: i128 = liquidity_delta + .parse() + .expect("Failed to parse liquidity delta"); + + let (amount0, amount1) = + calculate_token_amounts(current_sqrt_price.into(), tick_lower, tick_upper, liquidity_delta) + .expect("Failed to calculate token amounts from liquidity delta"); + (BigInt::from(amount0), BigInt::from(amount1)) +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/5_map_store_liquidity.rs b/substreams/ethereum-uniswap-v4/src/modules/5_map_store_liquidity.rs new file mode 100644 index 0000000..96a7477 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/5_map_store_liquidity.rs @@ -0,0 +1,101 @@ +use std::str::FromStr; + +use substreams::store::{StoreGet, StoreGetInt64, StoreSetSum, StoreSetSumBigInt}; + +use crate::pb::uniswap::v4::{ + events::{pool_event, PoolEvent}, + Events, LiquidityChange, LiquidityChangeType, LiquidityChanges, +}; + +use substreams::scalar::BigInt; + +use anyhow::Ok; +use substreams_helper::hex::Hexable; + +#[substreams::handlers::map] +pub fn map_liquidity_changes( + events: Events, + pools_current_tick_store: StoreGetInt64, +) -> Result { + let mut changes = events + .pool_events + .into_iter() + .filter(PoolEvent::can_introduce_liquidity_changes) + .map(|e| { + ( + pools_current_tick_store + .get_at(e.log_ordinal, format!("pool:{0}", &e.pool_id)) + .unwrap_or(0), + e, + ) + }) + .filter_map(|(current_tick, event)| event_to_liquidity_deltas(current_tick, event)) + .collect::>(); + + changes.sort_unstable_by_key(|l| l.ordinal); + Ok(LiquidityChanges { changes }) +} + +#[substreams::handlers::store] +pub fn store_liquidity(ticks_deltas: LiquidityChanges, store: StoreSetSumBigInt) { + ticks_deltas + .changes + .iter() + .for_each(|changes| match changes.change_type() { + LiquidityChangeType::Delta => { + store.sum( + changes.ordinal, + format!("pool:{0}", &changes.pool_address.to_hex()), + BigInt::from_signed_bytes_be(&changes.value), + ); + } + LiquidityChangeType::Absolute => { + store.set( + changes.ordinal, + format!("pool:{0}", &changes.pool_address.to_hex()), + BigInt::from_signed_bytes_be(&changes.value), + ); + } + }); +} + +fn event_to_liquidity_deltas(current_tick: i64, event: PoolEvent) -> Option { + match event.r#type.as_ref().unwrap() { + pool_event::Type::ModifyLiquidity(mod_liquidity) => { + if current_tick >= mod_liquidity.tick_lower.into() && + current_tick < mod_liquidity.tick_upper.into() + { + Some(LiquidityChange { + pool_address: hex::decode(event.pool_id.trim_start_matches("0x")).unwrap(), + value: BigInt::from_str(&mod_liquidity.liquidity_delta) + .unwrap() + .to_signed_bytes_be(), + change_type: LiquidityChangeType::Delta.into(), + ordinal: event.log_ordinal, + transaction: Some(event.transaction.unwrap()), + }) + } else { + None + } + } + pool_event::Type::Swap(swap) => Some(LiquidityChange { + pool_address: hex::decode(event.pool_id.trim_start_matches("0x")).unwrap(), + value: BigInt::from_str(&swap.liquidity) + .unwrap() + .to_signed_bytes_be(), + change_type: LiquidityChangeType::Absolute.into(), + ordinal: event.log_ordinal, + transaction: Some(event.transaction.unwrap()), + }), + _ => None, + } +} + +impl PoolEvent { + fn can_introduce_liquidity_changes(&self) -> bool { + matches!( + self.r#type.as_ref().unwrap(), + pool_event::Type::ModifyLiquidity(_) | pool_event::Type::Swap(_) + ) + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/5_map_store_ticks.rs b/substreams/ethereum-uniswap-v4/src/modules/5_map_store_ticks.rs new file mode 100644 index 0000000..f60a09e --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/5_map_store_ticks.rs @@ -0,0 +1,73 @@ +use std::str::FromStr; + +use substreams::store::StoreAddBigInt; + +use crate::pb::uniswap::v4::{ + events::{pool_event, PoolEvent}, + Events, TickDelta, TickDeltas, +}; + +use substreams::{ + scalar::BigInt, + store::{StoreAdd, StoreNew}, +}; + +use anyhow::Ok; +use substreams_helper::hex::Hexable; + +#[substreams::handlers::map] +pub fn map_ticks_changes(events: Events) -> Result { + let ticks_deltas = events + .pool_events + .into_iter() + .flat_map(event_to_ticks_deltas) + .collect(); + + Ok(TickDeltas { deltas: ticks_deltas }) +} + +#[substreams::handlers::store] +pub fn store_ticks_liquidity(ticks_deltas: TickDeltas, store: StoreAddBigInt) { + let mut deltas = ticks_deltas.deltas.clone(); + + deltas.sort_unstable_by_key(|delta| delta.ordinal); + + deltas.iter().for_each(|delta| { + store.add( + delta.ordinal, + format!("pool:{0}:tick:{1}", &delta.pool_address.to_hex(), delta.tick_index,), + BigInt::from_signed_bytes_be(&delta.liquidity_net_delta), + ); + }); +} + +fn event_to_ticks_deltas(event: PoolEvent) -> Vec { + // On UniswapV4, the only event that changes liquidity is ModifyLiquidity. Liquidity Delta is + // now expressed as a signed int256. A positive number indicates a mint, while a negative + // indicates a burn. + // Mint events will have negative deltas for the upper tick and positive deltas for the lower. + // Burn events will have positive deltas for the upper tick and negative deltas for the lower. + match event.r#type.as_ref().unwrap() { + pool_event::Type::ModifyLiquidity(liq_change) => { + let amount = + BigInt::from_str(&liq_change.liquidity_delta).expect("Failed to parse BigInt"); + vec![ + TickDelta { + pool_address: hex::decode(event.pool_id.trim_start_matches("0x")).unwrap(), + tick_index: liq_change.tick_lower, + liquidity_net_delta: amount.to_signed_bytes_be(), + ordinal: event.log_ordinal, + transaction: event.transaction.clone(), + }, + TickDelta { + pool_address: hex::decode(event.pool_id.trim_start_matches("0x")).unwrap(), + tick_index: liq_change.tick_upper, + liquidity_net_delta: amount.neg().to_signed_bytes_be(), + ordinal: event.log_ordinal, + transaction: event.transaction, + }, + ] + } + _ => vec![], + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/6_map_protocol_changes.rs b/substreams/ethereum-uniswap-v4/src/modules/6_map_protocol_changes.rs new file mode 100644 index 0000000..b0b9c2c --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/6_map_protocol_changes.rs @@ -0,0 +1,240 @@ +use crate::pb::uniswap::v4::{ + events::{pool_event, PoolEvent}, + Events, LiquidityChanges, TickDeltas, +}; +use itertools::Itertools; +use std::{collections::HashMap, str::FromStr, vec}; +use substreams::{pb::substreams::StoreDeltas, scalar::BigInt}; +use substreams_ethereum::pb::eth::v2::{self as eth}; +use substreams_helper::hex::Hexable; +use tycho_substreams::{balances::aggregate_balances_changes, prelude::*}; + +type PoolAddress = Vec; + +#[substreams::handlers::map] +pub fn map_protocol_changes( + block: eth::Block, + created_pools: BlockEntityChanges, + events: Events, + balances_map_deltas: BlockBalanceDeltas, + balances_store_deltas: StoreDeltas, + ticks_map_deltas: TickDeltas, + ticks_store_deltas: StoreDeltas, + pool_liquidity_changes: LiquidityChanges, + pool_liquidity_store_deltas: StoreDeltas, +) -> Result { + // We merge contract changes by transaction (identified by transaction index) making it easy to + // sort them at the very end. + let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new(); + + // Add created pools to the tx_changes_map + for change in created_pools.changes.into_iter() { + let tx = change.tx.as_ref().unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(tx)); + change + .component_changes + .iter() + .for_each(|c| { + builder.add_protocol_component(c); + }); + change + .entity_changes + .iter() + .for_each(|ec| { + builder.add_entity_change(ec); + }); + change + .balance_changes + .iter() + .for_each(|bc| { + builder.add_balance_change(bc); + }); + } + + // Balance changes are gathered by the `StoreDelta` based on `PoolBalanceChanged` creating + // `BlockBalanceDeltas`. We essentially just process the changes that occurred to the `store` + // this block. Then, these balance changes are merged onto the existing map of tx contract + // changes, inserting a new one if it doesn't exist. + aggregate_balances_changes(balances_store_deltas, balances_map_deltas) + .into_iter() + .for_each(|(_, (tx, balances))| { + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx)); + balances + .values() + .for_each(|token_bc_map| { + token_bc_map + .values() + .for_each(|bc| builder.add_balance_change(bc)) + }); + }); + + // Insert ticks net-liquidity changes + ticks_store_deltas + .deltas + .into_iter() + .zip(ticks_map_deltas.deltas) + .for_each(|(store_delta, tick_delta)| { + let new_value_bigint = + BigInt::from_str(&String::from_utf8(store_delta.new_value).unwrap()).unwrap(); + + // If old value is empty or the int value is 0, it's considered as a creation. + let is_creation = store_delta.old_value.is_empty() || + BigInt::from_str(&String::from_utf8(store_delta.old_value).unwrap()) + .unwrap() + .is_zero(); + let attribute_name = format!("ticks/{}/net-liquidity", tick_delta.tick_index); + let attribute = Attribute { + name: attribute_name, + value: new_value_bigint.to_signed_bytes_be(), + change: if is_creation { + ChangeType::Creation.into() + } else if new_value_bigint.is_zero() { + ChangeType::Deletion.into() + } else { + ChangeType::Update.into() + }, + }; + let tx = tick_delta.transaction.unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx.into())); + + builder.add_entity_change(&EntityChanges { + component_id: tick_delta.pool_address.to_hex(), + attributes: vec![attribute], + }); + }); + + // Insert liquidity changes + pool_liquidity_store_deltas + .deltas + .into_iter() + .zip(pool_liquidity_changes.changes) + .for_each(|(store_delta, change)| { + let new_value_bigint = BigInt::from_str( + String::from_utf8(store_delta.new_value) + .unwrap() + .split(':') + .nth(1) + .unwrap(), + ) + .unwrap(); + let tx = change.transaction.unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx.into())); + + builder.add_entity_change(&EntityChanges { + component_id: change.pool_address.to_hex(), + attributes: vec![Attribute { + name: "liquidity".to_string(), + value: new_value_bigint.to_signed_bytes_be(), + change: ChangeType::Update.into(), + }], + }); + }); + + // Insert others changes + events + .pool_events + .into_iter() + .flat_map(event_to_attributes_updates) + .for_each(|(tx, pool_address, attr)| { + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx)); + builder.add_entity_change(&EntityChanges { + component_id: pool_address.to_hex(), + attributes: vec![attr], + }); + }); + + Ok(BlockChanges { + block: Some((&block).into()), + changes: transaction_changes + .drain() + .sorted_unstable_by_key(|(index, _)| *index) + .filter_map(|(_, builder)| builder.build()) + .collect::>(), + }) +} + +fn event_to_attributes_updates(event: PoolEvent) -> Vec<(Transaction, PoolAddress, Attribute)> { + match event.r#type.as_ref().unwrap() { + pool_event::Type::Swap(swap) => vec![ + ( + event + .transaction + .clone() + .unwrap() + .into(), + hex::decode( + event + .pool_id + .clone() + .trim_start_matches("0x"), + ) + .unwrap(), + Attribute { + name: "sqrt_price_x96".to_string(), + value: BigInt::from_str(&swap.sqrt_price_x96) + .unwrap() + .to_signed_bytes_be(), + change: ChangeType::Update.into(), + }, + ), + ( + event.transaction.unwrap().into(), + hex::decode(event.pool_id.trim_start_matches("0x")).unwrap(), + Attribute { + name: "tick".to_string(), + value: BigInt::from(swap.tick).to_signed_bytes_be(), + change: ChangeType::Update.into(), + }, + ), + ], + pool_event::Type::ProtocolFeeUpdated(sfp) => { + // Mask to extract the lower 12 bits (0xFFF corresponds to 12 bits set to 1) + let lower_12_bits = sfp.protocol_fee & 0xFFF; + + // Shift right by 12 bits and mask again to get the next 12 bits + let upper_12_bits = (sfp.protocol_fee >> 12) & 0xFFF; + + vec![ + ( + event + .transaction + .clone() + .unwrap() + .into(), + hex::decode( + event + .pool_id + .clone() + .trim_start_matches("0x"), + ) + .unwrap(), + Attribute { + name: "protocol_fees/zero2one".to_string(), + value: BigInt::from(lower_12_bits).to_signed_bytes_be(), + change: ChangeType::Update.into(), + }, + ), + ( + event.transaction.unwrap().into(), + hex::decode(event.pool_id.trim_start_matches("0x")).unwrap(), + Attribute { + name: "protocol_fees/one2zero".to_string(), + value: BigInt::from(upper_12_bits).to_signed_bytes_be(), + change: ChangeType::Update.into(), + }, + ), + ] + } + _ => vec![], + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/mod.rs b/substreams/ethereum-uniswap-v4/src/modules/mod.rs new file mode 100644 index 0000000..cd855e9 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/mod.rs @@ -0,0 +1,43 @@ +use substreams_ethereum::pb::eth::v2::TransactionTrace; + +use crate::pb::uniswap::v4::Transaction; + +#[path = "1_map_pool_created.rs"] +mod map_pool_created; + +#[path = "2_store_pools.rs"] +mod store_pools; + +#[path = "3_map_events.rs"] +mod map_events; + +#[path = "4_store_current_tick.rs"] +mod store_current_tick; + +#[path = "4_store_current_sqrtprice.rs"] +mod store_current_sqrtprice; + +#[path = "5_map_store_balance_changes.rs"] +mod map_store_balance_changes; + +#[path = "5_map_store_ticks.rs"] +mod map_store_ticks; + +#[path = "5_map_store_liquidity.rs"] +mod map_store_liquidity; + +#[path = "6_map_protocol_changes.rs"] +mod map_protocol_changes; +mod uni_math; + +impl From for Transaction { + fn from(value: TransactionTrace) -> Self { + Self { hash: value.hash, from: value.from, to: value.to, index: value.index.into() } + } +} + +impl From for tycho_substreams::prelude::Transaction { + fn from(value: Transaction) -> Self { + Self { hash: value.hash, from: value.from, to: value.to, index: value.index } + } +} diff --git a/substreams/ethereum-uniswap-v4/src/modules/uni_math.rs b/substreams/ethereum-uniswap-v4/src/modules/uni_math.rs new file mode 100644 index 0000000..2fcdede --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/modules/uni_math.rs @@ -0,0 +1,469 @@ +use num_bigint::BigInt; +use std::ops::Shr; +/// Calculates the amounts of token0 and token1 for a given position +/// +/// Source: https://github.com/Uniswap/v4-core/blob/main/src/libraries/Pool.sol +/// Function: modifyLiquidity +/// +/// # Arguments +/// * `current_sqrt_price` - Current square root price +/// * `tick_lower` - Lower tick of the position +/// * `tick_upper` - Upper tick of the position +/// * `liquidity_delta` - Amount of liquidity to add/remove +/// +/// # Returns +/// * `Result<(BigInt, BigInt), String>` - Token amounts (amount0, amount1) +pub fn calculate_token_amounts( + current_sqrt_price: BigInt, + tick_lower: i32, + tick_upper: i32, + liquidity_delta: i128, +) -> Result<(BigInt, BigInt), String> { + let sqrt_price_lower_x96: BigInt = get_sqrt_ratio_at_tick(tick_lower)?; + let sqrt_price_upper_x96: BigInt = get_sqrt_ratio_at_tick(tick_upper)?; + + // Calculate amounts based on current price relative to the range + let (amount0, amount1) = if current_sqrt_price < sqrt_price_lower_x96 { + // Current price is below the range: position in token0 + let amount0 = + get_amount_0_delta_signed(sqrt_price_lower_x96, sqrt_price_upper_x96, liquidity_delta)?; + (amount0, BigInt::from(0)) + } else if current_sqrt_price < sqrt_price_upper_x96 { + // Current price is within the range: position in both tokens + let amount0 = get_amount_0_delta_signed( + current_sqrt_price.clone(), + sqrt_price_upper_x96, + liquidity_delta, + )?; + + let amount1 = + get_amount_1_delta_signed(sqrt_price_lower_x96, current_sqrt_price, liquidity_delta)?; + + (amount0, amount1) + } else { + // Current price is above the range: position in token1 + let amount1 = + get_amount_1_delta_signed(sqrt_price_lower_x96, sqrt_price_upper_x96, liquidity_delta)?; + + (BigInt::from(0), amount1) + }; + + Ok((amount0, amount1)) +} + +const MAX_TICK: i32 = 887272; + +/// Returns the sqrt ratio as a Q64.96 for the given tick. The sqrt ratio is computed as +/// sqrt(1.0001)^tick +/// Adapted from: https://github.com/shuhuiluo/uniswap-v3-sdk-rs/blob/v2.9.1/src/utils/tick_math.rs#L57 +fn get_sqrt_ratio_at_tick(tick: i32) -> Result { + let abs_tick = tick.abs(); + + if abs_tick > MAX_TICK { + return Err("Tick out of bounds".to_string()); + } + + // Initialize ratio with either 2^128 / sqrt(1.0001) or 2^128 + let mut ratio = if abs_tick & 0x1 != 0 { + BigInt::parse_bytes(b"fffcb933bd6fad37aa2d162d1a594001", 16).unwrap() + } else { + BigInt::from(1) << 128 + }; + + if abs_tick & 0x2 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"fff97272373d413259a46990580e213a", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x4 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"fff2e50f5f656932ef12357cf3c7fdcc", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x8 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"ffe5caca7e10e4e61c3624eaa0941cd0", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x10 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"ffcb9843d60f6159c9db58835c926644", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x20 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"ff973b41fa98c081472e6896dfb254c0", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x40 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"ff2ea16466c96a3843ec78b326b52861", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x80 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"fe5dee046a99a2a811c461f1969c3053", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x100 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"fcbe86c7900a88aedcffc83b479aa3a4", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x200 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"f987a7253ac413176f2b074cf7815e54", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x400 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"f3392b0822b70005940c7a398e4b70f3", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x800 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"e7159475a2c29b7443b29c7fa6e889d9", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x1000 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"d097f3bdfd2022b8845ad8f792aa5825", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x2000 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"a9f746462d870fdf8a65dc1f90e061e5", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x4000 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"70d869a156d2a1b890bb3df62baf32f7", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x8000 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"31be135f97d08fd981231505542fcfa6", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x10000 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"9aa508b5b7a84e1c677de54f3e99bc9", 16).unwrap()) + .shr(128); + } + if abs_tick & 0x20000 != 0 { + ratio = + (&ratio * BigInt::parse_bytes(b"5d6af8dedb81196699c329225ee604", 16).unwrap()).shr(128); + } + if abs_tick & 0x40000 != 0 { + ratio = + (&ratio * BigInt::parse_bytes(b"2216e584f5fa1ea926041bedfe98", 16).unwrap()).shr(128); + } + if abs_tick & 0x80000 != 0 { + ratio = (&ratio * BigInt::parse_bytes(b"48a170391f7dc42444e8fa2", 16).unwrap()).shr(128); + } + + if tick > 0 { + let max = (BigInt::from(1) << 256) - 1; + ratio = max / ratio; + } + // Add 2^32 - 1 and shift right by 32 + ratio = (ratio + ((BigInt::from(1) << 32) - 1)) >> 32; + + Ok(ratio) +} + +/// Helper that gets signed token0 delta +/// Source: https://github.com/shuhuiluo/uniswap-v3-sdk-rs/blob/v2.9.1/src/utils/sqrt_price_math.rs#L422 +/// +/// ## Arguments +/// +/// * `sqrt_ratio_a_x96`: A sqrt price +/// * `sqrt_ratio_b_x96`: Another sqrt price +/// * `liquidity`: The change in liquidity for which to compute the amount0 delta +/// +/// ## Returns +/// +/// Amount of token0 corresponding to the passed liquidityDelta between the two prices +fn get_amount_0_delta_signed( + sqrt_ratio_a_x96: BigInt, + sqrt_ratio_b_x96: BigInt, + liquidity: i128, +) -> Result { + let sign = !liquidity.is_negative(); + // Create mask for negative numbers + let mask = if sign { 0u128 } else { u128::MAX }; + + // Get absolute value of liquidity using XOR and addition + let liquidity = mask ^ mask.wrapping_add_signed(liquidity); + + // Convert mask to BigInt (all 1s or all 0s) + let mask = if sign { BigInt::from(0) } else { -BigInt::from(1) }; + + let amount_0 = get_amount_0_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, liquidity, sign)?; + + // Apply the mask using XOR and subtraction to restore the sign + Ok((amount_0 ^ &mask) - mask) +} + +/// Gets the amount0 delta between two prices +/// +/// Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper), +/// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower)) +/// +/// ## Arguments +/// +/// * `sqrt_ratio_a_x96`: A sqrt price assumed to be lower otherwise swapped +/// * `sqrt_ratio_b_x96`: Another sqrt price +/// * `liquidity`: The amount of usable liquidity +/// * `round_up`: Whether to round the amount up or down +/// +/// ## Returns +/// +/// Amount of token0 required to cover a position of size liquidity between the two passed prices +fn get_amount_0_delta( + sqrt_ratio_a_x96: BigInt, + sqrt_ratio_b_x96: BigInt, + liquidity: u128, + round_up: bool, +) -> Result { + let (sqrt_ratio_a_x96, sqrt_ratio_b_x96) = if sqrt_ratio_a_x96 < sqrt_ratio_b_x96 { + (sqrt_ratio_a_x96, sqrt_ratio_b_x96) + } else { + (sqrt_ratio_b_x96, sqrt_ratio_a_x96) + }; + + if sqrt_ratio_a_x96 == BigInt::from(0) { + return Err("Price cannot be zero".to_string()); + } + + let numerator_1 = BigInt::from(liquidity) << 96; + let numerator_2 = &sqrt_ratio_b_x96 - &sqrt_ratio_a_x96; + + if round_up { + // For rounding up: ceil(ceil(numerator_1 * numerator_2 / sqrt_ratio_b_x96) / + // sqrt_ratio_a_x96) + let temp = + (&numerator_1 * &numerator_2 + &sqrt_ratio_b_x96 - BigInt::from(1)) / &sqrt_ratio_b_x96; + Ok((&temp + &sqrt_ratio_a_x96 - BigInt::from(1)) / sqrt_ratio_a_x96) + } else { + // For rounding down: floor(floor(numerator_1 * numerator_2 / sqrt_ratio_b_x96) / + // sqrt_ratio_a_x96) + Ok((&numerator_1 * &numerator_2) / &sqrt_ratio_b_x96 / sqrt_ratio_a_x96) + } +} + +const Q96: u128 = 1 << 96; + +/// Helper that gets signed token1 delta +/// +/// ## Arguments +/// +/// * `sqrt_ratio_a_x96`: A sqrt price +/// * `sqrt_ratio_b_x96`: Another sqrt price +/// * `liquidity`: The change in liquidity for which to compute the amount1 delta +/// +/// ## Returns +/// +/// Amount of token1 corresponding to the passed liquidityDelta between the two prices +fn get_amount_1_delta_signed( + sqrt_ratio_a_x96: BigInt, + sqrt_ratio_b_x96: BigInt, + liquidity: i128, +) -> Result { + let sign = !liquidity.is_negative(); + + // Create mask for negative numbers + let mask = if sign { 0u128 } else { u128::MAX }; + + // Get absolute value of liquidity using XOR and addition + let liquidity = mask ^ mask.wrapping_add_signed(liquidity); + + // Convert mask to BigInt (all 1s or all 0s) + let mask = if sign { BigInt::from(0) } else { -BigInt::from(1) }; + + let amount_1 = get_amount_1_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, liquidity, sign)?; + + // Apply the mask using XOR and subtraction to restore the sign + Ok((amount_1 ^ &mask) - mask) +} + +/// Gets the amount1 delta between two prices +/// +/// Calculates liquidity * (sqrt(upper) - sqrt(lower)) +/// +/// ## Arguments +/// +/// * `sqrt_ratio_a_x96`: A sqrt price assumed to be lower otherwise swapped +/// * `sqrt_ratio_b_x96`: Another sqrt price +/// * `liquidity`: The amount of usable liquidity +/// * `round_up`: Whether to round the amount up, or down +/// +/// ## Returns +/// +/// Amount of token1 required to cover a position of size liquidity between the two passed prices +fn get_amount_1_delta( + sqrt_ratio_a_x96: BigInt, + sqrt_ratio_b_x96: BigInt, + liquidity: u128, + round_up: bool, +) -> Result { + let (sqrt_ratio_a_x96, sqrt_ratio_b_x96) = if sqrt_ratio_a_x96 < sqrt_ratio_b_x96 { + (sqrt_ratio_a_x96, sqrt_ratio_b_x96) + } else { + (sqrt_ratio_b_x96, sqrt_ratio_a_x96) + }; + + let numerator = &sqrt_ratio_b_x96 - &sqrt_ratio_a_x96; + let denominator = BigInt::from(Q96); + + let liquidity = BigInt::from(liquidity); + let amount_1 = &liquidity * &numerator / &denominator; + + // Calculate if there's a remainder + let remainder = (&liquidity * &numerator) % &denominator; + let carry = remainder > BigInt::from(0) && round_up; + + Ok(if carry { amount_1 + 1 } else { amount_1 }) +} + +#[cfg(test)] +mod tests { + use super::*; + use num_bigint::BigInt; + use rstest::rstest; + use std::str::FromStr; + const MIN_TICK: i32 = -887272; + + #[test] + fn test_get_sqrt_ratio_at_tick_is_valid_min_tick() { + assert_eq!(get_sqrt_ratio_at_tick(MIN_TICK).unwrap(), BigInt::from(4295128739_u128)); + } + + #[test] + fn test_get_sqrt_ratio_at_tick_is_valid_min_tick_add_one() { + assert_eq!(get_sqrt_ratio_at_tick(MIN_TICK + 1).unwrap(), BigInt::from(4295343490_u128)); + } + + #[test] + fn test_get_sqrt_ratio_at_tick_is_valid_max_tick() { + assert_eq!( + get_sqrt_ratio_at_tick(MAX_TICK).unwrap(), + BigInt::from_str("1461446703485210103287273052203988822378723970342").unwrap() + ); + } + + #[test] + fn test_get_sqrt_ratio_at_tick_is_valid_max_tick_sub_one() { + assert_eq!( + get_sqrt_ratio_at_tick(MAX_TICK - 1).unwrap(), + BigInt::from_str("1461373636630004318706518188784493106690254656249").unwrap() + ); + } + + #[test] + fn test_get_sqrt_ratio_at_tick_is_valid_tick_zero() { + assert_eq!( + get_sqrt_ratio_at_tick(0).unwrap(), + BigInt::from_str("79228162514264337593543950336").unwrap() + ); + } + + #[test] + fn test_get_sqrt_ratio_at_tick_is_less_than_js_impl_min_tick() { + let js_min_sqrt_price = BigInt::from(6085630636u64); + let sol_min_sqrt_price = get_sqrt_ratio_at_tick(MIN_TICK).unwrap(); + assert!(sol_min_sqrt_price < js_min_sqrt_price); + } + + #[test] + fn test_get_sqrt_ratio_at_tick_is_greater_than_js_impl_max_tick() { + let js_max_sqrt_price = + BigInt::from_str("1033437718471923706666374484006904511252097097914").unwrap(); + let sol_max_sqrt_price = get_sqrt_ratio_at_tick(MAX_TICK).unwrap(); + assert!(sol_max_sqrt_price > js_max_sqrt_price); + } + #[test] + fn test_get_amount_0_delta_returns_0_if_liquidity_is_0() { + let result = get_amount_0_delta( + BigInt::from_str("79228162514264337593543950336").unwrap(), + BigInt::from_str("112045541949572279837463876454").unwrap(), + 0, + true, + ) + .unwrap(); + assert_eq!(result, BigInt::from(0)); + } + + #[test] + fn test_get_amount_0_delta_returns_0_if_prices_are_equal() { + let result = get_amount_0_delta( + BigInt::from_str("79228162514264337593543950336").unwrap(), + BigInt::from_str("79228162514264337593543950336").unwrap(), + 0, + true, + ) + .unwrap(); + assert_eq!(result, BigInt::from(0)); + } + + #[test] + fn test_get_amount_0_delta_reverts_if_price_is_zero() { + let result = get_amount_0_delta(BigInt::from(0), BigInt::from(1), 1, true); + assert!(result.is_err()); + } + + #[test] + fn test_get_amount_0_delta_1_amount_1_for_price_of_1_to_1_21() { + let sqrt_price_1_1 = BigInt::from_str("79228162514264337593543950336").unwrap(); + let sqrt_price_121_100 = BigInt::from_str("87150978765690771352898345369").unwrap(); + + let amount_0 = get_amount_0_delta( + sqrt_price_1_1.clone(), + sqrt_price_121_100.clone(), + 1_000_000_000_000_000_000, + true, + ) + .unwrap(); + assert_eq!(amount_0, BigInt::from(90_909_090_909_090_910u128)); + + let amount_0_rounded_down = get_amount_0_delta( + sqrt_price_1_1, + sqrt_price_121_100, + 1_000_000_000_000_000_000, + false, + ) + .unwrap(); + assert_eq!(amount_0_rounded_down, amount_0 - 1); + } + + #[test] + fn test_get_amount_0_delta_works_for_prices_that_overflow() { + let sqrt_p_1 = BigInt::from_str("2787593149816327892691964784081045188247552").unwrap(); + let sqrt_p_2 = BigInt::from_str("22300745198530623141535718272648361505980416").unwrap(); + + let amount_0_up = + get_amount_0_delta(sqrt_p_1.clone(), sqrt_p_2.clone(), 1_000_000_000_000_000_000, true) + .unwrap(); + let amount_0_down = + get_amount_0_delta(sqrt_p_1, sqrt_p_2, 1_000_000_000_000_000_000, false).unwrap(); + + assert_eq!(amount_0_up, amount_0_down + 1); + } + + #[rstest] + #[case( + BigInt::from(79228162514264337593543950336_i128), + -887270, + 887270, + 2779504125, + BigInt::from(2779504125_u128), + BigInt::from(2779504125_u128) + )] + #[case( + BigInt::from(17189630842187678489986852982138_i128), + -138200, + 107600, + -79381057257465377800177, + BigInt::from(-445577797388351_i128), + BigInt::from(-17222724212376326765220041_i128) + )] + fn test_calculate_token_amounts( + #[case] current_sqrtprice: BigInt, + #[case] tick_lower: i32, + #[case] tick_upper: i32, + #[case] liquidity_delta: i128, + #[case] expected_amount0: BigInt, + #[case] expected_amount1: BigInt, + ) { + let (amount0, amount1) = + calculate_token_amounts(current_sqrtprice, tick_lower, tick_upper, liquidity_delta) + .unwrap(); + + assert_eq!(amount0, expected_amount0); + assert_eq!(amount1, expected_amount1); + } +} diff --git a/substreams/ethereum-uniswap-v4/src/pb/mod.rs b/substreams/ethereum-uniswap-v4/src/pb/mod.rs new file mode 100644 index 0000000..ce1c554 --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/pb/mod.rs @@ -0,0 +1,8 @@ +// @generated +pub mod uniswap { + // @@protoc_insertion_point(attribute:uniswap.v4) + pub mod v4 { + include!("uniswap.v4.rs"); + // @@protoc_insertion_point(uniswap.v4) + } +} diff --git a/substreams/ethereum-uniswap-v4/src/pb/uniswap.v4.rs b/substreams/ethereum-uniswap-v4/src/pb/uniswap.v4.rs new file mode 100644 index 0000000..cbb729a --- /dev/null +++ b/substreams/ethereum-uniswap-v4/src/pb/uniswap.v4.rs @@ -0,0 +1,228 @@ +// @generated +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Pool { + /// // The pool address. + #[prost(bytes="vec", tag="1")] + pub id: ::prost::alloc::vec::Vec, + /// The token0 address. + #[prost(bytes="vec", tag="2")] + pub currency0: ::prost::alloc::vec::Vec, + /// The token1 address. + #[prost(bytes="vec", tag="3")] + pub currency1: ::prost::alloc::vec::Vec, + /// The transaction where the pool was created. + #[prost(bytes="vec", tag="4")] + pub created_tx_hash: ::prost::alloc::vec::Vec, +} +/// A struct describing a transaction. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Transaction { + /// The transaction hash. + #[prost(bytes="vec", tag="1")] + pub hash: ::prost::alloc::vec::Vec, + /// The sender of the transaction. + #[prost(bytes="vec", tag="2")] + pub from: ::prost::alloc::vec::Vec, + /// The receiver of the transaction. + #[prost(bytes="vec", tag="3")] + pub to: ::prost::alloc::vec::Vec, + /// The transactions index within the block. + #[prost(uint64, tag="4")] + pub index: u64, +} +/// A change to a pool's tick. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TickDelta { + /// The address of the pool. + #[prost(bytes="vec", tag="1")] + pub pool_address: ::prost::alloc::vec::Vec, + /// The index of the tick. + #[prost(int32, tag="2")] + pub tick_index: i32, + /// The liquidity net delta of this tick. Bigint encoded as signed little endian bytes. + #[prost(bytes="vec", tag="3")] + pub liquidity_net_delta: ::prost::alloc::vec::Vec, + /// Used to determine the order of the balance changes. Necessary for the balance store. + #[prost(uint64, tag="4")] + pub ordinal: u64, + #[prost(message, optional, tag="5")] + pub transaction: ::core::option::Option, +} +/// A group of TickDelta +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TickDeltas { + #[prost(message, repeated, tag="1")] + pub deltas: ::prost::alloc::vec::Vec, +} +/// A change to a pool's liquidity. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LiquidityChange { + /// The address of the pool. + #[prost(bytes="vec", tag="1")] + pub pool_address: ::prost::alloc::vec::Vec, + /// The liquidity changed amount. Bigint encoded as signed little endian bytes. + #[prost(bytes="vec", tag="2")] + pub value: ::prost::alloc::vec::Vec, + /// The type of update, can be absolute or delta. + #[prost(enumeration="LiquidityChangeType", tag="3")] + pub change_type: i32, + /// Used to determine the order of the balance changes. Necessary for the balance store. + #[prost(uint64, tag="4")] + pub ordinal: u64, + #[prost(message, optional, tag="5")] + pub transaction: ::core::option::Option, +} +/// A group of LiquidityChange +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LiquidityChanges { + #[prost(message, repeated, tag="1")] + pub changes: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Events { + #[prost(message, repeated, tag="3")] + pub pool_events: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `Events`. +pub mod events { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct PoolEvent { + #[prost(uint64, tag="100")] + pub log_ordinal: u64, + /// Changed from pool_address to pool_id as V4 uses PoolId + #[prost(string, tag="102")] + pub pool_id: ::prost::alloc::string::String, + /// Changed from token0 to currency0 + #[prost(string, tag="103")] + pub currency0: ::prost::alloc::string::String, + /// Changed from token1 to currency1 + #[prost(string, tag="104")] + pub currency1: ::prost::alloc::string::String, + #[prost(message, optional, tag="105")] + pub transaction: ::core::option::Option, + #[prost(oneof="pool_event::Type", tags="1, 2, 3, 4, 5")] + pub r#type: ::core::option::Option, + } + /// Nested message and enum types in `PoolEvent`. + pub mod pool_event { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Initialize { + #[prost(string, tag="1")] + pub sqrt_price_x96: ::prost::alloc::string::String, + #[prost(int32, tag="2")] + pub tick: i32, + #[prost(uint32, tag="3")] + pub fee: u32, + #[prost(int32, tag="4")] + pub tick_spacing: i32, + /// Address of the hooks contract + #[prost(string, tag="5")] + pub hooks: ::prost::alloc::string::String, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct ModifyLiquidity { + #[prost(string, tag="1")] + pub sender: ::prost::alloc::string::String, + #[prost(int32, tag="2")] + pub tick_lower: i32, + #[prost(int32, tag="3")] + pub tick_upper: i32, + /// Changed to support signed integers + #[prost(string, tag="4")] + pub liquidity_delta: ::prost::alloc::string::String, + /// Added salt field + #[prost(string, tag="5")] + pub salt: ::prost::alloc::string::String, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Swap { + #[prost(string, tag="1")] + pub sender: ::prost::alloc::string::String, + /// Signed int128 + #[prost(string, tag="2")] + pub amount0: ::prost::alloc::string::String, + /// Signed int128 + #[prost(string, tag="3")] + pub amount1: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub sqrt_price_x96: ::prost::alloc::string::String, + #[prost(string, tag="5")] + pub liquidity: ::prost::alloc::string::String, + #[prost(int32, tag="6")] + pub tick: i32, + /// Added fee field + #[prost(uint32, tag="7")] + pub fee: u32, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Donate { + #[prost(string, tag="1")] + pub sender: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub amount1: ::prost::alloc::string::String, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct ProtocolFeeUpdated { + #[prost(string, tag="1")] + pub pool_id: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub protocol_fee: u32, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Type { + #[prost(message, tag="1")] + Initialize(Initialize), + #[prost(message, tag="2")] + ModifyLiquidity(ModifyLiquidity), + #[prost(message, tag="3")] + Swap(Swap), + #[prost(message, tag="4")] + Donate(Donate), + #[prost(message, tag="5")] + ProtocolFeeUpdated(ProtocolFeeUpdated), + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum LiquidityChangeType { + Delta = 0, + Absolute = 1, +} +impl LiquidityChangeType { + /// 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 { + LiquidityChangeType::Delta => "DELTA", + LiquidityChangeType::Absolute => "ABSOLUTE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "DELTA" => Some(Self::Delta), + "ABSOLUTE" => Some(Self::Absolute), + _ => None, + } + } +} +// @@protoc_insertion_point(module) diff --git a/substreams/rust-toolchain.toml b/substreams/rust-toolchain.toml new file mode 100644 index 0000000..15b4897 --- /dev/null +++ b/substreams/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.83.0" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] diff --git a/substreams/rustfmt.toml b/substreams/rustfmt.toml index 1c06912..42cdd4f 100644 --- a/substreams/rustfmt.toml +++ b/substreams/rustfmt.toml @@ -18,4 +18,5 @@ ignore = [ "ethereum-uniswap-v2/src/abi", "ethereum-uniswap-v3/src/abi", "ethereum-uniswap-v3-logs-only/src/abi", + "ethereum-uniswap-v4/src/abi", ]