From e4609bed0bb3c73a13864a951b8cb64c523bc148 Mon Sep 17 00:00:00 2001 From: die-herdplatte <173669014+die-herdplatte@users.noreply.github.com> Date: Thu, 27 Mar 2025 18:29:39 +0700 Subject: [PATCH] Ekubo Integration (#172) * fix: Implement ethereum-ekubo * fix: Remove unnecessary store * fix: Correct balance accounting * Adjust deltas by fee at PositionUpdated event * Add partial Ekubo integration * Generalize Hexable * Native Ekubo integration * cargo fmt & clippy --------- Co-authored-by: kayibal Co-authored-by: Zizou <111426680+zizou0x@users.noreply.github.com> --- substreams/Cargo.lock | 40 +- substreams/Cargo.toml | 1 + .../crates/substreams-helper/src/hex.rs | 15 +- substreams/ethereum-ekubo/.cargo/config.toml | 2 + substreams/ethereum-ekubo/Cargo.toml | 26 + substreams/ethereum-ekubo/abi/core.json | 1266 ++++++ substreams/ethereum-ekubo/buf.gen.yaml | 12 + substreams/ethereum-ekubo/build.rs | 49 + .../integration_test.tycho.yaml | 23 + substreams/ethereum-ekubo/proto/ekubo.proto | 109 + substreams/ethereum-ekubo/src/abi/core.rs | 3896 +++++++++++++++++ substreams/ethereum-ekubo/src/abi/mod.rs | 2 + .../ethereum-ekubo/src/deployment_config.rs | 9 + substreams/ethereum-ekubo/src/lib.rs | 6 + .../src/modules/1_map_events.rs | 129 + .../src/modules/2_map_components.rs | 135 + .../src/modules/2_map_tick_changes.rs | 63 + .../src/modules/2_store_active_ticks.rs | 30 + .../src/modules/3_map_liquidity_changes.rs | 69 + .../src/modules/3_store_pool_details.rs | 31 + .../src/modules/3_store_tick_liquidities.rs | 21 + .../src/modules/4_map_balance_changes.rs | 127 + .../src/modules/4_store_liquidities.rs | 30 + .../src/modules/5_store_balance_changes.rs | 7 + .../src/modules/6_map_protocol_changes.rs | 203 + substreams/ethereum-ekubo/src/modules/mod.rs | 48 + substreams/ethereum-ekubo/src/pb/ekubo.rs | 256 ++ substreams/ethereum-ekubo/src/pb/mod.rs | 6 + substreams/ethereum-ekubo/src/pool_config.rs | 15 + substreams/ethereum-ekubo/src/sqrt_ratio.rs | 11 + substreams/ethereum-ekubo/substreams.yaml | 122 + 31 files changed, 6735 insertions(+), 24 deletions(-) create mode 100644 substreams/ethereum-ekubo/.cargo/config.toml create mode 100644 substreams/ethereum-ekubo/Cargo.toml create mode 100644 substreams/ethereum-ekubo/abi/core.json create mode 100644 substreams/ethereum-ekubo/buf.gen.yaml create mode 100644 substreams/ethereum-ekubo/build.rs create mode 100644 substreams/ethereum-ekubo/integration_test.tycho.yaml create mode 100644 substreams/ethereum-ekubo/proto/ekubo.proto create mode 100644 substreams/ethereum-ekubo/src/abi/core.rs create mode 100644 substreams/ethereum-ekubo/src/abi/mod.rs create mode 100644 substreams/ethereum-ekubo/src/deployment_config.rs create mode 100644 substreams/ethereum-ekubo/src/lib.rs create mode 100644 substreams/ethereum-ekubo/src/modules/1_map_events.rs create mode 100644 substreams/ethereum-ekubo/src/modules/2_map_components.rs create mode 100644 substreams/ethereum-ekubo/src/modules/2_map_tick_changes.rs create mode 100644 substreams/ethereum-ekubo/src/modules/2_store_active_ticks.rs create mode 100644 substreams/ethereum-ekubo/src/modules/3_map_liquidity_changes.rs create mode 100644 substreams/ethereum-ekubo/src/modules/3_store_pool_details.rs create mode 100644 substreams/ethereum-ekubo/src/modules/3_store_tick_liquidities.rs create mode 100644 substreams/ethereum-ekubo/src/modules/4_map_balance_changes.rs create mode 100644 substreams/ethereum-ekubo/src/modules/4_store_liquidities.rs create mode 100644 substreams/ethereum-ekubo/src/modules/5_store_balance_changes.rs create mode 100644 substreams/ethereum-ekubo/src/modules/6_map_protocol_changes.rs create mode 100644 substreams/ethereum-ekubo/src/modules/mod.rs create mode 100644 substreams/ethereum-ekubo/src/pb/ekubo.rs create mode 100644 substreams/ethereum-ekubo/src/pb/mod.rs create mode 100644 substreams/ethereum-ekubo/src/pool_config.rs create mode 100644 substreams/ethereum-ekubo/src/sqrt_ratio.rs create mode 100644 substreams/ethereum-ekubo/substreams.yaml diff --git a/substreams/Cargo.lock b/substreams/Cargo.lock index 4fb9449..0cce934 100644 --- a/substreams/Cargo.lock +++ b/substreams/Cargo.lock @@ -260,6 +260,24 @@ dependencies = [ "tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)", ] +[[package]] +name = "ethereum-ekubo" +version = "0.1.0" +dependencies = [ + "anyhow", + "ethabi 18.0.0", + "hex", + "itertools 0.10.5", + "num-bigint", + "prost 0.11.9", + "serde", + "serde_qs", + "substreams", + "substreams-ethereum", + "substreams-helper 0.0.2", + "tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=3c08359)", +] + [[package]] name = "ethereum-pancakeswap-v3" version = "0.1.2" @@ -499,7 +517,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] [[package]] @@ -641,7 +659,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] [[package]] @@ -838,7 +856,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] [[package]] @@ -1002,7 +1020,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] [[package]] @@ -1139,7 +1157,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.96", + "syn 2.0.100", "unicode-ident", ] @@ -1210,7 +1228,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] [[package]] @@ -1477,9 +1495,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1531,7 +1549,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] [[package]] @@ -1542,7 +1560,7 @@ checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] [[package]] @@ -1803,5 +1821,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.96", + "syn 2.0.100", ] diff --git a/substreams/Cargo.toml b/substreams/Cargo.toml index 583e627..39918e5 100644 --- a/substreams/Cargo.toml +++ b/substreams/Cargo.toml @@ -14,6 +14,7 @@ members = [ "ethereum-template-factory", "ethereum-template-singleton", "ethereum-uniswap-v4", + "ethereum-ekubo", ] resolver = "2" diff --git a/substreams/crates/substreams-helper/src/hex.rs b/substreams/crates/substreams-helper/src/hex.rs index c056ba7..26ed473 100644 --- a/substreams/crates/substreams-helper/src/hex.rs +++ b/substreams/crates/substreams-helper/src/hex.rs @@ -1,20 +1,9 @@ -use ethabi::ethereum_types::Address; -use substreams::Hex; - pub trait Hexable { fn to_hex(&self) -> String; } -impl Hexable for Vec { +impl> Hexable for T { fn to_hex(&self) -> String { - let mut str = Hex::encode(self); - str.insert_str(0, "0x"); - str - } -} - -impl Hexable for Address { - fn to_hex(&self) -> String { - self.as_bytes().to_vec().to_hex() + format!("0x{}", hex::encode(self)) } } diff --git a/substreams/ethereum-ekubo/.cargo/config.toml b/substreams/ethereum-ekubo/.cargo/config.toml new file mode 100644 index 0000000..f4e8c00 --- /dev/null +++ b/substreams/ethereum-ekubo/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-unknown-unknown" diff --git a/substreams/ethereum-ekubo/Cargo.toml b/substreams/ethereum-ekubo/Cargo.toml new file mode 100644 index 0000000..c5a917f --- /dev/null +++ b/substreams/ethereum-ekubo/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ethereum-ekubo" +version = "0.1.0" +edition = "2021" + +[lib] +name = "ethereum_ekubo" +crate-type = ["cdylib"] + +[dependencies] +substreams = "0.5.22" +substreams-ethereum = "0.9.9" +substreams-helper = { path = "../crates/substreams-helper" } # TODO Update to git once pushed +prost = "0.11" +tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "3c08359" } +anyhow = "1.0.95" +ethabi = "18.0.0" +num-bigint = "0.4.6" +hex = { version = "0.4", features = ["serde"] } +itertools = "0.10.5" +serde = "1.0.217" +serde_qs = "0.13.0" + +[build-dependencies] +anyhow = "1" +substreams-ethereum = "0.9.9" diff --git a/substreams/ethereum-ekubo/abi/core.json b/substreams/ethereum-ekubo/abi/core.json new file mode 100644 index 0000000..c3d4300 --- /dev/null +++ b/substreams/ethereum-ekubo/abi/core.json @@ -0,0 +1,1266 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "function", + "name": "accumulateAsFees", + "inputs": [ + { + "name": "poolKey", + "type": "tuple", + "internalType": "struct PoolKey", + "components": [ + { + "name": "token0", + "type": "address", + "internalType": "address" + }, + { + "name": "token1", + "type": "address", + "internalType": "address" + }, + { + "name": "config", + "type": "bytes32", + "internalType": "Config" + } + ] + }, + { + "name": "amount0", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "amount1", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "cancelOwnershipHandover", + "inputs": [], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "collectFees", + "inputs": [ + { + "name": "poolKey", + "type": "tuple", + "internalType": "struct PoolKey", + "components": [ + { + "name": "token0", + "type": "address", + "internalType": "address" + }, + { + "name": "token1", + "type": "address", + "internalType": "address" + }, + { + "name": "config", + "type": "bytes32", + "internalType": "Config" + } + ] + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "bounds", + "type": "tuple", + "internalType": "struct Bounds", + "components": [ + { + "name": "lower", + "type": "int32", + "internalType": "int32" + }, + { + "name": "upper", + "type": "int32", + "internalType": "int32" + } + ] + } + ], + "outputs": [ + { + "name": "amount0", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "amount1", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "completeOwnershipHandover", + "inputs": [ + { + "name": "pendingOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "forward", + "inputs": [ + { + "name": "to", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getPoolFeesPerLiquidityInside", + "inputs": [ + { + "name": "poolKey", + "type": "tuple", + "internalType": "struct PoolKey", + "components": [ + { + "name": "token0", + "type": "address", + "internalType": "address" + }, + { + "name": "token1", + "type": "address", + "internalType": "address" + }, + { + "name": "config", + "type": "bytes32", + "internalType": "Config" + } + ] + }, + { + "name": "bounds", + "type": "tuple", + "internalType": "struct Bounds", + "components": [ + { + "name": "lower", + "type": "int32", + "internalType": "int32" + }, + { + "name": "upper", + "type": "int32", + "internalType": "int32" + } + ] + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct FeesPerLiquidity", + "components": [ + { + "name": "value0", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "value1", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initializePool", + "inputs": [ + { + "name": "poolKey", + "type": "tuple", + "internalType": "struct PoolKey", + "components": [ + { + "name": "token0", + "type": "address", + "internalType": "address" + }, + { + "name": "token1", + "type": "address", + "internalType": "address" + }, + { + "name": "config", + "type": "bytes32", + "internalType": "Config" + } + ] + }, + { + "name": "tick", + "type": "int32", + "internalType": "int32" + } + ], + "outputs": [ + { + "name": "sqrtRatio", + "type": "uint96", + "internalType": "SqrtRatio" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "load", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "amount", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "lock", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "nextInitializedTick", + "inputs": [ + { + "name": "poolId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "fromTick", + "type": "int32", + "internalType": "int32" + }, + { + "name": "tickSpacing", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "skipAhead", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "tick", + "type": "int32", + "internalType": "int32" + }, + { + "name": "isInitialized", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "result", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ownershipHandoverExpiresAt", + "inputs": [ + { + "name": "pendingOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "result", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pay", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "payment", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "prevInitializedTick", + "inputs": [ + { + "name": "poolId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "fromTick", + "type": "int32", + "internalType": "int32" + }, + { + "name": "tickSpacing", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "skipAhead", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "tick", + "type": "int32", + "internalType": "int32" + }, + { + "name": "isInitialized", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registerExtension", + "inputs": [ + { + "name": "expectedCallPoints", + "type": "tuple", + "internalType": "struct CallPoints", + "components": [ + { + "name": "beforeInitializePool", + "type": "bool", + "internalType": "bool" + }, + { + "name": "afterInitializePool", + "type": "bool", + "internalType": "bool" + }, + { + "name": "beforeSwap", + "type": "bool", + "internalType": "bool" + }, + { + "name": "afterSwap", + "type": "bool", + "internalType": "bool" + }, + { + "name": "beforeUpdatePosition", + "type": "bool", + "internalType": "bool" + }, + { + "name": "afterUpdatePosition", + "type": "bool", + "internalType": "bool" + }, + { + "name": "beforeCollectFees", + "type": "bool", + "internalType": "bool" + }, + { + "name": "afterCollectFees", + "type": "bool", + "internalType": "bool" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "requestOwnershipHandover", + "inputs": [], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "save", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "amount", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "sload", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "swap", + "inputs": [ + { + "name": "poolKey", + "type": "tuple", + "internalType": "struct PoolKey", + "components": [ + { + "name": "token0", + "type": "address", + "internalType": "address" + }, + { + "name": "token1", + "type": "address", + "internalType": "address" + }, + { + "name": "config", + "type": "bytes32", + "internalType": "Config" + } + ] + }, + { + "name": "params", + "type": "tuple", + "internalType": "struct SwapParameters", + "components": [ + { + "name": "amount", + "type": "int128", + "internalType": "int128" + }, + { + "name": "isToken1", + "type": "bool", + "internalType": "bool" + }, + { + "name": "sqrtRatioLimit", + "type": "uint96", + "internalType": "SqrtRatio" + }, + { + "name": "skipAhead", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "outputs": [ + { + "name": "delta0", + "type": "int128", + "internalType": "int128" + }, + { + "name": "delta1", + "type": "int128", + "internalType": "int128" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "tload", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "updatePosition", + "inputs": [ + { + "name": "poolKey", + "type": "tuple", + "internalType": "struct PoolKey", + "components": [ + { + "name": "token0", + "type": "address", + "internalType": "address" + }, + { + "name": "token1", + "type": "address", + "internalType": "address" + }, + { + "name": "config", + "type": "bytes32", + "internalType": "Config" + } + ] + }, + { + "name": "params", + "type": "tuple", + "internalType": "struct UpdatePositionParameters", + "components": [ + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "bounds", + "type": "tuple", + "internalType": "struct Bounds", + "components": [ + { + "name": "lower", + "type": "int32", + "internalType": "int32" + }, + { + "name": "upper", + "type": "int32", + "internalType": "int32" + } + ] + }, + { + "name": "liquidityDelta", + "type": "int128", + "internalType": "int128" + } + ] + } + ], + "outputs": [ + { + "name": "delta0", + "type": "int128", + "internalType": "int128" + }, + { + "name": "delta1", + "type": "int128", + "internalType": "int128" + } + ], + "stateMutability": "payable" + }, + { + "type": "function", + "name": "withdraw", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "withdrawProtocolFees", + "inputs": [ + { + "name": "recipient", + "type": "address", + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "ExtensionRegistered", + "inputs": [ + { + "name": "extension", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "FeesAccumulated", + "inputs": [ + { + "name": "poolId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "amount0", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + }, + { + "name": "amount1", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "LoadedBalance", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "amount", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipHandoverCanceled", + "inputs": [ + { + "name": "pendingOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipHandoverRequested", + "inputs": [ + { + "name": "pendingOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "oldOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PoolInitialized", + "inputs": [ + { + "name": "poolId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "poolKey", + "type": "tuple", + "indexed": false, + "internalType": "struct PoolKey", + "components": [ + { + "name": "token0", + "type": "address", + "internalType": "address" + }, + { + "name": "token1", + "type": "address", + "internalType": "address" + }, + { + "name": "config", + "type": "bytes32", + "internalType": "Config" + } + ] + }, + { + "name": "tick", + "type": "int32", + "indexed": false, + "internalType": "int32" + }, + { + "name": "sqrtRatio", + "type": "uint96", + "indexed": false, + "internalType": "SqrtRatio" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PositionFeesCollected", + "inputs": [ + { + "name": "poolId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "positionKey", + "type": "tuple", + "indexed": false, + "internalType": "struct PositionKey", + "components": [ + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "owner", + "type": "address", + "internalType": "address" + }, + { + "name": "bounds", + "type": "tuple", + "internalType": "struct Bounds", + "components": [ + { + "name": "lower", + "type": "int32", + "internalType": "int32" + }, + { + "name": "upper", + "type": "int32", + "internalType": "int32" + } + ] + } + ] + }, + { + "name": "amount0", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + }, + { + "name": "amount1", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PositionUpdated", + "inputs": [ + { + "name": "locker", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "poolId", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "params", + "type": "tuple", + "indexed": false, + "internalType": "struct UpdatePositionParameters", + "components": [ + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "bounds", + "type": "tuple", + "internalType": "struct Bounds", + "components": [ + { + "name": "lower", + "type": "int32", + "internalType": "int32" + }, + { + "name": "upper", + "type": "int32", + "internalType": "int32" + } + ] + }, + { + "name": "liquidityDelta", + "type": "int128", + "internalType": "int128" + } + ] + }, + { + "name": "delta0", + "type": "int128", + "indexed": false, + "internalType": "int128" + }, + { + "name": "delta1", + "type": "int128", + "indexed": false, + "internalType": "int128" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ProtocolFeesWithdrawn", + "inputs": [ + { + "name": "recipient", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "SavedBalance", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "token", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "amount", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AlreadyInitialized", + "inputs": [] + }, + { + "type": "error", + "name": "Amount0DeltaOverflow", + "inputs": [] + }, + { + "type": "error", + "name": "Amount1DeltaOverflow", + "inputs": [] + }, + { + "type": "error", + "name": "AmountBeforeFeeOverflow", + "inputs": [] + }, + { + "type": "error", + "name": "BoundsOrder", + "inputs": [] + }, + { + "type": "error", + "name": "BoundsTickSpacing", + "inputs": [] + }, + { + "type": "error", + "name": "DebtsNotZeroed", + "inputs": [ + { + "name": "id", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ExtensionAlreadyRegistered", + "inputs": [] + }, + { + "type": "error", + "name": "ExtensionNotRegistered", + "inputs": [] + }, + { + "type": "error", + "name": "FailedRegisterInvalidCallPoints", + "inputs": [] + }, + { + "type": "error", + "name": "FullRangeOnlyPool", + "inputs": [] + }, + { + "type": "error", + "name": "InsufficientSavedBalance", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSqrtRatioLimit", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidTick", + "inputs": [ + { + "name": "tick", + "type": "int32", + "internalType": "int32" + } + ] + }, + { + "type": "error", + "name": "InvalidTickSpacing", + "inputs": [] + }, + { + "type": "error", + "name": "LockerOnly", + "inputs": [] + }, + { + "type": "error", + "name": "MinMaxBounds", + "inputs": [] + }, + { + "type": "error", + "name": "MustCollectFeesBeforeWithdrawingAllLiquidity", + "inputs": [] + }, + { + "type": "error", + "name": "NewOwnerIsZeroAddress", + "inputs": [] + }, + { + "type": "error", + "name": "NoHandoverRequest", + "inputs": [] + }, + { + "type": "error", + "name": "NoPaymentMade", + "inputs": [] + }, + { + "type": "error", + "name": "NotLocked", + "inputs": [] + }, + { + "type": "error", + "name": "PaymentOverflow", + "inputs": [] + }, + { + "type": "error", + "name": "PoolAlreadyInitialized", + "inputs": [] + }, + { + "type": "error", + "name": "PoolNotInitialized", + "inputs": [] + }, + { + "type": "error", + "name": "SqrtRatioLimitOutOfRange", + "inputs": [] + }, + { + "type": "error", + "name": "SqrtRatioLimitWrongDirection", + "inputs": [] + }, + { + "type": "error", + "name": "TokensMustBeSorted", + "inputs": [] + }, + { + "type": "error", + "name": "Unauthorized", + "inputs": [] + }, + { + "type": "error", + "name": "ZeroLiquidityNextSqrtRatioFromAmount0", + "inputs": [] + }, + { + "type": "error", + "name": "ZeroLiquidityNextSqrtRatioFromAmount1", + "inputs": [] + } +] diff --git a/substreams/ethereum-ekubo/buf.gen.yaml b/substreams/ethereum-ekubo/buf.gen.yaml new file mode 100644 index 0000000..d2e6544 --- /dev/null +++ b/substreams/ethereum-ekubo/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-ekubo/build.rs b/substreams/ethereum-ekubo/build.rs new file mode 100644 index 0000000..5e7bd34 --- /dev/null +++ b/substreams/ethereum-ekubo/build.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use std::{fs, io::Write}; +use substreams_ethereum::Abigen; + +fn main() -> Result<()> { + let abi_folder = "abi"; + let output_folder = "src/abi"; + + let abis = fs::read_dir(abi_folder)?; + + let mut files = abis.collect::, _>>()?; + + // Sort the files by their name + files.sort_by_key(|a| a.file_name()); + + let mut mod_rs_content = String::new(); + mod_rs_content.push_str("#![allow(clippy::all)]\n"); + + for file in files { + let file_name = file.file_name(); + let file_name = file_name.to_string_lossy(); + + if !file_name.ends_with(".json") { + continue; + } + + let contract_name = file_name.split('.').next().unwrap(); + + let input_path = format!("{}/{}", abi_folder, file_name); + let output_path = format!("{}/{}.rs", output_folder, contract_name); + + mod_rs_content.push_str(&format!("pub mod {};\n", contract_name)); + + if std::path::Path::new(&output_path).exists() { + continue; + } + + Abigen::new(contract_name, &input_path)? + .generate()? + .write_to_file(&output_path)?; + } + + let mod_rs_path = format!("{}/mod.rs", output_folder); + let mut mod_rs_file = fs::File::create(mod_rs_path)?; + + mod_rs_file.write_all(mod_rs_content.as_bytes())?; + + Ok(()) +} diff --git a/substreams/ethereum-ekubo/integration_test.tycho.yaml b/substreams/ethereum-ekubo/integration_test.tycho.yaml new file mode 100644 index 0000000..76187be --- /dev/null +++ b/substreams/ethereum-ekubo/integration_test.tycho.yaml @@ -0,0 +1,23 @@ +adapter_contract: "EkuboSwapAdapter" +adapter_build_signature: "constructor(address)" +adapter_build_args: "0x16e186ecdc94083fff53ef2a41d46b92a54f61e2" +skip_balance_check: true # This seems to fail because testing/src/runner:TestRunner.validate_state tries to interpret the component id as an Ethereum address? +protocol_type_names: + - "ekubo" +tests: + - name: test_pool_creation + start_block: 7811236 + stop_block: 7811283 + expected_components: + - id: "0xae76f216ce250b7b9a388d59cbbf92407d7ccee71a99b27ad521508b1c74681f" + tokens: + - "0x0000000000000000000000000000000000000000" + - "0xb1b388f2ef1bb1f7979f009381f797f94b90c094" + static_attributes: + token0: "0x0000000000000000000000000000000000000000" + token1: "0xb1b388f2ef1bb1f7979f009381f797f94b90c094" + fee: "0x00c49ba5e353f7ce" + tick_spacing: "0x0000175e" + extension: "0x0000000000000000000000000000000000000000" + creation_tx: "0x4b39d41ae3b823409ba6a92e2937f52f545acfd9cb7ecf7fb8eb7ea2e3b9985e" + skip_simulation: false diff --git a/substreams/ethereum-ekubo/proto/ekubo.proto b/substreams/ethereum-ekubo/proto/ekubo.proto new file mode 100644 index 0000000..45bb2b0 --- /dev/null +++ b/substreams/ethereum-ekubo/proto/ekubo.proto @@ -0,0 +1,109 @@ +syntax = "proto3"; + +package ekubo; + +// Copy of tycho.evm.v1.Transaction to be able to implement conversions to/from TransactionTrace +message Transaction { + bytes hash = 1; + bytes from = 2; + bytes to = 3; + uint64 index = 4; +} + +message TickDelta { + bytes pool_id = 1; // bytes32 + int32 tick_index = 2; + bytes liquidity_net_delta = 3; // int128 + uint64 ordinal = 4; + Transaction transaction = 5; +} + +message TickDeltas { + repeated TickDelta deltas = 1; +} + +enum LiquidityChangeType { + DELTA = 0; + ABSOLUTE = 1; +} + +message LiquidityChange { + bytes pool_id = 1; // bytes32 + bytes value = 2; // uint128 or int128, depending on change_type + LiquidityChangeType change_type = 3; + uint64 ordinal = 4; + Transaction transaction = 5; +} + +message LiquidityChanges { + repeated LiquidityChange changes = 1; +} + +message PoolDetails { + bytes token0 = 1; // address + bytes token1 = 2; // address + fixed64 fee = 3; +} + +message BlockTransactionEvents { + repeated TransactionEvents block_transaction_events = 1; + + message TransactionEvents { + Transaction transaction = 1; + repeated PoolLog pool_logs = 2; + + message PoolLog { + uint64 ordinal = 1; + bytes pool_id = 2; // bytes32 + + oneof event { + Swapped swapped = 3; + PositionUpdated position_updated = 4; + PositionFeesCollected position_fees_collected = 5; + PoolInitialized pool_initialized = 6; + FeesAccumulated fees_accumulated = 7; + } + + message Swapped { + bytes delta0 = 1; // int128 + bytes delta1 = 2; // int128 + bytes sqrt_ratio_after = 3; // uint192 + bytes liquidity_after = 4; // uint128 + sint32 tick_after = 5; // int32 + } + + message PositionUpdated { + sint32 lower = 1; // int32 + sint32 upper = 2; // int32 + bytes liquidity_delta = 3; // int128 + bytes delta0 = 4; // int128 + bytes delta1 = 5; // int128 + } + + message PositionFeesCollected { + bytes amount0 = 1; // uint128 + bytes amount1 = 2; // uint128 + } + + message PoolInitialized { + bytes token0 = 1; // address + bytes token1 = 2; // address + bytes config = 3; // bytes32 + sint32 tick = 4; // int32 + bytes sqrt_ratio = 5; // uint192 + Extension extension = 6; + + enum Extension { + EXTENSION_UNKNOWN = 0; + EXTENSION_BASE = 1; + EXTENSION_ORACLE = 2; + } + } + + message FeesAccumulated { + bytes amount0 = 1; // uint128 + bytes amount1 = 2; // uint128 + } + } + } +} diff --git a/substreams/ethereum-ekubo/src/abi/core.rs b/substreams/ethereum-ekubo/src/abi/core.rs new file mode 100644 index 0000000..67ee214 --- /dev/null +++ b/substreams/ethereum-ekubo/src/abi/core.rs @@ -0,0 +1,3896 @@ +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 AccumulateAsFees { + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl AccumulateAsFees { + const METHOD_ID: [u8; 4] = [233u8, 100u8, 4u8, 248u8]; + 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::FixedBytes(32usize), + ]), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_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 result = [0u8; 32]; + let v = tuple_elements[2usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + 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) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.1)), + ethabi::Token::FixedBytes(self.pool_key.2.as_ref().to_vec()), + ]), + 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(), + )), + ]); + 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 AccumulateAsFees { + const NAME: &'static str = "accumulateAsFees"; + 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 CancelOwnershipHandover {} + impl CancelOwnershipHandover { + const METHOD_ID: [u8; 4] = [84u8, 209u8, 241u8, 61u8]; + 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 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 CancelOwnershipHandover { + const NAME: &'static str = "cancelOwnershipHandover"; + 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 CollectFees { + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub salt: [u8; 32usize], + pub bounds: (substreams::scalar::BigInt, substreams::scalar::BigInt), + } + impl CollectFees { + const METHOD_ID: [u8; 4] = [100u8, 94u8, 201u8, 181u8]; + 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::FixedBytes(32usize), + ]), + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Int(32usize), + ethabi::ParamType::Int(32usize), + ]), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_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 result = [0u8; 32]; + let v = tuple_elements[2usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + 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 + }, + bounds: { + 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) + }, + ) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.1)), + ethabi::Token::FixedBytes(self.pool_key.2.as_ref().to_vec()), + ]), + ethabi::Token::FixedBytes(self.salt.as_ref().to_vec()), + ethabi::Token::Tuple(vec![ + { + let non_full_signed_bytes = self.bounds.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.bounds.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 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::Uint(128usize), ethabi::ParamType::Uint(128usize)], + 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_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]; + 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 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 CollectFees { + const NAME: &'static str = "collectFees"; + 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 CollectFees + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct CompleteOwnershipHandover { + pub pending_owner: Vec, + } + impl CompleteOwnershipHandover { + const METHOD_ID: [u8; 4] = [240u8, 78u8, 40u8, 62u8]; + 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 { + pending_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.pending_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 CompleteOwnershipHandover { + const NAME: &'static str = "completeOwnershipHandover"; + 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 Forward { + pub to: Vec, + } + impl Forward { + const METHOD_ID: [u8; 4] = [16u8, 30u8, 137u8, 82u8]; + 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 { + to: 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.to))]); + 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 Forward { + const NAME: &'static str = "forward"; + 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 GetPoolFeesPerLiquidityInside { + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub bounds: (substreams::scalar::BigInt, substreams::scalar::BigInt), + } + impl GetPoolFeesPerLiquidityInside { + const METHOD_ID: [u8; 4] = [5u8, 215u8, 230u8, 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::Tuple(vec![ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ]), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Int(32usize), + ethabi::ParamType::Int(32usize), + ]), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_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 result = [0u8; 32]; + let v = tuple_elements[2usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + bounds: { + 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) + }, + ) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.1)), + ethabi::Token::FixedBytes(self.pool_key.2.as_ref().to_vec()), + ]), + ethabi::Token::Tuple(vec![ + { + let non_full_signed_bytes = self.bounds.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.bounds.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 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::Tuple(vec![ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ])], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let tuple_elements = values + .pop() + .expect("one output data should have existed") + .into_tuple() + .expect(INTERNAL_ERR); + ( + { + let mut v = [0 as u8; 32]; + tuple_elements[0usize] + .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[1usize] + .clone() + .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<(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 GetPoolFeesPerLiquidityInside { + const NAME: &'static str = "getPoolFeesPerLiquidityInside"; + 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 GetPoolFeesPerLiquidityInside + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct InitializePool { + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub tick: substreams::scalar::BigInt, + } + impl InitializePool { + const METHOD_ID: [u8; 4] = [192u8, 83u8, 2u8, 68u8]; + 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::FixedBytes(32usize), + ]), + ethabi::ParamType::Int(32usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_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 result = [0u8; 32]; + let v = tuple_elements[2usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + 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) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.1)), + ethabi::Token::FixedBytes(self.pool_key.2.as_ref().to_vec()), + ]), + { + let non_full_signed_bytes = self.tick.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 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(96usize)], 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 InitializePool { + const NAME: &'static str = "initializePool"; + 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 InitializePool { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Load { + pub token: Vec, + pub salt: [u8; 32usize], + pub amount: substreams::scalar::BigInt, + } + impl Load { + const METHOD_ID: [u8; 4] = [88u8, 53u8, 42u8, 97u8]; + 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::FixedBytes(32usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + 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 + }, + 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.token)), + ethabi::Token::FixedBytes(self.salt.as_ref().to_vec()), + 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 Load { + const NAME: &'static str = "load"; + 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 Lock {} + impl Lock { + const METHOD_ID: [u8; 4] = [248u8, 61u8, 8u8, 186u8]; + 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 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 Lock { + const NAME: &'static str = "lock"; + 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 NextInitializedTick { + pub pool_id: [u8; 32usize], + pub from_tick: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + pub skip_ahead: substreams::scalar::BigInt, + } + impl NextInitializedTick { + const METHOD_ID: [u8; 4] = [102u8, 224u8, 100u8, 168u8]; + 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::Int(32usize), + ethabi::ParamType::Uint(32usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_id: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + from_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) + }, + tick_spacing: { + 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) + }, + skip_ahead: { + 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.pool_id.as_ref().to_vec()), + { + let non_full_signed_bytes = self.from_tick.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.tick_spacing.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.skip_ahead.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<(substreams::scalar::BigInt, bool), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result<(substreams::scalar::BigInt, bool), String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(32usize), ethabi::ParamType::Bool], + 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) + }, + values + .pop() + .expect(INTERNAL_ERR) + .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<(substreams::scalar::BigInt, bool)> { + 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 NextInitializedTick { + const NAME: &'static str = "nextInitializedTick"; + 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, bool)> + for NextInitializedTick + { + fn output(data: &[u8]) -> Result<(substreams::scalar::BigInt, bool), 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 OwnershipHandoverExpiresAt { + pub pending_owner: Vec, + } + impl OwnershipHandoverExpiresAt { + const METHOD_ID: [u8; 4] = [254u8, 232u8, 28u8, 244u8]; + 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 { + pending_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.pending_owner, + ))]); + 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 OwnershipHandoverExpiresAt { + const NAME: &'static str = "ownershipHandoverExpiresAt"; + 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 OwnershipHandoverExpiresAt + { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Pay { + pub token: Vec, + } + impl Pay { + const METHOD_ID: [u8; 4] = [12u8, 17u8, 222u8, 221u8]; + 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 { + token: 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.token))]); + 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(128usize)], 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 Pay { + const NAME: &'static str = "pay"; + 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 Pay { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct PrevInitializedTick { + pub pool_id: [u8; 32usize], + pub from_tick: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + pub skip_ahead: substreams::scalar::BigInt, + } + impl PrevInitializedTick { + const METHOD_ID: [u8; 4] = [14u8, 127u8, 38u8, 57u8]; + 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::Int(32usize), + ethabi::ParamType::Uint(32usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_id: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + from_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) + }, + tick_spacing: { + 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) + }, + skip_ahead: { + 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.pool_id.as_ref().to_vec()), + { + let non_full_signed_bytes = self.from_tick.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.tick_spacing.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.skip_ahead.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<(substreams::scalar::BigInt, bool), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result<(substreams::scalar::BigInt, bool), String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(32usize), ethabi::ParamType::Bool], + 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) + }, + values + .pop() + .expect(INTERNAL_ERR) + .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<(substreams::scalar::BigInt, bool)> { + 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 PrevInitializedTick { + const NAME: &'static str = "prevInitializedTick"; + 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, bool)> + for PrevInitializedTick + { + fn output(data: &[u8]) -> Result<(substreams::scalar::BigInt, bool), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RegisterExtension { + pub expected_call_points: (bool, bool, bool, bool, bool, bool, bool, bool), + } + impl RegisterExtension { + const METHOD_ID: [u8; 4] = [222u8, 111u8, 147u8, 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::Tuple(vec![ + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ])], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + expected_call_points: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[1usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[2usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[3usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[4usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[5usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[6usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[7usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + ) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Tuple(vec![ + ethabi::Token::Bool(self.expected_call_points.0.clone()), + ethabi::Token::Bool(self.expected_call_points.1.clone()), + ethabi::Token::Bool(self.expected_call_points.2.clone()), + ethabi::Token::Bool(self.expected_call_points.3.clone()), + ethabi::Token::Bool(self.expected_call_points.4.clone()), + ethabi::Token::Bool(self.expected_call_points.5.clone()), + ethabi::Token::Bool(self.expected_call_points.6.clone()), + ethabi::Token::Bool(self.expected_call_points.7.clone()), + ])]); + 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 RegisterExtension { + const NAME: &'static str = "registerExtension"; + 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 RenounceOwnership {} + impl RenounceOwnership { + const METHOD_ID: [u8; 4] = [113u8, 80u8, 24u8, 166u8]; + 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 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 RenounceOwnership { + const NAME: &'static str = "renounceOwnership"; + 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 RequestOwnershipHandover {} + impl RequestOwnershipHandover { + const METHOD_ID: [u8; 4] = [37u8, 105u8, 41u8, 98u8]; + 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 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 RequestOwnershipHandover { + const NAME: &'static str = "requestOwnershipHandover"; + 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 Save { + pub owner: Vec, + pub token: Vec, + pub salt: [u8; 32usize], + pub amount: substreams::scalar::BigInt, + } + impl Save { + const METHOD_ID: [u8; 4] = [133u8, 50u8, 209u8, 57u8]; + 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::FixedBytes(32usize), + ethabi::ParamType::Uint(128usize), + ], + 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(), + token: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + 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 + }, + 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.owner)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.token)), + ethabi::Token::FixedBytes(self.salt.as_ref().to_vec()), + 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 Save { + const NAME: &'static str = "save"; + 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 Sload { + pub slot: [u8; 32usize], + } + impl Sload { + const METHOD_ID: [u8; 4] = [244u8, 145u8, 10u8, 115u8]; + 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 Sload { + const NAME: &'static str = "sload"; + 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 Sload { + fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Swap { + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub params: ( + substreams::scalar::BigInt, + bool, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + } + impl Swap { + const METHOD_ID: [u8; 4] = [213u8, 86u8, 155u8, 177u8]; + 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::FixedBytes(32usize), + ]), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Bool, + ethabi::ParamType::Uint(96usize), + ethabi::ParamType::Uint(256usize), + ]), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_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 result = [0u8; 32]; + let v = tuple_elements[2usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + 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) + }, + tuple_elements[1usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + { + 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_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.pool_key.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.1)), + ethabi::Token::FixedBytes(self.pool_key.2.as_ref().to_vec()), + ]), + 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())) + }, + ethabi::Token::Bool(self.params.1.clone()), + 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::Uint(ethabi::Uint::from_big_endian( + match self.params.3.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<(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(128usize), ethabi::ParamType::Int(128usize)], + 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 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<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Swap + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Tload { + pub slot: [u8; 32usize], + } + impl Tload { + const METHOD_ID: [u8; 4] = [189u8, 46u8, 88u8, 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::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 Tload { + const NAME: &'static str = "tload"; + 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 Tload { + fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + 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 UpdatePosition { + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub params: ( + [u8; 32usize], + (substreams::scalar::BigInt, substreams::scalar::BigInt), + substreams::scalar::BigInt, + ), + } + impl UpdatePosition { + const METHOD_ID: [u8; 4] = [85u8, 244u8, 141u8, 1u8]; + 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::FixedBytes(32usize), + ]), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Int(32usize), + ethabi::ParamType::Int(32usize), + ]), + ethabi::ParamType::Int(128usize), + ]), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_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 result = [0u8; 32]; + let v = tuple_elements[2usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + params: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + { + let mut result = [0u8; 32]; + let v = tuple_elements[0usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + { + let tuple_elements = tuple_elements[1usize] + .clone() + .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) + }, + ) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.pool_key.1)), + ethabi::Token::FixedBytes(self.pool_key.2.as_ref().to_vec()), + ]), + ethabi::Token::Tuple(vec![ + ethabi::Token::FixedBytes(self.params.0.as_ref().to_vec()), + ethabi::Token::Tuple(vec![ + { + let non_full_signed_bytes = self.params.1 .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 .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())) + }, + ]), + ]); + 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(128usize), ethabi::ParamType::Int(128usize)], + 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 UpdatePosition { + const NAME: &'static str = "updatePosition"; + 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 UpdatePosition + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Withdraw { + pub token: Vec, + pub recipient: Vec, + pub amount: substreams::scalar::BigInt, + } + impl Withdraw { + const METHOD_ID: [u8; 4] = [3u8, 166u8, 90u8, 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::Address, + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + recipient: 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.token)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + 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 Withdraw { + const NAME: &'static str = "withdraw"; + 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 WithdrawProtocolFees { + pub recipient: Vec, + pub token: Vec, + pub amount: substreams::scalar::BigInt, + } + impl WithdrawProtocolFees { + const METHOD_ID: [u8; 4] = [61u8, 81u8, 37u8, 20u8]; + 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(), + token: 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.token)), + 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 WithdrawProtocolFees { + const NAME: &'static str = "withdrawProtocolFees"; + 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 ExtensionRegistered { + pub extension: Vec, + } + impl ExtensionRegistered { + const TOPIC_ID: [u8; 32] = [ + 236u8, 18u8, 86u8, 38u8, 110u8, 71u8, 10u8, 187u8, 134u8, 134u8, 32u8, 200u8, 81u8, + 246u8, 189u8, 226u8, 163u8, 255u8, 96u8, 37u8, 73u8, 220u8, 173u8, 49u8, 138u8, 185u8, + 204u8, 252u8, 178u8, 151u8, 127u8, 20u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + 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::Address], log.data.as_ref()) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + extension: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for ExtensionRegistered { + const NAME: &'static str = "ExtensionRegistered"; + 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 FeesAccumulated { + pub pool_id: [u8; 32usize], + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl FeesAccumulated { + const TOPIC_ID: [u8; 32] = [ + 247u8, 224u8, 80u8, 216u8, 102u8, 119u8, 72u8, 32u8, 216u8, 26u8, 134u8, 202u8, 103u8, + 111u8, 58u8, 254u8, 123u8, 199u8, 38u8, 3u8, 238u8, 137u8, 63u8, 130u8, 233u8, 156u8, + 8u8, 251u8, 222u8, 57u8, 175u8, 108u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 96usize { + 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::FixedBytes(32usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_id: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + 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 FeesAccumulated { + const NAME: &'static str = "FeesAccumulated"; + 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 LoadedBalance { + pub owner: Vec, + pub token: Vec, + pub salt: [u8; 32usize], + pub amount: substreams::scalar::BigInt, + } + impl LoadedBalance { + const TOPIC_ID: [u8; 32] = [ + 233u8, 93u8, 71u8, 15u8, 173u8, 181u8, 253u8, 214u8, 26u8, 9u8, 226u8, 252u8, 152u8, + 201u8, 140u8, 105u8, 92u8, 17u8, 12u8, 150u8, 63u8, 44u8, 120u8, 94u8, 114u8, 106u8, + 169u8, 4u8, 217u8, 220u8, 234u8, 57u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + 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::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + 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 + }, + 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 LoadedBalance { + const NAME: &'static str = "LoadedBalance"; + 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 OwnershipHandoverCanceled { + pub pending_owner: Vec, + } + impl OwnershipHandoverCanceled { + const TOPIC_ID: [u8; 32] = [ + 250u8, 123u8, 142u8, 171u8, 125u8, 166u8, 127u8, 65u8, 44u8, 201u8, 87u8, 94u8, 212u8, + 52u8, 100u8, 70u8, 143u8, 155u8, 251u8, 174u8, 137u8, 209u8, 103u8, 89u8, 23u8, 52u8, + 108u8, 166u8, 216u8, 254u8, 60u8, 146u8, + ]; + 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 { + pending_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'pending_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 OwnershipHandoverCanceled { + const NAME: &'static str = "OwnershipHandoverCanceled"; + 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 OwnershipHandoverRequested { + pub pending_owner: Vec, + } + impl OwnershipHandoverRequested { + const TOPIC_ID: [u8; 32] = [ + 219u8, 243u8, 106u8, 16u8, 125u8, 161u8, 158u8, 73u8, 82u8, 122u8, 113u8, 118u8, 161u8, + 186u8, 191u8, 150u8, 59u8, 75u8, 15u8, 248u8, 205u8, 227u8, 94u8, 227u8, 93u8, 108u8, + 216u8, 241u8, 249u8, 172u8, 126u8, 29u8, + ]; + 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 { + pending_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'pending_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 OwnershipHandoverRequested { + const NAME: &'static str = "OwnershipHandoverRequested"; + 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 old_owner: 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 { + old_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'old_owner' 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 PoolInitialized { + pub pool_id: [u8; 32usize], + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub tick: substreams::scalar::BigInt, + pub sqrt_ratio: substreams::scalar::BigInt, + } + impl PoolInitialized { + const TOPIC_ID: [u8; 32] = [ + 94u8, 70u8, 136u8, 179u8, 64u8, 105u8, 75u8, 124u8, 127u8, 211u8, 0u8, 71u8, 253u8, + 8u8, 33u8, 23u8, 220u8, 70u8, 227u8, 42u8, 207u8, 191u8, 129u8, 164u8, 75u8, 177u8, + 250u8, 192u8, 174u8, 101u8, 21u8, 77u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + 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::FixedBytes(32usize), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ]), + ethabi::ParamType::Int(32usize), + ethabi::ParamType::Uint(96usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_id: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + pool_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 result = [0u8; 32]; + let v = tuple_elements[2usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + ) + }, + 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) + }, + sqrt_ratio: { + 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 PoolInitialized { + const NAME: &'static str = "PoolInitialized"; + 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 PositionFeesCollected { + pub pool_id: [u8; 32usize], + pub position_key: + ([u8; 32usize], Vec, (substreams::scalar::BigInt, substreams::scalar::BigInt)), + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl PositionFeesCollected { + const TOPIC_ID: [u8; 32] = [ + 187u8, 57u8, 146u8, 216u8, 60u8, 114u8, 31u8, 18u8, 168u8, 243u8, 34u8, 66u8, 224u8, + 210u8, 28u8, 54u8, 19u8, 148u8, 156u8, 106u8, 105u8, 210u8, 163u8, 93u8, 238u8, 205u8, + 246u8, 148u8, 58u8, 97u8, 200u8, 178u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 224usize { + 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::FixedBytes(32usize), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Address, + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Int(32usize), + ethabi::ParamType::Int(32usize), + ]), + ]), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + pool_id: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + position_key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + { + let mut result = [0u8; 32]; + let v = tuple_elements[0usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let tuple_elements = tuple_elements[2usize] + .clone() + .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) + }, + ) + }, + ) + }, + 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 PositionFeesCollected { + const NAME: &'static str = "PositionFeesCollected"; + 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 PositionUpdated { + pub locker: Vec, + pub pool_id: [u8; 32usize], + pub params: ( + [u8; 32usize], + (substreams::scalar::BigInt, substreams::scalar::BigInt), + substreams::scalar::BigInt, + ), + pub delta0: substreams::scalar::BigInt, + pub delta1: substreams::scalar::BigInt, + } + impl PositionUpdated { + const TOPIC_ID: [u8; 32] = [ + 162u8, 212u8, 0u8, 139u8, 228u8, 24u8, 124u8, 99u8, 104u8, 79u8, 50u8, 55u8, 136u8, + 225u8, 49u8, 225u8, 55u8, 13u8, 188u8, 34u8, 5u8, 73u8, 155u8, 239u8, 226u8, 131u8, + 64u8, 5u8, 160u8, 12u8, 121u8, 44u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 256usize { + 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::FixedBytes(32usize), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Int(32usize), + ethabi::ParamType::Int(32usize), + ]), + ethabi::ParamType::Int(128usize), + ]), + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Int(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + locker: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + pool_id: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + params: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + { + let mut result = [0u8; 32]; + let v = tuple_elements[0usize] + .clone() + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + { + let tuple_elements = tuple_elements[1usize] + .clone() + .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) + }, + ) + }, + delta0: { + 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) + }, + delta1: { + 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 PositionUpdated { + const NAME: &'static str = "PositionUpdated"; + 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 ProtocolFeesWithdrawn { + pub recipient: Vec, + pub token: Vec, + pub amount: substreams::scalar::BigInt, + } + impl ProtocolFeesWithdrawn { + const TOPIC_ID: [u8; 32] = [ + 143u8, 194u8, 65u8, 48u8, 143u8, 252u8, 23u8, 129u8, 126u8, 106u8, 140u8, 106u8, 82u8, + 168u8, 247u8, 205u8, 73u8, 49u8, 223u8, 202u8, 12u8, 83u8, 159u8, 211u8, 90u8, 99u8, + 3u8, 17u8, 199u8, 228u8, 197u8, 123u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 96usize { + 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::Address, + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token: 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 ProtocolFeesWithdrawn { + const NAME: &'static str = "ProtocolFeesWithdrawn"; + 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 SavedBalance { + pub owner: Vec, + pub token: Vec, + pub salt: [u8; 32usize], + pub amount: substreams::scalar::BigInt, + } + impl SavedBalance { + const TOPIC_ID: [u8; 32] = [ + 174u8, 210u8, 149u8, 31u8, 166u8, 91u8, 95u8, 239u8, 236u8, 236u8, 23u8, 0u8, 103u8, + 193u8, 210u8, 138u8, 178u8, 101u8, 226u8, 13u8, 26u8, 232u8, 189u8, 96u8, 134u8, 5u8, + 95u8, 80u8, 182u8, 20u8, 99u8, 88u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + 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::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + 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 + }, + 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 SavedBalance { + const NAME: &'static str = "SavedBalance"; + 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) + } + } +} diff --git a/substreams/ethereum-ekubo/src/abi/mod.rs b/substreams/ethereum-ekubo/src/abi/mod.rs new file mode 100644 index 0000000..e871760 --- /dev/null +++ b/substreams/ethereum-ekubo/src/abi/mod.rs @@ -0,0 +1,2 @@ +#![allow(clippy::all)] +pub mod core; diff --git a/substreams/ethereum-ekubo/src/deployment_config.rs b/substreams/ethereum-ekubo/src/deployment_config.rs new file mode 100644 index 0000000..6a56353 --- /dev/null +++ b/substreams/ethereum-ekubo/src/deployment_config.rs @@ -0,0 +1,9 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct DeploymentConfig { + #[serde(with = "hex::serde")] + pub core: Vec, + #[serde(with = "hex::serde")] + pub oracle: Vec, +} diff --git a/substreams/ethereum-ekubo/src/lib.rs b/substreams/ethereum-ekubo/src/lib.rs new file mode 100644 index 0000000..14bc006 --- /dev/null +++ b/substreams/ethereum-ekubo/src/lib.rs @@ -0,0 +1,6 @@ +mod abi; +mod deployment_config; +mod modules; +mod pb; +mod pool_config; +mod sqrt_ratio; diff --git a/substreams/ethereum-ekubo/src/modules/1_map_events.rs b/substreams/ethereum-ekubo/src/modules/1_map_events.rs new file mode 100644 index 0000000..a0815f8 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/1_map_events.rs @@ -0,0 +1,129 @@ +use ethabi::Address; +use itertools::Itertools; +use substreams::scalar::BigInt; +use substreams_ethereum::{ + pb::eth::{self, v2::Log}, + Event as _, +}; + +use crate::{ + abi::core::events as abi_events, + deployment_config::DeploymentConfig, + pb::ekubo::{ + block_transaction_events::{ + transaction_events::{ + pool_log::{ + pool_initialized::Extension, Event, FeesAccumulated, PoolInitialized, + PositionFeesCollected, PositionUpdated, Swapped, + }, + PoolLog, + }, + TransactionEvents, + }, + BlockTransactionEvents, + }, + pool_config::PoolConfig, + sqrt_ratio::float_sqrt_ratio_to_fixed, +}; + +#[substreams::handlers::map] +fn map_events(params: String, block: eth::v2::Block) -> BlockTransactionEvents { + let config: DeploymentConfig = serde_qs::from_str(¶ms).unwrap(); + + BlockTransactionEvents { + block_transaction_events: block + .transactions() + .flat_map(|trace| { + let pool_logs = trace + .logs_with_calls() + .filter_map(|(log, _)| maybe_pool_log(log, &config)) + .collect_vec(); + + (!pool_logs.is_empty()) + .then(|| TransactionEvents { transaction: Some(trace.into()), pool_logs }) + }) + .collect(), + } +} + +fn maybe_pool_log(log: &Log, config: &DeploymentConfig) -> Option { + if log.address != config.core { + return None; + } + + let (pool_id, ev) = if log.topics.is_empty() { + let data = &log.data; + + assert!(data.len() == 116, "swap event data length mismatch"); + + ( + data[20..52].to_vec(), + Event::Swapped(Swapped { + delta0: data[52..68].to_vec(), + delta1: data[68..84].to_vec(), + liquidity_after: data[84..100].to_vec(), + sqrt_ratio_after: float_sqrt_ratio_to_fixed(BigInt::from_unsigned_bytes_be( + &data[100..112], + )), + tick_after: i32::from_be_bytes(data[112..116].try_into().unwrap()), + }), + ) + } else if let Some(ev) = abi_events::PositionUpdated::match_and_decode(log) { + ( + ev.pool_id.to_vec(), + Event::PositionUpdated(PositionUpdated { + lower: ev.params.1 .0.to_i32(), + upper: ev.params.1 .1.to_i32(), + liquidity_delta: ev.params.2.to_signed_bytes_be(), + delta0: ev.delta0.to_signed_bytes_be(), + delta1: ev.delta1.to_signed_bytes_be(), + }), + ) + } else if let Some(ev) = abi_events::PositionFeesCollected::match_and_decode(log) { + ( + ev.pool_id.to_vec(), + Event::PositionFeesCollected(PositionFeesCollected { + amount0: ev.amount0.to_bytes_be().1, + amount1: ev.amount1.to_bytes_be().1, + }), + ) + } else if let Some(ev) = abi_events::PoolInitialized::match_and_decode(log) { + let pool_config = PoolConfig::from(ev.pool_key.2); + + let extension = { + let extension = pool_config.extension; + + if extension == Address::zero().as_bytes() { + Extension::Base + } else if extension == config.oracle { + Extension::Oracle + } else { + Extension::Unknown + } + }; + + ( + ev.pool_id.to_vec(), + Event::PoolInitialized(PoolInitialized { + token0: ev.pool_key.0, + token1: ev.pool_key.1, + config: ev.pool_key.2.to_vec(), + tick: ev.tick.to_i32(), + sqrt_ratio: float_sqrt_ratio_to_fixed(ev.sqrt_ratio), + extension: extension.into(), + }), + ) + } else if let Some(ev) = abi_events::FeesAccumulated::match_and_decode(log) { + ( + ev.pool_id.to_vec(), + Event::FeesAccumulated(FeesAccumulated { + amount0: ev.amount0.to_bytes_be().1, + amount1: ev.amount1.to_bytes_be().1, + }), + ) + } else { + return None; + }; + + Some(PoolLog { ordinal: log.ordinal, pool_id, event: Some(ev) }) +} diff --git a/substreams/ethereum-ekubo/src/modules/2_map_components.rs b/substreams/ethereum-ekubo/src/modules/2_map_components.rs new file mode 100644 index 0000000..37a7d9a --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/2_map_components.rs @@ -0,0 +1,135 @@ +use itertools::Itertools; +use substreams::scalar::BigInt; +use substreams_helper::hex::Hexable; +use tycho_substreams::models::{ + Attribute, BalanceChange, BlockChanges, ChangeType, EntityChanges, FinancialType, + ImplementationType, ProtocolComponent, ProtocolType, TransactionChanges, +}; + +use crate::{ + pb::ekubo::{ + block_transaction_events::transaction_events::{pool_log::Event, PoolLog}, + BlockTransactionEvents, + }, + pool_config::PoolConfig, +}; + +#[substreams::handlers::map] +fn map_components(block_tx_events: BlockTransactionEvents) -> BlockChanges { + BlockChanges { + block: None, + changes: block_tx_events + .block_transaction_events + .into_iter() + .filter_map(|tx_events| { + let (components, entities, balance_changes): (Vec<_>, Vec<_>, Vec<_>) = tx_events + .pool_logs + .into_iter() + .filter_map(maybe_create_component) + .multiunzip(); + + (!components.is_empty()).then(|| TransactionChanges { + tx: Some(tx_events.transaction.unwrap().into()), + balance_changes: balance_changes + .into_iter() + .flatten() + .collect(), + contract_changes: vec![], + entity_changes: entities, + component_changes: components, + }) + }) + .collect(), + } +} + +fn maybe_create_component( + log: PoolLog, +) -> Option<(ProtocolComponent, EntityChanges, Vec)> { + if let Event::PoolInitialized(pi) = log.event.unwrap() { + let config = PoolConfig::from(<[u8; 32]>::try_from(pi.config).unwrap()); + let component_id = log.pool_id.to_hex(); + + return Some(( + ProtocolComponent { + id: component_id.clone(), + tokens: vec![pi.token0.clone(), pi.token1.clone()], + contracts: vec![], + change: ChangeType::Creation.into(), + protocol_type: Some(ProtocolType { + name: "ekubo".to_string(), + financial_type: FinancialType::Swap.into(), + implementation_type: ImplementationType::Custom.into(), + attribute_schema: vec![], + }), + // Order of attributes matters (used in store_pool_details) + static_att: vec![ + Attribute { + change: ChangeType::Creation.into(), + name: "token0".to_string(), + value: pi.token0.clone(), + }, + Attribute { + change: ChangeType::Creation.into(), + name: "token1".to_string(), + value: pi.token1.clone(), + }, + Attribute { + change: ChangeType::Creation.into(), + name: "fee".to_string(), + value: config.fee, + }, + Attribute { + change: ChangeType::Creation.into(), + name: "tick_spacing".to_string(), + value: config.tick_spacing, + }, + Attribute { + change: ChangeType::Creation.into(), + name: "extension".to_string(), + value: config.extension, + }, + Attribute { + change: ChangeType::Creation.into(), + name: "extension_id".to_string(), + value: pi.extension.to_be_bytes().to_vec(), + }, + ], + }, + EntityChanges { + component_id: component_id.clone(), + attributes: vec![ + Attribute { + change: ChangeType::Creation.into(), + name: "liquidity".to_string(), + value: 0_u128.to_be_bytes().to_vec(), + }, + Attribute { + change: ChangeType::Creation.into(), + name: "tick".to_string(), + value: pi.tick.to_be_bytes().to_vec(), + }, + Attribute { + change: ChangeType::Creation.into(), + name: "sqrt_ratio".to_string(), + value: pi.sqrt_ratio, + }, + ], + }, + vec![ + BalanceChange { + component_id: component_id.clone().into_bytes(), + token: pi.token0, + balance: BigInt::zero().to_signed_bytes_be(), + }, + BalanceChange { + component_id: component_id.into_bytes(), + token: pi.token1, + balance: BigInt::zero().to_signed_bytes_be(), + }, + ], + )); + } + + None +} diff --git a/substreams/ethereum-ekubo/src/modules/2_map_tick_changes.rs b/substreams/ethereum-ekubo/src/modules/2_map_tick_changes.rs new file mode 100644 index 0000000..059cb19 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/2_map_tick_changes.rs @@ -0,0 +1,63 @@ +use substreams::scalar::BigInt; + +use crate::pb::ekubo::{ + block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents, + TickDelta, TickDeltas, +}; + +#[substreams::handlers::map] +pub fn map_tick_changes(block_tx_events: BlockTransactionEvents) -> TickDeltas { + TickDeltas { + deltas: block_tx_events + .block_transaction_events + .into_iter() + .flat_map(|tx_events| { + let tx = tx_events.transaction; + + tx_events + .pool_logs + .into_iter() + .flat_map(move |log| { + let tx = tx.clone(); + + tick_deltas(log.event.unwrap()) + .into_iter() + .map(move |partial| TickDelta { + liquidity_net_delta: partial.liquidity_net_delta, + pool_id: log.pool_id.clone(), + tick_index: partial.tick_index, + ordinal: log.ordinal, + transaction: tx.clone(), + }) + }) + }) + .collect(), + } +} + +struct PartialTickDelta { + tick_index: i32, + liquidity_net_delta: Vec, +} + +fn tick_deltas(ev: Event) -> Vec { + match ev { + Event::PositionUpdated(position_updated) => { + vec![ + PartialTickDelta { + tick_index: position_updated.lower, + liquidity_net_delta: position_updated.liquidity_delta.clone(), + }, + PartialTickDelta { + tick_index: position_updated.upper, + liquidity_net_delta: BigInt::from_signed_bytes_be( + &position_updated.liquidity_delta, + ) + .neg() + .to_signed_bytes_be(), + }, + ] + } + _ => vec![], + } +} diff --git a/substreams/ethereum-ekubo/src/modules/2_store_active_ticks.rs b/substreams/ethereum-ekubo/src/modules/2_store_active_ticks.rs new file mode 100644 index 0000000..ea81927 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/2_store_active_ticks.rs @@ -0,0 +1,30 @@ +use substreams::store::{StoreSet, StoreSetInt64}; + +use substreams::store::StoreNew; +use substreams_helper::hex::Hexable; + +use crate::pb::ekubo::{ + block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents, +}; + +#[substreams::handlers::store] +pub fn store_active_ticks(block_tx_events: BlockTransactionEvents, store: StoreSetInt64) { + block_tx_events + .block_transaction_events + .into_iter() + .flat_map(|tx_events| tx_events.pool_logs) + .filter_map(|log| { + maybe_tick(log.event.unwrap()).map(|tick| (log.pool_id.to_hex(), log.ordinal, tick)) + }) + .for_each(|(pool, ordinal, new_tick_index)| { + store.set(ordinal, format!("pool:{pool}"), &new_tick_index.into()) + }); +} + +fn maybe_tick(ev: Event) -> Option { + match ev { + Event::PoolInitialized(pool_initialized) => Some(pool_initialized.tick), + Event::Swapped(swapped) => Some(swapped.tick_after), + _ => None, + } +} diff --git a/substreams/ethereum-ekubo/src/modules/3_map_liquidity_changes.rs b/substreams/ethereum-ekubo/src/modules/3_map_liquidity_changes.rs new file mode 100644 index 0000000..c6cd53f --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/3_map_liquidity_changes.rs @@ -0,0 +1,69 @@ +use substreams::store::{StoreGet, StoreGetInt64}; + +use substreams_helper::hex::Hexable; + +use crate::pb::ekubo::{ + block_transaction_events::transaction_events::{pool_log::Event, PoolLog}, + BlockTransactionEvents, LiquidityChange, LiquidityChangeType, LiquidityChanges, +}; + +#[substreams::handlers::map] +pub fn map_liquidity_changes( + block_tx_events: BlockTransactionEvents, + current_tick_store: StoreGetInt64, +) -> LiquidityChanges { + LiquidityChanges { + changes: block_tx_events + .block_transaction_events + .into_iter() + .flat_map(|tx_events| { + let current_tick_store = ¤t_tick_store; + + tx_events + .pool_logs + .into_iter() + .filter_map(move |log| { + maybe_liquidity_change(&log, current_tick_store).map(|partial| { + LiquidityChange { + change_type: partial.change_type.into(), + pool_id: log.pool_id, + value: partial.value, + ordinal: log.ordinal, + transaction: tx_events.transaction.clone(), + } + }) + }) + }) + .collect(), + } +} + +struct PartialLiquidityChange { + value: Vec, + change_type: LiquidityChangeType, +} + +fn maybe_liquidity_change( + log: &PoolLog, + current_tick_store: &StoreGetInt64, +) -> Option { + match log.event.as_ref().unwrap() { + Event::Swapped(swapped) => Some(PartialLiquidityChange { + value: swapped.liquidity_after.clone(), + change_type: LiquidityChangeType::Absolute, + }), + Event::PositionUpdated(position_updated) => { + let current_tick = current_tick_store + .get_at(log.ordinal, format!("pool:{0}", log.pool_id.to_hex())) + .expect("pool should have active tick when initialized"); + + (current_tick >= position_updated.lower.into() && + current_tick < position_updated.upper.into()) + .then(|| PartialLiquidityChange { + value: position_updated.liquidity_delta.clone(), + change_type: LiquidityChangeType::Delta, + }) + } + _ => None, + } +} diff --git a/substreams/ethereum-ekubo/src/modules/3_store_pool_details.rs b/substreams/ethereum-ekubo/src/modules/3_store_pool_details.rs new file mode 100644 index 0000000..fd61a95 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/3_store_pool_details.rs @@ -0,0 +1,31 @@ +use substreams::store::{StoreNew, StoreSetIfNotExists, StoreSetIfNotExistsProto}; +use tycho_substreams::models::BlockChanges; + +use crate::pb::ekubo::PoolDetails; + +// Since only the PoolInitialized event contains the complete pool key we need to store some info +// required when processing other events +#[substreams::handlers::store] +fn store_pool_details(changes: BlockChanges, store: StoreSetIfNotExistsProto) { + changes + .changes + .into_iter() + .flat_map(|c| c.component_changes.into_iter()) + .for_each(|component| { + let attrs = component.static_att; + + let pool_details = PoolDetails { + token0: attrs[0].value.clone(), + token1: attrs[1].value.clone(), + fee: u64::from_be_bytes( + attrs[2] + .value + .clone() + .try_into() + .unwrap(), + ), + }; + + store.set_if_not_exists(0, component.id, &pool_details); + }); +} diff --git a/substreams/ethereum-ekubo/src/modules/3_store_tick_liquidities.rs b/substreams/ethereum-ekubo/src/modules/3_store_tick_liquidities.rs new file mode 100644 index 0000000..b6d6a32 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/3_store_tick_liquidities.rs @@ -0,0 +1,21 @@ +use substreams::{ + scalar::BigInt, + store::{StoreAdd, StoreAddBigInt, StoreNew}, +}; +use substreams_helper::hex::Hexable; + +use crate::pb::ekubo::TickDeltas; + +#[substreams::handlers::store] +pub fn store_tick_liquidities(tick_deltas: TickDeltas, store: StoreAddBigInt) { + tick_deltas + .deltas + .into_iter() + .for_each(|delta| { + store.add( + delta.ordinal, + format!("pool:{}:tick:{}", delta.pool_id.to_hex(), delta.tick_index), + BigInt::from_signed_bytes_be(&delta.liquidity_net_delta), + ); + }); +} diff --git a/substreams/ethereum-ekubo/src/modules/4_map_balance_changes.rs b/substreams/ethereum-ekubo/src/modules/4_map_balance_changes.rs new file mode 100644 index 0000000..5af0acd --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/4_map_balance_changes.rs @@ -0,0 +1,127 @@ +use substreams::{ + hex, + scalar::BigInt, + store::{StoreGet, StoreGetProto}, +}; +use substreams_helper::hex::Hexable; +use tycho_substreams::models::{BalanceDelta, BlockBalanceDeltas, Transaction}; + +use crate::pb::ekubo::{ + block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents, + PoolDetails, +}; + +#[substreams::handlers::map] +fn map_balance_changes( + block_tx_events: BlockTransactionEvents, + store: StoreGetProto, +) -> BlockBalanceDeltas { + BlockBalanceDeltas { + balance_deltas: block_tx_events + .block_transaction_events + .into_iter() + .flat_map(|tx_events| { + let tx: Transaction = tx_events.transaction.unwrap().into(); + + let store = &store; + + tx_events + .pool_logs + .into_iter() + .flat_map(move |log| { + let component_id = log.pool_id.to_hex(); + let pool_details = get_pool_details(store, &component_id); + + let component_id_bytes = component_id.into_bytes(); + let tx = tx.clone(); + + balance_deltas(log.event.unwrap(), pool_details) + .into_iter() + .map(move |reduced| BalanceDelta { + ord: log.ordinal, + tx: Some(tx.clone()), + token: reduced.token, + delta: reduced.delta, + component_id: component_id_bytes.clone(), + }) + }) + }) + .collect(), + } +} + +struct ReducedBalanceDelta { + token: Vec, + delta: Vec, +} + +fn balance_deltas(ev: Event, pool_details: PoolDetails) -> Vec { + match ev { + Event::Swapped(swapped) => { + vec![ + ReducedBalanceDelta { token: pool_details.token0, delta: swapped.delta0 }, + ReducedBalanceDelta { token: pool_details.token1, delta: swapped.delta1 }, + ] + } + Event::PositionUpdated(position_updated) => { + vec![ + ReducedBalanceDelta { + token: pool_details.token0, + delta: adjust_delta_by_fee( + BigInt::from_signed_bytes_be(&position_updated.delta0), + pool_details.fee, + ) + .to_signed_bytes_be(), + }, + ReducedBalanceDelta { + token: pool_details.token1, + delta: adjust_delta_by_fee( + BigInt::from_signed_bytes_be(&position_updated.delta1), + pool_details.fee, + ) + .to_signed_bytes_be(), + }, + ] + } + Event::PositionFeesCollected(position_fees_collected) => { + vec![ + ReducedBalanceDelta { + token: pool_details.token0, + delta: BigInt::from_unsigned_bytes_be(&position_fees_collected.amount0) + .neg() + .to_signed_bytes_be(), + }, + ReducedBalanceDelta { + token: pool_details.token1, + delta: BigInt::from_unsigned_bytes_be(&position_fees_collected.amount1) + .neg() + .to_signed_bytes_be(), + }, + ] + } + Event::FeesAccumulated(fees_accumulated) => { + vec![ + ReducedBalanceDelta { token: pool_details.token0, delta: fees_accumulated.amount0 }, + ReducedBalanceDelta { token: pool_details.token1, delta: fees_accumulated.amount1 }, + ] + } + _ => vec![], + } +} + +// Negative deltas don't include the fees paid by the position owner, thus we need to add it back +// here (i.e. subtract from the component's balance) +fn adjust_delta_by_fee(delta: BigInt, fee: u64) -> BigInt { + if delta < BigInt::zero() { + let denom = BigInt::from_signed_bytes_be(&hex!("0100000000000000000000000000000000")); + (delta * denom.clone()) / (denom - fee) + } else { + delta + } +} + +fn get_pool_details(store: &StoreGetProto, component_id: &str) -> PoolDetails { + store + .get_at(0, component_id) + .expect("pool id should exist in store") +} diff --git a/substreams/ethereum-ekubo/src/modules/4_store_liquidities.rs b/substreams/ethereum-ekubo/src/modules/4_store_liquidities.rs new file mode 100644 index 0000000..19717ca --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/4_store_liquidities.rs @@ -0,0 +1,30 @@ +use substreams::{ + scalar::BigInt, + store::{StoreSetSum, StoreSetSumBigInt}, +}; +use substreams_helper::hex::Hexable; + +use crate::pb::ekubo::{LiquidityChangeType, LiquidityChanges}; + +#[substreams::handlers::store] +pub fn store_liquidities(liquidity_changes: LiquidityChanges, store: StoreSetSumBigInt) { + liquidity_changes + .changes + .into_iter() + .for_each(|changes| match changes.change_type() { + LiquidityChangeType::Delta => { + store.sum( + changes.ordinal, + format!("pool:{0}", changes.pool_id.to_hex()), + BigInt::from_signed_bytes_be(&changes.value), + ); + } + LiquidityChangeType::Absolute => { + store.set( + changes.ordinal, + format!("pool:{0}", changes.pool_id.to_hex()), + BigInt::from_signed_bytes_be(&changes.value), + ); + } + }); +} diff --git a/substreams/ethereum-ekubo/src/modules/5_store_balance_changes.rs b/substreams/ethereum-ekubo/src/modules/5_store_balance_changes.rs new file mode 100644 index 0000000..f9595e9 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/5_store_balance_changes.rs @@ -0,0 +1,7 @@ +use substreams::store::{StoreAddBigInt, StoreNew}; +use tycho_substreams::models::BlockBalanceDeltas; + +#[substreams::handlers::store] +fn store_balance_changes(deltas: BlockBalanceDeltas, store: StoreAddBigInt) { + tycho_substreams::balances::store_balance_changes(deltas, store); +} diff --git a/substreams/ethereum-ekubo/src/modules/6_map_protocol_changes.rs b/substreams/ethereum-ekubo/src/modules/6_map_protocol_changes.rs new file mode 100644 index 0000000..dcf4d45 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/6_map_protocol_changes.rs @@ -0,0 +1,203 @@ +use std::{collections::HashMap, str::FromStr}; + +use itertools::Itertools; +use substreams::{key, pb::substreams::StoreDeltas, scalar::BigInt}; +use substreams_ethereum::pb::eth; +use substreams_helper::hex::Hexable; +use tycho_substreams::{ + balances::aggregate_balances_changes, + models::{ + Attribute, BlockBalanceDeltas, BlockChanges, ChangeType, EntityChanges, + TransactionChangesBuilder, + }, +}; + +use crate::pb::ekubo::{ + block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents, + LiquidityChanges, TickDeltas, +}; + +/// Aggregates protocol components and balance changes by transaction. +/// +/// This is the main method that will aggregate all changes as well as extract all +/// relevant contract storage deltas. +#[substreams::handlers::map] +fn map_protocol_changes( + block: eth::v2::Block, + new_components: BlockChanges, + block_tx_events: BlockTransactionEvents, + balances_map_deltas: BlockBalanceDeltas, + balances_store_deltas: StoreDeltas, + ticks_map_deltas: TickDeltas, + ticks_store_deltas: StoreDeltas, + liquidity_changes: LiquidityChanges, + liquidity_store_deltas: StoreDeltas, +) -> Result { + let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new(); + + // New components + new_components + .changes + .iter() + .for_each(|tx_changes| { + let tx = tx_changes.tx.as_ref().unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(tx)); + + tx_changes + .component_changes + .iter() + .for_each(|component| { + builder.add_protocol_component(component); + }); + + tx_changes + .entity_changes + .iter() + .for_each(|entity_change| { + builder.add_entity_change(entity_change); + }); + + tx_changes + .balance_changes + .iter() + .for_each(|balance_change| { + builder.add_balance_change(balance_change); + }); + }); + + // Component balances + 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); + }) + }); + }); + + // Tick liquidities + ticks_store_deltas + .deltas + .into_iter() + .zip(ticks_map_deltas.deltas) + .for_each(|(store_delta, tick_delta)| { + let new_value_bigint = BigInt::from_store_bytes(&store_delta.new_value); + + let is_creation = BigInt::from_store_bytes(&store_delta.old_value).is_zero(); + + let attribute = Attribute { + name: format!("ticks/{}", tick_delta.tick_index), + 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_id.to_hex(), + attributes: vec![attribute], + }); + }); + + // Pool liquidities + liquidity_store_deltas + .deltas + .into_iter() + .zip(liquidity_changes.changes) + .for_each(|(store_delta, change)| { + let tx = change.transaction.unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx.into())); + + let new_value_bigint = BigInt::from_str(key::segment_at( + &String::from_utf8(store_delta.new_value).unwrap(), + 1, + )) + .unwrap(); + + builder.add_entity_change(&EntityChanges { + component_id: change.pool_id.to_hex(), + attributes: vec![Attribute { + name: "liquidity".to_string(), + value: new_value_bigint.to_signed_bytes_be(), + change: ChangeType::Update.into(), + }], + }); + }); + + // Remaining event changes not subject to special treatment + block_tx_events + .block_transaction_events + .into_iter() + .flat_map(|tx_events| { + let tx = tx_events.transaction.unwrap(); + + tx_events + .pool_logs + .into_iter() + .flat_map(move |log| { + let tx = tx.clone(); + + maybe_attribute_updates(log.event.unwrap()).map(|attrs| { + ( + tx, + EntityChanges { component_id: log.pool_id.to_hex(), attributes: attrs }, + ) + }) + }) + }) + .for_each(|(tx, entity_changes)| { + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx.into())); + builder.add_entity_change(&entity_changes); + }); + + Ok(BlockChanges { + block: Some((&block).into()), + changes: transaction_changes + .drain() + .sorted_unstable_by_key(|(index, _)| *index) + .filter_map(|(_, builder)| builder.build()) + .collect(), + }) +} + +fn maybe_attribute_updates(ev: Event) -> Option> { + match ev { + Event::Swapped(swapped) => Some(vec![ + Attribute { + name: "tick".into(), + value: swapped + .tick_after + .to_be_bytes() + .to_vec(), + change: ChangeType::Update.into(), + }, + Attribute { + name: "sqrt_ratio".into(), + value: swapped.sqrt_ratio_after, + change: ChangeType::Update.into(), + }, + ]), + _ => None, + } +} diff --git a/substreams/ethereum-ekubo/src/modules/mod.rs b/substreams/ethereum-ekubo/src/modules/mod.rs new file mode 100644 index 0000000..ad15e77 --- /dev/null +++ b/substreams/ethereum-ekubo/src/modules/mod.rs @@ -0,0 +1,48 @@ +use substreams_ethereum::pb::eth::v2::TransactionTrace; + +use crate::pb::ekubo::Transaction; + +#[path = "1_map_events.rs"] +mod map_events; + +#[path = "2_map_components.rs"] +mod map_components; +#[path = "2_map_tick_changes.rs"] +mod map_tick_changes; +#[path = "2_store_active_ticks.rs"] +mod store_active_ticks; + +#[path = "3_map_liquidity_changes.rs"] +mod map_liquidity_changes; +#[path = "3_store_pool_details.rs"] +mod store_pool_details; +#[path = "3_store_tick_liquidities.rs"] +mod store_tick_liquidities; + +#[path = "4_map_balance_changes.rs"] +mod map_balance_changes; +#[path = "4_store_liquidities.rs"] +mod store_liquidities; + +#[path = "5_store_balance_changes.rs"] +mod store_balance_changes; + +#[path = "6_map_protocol_changes.rs"] +mod map_protocol_changes; + +impl From<&TransactionTrace> for Transaction { + fn from(value: &TransactionTrace) -> Self { + Self { + hash: value.hash.clone(), + from: value.from.clone(), + to: value.to.clone(), + 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-ekubo/src/pb/ekubo.rs b/substreams/ethereum-ekubo/src/pb/ekubo.rs new file mode 100644 index 0000000..0096991 --- /dev/null +++ b/substreams/ethereum-ekubo/src/pb/ekubo.rs @@ -0,0 +1,256 @@ +// @generated +/// Copy of tycho.evm.v1.Transaction to be able to implement conversions to/from TransactionTrace +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Transaction { + #[prost(bytes="vec", tag="1")] + pub hash: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="2")] + pub from: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="3")] + pub to: ::prost::alloc::vec::Vec, + #[prost(uint64, tag="4")] + pub index: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TickDelta { + /// bytes32 + #[prost(bytes="vec", tag="1")] + pub pool_id: ::prost::alloc::vec::Vec, + #[prost(int32, tag="2")] + pub tick_index: i32, + /// int128 + #[prost(bytes="vec", tag="3")] + pub liquidity_net_delta: ::prost::alloc::vec::Vec, + #[prost(uint64, tag="4")] + pub ordinal: u64, + #[prost(message, optional, tag="5")] + pub transaction: ::core::option::Option, +} +#[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, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct LiquidityChange { + /// bytes32 + #[prost(bytes="vec", tag="1")] + pub pool_id: ::prost::alloc::vec::Vec, + /// uint128 or int128, depending on change_type + #[prost(bytes="vec", tag="2")] + pub value: ::prost::alloc::vec::Vec, + #[prost(enumeration="LiquidityChangeType", tag="3")] + pub change_type: i32, + #[prost(uint64, tag="4")] + pub ordinal: u64, + #[prost(message, optional, tag="5")] + pub transaction: ::core::option::Option, +} +#[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 PoolDetails { + /// address + #[prost(bytes="vec", tag="1")] + pub token0: ::prost::alloc::vec::Vec, + /// address + #[prost(bytes="vec", tag="2")] + pub token1: ::prost::alloc::vec::Vec, + #[prost(fixed64, tag="3")] + pub fee: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockTransactionEvents { + #[prost(message, repeated, tag="1")] + pub block_transaction_events: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `BlockTransactionEvents`. +pub mod block_transaction_events { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct TransactionEvents { + #[prost(message, optional, tag="1")] + pub transaction: ::core::option::Option, + #[prost(message, repeated, tag="2")] + pub pool_logs: ::prost::alloc::vec::Vec, + } + /// Nested message and enum types in `TransactionEvents`. + pub mod transaction_events { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct PoolLog { + #[prost(uint64, tag="1")] + pub ordinal: u64, + /// bytes32 + #[prost(bytes="vec", tag="2")] + pub pool_id: ::prost::alloc::vec::Vec, + #[prost(oneof="pool_log::Event", tags="3, 4, 5, 6, 7")] + pub event: ::core::option::Option, + } + /// Nested message and enum types in `PoolLog`. + pub mod pool_log { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct Swapped { + /// int128 + #[prost(bytes="vec", tag="1")] + pub delta0: ::prost::alloc::vec::Vec, + /// int128 + #[prost(bytes="vec", tag="2")] + pub delta1: ::prost::alloc::vec::Vec, + /// uint192 + #[prost(bytes="vec", tag="3")] + pub sqrt_ratio_after: ::prost::alloc::vec::Vec, + /// uint128 + #[prost(bytes="vec", tag="4")] + pub liquidity_after: ::prost::alloc::vec::Vec, + /// int32 + #[prost(sint32, tag="5")] + pub tick_after: i32, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct PositionUpdated { + /// int32 + #[prost(sint32, tag="1")] + pub lower: i32, + /// int32 + #[prost(sint32, tag="2")] + pub upper: i32, + /// int128 + #[prost(bytes="vec", tag="3")] + pub liquidity_delta: ::prost::alloc::vec::Vec, + /// int128 + #[prost(bytes="vec", tag="4")] + pub delta0: ::prost::alloc::vec::Vec, + /// int128 + #[prost(bytes="vec", tag="5")] + pub delta1: ::prost::alloc::vec::Vec, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct PositionFeesCollected { + /// uint128 + #[prost(bytes="vec", tag="1")] + pub amount0: ::prost::alloc::vec::Vec, + /// uint128 + #[prost(bytes="vec", tag="2")] + pub amount1: ::prost::alloc::vec::Vec, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct PoolInitialized { + /// address + #[prost(bytes="vec", tag="1")] + pub token0: ::prost::alloc::vec::Vec, + /// address + #[prost(bytes="vec", tag="2")] + pub token1: ::prost::alloc::vec::Vec, + /// bytes32 + #[prost(bytes="vec", tag="3")] + pub config: ::prost::alloc::vec::Vec, + /// int32 + #[prost(sint32, tag="4")] + pub tick: i32, + /// uint192 + #[prost(bytes="vec", tag="5")] + pub sqrt_ratio: ::prost::alloc::vec::Vec, + #[prost(enumeration="pool_initialized::Extension", tag="6")] + pub extension: i32, + } + /// Nested message and enum types in `PoolInitialized`. + pub mod pool_initialized { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Extension { + Unknown = 0, + Base = 1, + Oracle = 2, + } + impl Extension { + /// 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 { + Extension::Unknown => "EXTENSION_UNKNOWN", + Extension::Base => "EXTENSION_BASE", + Extension::Oracle => "EXTENSION_ORACLE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "EXTENSION_UNKNOWN" => Some(Self::Unknown), + "EXTENSION_BASE" => Some(Self::Base), + "EXTENSION_ORACLE" => Some(Self::Oracle), + _ => None, + } + } + } + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct FeesAccumulated { + /// uint128 + #[prost(bytes="vec", tag="1")] + pub amount0: ::prost::alloc::vec::Vec, + /// uint128 + #[prost(bytes="vec", tag="2")] + pub amount1: ::prost::alloc::vec::Vec, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Event { + #[prost(message, tag="3")] + Swapped(Swapped), + #[prost(message, tag="4")] + PositionUpdated(PositionUpdated), + #[prost(message, tag="5")] + PositionFeesCollected(PositionFeesCollected), + #[prost(message, tag="6")] + PoolInitialized(PoolInitialized), + #[prost(message, tag="7")] + FeesAccumulated(FeesAccumulated), + } + } + } +} +#[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/ethereum-ekubo/src/pb/mod.rs b/substreams/ethereum-ekubo/src/pb/mod.rs new file mode 100644 index 0000000..dd5eb86 --- /dev/null +++ b/substreams/ethereum-ekubo/src/pb/mod.rs @@ -0,0 +1,6 @@ +// @generated +// @@protoc_insertion_point(attribute:ekubo) +pub mod ekubo { + include!("ekubo.rs"); + // @@protoc_insertion_point(ekubo) +} diff --git a/substreams/ethereum-ekubo/src/pool_config.rs b/substreams/ethereum-ekubo/src/pool_config.rs new file mode 100644 index 0000000..52d5afa --- /dev/null +++ b/substreams/ethereum-ekubo/src/pool_config.rs @@ -0,0 +1,15 @@ +pub struct PoolConfig { + pub fee: Vec, + pub tick_spacing: Vec, + pub extension: Vec, +} + +impl From<[u8; 32]> for PoolConfig { + fn from(value: [u8; 32]) -> Self { + Self { + tick_spacing: value[28..32].into(), + fee: value[20..28].into(), + extension: value[..20].into(), + } + } +} diff --git a/substreams/ethereum-ekubo/src/sqrt_ratio.rs b/substreams/ethereum-ekubo/src/sqrt_ratio.rs new file mode 100644 index 0000000..1fa159a --- /dev/null +++ b/substreams/ethereum-ekubo/src/sqrt_ratio.rs @@ -0,0 +1,11 @@ +use substreams::scalar::BigInt; + +const BIT_MASK: u128 = 0xc00000000000000000000000; +const NOT_BIT_MASK: u128 = 0x3fffffffffffffffffffffff; + +pub fn float_sqrt_ratio_to_fixed(sqrt_ratio_float: BigInt) -> Vec { + let sqrt_ratio_fixed = (sqrt_ratio_float.clone() & NOT_BIT_MASK) << + >::into(2_u64 + ((sqrt_ratio_float & BIT_MASK) >> 89_u8)); + + sqrt_ratio_fixed.to_bytes_be().1 +} diff --git a/substreams/ethereum-ekubo/substreams.yaml b/substreams/ethereum-ekubo/substreams.yaml new file mode 100644 index 0000000..99aa0c3 --- /dev/null +++ b/substreams/ethereum-ekubo/substreams.yaml @@ -0,0 +1,122 @@ +specVersion: v0.1.0 +package: + name: "ethereum_ekubo" + version: v0.1.0 + +protobuf: + files: + - tycho/evm/v1/common.proto + - tycho/evm/v1/utils.proto + - ekubo.proto + importPaths: + - ../../proto + - ./proto + excludePaths: + - sf/substreams + - google + - tycho + +binaries: + default: + type: wasm/rust-v1 + file: ../target/wasm32-unknown-unknown/release/ethereum_ekubo.wasm + +network: ethereum +networks: + ethereum: + initialBlock: + map_events: 22048334 # First pool initialization https://etherscan.io/tx/0x7c2e697e73dc1f114a5473d1015c411f10585b2b671bee0bd6d5706895e16b27 + params: + map_events: "core=e0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444&oracle=51d02A5948496a67827242EaBc5725531342527C" + +modules: + - name: map_events + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:ekubo.BlockTransactionEvents + + - name: map_components + kind: map + inputs: + - map: map_events + output: + type: proto:tycho.evm.v1.BlockChanges + + - name: map_tick_changes + kind: map + inputs: + - map: map_events + output: + type: proto:ekubo.TickDeltas + + - name: store_active_ticks + kind: store + valueType: int64 + updatePolicy: set + inputs: + - map: map_events + + - name: map_liquidity_changes + kind: map + inputs: + - map: map_events + - store: store_active_ticks + output: + type: proto:ekubo.LiquidityChanges + + - name: store_pool_details + kind: store + valueType: proto:ekubo.PoolDetails + updatePolicy: set_if_not_exists + inputs: + - map: map_components + + - name: store_tick_liquidities + kind: store + valueType: bigint + updatePolicy: add + inputs: + - map: map_tick_changes + + - name: map_balance_changes + kind: map + inputs: + - map: map_events + - store: store_pool_details + output: + type: proto:tycho.evm.v1.BlockBalanceDeltas + + - name: store_liquidities + kind: store + valueType: bigint + updatePolicy: set_sum + inputs: + - map: map_liquidity_changes + + - name: store_balance_changes + kind: store + valueType: bigint + updatePolicy: add + inputs: + - map: map_balance_changes + + - name: map_protocol_changes + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + - map: map_components + - map: map_events + - map: map_balance_changes + - store: store_balance_changes + mode: deltas + - map: map_tick_changes + - store: store_tick_liquidities + mode: deltas + - map: map_liquidity_changes + - store: store_liquidities + mode: deltas + output: + type: proto:tycho.evm.v1.BlockChanges