Ekubo Integration (#172)
* fix: Implement ethereum-ekubo * fix: Remove unnecessary store * fix: Correct balance accounting * Adjust deltas by fee at PositionUpdated event * Add partial Ekubo integration * Generalize Hexable * Native Ekubo integration * cargo fmt & clippy --------- Co-authored-by: kayibal <alan@datarevenue.com> Co-authored-by: Zizou <111426680+zizou0x@users.noreply.github.com>
This commit is contained in:
40
substreams/Cargo.lock
generated
40
substreams/Cargo.lock
generated
@@ -260,6 +260,24 @@ dependencies = [
|
|||||||
"tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)",
|
"tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ethereum-ekubo"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"ethabi 18.0.0",
|
||||||
|
"hex",
|
||||||
|
"itertools 0.10.5",
|
||||||
|
"num-bigint",
|
||||||
|
"prost 0.11.9",
|
||||||
|
"serde",
|
||||||
|
"serde_qs",
|
||||||
|
"substreams",
|
||||||
|
"substreams-ethereum",
|
||||||
|
"substreams-helper 0.0.2",
|
||||||
|
"tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=3c08359)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ethereum-pancakeswap-v3"
|
name = "ethereum-pancakeswap-v3"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -499,7 +517,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -641,7 +659,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -838,7 +856,7 @@ dependencies = [
|
|||||||
"pest_meta",
|
"pest_meta",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1002,7 +1020,7 @@ dependencies = [
|
|||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1139,7 +1157,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"relative-path",
|
"relative-path",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1210,7 +1228,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1477,9 +1495,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.96"
|
version = "2.0.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1531,7 +1549,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1542,7 +1560,7 @@ checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1803,5 +1821,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.96",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ members = [
|
|||||||
"ethereum-template-factory",
|
"ethereum-template-factory",
|
||||||
"ethereum-template-singleton",
|
"ethereum-template-singleton",
|
||||||
"ethereum-uniswap-v4",
|
"ethereum-uniswap-v4",
|
||||||
|
"ethereum-ekubo",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,9 @@
|
|||||||
use ethabi::ethereum_types::Address;
|
|
||||||
use substreams::Hex;
|
|
||||||
|
|
||||||
pub trait Hexable {
|
pub trait Hexable {
|
||||||
fn to_hex(&self) -> String;
|
fn to_hex(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hexable for Vec<u8> {
|
impl<T: AsRef<[u8]>> Hexable for T {
|
||||||
fn to_hex(&self) -> String {
|
fn to_hex(&self) -> String {
|
||||||
let mut str = Hex::encode(self);
|
format!("0x{}", hex::encode(self))
|
||||||
str.insert_str(0, "0x");
|
|
||||||
str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hexable for Address {
|
|
||||||
fn to_hex(&self) -> String {
|
|
||||||
self.as_bytes().to_vec().to_hex()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
substreams/ethereum-ekubo/.cargo/config.toml
Normal file
2
substreams/ethereum-ekubo/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[build]
|
||||||
|
target = "wasm32-unknown-unknown"
|
||||||
26
substreams/ethereum-ekubo/Cargo.toml
Normal file
26
substreams/ethereum-ekubo/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[package]
|
||||||
|
name = "ethereum-ekubo"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "ethereum_ekubo"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
substreams = "0.5.22"
|
||||||
|
substreams-ethereum = "0.9.9"
|
||||||
|
substreams-helper = { path = "../crates/substreams-helper" } # TODO Update to git once pushed
|
||||||
|
prost = "0.11"
|
||||||
|
tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "3c08359" }
|
||||||
|
anyhow = "1.0.95"
|
||||||
|
ethabi = "18.0.0"
|
||||||
|
num-bigint = "0.4.6"
|
||||||
|
hex = { version = "0.4", features = ["serde"] }
|
||||||
|
itertools = "0.10.5"
|
||||||
|
serde = "1.0.217"
|
||||||
|
serde_qs = "0.13.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
substreams-ethereum = "0.9.9"
|
||||||
1266
substreams/ethereum-ekubo/abi/core.json
Normal file
1266
substreams/ethereum-ekubo/abi/core.json
Normal file
File diff suppressed because it is too large
Load Diff
12
substreams/ethereum-ekubo/buf.gen.yaml
Normal file
12
substreams/ethereum-ekubo/buf.gen.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
version: v1
|
||||||
|
plugins:
|
||||||
|
- plugin: buf.build/community/neoeinstein-prost:v0.2.2
|
||||||
|
out: src/pb
|
||||||
|
opt:
|
||||||
|
- file_descriptor_set=false
|
||||||
|
|
||||||
|
- plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
||||||
|
out: src/pb
|
||||||
|
opt:
|
||||||
|
- no_features
|
||||||
49
substreams/ethereum-ekubo/build.rs
Normal file
49
substreams/ethereum-ekubo/build.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::{fs, io::Write};
|
||||||
|
use substreams_ethereum::Abigen;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let abi_folder = "abi";
|
||||||
|
let output_folder = "src/abi";
|
||||||
|
|
||||||
|
let abis = fs::read_dir(abi_folder)?;
|
||||||
|
|
||||||
|
let mut files = abis.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
// Sort the files by their name
|
||||||
|
files.sort_by_key(|a| a.file_name());
|
||||||
|
|
||||||
|
let mut mod_rs_content = String::new();
|
||||||
|
mod_rs_content.push_str("#![allow(clippy::all)]\n");
|
||||||
|
|
||||||
|
for file in files {
|
||||||
|
let file_name = file.file_name();
|
||||||
|
let file_name = file_name.to_string_lossy();
|
||||||
|
|
||||||
|
if !file_name.ends_with(".json") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contract_name = file_name.split('.').next().unwrap();
|
||||||
|
|
||||||
|
let input_path = format!("{}/{}", abi_folder, file_name);
|
||||||
|
let output_path = format!("{}/{}.rs", output_folder, contract_name);
|
||||||
|
|
||||||
|
mod_rs_content.push_str(&format!("pub mod {};\n", contract_name));
|
||||||
|
|
||||||
|
if std::path::Path::new(&output_path).exists() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Abigen::new(contract_name, &input_path)?
|
||||||
|
.generate()?
|
||||||
|
.write_to_file(&output_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mod_rs_path = format!("{}/mod.rs", output_folder);
|
||||||
|
let mut mod_rs_file = fs::File::create(mod_rs_path)?;
|
||||||
|
|
||||||
|
mod_rs_file.write_all(mod_rs_content.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
23
substreams/ethereum-ekubo/integration_test.tycho.yaml
Normal file
23
substreams/ethereum-ekubo/integration_test.tycho.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
adapter_contract: "EkuboSwapAdapter"
|
||||||
|
adapter_build_signature: "constructor(address)"
|
||||||
|
adapter_build_args: "0x16e186ecdc94083fff53ef2a41d46b92a54f61e2"
|
||||||
|
skip_balance_check: true # This seems to fail because testing/src/runner:TestRunner.validate_state tries to interpret the component id as an Ethereum address?
|
||||||
|
protocol_type_names:
|
||||||
|
- "ekubo"
|
||||||
|
tests:
|
||||||
|
- name: test_pool_creation
|
||||||
|
start_block: 7811236
|
||||||
|
stop_block: 7811283
|
||||||
|
expected_components:
|
||||||
|
- id: "0xae76f216ce250b7b9a388d59cbbf92407d7ccee71a99b27ad521508b1c74681f"
|
||||||
|
tokens:
|
||||||
|
- "0x0000000000000000000000000000000000000000"
|
||||||
|
- "0xb1b388f2ef1bb1f7979f009381f797f94b90c094"
|
||||||
|
static_attributes:
|
||||||
|
token0: "0x0000000000000000000000000000000000000000"
|
||||||
|
token1: "0xb1b388f2ef1bb1f7979f009381f797f94b90c094"
|
||||||
|
fee: "0x00c49ba5e353f7ce"
|
||||||
|
tick_spacing: "0x0000175e"
|
||||||
|
extension: "0x0000000000000000000000000000000000000000"
|
||||||
|
creation_tx: "0x4b39d41ae3b823409ba6a92e2937f52f545acfd9cb7ecf7fb8eb7ea2e3b9985e"
|
||||||
|
skip_simulation: false
|
||||||
109
substreams/ethereum-ekubo/proto/ekubo.proto
Normal file
109
substreams/ethereum-ekubo/proto/ekubo.proto
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package ekubo;
|
||||||
|
|
||||||
|
// Copy of tycho.evm.v1.Transaction to be able to implement conversions to/from TransactionTrace
|
||||||
|
message Transaction {
|
||||||
|
bytes hash = 1;
|
||||||
|
bytes from = 2;
|
||||||
|
bytes to = 3;
|
||||||
|
uint64 index = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TickDelta {
|
||||||
|
bytes pool_id = 1; // bytes32
|
||||||
|
int32 tick_index = 2;
|
||||||
|
bytes liquidity_net_delta = 3; // int128
|
||||||
|
uint64 ordinal = 4;
|
||||||
|
Transaction transaction = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TickDeltas {
|
||||||
|
repeated TickDelta deltas = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LiquidityChangeType {
|
||||||
|
DELTA = 0;
|
||||||
|
ABSOLUTE = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LiquidityChange {
|
||||||
|
bytes pool_id = 1; // bytes32
|
||||||
|
bytes value = 2; // uint128 or int128, depending on change_type
|
||||||
|
LiquidityChangeType change_type = 3;
|
||||||
|
uint64 ordinal = 4;
|
||||||
|
Transaction transaction = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LiquidityChanges {
|
||||||
|
repeated LiquidityChange changes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PoolDetails {
|
||||||
|
bytes token0 = 1; // address
|
||||||
|
bytes token1 = 2; // address
|
||||||
|
fixed64 fee = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BlockTransactionEvents {
|
||||||
|
repeated TransactionEvents block_transaction_events = 1;
|
||||||
|
|
||||||
|
message TransactionEvents {
|
||||||
|
Transaction transaction = 1;
|
||||||
|
repeated PoolLog pool_logs = 2;
|
||||||
|
|
||||||
|
message PoolLog {
|
||||||
|
uint64 ordinal = 1;
|
||||||
|
bytes pool_id = 2; // bytes32
|
||||||
|
|
||||||
|
oneof event {
|
||||||
|
Swapped swapped = 3;
|
||||||
|
PositionUpdated position_updated = 4;
|
||||||
|
PositionFeesCollected position_fees_collected = 5;
|
||||||
|
PoolInitialized pool_initialized = 6;
|
||||||
|
FeesAccumulated fees_accumulated = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Swapped {
|
||||||
|
bytes delta0 = 1; // int128
|
||||||
|
bytes delta1 = 2; // int128
|
||||||
|
bytes sqrt_ratio_after = 3; // uint192
|
||||||
|
bytes liquidity_after = 4; // uint128
|
||||||
|
sint32 tick_after = 5; // int32
|
||||||
|
}
|
||||||
|
|
||||||
|
message PositionUpdated {
|
||||||
|
sint32 lower = 1; // int32
|
||||||
|
sint32 upper = 2; // int32
|
||||||
|
bytes liquidity_delta = 3; // int128
|
||||||
|
bytes delta0 = 4; // int128
|
||||||
|
bytes delta1 = 5; // int128
|
||||||
|
}
|
||||||
|
|
||||||
|
message PositionFeesCollected {
|
||||||
|
bytes amount0 = 1; // uint128
|
||||||
|
bytes amount1 = 2; // uint128
|
||||||
|
}
|
||||||
|
|
||||||
|
message PoolInitialized {
|
||||||
|
bytes token0 = 1; // address
|
||||||
|
bytes token1 = 2; // address
|
||||||
|
bytes config = 3; // bytes32
|
||||||
|
sint32 tick = 4; // int32
|
||||||
|
bytes sqrt_ratio = 5; // uint192
|
||||||
|
Extension extension = 6;
|
||||||
|
|
||||||
|
enum Extension {
|
||||||
|
EXTENSION_UNKNOWN = 0;
|
||||||
|
EXTENSION_BASE = 1;
|
||||||
|
EXTENSION_ORACLE = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message FeesAccumulated {
|
||||||
|
bytes amount0 = 1; // uint128
|
||||||
|
bytes amount1 = 2; // uint128
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3896
substreams/ethereum-ekubo/src/abi/core.rs
Normal file
3896
substreams/ethereum-ekubo/src/abi/core.rs
Normal file
File diff suppressed because it is too large
Load Diff
2
substreams/ethereum-ekubo/src/abi/mod.rs
Normal file
2
substreams/ethereum-ekubo/src/abi/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#![allow(clippy::all)]
|
||||||
|
pub mod core;
|
||||||
9
substreams/ethereum-ekubo/src/deployment_config.rs
Normal file
9
substreams/ethereum-ekubo/src/deployment_config.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DeploymentConfig {
|
||||||
|
#[serde(with = "hex::serde")]
|
||||||
|
pub core: Vec<u8>,
|
||||||
|
#[serde(with = "hex::serde")]
|
||||||
|
pub oracle: Vec<u8>,
|
||||||
|
}
|
||||||
6
substreams/ethereum-ekubo/src/lib.rs
Normal file
6
substreams/ethereum-ekubo/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
mod abi;
|
||||||
|
mod deployment_config;
|
||||||
|
mod modules;
|
||||||
|
mod pb;
|
||||||
|
mod pool_config;
|
||||||
|
mod sqrt_ratio;
|
||||||
129
substreams/ethereum-ekubo/src/modules/1_map_events.rs
Normal file
129
substreams/ethereum-ekubo/src/modules/1_map_events.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
use ethabi::Address;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use substreams::scalar::BigInt;
|
||||||
|
use substreams_ethereum::{
|
||||||
|
pb::eth::{self, v2::Log},
|
||||||
|
Event as _,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::core::events as abi_events,
|
||||||
|
deployment_config::DeploymentConfig,
|
||||||
|
pb::ekubo::{
|
||||||
|
block_transaction_events::{
|
||||||
|
transaction_events::{
|
||||||
|
pool_log::{
|
||||||
|
pool_initialized::Extension, Event, FeesAccumulated, PoolInitialized,
|
||||||
|
PositionFeesCollected, PositionUpdated, Swapped,
|
||||||
|
},
|
||||||
|
PoolLog,
|
||||||
|
},
|
||||||
|
TransactionEvents,
|
||||||
|
},
|
||||||
|
BlockTransactionEvents,
|
||||||
|
},
|
||||||
|
pool_config::PoolConfig,
|
||||||
|
sqrt_ratio::float_sqrt_ratio_to_fixed,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
fn map_events(params: String, block: eth::v2::Block) -> BlockTransactionEvents {
|
||||||
|
let config: DeploymentConfig = serde_qs::from_str(¶ms).unwrap();
|
||||||
|
|
||||||
|
BlockTransactionEvents {
|
||||||
|
block_transaction_events: block
|
||||||
|
.transactions()
|
||||||
|
.flat_map(|trace| {
|
||||||
|
let pool_logs = trace
|
||||||
|
.logs_with_calls()
|
||||||
|
.filter_map(|(log, _)| maybe_pool_log(log, &config))
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
(!pool_logs.is_empty())
|
||||||
|
.then(|| TransactionEvents { transaction: Some(trace.into()), pool_logs })
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_pool_log(log: &Log, config: &DeploymentConfig) -> Option<PoolLog> {
|
||||||
|
if log.address != config.core {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (pool_id, ev) = if log.topics.is_empty() {
|
||||||
|
let data = &log.data;
|
||||||
|
|
||||||
|
assert!(data.len() == 116, "swap event data length mismatch");
|
||||||
|
|
||||||
|
(
|
||||||
|
data[20..52].to_vec(),
|
||||||
|
Event::Swapped(Swapped {
|
||||||
|
delta0: data[52..68].to_vec(),
|
||||||
|
delta1: data[68..84].to_vec(),
|
||||||
|
liquidity_after: data[84..100].to_vec(),
|
||||||
|
sqrt_ratio_after: float_sqrt_ratio_to_fixed(BigInt::from_unsigned_bytes_be(
|
||||||
|
&data[100..112],
|
||||||
|
)),
|
||||||
|
tick_after: i32::from_be_bytes(data[112..116].try_into().unwrap()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if let Some(ev) = abi_events::PositionUpdated::match_and_decode(log) {
|
||||||
|
(
|
||||||
|
ev.pool_id.to_vec(),
|
||||||
|
Event::PositionUpdated(PositionUpdated {
|
||||||
|
lower: ev.params.1 .0.to_i32(),
|
||||||
|
upper: ev.params.1 .1.to_i32(),
|
||||||
|
liquidity_delta: ev.params.2.to_signed_bytes_be(),
|
||||||
|
delta0: ev.delta0.to_signed_bytes_be(),
|
||||||
|
delta1: ev.delta1.to_signed_bytes_be(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if let Some(ev) = abi_events::PositionFeesCollected::match_and_decode(log) {
|
||||||
|
(
|
||||||
|
ev.pool_id.to_vec(),
|
||||||
|
Event::PositionFeesCollected(PositionFeesCollected {
|
||||||
|
amount0: ev.amount0.to_bytes_be().1,
|
||||||
|
amount1: ev.amount1.to_bytes_be().1,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if let Some(ev) = abi_events::PoolInitialized::match_and_decode(log) {
|
||||||
|
let pool_config = PoolConfig::from(ev.pool_key.2);
|
||||||
|
|
||||||
|
let extension = {
|
||||||
|
let extension = pool_config.extension;
|
||||||
|
|
||||||
|
if extension == Address::zero().as_bytes() {
|
||||||
|
Extension::Base
|
||||||
|
} else if extension == config.oracle {
|
||||||
|
Extension::Oracle
|
||||||
|
} else {
|
||||||
|
Extension::Unknown
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
ev.pool_id.to_vec(),
|
||||||
|
Event::PoolInitialized(PoolInitialized {
|
||||||
|
token0: ev.pool_key.0,
|
||||||
|
token1: ev.pool_key.1,
|
||||||
|
config: ev.pool_key.2.to_vec(),
|
||||||
|
tick: ev.tick.to_i32(),
|
||||||
|
sqrt_ratio: float_sqrt_ratio_to_fixed(ev.sqrt_ratio),
|
||||||
|
extension: extension.into(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if let Some(ev) = abi_events::FeesAccumulated::match_and_decode(log) {
|
||||||
|
(
|
||||||
|
ev.pool_id.to_vec(),
|
||||||
|
Event::FeesAccumulated(FeesAccumulated {
|
||||||
|
amount0: ev.amount0.to_bytes_be().1,
|
||||||
|
amount1: ev.amount1.to_bytes_be().1,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(PoolLog { ordinal: log.ordinal, pool_id, event: Some(ev) })
|
||||||
|
}
|
||||||
135
substreams/ethereum-ekubo/src/modules/2_map_components.rs
Normal file
135
substreams/ethereum-ekubo/src/modules/2_map_components.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use substreams::scalar::BigInt;
|
||||||
|
use substreams_helper::hex::Hexable;
|
||||||
|
use tycho_substreams::models::{
|
||||||
|
Attribute, BalanceChange, BlockChanges, ChangeType, EntityChanges, FinancialType,
|
||||||
|
ImplementationType, ProtocolComponent, ProtocolType, TransactionChanges,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
pb::ekubo::{
|
||||||
|
block_transaction_events::transaction_events::{pool_log::Event, PoolLog},
|
||||||
|
BlockTransactionEvents,
|
||||||
|
},
|
||||||
|
pool_config::PoolConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
fn map_components(block_tx_events: BlockTransactionEvents) -> BlockChanges {
|
||||||
|
BlockChanges {
|
||||||
|
block: None,
|
||||||
|
changes: block_tx_events
|
||||||
|
.block_transaction_events
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|tx_events| {
|
||||||
|
let (components, entities, balance_changes): (Vec<_>, Vec<_>, Vec<_>) = tx_events
|
||||||
|
.pool_logs
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(maybe_create_component)
|
||||||
|
.multiunzip();
|
||||||
|
|
||||||
|
(!components.is_empty()).then(|| TransactionChanges {
|
||||||
|
tx: Some(tx_events.transaction.unwrap().into()),
|
||||||
|
balance_changes: balance_changes
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect(),
|
||||||
|
contract_changes: vec![],
|
||||||
|
entity_changes: entities,
|
||||||
|
component_changes: components,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_create_component(
|
||||||
|
log: PoolLog,
|
||||||
|
) -> Option<(ProtocolComponent, EntityChanges, Vec<BalanceChange>)> {
|
||||||
|
if let Event::PoolInitialized(pi) = log.event.unwrap() {
|
||||||
|
let config = PoolConfig::from(<[u8; 32]>::try_from(pi.config).unwrap());
|
||||||
|
let component_id = log.pool_id.to_hex();
|
||||||
|
|
||||||
|
return Some((
|
||||||
|
ProtocolComponent {
|
||||||
|
id: component_id.clone(),
|
||||||
|
tokens: vec![pi.token0.clone(), pi.token1.clone()],
|
||||||
|
contracts: vec![],
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
protocol_type: Some(ProtocolType {
|
||||||
|
name: "ekubo".to_string(),
|
||||||
|
financial_type: FinancialType::Swap.into(),
|
||||||
|
implementation_type: ImplementationType::Custom.into(),
|
||||||
|
attribute_schema: vec![],
|
||||||
|
}),
|
||||||
|
// Order of attributes matters (used in store_pool_details)
|
||||||
|
static_att: vec![
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "token0".to_string(),
|
||||||
|
value: pi.token0.clone(),
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "token1".to_string(),
|
||||||
|
value: pi.token1.clone(),
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "fee".to_string(),
|
||||||
|
value: config.fee,
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "tick_spacing".to_string(),
|
||||||
|
value: config.tick_spacing,
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "extension".to_string(),
|
||||||
|
value: config.extension,
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "extension_id".to_string(),
|
||||||
|
value: pi.extension.to_be_bytes().to_vec(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
EntityChanges {
|
||||||
|
component_id: component_id.clone(),
|
||||||
|
attributes: vec![
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "liquidity".to_string(),
|
||||||
|
value: 0_u128.to_be_bytes().to_vec(),
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "tick".to_string(),
|
||||||
|
value: pi.tick.to_be_bytes().to_vec(),
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
name: "sqrt_ratio".to_string(),
|
||||||
|
value: pi.sqrt_ratio,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
vec![
|
||||||
|
BalanceChange {
|
||||||
|
component_id: component_id.clone().into_bytes(),
|
||||||
|
token: pi.token0,
|
||||||
|
balance: BigInt::zero().to_signed_bytes_be(),
|
||||||
|
},
|
||||||
|
BalanceChange {
|
||||||
|
component_id: component_id.into_bytes(),
|
||||||
|
token: pi.token1,
|
||||||
|
balance: BigInt::zero().to_signed_bytes_be(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
63
substreams/ethereum-ekubo/src/modules/2_map_tick_changes.rs
Normal file
63
substreams/ethereum-ekubo/src/modules/2_map_tick_changes.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use substreams::scalar::BigInt;
|
||||||
|
|
||||||
|
use crate::pb::ekubo::{
|
||||||
|
block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents,
|
||||||
|
TickDelta, TickDeltas,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
pub fn map_tick_changes(block_tx_events: BlockTransactionEvents) -> TickDeltas {
|
||||||
|
TickDeltas {
|
||||||
|
deltas: block_tx_events
|
||||||
|
.block_transaction_events
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|tx_events| {
|
||||||
|
let tx = tx_events.transaction;
|
||||||
|
|
||||||
|
tx_events
|
||||||
|
.pool_logs
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(move |log| {
|
||||||
|
let tx = tx.clone();
|
||||||
|
|
||||||
|
tick_deltas(log.event.unwrap())
|
||||||
|
.into_iter()
|
||||||
|
.map(move |partial| TickDelta {
|
||||||
|
liquidity_net_delta: partial.liquidity_net_delta,
|
||||||
|
pool_id: log.pool_id.clone(),
|
||||||
|
tick_index: partial.tick_index,
|
||||||
|
ordinal: log.ordinal,
|
||||||
|
transaction: tx.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PartialTickDelta {
|
||||||
|
tick_index: i32,
|
||||||
|
liquidity_net_delta: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick_deltas(ev: Event) -> Vec<PartialTickDelta> {
|
||||||
|
match ev {
|
||||||
|
Event::PositionUpdated(position_updated) => {
|
||||||
|
vec![
|
||||||
|
PartialTickDelta {
|
||||||
|
tick_index: position_updated.lower,
|
||||||
|
liquidity_net_delta: position_updated.liquidity_delta.clone(),
|
||||||
|
},
|
||||||
|
PartialTickDelta {
|
||||||
|
tick_index: position_updated.upper,
|
||||||
|
liquidity_net_delta: BigInt::from_signed_bytes_be(
|
||||||
|
&position_updated.liquidity_delta,
|
||||||
|
)
|
||||||
|
.neg()
|
||||||
|
.to_signed_bytes_be(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use substreams::store::{StoreSet, StoreSetInt64};
|
||||||
|
|
||||||
|
use substreams::store::StoreNew;
|
||||||
|
use substreams_helper::hex::Hexable;
|
||||||
|
|
||||||
|
use crate::pb::ekubo::{
|
||||||
|
block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[substreams::handlers::store]
|
||||||
|
pub fn store_active_ticks(block_tx_events: BlockTransactionEvents, store: StoreSetInt64) {
|
||||||
|
block_tx_events
|
||||||
|
.block_transaction_events
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|tx_events| tx_events.pool_logs)
|
||||||
|
.filter_map(|log| {
|
||||||
|
maybe_tick(log.event.unwrap()).map(|tick| (log.pool_id.to_hex(), log.ordinal, tick))
|
||||||
|
})
|
||||||
|
.for_each(|(pool, ordinal, new_tick_index)| {
|
||||||
|
store.set(ordinal, format!("pool:{pool}"), &new_tick_index.into())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_tick(ev: Event) -> Option<i32> {
|
||||||
|
match ev {
|
||||||
|
Event::PoolInitialized(pool_initialized) => Some(pool_initialized.tick),
|
||||||
|
Event::Swapped(swapped) => Some(swapped.tick_after),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
use substreams::store::{StoreGet, StoreGetInt64};
|
||||||
|
|
||||||
|
use substreams_helper::hex::Hexable;
|
||||||
|
|
||||||
|
use crate::pb::ekubo::{
|
||||||
|
block_transaction_events::transaction_events::{pool_log::Event, PoolLog},
|
||||||
|
BlockTransactionEvents, LiquidityChange, LiquidityChangeType, LiquidityChanges,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
pub fn map_liquidity_changes(
|
||||||
|
block_tx_events: BlockTransactionEvents,
|
||||||
|
current_tick_store: StoreGetInt64,
|
||||||
|
) -> LiquidityChanges {
|
||||||
|
LiquidityChanges {
|
||||||
|
changes: block_tx_events
|
||||||
|
.block_transaction_events
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|tx_events| {
|
||||||
|
let current_tick_store = ¤t_tick_store;
|
||||||
|
|
||||||
|
tx_events
|
||||||
|
.pool_logs
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(move |log| {
|
||||||
|
maybe_liquidity_change(&log, current_tick_store).map(|partial| {
|
||||||
|
LiquidityChange {
|
||||||
|
change_type: partial.change_type.into(),
|
||||||
|
pool_id: log.pool_id,
|
||||||
|
value: partial.value,
|
||||||
|
ordinal: log.ordinal,
|
||||||
|
transaction: tx_events.transaction.clone(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PartialLiquidityChange {
|
||||||
|
value: Vec<u8>,
|
||||||
|
change_type: LiquidityChangeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_liquidity_change(
|
||||||
|
log: &PoolLog,
|
||||||
|
current_tick_store: &StoreGetInt64,
|
||||||
|
) -> Option<PartialLiquidityChange> {
|
||||||
|
match log.event.as_ref().unwrap() {
|
||||||
|
Event::Swapped(swapped) => Some(PartialLiquidityChange {
|
||||||
|
value: swapped.liquidity_after.clone(),
|
||||||
|
change_type: LiquidityChangeType::Absolute,
|
||||||
|
}),
|
||||||
|
Event::PositionUpdated(position_updated) => {
|
||||||
|
let current_tick = current_tick_store
|
||||||
|
.get_at(log.ordinal, format!("pool:{0}", log.pool_id.to_hex()))
|
||||||
|
.expect("pool should have active tick when initialized");
|
||||||
|
|
||||||
|
(current_tick >= position_updated.lower.into() &&
|
||||||
|
current_tick < position_updated.upper.into())
|
||||||
|
.then(|| PartialLiquidityChange {
|
||||||
|
value: position_updated.liquidity_delta.clone(),
|
||||||
|
change_type: LiquidityChangeType::Delta,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use substreams::store::{StoreNew, StoreSetIfNotExists, StoreSetIfNotExistsProto};
|
||||||
|
use tycho_substreams::models::BlockChanges;
|
||||||
|
|
||||||
|
use crate::pb::ekubo::PoolDetails;
|
||||||
|
|
||||||
|
// Since only the PoolInitialized event contains the complete pool key we need to store some info
|
||||||
|
// required when processing other events
|
||||||
|
#[substreams::handlers::store]
|
||||||
|
fn store_pool_details(changes: BlockChanges, store: StoreSetIfNotExistsProto<PoolDetails>) {
|
||||||
|
changes
|
||||||
|
.changes
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|c| c.component_changes.into_iter())
|
||||||
|
.for_each(|component| {
|
||||||
|
let attrs = component.static_att;
|
||||||
|
|
||||||
|
let pool_details = PoolDetails {
|
||||||
|
token0: attrs[0].value.clone(),
|
||||||
|
token1: attrs[1].value.clone(),
|
||||||
|
fee: u64::from_be_bytes(
|
||||||
|
attrs[2]
|
||||||
|
.value
|
||||||
|
.clone()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
store.set_if_not_exists(0, component.id, &pool_details);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use substreams::{
|
||||||
|
scalar::BigInt,
|
||||||
|
store::{StoreAdd, StoreAddBigInt, StoreNew},
|
||||||
|
};
|
||||||
|
use substreams_helper::hex::Hexable;
|
||||||
|
|
||||||
|
use crate::pb::ekubo::TickDeltas;
|
||||||
|
|
||||||
|
#[substreams::handlers::store]
|
||||||
|
pub fn store_tick_liquidities(tick_deltas: TickDeltas, store: StoreAddBigInt) {
|
||||||
|
tick_deltas
|
||||||
|
.deltas
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|delta| {
|
||||||
|
store.add(
|
||||||
|
delta.ordinal,
|
||||||
|
format!("pool:{}:tick:{}", delta.pool_id.to_hex(), delta.tick_index),
|
||||||
|
BigInt::from_signed_bytes_be(&delta.liquidity_net_delta),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
127
substreams/ethereum-ekubo/src/modules/4_map_balance_changes.rs
Normal file
127
substreams/ethereum-ekubo/src/modules/4_map_balance_changes.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use substreams::{
|
||||||
|
hex,
|
||||||
|
scalar::BigInt,
|
||||||
|
store::{StoreGet, StoreGetProto},
|
||||||
|
};
|
||||||
|
use substreams_helper::hex::Hexable;
|
||||||
|
use tycho_substreams::models::{BalanceDelta, BlockBalanceDeltas, Transaction};
|
||||||
|
|
||||||
|
use crate::pb::ekubo::{
|
||||||
|
block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents,
|
||||||
|
PoolDetails,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
fn map_balance_changes(
|
||||||
|
block_tx_events: BlockTransactionEvents,
|
||||||
|
store: StoreGetProto<PoolDetails>,
|
||||||
|
) -> BlockBalanceDeltas {
|
||||||
|
BlockBalanceDeltas {
|
||||||
|
balance_deltas: block_tx_events
|
||||||
|
.block_transaction_events
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|tx_events| {
|
||||||
|
let tx: Transaction = tx_events.transaction.unwrap().into();
|
||||||
|
|
||||||
|
let store = &store;
|
||||||
|
|
||||||
|
tx_events
|
||||||
|
.pool_logs
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(move |log| {
|
||||||
|
let component_id = log.pool_id.to_hex();
|
||||||
|
let pool_details = get_pool_details(store, &component_id);
|
||||||
|
|
||||||
|
let component_id_bytes = component_id.into_bytes();
|
||||||
|
let tx = tx.clone();
|
||||||
|
|
||||||
|
balance_deltas(log.event.unwrap(), pool_details)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |reduced| BalanceDelta {
|
||||||
|
ord: log.ordinal,
|
||||||
|
tx: Some(tx.clone()),
|
||||||
|
token: reduced.token,
|
||||||
|
delta: reduced.delta,
|
||||||
|
component_id: component_id_bytes.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReducedBalanceDelta {
|
||||||
|
token: Vec<u8>,
|
||||||
|
delta: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn balance_deltas(ev: Event, pool_details: PoolDetails) -> Vec<ReducedBalanceDelta> {
|
||||||
|
match ev {
|
||||||
|
Event::Swapped(swapped) => {
|
||||||
|
vec![
|
||||||
|
ReducedBalanceDelta { token: pool_details.token0, delta: swapped.delta0 },
|
||||||
|
ReducedBalanceDelta { token: pool_details.token1, delta: swapped.delta1 },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Event::PositionUpdated(position_updated) => {
|
||||||
|
vec![
|
||||||
|
ReducedBalanceDelta {
|
||||||
|
token: pool_details.token0,
|
||||||
|
delta: adjust_delta_by_fee(
|
||||||
|
BigInt::from_signed_bytes_be(&position_updated.delta0),
|
||||||
|
pool_details.fee,
|
||||||
|
)
|
||||||
|
.to_signed_bytes_be(),
|
||||||
|
},
|
||||||
|
ReducedBalanceDelta {
|
||||||
|
token: pool_details.token1,
|
||||||
|
delta: adjust_delta_by_fee(
|
||||||
|
BigInt::from_signed_bytes_be(&position_updated.delta1),
|
||||||
|
pool_details.fee,
|
||||||
|
)
|
||||||
|
.to_signed_bytes_be(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Event::PositionFeesCollected(position_fees_collected) => {
|
||||||
|
vec![
|
||||||
|
ReducedBalanceDelta {
|
||||||
|
token: pool_details.token0,
|
||||||
|
delta: BigInt::from_unsigned_bytes_be(&position_fees_collected.amount0)
|
||||||
|
.neg()
|
||||||
|
.to_signed_bytes_be(),
|
||||||
|
},
|
||||||
|
ReducedBalanceDelta {
|
||||||
|
token: pool_details.token1,
|
||||||
|
delta: BigInt::from_unsigned_bytes_be(&position_fees_collected.amount1)
|
||||||
|
.neg()
|
||||||
|
.to_signed_bytes_be(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Event::FeesAccumulated(fees_accumulated) => {
|
||||||
|
vec![
|
||||||
|
ReducedBalanceDelta { token: pool_details.token0, delta: fees_accumulated.amount0 },
|
||||||
|
ReducedBalanceDelta { token: pool_details.token1, delta: fees_accumulated.amount1 },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative deltas don't include the fees paid by the position owner, thus we need to add it back
|
||||||
|
// here (i.e. subtract from the component's balance)
|
||||||
|
fn adjust_delta_by_fee(delta: BigInt, fee: u64) -> BigInt {
|
||||||
|
if delta < BigInt::zero() {
|
||||||
|
let denom = BigInt::from_signed_bytes_be(&hex!("0100000000000000000000000000000000"));
|
||||||
|
(delta * denom.clone()) / (denom - fee)
|
||||||
|
} else {
|
||||||
|
delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pool_details(store: &StoreGetProto<PoolDetails>, component_id: &str) -> PoolDetails {
|
||||||
|
store
|
||||||
|
.get_at(0, component_id)
|
||||||
|
.expect("pool id should exist in store")
|
||||||
|
}
|
||||||
30
substreams/ethereum-ekubo/src/modules/4_store_liquidities.rs
Normal file
30
substreams/ethereum-ekubo/src/modules/4_store_liquidities.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use substreams::{
|
||||||
|
scalar::BigInt,
|
||||||
|
store::{StoreSetSum, StoreSetSumBigInt},
|
||||||
|
};
|
||||||
|
use substreams_helper::hex::Hexable;
|
||||||
|
|
||||||
|
use crate::pb::ekubo::{LiquidityChangeType, LiquidityChanges};
|
||||||
|
|
||||||
|
#[substreams::handlers::store]
|
||||||
|
pub fn store_liquidities(liquidity_changes: LiquidityChanges, store: StoreSetSumBigInt) {
|
||||||
|
liquidity_changes
|
||||||
|
.changes
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|changes| match changes.change_type() {
|
||||||
|
LiquidityChangeType::Delta => {
|
||||||
|
store.sum(
|
||||||
|
changes.ordinal,
|
||||||
|
format!("pool:{0}", changes.pool_id.to_hex()),
|
||||||
|
BigInt::from_signed_bytes_be(&changes.value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
LiquidityChangeType::Absolute => {
|
||||||
|
store.set(
|
||||||
|
changes.ordinal,
|
||||||
|
format!("pool:{0}", changes.pool_id.to_hex()),
|
||||||
|
BigInt::from_signed_bytes_be(&changes.value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use substreams::store::{StoreAddBigInt, StoreNew};
|
||||||
|
use tycho_substreams::models::BlockBalanceDeltas;
|
||||||
|
|
||||||
|
#[substreams::handlers::store]
|
||||||
|
fn store_balance_changes(deltas: BlockBalanceDeltas, store: StoreAddBigInt) {
|
||||||
|
tycho_substreams::balances::store_balance_changes(deltas, store);
|
||||||
|
}
|
||||||
203
substreams/ethereum-ekubo/src/modules/6_map_protocol_changes.rs
Normal file
203
substreams/ethereum-ekubo/src/modules/6_map_protocol_changes.rs
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use substreams::{key, pb::substreams::StoreDeltas, scalar::BigInt};
|
||||||
|
use substreams_ethereum::pb::eth;
|
||||||
|
use substreams_helper::hex::Hexable;
|
||||||
|
use tycho_substreams::{
|
||||||
|
balances::aggregate_balances_changes,
|
||||||
|
models::{
|
||||||
|
Attribute, BlockBalanceDeltas, BlockChanges, ChangeType, EntityChanges,
|
||||||
|
TransactionChangesBuilder,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::pb::ekubo::{
|
||||||
|
block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents,
|
||||||
|
LiquidityChanges, TickDeltas,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Aggregates protocol components and balance changes by transaction.
|
||||||
|
///
|
||||||
|
/// This is the main method that will aggregate all changes as well as extract all
|
||||||
|
/// relevant contract storage deltas.
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
fn map_protocol_changes(
|
||||||
|
block: eth::v2::Block,
|
||||||
|
new_components: BlockChanges,
|
||||||
|
block_tx_events: BlockTransactionEvents,
|
||||||
|
balances_map_deltas: BlockBalanceDeltas,
|
||||||
|
balances_store_deltas: StoreDeltas,
|
||||||
|
ticks_map_deltas: TickDeltas,
|
||||||
|
ticks_store_deltas: StoreDeltas,
|
||||||
|
liquidity_changes: LiquidityChanges,
|
||||||
|
liquidity_store_deltas: StoreDeltas,
|
||||||
|
) -> Result<BlockChanges, substreams::errors::Error> {
|
||||||
|
let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new();
|
||||||
|
|
||||||
|
// New components
|
||||||
|
new_components
|
||||||
|
.changes
|
||||||
|
.iter()
|
||||||
|
.for_each(|tx_changes| {
|
||||||
|
let tx = tx_changes.tx.as_ref().unwrap();
|
||||||
|
let builder = transaction_changes
|
||||||
|
.entry(tx.index)
|
||||||
|
.or_insert_with(|| TransactionChangesBuilder::new(tx));
|
||||||
|
|
||||||
|
tx_changes
|
||||||
|
.component_changes
|
||||||
|
.iter()
|
||||||
|
.for_each(|component| {
|
||||||
|
builder.add_protocol_component(component);
|
||||||
|
});
|
||||||
|
|
||||||
|
tx_changes
|
||||||
|
.entity_changes
|
||||||
|
.iter()
|
||||||
|
.for_each(|entity_change| {
|
||||||
|
builder.add_entity_change(entity_change);
|
||||||
|
});
|
||||||
|
|
||||||
|
tx_changes
|
||||||
|
.balance_changes
|
||||||
|
.iter()
|
||||||
|
.for_each(|balance_change| {
|
||||||
|
builder.add_balance_change(balance_change);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Component balances
|
||||||
|
aggregate_balances_changes(balances_store_deltas, balances_map_deltas)
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|(_, (tx, balances))| {
|
||||||
|
let builder = transaction_changes
|
||||||
|
.entry(tx.index)
|
||||||
|
.or_insert_with(|| TransactionChangesBuilder::new(&tx));
|
||||||
|
|
||||||
|
balances
|
||||||
|
.values()
|
||||||
|
.for_each(|token_bc_map| {
|
||||||
|
token_bc_map.values().for_each(|bc| {
|
||||||
|
builder.add_balance_change(bc);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tick liquidities
|
||||||
|
ticks_store_deltas
|
||||||
|
.deltas
|
||||||
|
.into_iter()
|
||||||
|
.zip(ticks_map_deltas.deltas)
|
||||||
|
.for_each(|(store_delta, tick_delta)| {
|
||||||
|
let new_value_bigint = BigInt::from_store_bytes(&store_delta.new_value);
|
||||||
|
|
||||||
|
let is_creation = BigInt::from_store_bytes(&store_delta.old_value).is_zero();
|
||||||
|
|
||||||
|
let attribute = Attribute {
|
||||||
|
name: format!("ticks/{}", tick_delta.tick_index),
|
||||||
|
value: new_value_bigint.to_signed_bytes_be(),
|
||||||
|
change: if is_creation {
|
||||||
|
ChangeType::Creation.into()
|
||||||
|
} else if new_value_bigint.is_zero() {
|
||||||
|
ChangeType::Deletion.into()
|
||||||
|
} else {
|
||||||
|
ChangeType::Update.into()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let tx = tick_delta.transaction.unwrap();
|
||||||
|
let builder = transaction_changes
|
||||||
|
.entry(tx.index)
|
||||||
|
.or_insert_with(|| TransactionChangesBuilder::new(&tx.into()));
|
||||||
|
|
||||||
|
builder.add_entity_change(&EntityChanges {
|
||||||
|
component_id: tick_delta.pool_id.to_hex(),
|
||||||
|
attributes: vec![attribute],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pool liquidities
|
||||||
|
liquidity_store_deltas
|
||||||
|
.deltas
|
||||||
|
.into_iter()
|
||||||
|
.zip(liquidity_changes.changes)
|
||||||
|
.for_each(|(store_delta, change)| {
|
||||||
|
let tx = change.transaction.unwrap();
|
||||||
|
let builder = transaction_changes
|
||||||
|
.entry(tx.index)
|
||||||
|
.or_insert_with(|| TransactionChangesBuilder::new(&tx.into()));
|
||||||
|
|
||||||
|
let new_value_bigint = BigInt::from_str(key::segment_at(
|
||||||
|
&String::from_utf8(store_delta.new_value).unwrap(),
|
||||||
|
1,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
builder.add_entity_change(&EntityChanges {
|
||||||
|
component_id: change.pool_id.to_hex(),
|
||||||
|
attributes: vec![Attribute {
|
||||||
|
name: "liquidity".to_string(),
|
||||||
|
value: new_value_bigint.to_signed_bytes_be(),
|
||||||
|
change: ChangeType::Update.into(),
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remaining event changes not subject to special treatment
|
||||||
|
block_tx_events
|
||||||
|
.block_transaction_events
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|tx_events| {
|
||||||
|
let tx = tx_events.transaction.unwrap();
|
||||||
|
|
||||||
|
tx_events
|
||||||
|
.pool_logs
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(move |log| {
|
||||||
|
let tx = tx.clone();
|
||||||
|
|
||||||
|
maybe_attribute_updates(log.event.unwrap()).map(|attrs| {
|
||||||
|
(
|
||||||
|
tx,
|
||||||
|
EntityChanges { component_id: log.pool_id.to_hex(), attributes: attrs },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.for_each(|(tx, entity_changes)| {
|
||||||
|
let builder = transaction_changes
|
||||||
|
.entry(tx.index)
|
||||||
|
.or_insert_with(|| TransactionChangesBuilder::new(&tx.into()));
|
||||||
|
builder.add_entity_change(&entity_changes);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(BlockChanges {
|
||||||
|
block: Some((&block).into()),
|
||||||
|
changes: transaction_changes
|
||||||
|
.drain()
|
||||||
|
.sorted_unstable_by_key(|(index, _)| *index)
|
||||||
|
.filter_map(|(_, builder)| builder.build())
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_attribute_updates(ev: Event) -> Option<Vec<Attribute>> {
|
||||||
|
match ev {
|
||||||
|
Event::Swapped(swapped) => Some(vec![
|
||||||
|
Attribute {
|
||||||
|
name: "tick".into(),
|
||||||
|
value: swapped
|
||||||
|
.tick_after
|
||||||
|
.to_be_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
change: ChangeType::Update.into(),
|
||||||
|
},
|
||||||
|
Attribute {
|
||||||
|
name: "sqrt_ratio".into(),
|
||||||
|
value: swapped.sqrt_ratio_after,
|
||||||
|
change: ChangeType::Update.into(),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
48
substreams/ethereum-ekubo/src/modules/mod.rs
Normal file
48
substreams/ethereum-ekubo/src/modules/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::TransactionTrace;
|
||||||
|
|
||||||
|
use crate::pb::ekubo::Transaction;
|
||||||
|
|
||||||
|
#[path = "1_map_events.rs"]
|
||||||
|
mod map_events;
|
||||||
|
|
||||||
|
#[path = "2_map_components.rs"]
|
||||||
|
mod map_components;
|
||||||
|
#[path = "2_map_tick_changes.rs"]
|
||||||
|
mod map_tick_changes;
|
||||||
|
#[path = "2_store_active_ticks.rs"]
|
||||||
|
mod store_active_ticks;
|
||||||
|
|
||||||
|
#[path = "3_map_liquidity_changes.rs"]
|
||||||
|
mod map_liquidity_changes;
|
||||||
|
#[path = "3_store_pool_details.rs"]
|
||||||
|
mod store_pool_details;
|
||||||
|
#[path = "3_store_tick_liquidities.rs"]
|
||||||
|
mod store_tick_liquidities;
|
||||||
|
|
||||||
|
#[path = "4_map_balance_changes.rs"]
|
||||||
|
mod map_balance_changes;
|
||||||
|
#[path = "4_store_liquidities.rs"]
|
||||||
|
mod store_liquidities;
|
||||||
|
|
||||||
|
#[path = "5_store_balance_changes.rs"]
|
||||||
|
mod store_balance_changes;
|
||||||
|
|
||||||
|
#[path = "6_map_protocol_changes.rs"]
|
||||||
|
mod map_protocol_changes;
|
||||||
|
|
||||||
|
impl From<&TransactionTrace> for Transaction {
|
||||||
|
fn from(value: &TransactionTrace) -> Self {
|
||||||
|
Self {
|
||||||
|
hash: value.hash.clone(),
|
||||||
|
from: value.from.clone(),
|
||||||
|
to: value.to.clone(),
|
||||||
|
index: value.index.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Transaction> for tycho_substreams::prelude::Transaction {
|
||||||
|
fn from(value: Transaction) -> Self {
|
||||||
|
Self { hash: value.hash, from: value.from, to: value.to, index: value.index }
|
||||||
|
}
|
||||||
|
}
|
||||||
256
substreams/ethereum-ekubo/src/pb/ekubo.rs
Normal file
256
substreams/ethereum-ekubo/src/pb/ekubo.rs
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// @generated
|
||||||
|
/// Copy of tycho.evm.v1.Transaction to be able to implement conversions to/from TransactionTrace
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct Transaction {
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub hash: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub from: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(bytes="vec", tag="3")]
|
||||||
|
pub to: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(uint64, tag="4")]
|
||||||
|
pub index: u64,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct TickDelta {
|
||||||
|
/// bytes32
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub pool_id: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(int32, tag="2")]
|
||||||
|
pub tick_index: i32,
|
||||||
|
/// int128
|
||||||
|
#[prost(bytes="vec", tag="3")]
|
||||||
|
pub liquidity_net_delta: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(uint64, tag="4")]
|
||||||
|
pub ordinal: u64,
|
||||||
|
#[prost(message, optional, tag="5")]
|
||||||
|
pub transaction: ::core::option::Option<Transaction>,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct TickDeltas {
|
||||||
|
#[prost(message, repeated, tag="1")]
|
||||||
|
pub deltas: ::prost::alloc::vec::Vec<TickDelta>,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct LiquidityChange {
|
||||||
|
/// bytes32
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub pool_id: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// uint128 or int128, depending on change_type
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub value: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(enumeration="LiquidityChangeType", tag="3")]
|
||||||
|
pub change_type: i32,
|
||||||
|
#[prost(uint64, tag="4")]
|
||||||
|
pub ordinal: u64,
|
||||||
|
#[prost(message, optional, tag="5")]
|
||||||
|
pub transaction: ::core::option::Option<Transaction>,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct LiquidityChanges {
|
||||||
|
#[prost(message, repeated, tag="1")]
|
||||||
|
pub changes: ::prost::alloc::vec::Vec<LiquidityChange>,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct PoolDetails {
|
||||||
|
/// address
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub token0: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// address
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub token1: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(fixed64, tag="3")]
|
||||||
|
pub fee: u64,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct BlockTransactionEvents {
|
||||||
|
#[prost(message, repeated, tag="1")]
|
||||||
|
pub block_transaction_events: ::prost::alloc::vec::Vec<block_transaction_events::TransactionEvents>,
|
||||||
|
}
|
||||||
|
/// Nested message and enum types in `BlockTransactionEvents`.
|
||||||
|
pub mod block_transaction_events {
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct TransactionEvents {
|
||||||
|
#[prost(message, optional, tag="1")]
|
||||||
|
pub transaction: ::core::option::Option<super::Transaction>,
|
||||||
|
#[prost(message, repeated, tag="2")]
|
||||||
|
pub pool_logs: ::prost::alloc::vec::Vec<transaction_events::PoolLog>,
|
||||||
|
}
|
||||||
|
/// Nested message and enum types in `TransactionEvents`.
|
||||||
|
pub mod transaction_events {
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct PoolLog {
|
||||||
|
#[prost(uint64, tag="1")]
|
||||||
|
pub ordinal: u64,
|
||||||
|
/// bytes32
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub pool_id: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(oneof="pool_log::Event", tags="3, 4, 5, 6, 7")]
|
||||||
|
pub event: ::core::option::Option<pool_log::Event>,
|
||||||
|
}
|
||||||
|
/// Nested message and enum types in `PoolLog`.
|
||||||
|
pub mod pool_log {
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct Swapped {
|
||||||
|
/// int128
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub delta0: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// int128
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub delta1: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// uint192
|
||||||
|
#[prost(bytes="vec", tag="3")]
|
||||||
|
pub sqrt_ratio_after: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// uint128
|
||||||
|
#[prost(bytes="vec", tag="4")]
|
||||||
|
pub liquidity_after: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// int32
|
||||||
|
#[prost(sint32, tag="5")]
|
||||||
|
pub tick_after: i32,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct PositionUpdated {
|
||||||
|
/// int32
|
||||||
|
#[prost(sint32, tag="1")]
|
||||||
|
pub lower: i32,
|
||||||
|
/// int32
|
||||||
|
#[prost(sint32, tag="2")]
|
||||||
|
pub upper: i32,
|
||||||
|
/// int128
|
||||||
|
#[prost(bytes="vec", tag="3")]
|
||||||
|
pub liquidity_delta: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// int128
|
||||||
|
#[prost(bytes="vec", tag="4")]
|
||||||
|
pub delta0: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// int128
|
||||||
|
#[prost(bytes="vec", tag="5")]
|
||||||
|
pub delta1: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
#[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<u8>,
|
||||||
|
/// uint128
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub amount1: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct PoolInitialized {
|
||||||
|
/// address
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub token0: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// address
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub token1: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// bytes32
|
||||||
|
#[prost(bytes="vec", tag="3")]
|
||||||
|
pub config: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// int32
|
||||||
|
#[prost(sint32, tag="4")]
|
||||||
|
pub tick: i32,
|
||||||
|
/// uint192
|
||||||
|
#[prost(bytes="vec", tag="5")]
|
||||||
|
pub sqrt_ratio: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(enumeration="pool_initialized::Extension", tag="6")]
|
||||||
|
pub extension: i32,
|
||||||
|
}
|
||||||
|
/// Nested message and enum types in `PoolInitialized`.
|
||||||
|
pub mod pool_initialized {
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Extension {
|
||||||
|
Unknown = 0,
|
||||||
|
Base = 1,
|
||||||
|
Oracle = 2,
|
||||||
|
}
|
||||||
|
impl Extension {
|
||||||
|
/// String value of the enum field names used in the ProtoBuf definition.
|
||||||
|
///
|
||||||
|
/// The values are not transformed in any way and thus are considered stable
|
||||||
|
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||||
|
pub fn as_str_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Extension::Unknown => "EXTENSION_UNKNOWN",
|
||||||
|
Extension::Base => "EXTENSION_BASE",
|
||||||
|
Extension::Oracle => "EXTENSION_ORACLE",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||||
|
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||||
|
match value {
|
||||||
|
"EXTENSION_UNKNOWN" => Some(Self::Unknown),
|
||||||
|
"EXTENSION_BASE" => Some(Self::Base),
|
||||||
|
"EXTENSION_ORACLE" => Some(Self::Oracle),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct FeesAccumulated {
|
||||||
|
/// uint128
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub amount0: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// uint128
|
||||||
|
#[prost(bytes="vec", tag="2")]
|
||||||
|
pub amount1: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
||||||
|
pub enum Event {
|
||||||
|
#[prost(message, tag="3")]
|
||||||
|
Swapped(Swapped),
|
||||||
|
#[prost(message, tag="4")]
|
||||||
|
PositionUpdated(PositionUpdated),
|
||||||
|
#[prost(message, tag="5")]
|
||||||
|
PositionFeesCollected(PositionFeesCollected),
|
||||||
|
#[prost(message, tag="6")]
|
||||||
|
PoolInitialized(PoolInitialized),
|
||||||
|
#[prost(message, tag="7")]
|
||||||
|
FeesAccumulated(FeesAccumulated),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum LiquidityChangeType {
|
||||||
|
Delta = 0,
|
||||||
|
Absolute = 1,
|
||||||
|
}
|
||||||
|
impl LiquidityChangeType {
|
||||||
|
/// String value of the enum field names used in the ProtoBuf definition.
|
||||||
|
///
|
||||||
|
/// The values are not transformed in any way and thus are considered stable
|
||||||
|
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||||
|
pub fn as_str_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
LiquidityChangeType::Delta => "DELTA",
|
||||||
|
LiquidityChangeType::Absolute => "ABSOLUTE",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||||
|
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||||
|
match value {
|
||||||
|
"DELTA" => Some(Self::Delta),
|
||||||
|
"ABSOLUTE" => Some(Self::Absolute),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @@protoc_insertion_point(module)
|
||||||
6
substreams/ethereum-ekubo/src/pb/mod.rs
Normal file
6
substreams/ethereum-ekubo/src/pb/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// @generated
|
||||||
|
// @@protoc_insertion_point(attribute:ekubo)
|
||||||
|
pub mod ekubo {
|
||||||
|
include!("ekubo.rs");
|
||||||
|
// @@protoc_insertion_point(ekubo)
|
||||||
|
}
|
||||||
15
substreams/ethereum-ekubo/src/pool_config.rs
Normal file
15
substreams/ethereum-ekubo/src/pool_config.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
pub struct PoolConfig {
|
||||||
|
pub fee: Vec<u8>,
|
||||||
|
pub tick_spacing: Vec<u8>,
|
||||||
|
pub extension: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
substreams/ethereum-ekubo/src/sqrt_ratio.rs
Normal file
11
substreams/ethereum-ekubo/src/sqrt_ratio.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use substreams::scalar::BigInt;
|
||||||
|
|
||||||
|
const BIT_MASK: u128 = 0xc00000000000000000000000;
|
||||||
|
const NOT_BIT_MASK: u128 = 0x3fffffffffffffffffffffff;
|
||||||
|
|
||||||
|
pub fn float_sqrt_ratio_to_fixed(sqrt_ratio_float: BigInt) -> Vec<u8> {
|
||||||
|
let sqrt_ratio_fixed = (sqrt_ratio_float.clone() & NOT_BIT_MASK) <<
|
||||||
|
<BigInt as Into<u32>>::into(2_u64 + ((sqrt_ratio_float & BIT_MASK) >> 89_u8));
|
||||||
|
|
||||||
|
sqrt_ratio_fixed.to_bytes_be().1
|
||||||
|
}
|
||||||
122
substreams/ethereum-ekubo/substreams.yaml
Normal file
122
substreams/ethereum-ekubo/substreams.yaml
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
specVersion: v0.1.0
|
||||||
|
package:
|
||||||
|
name: "ethereum_ekubo"
|
||||||
|
version: v0.1.0
|
||||||
|
|
||||||
|
protobuf:
|
||||||
|
files:
|
||||||
|
- tycho/evm/v1/common.proto
|
||||||
|
- tycho/evm/v1/utils.proto
|
||||||
|
- ekubo.proto
|
||||||
|
importPaths:
|
||||||
|
- ../../proto
|
||||||
|
- ./proto
|
||||||
|
excludePaths:
|
||||||
|
- sf/substreams
|
||||||
|
- google
|
||||||
|
- tycho
|
||||||
|
|
||||||
|
binaries:
|
||||||
|
default:
|
||||||
|
type: wasm/rust-v1
|
||||||
|
file: ../target/wasm32-unknown-unknown/release/ethereum_ekubo.wasm
|
||||||
|
|
||||||
|
network: ethereum
|
||||||
|
networks:
|
||||||
|
ethereum:
|
||||||
|
initialBlock:
|
||||||
|
map_events: 22048334 # First pool initialization https://etherscan.io/tx/0x7c2e697e73dc1f114a5473d1015c411f10585b2b671bee0bd6d5706895e16b27
|
||||||
|
params:
|
||||||
|
map_events: "core=e0e0e08A6A4b9Dc7bD67BCB7aadE5cF48157d444&oracle=51d02A5948496a67827242EaBc5725531342527C"
|
||||||
|
|
||||||
|
modules:
|
||||||
|
- name: map_events
|
||||||
|
kind: map
|
||||||
|
inputs:
|
||||||
|
- params: string
|
||||||
|
- source: sf.ethereum.type.v2.Block
|
||||||
|
output:
|
||||||
|
type: proto:ekubo.BlockTransactionEvents
|
||||||
|
|
||||||
|
- name: map_components
|
||||||
|
kind: map
|
||||||
|
inputs:
|
||||||
|
- map: map_events
|
||||||
|
output:
|
||||||
|
type: proto:tycho.evm.v1.BlockChanges
|
||||||
|
|
||||||
|
- name: map_tick_changes
|
||||||
|
kind: map
|
||||||
|
inputs:
|
||||||
|
- map: map_events
|
||||||
|
output:
|
||||||
|
type: proto:ekubo.TickDeltas
|
||||||
|
|
||||||
|
- name: store_active_ticks
|
||||||
|
kind: store
|
||||||
|
valueType: int64
|
||||||
|
updatePolicy: set
|
||||||
|
inputs:
|
||||||
|
- map: map_events
|
||||||
|
|
||||||
|
- name: map_liquidity_changes
|
||||||
|
kind: map
|
||||||
|
inputs:
|
||||||
|
- map: map_events
|
||||||
|
- store: store_active_ticks
|
||||||
|
output:
|
||||||
|
type: proto:ekubo.LiquidityChanges
|
||||||
|
|
||||||
|
- name: store_pool_details
|
||||||
|
kind: store
|
||||||
|
valueType: proto:ekubo.PoolDetails
|
||||||
|
updatePolicy: set_if_not_exists
|
||||||
|
inputs:
|
||||||
|
- map: map_components
|
||||||
|
|
||||||
|
- name: store_tick_liquidities
|
||||||
|
kind: store
|
||||||
|
valueType: bigint
|
||||||
|
updatePolicy: add
|
||||||
|
inputs:
|
||||||
|
- map: map_tick_changes
|
||||||
|
|
||||||
|
- name: map_balance_changes
|
||||||
|
kind: map
|
||||||
|
inputs:
|
||||||
|
- map: map_events
|
||||||
|
- store: store_pool_details
|
||||||
|
output:
|
||||||
|
type: proto:tycho.evm.v1.BlockBalanceDeltas
|
||||||
|
|
||||||
|
- name: store_liquidities
|
||||||
|
kind: store
|
||||||
|
valueType: bigint
|
||||||
|
updatePolicy: set_sum
|
||||||
|
inputs:
|
||||||
|
- map: map_liquidity_changes
|
||||||
|
|
||||||
|
- name: store_balance_changes
|
||||||
|
kind: store
|
||||||
|
valueType: bigint
|
||||||
|
updatePolicy: add
|
||||||
|
inputs:
|
||||||
|
- map: map_balance_changes
|
||||||
|
|
||||||
|
- name: map_protocol_changes
|
||||||
|
kind: map
|
||||||
|
inputs:
|
||||||
|
- source: sf.ethereum.type.v2.Block
|
||||||
|
- map: map_components
|
||||||
|
- map: map_events
|
||||||
|
- map: map_balance_changes
|
||||||
|
- store: store_balance_changes
|
||||||
|
mode: deltas
|
||||||
|
- map: map_tick_changes
|
||||||
|
- store: store_tick_liquidities
|
||||||
|
mode: deltas
|
||||||
|
- map: map_liquidity_changes
|
||||||
|
- store: store_liquidities
|
||||||
|
mode: deltas
|
||||||
|
output:
|
||||||
|
type: proto:tycho.evm.v1.BlockChanges
|
||||||
Reference in New Issue
Block a user