From 1ff97ff43f637cd966fb1595e8ebf4568197a66f Mon Sep 17 00:00:00 2001 From: die-herdplatte <173669014+die-herdplatte@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:45:08 +0200 Subject: [PATCH] Ekubo TWAMM & MEV-resist integration (#192) * Add Ekubo TWAMM support * Change order of words * Account TWAMM order balances * Fix tracking wrong component balance deltas Swapped and PositionUpdated are the only events affecting pool TVL * Fix fee addition Fees are a .64 instead of a .128 since v2 & the result is rounded * Consistent naming * cargo fmt * Add method for selecting store method from change type * Only store the affected sale rate delta on OrderUpdated events * Remove unnecessary parameterization * Index Ekubo MEV-resist pools * cargo clippy --- substreams/Cargo.lock | 3 +- substreams/ethereum-ekubo-v2/Cargo.toml | 3 +- substreams/ethereum-ekubo-v2/abi/twamm.json | 720 ++++++ substreams/ethereum-ekubo-v2/build.rs | 8 +- .../ethereum-ekubo-v2/proto/ekubo.proto | 92 +- substreams/ethereum-ekubo-v2/src/abi/mod.rs | 1 + substreams/ethereum-ekubo-v2/src/abi/twamm.rs | 2146 +++++++++++++++++ .../src/deployment_config.rs | 4 + substreams/ethereum-ekubo-v2/src/lib.rs | 4 +- .../src/modules/1_map_events.rs | 174 +- .../src/modules/2_map_components.rs | 96 +- .../modules/2_map_order_sale_rate_deltas.rs | 70 + .../src/modules/2_map_sale_rate_changes.rs | 71 + ...p_tick_changes.rs => 2_map_tick_deltas.rs} | 2 +- .../src/modules/3_map_liquidity_changes.rs | 8 +- .../src/modules/3_store_active_sale_rates.rs | 32 + .../src/modules/3_store_order_sale_rates.rs | 25 + .../src/modules/4_map_balance_changes.rs | 76 +- .../src/modules/4_store_active_liquidities.rs | 22 + .../src/modules/4_store_liquidities.rs | 30 - .../src/modules/6_map_protocol_changes.rs | 147 +- .../ethereum-ekubo-v2/src/modules/mod.rs | 16 +- substreams/ethereum-ekubo-v2/src/pb/ekubo.rs | 127 +- .../ethereum-ekubo-v2/src/pool_config.rs | 15 - substreams/ethereum-ekubo-v2/src/pool_key.rs | 83 + substreams/ethereum-ekubo-v2/src/store.rs | 12 + substreams/ethereum-ekubo-v2/src/twamm.rs | 27 + substreams/ethereum-ekubo-v2/substreams.yaml | 49 +- 28 files changed, 3750 insertions(+), 313 deletions(-) create mode 100644 substreams/ethereum-ekubo-v2/abi/twamm.json create mode 100644 substreams/ethereum-ekubo-v2/src/abi/twamm.rs create mode 100644 substreams/ethereum-ekubo-v2/src/modules/2_map_order_sale_rate_deltas.rs create mode 100644 substreams/ethereum-ekubo-v2/src/modules/2_map_sale_rate_changes.rs rename substreams/ethereum-ekubo-v2/src/modules/{2_map_tick_changes.rs => 2_map_tick_deltas.rs} (96%) create mode 100644 substreams/ethereum-ekubo-v2/src/modules/3_store_active_sale_rates.rs create mode 100644 substreams/ethereum-ekubo-v2/src/modules/3_store_order_sale_rates.rs create mode 100644 substreams/ethereum-ekubo-v2/src/modules/4_store_active_liquidities.rs delete mode 100644 substreams/ethereum-ekubo-v2/src/modules/4_store_liquidities.rs delete mode 100644 substreams/ethereum-ekubo-v2/src/pool_config.rs create mode 100644 substreams/ethereum-ekubo-v2/src/pool_key.rs create mode 100644 substreams/ethereum-ekubo-v2/src/store.rs create mode 100644 substreams/ethereum-ekubo-v2/src/twamm.rs diff --git a/substreams/Cargo.lock b/substreams/Cargo.lock index ca96549..4a8b62a 100644 --- a/substreams/Cargo.lock +++ b/substreams/Cargo.lock @@ -299,7 +299,7 @@ dependencies = [ [[package]] name = "ethereum-ekubo-v2" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "ethabi 18.0.0", @@ -312,6 +312,7 @@ dependencies = [ "substreams", "substreams-ethereum", "substreams-helper 0.0.2 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=e4609be)", + "tiny-keccak", "tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=3c08359)", ] diff --git a/substreams/ethereum-ekubo-v2/Cargo.toml b/substreams/ethereum-ekubo-v2/Cargo.toml index 3d50afc..9fa080d 100644 --- a/substreams/ethereum-ekubo-v2/Cargo.toml +++ b/substreams/ethereum-ekubo-v2/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethereum-ekubo-v2" -version = "0.1.0" +version = "0.2.0" edition = "2021" [lib] @@ -20,6 +20,7 @@ hex = { version = "0.4", features = ["serde"] } itertools = "0.10.5" serde = "1.0.217" serde_qs = "0.13.0" +tiny-keccak = { version = "2.0.2", features = ["keccak"] } [build-dependencies] anyhow = "1" diff --git a/substreams/ethereum-ekubo-v2/abi/twamm.json b/substreams/ethereum-ekubo-v2/abi/twamm.json new file mode 100644 index 0000000..bc2a069 --- /dev/null +++ b/substreams/ethereum-ekubo-v2/abi/twamm.json @@ -0,0 +1,720 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "core", + "type": "address", + "internalType": "contract ICore" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "afterCollectFees", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "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": "", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "", + "type": "tuple", + "internalType": "struct Bounds", + "components": [ + { + "name": "lower", + "type": "int32", + "internalType": "int32" + }, + { + "name": "upper", + "type": "int32", + "internalType": "int32" + } + ] + }, + { + "name": "", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "afterInitializePool", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "key", + "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": "", + "type": "int32", + "internalType": "int32" + }, + { + "name": "", + "type": "uint96", + "internalType": "SqrtRatio" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "afterSwap", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "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": "", + "type": "int128", + "internalType": "int128" + }, + { + "name": "", + "type": "bool", + "internalType": "bool" + }, + { + "name": "", + "type": "uint96", + "internalType": "SqrtRatio" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "int128", + "internalType": "int128" + }, + { + "name": "", + "type": "int128", + "internalType": "int128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "afterUpdatePosition", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "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": "", + "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" + } + ] + }, + { + "name": "", + "type": "int128", + "internalType": "int128" + }, + { + "name": "", + "type": "int128", + "internalType": "int128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "beforeCollectFees", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "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": "", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "", + "type": "tuple", + "internalType": "struct Bounds", + "components": [ + { + "name": "lower", + "type": "int32", + "internalType": "int32" + }, + { + "name": "upper", + "type": "int32", + "internalType": "int32" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "beforeInitializePool", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "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": "", + "type": "int32", + "internalType": "int32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "beforeSwap", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "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": "", + "type": "int128", + "internalType": "int128" + }, + { + "name": "", + "type": "bool", + "internalType": "bool" + }, + { + "name": "", + "type": "uint96", + "internalType": "SqrtRatio" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "beforeUpdatePosition", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "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": "", + "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": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "forwarded", + "inputs": [ + { + "name": "id", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "originalLocker", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "locked", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "sload", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "tload", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "OrderProceedsWithdrawn", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "orderKey", + "type": "tuple", + "indexed": false, + "internalType": "struct OrderKey", + "components": [ + { + "name": "sellToken", + "type": "address", + "internalType": "address" + }, + { + "name": "buyToken", + "type": "address", + "internalType": "address" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "startTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "endTime", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "amount", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OrderUpdated", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "orderKey", + "type": "tuple", + "indexed": false, + "internalType": "struct OrderKey", + "components": [ + { + "name": "sellToken", + "type": "address", + "internalType": "address" + }, + { + "name": "buyToken", + "type": "address", + "internalType": "address" + }, + { + "name": "fee", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "startTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "endTime", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "saleRateDelta", + "type": "int112", + "indexed": false, + "internalType": "int112" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "BaseForwardeeAccountantOnly", + "inputs": [] + }, + { + "type": "error", + "name": "CallPointNotImplemented", + "inputs": [] + }, + { + "type": "error", + "name": "CoreOnly", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidTimestamps", + "inputs": [] + }, + { + "type": "error", + "name": "MaxSaleRateDeltaPerTick", + "inputs": [] + }, + { + "type": "error", + "name": "MustCollectProceedsBeforeCanceling", + "inputs": [] + }, + { + "type": "error", + "name": "OrderAlreadyEnded", + "inputs": [] + }, + { + "type": "error", + "name": "TickSpacingMustBeMaximum", + "inputs": [] + }, + { + "type": "error", + "name": "TimeNumOrdersOverflow", + "inputs": [] + } +] \ No newline at end of file diff --git a/substreams/ethereum-ekubo-v2/build.rs b/substreams/ethereum-ekubo-v2/build.rs index 5e7bd34..c557d5b 100644 --- a/substreams/ethereum-ekubo-v2/build.rs +++ b/substreams/ethereum-ekubo-v2/build.rs @@ -26,10 +26,10 @@ fn main() -> Result<()> { 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); + let input_path = format!("{abi_folder}/{file_name}"); + let output_path = format!("{output_folder}/{contract_name}.rs"); - mod_rs_content.push_str(&format!("pub mod {};\n", contract_name)); + mod_rs_content.push_str(&format!("pub mod {contract_name};\n")); if std::path::Path::new(&output_path).exists() { continue; @@ -40,7 +40,7 @@ fn main() -> Result<()> { .write_to_file(&output_path)?; } - let mod_rs_path = format!("{}/mod.rs", output_folder); + let mod_rs_path = format!("{output_folder}/mod.rs"); let mut mod_rs_file = fs::File::create(mod_rs_path)?; mod_rs_file.write_all(mod_rs_content.as_bytes())?; diff --git a/substreams/ethereum-ekubo-v2/proto/ekubo.proto b/substreams/ethereum-ekubo-v2/proto/ekubo.proto index 45bb2b0..b7ba767 100644 --- a/substreams/ethereum-ekubo-v2/proto/ekubo.proto +++ b/substreams/ethereum-ekubo-v2/proto/ekubo.proto @@ -2,7 +2,8 @@ syntax = "proto3"; package ekubo; -// Copy of tycho.evm.v1.Transaction to be able to implement conversions to/from TransactionTrace +// Copy of tycho.evm.v1.Transaction to be able to implement conversions to/from +// TransactionTrace message Transaction { bytes hash = 1; bytes from = 2; @@ -18,27 +19,45 @@ message TickDelta { Transaction transaction = 5; } -message TickDeltas { - repeated TickDelta deltas = 1; +message TickDeltas { repeated TickDelta deltas = 1; } + +message OrderSaleRateDelta { + bytes pool_id = 1; // bytes32 + uint64 time = 2; + bytes sale_rate_delta = 3; // int112 + bool is_token1 = 4; + uint64 ordinal = 5; + Transaction transaction = 6; } -enum LiquidityChangeType { +message OrderSaleRateDeltas { repeated OrderSaleRateDelta deltas = 1; } + +enum ChangeType { 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; + bytes value = 2; // uint128 or int128, depending on change_type + ChangeType change_type = 3; uint64 ordinal = 4; Transaction transaction = 5; } -message LiquidityChanges { - repeated LiquidityChange changes = 1; +message LiquidityChanges { repeated LiquidityChange changes = 1; } + +message SaleRateChange { + bytes pool_id = 1; // bytes32 + bytes token0_value = 2; // uint112 or int112, depending on change_type + bytes token1_value = 3; // uint112 or int112, depending on change_type + ChangeType change_type = 4; + uint64 ordinal = 5; + Transaction transaction = 6; } +message SaleRateChanges { repeated SaleRateChange changes = 1; } + message PoolDetails { bytes token0 = 1; // address bytes token1 = 2; // address @@ -47,6 +66,7 @@ message PoolDetails { message BlockTransactionEvents { repeated TransactionEvents block_transaction_events = 1; + uint64 timestamp = 2; // block timestamp message TransactionEvents { Transaction transaction = 1; @@ -59,37 +79,32 @@ message BlockTransactionEvents { oneof event { Swapped swapped = 3; PositionUpdated position_updated = 4; - PositionFeesCollected position_fees_collected = 5; - PoolInitialized pool_initialized = 6; - FeesAccumulated fees_accumulated = 7; + PoolInitialized pool_initialized = 5; + VirtualOrdersExecuted virtual_orders_executed = 6; + OrderUpdated order_updated = 7; } message Swapped { - bytes delta0 = 1; // int128 - bytes delta1 = 2; // int128 + bytes delta0 = 1; // int128 + bytes delta1 = 2; // int128 bytes sqrt_ratio_after = 3; // uint192 - bytes liquidity_after = 4; // uint128 - sint32 tick_after = 5; // int32 + bytes liquidity_after = 4; // uint128 + sint32 tick_after = 5; // int32 } message PositionUpdated { - sint32 lower = 1; // int32 - sint32 upper = 2; // int32 + 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 + bytes delta0 = 4; // int128 + bytes delta1 = 5; // int128 } message PoolInitialized { - bytes token0 = 1; // address - bytes token1 = 2; // address - bytes config = 3; // bytes32 - sint32 tick = 4; // int32 + bytes token0 = 1; // address + bytes token1 = 2; // address + bytes config = 3; // bytes32 + sint32 tick = 4; // int32 bytes sqrt_ratio = 5; // uint192 Extension extension = 6; @@ -97,12 +112,27 @@ message BlockTransactionEvents { EXTENSION_UNKNOWN = 0; EXTENSION_BASE = 1; EXTENSION_ORACLE = 2; + EXTENSION_TWAMM = 3; + EXTENSION_MEV_RESIST = 4; } } - message FeesAccumulated { - bytes amount0 = 1; // uint128 - bytes amount1 = 2; // uint128 + message VirtualOrdersExecuted { + bytes token0_sale_rate = 1; // int112 + bytes token1_sale_rate = 2; // int112 + } + + message OrderUpdated { + OrderKey order_key = 1; + bytes sale_rate_delta = 2; // int112 + + message OrderKey { + bytes sell_token = 1; // address + bytes buy_token = 2; // address + fixed64 fee = 3; + uint64 start_time = 4; // block timestamp + uint64 end_time = 5; // block timestamp + } } } } diff --git a/substreams/ethereum-ekubo-v2/src/abi/mod.rs b/substreams/ethereum-ekubo-v2/src/abi/mod.rs index e871760..9bc3390 100644 --- a/substreams/ethereum-ekubo-v2/src/abi/mod.rs +++ b/substreams/ethereum-ekubo-v2/src/abi/mod.rs @@ -1,2 +1,3 @@ #![allow(clippy::all)] pub mod core; +pub mod twamm; diff --git a/substreams/ethereum-ekubo-v2/src/abi/twamm.rs b/substreams/ethereum-ekubo-v2/src/abi/twamm.rs new file mode 100644 index 0000000..49f3835 --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/abi/twamm.rs @@ -0,0 +1,2146 @@ +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 AfterCollectFees { + pub param0: Vec, + pub param1: (Vec, Vec, [u8; 32usize]), + pub param2: [u8; 32usize], + pub param3: (substreams::scalar::BigInt, substreams::scalar::BigInt), + pub param4: substreams::scalar::BigInt, + pub param5: substreams::scalar::BigInt, + } + impl AfterCollectFees { + const METHOD_ID: [u8; 4] = [205u8, 208u8, 250u8, 14u8]; + 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::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), + ]), + 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 { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param1: { + 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 + }, + ) + }, + param2: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + param3: { + 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) + }, + ) + }, + param4: { + 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) + }, + param5: { + 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.param0)), + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.1)), + ethabi::Token::FixedBytes(self.param1.2.as_ref().to_vec()), + ]), + ethabi::Token::FixedBytes(self.param2.as_ref().to_vec()), + ethabi::Token::Tuple(vec![ + { + let non_full_signed_bytes = self.param3.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.param3.1.to_signed_bytes_be(); + let full_signed_bytes_init = + if non_full_signed_bytes[0] & 0x80 == 0x80 { 0xff } else { 0x00 }; + let mut full_signed_bytes = [full_signed_bytes_init as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ]), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param4.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.param5.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 AfterCollectFees { + const NAME: &'static str = "afterCollectFees"; + 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 AfterInitializePool { + pub param0: Vec, + pub key: (Vec, Vec, [u8; 32usize]), + pub param2: substreams::scalar::BigInt, + pub param3: substreams::scalar::BigInt, + } + impl AfterInitializePool { + const METHOD_ID: [u8; 4] = [148u8, 131u8, 116u8, 255u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Tuple(vec![ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ]), + ethabi::ParamType::Int(32usize), + ethabi::ParamType::Uint(96usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + 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 + }, + ) + }, + param2: { + 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) + }, + param3: { + 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.param0)), + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.key.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.key.1)), + ethabi::Token::FixedBytes(self.key.2.as_ref().to_vec()), + ]), + { + let non_full_signed_bytes = self.param2.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.param3.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 AfterInitializePool { + const NAME: &'static str = "afterInitializePool"; + 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 AfterSwap { + pub param0: Vec, + pub param1: (Vec, Vec, [u8; 32usize]), + pub param2: substreams::scalar::BigInt, + pub param3: bool, + pub param4: substreams::scalar::BigInt, + pub param5: substreams::scalar::BigInt, + pub param6: substreams::scalar::BigInt, + pub param7: substreams::scalar::BigInt, + } + impl AfterSwap { + const METHOD_ID: [u8; 4] = [192u8, 87u8, 138u8, 187u8]; + 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::Tuple(vec![ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ]), + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Bool, + ethabi::ParamType::Uint(96usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Int(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param1: { + 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 + }, + ) + }, + param2: { + 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) + }, + param3: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + param4: { + 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) + }, + param5: { + 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) + }, + param6: { + 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) + }, + param7: { + 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::Address(ethabi::Address::from_slice(&self.param0)), + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.1)), + ethabi::Token::FixedBytes(self.param1.2.as_ref().to_vec()), + ]), + { + let non_full_signed_bytes = self.param2.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.param3.clone()), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param4.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.param5.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + { + let non_full_signed_bytes = self.param6.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.param7.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 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 AfterSwap { + const NAME: &'static str = "afterSwap"; + 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 AfterUpdatePosition { + pub param0: Vec, + pub param1: (Vec, Vec, [u8; 32usize]), + pub param2: ( + [u8; 32usize], + (substreams::scalar::BigInt, substreams::scalar::BigInt), + substreams::scalar::BigInt, + ), + pub param3: substreams::scalar::BigInt, + pub param4: substreams::scalar::BigInt, + } + impl AfterUpdatePosition { + const METHOD_ID: [u8; 4] = [60u8, 133u8, 229u8, 161u8]; + 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::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), + ]), + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Int(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param1: { + 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 + }, + ) + }, + param2: { + 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) + }, + ) + }, + param3: { + 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) + }, + param4: { + 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::Address(ethabi::Address::from_slice(&self.param0)), + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.1)), + ethabi::Token::FixedBytes(self.param1.2.as_ref().to_vec()), + ]), + ethabi::Token::Tuple(vec![ + ethabi::Token::FixedBytes(self.param2.0.as_ref().to_vec()), + ethabi::Token::Tuple(vec![ + { + let non_full_signed_bytes = self.param2.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.param2.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.param2.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 non_full_signed_bytes = self.param3.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.param4.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 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 AfterUpdatePosition { + const NAME: &'static str = "afterUpdatePosition"; + 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 BeforeCollectFees { + pub param0: Vec, + pub param1: (Vec, Vec, [u8; 32usize]), + pub param2: [u8; 32usize], + pub param3: (substreams::scalar::BigInt, substreams::scalar::BigInt), + } + impl BeforeCollectFees { + const METHOD_ID: [u8; 4] = [111u8, 181u8, 191u8, 227u8]; + 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::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 { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param1: { + 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 + }, + ) + }, + param2: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + param3: { + 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::Address(ethabi::Address::from_slice(&self.param0)), + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.1)), + ethabi::Token::FixedBytes(self.param1.2.as_ref().to_vec()), + ]), + ethabi::Token::FixedBytes(self.param2.as_ref().to_vec()), + ethabi::Token::Tuple(vec![ + { + let non_full_signed_bytes = self.param3.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.param3.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 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 BeforeCollectFees { + const NAME: &'static str = "beforeCollectFees"; + 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 BeforeInitializePool { + pub param0: Vec, + pub param1: (Vec, Vec, [u8; 32usize]), + pub param2: substreams::scalar::BigInt, + } + impl BeforeInitializePool { + const METHOD_ID: [u8; 4] = [31u8, 187u8, 180u8, 98u8]; + 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::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 { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param1: { + 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 + }, + ) + }, + param2: { + 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::Address(ethabi::Address::from_slice(&self.param0)), + ethabi::Token::Tuple(vec![ + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1.1)), + ethabi::Token::FixedBytes(self.param1.2.as_ref().to_vec()), + ]), + { + let non_full_signed_bytes = self.param2.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 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 BeforeInitializePool { + const NAME: &'static str = "beforeInitializePool"; + 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 BeforeSwap { + pub param0: Vec, + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub param2: substreams::scalar::BigInt, + pub param3: bool, + pub param4: substreams::scalar::BigInt, + pub param5: substreams::scalar::BigInt, + } + impl BeforeSwap { + const METHOD_ID: [u8; 4] = [60u8, 101u8, 200u8, 122u8]; + 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::Tuple(vec![ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ]), + 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 { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + 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 + }, + ) + }, + param2: { + 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) + }, + param3: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + param4: { + 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) + }, + param5: { + 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.param0)), + 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.param2.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.param3.clone()), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param4.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.param5.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 BeforeSwap { + const NAME: &'static str = "beforeSwap"; + 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 BeforeUpdatePosition { + pub param0: Vec, + pub pool_key: (Vec, Vec, [u8; 32usize]), + pub param2: ( + [u8; 32usize], + (substreams::scalar::BigInt, substreams::scalar::BigInt), + substreams::scalar::BigInt, + ), + } + impl BeforeUpdatePosition { + const METHOD_ID: [u8; 4] = [177u8, 75u8, 16u8, 109u8]; + 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::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 { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + 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 + }, + ) + }, + param2: { + 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::Address(ethabi::Address::from_slice(&self.param0)), + 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.param2.0.as_ref().to_vec()), + ethabi::Token::Tuple(vec![ + { + let non_full_signed_bytes = self.param2.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.param2.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.param2.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 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 BeforeUpdatePosition { + const NAME: &'static str = "beforeUpdatePosition"; + 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 Forwarded { + pub id: substreams::scalar::BigInt, + pub original_locker: Vec, + } + impl Forwarded { + const METHOD_ID: [u8; 4] = [100u8, 145u8, 157u8, 234u8]; + 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::Uint(256usize), ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + original_locker: 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::Uint(ethabi::Uint::from_big_endian( + match self.id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Address(ethabi::Address::from_slice(&self.original_locker)), + ]); + 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 Forwarded { + const NAME: &'static str = "forwarded"; + 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 Locked { + pub param0: substreams::scalar::BigInt, + } + impl Locked { + const METHOD_ID: [u8; 4] = [180u8, 90u8, 60u8, 14u8]; + 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::Uint(256usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + 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::Uint(ethabi::Uint::from_big_endian( + match self.param0.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 Locked { + const NAME: &'static str = "locked"; + 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 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) + } + } +} +/// Contract's events. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct OrderProceedsWithdrawn { + pub owner: Vec, + pub salt: [u8; 32usize], + pub order_key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + pub amount: substreams::scalar::BigInt, + } + impl OrderProceedsWithdrawn { + const TOPIC_ID: [u8; 32] = [ + 127u8, 206u8, 0u8, 95u8, 134u8, 213u8, 36u8, 183u8, 128u8, 196u8, 31u8, 139u8, 244u8, + 91u8, 0u8, 43u8, 239u8, 49u8, 169u8, 213u8, 118u8, 230u8, 68u8, 152u8, 178u8, 122u8, + 237u8, 239u8, 79u8, 247u8, 34u8, 10u8, + ]; + 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::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(64usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ]), + 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(), + 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 + }, + order_key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_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[4usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + ) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for OrderProceedsWithdrawn { + const NAME: &'static str = "OrderProceedsWithdrawn"; + 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 OrderUpdated { + pub owner: Vec, + pub salt: [u8; 32usize], + pub order_key: ( + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + pub sale_rate_delta: substreams::scalar::BigInt, + } + impl OrderUpdated { + const TOPIC_ID: [u8; 32] = [ + 124u8, 138u8, 114u8, 135u8, 20u8, 157u8, 211u8, 140u8, 207u8, 38u8, 156u8, 96u8, 196u8, + 7u8, 74u8, 145u8, 173u8, 185u8, 154u8, 157u8, 249u8, 11u8, 255u8, 82u8, 78u8, 31u8, + 117u8, 211u8, 233u8, 158u8, 27u8, 11u8, + ]; + 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::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(64usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ]), + ethabi::ParamType::Int(112usize), + ], + 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(), + 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 + }, + order_key: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + tuple_elements[0usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tuple_elements[1usize] + .clone() + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_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[4usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + ) + }, + sale_rate_delta: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for OrderUpdated { + const NAME: &'static str = "OrderUpdated"; + 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-v2/src/deployment_config.rs b/substreams/ethereum-ekubo-v2/src/deployment_config.rs index 6a56353..ae3742b 100644 --- a/substreams/ethereum-ekubo-v2/src/deployment_config.rs +++ b/substreams/ethereum-ekubo-v2/src/deployment_config.rs @@ -6,4 +6,8 @@ pub struct DeploymentConfig { pub core: Vec, #[serde(with = "hex::serde")] pub oracle: Vec, + #[serde(with = "hex::serde")] + pub twamm: Vec, + #[serde(with = "hex::serde")] + pub mev_resist: Vec, } diff --git a/substreams/ethereum-ekubo-v2/src/lib.rs b/substreams/ethereum-ekubo-v2/src/lib.rs index 14bc006..1b99a2d 100644 --- a/substreams/ethereum-ekubo-v2/src/lib.rs +++ b/substreams/ethereum-ekubo-v2/src/lib.rs @@ -2,5 +2,7 @@ mod abi; mod deployment_config; mod modules; mod pb; -mod pool_config; +mod pool_key; mod sqrt_ratio; +mod store; +mod twamm; diff --git a/substreams/ethereum-ekubo-v2/src/modules/1_map_events.rs b/substreams/ethereum-ekubo-v2/src/modules/1_map_events.rs index a0815f8..cb575a7 100644 --- a/substreams/ethereum-ekubo-v2/src/modules/1_map_events.rs +++ b/substreams/ethereum-ekubo-v2/src/modules/1_map_events.rs @@ -7,14 +7,14 @@ use substreams_ethereum::{ }; use crate::{ - abi::core::events as abi_events, + abi::{core::events as core_events, twamm::events as twamm_events}, deployment_config::DeploymentConfig, pb::ekubo::{ block_transaction_events::{ transaction_events::{ pool_log::{ - pool_initialized::Extension, Event, FeesAccumulated, PoolInitialized, - PositionFeesCollected, PositionUpdated, Swapped, + order_updated::OrderKey, pool_initialized::Extension, Event, OrderUpdated, + PoolInitialized, PositionUpdated, Swapped, VirtualOrdersExecuted, }, PoolLog, }, @@ -22,7 +22,7 @@ use crate::{ }, BlockTransactionEvents, }, - pool_config::PoolConfig, + pool_key::{PoolConfig, PoolKey}, sqrt_ratio::float_sqrt_ratio_to_fixed, }; @@ -43,84 +43,114 @@ fn map_events(params: String, block: eth::v2::Block) -> BlockTransactionEvents { .then(|| TransactionEvents { transaction: Some(trace.into()), pool_logs }) }) .collect(), + timestamp: block + .header + .as_ref() + .unwrap() + .timestamp + .as_ref() + .unwrap() + .seconds + .try_into() + .unwrap(), } } fn maybe_pool_log(log: &Log, config: &DeploymentConfig) -> Option { - if log.address != config.core { - return None; - } + let (pool_id, ev) = if log.address == config.core { + if log.topics.is_empty() { + let data = &log.data; - let (pool_id, ev) = if log.topics.is_empty() { - let data = &log.data; + assert!(data.len() == 116, "swap event data length mismatch"); - 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) = core_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) = core_events::PoolInitialized::match_and_decode(log) { + let pool_config = PoolConfig::from(ev.pool_key.2); - ( - 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.as_bytes(); - let extension = { - let extension = pool_config.extension; + if extension == Address::zero().as_bytes() { + Extension::Base + } else if extension == config.oracle { + Extension::Oracle + } else if extension == config.twamm { + Extension::Twamm + } else if extension == config.mev_resist { + Extension::MevResist + } else { + Extension::Unknown + } + }; - 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 { + return None; + } + } else if log.address == config.twamm { + if log.topics.is_empty() { + let data = &log.data; - ( - 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, - }), - ) + assert!(data.len() == 60, "virtual orders executed event data length mismatch"); + + ( + data[0..32].to_vec(), + Event::VirtualOrdersExecuted(VirtualOrdersExecuted { + token0_sale_rate: data[32..46].to_vec(), + token1_sale_rate: data[46..60].to_vec(), + }), + ) + } else if let Some(ev) = twamm_events::OrderUpdated::match_and_decode(log) { + let key = ev.order_key; + + ( + PoolKey::from_order_key(&key, &log.address).into_pool_id(), + Event::OrderUpdated(OrderUpdated { + order_key: Some(OrderKey { + sell_token: key.0, + buy_token: key.1, + fee: key.2.to_u64(), + start_time: key.3.to_u64(), + end_time: key.4.to_u64(), + }), + sale_rate_delta: ev.sale_rate_delta.to_signed_bytes_be(), + }), + ) + } else { + return None; + } } else { return None; }; diff --git a/substreams/ethereum-ekubo-v2/src/modules/2_map_components.rs b/substreams/ethereum-ekubo-v2/src/modules/2_map_components.rs index 41ed9ee..b959c83 100644 --- a/substreams/ethereum-ekubo-v2/src/modules/2_map_components.rs +++ b/substreams/ethereum-ekubo-v2/src/modules/2_map_components.rs @@ -8,10 +8,13 @@ use tycho_substreams::models::{ use crate::{ pb::ekubo::{ - block_transaction_events::transaction_events::{pool_log::Event, PoolLog}, + block_transaction_events::transaction_events::{ + pool_log::{pool_initialized::Extension, Event}, + PoolLog, + }, BlockTransactionEvents, }, - pool_config::PoolConfig, + pool_key::PoolConfig, }; #[substreams::handlers::map] @@ -25,7 +28,7 @@ fn map_components(block_tx_events: BlockTransactionEvents) -> BlockChanges { let (components, entities, balance_changes): (Vec<_>, Vec<_>, Vec<_>) = tx_events .pool_logs .into_iter() - .filter_map(maybe_create_component) + .filter_map(|log| maybe_create_component(log, block_tx_events.timestamp)) .multiunzip(); (!components.is_empty()).then(|| TransactionChanges { @@ -45,8 +48,56 @@ fn map_components(block_tx_events: BlockTransactionEvents) -> BlockChanges { fn maybe_create_component( log: PoolLog, + timestamp: u64, ) -> Option<(ProtocolComponent, EntityChanges, Vec)> { if let Event::PoolInitialized(pi) = log.event.unwrap() { + let entity_attributes = (pi.extension() == Extension::Twamm) + .then(|| { + [ + Attribute { + change: ChangeType::Creation.into(), + name: "token0_sale_rate".to_string(), + value: vec![], + }, + Attribute { + change: ChangeType::Creation.into(), + name: "token1_sale_rate".to_string(), + value: vec![], + }, + Attribute { + change: ChangeType::Creation.into(), + name: "last_execution_time".to_string(), + value: timestamp.to_be_bytes().to_vec(), + }, + ] + }) + .into_iter() + .flatten() + .chain([ + 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, + }, + Attribute { + change: ChangeType::Creation.into(), + name: "balance_owner".to_string(), /* TODO: We should use AccountBalances + * instead */ + value: hex!("e0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444").to_vec(), + }, + ]) + .collect(); + let config = PoolConfig::from(<[u8; 32]>::try_from(pi.config).unwrap()); let component_id = log.pool_id.to_hex(); @@ -77,17 +128,23 @@ fn maybe_create_component( Attribute { change: ChangeType::Creation.into(), name: "fee".to_string(), - value: config.fee, + value: config.fee.to_be_bytes().to_vec(), }, Attribute { change: ChangeType::Creation.into(), name: "tick_spacing".to_string(), - value: config.tick_spacing, + value: config + .tick_spacing + .to_be_bytes() + .to_vec(), }, Attribute { change: ChangeType::Creation.into(), name: "extension".to_string(), - value: config.extension, + value: config + .extension + .to_fixed_bytes() + .to_vec(), }, Attribute { change: ChangeType::Creation.into(), @@ -96,32 +153,7 @@ fn maybe_create_component( }, ], }, - 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, - }, - Attribute { - change: ChangeType::Creation.into(), - name: "balance_owner".to_string(), /* TODO: We should use AccountBalances - * instead */ - value: hex!("e0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444").to_vec(), - }, - ], - }, + EntityChanges { component_id: component_id.clone(), attributes: entity_attributes }, vec![ BalanceChange { component_id: component_id.clone().into_bytes(), diff --git a/substreams/ethereum-ekubo-v2/src/modules/2_map_order_sale_rate_deltas.rs b/substreams/ethereum-ekubo-v2/src/modules/2_map_order_sale_rate_deltas.rs new file mode 100644 index 0000000..fc33d31 --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/modules/2_map_order_sale_rate_deltas.rs @@ -0,0 +1,70 @@ +use substreams::scalar::BigInt; + +use crate::pb::ekubo::{ + block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents, + OrderSaleRateDelta, OrderSaleRateDeltas, +}; + +#[substreams::handlers::map] +pub fn map_order_sale_rate_deltas(block_tx_events: BlockTransactionEvents) -> OrderSaleRateDeltas { + OrderSaleRateDeltas { + 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(); + + order_sale_rate_deltas(log.event.unwrap()) + .into_iter() + .map(move |partial| OrderSaleRateDelta { + pool_id: log.pool_id.clone(), + time: partial.time, + sale_rate_delta: partial.sale_rate_delta, + is_token1: partial.is_token1, + ordinal: log.ordinal, + transaction: tx.clone(), + }) + }) + }) + .collect(), + } +} + +struct PartialOrderSaleRateDelta { + time: u64, + sale_rate_delta: Vec, + is_token1: bool, +} + +fn order_sale_rate_deltas(ev: Event) -> Vec { + match ev { + Event::OrderUpdated(ev) => { + let key = ev.order_key.unwrap(); + + let is_token1 = key.sell_token > key.buy_token; + let sale_rate_delta = ev.sale_rate_delta; + + vec![ + PartialOrderSaleRateDelta { + time: key.start_time, + sale_rate_delta: sale_rate_delta.clone(), + is_token1, + }, + PartialOrderSaleRateDelta { + time: key.end_time, + sale_rate_delta: BigInt::from_signed_bytes_be(&sale_rate_delta) + .neg() + .to_signed_bytes_be(), + is_token1, + }, + ] + } + _ => vec![], + } +} diff --git a/substreams/ethereum-ekubo-v2/src/modules/2_map_sale_rate_changes.rs b/substreams/ethereum-ekubo-v2/src/modules/2_map_sale_rate_changes.rs new file mode 100644 index 0000000..f03b3b6 --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/modules/2_map_sale_rate_changes.rs @@ -0,0 +1,71 @@ +use crate::pb::ekubo::{ + block_transaction_events::transaction_events::{pool_log::Event, PoolLog}, + BlockTransactionEvents, ChangeType, SaleRateChange, SaleRateChanges, +}; + +#[substreams::handlers::map] +pub fn map_sale_rate_changes(block_tx_events: BlockTransactionEvents) -> SaleRateChanges { + SaleRateChanges { + changes: block_tx_events + .block_transaction_events + .into_iter() + .flat_map(|tx_events| { + tx_events + .pool_logs + .into_iter() + .filter_map(move |log| { + maybe_sale_rate_change(&log, block_tx_events.timestamp).map(|partial| { + SaleRateChange { + change_type: partial.change_type.into(), + pool_id: log.pool_id, + token0_value: partial.token0_value, + token1_value: partial.token1_value, + ordinal: log.ordinal, + transaction: tx_events.transaction.clone(), + } + }) + }) + }) + .collect(), + } +} + +struct PartialSaleRateChange { + token0_value: Vec, + token1_value: Vec, + change_type: ChangeType, +} + +fn maybe_sale_rate_change(log: &PoolLog, timestamp: u64) -> Option { + match log.event.as_ref().unwrap() { + Event::VirtualOrdersExecuted(ev) => Some(PartialSaleRateChange { + change_type: ChangeType::Absolute, + token0_value: ev.token0_sale_rate.clone(), + token1_value: ev.token1_sale_rate.clone(), + }), + Event::OrderUpdated(ev) => { + // A virtual order execution always happens before an order update + let last_execution_time = timestamp; + + let key = ev.order_key.as_ref().unwrap(); + + (last_execution_time >= key.start_time && last_execution_time < key.end_time).then( + || { + let (token0_sale_rate_delta, token1_sale_rate_delta) = + if key.sell_token > key.buy_token { + (vec![], ev.sale_rate_delta.clone()) + } else { + (ev.sale_rate_delta.clone(), vec![]) + }; + + PartialSaleRateChange { + change_type: ChangeType::Delta, + token0_value: token0_sale_rate_delta, + token1_value: token1_sale_rate_delta, + } + }, + ) + } + _ => None, + } +} diff --git a/substreams/ethereum-ekubo-v2/src/modules/2_map_tick_changes.rs b/substreams/ethereum-ekubo-v2/src/modules/2_map_tick_deltas.rs similarity index 96% rename from substreams/ethereum-ekubo-v2/src/modules/2_map_tick_changes.rs rename to substreams/ethereum-ekubo-v2/src/modules/2_map_tick_deltas.rs index 059cb19..cf6beb7 100644 --- a/substreams/ethereum-ekubo-v2/src/modules/2_map_tick_changes.rs +++ b/substreams/ethereum-ekubo-v2/src/modules/2_map_tick_deltas.rs @@ -6,7 +6,7 @@ use crate::pb::ekubo::{ }; #[substreams::handlers::map] -pub fn map_tick_changes(block_tx_events: BlockTransactionEvents) -> TickDeltas { +pub fn map_tick_deltas(block_tx_events: BlockTransactionEvents) -> TickDeltas { TickDeltas { deltas: block_tx_events .block_transaction_events diff --git a/substreams/ethereum-ekubo-v2/src/modules/3_map_liquidity_changes.rs b/substreams/ethereum-ekubo-v2/src/modules/3_map_liquidity_changes.rs index c6cd53f..2f84000 100644 --- a/substreams/ethereum-ekubo-v2/src/modules/3_map_liquidity_changes.rs +++ b/substreams/ethereum-ekubo-v2/src/modules/3_map_liquidity_changes.rs @@ -4,7 +4,7 @@ use substreams_helper::hex::Hexable; use crate::pb::ekubo::{ block_transaction_events::transaction_events::{pool_log::Event, PoolLog}, - BlockTransactionEvents, LiquidityChange, LiquidityChangeType, LiquidityChanges, + BlockTransactionEvents, ChangeType, LiquidityChange, LiquidityChanges, }; #[substreams::handlers::map] @@ -40,7 +40,7 @@ pub fn map_liquidity_changes( struct PartialLiquidityChange { value: Vec, - change_type: LiquidityChangeType, + change_type: ChangeType, } fn maybe_liquidity_change( @@ -50,7 +50,7 @@ fn maybe_liquidity_change( match log.event.as_ref().unwrap() { Event::Swapped(swapped) => Some(PartialLiquidityChange { value: swapped.liquidity_after.clone(), - change_type: LiquidityChangeType::Absolute, + change_type: ChangeType::Absolute, }), Event::PositionUpdated(position_updated) => { let current_tick = current_tick_store @@ -61,7 +61,7 @@ fn maybe_liquidity_change( current_tick < position_updated.upper.into()) .then(|| PartialLiquidityChange { value: position_updated.liquidity_delta.clone(), - change_type: LiquidityChangeType::Delta, + change_type: ChangeType::Delta, }) } _ => None, diff --git a/substreams/ethereum-ekubo-v2/src/modules/3_store_active_sale_rates.rs b/substreams/ethereum-ekubo-v2/src/modules/3_store_active_sale_rates.rs new file mode 100644 index 0000000..89061b5 --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/modules/3_store_active_sale_rates.rs @@ -0,0 +1,32 @@ +use substreams::{ + scalar::BigInt, + store::{StoreSetSum, StoreSetSumBigInt}, +}; +use substreams_helper::hex::Hexable; + +use crate::{pb::ekubo::SaleRateChanges, store::store_method_from_change_type}; + +#[substreams::handlers::store] +pub fn store_active_sale_rates(sale_rate_changes: SaleRateChanges, store: StoreSetSumBigInt) { + sale_rate_changes + .changes + .into_iter() + .for_each(|changes| { + let pool_id = changes.pool_id.to_hex(); + + let store_method = store_method_from_change_type(changes.change_type()); + + store_method( + &store, + changes.ordinal, + format!("pool:{pool_id}:token0"), + BigInt::from_signed_bytes_be(&changes.token0_value), + ); + store_method( + &store, + changes.ordinal, + format!("pool:{pool_id}:token1"), + BigInt::from_signed_bytes_be(&changes.token1_value), + ); + }); +} diff --git a/substreams/ethereum-ekubo-v2/src/modules/3_store_order_sale_rates.rs b/substreams/ethereum-ekubo-v2/src/modules/3_store_order_sale_rates.rs new file mode 100644 index 0000000..f315bbf --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/modules/3_store_order_sale_rates.rs @@ -0,0 +1,25 @@ +use substreams::{ + scalar::BigInt, + store::{StoreAdd, StoreAddBigInt, StoreNew}, +}; +use substreams_helper::hex::Hexable; + +use crate::pb::ekubo::OrderSaleRateDeltas; + +#[substreams::handlers::store] +pub fn store_order_sale_rates(order_sale_rate_deltas: OrderSaleRateDeltas, store: StoreAddBigInt) { + order_sale_rate_deltas + .deltas + .into_iter() + .for_each(|delta| { + let pool_id = delta.pool_id.to_hex(); + let time = delta.time; + let token = if delta.is_token1 { "token1" } else { "token0" }; + + store.add( + delta.ordinal, + format!("pool:{pool_id}:{token}:time:{time}:"), + BigInt::from_signed_bytes_be(&delta.sale_rate_delta), + ); + }); +} diff --git a/substreams/ethereum-ekubo-v2/src/modules/4_map_balance_changes.rs b/substreams/ethereum-ekubo-v2/src/modules/4_map_balance_changes.rs index 5af0acd..2d57514 100644 --- a/substreams/ethereum-ekubo-v2/src/modules/4_map_balance_changes.rs +++ b/substreams/ethereum-ekubo-v2/src/modules/4_map_balance_changes.rs @@ -57,54 +57,28 @@ struct ReducedBalanceDelta { 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 }, - ] - } + Event::Swapped(ev) => vec![ + ReducedBalanceDelta { token: pool_details.token0, delta: ev.delta0 }, + ReducedBalanceDelta { token: pool_details.token1, delta: ev.delta1 }, + ], + Event::PositionUpdated(ev) => vec![ + ReducedBalanceDelta { + token: pool_details.token0, + delta: adjust_delta_by_fee( + BigInt::from_signed_bytes_be(&ev.delta0), + pool_details.fee, + ) + .to_signed_bytes_be(), + }, + ReducedBalanceDelta { + token: pool_details.token1, + delta: adjust_delta_by_fee( + BigInt::from_signed_bytes_be(&ev.delta1), + pool_details.fee, + ) + .to_signed_bytes_be(), + }, + ], _ => vec![], } } @@ -113,8 +87,10 @@ fn balance_deltas(ev: Event, pool_details: PoolDetails) -> Vec BigInt { if delta < BigInt::zero() { - let denom = BigInt::from_signed_bytes_be(&hex!("0100000000000000000000000000000000")); - (delta * denom.clone()) / (denom - fee) + let denom = BigInt::from_signed_bytes_be(&hex!("010000000000000000")); + let (quotient, remainder) = (delta * denom.clone()).div_rem(&(denom - fee)); + + quotient - (!remainder.is_zero()) as u8 } else { delta } diff --git a/substreams/ethereum-ekubo-v2/src/modules/4_store_active_liquidities.rs b/substreams/ethereum-ekubo-v2/src/modules/4_store_active_liquidities.rs new file mode 100644 index 0000000..373f44e --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/modules/4_store_active_liquidities.rs @@ -0,0 +1,22 @@ +use substreams::{ + scalar::BigInt, + store::{StoreSetSum, StoreSetSumBigInt}, +}; +use substreams_helper::hex::Hexable; + +use crate::{pb::ekubo::LiquidityChanges, store::store_method_from_change_type}; + +#[substreams::handlers::store] +pub fn store_active_liquidities(liquidity_changes: LiquidityChanges, store: StoreSetSumBigInt) { + liquidity_changes + .changes + .into_iter() + .for_each(|changes| { + store_method_from_change_type(changes.change_type())( + &store, + changes.ordinal, + format!("pool:{}", changes.pool_id.to_hex()), + BigInt::from_signed_bytes_be(&changes.value), + ); + }); +} diff --git a/substreams/ethereum-ekubo-v2/src/modules/4_store_liquidities.rs b/substreams/ethereum-ekubo-v2/src/modules/4_store_liquidities.rs deleted file mode 100644 index 19717ca..0000000 --- a/substreams/ethereum-ekubo-v2/src/modules/4_store_liquidities.rs +++ /dev/null @@ -1,30 +0,0 @@ -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-v2/src/modules/6_map_protocol_changes.rs b/substreams/ethereum-ekubo-v2/src/modules/6_map_protocol_changes.rs index dcf4d45..454367e 100644 --- a/substreams/ethereum-ekubo-v2/src/modules/6_map_protocol_changes.rs +++ b/substreams/ethereum-ekubo-v2/src/modules/6_map_protocol_changes.rs @@ -14,7 +14,7 @@ use tycho_substreams::{ use crate::pb::ekubo::{ block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents, - LiquidityChanges, TickDeltas, + LiquidityChanges, OrderSaleRateDeltas, SaleRateChanges, TickDeltas, }; /// Aggregates protocol components and balance changes by transaction. @@ -30,8 +30,12 @@ fn map_protocol_changes( balances_store_deltas: StoreDeltas, ticks_map_deltas: TickDeltas, ticks_store_deltas: StoreDeltas, + order_sale_rate_map_deltas: OrderSaleRateDeltas, + order_sale_rate_store_deltas: StoreDeltas, liquidity_changes: LiquidityChanges, liquidity_store_deltas: StoreDeltas, + sale_rate_changes: SaleRateChanges, + sale_rate_store_deltas: StoreDeltas, ) -> Result { let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new(); @@ -90,21 +94,11 @@ fn map_protocol_changes( .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 (old_value, new_value) = ( + BigInt::from_store_bytes(&store_delta.old_value), + 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) @@ -112,7 +106,39 @@ fn map_protocol_changes( builder.add_entity_change(&EntityChanges { component_id: tick_delta.pool_id.to_hex(), - attributes: vec![attribute], + attributes: vec![Attribute { + name: format!("ticks/{}", tick_delta.tick_index), + value: new_value.to_signed_bytes_be(), + change: change_type_from_delta(&old_value, &new_value).into(), + }], + }); + }); + + // TWAMM order sale rate deltas + order_sale_rate_store_deltas + .deltas + .into_iter() + .zip(order_sale_rate_map_deltas.deltas) + .for_each(|(store_delta, sale_rate_delta)| { + let tx = sale_rate_delta.transaction.unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx.into())); + + let (old_value, new_value) = ( + BigInt::from_store_bytes(&store_delta.old_value), + BigInt::from_store_bytes(&store_delta.new_value), + ); + + let token = if sale_rate_delta.is_token1 { "token1" } else { "token0" }; + + builder.add_entity_change(&EntityChanges { + component_id: sale_rate_delta.pool_id.to_hex(), + attributes: vec![Attribute { + name: format!("orders/{}/{}", token, sale_rate_delta.time), + value: new_value.to_signed_bytes_be(), + change: change_type_from_delta(&old_value, &new_value).into(), + }], }); }); @@ -127,22 +153,50 @@ fn map_protocol_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(), + value: bigint_from_set_sum_store_delta_value(store_delta.new_value) + .to_signed_bytes_be(), change: ChangeType::Update.into(), }], }); }); + // TWAMM active sale rates + sale_rate_store_deltas + .deltas + .chunks(2) + .zip(sale_rate_changes.changes) + .for_each(|(store_deltas, change)| { + let tx = change.transaction.unwrap(); + let builder = transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChangesBuilder::new(&tx.into())); + + let (token0_sale_rate, token1_sale_rate) = ( + bigint_from_set_sum_store_delta_value(store_deltas[0].new_value.clone()), + bigint_from_set_sum_store_delta_value(store_deltas[1].new_value.clone()), + ); + + builder.add_entity_change(&EntityChanges { + component_id: change.pool_id.to_hex(), + attributes: vec![ + Attribute { + name: "token0_sale_rate".to_string(), + value: token0_sale_rate.to_bytes_be().1, + change: ChangeType::Update.into(), + }, + Attribute { + name: "token1_sale_rate".to_string(), + value: token1_sale_rate.to_bytes_be().1, + change: ChangeType::Update.into(), + }, + ], + }); + }); + // Remaining event changes not subject to special treatment block_tx_events .block_transaction_events @@ -156,12 +210,17 @@ fn map_protocol_changes( .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 }, - ) - }) + maybe_attribute_updates(log.event.unwrap(), block_tx_events.timestamp).map( + |attrs| { + ( + tx, + EntityChanges { + component_id: log.pool_id.to_hex(), + attributes: attrs, + }, + ) + }, + ) }) }) .for_each(|(tx, entity_changes)| { @@ -181,23 +240,39 @@ fn map_protocol_changes( }) } -fn maybe_attribute_updates(ev: Event) -> Option> { +fn maybe_attribute_updates(ev: Event, timestamp: u64) -> Option> { match ev { - Event::Swapped(swapped) => Some(vec![ + Event::Swapped(ev) => Some(vec![ Attribute { name: "tick".into(), - value: swapped - .tick_after - .to_be_bytes() - .to_vec(), + value: ev.tick_after.to_be_bytes().to_vec(), change: ChangeType::Update.into(), }, Attribute { name: "sqrt_ratio".into(), - value: swapped.sqrt_ratio_after, + value: ev.sqrt_ratio_after, change: ChangeType::Update.into(), }, ]), + Event::VirtualOrdersExecuted(_) => Some(vec![Attribute { + name: "last_execution_time".to_string(), + value: timestamp.to_be_bytes().to_vec(), + change: ChangeType::Update.into(), + }]), _ => None, } } + +fn change_type_from_delta(old_value: &BigInt, new_value: &BigInt) -> ChangeType { + if old_value.is_zero() { + ChangeType::Creation + } else if new_value.is_zero() { + ChangeType::Deletion + } else { + ChangeType::Update + } +} + +fn bigint_from_set_sum_store_delta_value(value: Vec) -> BigInt { + BigInt::from_str(key::segment_at(&String::from_utf8(value).unwrap(), 1)).unwrap() +} diff --git a/substreams/ethereum-ekubo-v2/src/modules/mod.rs b/substreams/ethereum-ekubo-v2/src/modules/mod.rs index ad15e77..6e02c04 100644 --- a/substreams/ethereum-ekubo-v2/src/modules/mod.rs +++ b/substreams/ethereum-ekubo-v2/src/modules/mod.rs @@ -7,13 +7,21 @@ mod map_events; #[path = "2_map_components.rs"] mod map_components; -#[path = "2_map_tick_changes.rs"] -mod map_tick_changes; +#[path = "2_map_order_sale_rate_deltas.rs"] +mod map_order_sale_rate_deltas; +#[path = "2_map_sale_rate_changes.rs"] +mod map_sale_rate_changes; +#[path = "2_map_tick_deltas.rs"] +mod map_tick_deltas; #[path = "2_store_active_ticks.rs"] mod store_active_ticks; #[path = "3_map_liquidity_changes.rs"] mod map_liquidity_changes; +#[path = "3_store_active_sale_rates.rs"] +mod store_active_sale_rates; +#[path = "3_store_order_sale_rates.rs"] +mod store_order_sale_rates; #[path = "3_store_pool_details.rs"] mod store_pool_details; #[path = "3_store_tick_liquidities.rs"] @@ -21,8 +29,8 @@ mod store_tick_liquidities; #[path = "4_map_balance_changes.rs"] mod map_balance_changes; -#[path = "4_store_liquidities.rs"] -mod store_liquidities; +#[path = "4_store_active_liquidities.rs"] +mod store_active_liquidities; #[path = "5_store_balance_changes.rs"] mod store_balance_changes; diff --git a/substreams/ethereum-ekubo-v2/src/pb/ekubo.rs b/substreams/ethereum-ekubo-v2/src/pb/ekubo.rs index 0096991..0b9c3b8 100644 --- a/substreams/ethereum-ekubo-v2/src/pb/ekubo.rs +++ b/substreams/ethereum-ekubo-v2/src/pb/ekubo.rs @@ -1,5 +1,6 @@ // @generated -/// Copy of tycho.evm.v1.Transaction to be able to implement conversions to/from TransactionTrace +/// 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 { @@ -36,6 +37,30 @@ pub struct TickDeltas { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct OrderSaleRateDelta { + /// bytes32 + #[prost(bytes="vec", tag="1")] + pub pool_id: ::prost::alloc::vec::Vec, + #[prost(uint64, tag="2")] + pub time: u64, + /// int112 + #[prost(bytes="vec", tag="3")] + pub sale_rate_delta: ::prost::alloc::vec::Vec, + #[prost(bool, tag="4")] + pub is_token1: bool, + #[prost(uint64, tag="5")] + pub ordinal: u64, + #[prost(message, optional, tag="6")] + pub transaction: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct OrderSaleRateDeltas { + #[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")] @@ -43,7 +68,7 @@ pub struct LiquidityChange { /// uint128 or int128, depending on change_type #[prost(bytes="vec", tag="2")] pub value: ::prost::alloc::vec::Vec, - #[prost(enumeration="LiquidityChangeType", tag="3")] + #[prost(enumeration="ChangeType", tag="3")] pub change_type: i32, #[prost(uint64, tag="4")] pub ordinal: u64, @@ -58,6 +83,31 @@ pub struct LiquidityChanges { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct SaleRateChange { + /// bytes32 + #[prost(bytes="vec", tag="1")] + pub pool_id: ::prost::alloc::vec::Vec, + /// uint112 or int112, depending on change_type + #[prost(bytes="vec", tag="2")] + pub token0_value: ::prost::alloc::vec::Vec, + /// uint112 or int112, depending on change_type + #[prost(bytes="vec", tag="3")] + pub token1_value: ::prost::alloc::vec::Vec, + #[prost(enumeration="ChangeType", tag="4")] + pub change_type: i32, + #[prost(uint64, tag="5")] + pub ordinal: u64, + #[prost(message, optional, tag="6")] + pub transaction: ::core::option::Option, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SaleRateChanges { + #[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")] @@ -73,6 +123,9 @@ pub struct PoolDetails { pub struct BlockTransactionEvents { #[prost(message, repeated, tag="1")] pub block_transaction_events: ::prost::alloc::vec::Vec, + /// block timestamp + #[prost(uint64, tag="2")] + pub timestamp: u64, } /// Nested message and enum types in `BlockTransactionEvents`. pub mod block_transaction_events { @@ -138,16 +191,6 @@ pub mod block_transaction_events { 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 @@ -176,6 +219,8 @@ pub mod block_transaction_events { Unknown = 0, Base = 1, Oracle = 2, + Twamm = 3, + MevResist = 4, } impl Extension { /// String value of the enum field names used in the ProtoBuf definition. @@ -187,6 +232,8 @@ pub mod block_transaction_events { Extension::Unknown => "EXTENSION_UNKNOWN", Extension::Base => "EXTENSION_BASE", Extension::Oracle => "EXTENSION_ORACLE", + Extension::Twamm => "EXTENSION_TWAMM", + Extension::MevResist => "EXTENSION_MEV_RESIST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -195,6 +242,8 @@ pub mod block_transaction_events { "EXTENSION_UNKNOWN" => Some(Self::Unknown), "EXTENSION_BASE" => Some(Self::Base), "EXTENSION_ORACLE" => Some(Self::Oracle), + "EXTENSION_TWAMM" => Some(Self::Twamm), + "EXTENSION_MEV_RESIST" => Some(Self::MevResist), _ => None, } } @@ -202,13 +251,43 @@ pub mod block_transaction_events { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] - pub struct FeesAccumulated { - /// uint128 + pub struct VirtualOrdersExecuted { + /// int112 #[prost(bytes="vec", tag="1")] - pub amount0: ::prost::alloc::vec::Vec, - /// uint128 + pub token0_sale_rate: ::prost::alloc::vec::Vec, + /// int112 #[prost(bytes="vec", tag="2")] - pub amount1: ::prost::alloc::vec::Vec, + pub token1_sale_rate: ::prost::alloc::vec::Vec, + } + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct OrderUpdated { + #[prost(message, optional, tag="1")] + pub order_key: ::core::option::Option, + /// int112 + #[prost(bytes="vec", tag="2")] + pub sale_rate_delta: ::prost::alloc::vec::Vec, + } + /// Nested message and enum types in `OrderUpdated`. + pub mod order_updated { + #[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] + pub struct OrderKey { + /// address + #[prost(bytes="vec", tag="1")] + pub sell_token: ::prost::alloc::vec::Vec, + /// address + #[prost(bytes="vec", tag="2")] + pub buy_token: ::prost::alloc::vec::Vec, + #[prost(fixed64, tag="3")] + pub fee: u64, + /// block timestamp + #[prost(uint64, tag="4")] + pub start_time: u64, + /// block timestamp + #[prost(uint64, tag="5")] + pub end_time: u64, + } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] @@ -218,30 +297,30 @@ pub mod block_transaction_events { #[prost(message, tag="4")] PositionUpdated(PositionUpdated), #[prost(message, tag="5")] - PositionFeesCollected(PositionFeesCollected), - #[prost(message, tag="6")] PoolInitialized(PoolInitialized), + #[prost(message, tag="6")] + VirtualOrdersExecuted(VirtualOrdersExecuted), #[prost(message, tag="7")] - FeesAccumulated(FeesAccumulated), + OrderUpdated(OrderUpdated), } } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] -pub enum LiquidityChangeType { +pub enum ChangeType { Delta = 0, Absolute = 1, } -impl LiquidityChangeType { +impl ChangeType { /// 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", + ChangeType::Delta => "DELTA", + ChangeType::Absolute => "ABSOLUTE", } } /// Creates an enum from field names used in the ProtoBuf definition. diff --git a/substreams/ethereum-ekubo-v2/src/pool_config.rs b/substreams/ethereum-ekubo-v2/src/pool_config.rs deleted file mode 100644 index 52d5afa..0000000 --- a/substreams/ethereum-ekubo-v2/src/pool_config.rs +++ /dev/null @@ -1,15 +0,0 @@ -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-v2/src/pool_key.rs b/substreams/ethereum-ekubo-v2/src/pool_key.rs new file mode 100644 index 0000000..d9eb97d --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/pool_key.rs @@ -0,0 +1,83 @@ +use ethabi::{Address, FixedBytes, Token}; +use tiny_keccak::{Hasher, Keccak}; + +pub struct PoolConfig { + pub fee: u64, + pub tick_spacing: u32, + pub extension: Address, +} + +impl From<[u8; 32]> for PoolConfig { + fn from(value: [u8; 32]) -> Self { + Self { + tick_spacing: u32::from_be_bytes(value[28..32].try_into().unwrap()), + fee: u64::from_be_bytes(value[20..28].try_into().unwrap()), + extension: <[u8; 20]>::try_from(&value[0..20]) + .unwrap() + .into(), + } + } +} + +impl From for FixedBytes { + fn from(value: PoolConfig) -> Self { + [ + value + .extension + .to_fixed_bytes() + .as_slice(), + &value.fee.to_be_bytes(), + &value.tick_spacing.to_be_bytes(), + ] + .concat() + } +} + +pub struct PoolKey { + pub token0: Address, + pub token1: Address, + pub config: PoolConfig, +} + +impl PoolKey { + pub fn into_pool_id(self) -> Vec { + let mut hasher = Keccak::v256(); + + hasher.update(ðabi::encode(&[ + Token::Address(self.token0), + Token::Address(self.token1), + Token::FixedBytes(self.config.into()), + ])); + + let mut output = vec![0; 32]; + hasher.finalize(&mut output); + output + } +} + +#[cfg(test)] +mod tests { + use ethabi::ethereum_types::H160; + use substreams_helper::hex::Hexable; + + use super::*; + + #[test] + fn test_pool_id_computation() { + let pool_key = PoolKey { + token0: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // USDC + .parse() + .unwrap(), + token1: "0xdAC17F958D2ee523a2206206994597C13D831ec7" // USDT + .parse() + .unwrap(), + config: PoolConfig { fee: 922337203685477, tick_spacing: 100, extension: H160::zero() }, + }; + + // https://etherscan.io/tx/0x8d8c4aaee4cc5a23670f7b4894eb63f2eba82779b691e3a97bb073ae857d82e2#eventlog#153 + assert_eq!( + pool_key.into_pool_id().to_hex(), + "0x91ffc128bf8e0afbd2c0f14722e2fd5b6625341a5e5f551aa36242d98756798d" + ); + } +} diff --git a/substreams/ethereum-ekubo-v2/src/store.rs b/substreams/ethereum-ekubo-v2/src/store.rs new file mode 100644 index 0000000..5df5147 --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/store.rs @@ -0,0 +1,12 @@ +use substreams::store::StoreSetSum; + +use crate::pb::ekubo::ChangeType; + +pub fn store_method_from_change_type>( + change_type: ChangeType, +) -> fn(&S, u64, String, T) { + match change_type { + ChangeType::Delta => StoreSetSum::sum, + ChangeType::Absolute => StoreSetSum::set, + } +} diff --git a/substreams/ethereum-ekubo-v2/src/twamm.rs b/substreams/ethereum-ekubo-v2/src/twamm.rs new file mode 100644 index 0000000..5c033ba --- /dev/null +++ b/substreams/ethereum-ekubo-v2/src/twamm.rs @@ -0,0 +1,27 @@ +use substreams::scalar::BigInt; + +use crate::pool_key::{PoolConfig, PoolKey}; + +pub type OrderKey = (Vec, Vec, BigInt, BigInt, BigInt); + +impl PoolKey { + pub fn from_order_key(key: &OrderKey, twamm_address: &Vec) -> Self { + let (token0, token1) = if key.1 > key.0 { (&key.0, &key.1) } else { (&key.1, &key.0) }; + + Self { + token0: <&[u8; 20]>::try_from(token0.as_slice()) + .unwrap() + .into(), + token1: <&[u8; 20]>::try_from(token1.as_slice()) + .unwrap() + .into(), + config: PoolConfig { + fee: key.2.to_u64(), + tick_spacing: 0, + extension: <&[u8; 20]>::try_from(twamm_address.as_slice()) + .unwrap() + .into(), + }, + } + } +} diff --git a/substreams/ethereum-ekubo-v2/substreams.yaml b/substreams/ethereum-ekubo-v2/substreams.yaml index c1995b8..5207eba 100644 --- a/substreams/ethereum-ekubo-v2/substreams.yaml +++ b/substreams/ethereum-ekubo-v2/substreams.yaml @@ -1,7 +1,7 @@ specVersion: v0.1.0 package: name: "ethereum_ekubo_v2" - version: v0.1.0 + version: v0.2.0 url: "https://github.com/propeller-heads/tycho-protocol-sdk/tree/main/substreams/ethereum-ekubo-v2" protobuf: @@ -15,6 +15,7 @@ protobuf: excludePaths: - google - tycho + - sf/substreams binaries: default: @@ -27,7 +28,7 @@ networks: initialBlock: map_events: 22048334 # First pool initialization https://etherscan.io/tx/0x7c2e697e73dc1f114a5473d1015c411f10585b2b671bee0bd6d5706895e16b27 params: - map_events: "core=e0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444&oracle=51d02A5948496a67827242EaBc5725531342527C" + map_events: "core=e0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444&oracle=51d02A5948496a67827242EaBc5725531342527C&twamm=D4279c050DA1F5c5B2830558C7A08E57e12b54eC&mev_resist=553a2EFc570c9e104942cEC6aC1c18118e54C091" modules: - name: map_events @@ -45,7 +46,21 @@ modules: output: type: proto:tycho.evm.v1.BlockChanges - - name: map_tick_changes + - name: map_order_sale_rate_deltas + kind: map + inputs: + - map: map_events + output: + type: proto:ekubo.OrderSaleRateDeltas + + - name: map_sale_rate_changes + kind: map + inputs: + - map: map_events + output: + type: proto:ekubo.SaleRateChanges + + - name: map_tick_deltas kind: map inputs: - map: map_events @@ -67,6 +82,20 @@ modules: output: type: proto:ekubo.LiquidityChanges + - name: store_active_sale_rates + kind: store + valueType: bigint + updatePolicy: set_sum + inputs: + - map: map_sale_rate_changes + + - name: store_order_sale_rates + kind: store + valueType: bigint + updatePolicy: add + inputs: + - map: map_order_sale_rate_deltas + - name: store_pool_details kind: store valueType: proto:ekubo.PoolDetails @@ -79,7 +108,7 @@ modules: valueType: bigint updatePolicy: add inputs: - - map: map_tick_changes + - map: map_tick_deltas - name: map_balance_changes kind: map @@ -89,7 +118,7 @@ modules: output: type: proto:tycho.evm.v1.BlockBalanceDeltas - - name: store_liquidities + - name: store_active_liquidities kind: store valueType: bigint updatePolicy: set_sum @@ -112,11 +141,17 @@ modules: - map: map_balance_changes - store: store_balance_changes mode: deltas - - map: map_tick_changes + - map: map_tick_deltas - store: store_tick_liquidities mode: deltas + - map: map_order_sale_rate_deltas + - store: store_order_sale_rates + mode: deltas - map: map_liquidity_changes - - store: store_liquidities + - store: store_active_liquidities + mode: deltas + - map: map_sale_rate_changes + - store: store_active_sale_rates mode: deltas output: type: proto:tycho.evm.v1.BlockChanges