feat(substreams): add substreams for Uniswap v2 and v3

This commit is contained in:
zizou
2024-10-11 12:57:34 +02:00
parent 58455a1188
commit 73d48236ba
70 changed files with 16697 additions and 1 deletions

96
substreams/Cargo.lock generated
View File

@@ -29,6 +29,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bigdecimal"
version = "0.3.1"
@@ -126,6 +132,12 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "downcast-rs"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "either"
version = "1.10.0"
@@ -934,6 +946,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "substreams-entity-change"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2c7fca123abff659d15ed30da5b605fa954a29e912c94260c488d0d18f9107d"
dependencies = [
"base64",
"prost 0.11.9",
"prost-types 0.11.9",
"substreams",
]
[[package]]
name = "substreams-ethereum"
version = "0.9.9"
@@ -965,6 +989,20 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "substreams-ethereum-ambient"
version = "0.3.0"
dependencies = [
"anyhow",
"bytes",
"ethabi 18.0.0",
"hex",
"hex-literal 0.4.1",
"prost 0.11.9",
"substreams",
"substreams-ethereum",
]
[[package]]
name = "substreams-ethereum-core"
version = "0.9.9"
@@ -997,6 +1035,64 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "substreams-ethereum-uniswap-v2"
version = "0.2.0"
dependencies = [
"anyhow",
"ethabi 18.0.0",
"getrandom",
"hex-literal 0.4.1",
"itertools 0.12.1",
"num-bigint",
"prost 0.11.9",
"serde",
"serde_qs",
"substreams",
"substreams-ethereum",
"substreams-helper",
]
[[package]]
name = "substreams-ethereum-uniswap-v3"
version = "0.2.0"
dependencies = [
"anyhow",
"ethabi 18.0.0",
"getrandom",
"hex",
"hex-literal 0.4.1",
"num-bigint",
"prost 0.11.9",
"substreams",
"substreams-entity-change",
"substreams-ethereum",
"substreams-helper",
"tiny-keccak",
]
[[package]]
name = "substreams-helper"
version = "0.0.2"
dependencies = [
"anyhow",
"base64",
"bigdecimal",
"downcast-rs",
"ethabi 18.0.0",
"hex",
"hex-literal 0.4.1",
"num-bigint",
"pad",
"prost 0.11.9",
"prost-types 0.12.3",
"substreams",
"substreams-entity-change",
"substreams-ethereum",
"thiserror",
"tiny-keccak",
]
[[package]]
name = "substreams-macro"
version = "0.5.13"

View File

@@ -1,5 +1,13 @@
[workspace]
members = ["ethereum-balancer", "ethereum-curve", "crates/tycho-substreams"]
members = [
"ethereum-balancer",
"ethereum-curve",
"crates/tycho-substreams",
"crates/substreams-helper",
"ethereum-ambient",
"ethereum-uniswap-v2",
"ethereum-uniswap-v3",
]
resolver = "2"
@@ -9,9 +17,11 @@ substreams = "0.5"
prost = "0.11"
prost-types = "0.12.3"
hex-literal = "0.4.1"
anyhow = "1.0.75"
hex = "0.4.3"
ethabi = "18.0.0"
tycho-substreams = { path = "crates/tycho-substreams" }
substreams-helper = { path = "crates/substreams-helper" }
serde = "1.0.204"
serde_json = "1.0.120"

View File

@@ -0,0 +1 @@
Cargo.lock

View File

@@ -0,0 +1,29 @@
[package]
name = "substreams-helper"
description = "Substreams Helper Crate - by Messari"
version = "0.0.2"
edition = "2021"
license = "MIT"
homepage = "https://messari.io/"
repository = "https://github.com/messari/substreams/substreams-helper"
[dependencies]
ethabi.workspace = true
hex.workspace = true
hex-literal.workspace = true
prost.workspace = true
prost-types.workspace = true
num-bigint = "0.4"
bigdecimal = "0.3"
pad = "0.1"
tiny-keccak = { version = "2.0", features = ["keccak"] }
substreams = { workspace = true }
substreams-ethereum = { workspace = true }
thiserror = "1.0.37"
downcast-rs = "1.2.0"
substreams-entity-change = "1.1.0"
base64 = "0.13.0"
[build-dependencies]
anyhow.workspace = true

View File

@@ -0,0 +1,7 @@
.PHONY: build
build:
cargo build --target wasm32-unknown-unknown --release
.PHONY: publish
publish:
cargo publish --target wasm32-unknown-unknown

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "1.75.0"
components = [ "rustfmt" ]
targets = [ "wasm32-unknown-unknown" ]

View File

@@ -0,0 +1,69 @@
use ethabi::ethereum_types::Address;
use substreams::store::{
StoreGet, StoreGetBigDecimal, StoreGetBigInt, StoreGetInt64, StoreGetProto, StoreGetRaw,
StoreGetString,
};
use crate::hex::Hexable;
/// HasAddresser is a trait that a few functionalities in this crate depend on.
/// Every time we need to filter something by address (events emmited by a set of addresses,
/// storage changes occurring on certain contracts, etc) you'll likely need
/// to provide a HasAddresser.
///
/// HasAddresser has been implemented already for all substreams::store's for convenience.
/// So if you know a given store module contains the list of addresses you want to filter by
/// you can pass it directly as a HasAddresser. In this case, the addresses need to be the store key
/// hex encoded as a string including the leading 0x. The value of the store is ignored.
pub trait HasAddresser {
fn has_address(&self, key: Address) -> bool;
}
impl HasAddresser for Vec<Address> {
fn has_address(&self, key: Address) -> bool {
self.contains(&key)
}
}
impl HasAddresser for StoreGetString {
fn has_address(&self, key: Address) -> bool {
self.get_last(key.to_hex()).is_some()
}
}
impl<T: Default + prost::Message> HasAddresser for StoreGetProto<T> {
fn has_address(&self, key: Address) -> bool {
self.get_last(key.to_hex()).is_some()
}
}
impl HasAddresser for StoreGetRaw {
fn has_address(&self, key: Address) -> bool {
self.get_last(key.to_hex()).is_some()
}
}
impl HasAddresser for StoreGetBigInt {
fn has_address(&self, key: Address) -> bool {
self.get_last(key.to_hex()).is_some()
}
}
impl HasAddresser for StoreGetBigDecimal {
fn has_address(&self, key: Address) -> bool {
self.get_last(key.to_hex()).is_some()
}
}
impl HasAddresser for StoreGetInt64 {
fn has_address(&self, key: Address) -> bool {
self.get_last(key.to_hex()).is_some()
}
}
impl HasAddresser for Address {
fn has_address(&self, key: Address) -> bool {
key == *self
}
}

View File

@@ -0,0 +1,103 @@
use std::collections::HashMap;
use ethabi::ethereum_types::Address;
use substreams_ethereum::{
pb::eth::v2::{self as eth},
Event,
};
use crate::common::HasAddresser;
/// Utility struct to easily filter events and assign them handlers.
///
/// Usage:
/// ```
/// let eh = EventHandler::new(&block);
/// eh.filter_by_address(store); // This is optional, if omitted it will handle all events that match the type, independently of the emitting contract.
/// eh.on::<Transfer, _>(&mut on_transfer);
/// eh.on::<Approval, _>(&mut on_approval);
/// eh.on::<Mint, _>(&mut on_mint);
/// eh.on::<Burn, _>(&mut on_burn);
/// eh.handle_events(); // this will run all handlers
/// ```
///
/// You'll likely want to mutate some value from the handlers that is in the current scope.
/// For that, make your handlers be closures, that close over the variable you want to mutate, and
/// have the whole EventHandler block of code in its own scope (either by wrapping it in an aux
/// function or by wrapping it in {...})
///
/// Like so:
/// ```
/// let mut balances : Vec<Balance> = vec![];
/// {
/// let mut on_transfer = |/*...*/| {
/// // this handler modifies `balances`
/// balances.push(some_balance);
/// };
/// let eh = EventHandler::new(&block);
/// eh.on::<Transfer, _>(&mut on_transfer);
/// eh.handle_events();
/// }
///
/// // do whatever else with `balances` here.
/// ```
pub struct EventHandler<'a> {
block: &'a eth::Block,
#[allow(clippy::type_complexity)]
handlers: HashMap<&'static str, Box<dyn FnMut(&eth::Log, &eth::TransactionTrace) + 'a>>,
addresses: Option<Box<dyn HasAddresser + 'a>>,
}
impl<'a> EventHandler<'a> {
pub fn new(block: &'a eth::Block) -> Self {
Self { block, handlers: HashMap::new(), addresses: None }
}
/// Sets the HasAddresser as a filter for which events to handle.
/// Only one at a time can be set. Setting it twice will remove the first one.
/// Addresses found in the `HasAddresser` will be the ones we'll handle events from.
pub fn filter_by_address(&mut self, addresser: impl HasAddresser + 'a) {
self.addresses = Some(Box::new(addresser));
}
/// Registers a handler to be run on a given event. The handler should have the signature:
/// `|ev: SomeEvent, tx: &pbeth::v2::TransactionTrace, log: &pbeth::v2::Log|`.
/// You can only assign one handler to each Event type.
/// Handlers are keyed by the name of the event they are handling, so be careful to not assign
/// handlers for 2 different events named equal.
pub fn on<E: Event, F>(&mut self, mut handler: F)
where
F: FnMut(E, &eth::TransactionTrace, &eth::Log) + 'a,
{
self.handlers.insert(
E::NAME,
Box::new(move |log: &eth::Log, tx: &eth::TransactionTrace| {
if let Some(event) = E::match_and_decode(log) {
handler(event, tx, log);
}
}),
);
}
/// Will run all registered handlers for all events present on the block that match the given
/// filters. You'll likely want to run this just once.
pub fn handle_events(&mut self) {
// Here we don't need to filter out failed transactions because logs only exist for
// successful ones.
for log in self.block.logs() {
if self.addresses.is_some() &&
!&self
.addresses
.as_ref()
.unwrap()
.has_address(Address::from_slice(log.log.address.as_slice()))
{
continue;
}
for handler in self.handlers.values_mut() {
handler(log.log, log.receipt.transaction);
}
}
}
}

View File

@@ -0,0 +1,20 @@
use ethabi::ethereum_types::Address;
use substreams::Hex;
pub trait Hexable {
fn to_hex(&self) -> String;
}
impl Hexable for Vec<u8> {
fn to_hex(&self) -> String {
let mut str = Hex::encode(self);
str.insert_str(0, "0x");
str
}
}
impl Hexable for Address {
fn to_hex(&self) -> String {
self.as_bytes().to_vec().to_hex()
}
}

View File

@@ -0,0 +1,5 @@
pub mod common;
pub mod event_handler;
pub mod hex;
pub mod storage_change;

View File

@@ -0,0 +1,14 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
pub trait StorageChangesFilter {
fn filter_by_address(&self, contract_addr: &[u8; 20]) -> Vec<&StorageChange>;
}
impl StorageChangesFilter for Vec<StorageChange> {
fn filter_by_address(&self, contract_addr: &[u8; 20]) -> Vec<&StorageChange> {
return self
.iter()
.filter(|change| change.address == contract_addr)
.collect();
}
}

View File

@@ -0,0 +1,28 @@
[package]
name = "substreams-ethereum-uniswap-v2"
version = "0.2.0"
edition = "2021"
[lib]
name = "ethereum_uniswap_v2"
crate-type = ["cdylib"]
[dependencies]
substreams.workspace = true
substreams-ethereum.workspace = true
prost.workspace = true
ethabi.workspace = true
anyhow = { workspace = true, features = [] }
hex-literal.workspace = true
substreams-helper.workspace = true
num-bigint = "0.4.4"
itertools = "0.12.1"
serde_qs = "0.13.0"
serde.workspace = true
[target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2", features = ["custom"] }
[build-dependencies]
anyhow.workspace = true
substreams-ethereum.workspace = true

View File

@@ -0,0 +1,2 @@
build:
cargo build --target wasm32-unknown-unknown --release

View File

@@ -0,0 +1,125 @@
[
{
"inputs": [
{ "internalType": "address", "name": "_feeToSetter", "type": "address" }
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "pair",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "PairCreated",
"type": "event"
},
{
"constant": true,
"inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"name": "allPairs",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "allPairsLength",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "address", "name": "tokenA", "type": "address" },
{ "internalType": "address", "name": "tokenB", "type": "address" }
],
"name": "createPair",
"outputs": [
{ "internalType": "address", "name": "pair", "type": "address" }
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "feeTo",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "feeToSetter",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "address", "name": "", "type": "address" }
],
"name": "getPair",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "address", "name": "_feeTo", "type": "address" }
],
"name": "setFeeTo",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "internalType": "address", "name": "_feeToSetter", "type": "address" }
],
"name": "setFeeToSetter",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -0,0 +1,713 @@
[
{
"inputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0In",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1In",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0Out",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1Out",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "Swap",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint112",
"name": "reserve0",
"type": "uint112"
},
{
"indexed": false,
"internalType": "uint112",
"name": "reserve1",
"type": "uint112"
}
],
"name": "Sync",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"constant": true,
"inputs": [],
"name": "DOMAIN_SEPARATOR",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MINIMUM_LIQUIDITY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "PERMIT_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "burn",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getReserves",
"outputs": [
{
"internalType": "uint112",
"name": "_reserve0",
"type": "uint112"
},
{
"internalType": "uint112",
"name": "_reserve1",
"type": "uint112"
},
{
"internalType": "uint32",
"name": "_blockTimestampLast",
"type": "uint32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "_token0",
"type": "address"
},
{
"internalType": "address",
"name": "_token1",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "kLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "nonces",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "permit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "price0CumulativeLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "price1CumulativeLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "skim",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "amount0Out",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1Out",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swap",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "sync",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "token0",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "token1",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -0,0 +1,49 @@
specVersion: v0.1.0
package:
name: "arbitrum_uniswap_v2"
version: v0.2.0
protobuf:
files:
- tycho/evm/v1/common.proto
- tycho/evm/v1/entity.proto
- uniswap.proto
importPaths:
- ./proto/v1
- ../../proto/
binaries:
default:
type: wasm/rust-v1
file: ../../target/wasm32-unknown-unknown/substreams/ethereum_uniswap_v2.wasm
modules:
- name: map_pools_created
kind: map
initialBlock: 150442611
inputs:
- params: string
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.BlockChanges
- name: store_pools
kind: store
initialBlock: 150442611
updatePolicy: set_if_not_exists
valueType: proto:tycho.evm.uniswap.v2.Pool
inputs:
- map: map_pools_created
- name: map_pool_events
kind: map
initialBlock: 150442611
inputs:
- source: sf.ethereum.type.v2.Block
- map: map_pools_created
- store: store_pools
output:
type: proto:tycho.evm.v1.BlockChanges
params:
map_pools_created: factory_address=f1D7CC64Fb4452F05c498126312eBE29f30Fbcf9&protocol_type_name=uniswap_v2_pool

View File

@@ -0,0 +1,12 @@
use anyhow::{Ok, Result};
use substreams_ethereum::Abigen;
fn main() -> Result<(), anyhow::Error> {
Abigen::new("Factory", "abi/Factory.json")?
.generate()?
.write_to_file("src/abi/factory.rs")?;
Abigen::new("Pool", "abi/Pool.json")?
.generate()?
.write_to_file("src/abi/pool.rs")?;
Ok(())
}

View File

@@ -0,0 +1,49 @@
specVersion: v0.1.0
package:
name: "ethereum_pancakeswap"
version: v0.2.0
protobuf:
files:
- tycho/evm/v1/common.proto
- tycho/evm/v1/entity.proto
- uniswap.proto
importPaths:
- ./proto/v1
- ../../proto/
binaries:
default:
type: wasm/rust-v1
file: ../../target/wasm32-unknown-unknown/substreams/ethereum_uniswap_v2.wasm
modules:
- name: map_pools_created
kind: map
initialBlock: 15614590
inputs:
- params: string
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.BlockChanges
- name: store_pools
kind: store
initialBlock: 15614590
updatePolicy: set_if_not_exists
valueType: proto:tycho.evm.v1.ProtocolComponent
inputs:
- map: map_pools_created
- name: map_pool_events
kind: map
initialBlock: 15614590
inputs:
- source: sf.ethereum.type.v2.Block
- map: map_pools_created
- store: store_pools
output:
type: proto:tycho.evm.v1.BlockChanges
params:
map_pools_created: factory_address=1097053fd2ea711dad45caccc45eff7548fcb362&protocol_type_name=pancakeswap_pool

View File

@@ -0,0 +1,49 @@
specVersion: v0.1.0
package:
name: "ethereum_sushiswap_v2"
version: v0.2.0
protobuf:
files:
- tycho/evm/v1/common.proto
- tycho/evm/v1/entity.proto
- uniswap.proto
importPaths:
- ./proto/v1
- ../../proto/
binaries:
default:
type: wasm/rust-v1
file: ../../target/wasm32-unknown-unknown/substreams/ethereum_uniswap_v2.wasm
modules:
- name: map_pools_created
kind: map
initialBlock: 10794229
inputs:
- params: string
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.BlockChanges
- name: store_pools
kind: store
initialBlock: 10794229
updatePolicy: set_if_not_exists
valueType: proto:tycho.evm.uniswap.v2.Pool
inputs:
- map: map_pools_created
- name: map_pool_events
kind: map
initialBlock: 10794229
inputs:
- source: sf.ethereum.type.v2.Block
- map: map_pools_created
- store: store_pools
output:
type: proto:tycho.evm.v1.BlockChanges
params:
map_pools_created: factory_address=c0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac&protocol_type_name=sushiswap_v2_pool

View File

@@ -0,0 +1,49 @@
specVersion: v0.1.0
package:
name: "ethereum_uniswap_v2"
version: v0.2.0
protobuf:
files:
- tycho/evm/v1/common.proto
- tycho/evm/v1/entity.proto
- uniswap.proto
importPaths:
- ./proto/v1
- ../../proto/
binaries:
default:
type: wasm/rust-v1
file: ../../target/wasm32-unknown-unknown/substreams/ethereum_uniswap_v2.wasm
modules:
- name: map_pools_created
kind: map
initialBlock: 10008300
inputs:
- params: string
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.BlockChanges
- name: store_pools
kind: store
initialBlock: 10008300
updatePolicy: set_if_not_exists
valueType: proto:tycho.evm.uniswap.v2.Pool
inputs:
- map: map_pools_created
- name: map_pool_events
kind: map
initialBlock: 10008300
inputs:
- source: sf.ethereum.type.v2.Block
- map: map_pools_created
- store: store_pools
output:
type: proto:tycho.evm.v1.BlockChanges
params:
map_pools_created: factory_address=5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f&protocol_type_name=uniswap_v2_pool

View File

@@ -0,0 +1,61 @@
syntax = "proto3";
package tycho.evm.uniswap.v2;
message Pools {
repeated Pool pools = 1;
}
message Pool {
bytes address = 1;
bytes token0 = 2;
bytes token1 = 3;
bytes created_tx_hash = 4;
}
message Events {
repeated Event events = 1;
}
message Event {
oneof type {
DepositEvent deposit_type = 10;
WithdrawEvent withdraw_type = 20;
SyncEvent sync_type = 30;
SwapEvent swap_type = 40;
}
string hash = 100;
uint32 log_index = 101;
uint64 log_ordinal = 102;
string to = 103;
string from = 104;
uint64 block_number = 105;
uint64 timestamp = 106;
string pool = 107;
}
message DepositEvent {
repeated string input_token_amounts = 1;
optional string output_token_amount = 2;
}
message WithdrawEvent {
repeated string input_token_amounts = 1;
optional string output_token_amount = 2;
}
message SyncEvent {
string reserve0 = 1;
string reserve1 = 2;
}
message SwapEvent {
string token_in = 1;
string amount_in = 2;
string token_out = 3;
string amount_out = 4;
}

View File

@@ -0,0 +1,904 @@
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 AllPairs {
pub param0: substreams::scalar::BigInt,
}
impl AllPairs {
const METHOD_ID: [u8; 4] = [30u8, 61u8, 209u8, 139u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
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<u8> {
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 output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Vec<u8>, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<Vec<u8>, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<Vec<u8>> {
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 AllPairs {
const NAME: &'static str = "allPairs";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<Vec<u8>> for AllPairs {
fn output(data: &[u8]) -> Result<Vec<u8>, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AllPairsLength {}
impl AllPairsLength {
const METHOD_ID: [u8; 4] = [87u8, 79u8, 43u8, 163u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Ok(Self {})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(&[]);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<substreams::scalar::BigInt, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<substreams::scalar::BigInt, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Uint(256usize)],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok({
let mut v = [0 as u8; 32];
values
.pop()
.expect("one output data should have existed")
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
})
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<substreams::scalar::BigInt> {
use substreams_ethereum::pb::eth::rpc;
let rpc_calls = rpc::RpcCalls {
calls: vec![
rpc::RpcCall { to_addr : address, data : self.encode(), }
],
};
let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses;
let response = responses
.get(0)
.expect("one response should have existed");
if response.failed {
return None;
}
match Self::output(response.raw.as_ref()) {
Ok(data) => Some(data),
Err(err) => {
use substreams_ethereum::Function;
substreams::log::info!(
"Call output for function `{}` failed to decode with error: {}",
Self::NAME, err
);
None
}
}
}
}
impl substreams_ethereum::Function for AllPairsLength {
const NAME: &'static str = "allPairsLength";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<substreams::scalar::BigInt>
for AllPairsLength {
fn output(data: &[u8]) -> Result<substreams::scalar::BigInt, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreatePair {
pub token_a: Vec<u8>,
pub token_b: Vec<u8>,
}
impl CreatePair {
const METHOD_ID: [u8; 4] = [201u8, 198u8, 83u8, 150u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
let maybe_data = call.input.get(4..);
if maybe_data.is_none() {
return Err("no data to decode".to_string());
}
let mut values = ethabi::decode(
&[ethabi::ParamType::Address, ethabi::ParamType::Address],
maybe_data.unwrap(),
)
.map_err(|e| format!("unable to decode call.input: {:?}", e))?;
values.reverse();
Ok(Self {
token_a: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
token_b: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(
&[
ethabi::Token::Address(
ethabi::Address::from_slice(&self.token_a),
),
ethabi::Token::Address(
ethabi::Address::from_slice(&self.token_b),
),
],
);
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<Vec<u8>, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<Vec<u8>, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<Vec<u8>> {
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 CreatePair {
const NAME: &'static str = "createPair";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<Vec<u8>> for CreatePair {
fn output(data: &[u8]) -> Result<Vec<u8>, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FeeTo {}
impl FeeTo {
const METHOD_ID: [u8; 4] = [1u8, 126u8, 126u8, 88u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Ok(Self {})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(&[]);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Vec<u8>, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<Vec<u8>, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<Vec<u8>> {
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 FeeTo {
const NAME: &'static str = "feeTo";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<Vec<u8>> for FeeTo {
fn output(data: &[u8]) -> Result<Vec<u8>, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FeeToSetter {}
impl FeeToSetter {
const METHOD_ID: [u8; 4] = [9u8, 75u8, 116u8, 21u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Ok(Self {})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(&[]);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Vec<u8>, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<Vec<u8>, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<Vec<u8>> {
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 FeeToSetter {
const NAME: &'static str = "feeToSetter";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<Vec<u8>> for FeeToSetter {
fn output(data: &[u8]) -> Result<Vec<u8>, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GetPair {
pub param0: Vec<u8>,
pub param1: Vec<u8>,
}
impl GetPair {
const METHOD_ID: [u8; 4] = [230u8, 164u8, 57u8, 5u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
let maybe_data = call.input.get(4..);
if maybe_data.is_none() {
return Err("no data to decode".to_string());
}
let mut values = ethabi::decode(
&[ethabi::ParamType::Address, ethabi::ParamType::Address],
maybe_data.unwrap(),
)
.map_err(|e| format!("unable to decode call.input: {:?}", e))?;
values.reverse();
Ok(Self {
param0: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
param1: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(
&[
ethabi::Token::Address(
ethabi::Address::from_slice(&self.param0),
),
ethabi::Token::Address(ethabi::Address::from_slice(&self.param1)),
],
);
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<Vec<u8>, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<Vec<u8>, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<Vec<u8>> {
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 GetPair {
const NAME: &'static str = "getPair";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<Vec<u8>> for GetPair {
fn output(data: &[u8]) -> Result<Vec<u8>, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SetFeeTo {
pub fee_to: Vec<u8>,
}
impl SetFeeTo {
const METHOD_ID: [u8; 4] = [244u8, 105u8, 1u8, 237u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
let maybe_data = call.input.get(4..);
if maybe_data.is_none() {
return Err("no data to decode".to_string());
}
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
maybe_data.unwrap(),
)
.map_err(|e| format!("unable to decode call.input: {:?}", e))?;
values.reverse();
Ok(Self {
fee_to: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(
&[ethabi::Token::Address(ethabi::Address::from_slice(&self.fee_to))],
);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
}
impl substreams_ethereum::Function for SetFeeTo {
const NAME: &'static str = "setFeeTo";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SetFeeToSetter {
pub fee_to_setter: Vec<u8>,
}
impl SetFeeToSetter {
const METHOD_ID: [u8; 4] = [162u8, 231u8, 74u8, 246u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
let maybe_data = call.input.get(4..);
if maybe_data.is_none() {
return Err("no data to decode".to_string());
}
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
maybe_data.unwrap(),
)
.map_err(|e| format!("unable to decode call.input: {:?}", e))?;
values.reverse();
Ok(Self {
fee_to_setter: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(
&[
ethabi::Token::Address(
ethabi::Address::from_slice(&self.fee_to_setter),
),
],
);
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 SetFeeToSetter {
const NAME: &'static str = "setFeeToSetter";
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, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
}
/// Contract's events.
#[allow(dead_code, unused_imports, unused_variables)]
pub mod events {
use super::INTERNAL_ERR;
#[derive(Debug, Clone, PartialEq)]
pub struct PairCreated {
pub token0: Vec<u8>,
pub token1: Vec<u8>,
pub pair: Vec<u8>,
pub param3: substreams::scalar::BigInt,
}
impl PairCreated {
const TOPIC_ID: [u8; 32] = [
13u8,
54u8,
72u8,
189u8,
15u8,
107u8,
168u8,
1u8,
52u8,
163u8,
59u8,
169u8,
39u8,
90u8,
197u8,
133u8,
217u8,
211u8,
21u8,
240u8,
173u8,
131u8,
85u8,
205u8,
222u8,
253u8,
227u8,
26u8,
250u8,
40u8,
208u8,
233u8,
];
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 3usize {
return false;
}
if log.data.len() != 64usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref()
== Self::TOPIC_ID;
}
pub fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)],
log.data.as_ref(),
)
.map_err(|e| format!("unable to decode log.data: {:?}", e))?;
values.reverse();
Ok(Self {
token0: ethabi::decode(
&[ethabi::ParamType::Address],
log.topics[1usize].as_ref(),
)
.map_err(|e| {
format!(
"unable to decode param 'token0' from topic of type 'address': {:?}",
e
)
})?
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
token1: ethabi::decode(
&[ethabi::ParamType::Address],
log.topics[2usize].as_ref(),
)
.map_err(|e| {
format!(
"unable to decode param 'token1' from topic of type 'address': {:?}",
e
)
})?
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
pair: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
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)
},
})
}
}
impl substreams_ethereum::Event for PairCreated {
const NAME: &'static str = "PairCreated";
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, String> {
Self::decode(log)
}
}
}

View File

@@ -0,0 +1,4 @@
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
pub mod factory;
pub mod pool;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
#![allow(clippy::not_unsafe_ptr_arg_deref)]
mod abi;
mod modules;
mod pb;
pub use modules::*;
mod store_key;
mod traits;

View File

@@ -0,0 +1,99 @@
use std::str::FromStr;
use ethabi::ethereum_types::Address;
use serde::Deserialize;
use substreams::prelude::BigInt;
use substreams_ethereum::pb::eth::v2::{self as eth};
use substreams_helper::{event_handler::EventHandler, hex::Hexable};
use crate::{
abi::factory::events::PairCreated,
pb::tycho::evm::v1::{
Attribute, Block, BlockChanges, ChangeType, EntityChanges, FinancialType,
ImplementationType, ProtocolComponent, ProtocolType, Transaction, TransactionChanges,
},
};
#[derive(Debug, Deserialize)]
struct Params {
factory_address: String,
protocol_type_name: String,
}
#[substreams::handlers::map]
pub fn map_pools_created(
params: String,
block: eth::Block,
) -> Result<BlockChanges, substreams::errors::Error> {
let mut new_pools: Vec<TransactionChanges> = vec![];
let params: Params = serde_qs::from_str(params.as_str()).expect("Unable to deserialize params");
get_pools(&block, &mut new_pools, &params);
let tycho_block: Block = block.into();
Ok(BlockChanges { block: Some(tycho_block), changes: new_pools })
}
fn get_pools(block: &eth::Block, new_pools: &mut Vec<TransactionChanges>, params: &Params) {
// Extract new pools from PairCreated events
let mut on_pair_created = |event: PairCreated, _tx: &eth::TransactionTrace, _log: &eth::Log| {
let tycho_tx: Transaction = _tx.into();
new_pools.push(TransactionChanges {
tx: Some(tycho_tx.clone()),
contract_changes: vec![],
entity_changes: vec![EntityChanges {
component_id: event.pair.to_hex(),
attributes: vec![
Attribute {
name: "reserve0".to_string(),
value: BigInt::from(0).to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
Attribute {
name: "reserve1".to_string(),
value: BigInt::from(0).to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
],
}],
component_changes: vec![ProtocolComponent {
id: event.pair.to_hex(),
tokens: vec![event.token0, event.token1],
contracts: vec![],
static_att: vec![
// Trading Fee is hardcoded to 0.3%, saved as int in bps (basis points)
Attribute {
name: "fee".to_string(),
value: BigInt::from(30).to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
Attribute {
name: "pool_address".to_string(),
value: event.pair,
change: ChangeType::Creation.into(),
},
],
change: i32::from(ChangeType::Creation),
protocol_type: Some(ProtocolType {
name: params.protocol_type_name.to_string(),
financial_type: FinancialType::Swap.into(),
attribute_schema: vec![],
implementation_type: ImplementationType::Custom.into(),
}),
tx: Some(tycho_tx),
}],
balance_changes: vec![],
})
};
let mut eh = EventHandler::new(block);
eh.filter_by_address(vec![Address::from_str(&params.factory_address).unwrap()]);
eh.on::<PairCreated, _>(&mut on_pair_created);
eh.handle_events();
}

View File

@@ -0,0 +1,25 @@
use substreams::store::{StoreNew, StoreSetIfNotExists, StoreSetIfNotExistsProto};
use crate::{
pb::tycho::evm::v1::{BlockChanges, ProtocolComponent},
store_key::StoreKey,
};
#[substreams::handlers::store]
pub fn store_pools(
pools_created: BlockChanges,
store: StoreSetIfNotExistsProto<ProtocolComponent>,
) {
// Store pools. Required so the next steps can match any event to a known pool by their address
for change in pools_created.changes {
for new_protocol_component in change.component_changes {
// Use ordinal 0 because the address should be unique, so ordering doesn't matter.
store.set_if_not_exists(
0,
StoreKey::Pool.get_unique_pool_key(&new_protocol_component.id),
&new_protocol_component,
);
}
}
}

View File

@@ -0,0 +1,232 @@
use itertools::Itertools;
use std::collections::HashMap;
use substreams::store::{StoreGet, StoreGetProto};
use substreams_ethereum::pb::eth::v2::{self as eth};
use substreams_helper::{event_handler::EventHandler, hex::Hexable};
use crate::{
abi::pool::events::Sync,
pb::tycho::evm::{
v1,
v1::{
Attribute, BalanceChange, BlockChanges, ChangeType, EntityChanges, ProtocolComponent,
TransactionChanges,
},
},
store_key::StoreKey,
traits::PoolAddresser,
};
// Auxiliary struct to serve as a key for the HashMaps.
#[derive(Clone, Hash, Eq, PartialEq)]
struct ComponentKey<T> {
component_id: String,
name: T,
}
impl<T> ComponentKey<T> {
fn new(component_id: String, name: T) -> Self {
ComponentKey { component_id, name }
}
}
#[derive(Clone)]
struct PartialChanges {
transaction: v1::Transaction,
entity_changes: HashMap<ComponentKey<String>, Attribute>,
balance_changes: HashMap<ComponentKey<Vec<u8>>, BalanceChange>,
}
impl PartialChanges {
// Consolidate the entity changes into a vector of EntityChanges. Initially, the entity changes
// are in a map to prevent duplicates. For each transaction, we need to have only one final
// state change, per state. Example:
// If we have two sync events for the same pool (in the same tx), we need to have only one final
// state change for the reserves. This will be the last sync event, as it is the final state
// of the pool after the transaction.
fn consolidate_entity_changes(self) -> Vec<EntityChanges> {
self.entity_changes
.into_iter()
.map(|(key, attribute)| (key.component_id, attribute))
.into_group_map()
.into_iter()
.map(|(component_id, attributes)| EntityChanges { component_id, attributes })
.collect()
}
}
#[substreams::handlers::map]
pub fn map_pool_events(
block: eth::Block,
block_entity_changes: BlockChanges,
pools_store: StoreGetProto<ProtocolComponent>,
) -> Result<BlockChanges, substreams::errors::Error> {
// Sync event is sufficient for our use-case. Since it's emitted on every reserve-altering
// function call, we can use it as the only event to update the reserves of a pool.
let mut block_entity_changes = block_entity_changes;
let mut tx_changes: HashMap<Vec<u8>, PartialChanges> = HashMap::new();
handle_sync(&block, &mut tx_changes, &pools_store);
merge_block(&mut tx_changes, &mut block_entity_changes);
Ok(block_entity_changes)
}
/// Handle the sync events and update the reserves of the pools.
///
/// This function is called for each block, and it will handle the sync events for each transaction.
/// On UniswapV2, Sync events are emitted on every reserve-altering function call, so we can use
/// only this event to keep track of the pool state.
///
/// This function also relies on an intermediate HashMap to store the changes for each transaction.
/// This is necessary because we need to consolidate the changes for each transaction before adding
/// them to the block_entity_changes. This HashMap prevents us from having duplicate changes for the
/// same pool and token. See the PartialChanges struct for more details.
fn handle_sync(
block: &eth::Block,
tx_changes: &mut HashMap<Vec<u8>, PartialChanges>,
store: &StoreGetProto<ProtocolComponent>,
) {
let mut on_sync = |event: Sync, _tx: &eth::TransactionTrace, _log: &eth::Log| {
let pool_address_hex = _log.address.to_hex();
let pool =
store.must_get_last(StoreKey::Pool.get_unique_pool_key(pool_address_hex.as_str()));
// Convert reserves to bytes
let reserves_bytes = [event.reserve0, event.reserve1];
let tx_change = tx_changes
.entry(_tx.hash.clone())
.or_insert_with(|| PartialChanges {
transaction: _tx.into(),
entity_changes: HashMap::new(),
balance_changes: HashMap::new(),
});
for (i, reserve_bytes) in reserves_bytes.iter().enumerate() {
let attribute_name = format!("reserve{}", i);
// By using a HashMap, we can overwrite the previous value of the reserve attribute if
// it is for the same pool and the same attribute name (reserves).
tx_change.entity_changes.insert(
ComponentKey::new(pool_address_hex.clone(), attribute_name.clone()),
Attribute {
name: attribute_name,
value: reserve_bytes
.clone()
.to_signed_bytes_le(), //TODO: Unify bytes encoding (either be or le)
change: ChangeType::Update.into(),
},
);
}
// Update balance changes for each token
for (index, token) in pool.tokens.iter().enumerate() {
let balance = &reserves_bytes[index];
// HashMap also prevents having duplicate balance changes for the same pool and token.
tx_change.balance_changes.insert(
ComponentKey::new(pool_address_hex.clone(), token.clone()),
BalanceChange {
token: token.clone(),
balance: balance.clone().to_signed_bytes_be(),
component_id: pool_address_hex.as_bytes().to_vec(),
},
);
}
};
let mut eh = EventHandler::new(block);
// Filter the sync events by the pool address, to make sure we don't process events for other
// Protocols that use the same event signature.
eh.filter_by_address(PoolAddresser { store });
eh.on::<Sync, _>(&mut on_sync);
eh.handle_events();
}
/// Merge the changes from the sync events with the create_pool events previously mapped on
/// block_entity_changes.
///
/// Parameters:
/// - tx_changes: HashMap with the changes for each transaction. This is the same HashMap used in
/// handle_sync
/// - block_entity_changes: The BlockChanges struct that will be updated with the changes from the
/// sync events.
/// This HashMap comes pre-filled with the changes for the create_pool events, mapped in
/// 1_map_pool_created.
///
/// This function is called after the handle_sync function, and it is expected that
/// block_entity_changes will be complete after this function ends.
fn merge_block(
tx_changes: &mut HashMap<Vec<u8>, PartialChanges>,
block_entity_changes: &mut BlockChanges,
) {
let mut tx_entity_changes_map = HashMap::new();
// Add created pools to the tx_changes_map
for change in block_entity_changes
.changes
.clone()
.into_iter()
{
let transaction = change.tx.as_ref().unwrap();
tx_entity_changes_map
.entry(transaction.hash.clone())
.and_modify(|c: &mut TransactionChanges| {
c.component_changes
.extend(change.component_changes.clone());
c.entity_changes
.extend(change.entity_changes.clone());
})
.or_insert(change);
}
// First, iterate through the previously created transactions, extracted from the
// map_pool_created step. If there are sync events for this transaction, add them to the
// block_entity_changes and the corresponding balance changes.
for change in tx_entity_changes_map.values_mut() {
let tx = change
.clone()
.tx
.expect("Transaction not found")
.clone();
// If there are sync events for this transaction, add them to the block_entity_changes
if let Some(partial_changes) = tx_changes.remove(&tx.hash) {
change.entity_changes = partial_changes
.clone()
.consolidate_entity_changes();
change.balance_changes = partial_changes
.balance_changes
.into_values()
.collect();
}
}
// If there are any transactions left in the tx_changes, it means that they are transactions
// that changed the state of the pools, but were not included in the block_entity_changes.
// This happens for every regular transaction that does not actually create a pool. By the
// end of this function, we expect block_entity_changes to be up-to-date with the changes
// for all sync and new_pools in the block.
for partial_changes in tx_changes.values() {
tx_entity_changes_map.insert(
partial_changes.transaction.hash.clone(),
TransactionChanges {
tx: Some(partial_changes.transaction.clone()),
contract_changes: vec![],
entity_changes: partial_changes
.clone()
.consolidate_entity_changes(),
balance_changes: partial_changes
.balance_changes
.clone()
.into_values()
.collect(),
component_changes: vec![],
},
);
}
block_entity_changes.changes = tx_entity_changes_map
.into_values()
.collect();
}

View File

@@ -0,0 +1,11 @@
pub use map_pool_created::map_pools_created;
pub use map_pool_events::map_pool_events;
pub use store_pools::store_pools;
#[path = "1_map_pool_created.rs"]
mod map_pool_created;
#[path = "2_store_pools.rs"]
mod store_pools;
#[path = "3_map_pool_events.rs"]
mod map_pool_events;

View File

@@ -0,0 +1,17 @@
// @generated
pub mod tycho {
pub mod evm {
pub mod uniswap {
// @@protoc_insertion_point(attribute:tycho.evm.uniswap.v2)
pub mod v2 {
include!("tycho.evm.uniswap.v2.rs");
// @@protoc_insertion_point(tycho.evm.uniswap.v2)
}
}
// @@protoc_insertion_point(attribute:tycho.evm.v1)
pub mod v1 {
include!("tycho.evm.v1.rs");
// @@protoc_insertion_point(tycho.evm.v1)
}
}
}

View File

@@ -0,0 +1,99 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pools {
#[prost(message, repeated, tag="1")]
pub pools: ::prost::alloc::vec::Vec<Pool>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pool {
#[prost(bytes="vec", tag="1")]
pub address: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="2")]
pub token0: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="3")]
pub token1: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="4")]
pub created_tx_hash: ::prost::alloc::vec::Vec<u8>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Events {
#[prost(message, repeated, tag="1")]
pub events: ::prost::alloc::vec::Vec<Event>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Event {
#[prost(string, tag="100")]
pub hash: ::prost::alloc::string::String,
#[prost(uint32, tag="101")]
pub log_index: u32,
#[prost(uint64, tag="102")]
pub log_ordinal: u64,
#[prost(string, tag="103")]
pub to: ::prost::alloc::string::String,
#[prost(string, tag="104")]
pub from: ::prost::alloc::string::String,
#[prost(uint64, tag="105")]
pub block_number: u64,
#[prost(uint64, tag="106")]
pub timestamp: u64,
#[prost(string, tag="107")]
pub pool: ::prost::alloc::string::String,
#[prost(oneof="event::Type", tags="10, 20, 30, 40")]
pub r#type: ::core::option::Option<event::Type>,
}
/// Nested message and enum types in `Event`.
pub mod event {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Type {
#[prost(message, tag="10")]
DepositType(super::DepositEvent),
#[prost(message, tag="20")]
WithdrawType(super::WithdrawEvent),
#[prost(message, tag="30")]
SyncType(super::SyncEvent),
#[prost(message, tag="40")]
SwapType(super::SwapEvent),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DepositEvent {
#[prost(string, repeated, tag="1")]
pub input_token_amounts: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(string, optional, tag="2")]
pub output_token_amount: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WithdrawEvent {
#[prost(string, repeated, tag="1")]
pub input_token_amounts: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
#[prost(string, optional, tag="2")]
pub output_token_amount: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SyncEvent {
#[prost(string, tag="1")]
pub reserve0: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub reserve1: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SwapEvent {
#[prost(string, tag="1")]
pub token_in: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub amount_in: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub token_out: ::prost::alloc::string::String,
#[prost(string, tag="4")]
pub amount_out: ::prost::alloc::string::String,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,307 @@
// @generated
// This file contains the proto definitions for Substreams common to all integrations.
/// A struct describing a block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Block {
/// The blocks hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The parent blocks hash.
#[prost(bytes="vec", tag="2")]
pub parent_hash: ::prost::alloc::vec::Vec<u8>,
/// The block number.
#[prost(uint64, tag="3")]
pub number: u64,
/// The block timestamp.
#[prost(uint64, tag="4")]
pub ts: u64,
}
/// A struct describing a transaction.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transaction {
/// The transaction hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The sender of the transaction.
#[prost(bytes="vec", tag="2")]
pub from: ::prost::alloc::vec::Vec<u8>,
/// The receiver of the transaction.
#[prost(bytes="vec", tag="3")]
pub to: ::prost::alloc::vec::Vec<u8>,
/// The transactions index within the block.
#[prost(uint64, tag="4")]
pub index: u64,
}
/// A custom struct representing an arbitrary attribute of a protocol component.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Attribute {
/// The name of the attribute.
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
/// The value of the attribute.
#[prost(bytes="vec", tag="2")]
pub value: ::prost::alloc::vec::Vec<u8>,
/// The type of change the attribute underwent.
#[prost(enumeration="ChangeType", tag="3")]
pub change: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtocolType {
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(enumeration="FinancialType", tag="2")]
pub financial_type: i32,
#[prost(message, repeated, tag="3")]
pub attribute_schema: ::prost::alloc::vec::Vec<Attribute>,
#[prost(enumeration="ImplementationType", tag="4")]
pub implementation_type: i32,
}
/// A struct describing a part of the protocol.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtocolComponent {
/// A unique identifier for the component within the protocol.
/// Can be a stringified address or a string describing the trading pair.
#[prost(string, tag="1")]
pub id: ::prost::alloc::string::String,
/// Addresses of the ERC20 tokens used by the component.
#[prost(bytes="vec", repeated, tag="2")]
pub tokens: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Addresses of the contracts used by the component.
#[prost(bytes="vec", repeated, tag="3")]
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Attributes of the component.
/// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
#[prost(message, repeated, tag="4")]
pub static_att: ::prost::alloc::vec::Vec<Attribute>,
/// Type of change the component underwent.
#[prost(enumeration="ChangeType", tag="5")]
pub change: i32,
/// / Represents the functionality of the component.
#[prost(message, optional, tag="6")]
pub protocol_type: ::core::option::Option<ProtocolType>,
/// Transaction where this component was created
#[prost(message, optional, tag="7")]
pub tx: ::core::option::Option<Transaction>,
}
/// A struct for following the changes of Total Value Locked (TVL) of a protocol component.
/// Note that if the ProtocolComponent contains multiple contracts, the TVL is tracked for the component as a whole.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceChange {
/// The address of the ERC20 token whose balance changed.
#[prost(bytes="vec", tag="1")]
pub token: ::prost::alloc::vec::Vec<u8>,
/// The new balance of the token.
#[prost(bytes="vec", tag="2")]
pub balance: ::prost::alloc::vec::Vec<u8>,
/// The id of the component whose TVL is tracked. Note: This MUST be utf8 encoded.
#[prost(bytes="vec", tag="3")]
pub component_id: ::prost::alloc::vec::Vec<u8>,
}
// Native entities
/// A component is a set of attributes that are associated with a custom entity.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EntityChanges {
/// A unique identifier of the entity within the protocol.
#[prost(string, tag="1")]
pub component_id: ::prost::alloc::string::String,
/// The set of attributes that are associated with the entity.
#[prost(message, repeated, tag="2")]
pub attributes: ::prost::alloc::vec::Vec<Attribute>,
}
// VM entities
/// A key value entry into contract storage.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContractSlot {
/// A contract's storage slot.
#[prost(bytes="vec", tag="2")]
pub slot: ::prost::alloc::vec::Vec<u8>,
/// The new value for this storage slot.
#[prost(bytes="vec", tag="3")]
pub value: ::prost::alloc::vec::Vec<u8>,
}
/// Changes made to a single contract's state.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContractChange {
/// The contract's address
#[prost(bytes="vec", tag="1")]
pub address: ::prost::alloc::vec::Vec<u8>,
/// The new balance of the contract, empty bytes indicates no change.
#[prost(bytes="vec", tag="2")]
pub balance: ::prost::alloc::vec::Vec<u8>,
/// The new code of the contract, empty bytes indicates no change.
#[prost(bytes="vec", tag="3")]
pub code: ::prost::alloc::vec::Vec<u8>,
/// The changes to this contract's slots, empty sequence indicates no change.
#[prost(message, repeated, tag="4")]
pub slots: ::prost::alloc::vec::Vec<ContractSlot>,
/// Whether this is an update, a creation or a deletion.
#[prost(enumeration="ChangeType", tag="5")]
pub change: i32,
}
// Aggregate entities
/// A set of changes aggregated by transaction.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TransactionChanges {
/// The transaction instance that results in the changes.
#[prost(message, optional, tag="1")]
pub tx: ::core::option::Option<Transaction>,
/// Contains the changes induced by the above transaction, aggregated on a per-contract basis.
/// Contains the contract changes induced by the above transaction, usually for tracking VM components.
#[prost(message, repeated, tag="2")]
pub contract_changes: ::prost::alloc::vec::Vec<ContractChange>,
/// Contains the entity changes induced by the above transaction.
/// Usually for tracking native components or used for VM extensions (plugins).
#[prost(message, repeated, tag="3")]
pub entity_changes: ::prost::alloc::vec::Vec<EntityChanges>,
/// An array of newly added components.
#[prost(message, repeated, tag="4")]
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
/// An array of balance changes to components.
#[prost(message, repeated, tag="5")]
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
}
/// A set of transaction changes within a single block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockChanges {
/// The block for which these changes are collectively computed.
#[prost(message, optional, tag="1")]
pub block: ::core::option::Option<Block>,
/// The set of transaction changes observed in the specified block.
#[prost(message, repeated, tag="2")]
pub changes: ::prost::alloc::vec::Vec<TransactionChanges>,
}
/// Enum to specify the type of a change.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ChangeType {
Unspecified = 0,
Update = 1,
Creation = 2,
Deletion = 3,
}
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 {
ChangeType::Unspecified => "CHANGE_TYPE_UNSPECIFIED",
ChangeType::Update => "CHANGE_TYPE_UPDATE",
ChangeType::Creation => "CHANGE_TYPE_CREATION",
ChangeType::Deletion => "CHANGE_TYPE_DELETION",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"CHANGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified),
"CHANGE_TYPE_UPDATE" => Some(Self::Update),
"CHANGE_TYPE_CREATION" => Some(Self::Creation),
"CHANGE_TYPE_DELETION" => Some(Self::Deletion),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum FinancialType {
Swap = 0,
Lend = 1,
Leverage = 2,
Psm = 3,
}
impl FinancialType {
/// 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 {
FinancialType::Swap => "SWAP",
FinancialType::Lend => "LEND",
FinancialType::Leverage => "LEVERAGE",
FinancialType::Psm => "PSM",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"SWAP" => Some(Self::Swap),
"LEND" => Some(Self::Lend),
"LEVERAGE" => Some(Self::Leverage),
"PSM" => Some(Self::Psm),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ImplementationType {
Vm = 0,
Custom = 1,
}
impl ImplementationType {
/// 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 {
ImplementationType::Vm => "VM",
ImplementationType::Custom => "CUSTOM",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"VM" => Some(Self::Vm),
"CUSTOM" => Some(Self::Custom),
_ => None,
}
}
}
// WARNING: DEPRECATED. Please use common.proto's TransactionChanges and BlockChanges instead.
// This file contains the definition for the native integration of Substreams.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TransactionEntityChanges {
#[prost(message, optional, tag="1")]
pub tx: ::core::option::Option<Transaction>,
#[prost(message, repeated, tag="2")]
pub entity_changes: ::prost::alloc::vec::Vec<EntityChanges>,
/// An array of newly added components.
#[prost(message, repeated, tag="3")]
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
/// An array of balance changes to components.
#[prost(message, repeated, tag="4")]
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
}
/// A set of transaction changes within a single block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockEntityChanges {
/// The block for which these changes are collectively computed.
#[prost(message, optional, tag="1")]
pub block: ::core::option::Option<Block>,
/// The set of transaction changes observed in the specified block.
#[prost(message, repeated, tag="2")]
pub changes: ::prost::alloc::vec::Vec<TransactionEntityChanges>,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,16 @@
#[derive(Clone)]
pub enum StoreKey {
Pool,
}
impl StoreKey {
pub fn get_unique_pool_key(&self, key: &str) -> String {
format!("{}:{}", self.unique_id(), key)
}
pub fn unique_id(&self) -> String {
match self {
StoreKey::Pool => "Pool".to_string(),
}
}
}

View File

@@ -0,0 +1,51 @@
use ethabi::ethereum_types::Address;
use substreams::store::{StoreGet, StoreGetProto};
use substreams_ethereum::pb::eth::v2::{self as eth};
use substreams_helper::{common::HasAddresser, hex::Hexable};
use crate::{
pb::tycho::evm::v1::{Block, ProtocolComponent, Transaction},
store_key::StoreKey,
};
pub struct PoolAddresser<'a> {
pub store: &'a StoreGetProto<ProtocolComponent>,
}
impl<'a> HasAddresser for PoolAddresser<'a> {
fn has_address(&self, key: Address) -> bool {
let pool = self
.store
.get_last(StoreKey::Pool.get_unique_pool_key(&key.to_hex()));
pool.is_some()
}
}
impl From<eth::Block> for Block {
fn from(block: eth::Block) -> Self {
Self {
hash: block.hash.clone(),
parent_hash: block
.header
.as_ref()
.expect("Block header not present")
.parent_hash
.clone(),
number: block.number,
ts: block.timestamp_seconds(),
}
}
}
impl From<&eth::TransactionTrace> for Transaction {
fn from(tx: &eth::TransactionTrace) -> Self {
Self {
hash: tx.hash.clone(),
from: tx.from.clone(),
to: tx.to.clone(),
index: tx.index.into(),
}
}
}

View File

@@ -0,0 +1,28 @@
[package]
name = "substreams-ethereum-uniswap-v3"
version = "0.2.0"
edition = "2021"
[lib]
name = "substreams_ethereum_uniswap_v3"
crate-type = ["cdylib"]
[dependencies]
substreams.workspace = true
substreams-ethereum.workspace = true
prost.workspace = true
ethabi.workspace = true
anyhow = { workspace = true, features = [] }
hex-literal.workspace = true
substreams-helper.workspace = true
num-bigint = "0.4.4"
hex.workspace = true
tiny-keccak = "2.0"
substreams-entity-change = "1.3"
[target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2", features = ["custom"] }
[build-dependencies]
anyhow.workspace = true
substreams-ethereum.workspace = true

View File

@@ -0,0 +1,2 @@
build:
cargo build --target wasm32-unknown-unknown --profile substreams

View File

@@ -0,0 +1,198 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "FeeAmountEnabled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnerChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token1",
"type": "address"
},
{
"indexed": true,
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"indexed": false,
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
},
{
"indexed": false,
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"name": "PoolCreated",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
}
],
"name": "createPool",
"outputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
},
{
"internalType": "int24",
"name": "tickSpacing",
"type": "int24"
}
],
"name": "enableFeeAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
}
],
"name": "feeAmountTickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenA",
"type": "address"
},
{
"internalType": "address",
"name": "tokenB",
"type": "address"
},
{
"internalType": "uint24",
"name": "fee",
"type": "uint24"
}
],
"name": "getPool",
"outputs": [
{
"internalType": "address",
"name": "pool",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -0,0 +1,988 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "Collect",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"name": "CollectProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paid1",
"type": "uint256"
}
],
"name": "Flash",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextOld",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "observationCardinalityNextNew",
"type": "uint16"
}
],
"name": "IncreaseObservationCardinalityNext",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Initialize",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"indexed": true,
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"indexed": false,
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1Old",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol0New",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint8",
"name": "feeProtocol1New",
"type": "uint8"
}
],
"name": "SetFeeProtocol",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"indexed": false,
"internalType": "int256",
"name": "amount1",
"type": "int256"
},
{
"indexed": false,
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"indexed": false,
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "Swap",
"type": "event"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
}
],
"name": "burn",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collect",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint128",
"name": "amount0Requested",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1Requested",
"type": "uint128"
}
],
"name": "collectProtocol",
"outputs": [
{
"internalType": "uint128",
"name": "amount0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount1",
"type": "uint128"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fee",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal0X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeGrowthGlobal1X128",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "flash",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
}
],
"name": "increaseObservationCardinalityNext",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "liquidity",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxLiquidityPerTick",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "observations",
"outputs": [
{
"internalType": "uint32",
"name": "blockTimestamp",
"type": "uint32"
},
{
"internalType": "int56",
"name": "tickCumulative",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityCumulativeX128",
"type": "uint160"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint32[]",
"name": "secondsAgos",
"type": "uint32[]"
}
],
"name": "observe",
"outputs": [
{
"internalType": "int56[]",
"name": "tickCumulatives",
"type": "int56[]"
},
{
"internalType": "uint160[]",
"name": "secondsPerLiquidityCumulativeX128s",
"type": "uint160[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "key",
"type": "bytes32"
}
],
"name": "positions",
"outputs": [
{
"internalType": "uint128",
"name": "_liquidity",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "feeGrowthInside0LastX128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthInside1LastX128",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "tokensOwed0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "tokensOwed1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFees",
"outputs": [
{
"internalType": "uint128",
"name": "token0",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "token1",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "feeProtocol0",
"type": "uint8"
},
{
"internalType": "uint8",
"name": "feeProtocol1",
"type": "uint8"
}
],
"name": "setFeeProtocol",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "slot0",
"outputs": [
{
"internalType": "uint160",
"name": "sqrtPriceX96",
"type": "uint160"
},
{
"internalType": "int24",
"name": "tick",
"type": "int24"
},
{
"internalType": "uint16",
"name": "observationIndex",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinality",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "observationCardinalityNext",
"type": "uint16"
},
{
"internalType": "uint8",
"name": "feeProtocol",
"type": "uint8"
},
{
"internalType": "bool",
"name": "unlocked",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tickLower",
"type": "int24"
},
{
"internalType": "int24",
"name": "tickUpper",
"type": "int24"
}
],
"name": "snapshotCumulativesInside",
"outputs": [
{
"internalType": "int56",
"name": "tickCumulativeInside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityInsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsInside",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "bool",
"name": "zeroForOne",
"type": "bool"
},
{
"internalType": "int256",
"name": "amountSpecified",
"type": "int256"
},
{
"internalType": "uint160",
"name": "sqrtPriceLimitX96",
"type": "uint160"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swap",
"outputs": [
{
"internalType": "int256",
"name": "amount0",
"type": "int256"
},
{
"internalType": "int256",
"name": "amount1",
"type": "int256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int16",
"name": "wordPosition",
"type": "int16"
}
],
"name": "tickBitmap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tickSpacing",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int24",
"name": "tick",
"type": "int24"
}
],
"name": "ticks",
"outputs": [
{
"internalType": "uint128",
"name": "liquidityGross",
"type": "uint128"
},
{
"internalType": "int128",
"name": "liquidityNet",
"type": "int128"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside0X128",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feeGrowthOutside1X128",
"type": "uint256"
},
{
"internalType": "int56",
"name": "tickCumulativeOutside",
"type": "int56"
},
{
"internalType": "uint160",
"name": "secondsPerLiquidityOutsideX128",
"type": "uint160"
},
{
"internalType": "uint32",
"name": "secondsOutside",
"type": "uint32"
},
{
"internalType": "bool",
"name": "initialized",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token0",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "token1",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@@ -0,0 +1,67 @@
specVersion: v0.1.0
package:
name: "substreams_arbitrum_uniswap_v3"
version: v0.2.0
protobuf:
files:
- tycho/evm/v1/common.proto
- tycho/evm/v1/entity.proto
- uniswap.proto
importPaths:
- ./proto/v1
- ../../proto/
binaries:
default:
type: wasm/rust-v1
file: ../../target/wasm32-unknown-unknown/substreams/substreams_ethereum_uniswap_v3.wasm
modules:
- name: map_pools_created
kind: map
initialBlock: 37418321
inputs:
- params: string
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.BlockChanges
- name: store_pools
kind: store
initialBlock: 37418321
updatePolicy: set_if_not_exists
valueType: proto:uniswap.v3.Pool
inputs:
- map: map_pools_created
- name: map_balance_changes
kind: map
initialBlock: 37418321
inputs:
- source: sf.ethereum.type.v2.Block
- store: store_pools
output:
type: proto:tycho.evm.uniswap.v3.BalanceDeltas
- name: store_pools_balances
kind: store
initialBlock: 37418321
updatePolicy: add
valueType: bigint
inputs:
- map: map_balance_changes
- name: map_pool_events
kind: map
initialBlock: 37418321
inputs:
- source: sf.ethereum.type.v2.Block
- map: map_pools_created
- store: store_pools
- store: store_pools_balances
output:
type: proto:tycho.evm.v1.BlockChanges
params:
map_pools_created: "1F98431c8aD98523631AE4a59f267346ea31F984"

View File

@@ -0,0 +1,12 @@
use anyhow::{Ok, Result};
use substreams_ethereum::Abigen;
fn main() -> Result<(), anyhow::Error> {
Abigen::new("Factory", "abi/Factory.json")?
.generate()?
.write_to_file("src/abi/factory.rs")?;
Abigen::new("Pool", "abi/Pool.json")?
.generate()?
.write_to_file("src/abi/pool.rs")?;
Ok(())
}

View File

@@ -0,0 +1,67 @@
specVersion: v0.1.0
package:
name: "substreams_ethereum_uniswap_v3"
version: v0.2.0
protobuf:
files:
- tycho/evm/v1/common.proto
- tycho/evm/v1/entity.proto
- uniswap.proto
importPaths:
- ./proto/v1
- ../../proto/
binaries:
default:
type: wasm/rust-v1
file: ../../target/wasm32-unknown-unknown/substreams/substreams_ethereum_uniswap_v3.wasm
modules:
- name: map_pools_created
kind: map
initialBlock: 12369621
inputs:
- params: string
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.BlockChanges
- name: store_pools
kind: store
initialBlock: 12369621
updatePolicy: set_if_not_exists
valueType: proto:uniswap.v3.Pool
inputs:
- map: map_pools_created
- name: map_balance_changes
kind: map
initialBlock: 12369621
inputs:
- source: sf.ethereum.type.v2.Block
- store: store_pools
output:
type: proto:tycho.evm.uniswap.v3.BalanceDeltas
- name: store_pools_balances
kind: store
initialBlock: 12369621
updatePolicy: add
valueType: bigint
inputs:
- map: map_balance_changes
- name: map_pool_events
kind: map
initialBlock: 12369621
inputs:
- source: sf.ethereum.type.v2.Block
- map: map_pools_created
- store: store_pools
- store: store_pools_balances
output:
type: proto:tycho.evm.v1.BlockChanges
params:
map_pools_created: "1F98431c8aD98523631AE4a59f267346ea31F984"

View File

@@ -0,0 +1,29 @@
syntax = "proto3";
package uniswap.v3;
message Pool {
bytes address = 1;
bytes token0 = 2;
bytes token1 = 3;
bytes created_tx_hash = 4;
}
// A change to a pool's balance.
message BalanceDelta {
// The address of the ERC20 token.
bytes token_address = 1;
// The delta of the token.
bytes amount = 2;
// The sign of the delta, true for positive, false for negative.
bool sign = 3;
// The address of the pool whose balance changed.
bytes pool_address = 4;
// Used to determine the order of the balance changes. Necessary for the balance store.
uint64 ordinal = 5;
}
// A group of BalanceDelta
message BalanceDeltas {
repeated BalanceDelta deltas = 1;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
pub mod factory;
pub mod pool;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
use substreams_helper::storage_change::StorageChangesFilter;
use crate::{
abi::pool::events::Burn,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use super::{BalanceDelta, EventTrait};
impl EventTrait for Burn {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
let mut changed_attributes =
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect());
let changed_ticks =
pool_storage.get_ticks_changes(vec![&self.tick_upper, &self.tick_lower]);
changed_attributes.extend(changed_ticks);
changed_attributes
}
fn get_balance_delta(&self, _pool: &Pool, _ordinal: u64) -> Vec<BalanceDelta> {
// Burn event balances deltas are accounted for by the Collect event.
// In the case of a burn, the Collect event amounts will include both the burned amount and
// the fees earned.
vec![]
}
}

View File

@@ -0,0 +1,58 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
use crate::{
abi::pool::events::Collect,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use substreams_helper::storage_change::StorageChangesFilter;
use super::{BalanceDelta, EventTrait};
impl EventTrait for Collect {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
let mut changed_attributes =
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect());
let changed_ticks =
pool_storage.get_ticks_changes(vec![&self.tick_upper, &self.tick_lower]);
changed_attributes.extend(changed_ticks);
changed_attributes
}
fn get_balance_delta(&self, pool: &Pool, ordinal: u64) -> Vec<BalanceDelta> {
let changed_balance = vec![
BalanceDelta {
token_address: pool.token0.clone(),
amount: self.amount0.clone().to_bytes_le().1,
sign: false,
pool_address: pool.address.clone(),
ordinal,
},
BalanceDelta {
token_address: pool.token1.clone(),
amount: self.amount1.clone().to_bytes_le().1,
sign: false,
pool_address: pool.address.clone(),
ordinal,
},
];
changed_balance
}
}

View File

@@ -0,0 +1,50 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
use substreams_helper::storage_change::StorageChangesFilter;
use crate::{
abi::pool::events::CollectProtocol,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use super::{BalanceDelta, EventTrait};
impl EventTrait for CollectProtocol {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect())
}
fn get_balance_delta(&self, pool: &Pool, ordinal: u64) -> Vec<BalanceDelta> {
let changed_balance = vec![
BalanceDelta {
token_address: pool.token0.clone(),
amount: self.amount0.clone().to_bytes_le().1,
sign: false,
pool_address: pool.address.clone(),
ordinal,
},
BalanceDelta {
token_address: pool.token1.clone(),
amount: self.amount1.clone().to_bytes_le().1,
sign: false,
pool_address: pool.address.clone(),
ordinal,
},
];
changed_balance
}
}

View File

@@ -0,0 +1,50 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
use crate::{
abi::pool::events::Flash,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use substreams_helper::storage_change::StorageChangesFilter;
use super::{BalanceDelta, EventTrait};
impl EventTrait for Flash {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect())
}
fn get_balance_delta(&self, pool: &Pool, ordinal: u64) -> Vec<BalanceDelta> {
let changed_balance = vec![
BalanceDelta {
token_address: pool.token0.clone(),
amount: self.paid0.clone().to_bytes_le().1,
sign: true,
pool_address: pool.address.clone(),
ordinal,
},
BalanceDelta {
token_address: pool.token1.clone(),
amount: self.paid1.clone().to_bytes_le().1,
sign: true,
pool_address: pool.address.clone(),
ordinal,
},
];
changed_balance
}
}

View File

@@ -0,0 +1,34 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
use crate::{
abi::pool::events::Initialize,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use substreams_helper::storage_change::StorageChangesFilter;
use super::{BalanceDelta, EventTrait};
impl EventTrait for Initialize {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect())
}
fn get_balance_delta(&self, _pool: &Pool, _ordinal: u64) -> Vec<BalanceDelta> {
vec![]
}
}

View File

@@ -0,0 +1,58 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
use crate::{
abi::pool::events::Mint,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use substreams_helper::storage_change::StorageChangesFilter;
use super::{BalanceDelta, EventTrait};
impl EventTrait for Mint {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
let mut changed_attributes =
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect());
let changed_ticks =
pool_storage.get_ticks_changes(vec![&self.tick_upper, &self.tick_lower]);
changed_attributes.extend(changed_ticks);
changed_attributes
}
fn get_balance_delta(&self, pool: &Pool, ordinal: u64) -> Vec<BalanceDelta> {
let changed_balance = vec![
BalanceDelta {
token_address: pool.token0.clone(),
amount: self.amount0.clone().to_bytes_le().1,
sign: true,
pool_address: pool.address.clone(),
ordinal,
},
BalanceDelta {
token_address: pool.token1.clone(),
amount: self.amount1.clone().to_bytes_le().1,
sign: true,
pool_address: pool.address.clone(),
ordinal,
},
];
changed_balance
}
}

View File

@@ -0,0 +1,150 @@
use substreams_ethereum::{
pb::eth::v2::{Log, StorageChange},
Event,
};
use crate::{
abi::pool::events::{
Burn, Collect, CollectProtocol, Flash, Initialize, Mint, SetFeeProtocol, Swap,
},
pb::{
tycho::evm::v1::Attribute,
uniswap::v3::{BalanceDelta, Pool},
},
};
pub mod burn;
pub mod collect;
pub mod collect_fee_protocol;
pub mod flash;
pub mod initialize;
pub mod mint;
pub mod set_fee_protocol;
pub mod swap;
/// A trait for extracting changed attributes and balance from an event.
pub trait EventTrait {
/// Get all relevant changed attributes from the `[StorageChange]`.
/// If an attribute is changed multiple times, only the last state will be returned.
///
/// # Arguments
///
/// * `storage_changes` - A slice of `StorageChange` that indicates the changes in storage.
/// * `pool` - Reference to the `Pool`.
///
/// # Returns
///
/// A vector of `Attribute` that represents the changed attributes.
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute>;
/// Get all balance deltas from the event.
///
/// # Arguments
///
/// * `pool` - Reference to the `Pool`.
/// * `ordinal` - The ordinal number of the event. This is used by the balance store to sort the
/// balance deltas in the correct order.
///
/// # Returns
///
/// A vector of `BalanceDelta` that represents the balance deltas.
fn get_balance_delta(&self, pool: &Pool, ordinal: u64) -> Vec<BalanceDelta>;
}
/// Represent every events of a UniswapV3 pool.
pub enum EventType {
Initialize(Initialize),
Swap(Swap),
Flash(Flash),
Mint(Mint),
Burn(Burn),
Collect(Collect),
SetFeeProtocol(SetFeeProtocol),
CollectProtocol(CollectProtocol),
}
impl EventType {
fn as_event_trait(&self) -> &dyn EventTrait {
match self {
EventType::Initialize(e) => e,
EventType::Swap(e) => e,
EventType::Flash(e) => e,
EventType::Mint(e) => e,
EventType::Burn(e) => e,
EventType::Collect(e) => e,
EventType::SetFeeProtocol(e) => e,
EventType::CollectProtocol(e) => e,
}
}
}
/// Decodes a given log into an `EventType`.
///
/// # Arguments
///
/// * `event` - A reference to the `Log`.
///
/// # Returns
///
/// An `Option<EventType>` that represents the decoded event type.
pub fn decode_event(event: &Log) -> Option<EventType> {
[
Initialize::match_and_decode(event).map(EventType::Initialize),
Swap::match_and_decode(event).map(EventType::Swap),
Flash::match_and_decode(event).map(EventType::Flash),
Mint::match_and_decode(event).map(EventType::Mint),
Burn::match_and_decode(event).map(EventType::Burn),
Collect::match_and_decode(event).map(EventType::Collect),
SetFeeProtocol::match_and_decode(event).map(EventType::SetFeeProtocol),
CollectProtocol::match_and_decode(event).map(EventType::CollectProtocol),
]
.into_iter()
.find_map(std::convert::identity)
}
/// Gets the changed attributes from the log.
///
/// # Arguments
///
/// * `event` - A reference to the `Log`.
/// * `storage_changes` - A slice of `StorageChange` that indicates the changes in storage.
/// * `pool` - Reference to the `Pool` structure.
///
/// # Returns
///
/// A vector of `Attribute` that represents the changed attributes.
pub fn get_log_changed_attributes(
event: &Log,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
decode_event(event)
.map(|e| {
e.as_event_trait()
.get_changed_attributes(storage_changes, pool_address)
})
.unwrap_or_default()
}
/// Gets the changed balances from the log.
///
/// # Arguments
///
/// * `event` - A reference to the `Log`.
/// * `pool` - Reference to the `Pool` structure.
///
/// # Returns
///
/// A vector of `BalanceDelta` that represents
pub fn get_log_changed_balances(event: &Log, pool: &Pool) -> Vec<BalanceDelta> {
decode_event(event)
.map(|e| {
e.as_event_trait()
.get_balance_delta(pool, event.ordinal)
})
.unwrap_or_default()
}

View File

@@ -0,0 +1,34 @@
use substreams_ethereum::pb::eth::v2::StorageChange;
use crate::{
abi::pool::events::SetFeeProtocol,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use substreams_helper::storage_change::StorageChangesFilter;
use super::{BalanceDelta, EventTrait};
impl EventTrait for SetFeeProtocol {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect())
}
fn get_balance_delta(&self, _pool: &Pool, _ordinal: u64) -> Vec<BalanceDelta> {
vec![]
}
}

View File

@@ -0,0 +1,50 @@
use num_bigint::Sign;
use substreams::scalar::BigInt;
use substreams_ethereum::pb::eth::v2::StorageChange;
use substreams_helper::storage_change::StorageChangesFilter;
use crate::{
abi::pool::events::Swap,
pb::{tycho::evm::v1::Attribute, uniswap::v3::Pool},
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
};
use super::{BalanceDelta, EventTrait};
impl EventTrait for Swap {
fn get_changed_attributes(
&self,
storage_changes: &[StorageChange],
pool_address: &[u8; 20],
) -> Vec<Attribute> {
let storage_vec = storage_changes.to_vec();
let filtered_storage_changes = storage_vec
.filter_by_address(pool_address)
.into_iter()
.cloned()
.collect();
let pool_storage = UniswapPoolStorage::new(&filtered_storage_changes);
pool_storage.get_changed_attributes(TRACKED_SLOTS.to_vec().iter().collect())
}
fn get_balance_delta(&self, pool: &Pool, ordinal: u64) -> Vec<BalanceDelta> {
let create_balance_delta = |token_address: Vec<u8>, amount: BigInt| -> BalanceDelta {
let (amount_sign, amount_bytes) = amount.clone().to_bytes_le();
BalanceDelta {
token_address,
amount: amount_bytes,
sign: amount_sign == Sign::Plus,
pool_address: pool.address.clone(),
ordinal,
}
};
vec![
create_balance_delta(pool.token0.clone(), self.amount0.clone()),
create_balance_delta(pool.token1.clone(), self.amount1.clone()),
]
}
}

View File

@@ -0,0 +1,11 @@
#![allow(clippy::not_unsafe_ptr_arg_deref)]
mod abi;
mod modules;
mod pb;
mod storage;
pub use modules::*;
mod events;
mod traits;

View File

@@ -0,0 +1,103 @@
use std::str::FromStr;
use ethabi::ethereum_types::Address;
use substreams::scalar::BigInt;
use substreams_ethereum::pb::eth::v2::{self as eth};
use substreams_helper::{event_handler::EventHandler, hex::Hexable};
use crate::{
abi::factory::events::PoolCreated,
pb::tycho::evm::v1::{
Attribute, BlockChanges, ChangeType, EntityChanges, FinancialType, ImplementationType,
ProtocolComponent, ProtocolType, Transaction, TransactionChanges,
},
};
#[substreams::handlers::map]
pub fn map_pools_created(
params: String,
block: eth::Block,
) -> Result<BlockChanges, substreams::errors::Error> {
let mut new_pools: Vec<TransactionChanges> = vec![];
let factory_address = params.as_str();
get_new_pools(&block, &mut new_pools, factory_address);
Ok(BlockChanges { block: Some(block.into()), changes: new_pools })
}
// Extract new pools from PoolCreated events
fn get_new_pools(
block: &eth::Block,
new_pools: &mut Vec<TransactionChanges>,
factory_address: &str,
) {
// Extract new pools from PoolCreated events
let mut on_pool_created = |event: PoolCreated, _tx: &eth::TransactionTrace, _log: &eth::Log| {
let tycho_tx: Transaction = _tx.into();
new_pools.push(TransactionChanges {
tx: Some(tycho_tx.clone()),
contract_changes: vec![],
entity_changes: vec![EntityChanges {
component_id: event.pool.to_hex(),
attributes: vec![
Attribute {
name: "liquidity".to_string(),
value: BigInt::from(0).to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
Attribute {
name: "tick".to_string(),
value: BigInt::from(0).to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
Attribute {
name: "sqrt_price_x96".to_string(),
value: BigInt::from(0).to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
],
}],
component_changes: vec![ProtocolComponent {
id: event.pool.to_hex(),
tokens: vec![event.token0, event.token1],
contracts: vec![],
static_att: vec![
Attribute {
name: "fee".to_string(),
value: event.fee.to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
Attribute {
name: "tick_spacing".to_string(),
value: event.tick_spacing.to_signed_bytes_le(),
change: ChangeType::Creation.into(),
},
Attribute {
name: "pool_address".to_string(),
value: event.pool,
change: ChangeType::Creation.into(),
},
],
change: i32::from(ChangeType::Creation),
protocol_type: Option::from(ProtocolType {
name: "uniswap_v3_pool".to_string(),
financial_type: FinancialType::Swap.into(),
attribute_schema: vec![],
implementation_type: ImplementationType::Custom.into(),
}),
tx: Some(tycho_tx),
}],
balance_changes: vec![],
})
};
let mut eh = EventHandler::new(block);
eh.filter_by_address(vec![Address::from_str(factory_address).unwrap()]);
eh.on::<PoolCreated, _>(&mut on_pool_created);
eh.handle_events();
}

View File

@@ -0,0 +1,23 @@
use std::str;
use substreams::store::{StoreNew, StoreSetIfNotExists, StoreSetIfNotExistsProto};
use crate::pb::{tycho::evm::v1::BlockChanges, uniswap::v3::Pool};
#[substreams::handlers::store]
pub fn store_pools(pools_created: BlockChanges, store: StoreSetIfNotExistsProto<Pool>) {
// Store pools. Required so the next maps can match any event to a known pool by their address
for change in pools_created.changes {
for component_change in &change.component_changes {
let pool_address: &str = &component_change.id;
let pool: Pool = Pool {
address: hex::decode(pool_address.trim_start_matches("0x")).unwrap(),
token0: component_change.tokens[0].clone(),
token1: component_change.tokens[1].clone(),
created_tx_hash: change.tx.as_ref().unwrap().hash.clone(),
};
store.set_if_not_exists(0, format!("{}:{}", "Pool", pool_address), &pool);
}
}
}

View File

@@ -0,0 +1,40 @@
use anyhow::Ok;
use substreams::store::{StoreGet, StoreGetProto};
use substreams_ethereum::pb::eth::v2::{self as eth};
use substreams_helper::hex::Hexable;
use crate::{
events::get_log_changed_balances,
pb::uniswap::v3::{BalanceDeltas, Pool},
};
#[substreams::handlers::map]
pub fn map_balance_changes(
block: eth::Block,
pools_store: StoreGetProto<Pool>,
) -> Result<BalanceDeltas, anyhow::Error> {
let mut balances_deltas = Vec::new();
for trx in block.transactions() {
let mut tx_deltas = Vec::new();
for log in trx
.calls
.iter()
.filter(|call| !call.state_reverted)
.flat_map(|call| &call.logs)
{
// Skip if the log is not from a known uniswapV3 pool.
if let Some(pool) =
pools_store.get_last(format!("{}:{}", "Pool", &log.address.to_hex()))
{
tx_deltas.extend(get_log_changed_balances(log, &pool))
} else {
continue;
}
}
if !tx_deltas.is_empty() {
balances_deltas.extend(tx_deltas);
}
}
Ok(BalanceDeltas { deltas: balances_deltas })
}

View File

@@ -0,0 +1,28 @@
use crate::pb::uniswap::v3::BalanceDeltas;
use num_bigint::Sign;
use substreams::{
scalar::BigInt,
store::{StoreAdd, StoreAddBigInt, StoreNew},
};
#[substreams::handlers::store]
pub fn store_pools_balances(balances_deltas: BalanceDeltas, store: StoreAddBigInt) {
let mut deltas = balances_deltas.deltas.clone();
deltas.sort_unstable_by_key(|delta| delta.ordinal);
deltas.iter().for_each(|delta| {
store.add(
delta.ordinal,
format!(
"pool:{0}:token:{1}",
hex::encode(&delta.pool_address),
hex::encode(&delta.token_address)
),
BigInt::from_bytes_le(
if delta.sign { Sign::Plus } else { Sign::Minus },
delta.amount.as_slice(),
),
);
});
}

View File

@@ -0,0 +1,235 @@
use std::{collections::HashMap, usize, vec};
use substreams::store::{StoreGet, StoreGetBigInt, StoreGetProto};
use substreams_ethereum::pb::eth::v2::{self as eth, TransactionTrace};
use substreams_helper::hex::Hexable;
use crate::{
events::{get_log_changed_attributes, get_log_changed_balances},
pb::{
tycho::evm::v1::{BalanceChange, Block, BlockChanges, EntityChanges, TransactionChanges},
uniswap::v3::Pool,
},
};
#[substreams::handlers::map]
pub fn map_pool_events(
block: eth::Block,
created_pools: BlockChanges,
pools_store: StoreGetProto<Pool>,
balance_store: StoreGetBigInt,
) -> Result<BlockChanges, substreams::errors::Error> {
let mut tx_changes_map: HashMap<Vec<u8>, TransactionChanges> = HashMap::new();
// Add created pools to the tx_changes_map
for change in created_pools.changes.into_iter() {
let transaction = change.tx.as_ref().unwrap();
tx_changes_map
.entry(transaction.hash.clone())
.and_modify(|c| {
c.component_changes
.extend(change.component_changes.clone());
c.entity_changes
.extend(change.entity_changes.clone());
})
.or_insert(change);
}
for trx in block.transactions() {
for (log, call_view) in trx.logs_with_calls() {
// Skip if the log is not from a known uniswapV3 pool.
if let Some(pool) =
pools_store.get_last(format!("{}:{}", "Pool", &log.address.to_hex()))
{
let changed_attributes = get_log_changed_attributes(
log,
&call_view.call.storage_changes,
pool.address
.clone()
.as_slice()
.try_into()
.expect("Pool address is not 20 bytes long"),
);
let mut balance_changes: Vec<BalanceChange> = vec![];
if !(get_log_changed_balances(log, &pool).is_empty()) {
let token_0_balance = balance_store.get_last(format!(
"pool:{0}:token:{1}",
hex::encode(&pool.address),
hex::encode(&pool.token0)
));
let token_1_balance = balance_store.get_last(format!(
"pool:{0}:token:{1}",
hex::encode(&pool.address),
hex::encode(&pool.token1)
));
let pool_address_utf8 = pool
.address
.clone()
.to_hex()
.as_bytes()
.to_vec();
let token_0_balance_change = BalanceChange {
component_id: pool_address_utf8.clone(),
token: pool.token0.clone(),
balance: token_0_balance
.clone()
.expect("Couldn't get balance from store")
.to_bytes_be()
.1,
};
let token_1_balance_change = BalanceChange {
component_id: pool_address_utf8.clone(),
token: pool.token1.clone(),
balance: token_1_balance
.clone()
.expect("Couldn't get balance from store")
.to_bytes_be()
.1,
};
balance_changes.extend([token_0_balance_change, token_1_balance_change]);
}
// Create entity changes
let entity_changes: Vec<EntityChanges> = vec![EntityChanges {
component_id: pool.address.clone().to_hex(),
attributes: changed_attributes,
}];
update_tx_changes_map(entity_changes, balance_changes, &mut tx_changes_map, trx);
} else {
continue;
}
}
}
// Make a list of all HashMap values:
let tx_entity_changes: Vec<TransactionChanges> = tx_changes_map.into_values().collect();
let tycho_block: Block = block.into();
let block_entity_changes =
BlockChanges { block: Some(tycho_block), changes: tx_entity_changes };
Ok(block_entity_changes)
}
fn update_tx_changes_map(
entity_changes: Vec<EntityChanges>,
balance_changes: Vec<BalanceChange>,
tx_changes_map: &mut HashMap<Vec<u8>, TransactionChanges>,
tx_trace: &TransactionTrace,
) {
// Get the tx hash
let tx_hash = tx_trace.hash.clone();
// Get the tx changes from the map
let tx_changes = tx_changes_map.get_mut(&tx_hash);
// Update the tx changes
if let Some(tx_changes) = tx_changes {
// Merge the entity changes
tx_changes.entity_changes =
merge_entity_changes(&tx_changes.entity_changes, &entity_changes);
// Merge the balance changes
tx_changes.balance_changes =
merge_balance_changes(&tx_changes.balance_changes, &balance_changes);
} else {
// If the tx is not in the map, add it
let tx_changes = TransactionChanges {
tx: Some(tx_trace.into()),
contract_changes: vec![],
entity_changes,
balance_changes,
component_changes: vec![],
};
tx_changes_map.insert(tx_hash, tx_changes);
}
}
/// Merges new entity changes into an existing collection of entity changes and returns the merged
/// result. For each entity change, if an entity change with the same component_id exists, its
/// attributes are merged. If an attribute with the same name exists, the new attribute replaces the
/// old one.
///
/// Parameters:
/// - `existing_changes`: A reference to a vector of existing entity changes.
/// - `new_changes`: A reference to a vector of new entity changes to be merged.
///
/// Returns:
/// A new `Vec<EntityChanges>` containing the merged entity changes.
fn merge_entity_changes(
existing_changes: &[EntityChanges],
new_changes: &Vec<EntityChanges>,
) -> Vec<EntityChanges> {
let mut changes_map = existing_changes
.iter()
.cloned()
.map(|change| (change.component_id.clone(), change))
.collect::<HashMap<_, _>>();
for change in new_changes {
match changes_map.get_mut(&change.component_id) {
Some(existing_change) => {
let mut attributes_map = existing_change
.attributes
.iter()
.cloned()
.map(|attr| (attr.name.clone(), attr))
.collect::<HashMap<_, _>>();
for attr in &change.attributes {
attributes_map.insert(attr.name.clone(), attr.clone());
}
existing_change.attributes = attributes_map.into_values().collect();
}
None => {
changes_map.insert(change.component_id.clone(), change.clone());
}
}
}
changes_map.into_values().collect()
}
#[derive(Debug, PartialEq, Eq, Hash)]
struct BalanceChangeKey {
token: Vec<u8>,
component_id: Vec<u8>,
}
/// Merges two vectors of `BalanceChange` structures into a single vector. If two `BalanceChange`
/// instances have the same combination of `token` and `component_id`, the value from the
/// `new_entries` vector will replace the one from the `current` vector.
///
/// Parameters:
/// - `current`: A reference to a vector of `BalanceChange` instances representing the current
/// balance changes.
/// - `new_entries`: A reference to a vector of `BalanceChange` instances representing new balance
/// changes to be merged.
///
/// Returns:
/// A `Vec<BalanceChange>` that contains the merged balance changes.
fn merge_balance_changes(
current: &[BalanceChange],
new_entries: &Vec<BalanceChange>,
) -> Vec<BalanceChange> {
let mut balances = HashMap::new();
for balance_change in current.iter().chain(new_entries) {
let key = BalanceChangeKey {
token: balance_change.token.clone(),
component_id: balance_change.component_id.clone(),
};
balances.insert(key, balance_change.clone());
}
balances.into_values().collect()
}

View File

@@ -0,0 +1,18 @@
pub use map_pool_created::map_pools_created;
pub use map_pool_events::map_pool_events;
pub use store_pools::store_pools;
#[path = "1_map_pool_created.rs"]
mod map_pool_created;
#[path = "2_store_pools.rs"]
mod store_pools;
#[path = "3_map_balance_changes.rs"]
mod map_balance_changes;
#[path = "4_store_pools_balances.rs"]
mod store_pools_balances;
#[path = "5_map_pool_events.rs"]
mod map_pool_events;

View File

@@ -0,0 +1,17 @@
// @generated
pub mod tycho {
pub mod evm {
// @@protoc_insertion_point(attribute:tycho.evm.v1)
pub mod v1 {
include!("tycho.evm.v1.rs");
// @@protoc_insertion_point(tycho.evm.v1)
}
}
}
pub mod uniswap {
// @@protoc_insertion_point(attribute:uniswap.v3)
pub mod v3 {
include!("uniswap.v3.rs");
// @@protoc_insertion_point(uniswap.v3)
}
}

View File

@@ -0,0 +1,307 @@
// @generated
// This file contains the proto definitions for Substreams common to all integrations.
/// A struct describing a block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Block {
/// The blocks hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The parent blocks hash.
#[prost(bytes="vec", tag="2")]
pub parent_hash: ::prost::alloc::vec::Vec<u8>,
/// The block number.
#[prost(uint64, tag="3")]
pub number: u64,
/// The block timestamp.
#[prost(uint64, tag="4")]
pub ts: u64,
}
/// A struct describing a transaction.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transaction {
/// The transaction hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The sender of the transaction.
#[prost(bytes="vec", tag="2")]
pub from: ::prost::alloc::vec::Vec<u8>,
/// The receiver of the transaction.
#[prost(bytes="vec", tag="3")]
pub to: ::prost::alloc::vec::Vec<u8>,
/// The transactions index within the block.
#[prost(uint64, tag="4")]
pub index: u64,
}
/// A custom struct representing an arbitrary attribute of a protocol component.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Attribute {
/// The name of the attribute.
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
/// The value of the attribute.
#[prost(bytes="vec", tag="2")]
pub value: ::prost::alloc::vec::Vec<u8>,
/// The type of change the attribute underwent.
#[prost(enumeration="ChangeType", tag="3")]
pub change: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtocolType {
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
#[prost(enumeration="FinancialType", tag="2")]
pub financial_type: i32,
#[prost(message, repeated, tag="3")]
pub attribute_schema: ::prost::alloc::vec::Vec<Attribute>,
#[prost(enumeration="ImplementationType", tag="4")]
pub implementation_type: i32,
}
/// A struct describing a part of the protocol.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtocolComponent {
/// A unique identifier for the component within the protocol.
/// Can be a stringified address or a string describing the trading pair.
#[prost(string, tag="1")]
pub id: ::prost::alloc::string::String,
/// Addresses of the ERC20 tokens used by the component.
#[prost(bytes="vec", repeated, tag="2")]
pub tokens: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Addresses of the contracts used by the component.
#[prost(bytes="vec", repeated, tag="3")]
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Attributes of the component.
/// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
#[prost(message, repeated, tag="4")]
pub static_att: ::prost::alloc::vec::Vec<Attribute>,
/// Type of change the component underwent.
#[prost(enumeration="ChangeType", tag="5")]
pub change: i32,
/// / Represents the functionality of the component.
#[prost(message, optional, tag="6")]
pub protocol_type: ::core::option::Option<ProtocolType>,
/// Transaction where this component was created
#[prost(message, optional, tag="7")]
pub tx: ::core::option::Option<Transaction>,
}
/// A struct for following the changes of Total Value Locked (TVL) of a protocol component.
/// Note that if the ProtocolComponent contains multiple contracts, the TVL is tracked for the component as a whole.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceChange {
/// The address of the ERC20 token whose balance changed.
#[prost(bytes="vec", tag="1")]
pub token: ::prost::alloc::vec::Vec<u8>,
/// The new balance of the token.
#[prost(bytes="vec", tag="2")]
pub balance: ::prost::alloc::vec::Vec<u8>,
/// The id of the component whose TVL is tracked. Note: This MUST be utf8 encoded.
#[prost(bytes="vec", tag="3")]
pub component_id: ::prost::alloc::vec::Vec<u8>,
}
// Native entities
/// A component is a set of attributes that are associated with a custom entity.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EntityChanges {
/// A unique identifier of the entity within the protocol.
#[prost(string, tag="1")]
pub component_id: ::prost::alloc::string::String,
/// The set of attributes that are associated with the entity.
#[prost(message, repeated, tag="2")]
pub attributes: ::prost::alloc::vec::Vec<Attribute>,
}
// VM entities
/// A key value entry into contract storage.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContractSlot {
/// A contract's storage slot.
#[prost(bytes="vec", tag="2")]
pub slot: ::prost::alloc::vec::Vec<u8>,
/// The new value for this storage slot.
#[prost(bytes="vec", tag="3")]
pub value: ::prost::alloc::vec::Vec<u8>,
}
/// Changes made to a single contract's state.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContractChange {
/// The contract's address
#[prost(bytes="vec", tag="1")]
pub address: ::prost::alloc::vec::Vec<u8>,
/// The new balance of the contract, empty bytes indicates no change.
#[prost(bytes="vec", tag="2")]
pub balance: ::prost::alloc::vec::Vec<u8>,
/// The new code of the contract, empty bytes indicates no change.
#[prost(bytes="vec", tag="3")]
pub code: ::prost::alloc::vec::Vec<u8>,
/// The changes to this contract's slots, empty sequence indicates no change.
#[prost(message, repeated, tag="4")]
pub slots: ::prost::alloc::vec::Vec<ContractSlot>,
/// Whether this is an update, a creation or a deletion.
#[prost(enumeration="ChangeType", tag="5")]
pub change: i32,
}
// Aggregate entities
/// A set of changes aggregated by transaction.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TransactionChanges {
/// The transaction instance that results in the changes.
#[prost(message, optional, tag="1")]
pub tx: ::core::option::Option<Transaction>,
/// Contains the changes induced by the above transaction, aggregated on a per-contract basis.
/// Contains the contract changes induced by the above transaction, usually for tracking VM components.
#[prost(message, repeated, tag="2")]
pub contract_changes: ::prost::alloc::vec::Vec<ContractChange>,
/// Contains the entity changes induced by the above transaction.
/// Usually for tracking native components or used for VM extensions (plugins).
#[prost(message, repeated, tag="3")]
pub entity_changes: ::prost::alloc::vec::Vec<EntityChanges>,
/// An array of newly added components.
#[prost(message, repeated, tag="4")]
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
/// An array of balance changes to components.
#[prost(message, repeated, tag="5")]
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
}
/// A set of transaction changes within a single block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockChanges {
/// The block for which these changes are collectively computed.
#[prost(message, optional, tag="1")]
pub block: ::core::option::Option<Block>,
/// The set of transaction changes observed in the specified block.
#[prost(message, repeated, tag="2")]
pub changes: ::prost::alloc::vec::Vec<TransactionChanges>,
}
/// Enum to specify the type of a change.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ChangeType {
Unspecified = 0,
Update = 1,
Creation = 2,
Deletion = 3,
}
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 {
ChangeType::Unspecified => "CHANGE_TYPE_UNSPECIFIED",
ChangeType::Update => "CHANGE_TYPE_UPDATE",
ChangeType::Creation => "CHANGE_TYPE_CREATION",
ChangeType::Deletion => "CHANGE_TYPE_DELETION",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"CHANGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified),
"CHANGE_TYPE_UPDATE" => Some(Self::Update),
"CHANGE_TYPE_CREATION" => Some(Self::Creation),
"CHANGE_TYPE_DELETION" => Some(Self::Deletion),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum FinancialType {
Swap = 0,
Lend = 1,
Leverage = 2,
Psm = 3,
}
impl FinancialType {
/// 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 {
FinancialType::Swap => "SWAP",
FinancialType::Lend => "LEND",
FinancialType::Leverage => "LEVERAGE",
FinancialType::Psm => "PSM",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"SWAP" => Some(Self::Swap),
"LEND" => Some(Self::Lend),
"LEVERAGE" => Some(Self::Leverage),
"PSM" => Some(Self::Psm),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ImplementationType {
Vm = 0,
Custom = 1,
}
impl ImplementationType {
/// 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 {
ImplementationType::Vm => "VM",
ImplementationType::Custom => "CUSTOM",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"VM" => Some(Self::Vm),
"CUSTOM" => Some(Self::Custom),
_ => None,
}
}
}
// WARNING: DEPRECATED. Please use common.proto's TransactionChanges and BlockChanges instead.
// This file contains the definition for the native integration of Substreams.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TransactionEntityChanges {
#[prost(message, optional, tag="1")]
pub tx: ::core::option::Option<Transaction>,
#[prost(message, repeated, tag="2")]
pub entity_changes: ::prost::alloc::vec::Vec<EntityChanges>,
/// An array of newly added components.
#[prost(message, repeated, tag="3")]
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
/// An array of balance changes to components.
#[prost(message, repeated, tag="4")]
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
}
/// A set of transaction changes within a single block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockEntityChanges {
/// The block for which these changes are collectively computed.
#[prost(message, optional, tag="1")]
pub block: ::core::option::Option<Block>,
/// The set of transaction changes observed in the specified block.
#[prost(message, repeated, tag="2")]
pub changes: ::prost::alloc::vec::Vec<TransactionEntityChanges>,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,541 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Erc20Tokens {
#[prost(message, repeated, tag="1")]
pub tokens: ::prost::alloc::vec::Vec<Erc20Token>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Erc20Token {
#[prost(string, tag="1")]
pub address: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub name: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub symbol: ::prost::alloc::string::String,
#[prost(uint64, tag="4")]
pub decimals: u64,
#[prost(string, tag="5")]
pub total_supply: ::prost::alloc::string::String,
#[prost(string, repeated, tag="6")]
pub whitelist_pools: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Liquidity {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="2")]
pub value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pools {
#[prost(message, repeated, tag="1")]
pub pools: ::prost::alloc::vec::Vec<Pool>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pool {
#[prost(string, tag="1")]
pub address: ::prost::alloc::string::String,
#[prost(uint64, tag="3")]
pub created_at_timestamp: u64,
#[prost(uint64, tag="4")]
pub created_at_block_number: u64,
#[prost(message, optional, tag="5")]
pub token0: ::core::option::Option<Erc20Token>,
#[prost(message, optional, tag="6")]
pub token1: ::core::option::Option<Erc20Token>,
/// Integer
#[prost(string, tag="7")]
pub fee_tier: ::prost::alloc::string::String,
/// internals
#[prost(int32, tag="30")]
pub tick_spacing: i32,
#[prost(uint64, tag="31")]
pub log_ordinal: u64,
#[prost(string, tag="32")]
pub transaction_id: ::prost::alloc::string::String,
#[prost(bool, tag="33")]
pub ignore_pool: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Events {
#[prost(message, repeated, tag="1")]
pub pool_sqrt_prices: ::prost::alloc::vec::Vec<events::PoolSqrtPrice>,
#[prost(message, repeated, tag="2")]
pub pool_liquidities: ::prost::alloc::vec::Vec<events::PoolLiquidity>,
#[prost(message, repeated, tag="7")]
pub fee_growth_global_updates: ::prost::alloc::vec::Vec<events::FeeGrowthGlobal>,
#[prost(message, repeated, tag="10")]
pub fee_growth_inside_updates: ::prost::alloc::vec::Vec<events::FeeGrowthInside>,
#[prost(message, repeated, tag="11")]
pub fee_growth_outside_updates: ::prost::alloc::vec::Vec<events::FeeGrowthOutside>,
#[prost(message, repeated, tag="3")]
pub pool_events: ::prost::alloc::vec::Vec<events::PoolEvent>,
#[prost(message, repeated, tag="4")]
pub transactions: ::prost::alloc::vec::Vec<events::Transaction>,
#[prost(message, repeated, tag="6")]
pub flashes: ::prost::alloc::vec::Vec<events::Flash>,
#[prost(message, repeated, tag="8")]
pub ticks_created: ::prost::alloc::vec::Vec<events::TickCreated>,
#[prost(message, repeated, tag="9")]
pub ticks_updated: ::prost::alloc::vec::Vec<events::TickUpdated>,
#[prost(message, repeated, tag="20")]
pub created_positions: ::prost::alloc::vec::Vec<events::CreatedPosition>,
#[prost(message, repeated, tag="21")]
pub increase_liquidity_positions: ::prost::alloc::vec::Vec<events::IncreaseLiquidityPosition>,
#[prost(message, repeated, tag="22")]
pub decrease_liquidity_positions: ::prost::alloc::vec::Vec<events::DecreaseLiquidityPosition>,
#[prost(message, repeated, tag="23")]
pub collect_positions: ::prost::alloc::vec::Vec<events::CollectPosition>,
#[prost(message, repeated, tag="24")]
pub transfer_positions: ::prost::alloc::vec::Vec<events::TransferPosition>,
}
/// Nested message and enum types in `Events`.
pub mod events {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FeeGrowthGlobal {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
#[prost(uint64, tag="2")]
pub ordinal: u64,
#[prost(int32, tag="3")]
pub token_idx: i32,
/// Integer
#[prost(string, tag="4")]
pub new_value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FeeGrowthInside {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub tick_idx: i32,
#[prost(uint64, tag="3")]
pub ordinal: u64,
/// Integer
#[prost(string, tag="4")]
pub new_value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FeeGrowthOutside {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
#[prost(int32, tag="2")]
pub tick_lower: i32,
#[prost(int32, tag="3")]
pub tick_upper: i32,
#[prost(uint64, tag="4")]
pub ordinal: u64,
/// Integer
#[prost(string, tag="5")]
pub new_value: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TickCreated {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="2")]
pub idx: ::prost::alloc::string::String,
#[prost(uint64, tag="3")]
pub log_ordinal: u64,
#[prost(uint64, tag="4")]
pub created_at_timestamp: u64,
#[prost(uint64, tag="5")]
pub created_at_block_number: u64,
/// Decimal
#[prost(string, tag="6")]
pub price0: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="7")]
pub price1: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="8")]
pub amount: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TickUpdated {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="2")]
pub idx: ::prost::alloc::string::String,
#[prost(uint64, tag="3")]
pub log_ordinal: u64,
/// Integer
#[prost(string, tag="4")]
pub fee_growth_outside_0x_128: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="5")]
pub fee_growth_outside_1x_128: ::prost::alloc::string::String,
#[prost(uint64, tag="6")]
pub timestamp: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PoolSqrtPrice {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
#[prost(uint64, tag="2")]
pub ordinal: u64,
/// Integer
#[prost(string, tag="3")]
pub sqrt_price: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="4")]
pub tick: ::prost::alloc::string::String,
#[prost(bool, tag="5")]
pub initialized: bool,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PoolEvent {
#[prost(uint64, tag="100")]
pub log_ordinal: u64,
#[prost(uint64, tag="101")]
pub log_index: u64,
#[prost(string, tag="102")]
pub pool_address: ::prost::alloc::string::String,
#[prost(string, tag="103")]
pub token0: ::prost::alloc::string::String,
#[prost(string, tag="104")]
pub token1: ::prost::alloc::string::String,
#[prost(string, tag="105")]
pub fee: ::prost::alloc::string::String,
#[prost(string, tag="106")]
pub transaction_id: ::prost::alloc::string::String,
#[prost(uint64, tag="107")]
pub timestamp: u64,
#[prost(uint64, tag="108")]
pub created_at_block_number: u64,
#[prost(oneof="pool_event::Type", tags="1, 2, 3")]
pub r#type: ::core::option::Option<pool_event::Type>,
}
/// Nested message and enum types in `PoolEvent`.
pub mod pool_event {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Swap {
#[prost(string, tag="1")]
pub sender: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub recipient: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub origin: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="4")]
pub amount_0: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="5")]
pub amount_1: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="6")]
pub sqrt_price: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="7")]
pub liquidity: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="8")]
pub tick: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Burn {
#[prost(string, tag="1")]
pub owner: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub origin: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="3")]
pub amount: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="4")]
pub amount_0: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="5")]
pub amount_1: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="6")]
pub tick_lower: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="7")]
pub tick_upper: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Mint {
#[prost(string, tag="1")]
pub owner: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub sender: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub origin: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="4")]
pub amount_0: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="5")]
pub amount_1: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="6")]
pub tick_lower: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="7")]
pub tick_upper: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="8")]
pub amount: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Type {
#[prost(message, tag="1")]
Swap(Swap),
#[prost(message, tag="2")]
Burn(Burn),
#[prost(message, tag="3")]
Mint(Mint),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PoolLiquidity {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="2")]
pub liquidity: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub token0: ::prost::alloc::string::String,
#[prost(string, tag="4")]
pub token1: ::prost::alloc::string::String,
/// internals
#[prost(uint64, tag="30")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Flash {
#[prost(string, tag="1")]
pub pool_address: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="2")]
pub fee_growth_global_0x_128: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="3")]
pub fee_growth_global_1x_128: ::prost::alloc::string::String,
#[prost(uint64, tag="4")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transaction {
#[prost(string, tag="1")]
pub id: ::prost::alloc::string::String,
#[prost(uint64, tag="2")]
pub block_number: u64,
#[prost(uint64, tag="3")]
pub timestamp: u64,
#[prost(uint64, tag="4")]
pub gas_used: u64,
/// Integer
#[prost(string, tag="5")]
pub gas_price: ::prost::alloc::string::String,
#[prost(uint64, tag="6")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PositionEvent {
#[prost(oneof="position_event::Type", tags="1, 2, 3, 4, 5")]
pub r#type: ::core::option::Option<position_event::Type>,
}
/// Nested message and enum types in `PositionEvent`.
pub mod position_event {
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Type {
#[prost(message, tag="1")]
CreatedPosition(super::CreatedPosition),
#[prost(message, tag="2")]
IncreaseLiquidityPosition(super::IncreaseLiquidityPosition),
#[prost(message, tag="3")]
DecreaseLiquidityPosition(super::DecreaseLiquidityPosition),
#[prost(message, tag="4")]
CollectPosition(super::CollectPosition),
#[prost(message, tag="5")]
TransferPosition(super::TransferPosition),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CreatedPosition {
#[prost(string, tag="1")]
pub token_id: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub pool: ::prost::alloc::string::String,
#[prost(string, tag="3")]
pub token0: ::prost::alloc::string::String,
#[prost(string, tag="4")]
pub token1: ::prost::alloc::string::String,
#[prost(string, tag="5")]
pub tick_lower: ::prost::alloc::string::String,
#[prost(string, tag="6")]
pub tick_upper: ::prost::alloc::string::String,
#[prost(string, tag="7")]
pub transaction: ::prost::alloc::string::String,
#[prost(uint64, tag="8")]
pub log_ordinal: u64,
#[prost(uint64, tag="9")]
pub timestamp: u64,
#[prost(uint64, tag="10")]
pub block_number: u64,
/// BigInt
#[prost(string, optional, tag="11")]
pub fee_growth_inside0_last_x128: ::core::option::Option<::prost::alloc::string::String>,
/// BigInt
#[prost(string, optional, tag="12")]
pub fee_growth_inside1_last_x128: ::core::option::Option<::prost::alloc::string::String>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct IncreaseLiquidityPosition {
#[prost(string, tag="1")]
pub token_id: ::prost::alloc::string::String,
/// BigInt
#[prost(string, tag="2")]
pub liquidity: ::prost::alloc::string::String,
/// BigDecimal
#[prost(string, tag="3")]
pub deposited_token0: ::prost::alloc::string::String,
/// BigDecimal
#[prost(string, tag="4")]
pub deposited_token1: ::prost::alloc::string::String,
/// BigInt
#[prost(string, optional, tag="5")]
pub fee_growth_inside0_last_x128: ::core::option::Option<::prost::alloc::string::String>,
/// BigInt
#[prost(string, optional, tag="6")]
pub fee_growth_inside1_last_x128: ::core::option::Option<::prost::alloc::string::String>,
#[prost(uint64, tag="10")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DecreaseLiquidityPosition {
#[prost(string, tag="1")]
pub token_id: ::prost::alloc::string::String,
/// BigInt
#[prost(string, tag="2")]
pub liquidity: ::prost::alloc::string::String,
/// BigDecimal
#[prost(string, tag="3")]
pub withdrawn_token0: ::prost::alloc::string::String,
/// BigDecimal
#[prost(string, tag="4")]
pub withdrawn_token1: ::prost::alloc::string::String,
/// BigInt
#[prost(string, optional, tag="5")]
pub fee_growth_inside0_last_x128: ::core::option::Option<::prost::alloc::string::String>,
/// BigInt
#[prost(string, optional, tag="6")]
pub fee_growth_inside1_last_x128: ::core::option::Option<::prost::alloc::string::String>,
#[prost(uint64, tag="10")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct CollectPosition {
#[prost(string, tag="1")]
pub token_id: ::prost::alloc::string::String,
/// BigInt
#[prost(string, tag="2")]
pub collected_fees_token0: ::prost::alloc::string::String,
/// BigInt
#[prost(string, tag="3")]
pub collected_fees_token1: ::prost::alloc::string::String,
/// BigInt
#[prost(string, optional, tag="5")]
pub fee_growth_inside0_last_x128: ::core::option::Option<::prost::alloc::string::String>,
/// BigInt
#[prost(string, optional, tag="6")]
pub fee_growth_inside1_last_x128: ::core::option::Option<::prost::alloc::string::String>,
#[prost(uint64, tag="10")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TransferPosition {
#[prost(string, tag="1")]
pub token_id: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub owner: ::prost::alloc::string::String,
#[prost(uint64, tag="10")]
pub log_ordinal: u64,
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SnapshotPositions {
#[prost(message, repeated, tag="1")]
pub snapshot_positions: ::prost::alloc::vec::Vec<SnapshotPosition>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SnapshotPosition {
#[prost(string, tag="1")]
pub pool: ::prost::alloc::string::String,
/// the token_id of the position
#[prost(string, tag="2")]
pub position: ::prost::alloc::string::String,
#[prost(uint64, tag="3")]
pub block_number: u64,
#[prost(string, tag="4")]
pub owner: ::prost::alloc::string::String,
#[prost(uint64, tag="6")]
pub timestamp: u64,
/// Decimal
#[prost(string, tag="7")]
pub liquidity: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="8")]
pub deposited_token0: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="9")]
pub deposited_token1: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="10")]
pub withdrawn_token0: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="11")]
pub withdrawn_token1: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="12")]
pub collected_fees_token0: ::prost::alloc::string::String,
/// Decimal
#[prost(string, tag="13")]
pub collected_fees_token1: ::prost::alloc::string::String,
#[prost(string, tag="14")]
pub transaction: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="15")]
pub fee_growth_inside_0_last_x_128: ::prost::alloc::string::String,
/// Integer
#[prost(string, tag="16")]
pub fee_growth_inside_1_last_x_128: ::prost::alloc::string::String,
/// internal
#[prost(uint64, tag="17")]
pub log_ordinal: u64,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,41 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pool {
#[prost(bytes="vec", tag="1")]
pub address: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="2")]
pub token0: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="3")]
pub token1: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="4")]
pub created_tx_hash: ::prost::alloc::vec::Vec<u8>,
}
/// A change to a pool's balance.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceDelta {
/// The address of the ERC20 token.
#[prost(bytes="vec", tag="1")]
pub token_address: ::prost::alloc::vec::Vec<u8>,
/// The delta of the token.
#[prost(bytes="vec", tag="2")]
pub amount: ::prost::alloc::vec::Vec<u8>,
/// The sign of the delta, true for positive, false for negative.
#[prost(bool, tag="3")]
pub sign: bool,
/// The address of the pool whose balance changed.
#[prost(bytes="vec", tag="4")]
pub pool_address: ::prost::alloc::vec::Vec<u8>,
/// Used to determine the order of the balance changes. Necessary for the balance store.
#[prost(uint64, tag="5")]
pub ordinal: u64,
}
/// A group of BalanceDelta
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceDeltas {
#[prost(message, repeated, tag="1")]
pub deltas: ::prost::alloc::vec::Vec<BalanceDelta>,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,60 @@
use hex_literal::hex;
use super::pool_storage::StorageLocation;
const SLOT0: [u8; 32] = hex!("0000000000000000000000000000000000000000000000000000000000000000");
const LIQUIDITY_SLOT: StorageLocation = StorageLocation {
name: "liquidity",
slot: hex!("0000000000000000000000000000000000000000000000000000000000000004"),
offset: 0,
number_of_bytes: 16,
signed: false,
};
const PROTOCOL_FEES_TOKEN_0_SLOT: StorageLocation = StorageLocation {
name: "protocol_fees/token0",
slot: hex!("0000000000000000000000000000000000000000000000000000000000000003"),
offset: 0,
number_of_bytes: 16,
signed: false,
};
const PROTOCOL_FEES_TOKEN_1_SLOT: StorageLocation = StorageLocation {
name: "protocol_fees/token1",
slot: hex!("0000000000000000000000000000000000000000000000000000000000000003"),
offset: 16,
number_of_bytes: 16,
signed: false,
};
const SQRT_PRICE_X96_SLOT: StorageLocation = StorageLocation {
name: "sqrt_price_x96",
slot: SLOT0,
offset: 0,
number_of_bytes: 20,
signed: false,
};
const CURRENT_TICK_SLOT: StorageLocation =
StorageLocation { name: "tick", slot: SLOT0, offset: 20, number_of_bytes: 3, signed: true };
const FEE_PROTOCOL_SLOT: StorageLocation = StorageLocation {
name: "fee_protocol",
slot: SLOT0,
offset: 29,
number_of_bytes: 1,
signed: false,
};
pub(crate) const TICKS_MAP_SLOT: [u8; 32] =
hex!("0000000000000000000000000000000000000000000000000000000000000005");
pub(crate) const TRACKED_SLOTS: [StorageLocation; 6] = [
LIQUIDITY_SLOT,
PROTOCOL_FEES_TOKEN_0_SLOT,
PROTOCOL_FEES_TOKEN_1_SLOT,
SQRT_PRICE_X96_SLOT,
CURRENT_TICK_SLOT,
FEE_PROTOCOL_SLOT,
];

View File

@@ -0,0 +1,4 @@
pub mod pool_storage;
pub mod constants;
mod utils;

View File

@@ -0,0 +1,132 @@
use crate::{
pb::tycho::evm::v1::{Attribute, ChangeType},
storage::utils,
};
use substreams::scalar::BigInt;
use substreams_ethereum::pb::eth::v2::StorageChange;
use super::{constants::TICKS_MAP_SLOT, utils::read_bytes};
/// `StorageLocation` is a struct that represents a specific location within a contract's storage
/// associated with a name.
///
/// # Fields
///
/// * `name` - A string slice (`&str`) reference representing the unique name associated with this
/// storage location.
/// * `slot` - A fixed-size byte array `[u8; 32]` representing the slot in the contract storage
/// where this data is stored. This acts as a primary identifier for the location of the data.
/// * `offset` - A usize value indicating the offset in bytes from the start of the slot. This
/// allows for fine-grained control and access within a single slot.
/// * `number_of_bytes` - A usize value indicating the size of the data in bytes.
/// ```
#[derive(Clone)]
pub struct StorageLocation<'a> {
pub name: &'a str,
pub slot: [u8; 32],
pub offset: usize,
pub number_of_bytes: usize,
pub signed: bool,
}
pub struct UniswapPoolStorage<'a> {
pub storage_changes: &'a Vec<StorageChange>,
}
impl<'a> UniswapPoolStorage<'a> {
pub fn new(storage_changes: &'a Vec<StorageChange>) -> UniswapPoolStorage<'a> {
Self { storage_changes }
}
/// Iterates through storage changes and checks for modifications in the provided list of
/// storage locations. For each change, it compares the old and new values at the specified
/// offset and length for that location. If a change is detected, it's added to the returned
/// `Attribute` list.
///
/// Arguments:
/// locations: Vec<&StorageLocation> - A vector of references to StorageLocation objects
/// that define the slots, offsets, and lengths to be checked for changes.
///
/// Returns:
/// `Vec<Attribute>`: A vector containing Attributes for each change detected in the tracked
/// slots. Returns an empty vector if no changes are detected.
pub fn get_changed_attributes(&self, locations: Vec<&StorageLocation>) -> Vec<Attribute> {
let mut attributes = Vec::new();
// For each storage change, check if it changes a tracked slot.
// If it does, add the attribute to the list of attributes
for change in self.storage_changes {
for storage_location in locations.iter() {
// Check if the change slot matches the tracked slot
if change.key == storage_location.slot {
let old_data = read_bytes(
&change.old_value,
storage_location.offset,
storage_location.number_of_bytes,
);
let new_data = read_bytes(
&change.new_value,
storage_location.offset,
storage_location.number_of_bytes,
);
// Check if there is a change in the data
if old_data != new_data {
let value = match storage_location.signed {
true => BigInt::from_signed_bytes_be(new_data),
false => BigInt::from_unsigned_bytes_be(new_data),
};
attributes.push(Attribute {
name: storage_location.name.to_string(),
value: value.to_signed_bytes_le(),
change: ChangeType::Update.into(),
});
}
}
}
}
attributes
}
/// Iterates over a list of tick indexes and checks for modifications in the list of
/// storage changes. If a relevent change is detected, it's added to the returned `Attribute`
/// list.
///
/// Arguments:
/// ticks_idx: `Vec<&BigInt>` - A vector of references to tick indexes as BigInt objects.
///
/// Returns:
/// `Vec<Attribute>`: A vector containing Attributes for each change detected. Returns an
/// empty vector if no changes are detected.
///
/// Note: Currently, we only track the net-liquidity attribute for each tick.
pub fn get_ticks_changes(&self, ticks_idx: Vec<&BigInt>) -> Vec<Attribute> {
let mut storage_locs = Vec::new();
let mut tick_names = Vec::new();
// First, create all the names and push them into tick_names.
// We need this to keep the references to the names alive until we call
// `get_changed_attributes()`
for tick_idx in ticks_idx.iter() {
tick_names.push(format!("ticks/{}/net-liquidity", tick_idx));
}
// Then, iterate over ticks_idx and tick_names simultaneously
for (tick_idx, tick_name) in ticks_idx.iter().zip(tick_names.iter()) {
let tick_slot =
utils::calc_map_slot(&utils::left_pad_from_bigint(tick_idx), &TICKS_MAP_SLOT);
storage_locs.push(StorageLocation {
name: tick_name,
slot: tick_slot,
offset: 16,
number_of_bytes: 16,
signed: true,
});
}
self.get_changed_attributes(storage_locs.iter().collect())
}
}

View File

@@ -0,0 +1,165 @@
use substreams::scalar::BigInt;
use tiny_keccak::{Hasher, Keccak};
pub fn calc_map_slot(map_index: &[u8; 32], base_slot: &[u8; 32]) -> [u8; 32] {
let mut output = [0u8; 32];
let mut hasher = Keccak::v256();
hasher.update(map_index);
hasher.update(base_slot);
hasher.finalize(&mut output);
output
}
pub fn left_pad_from_bigint(input: &BigInt) -> [u8; 32] {
if input.lt(&BigInt::zero()) {
return left_pad(&input.to_signed_bytes_be(), 255);
}
left_pad(&input.to_signed_bytes_be(), 0)
}
pub fn left_pad(input: &[u8], padding_value: u8) -> [u8; 32] {
if input.len() > 32 {
panic!("cannot convert vec<u8> to H256");
}
let mut data = [padding_value; 32];
let offset = 32 - input.len();
data[offset..(input.len() + offset)].copy_from_slice(input);
data
}
pub fn read_bytes(buf: &[u8], offset: usize, number_of_bytes: usize) -> &[u8] {
let buf_length = buf.len();
if buf_length < number_of_bytes {
panic!(
"attempting to read {number_of_bytes} bytes in buffer size {buf_size}",
number_of_bytes = number_of_bytes,
buf_size = buf.len()
)
}
if offset > (buf_length - 1) {
panic!(
"offset {offset} exceeds buffer size {buf_size}",
offset = offset,
buf_size = buf.len()
)
}
let end = buf_length - 1 - offset;
let start_opt = (end + 1).checked_sub(number_of_bytes);
if start_opt.is_none() {
panic!(
"number of bytes {number_of_bytes} with offset {offset} exceeds buffer size
{buf_size}",
number_of_bytes = number_of_bytes,
offset = offset,
buf_size = buf.len()
)
}
let start = start_opt.unwrap();
&buf[start..=end]
}
#[cfg(test)]
mod tests {
use crate::storage::utils::{left_pad, read_bytes};
use hex_literal::hex;
use std::{fmt::Write, num::ParseIntError};
#[test]
fn left_pad_lt_32_bytes() {
let input = hex!("dd62ed3e");
assert_eq!(
hex!("00000000000000000000000000000000000000000000000000000000dd62ed3e"),
left_pad(&input, 0)
)
}
#[test]
fn left_pad_eq_32_bytes() {
let input = hex!("00000a0000000000005d000000000000000000000000000000000000dd62ed3e");
assert_eq!(
hex!("00000a0000000000005d000000000000000000000000000000000000dd62ed3e"),
left_pad(&input, 0)
)
}
#[test]
#[should_panic]
fn left_pad_gt_32_bytes() {
let input = hex!("070000000a0000000000005d000000000000000000000000000000000000dd62ed3e");
let _ = left_pad(&input, 0);
}
#[test]
#[should_panic]
fn read_bytes_buf_too_small() {
let buf = decode_hex("ff").unwrap();
let offset = 0;
let number_of_bytes = 3;
let _ = read_bytes(&buf, offset, number_of_bytes);
}
#[test]
fn read_one_byte_with_no_offset() {
let buf = decode_hex("aabb").unwrap();
let offset = 0;
let number_of_bytes = 1;
assert_eq!(read_bytes(&buf, offset, number_of_bytes), hex!("bb"));
}
#[test]
fn read_one_byte_with_offset() {
let buf = decode_hex("aabb").unwrap();
let offset = 1;
let number_of_bytes = 1;
assert_eq!(read_bytes(&buf, offset, number_of_bytes), hex!("aa"));
}
#[test]
#[should_panic]
fn read_bytes_overflow() {
let buf = decode_hex("aabb").unwrap();
let offset = 1;
let number_of_bytes = 2;
let _ = read_bytes(&buf, offset, number_of_bytes);
}
#[test]
fn read_bytes_with_no_offset() {
let buf =
decode_hex("ffffffffffffffffffffecb6826b89a60000000000000000000013497d94765a").unwrap();
let offset = 0;
let number_of_bytes = 16;
let out = read_bytes(&buf, offset, number_of_bytes);
assert_eq!(encode_hex(out), "0000000000000000000013497d94765a".to_string());
}
#[test]
fn read_byte_with_big_offset() {
let buf =
decode_hex("0100000000000000000000000000000000000000000000000000000000000000").unwrap();
let offset = 31;
let number_of_bytes = 1;
let out = read_bytes(&buf, offset, number_of_bytes);
assert_eq!(encode_hex(out), "01".to_string());
}
fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
.collect()
}
fn encode_hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for &b in bytes {
write!(&mut s, "{:02x}", b).unwrap();
}
s
}
}

View File

@@ -0,0 +1,30 @@
use substreams_ethereum::pb::eth::v2::{self as eth};
use crate::pb::tycho::evm::v1::{Block, Transaction};
impl From<eth::Block> for Block {
fn from(block: eth::Block) -> Self {
Self {
hash: block.hash.clone(),
parent_hash: block
.header
.as_ref()
.expect("Block header not present")
.parent_hash
.clone(),
number: block.number,
ts: block.timestamp_seconds(),
}
}
}
impl From<&eth::TransactionTrace> for Transaction {
fn from(tx: &eth::TransactionTrace) -> Self {
Self {
hash: tx.hash.clone(),
from: tx.from.clone(),
to: tx.to.clone(),
index: tx.index.into(),
}
}
}