Merge pull request #86 from propeller-heads/zz/substreams/add-uniswaps
feat(substreams): add substreams modules for Uniswaps and update Ambient
This commit is contained in:
@@ -1,2 +1,2 @@
|
|||||||
# Generated by buf. DO NOT EDIT.
|
# Generated by buf. DO NOT EDIT.
|
||||||
version: v1
|
version: v2
|
||||||
19
buf.yaml
Normal file
19
buf.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
version: v2
|
||||||
|
modules:
|
||||||
|
- path: proto
|
||||||
|
excludes:
|
||||||
|
- proto/sf
|
||||||
|
- path: substreams/ethereum-ambient/proto
|
||||||
|
lint:
|
||||||
|
use:
|
||||||
|
- BASIC
|
||||||
|
except:
|
||||||
|
- FIELD_NOT_REQUIRED
|
||||||
|
- PACKAGE_NO_IMPORT_CYCLE
|
||||||
|
disallow_comment_ignores: true
|
||||||
|
breaking:
|
||||||
|
use:
|
||||||
|
- FILE
|
||||||
|
except:
|
||||||
|
- EXTENSION_NO_DELETE
|
||||||
|
- FIELD_SAME_DEFAULT
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
version: v1
|
|
||||||
breaking:
|
|
||||||
use:
|
|
||||||
- FILE
|
|
||||||
lint:
|
|
||||||
use:
|
|
||||||
- BASIC
|
|
||||||
102
substreams/Cargo.lock
generated
102
substreams/Cargo.lock
generated
@@ -29,6 +29,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bigdecimal"
|
name = "bigdecimal"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -126,6 +132,12 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@@ -934,6 +946,18 @@ dependencies = [
|
|||||||
"thiserror",
|
"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]]
|
[[package]]
|
||||||
name = "substreams-ethereum"
|
name = "substreams-ethereum"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
@@ -965,6 +989,24 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "substreams-ethereum-ambient"
|
||||||
|
version = "0.5.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bytes",
|
||||||
|
"ethabi 18.0.0",
|
||||||
|
"hex",
|
||||||
|
"hex-literal 0.4.1",
|
||||||
|
"num-bigint",
|
||||||
|
"prost 0.11.9",
|
||||||
|
"quote",
|
||||||
|
"substreams",
|
||||||
|
"substreams-ethereum",
|
||||||
|
"tiny-keccak",
|
||||||
|
"tycho-substreams",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "substreams-ethereum-core"
|
name = "substreams-ethereum-core"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
@@ -997,6 +1039,66 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "substreams-ethereum-uniswap-v2"
|
||||||
|
version = "0.2.1"
|
||||||
|
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",
|
||||||
|
"tycho-substreams",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "substreams-ethereum-uniswap-v3"
|
||||||
|
version = "0.2.1"
|
||||||
|
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",
|
||||||
|
"tycho-substreams",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "substreams-macro"
|
name = "substreams-macro"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
[workspace]
|
[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"
|
resolver = "2"
|
||||||
|
|
||||||
|
|
||||||
@@ -9,9 +17,11 @@ substreams = "0.5"
|
|||||||
prost = "0.11"
|
prost = "0.11"
|
||||||
prost-types = "0.12.3"
|
prost-types = "0.12.3"
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
|
anyhow = "1.0.75"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
ethabi = "18.0.0"
|
ethabi = "18.0.0"
|
||||||
tycho-substreams = { path = "crates/tycho-substreams" }
|
tycho-substreams = { path = "crates/tycho-substreams" }
|
||||||
|
substreams-helper = { path = "crates/substreams-helper" }
|
||||||
serde = "1.0.204"
|
serde = "1.0.204"
|
||||||
serde_json = "1.0.120"
|
serde_json = "1.0.120"
|
||||||
|
|
||||||
|
|||||||
1
substreams/crates/substreams-helper/.gitignore
vendored
Normal file
1
substreams/crates/substreams-helper/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Cargo.lock
|
||||||
29
substreams/crates/substreams-helper/Cargo.toml
Normal file
29
substreams/crates/substreams-helper/Cargo.toml
Normal 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
|
||||||
7
substreams/crates/substreams-helper/Makefile
Normal file
7
substreams/crates/substreams-helper/Makefile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
cargo build --target wasm32-unknown-unknown --release
|
||||||
|
|
||||||
|
.PHONY: publish
|
||||||
|
publish:
|
||||||
|
cargo publish --target wasm32-unknown-unknown
|
||||||
4
substreams/crates/substreams-helper/rust-toolchain.toml
Normal file
4
substreams/crates/substreams-helper/rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "1.75.0"
|
||||||
|
components = [ "rustfmt" ]
|
||||||
|
targets = [ "wasm32-unknown-unknown" ]
|
||||||
70
substreams/crates/substreams-helper/src/common.rs
Normal file
70
substreams/crates/substreams-helper/src/common.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
103
substreams/crates/substreams-helper/src/event_handler.rs
Normal file
103
substreams/crates/substreams-helper/src/event_handler.rs
Normal 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(ð::Log, ð::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, ð::TransactionTrace, ð::Log) + 'a,
|
||||||
|
{
|
||||||
|
self.handlers.insert(
|
||||||
|
E::NAME,
|
||||||
|
Box::new(move |log: ð::Log, tx: ð::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
substreams/crates/substreams-helper/src/hex.rs
Normal file
20
substreams/crates/substreams-helper/src/hex.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
5
substreams/crates/substreams-helper/src/lib.rs
Normal file
5
substreams/crates/substreams-helper/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod common;
|
||||||
|
|
||||||
|
pub mod event_handler;
|
||||||
|
pub mod hex;
|
||||||
|
pub mod storage_change;
|
||||||
13
substreams/crates/substreams-helper/src/storage_change.rs
Normal file
13
substreams/crates/substreams-helper/src/storage_change.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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> {
|
||||||
|
self.iter()
|
||||||
|
.filter(|change| change.address == contract_addr)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,12 @@ Some shared functionality that is used to create tycho substream packages.
|
|||||||
|
|
||||||
## Protobuf Models
|
## Protobuf Models
|
||||||
|
|
||||||
Protobuf models are manually synced from the `tycho-indexer` repository whenever they
|
Protobuf models are manually synced from the `tycho-indexer` repository whenever they
|
||||||
changed.
|
changed.
|
||||||
|
|
||||||
To generate the rust structs run the following command from within the `./proto`
|
To generate the rust structs run the following command from within the root
|
||||||
directory:
|
directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
buf generate \
|
buf generate --template substreams/crates/tycho-substreams/buf.gen.yaml --output substreams/crates/tycho-substreams/
|
||||||
--path tycho \
|
```
|
||||||
--template ../substreams/crates/tycho-substreams/buf.gen.yaml \
|
|
||||||
--output ../substreams/crates/tycho-substreams/
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
version: v1
|
version: v2
|
||||||
plugins:
|
plugins:
|
||||||
- plugin: buf.build/community/neoeinstein-prost:v0.2.2
|
- remote: buf.build/community/neoeinstein-prost:v0.2.2
|
||||||
out: src/pb
|
out: src/pb
|
||||||
opt:
|
opt:
|
||||||
- file_descriptor_set=false
|
- file_descriptor_set=false
|
||||||
- type_attribute=.tycho.evm.v1.Transaction=#[derive(Eq\, Hash)]
|
- type_attribute=.tycho.evm.v1.Transaction=#[derive(Eq\, Hash)]
|
||||||
|
- remote: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
||||||
- plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
|
||||||
out: src/pb
|
out: src/pb
|
||||||
opt:
|
opt: no_features
|
||||||
- no_features
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||||||
use substreams_ethereum::pb::eth::v2::{self as sf, StorageChange};
|
use substreams_ethereum::pb::eth::v2::{self as sf, StorageChange};
|
||||||
|
|
||||||
// re-export the protobuf types here.
|
// re-export the protobuf types here.
|
||||||
pub use crate::pb::tycho::evm::v1::*;
|
pub use crate::pb::tycho::{ambient::v1::*, evm::v1::*};
|
||||||
|
|
||||||
impl TransactionContractChanges {
|
impl TransactionContractChanges {
|
||||||
/// Creates a new empty `TransactionContractChanges` instance.
|
/// Creates a new empty `TransactionContractChanges` instance.
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
// @generated
|
// @generated
|
||||||
pub mod tycho {
|
pub mod tycho {
|
||||||
|
pub mod ambient {
|
||||||
|
// @@protoc_insertion_point(attribute:tycho.ambient.v1)
|
||||||
|
pub mod v1 {
|
||||||
|
include!("tycho.ambient.v1.rs");
|
||||||
|
// @@protoc_insertion_point(tycho.ambient.v1)
|
||||||
|
}
|
||||||
|
}
|
||||||
pub mod evm {
|
pub mod evm {
|
||||||
// @@protoc_insertion_point(attribute:tycho.evm.v1)
|
// @@protoc_insertion_point(attribute:tycho.evm.v1)
|
||||||
pub mod v1 {
|
pub mod v1 {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// @generated
|
||||||
|
/// A change to a pool's balance. Ambient specific.
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct AmbientBalanceDelta {
|
||||||
|
/// The address of the ERC20 token whose balance changed.
|
||||||
|
#[prost(bytes="vec", tag="1")]
|
||||||
|
pub pool_hash: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// The token type: it can be base or quote.
|
||||||
|
#[prost(string, tag="2")]
|
||||||
|
pub token_type: ::prost::alloc::string::String,
|
||||||
|
/// The delta of the token.
|
||||||
|
#[prost(bytes="vec", tag="3")]
|
||||||
|
pub token_delta: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// Used to determine the order of the balance changes. Necessary for the balance store.
|
||||||
|
#[prost(uint64, tag="4")]
|
||||||
|
pub ordinal: u64,
|
||||||
|
/// Transaction where the balance changed.
|
||||||
|
#[prost(message, optional, tag="5")]
|
||||||
|
pub tx: ::core::option::Option<super::super::evm::v1::Transaction>,
|
||||||
|
}
|
||||||
|
/// Ambient pool changes within a single block
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct BlockPoolChanges {
|
||||||
|
/// New protocol components added in this block
|
||||||
|
#[prost(message, repeated, tag="1")]
|
||||||
|
pub protocol_components: ::prost::alloc::vec::Vec<super::super::evm::v1::ProtocolComponent>,
|
||||||
|
/// Balance changes to pools in this block
|
||||||
|
#[prost(message, repeated, tag="2")]
|
||||||
|
pub balance_deltas: ::prost::alloc::vec::Vec<AmbientBalanceDelta>,
|
||||||
|
}
|
||||||
|
// @@protoc_insertion_point(module)
|
||||||
@@ -84,7 +84,8 @@ pub struct ProtocolComponent {
|
|||||||
/// Usually it is a single contract, but some protocols use multiple contracts.
|
/// Usually it is a single contract, but some protocols use multiple contracts.
|
||||||
#[prost(bytes="vec", repeated, tag="3")]
|
#[prost(bytes="vec", repeated, tag="3")]
|
||||||
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
||||||
/// Attributes of the component. Used mainly be the native integration.
|
/// Static attributes of the component.
|
||||||
|
/// These attributes MUST be immutable. If it can ever change, it should be given as an EntityChanges for this component id.
|
||||||
/// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
|
/// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
|
||||||
#[prost(message, repeated, tag="4")]
|
#[prost(message, repeated, tag="4")]
|
||||||
pub static_att: ::prost::alloc::vec::Vec<Attribute>,
|
pub static_att: ::prost::alloc::vec::Vec<Attribute>,
|
||||||
@@ -186,6 +187,7 @@ pub struct TransactionChanges {
|
|||||||
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
|
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
|
||||||
}
|
}
|
||||||
/// A set of transaction changes within a single block.
|
/// A set of transaction changes within a single block.
|
||||||
|
/// This message must be the output of your substreams module.
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct BlockChanges {
|
pub struct BlockChanges {
|
||||||
|
|||||||
108
substreams/ethereum-ambient/Cargo.lock
generated
108
substreams/ethereum-ambient/Cargo.lock
generated
@@ -180,24 +180,7 @@ version = "17.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b"
|
checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ethereum-types 0.13.1",
|
"ethereum-types",
|
||||||
"hex",
|
|
||||||
"once_cell",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha3",
|
|
||||||
"thiserror",
|
|
||||||
"uint",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ethabi"
|
|
||||||
version = "18.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898"
|
|
||||||
dependencies = [
|
|
||||||
"ethereum-types 0.14.1",
|
|
||||||
"hex",
|
"hex",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -215,22 +198,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef"
|
checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crunchy",
|
"crunchy",
|
||||||
"fixed-hash 0.7.0",
|
"fixed-hash",
|
||||||
"impl-rlp",
|
"impl-rlp",
|
||||||
"impl-serde 0.3.2",
|
"impl-serde",
|
||||||
"tiny-keccak",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ethbloom"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60"
|
|
||||||
dependencies = [
|
|
||||||
"crunchy",
|
|
||||||
"fixed-hash 0.8.0",
|
|
||||||
"impl-rlp",
|
|
||||||
"impl-serde 0.4.0",
|
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -240,25 +210,11 @@ version = "0.13.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6"
|
checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ethbloom 0.12.1",
|
"ethbloom",
|
||||||
"fixed-hash 0.7.0",
|
"fixed-hash",
|
||||||
"impl-rlp",
|
"impl-rlp",
|
||||||
"impl-serde 0.3.2",
|
"impl-serde",
|
||||||
"primitive-types 0.11.1",
|
"primitive-types",
|
||||||
"uint",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ethereum-types"
|
|
||||||
version = "0.14.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee"
|
|
||||||
dependencies = [
|
|
||||||
"ethbloom 0.13.0",
|
|
||||||
"fixed-hash 0.8.0",
|
|
||||||
"impl-rlp",
|
|
||||||
"impl-serde 0.4.0",
|
|
||||||
"primitive-types 0.12.2",
|
|
||||||
"uint",
|
"uint",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -280,18 +236,6 @@ dependencies = [
|
|||||||
"static_assertions",
|
"static_assertions",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fixed-hash"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"rand",
|
|
||||||
"rustc-hex",
|
|
||||||
"static_assertions",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fixedbitset"
|
name = "fixedbitset"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -391,15 +335,6 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "impl-serde"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "impl-trait-for-tuples"
|
name = "impl-trait-for-tuples"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -584,23 +519,10 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a"
|
checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixed-hash 0.7.0",
|
"fixed-hash",
|
||||||
"impl-codec",
|
"impl-codec",
|
||||||
"impl-rlp",
|
"impl-rlp",
|
||||||
"impl-serde 0.3.2",
|
"impl-serde",
|
||||||
"uint",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "primitive-types"
|
|
||||||
version = "0.12.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2"
|
|
||||||
dependencies = [
|
|
||||||
"fixed-hash 0.8.0",
|
|
||||||
"impl-codec",
|
|
||||||
"impl-rlp",
|
|
||||||
"impl-serde 0.4.0",
|
|
||||||
"uint",
|
"uint",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -883,7 +805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "97a176f39a6e09553c17a287edacd1854e5686fd20ffea3c9655dfc44d94b35e"
|
checksum = "97a176f39a6e09553c17a287edacd1854e5686fd20ffea3c9655dfc44d94b35e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ethabi 17.2.0",
|
"ethabi",
|
||||||
"heck",
|
"heck",
|
||||||
"hex",
|
"hex",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
@@ -895,12 +817,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "substreams-ethereum-ambient"
|
name = "substreams-ethereum-ambient"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
|
||||||
"bytes",
|
|
||||||
"ethabi 18.0.0",
|
|
||||||
"hex",
|
|
||||||
"hex-literal 0.4.1",
|
"hex-literal 0.4.1",
|
||||||
"prost",
|
"prost",
|
||||||
"substreams",
|
"substreams",
|
||||||
@@ -914,7 +832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "db4700cfe408b75634a3c6b3a0caf7bddba4879601d2085c811485ea54cbde2d"
|
checksum = "db4700cfe408b75634a3c6b3a0caf7bddba4879601d2085c811485ea54cbde2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bigdecimal",
|
"bigdecimal",
|
||||||
"ethabi 17.2.0",
|
"ethabi",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"prost",
|
"prost",
|
||||||
@@ -929,7 +847,7 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40d6d278d926fe3f0775d996ee2b5e1dc822c1b4bf4f7bf07c7fbb5bce6c79a9"
|
checksum = "40d6d278d926fe3f0775d996ee2b5e1dc822c1b4bf4f7bf07c7fbb5bce6c79a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ethabi 17.2.0",
|
"ethabi",
|
||||||
"heck",
|
"heck",
|
||||||
"hex",
|
"hex",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "substreams-ethereum-ambient"
|
name = "substreams-ethereum-ambient"
|
||||||
version = "0.3.0"
|
version = "0.5.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -8,11 +8,15 @@ name = "substreams_ethereum_ambient"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
substreams = "0.5"
|
tycho-substreams.workspace = true
|
||||||
substreams-ethereum = "0.9"
|
substreams.workspace = true
|
||||||
prost = "0.11"
|
substreams-ethereum.workspace = true
|
||||||
hex-literal = "0.4.1"
|
prost.workspace = true
|
||||||
ethabi = "18.0.0"
|
hex-literal.workspace = true
|
||||||
hex = "0.4.2"
|
ethabi.workspace = true
|
||||||
|
hex.workspace = true
|
||||||
bytes = "1.5.0"
|
bytes = "1.5.0"
|
||||||
anyhow = "1.0.75"
|
anyhow.workspace = true
|
||||||
|
tiny-keccak = "2.0.2"
|
||||||
|
num-bigint = { version = "0.4.4", features = [] }
|
||||||
|
quote = "1.0.33"
|
||||||
|
|||||||
35
substreams/ethereum-ambient/Readme.md
Normal file
35
substreams/ethereum-ambient/Readme.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
Substreams Ethereum Ambient Module
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Modules Description
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
### `map_pool_changes`
|
||||||
|
|
||||||
|
* **Type**: Map
|
||||||
|
* **Purpose**: This module detects new pools within the Ethereum blockchain and balance changes.
|
||||||
|
* **Inputs**: Ethereum block data (`sf.ethereum.type.v2.Block`).
|
||||||
|
* **Output**: Emits data of type `proto:tycho.evm.state.v1.BlockPoolChanges`.
|
||||||
|
|
||||||
|
### `store_pools_balances`
|
||||||
|
|
||||||
|
* **Type**: Store
|
||||||
|
* **Purpose**: Accumulates and stores the balances of pools detected by `map_pool_changes`. It uses an additive update policy, implying that new values are added to existing balances.
|
||||||
|
* **Inputs**: Data mapped by `map_pool_changes`.
|
||||||
|
|
||||||
|
### `store_pools`
|
||||||
|
|
||||||
|
* **Type**: Store
|
||||||
|
* **Purpose**: Maintains a store of pool information using the `ProtocolComponent` data structure. This store is updated whenever `map_pool_changes` emits new pool data.
|
||||||
|
* **Inputs**: Data mapped by `map_pool_changes`.
|
||||||
|
|
||||||
|
### `map_changes`
|
||||||
|
|
||||||
|
* **Type**: Map
|
||||||
|
* **Purpose**: This module integrates all the processed information to generate comprehensive `BlockContractChanges`. It considers new pools, balance changes and contract changes.
|
||||||
|
* **Inputs**:
|
||||||
|
* Ethereum block data (`sf.ethereum.type.v2.Block`).
|
||||||
|
* Data from `map_pool_changes`.
|
||||||
|
* Data from `store_pools_balances`.
|
||||||
|
* Data from `store_pools`.
|
||||||
|
* **Output**: Emits `proto:tycho.evm.state.v1.BlockContractChanges`.
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
|
version: v2
|
||||||
version: v1
|
|
||||||
plugins:
|
plugins:
|
||||||
- plugin: buf.build/community/neoeinstein-prost:v0.2.2
|
- remote: buf.build/community/neoeinstein-prost:v0.2.2
|
||||||
out: src/pb
|
out: src/pb
|
||||||
opt:
|
opt: file_descriptor_set=false
|
||||||
- file_descriptor_set=false
|
- remote: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
||||||
|
out: src/pb
|
||||||
- plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
opt: no_features
|
||||||
out: src/pb
|
|
||||||
opt:
|
|
||||||
- no_features
|
|
||||||
|
|||||||
27
substreams/ethereum-ambient/proto/ambient.proto
Normal file
27
substreams/ethereum-ambient/proto/ambient.proto
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package tycho.ambient.v1;
|
||||||
|
|
||||||
|
import "tycho/evm/v1/common.proto";
|
||||||
|
|
||||||
|
// A change to a pool's balance. Ambient specific.
|
||||||
|
message AmbientBalanceDelta {
|
||||||
|
// The address of the ERC20 token whose balance changed.
|
||||||
|
bytes pool_hash = 1;
|
||||||
|
// The token type: it can be base or quote.
|
||||||
|
string token_type = 2;
|
||||||
|
// The delta of the token.
|
||||||
|
bytes token_delta = 3;
|
||||||
|
// Used to determine the order of the balance changes. Necessary for the balance store.
|
||||||
|
uint64 ordinal = 4;
|
||||||
|
// Transaction where the balance changed.
|
||||||
|
tycho.evm.v1.Transaction tx = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambient pool changes within a single block
|
||||||
|
message BlockPoolChanges {
|
||||||
|
// New protocol components added in this block
|
||||||
|
repeated tycho.evm.v1.ProtocolComponent protocol_components = 1;
|
||||||
|
// Balance changes to pools in this block
|
||||||
|
repeated AmbientBalanceDelta balance_deltas = 2;
|
||||||
|
}
|
||||||
83
substreams/ethereum-ambient/src/contracts/hotproxy.rs
Normal file
83
substreams/ethereum-ambient/src/contracts/hotproxy.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
|
||||||
|
use crate::utils::{decode_flows_from_output, encode_pool_hash};
|
||||||
|
use ethabi::{decode, ParamType};
|
||||||
|
use hex_literal::hex;
|
||||||
|
use substreams_ethereum::pb::eth::v2::Call;
|
||||||
|
|
||||||
|
pub const AMBIENT_HOTPROXY_CONTRACT: [u8; 20] = hex!("37e00522Ce66507239d59b541940F99eA19fF81F");
|
||||||
|
pub const USER_CMD_HOTPROXY_FN_SIG: [u8; 4] = hex!("f96dc788");
|
||||||
|
|
||||||
|
pub const SWAP_ABI_HOTPROXY_INPUT: &[ParamType] = &[
|
||||||
|
ParamType::Address, // base
|
||||||
|
ParamType::Address, // quote
|
||||||
|
ParamType::Uint(256), // pool index
|
||||||
|
// isBuy - if true the direction of the swap is for the user to send base
|
||||||
|
// tokens and receive back quote tokens.
|
||||||
|
ParamType::Bool,
|
||||||
|
ParamType::Bool, // inBaseQty
|
||||||
|
ParamType::Uint(128), //qty
|
||||||
|
ParamType::Uint(16), // poolTip
|
||||||
|
ParamType::Uint(128), // limitPrice
|
||||||
|
ParamType::Uint(128), // minOut
|
||||||
|
ParamType::Uint(8), // reserveFlags
|
||||||
|
];
|
||||||
|
const USER_CMD_EXTERNAL_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Bytes, // userCmd
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn decode_direct_swap_hotproxy_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(external_cmd) = decode(USER_CMD_EXTERNAL_ABI, &call.input[4..]) {
|
||||||
|
let input_bytes = external_cmd[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_bytes() // Convert Bytes32 to Vec<u8>
|
||||||
|
.ok_or_else(|| anyhow!("Failed to hotproxy userCmd input data.".to_string()))?;
|
||||||
|
|
||||||
|
if let Ok(input_params) = decode(SWAP_ABI_HOTPROXY_INPUT, &input_bytes) {
|
||||||
|
let base_token = input_params[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert base token to address for direct swap hotproxy call: {:?}",
|
||||||
|
&input_params[0]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let quote_token = input_params[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert quote token to address for direct swap hotproxy call: {:?}",
|
||||||
|
&input_params[1]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let mut pool_index_buf = [0u8; 32];
|
||||||
|
input_params[2]
|
||||||
|
.to_owned()
|
||||||
|
.into_uint()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert pool index to u32 for direct swap hotproxy call"
|
||||||
|
.to_string())
|
||||||
|
})?
|
||||||
|
.to_big_endian(&mut pool_index_buf);
|
||||||
|
let pool_index = pool_index_buf.to_vec();
|
||||||
|
|
||||||
|
let (base_flow, quote_flow) = decode_flows_from_output(call)?;
|
||||||
|
let pool_hash = encode_pool_hash(base_token, quote_token, pool_index);
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode hotproxy swap call internap inputs.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode hotproxy swap call external input.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
106
substreams/ethereum-ambient/src/contracts/knockout.rs
Normal file
106
substreams/ethereum-ambient/src/contracts/knockout.rs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
|
||||||
|
use crate::utils::{decode_flows_from_output, encode_pool_hash};
|
||||||
|
use ethabi::{decode, ParamType};
|
||||||
|
use hex_literal::hex;
|
||||||
|
use substreams_ethereum::pb::eth::v2::Call;
|
||||||
|
|
||||||
|
pub const AMBIENT_KNOCKOUT_CONTRACT: [u8; 20] = hex!("7F5D75AdE75646919c923C98D53E9Cc7Be7ea794");
|
||||||
|
pub const USER_CMD_KNOCKOUT_FN_SIG: [u8; 4] = hex!("f96dc788");
|
||||||
|
|
||||||
|
// Represents the ABI of any cmd which is not mint or burn
|
||||||
|
const KNOCKOUT_INTERNAL_OTHER_CMD_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(8),
|
||||||
|
ParamType::Address, // base
|
||||||
|
ParamType::Address, // quote
|
||||||
|
ParamType::Uint(256), // poolIdx
|
||||||
|
ParamType::Int(24),
|
||||||
|
ParamType::Int(24),
|
||||||
|
ParamType::Bool,
|
||||||
|
ParamType::Uint(8),
|
||||||
|
ParamType::Uint(256),
|
||||||
|
ParamType::Uint(256),
|
||||||
|
ParamType::Uint(32),
|
||||||
|
];
|
||||||
|
const KNOCKOUT_INTERNAL_MINT_BURN_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(8),
|
||||||
|
ParamType::Address, // base
|
||||||
|
ParamType::Address, // quote
|
||||||
|
ParamType::Uint(256), // poolIdx
|
||||||
|
ParamType::Int(24),
|
||||||
|
ParamType::Int(24),
|
||||||
|
ParamType::Bool,
|
||||||
|
ParamType::Uint(8),
|
||||||
|
ParamType::Uint(256),
|
||||||
|
ParamType::Uint(256),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Bool,
|
||||||
|
];
|
||||||
|
|
||||||
|
const KNOCKOUT_EXTERNAL_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Bytes, // userCmd
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn decode_knockout_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(external_cmd) = decode(KNOCKOUT_EXTERNAL_ABI, &call.input[4..]) {
|
||||||
|
let input_data = external_cmd[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_bytes() // Convert Bytes32 to Vec<u8>
|
||||||
|
.ok_or_else(|| anyhow!("Failed to Knockout userCmd input data.".to_string()))?;
|
||||||
|
|
||||||
|
let code = input_data[31];
|
||||||
|
let is_mint = code == 91;
|
||||||
|
let is_burn = code == 92;
|
||||||
|
|
||||||
|
let abi = if is_mint || is_burn {
|
||||||
|
KNOCKOUT_INTERNAL_MINT_BURN_ABI
|
||||||
|
} else {
|
||||||
|
KNOCKOUT_INTERNAL_OTHER_CMD_ABI
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(mint_burn_inputs) = decode(abi, &input_data) {
|
||||||
|
let base_token = mint_burn_inputs[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert base token to address for knockout call: {:?}",
|
||||||
|
&mint_burn_inputs[1]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
let quote_token = mint_burn_inputs[2]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert quote token to address for knockout call: {:?}",
|
||||||
|
&mint_burn_inputs[2]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let mut pool_index_buf = [0u8; 32];
|
||||||
|
mint_burn_inputs[3]
|
||||||
|
.to_owned()
|
||||||
|
.into_uint()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert pool index to bytes for knockout call".to_string())
|
||||||
|
})?
|
||||||
|
.to_big_endian(&mut pool_index_buf);
|
||||||
|
let pool_index = pool_index_buf.to_vec();
|
||||||
|
|
||||||
|
let (base_flow, quote_flow) = decode_flows_from_output(call)?;
|
||||||
|
let pool_hash = encode_pool_hash(base_token, quote_token, pool_index);
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode knockout call outputs.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode inputs for knockout call.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
170
substreams/ethereum-ambient/src/contracts/main.rs
Normal file
170
substreams/ethereum-ambient/src/contracts/main.rs
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
use tycho_substreams::models::{
|
||||||
|
Attribute, ChangeType, FinancialType, ImplementationType, ProtocolComponent, ProtocolType,
|
||||||
|
Transaction,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::utils::{decode_flows_from_output, encode_pool_hash};
|
||||||
|
use ethabi::{decode, ParamType};
|
||||||
|
use hex_literal::hex;
|
||||||
|
use substreams_ethereum::pb::eth::v2::Call;
|
||||||
|
|
||||||
|
pub const AMBIENT_CONTRACT: [u8; 20] = hex!("aaaaaaaaa24eeeb8d57d431224f73832bc34f688");
|
||||||
|
pub const USER_CMD_FN_SIG: [u8; 4] = hex!("a15112f9");
|
||||||
|
|
||||||
|
const USER_CMD_EXTERNAL_ABI: &[ParamType] = &[
|
||||||
|
// index of the proxy sidecar the command is being called on
|
||||||
|
ParamType::Uint(16),
|
||||||
|
// call data for internal UserCmd method
|
||||||
|
ParamType::Bytes,
|
||||||
|
];
|
||||||
|
const USER_CMD_INTERNAL_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(8), // command
|
||||||
|
ParamType::Address, // base
|
||||||
|
ParamType::Address, // quote
|
||||||
|
ParamType::Uint(256), // pool index
|
||||||
|
ParamType::Uint(128), // price
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const INIT_POOL_CODE: u8 = 71;
|
||||||
|
|
||||||
|
pub const SWAP_ABI_INPUT: &[ParamType] = &[
|
||||||
|
ParamType::Address, // base
|
||||||
|
ParamType::Address, // quote
|
||||||
|
ParamType::Uint(256), // pool index
|
||||||
|
// isBuy - if true the direction of the swap is for the user to send base
|
||||||
|
// tokens and receive back quote tokens.
|
||||||
|
ParamType::Bool,
|
||||||
|
ParamType::Bool, // inBaseQty
|
||||||
|
ParamType::Uint(128), //qty
|
||||||
|
ParamType::Uint(16), // poolTip
|
||||||
|
ParamType::Uint(128), // limitPrice
|
||||||
|
ParamType::Uint(128), // minOut
|
||||||
|
ParamType::Uint(8), // reserveFlags
|
||||||
|
];
|
||||||
|
|
||||||
|
// MicroPaths fn sigs
|
||||||
|
pub const SWAP_FN_SIG: [u8; 4] = hex!("3d719cd9");
|
||||||
|
|
||||||
|
pub fn decode_direct_swap_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(external_input_params) = decode(SWAP_ABI_INPUT, &call.input[4..]) {
|
||||||
|
let base_token = external_input_params[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert base token to address for direct swap call: {:?}",
|
||||||
|
&external_input_params[0]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let quote_token = external_input_params[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert quote token to address for direct swap call: {:?}",
|
||||||
|
&external_input_params[1]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let mut pool_index_buf = [0u8; 32];
|
||||||
|
external_input_params[2]
|
||||||
|
.to_owned()
|
||||||
|
.into_uint()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert pool index to u32 for direct swap call".to_string())
|
||||||
|
})?
|
||||||
|
.to_big_endian(&mut pool_index_buf);
|
||||||
|
let pool_index = pool_index_buf.to_vec();
|
||||||
|
|
||||||
|
let (base_flow, quote_flow) = decode_flows_from_output(call)?;
|
||||||
|
let pool_hash = encode_pool_hash(base_token, quote_token, pool_index);
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode swap call inputs.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn decode_pool_init(
|
||||||
|
call: &Call,
|
||||||
|
tx: Transaction,
|
||||||
|
) -> Result<Option<ProtocolComponent>, anyhow::Error> {
|
||||||
|
// Decode external call to UserCmd
|
||||||
|
if let Ok(external_params) = decode(USER_CMD_EXTERNAL_ABI, &call.input[4..]) {
|
||||||
|
let cmd_bytes = external_params[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_bytes()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert to bytes: {:?}", &external_params[1]))?;
|
||||||
|
|
||||||
|
// Call data is structured differently depending on the cmd code, so only
|
||||||
|
// decode if this is an init pool code.
|
||||||
|
if cmd_bytes[31] == INIT_POOL_CODE {
|
||||||
|
// Decode internal call to UserCmd
|
||||||
|
if let Ok(internal_params) = decode(USER_CMD_INTERNAL_ABI, &cmd_bytes) {
|
||||||
|
let base = internal_params[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert to address: {:?}", &internal_params[1])
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let quote = internal_params[2]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert to address: {:?}", &internal_params[2])
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let mut pool_index_buf = [0u8; 32];
|
||||||
|
internal_params[3]
|
||||||
|
.to_owned()
|
||||||
|
.into_uint()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert to u32".to_string()))?
|
||||||
|
.to_big_endian(&mut pool_index_buf);
|
||||||
|
let pool_index = pool_index_buf.to_vec();
|
||||||
|
let pool_hash = encode_pool_hash(base.clone(), quote.clone(), pool_index.clone());
|
||||||
|
|
||||||
|
let static_attribute = Attribute {
|
||||||
|
name: String::from("pool_index"),
|
||||||
|
value: pool_index,
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tokens: Vec<Vec<u8>> = vec![base.clone(), quote.clone()];
|
||||||
|
tokens.sort();
|
||||||
|
|
||||||
|
let new_component = ProtocolComponent {
|
||||||
|
id: hex::encode(pool_hash),
|
||||||
|
tokens,
|
||||||
|
contracts: vec![AMBIENT_CONTRACT.to_vec()],
|
||||||
|
static_att: vec![static_attribute],
|
||||||
|
change: ChangeType::Creation.into(),
|
||||||
|
protocol_type: Some(ProtocolType {
|
||||||
|
name: "ambient_pool".to_string(),
|
||||||
|
attribute_schema: vec![],
|
||||||
|
financial_type: FinancialType::Swap.into(),
|
||||||
|
implementation_type: ImplementationType::Vm.into(),
|
||||||
|
}),
|
||||||
|
tx: Some(tx.clone()),
|
||||||
|
};
|
||||||
|
Ok(Some(new_component))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode ABI internal call.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode ABI external call.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
307
substreams/ethereum-ambient/src/contracts/micropaths.rs
Normal file
307
substreams/ethereum-ambient/src/contracts/micropaths.rs
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
|
||||||
|
use ethabi::{decode, ParamType};
|
||||||
|
use hex_literal::hex;
|
||||||
|
use substreams_ethereum::pb::eth::v2::Call;
|
||||||
|
|
||||||
|
pub const AMBIENT_MICROPATHS_CONTRACT: [u8; 20] = hex!("f241bEf0Ea64020655C70963ef81Fea333752367");
|
||||||
|
|
||||||
|
pub const SWEEP_SWAP_FN_SIG: [u8; 4] = hex!("7b370fc2");
|
||||||
|
|
||||||
|
pub const MINT_AMBIENT_FN_SIG: [u8; 4] = hex!("2ee11587");
|
||||||
|
pub const MINT_RANGE_FN_SIG: [u8; 4] = hex!("2370632b");
|
||||||
|
pub const BURN_AMBIENT_FN_SIG: [u8; 4] = hex!("2a6f0864");
|
||||||
|
pub const BURN_RANGE_FN_SIG: [u8; 4] = hex!("7c6dfe3d");
|
||||||
|
|
||||||
|
// ABI for the mintAmbient function with return values
|
||||||
|
pub const MINT_AMBIENT_RETURN_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Int(128), // int128 baseFlow
|
||||||
|
ParamType::Int(128), // int128 quoteFlow
|
||||||
|
ParamType::Uint(128), // uint128 seedOut
|
||||||
|
];
|
||||||
|
|
||||||
|
// ABI for the mintAmbient function parameters
|
||||||
|
const MINT_AMBIENT_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(128), // uint128 price
|
||||||
|
ParamType::Uint(128), // uint128 seed
|
||||||
|
ParamType::Uint(128), // uint128 conc
|
||||||
|
ParamType::Uint(64), // uint64 seedGrowth
|
||||||
|
ParamType::Uint(64), // uint64 concGrowth
|
||||||
|
ParamType::Uint(128), // uint128 liq
|
||||||
|
ParamType::FixedBytes(32), // bytes32 poolHash
|
||||||
|
];
|
||||||
|
|
||||||
|
// ABI for the burnRange function
|
||||||
|
const BURN_RANGE_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(128), // price
|
||||||
|
ParamType::Int(24), // priceTick
|
||||||
|
ParamType::Uint(128), // seed
|
||||||
|
ParamType::Uint(128), // conc
|
||||||
|
ParamType::Uint(64), // seedGrowth
|
||||||
|
ParamType::Uint(64), // concGrowth
|
||||||
|
ParamType::Int(24), // lowTick
|
||||||
|
ParamType::Int(24), // highTick
|
||||||
|
ParamType::Uint(128), // liq
|
||||||
|
ParamType::FixedBytes(32), // poolHash
|
||||||
|
];
|
||||||
|
|
||||||
|
const BURN_RANGE_RETURN_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Int(128), // baseFlow
|
||||||
|
ParamType::Int(128), // quoteFlow
|
||||||
|
ParamType::Uint(128), // seedOut
|
||||||
|
ParamType::Uint(128), // concOut
|
||||||
|
];
|
||||||
|
|
||||||
|
// ABI for the burnAmbient function with return values
|
||||||
|
const BURN_AMBIENT_RETURN_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Int(128), // int128 baseFlow
|
||||||
|
ParamType::Int(128), // int128 quoteFlow
|
||||||
|
ParamType::Uint(128), // uint128 seedOut
|
||||||
|
];
|
||||||
|
|
||||||
|
// ABI for the burnAmbient function
|
||||||
|
const BURN_AMBIENT_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(128), // uint128 price
|
||||||
|
ParamType::Uint(128), // uint128 seed
|
||||||
|
ParamType::Uint(128), // uint128 conc
|
||||||
|
ParamType::Uint(64), // uint64 seedGrowth
|
||||||
|
ParamType::Uint(64), // uint64 concGrowth
|
||||||
|
ParamType::Uint(128), // uint128 liq
|
||||||
|
ParamType::FixedBytes(32), // bytes32 poolHash
|
||||||
|
];
|
||||||
|
|
||||||
|
// ABI for the mintRange function parameters
|
||||||
|
const MINT_RANGE_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(128), // price
|
||||||
|
ParamType::Int(24), // priceTick
|
||||||
|
ParamType::Uint(128), // seed
|
||||||
|
ParamType::Uint(128), // conc
|
||||||
|
ParamType::Uint(64), // seedGrowth
|
||||||
|
ParamType::Uint(64), // concGrowth
|
||||||
|
ParamType::Int(24), // lowTick
|
||||||
|
ParamType::Int(24), // highTick
|
||||||
|
ParamType::Uint(128), // liq
|
||||||
|
ParamType::FixedBytes(32), // poolHash
|
||||||
|
];
|
||||||
|
|
||||||
|
// ABI for the mintRange function with return values
|
||||||
|
const MINT_RANGE_RETURN_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Int(128), // baseFlow
|
||||||
|
ParamType::Int(128), // quoteFlow
|
||||||
|
ParamType::Uint(128), // seedOut
|
||||||
|
ParamType::Uint(128), // concOut
|
||||||
|
];
|
||||||
|
pub fn decode_mint_range_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(mint_range) = decode(MINT_RANGE_ABI, &call.input[4..]) {
|
||||||
|
let pool_hash: [u8; 32] = mint_range[9]
|
||||||
|
.to_owned()
|
||||||
|
.into_fixed_bytes()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert pool hash to fixed bytes".to_string()))?
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Ok(external_outputs) = decode(MINT_RANGE_RETURN_ABI, &call.return_data) {
|
||||||
|
let base_flow = external_outputs[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_int() // Needs conversion into bytes for next step
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert base flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
let quote_flow = external_outputs[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_int() // Needs conversion into bytes for next step
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert quote flow to i128".to_string()))?;
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode swap call outputs.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode inputs for WarmPath userCmd call.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_burn_ambient_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(burn_ambient) = decode(BURN_AMBIENT_ABI, &call.input[4..]) {
|
||||||
|
let pool_hash: [u8; 32] = burn_ambient[6]
|
||||||
|
.to_owned()
|
||||||
|
.into_fixed_bytes()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert pool hash to bytes".to_string()))?
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Ok(external_outputs) = decode(BURN_AMBIENT_RETURN_ABI, &call.return_data) {
|
||||||
|
let base_flow = external_outputs[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_int()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert base flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
let quote_flow = external_outputs[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_int()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert quote flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode burnAmbient call outputs.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode inputs for burnAmbient call.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_mint_ambient_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(mint_ambient) = decode(MINT_AMBIENT_ABI, &call.input[4..]) {
|
||||||
|
let pool_hash: [u8; 32] = mint_ambient[6]
|
||||||
|
.to_owned()
|
||||||
|
.into_fixed_bytes()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert pool hash to bytes".to_string()))?
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Ok(external_outputs) = decode(MINT_AMBIENT_RETURN_ABI, &call.return_data) {
|
||||||
|
let base_flow = external_outputs[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_int()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert base flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
let quote_flow = external_outputs[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_int()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert quote flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode mintAmbient call outputs.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode inputs for mintAmbient call.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_burn_range_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(burn_range) = decode(BURN_RANGE_ABI, &call.input[4..]) {
|
||||||
|
let pool_hash: [u8; 32] = burn_range[9]
|
||||||
|
.to_owned()
|
||||||
|
.into_fixed_bytes() // Convert Bytes32 to Vec<u8>
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert pool hash to bytes".to_string()))?
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Ok(external_outputs) = decode(BURN_RANGE_RETURN_ABI, &call.return_data) {
|
||||||
|
let base_flow = external_outputs[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_int()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert base flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
let quote_flow = external_outputs[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_int()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert quote flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode burnRange call outputs.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode inputs for burnRange call.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_sweep_swap_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<([u8; 32], ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
let sweep_swap_abi: &[ParamType] = &[
|
||||||
|
ParamType::Tuple(vec![
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(64),
|
||||||
|
ParamType::Uint(64),
|
||||||
|
]), // CurveState
|
||||||
|
ParamType::Int(24), // midTick
|
||||||
|
ParamType::Tuple(vec![
|
||||||
|
ParamType::Bool,
|
||||||
|
ParamType::Bool,
|
||||||
|
ParamType::Uint(8),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
]), // SwapDirective
|
||||||
|
ParamType::Tuple(vec![
|
||||||
|
ParamType::Tuple(vec![
|
||||||
|
ParamType::Uint(8), // schema
|
||||||
|
ParamType::Uint(16), // feeRate
|
||||||
|
ParamType::Uint(8), // protocolTake
|
||||||
|
ParamType::Uint(16), // tickSize
|
||||||
|
ParamType::Uint(8), // jitThresh
|
||||||
|
ParamType::Uint(8), // knockoutBits
|
||||||
|
ParamType::Uint(8), // oracleFlags
|
||||||
|
]),
|
||||||
|
ParamType::FixedBytes(32), // poolHash
|
||||||
|
ParamType::Address,
|
||||||
|
]), // PoolCursor
|
||||||
|
];
|
||||||
|
let sweep_swap_abi_output: &[ParamType] = &[
|
||||||
|
ParamType::Tuple(vec![
|
||||||
|
ParamType::Int(128), // baseFlow
|
||||||
|
ParamType::Int(128), // quoteFlow
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
]), // Chaining.PairFlow memory accum
|
||||||
|
ParamType::Uint(128), // priceOut
|
||||||
|
ParamType::Uint(128), // seedOut
|
||||||
|
ParamType::Uint(128), // concOut
|
||||||
|
ParamType::Uint(64), // ambientOut
|
||||||
|
ParamType::Uint(64), // concGrowthOut
|
||||||
|
];
|
||||||
|
if let Ok(sweep_swap_input) = decode(sweep_swap_abi, &call.input[4..]) {
|
||||||
|
let pool_cursor = sweep_swap_input[3]
|
||||||
|
.to_owned()
|
||||||
|
.into_tuple()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert pool cursor to tuple for sweepSwap call".to_string())
|
||||||
|
})?;
|
||||||
|
let pool_hash: [u8; 32] = pool_cursor[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_fixed_bytes()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert pool hash to fixed bytes for sweepSwap call".to_string())
|
||||||
|
})?
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
if let Ok(sweep_swap_output) = decode(sweep_swap_abi_output, &call.return_data) {
|
||||||
|
let pair_flow = sweep_swap_output[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_tuple()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert pair flow to tuple for sweepSwap call".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let base_flow = pair_flow[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_int() // Needs conversion into bytes for next step
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert base flow to i128 for sweepSwap call".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let quote_flow = pair_flow[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_int() // Needs conversion into bytes for next step
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert quote flow to i128 for sweepSwap call".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((pool_hash, base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode sweepSwap outputs.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode sweepSwap inputs.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
16
substreams/ethereum-ambient/src/contracts/mod.rs
Normal file
16
substreams/ethereum-ambient/src/contracts/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// @generated
|
||||||
|
pub mod warmpath {
|
||||||
|
include!("warmpath.rs");
|
||||||
|
}
|
||||||
|
pub mod micropaths {
|
||||||
|
include!("micropaths.rs");
|
||||||
|
}
|
||||||
|
pub mod knockout {
|
||||||
|
include!("knockout.rs");
|
||||||
|
}
|
||||||
|
pub mod hotproxy {
|
||||||
|
include!("hotproxy.rs");
|
||||||
|
}
|
||||||
|
pub mod main {
|
||||||
|
include!("main.rs");
|
||||||
|
}
|
||||||
87
substreams/ethereum-ambient/src/contracts/warmpath.rs
Normal file
87
substreams/ethereum-ambient/src/contracts/warmpath.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
|
||||||
|
use crate::utils::{decode_flows_from_output, encode_pool_hash};
|
||||||
|
use ethabi::{decode, ParamType};
|
||||||
|
use hex_literal::hex;
|
||||||
|
use substreams_ethereum::pb::eth::v2::Call;
|
||||||
|
|
||||||
|
pub const AMBIENT_WARMPATH_CONTRACT: [u8; 20] = hex!("d268767BE4597151Ce2BB4a70A9E368ff26cB195");
|
||||||
|
pub const USER_CMD_WARMPATH_FN_SIG: [u8; 4] = hex!("f96dc788");
|
||||||
|
const USER_CMD_EXTERNAL_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Bytes, // userCmd
|
||||||
|
];
|
||||||
|
|
||||||
|
const LIQUIDITY_CHANGE_ABI: &[ParamType] = &[
|
||||||
|
ParamType::Uint(8),
|
||||||
|
ParamType::Address, // base
|
||||||
|
ParamType::Address, // quote
|
||||||
|
ParamType::Uint(256), // pool index
|
||||||
|
ParamType::Int(256),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(128),
|
||||||
|
ParamType::Uint(8),
|
||||||
|
ParamType::Address,
|
||||||
|
];
|
||||||
|
pub fn decode_warm_path_user_cmd_call(
|
||||||
|
call: &Call,
|
||||||
|
) -> Result<Option<([u8; 32], ethabi::Int, ethabi::Int)>, anyhow::Error> {
|
||||||
|
if let Ok(external_cmd) = decode(USER_CMD_EXTERNAL_ABI, &call.input[4..]) {
|
||||||
|
let input_bytes = external_cmd[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_bytes() // Convert Bytes32 to Vec<u8>
|
||||||
|
.ok_or_else(|| anyhow!("Failed to hotproxy userCmd input data.".to_string()))?;
|
||||||
|
|
||||||
|
let code = input_bytes[31];
|
||||||
|
let is_mint = [1, 11, 12, 3, 31, 32].contains(&code);
|
||||||
|
let is_burn = [2, 21, 22, 4, 41, 42].contains(&code);
|
||||||
|
let is_harvest = code == 5;
|
||||||
|
if is_mint || is_burn || is_harvest {
|
||||||
|
if let Ok(liquidity_change_calldata) = decode(LIQUIDITY_CHANGE_ABI, &input_bytes) {
|
||||||
|
let base_token = liquidity_change_calldata[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert base token to address for WarmPath userCmd call: {:?}",
|
||||||
|
&liquidity_change_calldata[1]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
let quote_token = liquidity_change_calldata[2]
|
||||||
|
.to_owned()
|
||||||
|
.into_address()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Failed to convert quote token to address for WarmPath userCmd call: {:?}",
|
||||||
|
&liquidity_change_calldata[2]
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.to_fixed_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
|
let mut pool_index_buf = [0u8; 32];
|
||||||
|
liquidity_change_calldata[3]
|
||||||
|
.to_owned()
|
||||||
|
.into_uint()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!("Failed to convert pool index to bytes for WarmPath userCmd call"
|
||||||
|
.to_string())
|
||||||
|
})?
|
||||||
|
.to_big_endian(&mut pool_index_buf);
|
||||||
|
let pool_index = pool_index_buf.to_vec();
|
||||||
|
|
||||||
|
let (base_flow, quote_flow) = decode_flows_from_output(call)?;
|
||||||
|
let pool_hash = encode_pool_hash(base_token, quote_token, pool_index);
|
||||||
|
Ok(Some((pool_hash, base_flow, quote_flow)))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode inputs for WarmPath userCmd call.".to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode WarmPath call external input.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,385 +1,5 @@
|
|||||||
use std::collections::{hash_map::Entry, HashMap};
|
mod contracts;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail};
|
pub use modules::*;
|
||||||
use ethabi::{decode, ParamType};
|
mod modules;
|
||||||
use hex_literal::hex;
|
mod utils;
|
||||||
use substreams_ethereum::pb::eth::{self};
|
|
||||||
|
|
||||||
use pb::tycho::evm::v1::{self as tycho};
|
|
||||||
|
|
||||||
mod pb;
|
|
||||||
|
|
||||||
const AMBIENT_CONTRACT: [u8; 20] = hex!("aaaaaaaaa24eeeb8d57d431224f73832bc34f688");
|
|
||||||
const INIT_POOL_CODE: u8 = 71;
|
|
||||||
const USER_CMD_FN_SIG: [u8; 4] = [0xA1, 0x51, 0x12, 0xF9];
|
|
||||||
|
|
||||||
struct SlotValue {
|
|
||||||
new_value: Vec<u8>,
|
|
||||||
start_value: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SlotValue {
|
|
||||||
fn has_changed(&self) -> bool {
|
|
||||||
self.start_value != self.new_value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uses a map for slots, protobuf does not allow bytes in hashmap keys
|
|
||||||
struct InterimContractChange {
|
|
||||||
address: Vec<u8>,
|
|
||||||
balance: Vec<u8>,
|
|
||||||
code: Vec<u8>,
|
|
||||||
slots: HashMap<Vec<u8>, SlotValue>,
|
|
||||||
change: tycho::ChangeType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<InterimContractChange> for tycho::ContractChange {
|
|
||||||
fn from(value: InterimContractChange) -> Self {
|
|
||||||
tycho::ContractChange {
|
|
||||||
address: value.address,
|
|
||||||
balance: value.balance,
|
|
||||||
code: value.code,
|
|
||||||
slots: value
|
|
||||||
.slots
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(_, value)| value.has_changed())
|
|
||||||
.map(|(slot, value)| tycho::ContractSlot { slot, value: value.new_value })
|
|
||||||
.collect(),
|
|
||||||
change: value.change.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts all contract changes relevant to vm simulations
|
|
||||||
///
|
|
||||||
/// This is the main logic of the substreams integration. It takes a raw ethereum block on input and extracts the BlockContractChanges stream. It includes tracking:
|
|
||||||
/// - new pool initializations
|
|
||||||
/// - all storage slot changes for the Ambient contract
|
|
||||||
/// - all ERC20 balance changes for the Ambient pools
|
|
||||||
/// - all code changes and balance updates of the Ambient contract
|
|
||||||
///
|
|
||||||
/// Generally we detect all changes in transactions sequentially and detect if it is a CREATE or UPDATE change based on already present data.
|
|
||||||
#[substreams::handlers::map]
|
|
||||||
fn map_changes(
|
|
||||||
block: eth::v2::Block,
|
|
||||||
) -> Result<tycho::BlockContractChanges, substreams::errors::Error> {
|
|
||||||
let mut block_changes = tycho::BlockContractChanges::default();
|
|
||||||
|
|
||||||
let mut tx_change = tycho::TransactionContractChanges::default();
|
|
||||||
|
|
||||||
let mut changed_contracts: HashMap<Vec<u8>, InterimContractChange> = HashMap::new();
|
|
||||||
|
|
||||||
// Collect all accounts created in this block
|
|
||||||
let created_accounts: HashMap<_, _> = block
|
|
||||||
.transactions()
|
|
||||||
.flat_map(|tx| {
|
|
||||||
tx.calls.iter().flat_map(|call| {
|
|
||||||
call.account_creations
|
|
||||||
.iter()
|
|
||||||
.map(|ac| (&ac.account, ac.ordinal))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for block_tx in block.transactions() {
|
|
||||||
// Extract storage changes for all contracts relevant to this protocol system.
|
|
||||||
// Ambient is a protocol system consisting of many ProtocolComponents (one for each pool), but they all share the same AMBIENT_CONTRACT contract.
|
|
||||||
let mut storage_changes = block_tx
|
|
||||||
.calls
|
|
||||||
.iter()
|
|
||||||
.filter(|call| !call.state_reverted)
|
|
||||||
.flat_map(|call| {
|
|
||||||
call.storage_changes
|
|
||||||
.iter()
|
|
||||||
.filter(|c| c.address == AMBIENT_CONTRACT)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
storage_changes.sort_unstable_by_key(|change| change.ordinal);
|
|
||||||
|
|
||||||
// Detect all call to the Ambient contracts, even inner calls
|
|
||||||
let ambient_calls = block_tx
|
|
||||||
.calls
|
|
||||||
.iter()
|
|
||||||
.filter(|call| !call.state_reverted)
|
|
||||||
.filter(|call| call.address == AMBIENT_CONTRACT)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Detect all pool initializations
|
|
||||||
// Official documentation: https://docs.ambient.finance/developers/dex-contract-interface/pool-initialization
|
|
||||||
for call in ambient_calls {
|
|
||||||
if call.input.len() < 4 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if call.input[0..4] == USER_CMD_FN_SIG {
|
|
||||||
let user_cmd_external_abi_types = &[
|
|
||||||
// index of the proxy sidecar the command is being called on
|
|
||||||
ParamType::Uint(16),
|
|
||||||
// call data for internal UserCmd method
|
|
||||||
ParamType::Bytes,
|
|
||||||
];
|
|
||||||
let user_cmd_internal_abi_types = &[
|
|
||||||
ParamType::Uint(8), // command
|
|
||||||
ParamType::Address, // base
|
|
||||||
ParamType::Address, // quote
|
|
||||||
ParamType::Uint(256), // pool index
|
|
||||||
ParamType::Uint(128), // price
|
|
||||||
];
|
|
||||||
|
|
||||||
// Decode external call to UserCmd
|
|
||||||
if let Ok(external_params) = decode(user_cmd_external_abi_types, &call.input[4..]) {
|
|
||||||
let cmd_bytes = external_params[1]
|
|
||||||
.to_owned()
|
|
||||||
.into_bytes()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
anyhow!("Failed to convert to bytes: {:?}", &external_params[1])
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Call data is structured differently depending on the cmd code, so only
|
|
||||||
// decode if this is an init pool code.
|
|
||||||
if cmd_bytes[31] == INIT_POOL_CODE {
|
|
||||||
// Decode internal call to UserCmd
|
|
||||||
if let Ok(internal_params) = decode(user_cmd_internal_abi_types, &cmd_bytes)
|
|
||||||
{
|
|
||||||
let base = internal_params[1]
|
|
||||||
.to_owned()
|
|
||||||
.into_address()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
anyhow!(
|
|
||||||
"Failed to convert to address: {:?}",
|
|
||||||
&internal_params[1]
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_fixed_bytes()
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
let quote = internal_params[2]
|
|
||||||
.to_owned()
|
|
||||||
.into_address()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
anyhow!(
|
|
||||||
"Failed to convert to address: {:?}",
|
|
||||||
&internal_params[2]
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_fixed_bytes()
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
let pool_index = internal_params[3]
|
|
||||||
.to_owned()
|
|
||||||
.into_uint()
|
|
||||||
.ok_or_else(|| anyhow!("Failed to convert to u32".to_string()))?
|
|
||||||
.as_u32();
|
|
||||||
|
|
||||||
let static_attribute = tycho::Attribute {
|
|
||||||
name: String::from("pool_index"),
|
|
||||||
value: pool_index.to_be_bytes().to_vec(),
|
|
||||||
change: tycho::ChangeType::Creation.into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tokens: Vec<Vec<u8>> = vec![base.clone(), quote.clone()];
|
|
||||||
tokens.sort();
|
|
||||||
|
|
||||||
let new_component = tycho::ProtocolComponent {
|
|
||||||
id: format!(
|
|
||||||
"{}{}{}",
|
|
||||||
hex::encode(base.clone()),
|
|
||||||
hex::encode(quote.clone()),
|
|
||||||
pool_index
|
|
||||||
),
|
|
||||||
tokens,
|
|
||||||
contracts: vec![AMBIENT_CONTRACT.to_vec()],
|
|
||||||
static_att: vec![static_attribute],
|
|
||||||
change: tycho::ChangeType::Creation.into(),
|
|
||||||
};
|
|
||||||
tx_change
|
|
||||||
.component_changes
|
|
||||||
.push(new_component);
|
|
||||||
} else {
|
|
||||||
bail!("Failed to decode ABI internal call.".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!("Failed to decode ABI external call.".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract all contract changes.
|
|
||||||
// We cache the data in a general interim contract > slot > value data structure.
|
|
||||||
// Note: some contracts change slot values and change them back to their
|
|
||||||
// original value before the transactions ends we remember the initial
|
|
||||||
// value before the first change and in the end filter found deltas
|
|
||||||
// that ended up not actually changing anything.
|
|
||||||
for storage_change in storage_changes.iter() {
|
|
||||||
match changed_contracts.entry(storage_change.address.clone()) {
|
|
||||||
// We have already an entry recording a change about this contract
|
|
||||||
// only append the change about this storage slot
|
|
||||||
Entry::Occupied(mut e) => {
|
|
||||||
let contract_change = e.get_mut();
|
|
||||||
match contract_change
|
|
||||||
.slots
|
|
||||||
.entry(storage_change.key.clone())
|
|
||||||
{
|
|
||||||
// The storage slot was already changed before, simply
|
|
||||||
// update new_value
|
|
||||||
Entry::Occupied(mut v) => {
|
|
||||||
let slot_value = v.get_mut();
|
|
||||||
slot_value
|
|
||||||
.new_value
|
|
||||||
.copy_from_slice(&storage_change.new_value);
|
|
||||||
}
|
|
||||||
// The storage slots is being initialised for the first time
|
|
||||||
Entry::Vacant(v) => {
|
|
||||||
v.insert(SlotValue {
|
|
||||||
new_value: storage_change.new_value.clone(),
|
|
||||||
start_value: storage_change.old_value.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Intialise a new contract change after observing a storage change
|
|
||||||
Entry::Vacant(e) => {
|
|
||||||
let mut slots = HashMap::new();
|
|
||||||
slots.insert(
|
|
||||||
storage_change.key.clone(),
|
|
||||||
SlotValue {
|
|
||||||
new_value: storage_change.new_value.clone(),
|
|
||||||
start_value: storage_change.old_value.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
e.insert(InterimContractChange {
|
|
||||||
address: storage_change.address.clone(),
|
|
||||||
balance: Vec::new(),
|
|
||||||
code: Vec::new(),
|
|
||||||
slots,
|
|
||||||
change: if created_accounts.contains_key(&storage_change.address) {
|
|
||||||
tycho::ChangeType::Creation
|
|
||||||
} else {
|
|
||||||
tycho::ChangeType::Update
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract balance changes
|
|
||||||
let mut balance_changes = block_tx
|
|
||||||
.calls
|
|
||||||
.iter()
|
|
||||||
.filter(|call| !call.state_reverted)
|
|
||||||
.flat_map(|call| {
|
|
||||||
call.balance_changes
|
|
||||||
.iter()
|
|
||||||
.filter(|c| c.address == AMBIENT_CONTRACT)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
balance_changes.sort_unstable_by_key(|change| change.ordinal);
|
|
||||||
|
|
||||||
for balance_change in balance_changes.iter() {
|
|
||||||
match changed_contracts.entry(balance_change.address.clone()) {
|
|
||||||
Entry::Occupied(mut e) => {
|
|
||||||
let contract_change = e.get_mut();
|
|
||||||
if let Some(new_balance) = &balance_change.new_value {
|
|
||||||
contract_change.balance.clear();
|
|
||||||
contract_change
|
|
||||||
.balance
|
|
||||||
.extend_from_slice(&new_balance.bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Entry::Vacant(e) => {
|
|
||||||
if let Some(new_balance) = &balance_change.new_value {
|
|
||||||
e.insert(InterimContractChange {
|
|
||||||
address: balance_change.address.clone(),
|
|
||||||
balance: new_balance.bytes.clone(),
|
|
||||||
code: Vec::new(),
|
|
||||||
slots: HashMap::new(),
|
|
||||||
change: if created_accounts.contains_key(&balance_change.address) {
|
|
||||||
tycho::ChangeType::Creation
|
|
||||||
} else {
|
|
||||||
tycho::ChangeType::Update
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract code changes
|
|
||||||
let mut code_changes = block_tx
|
|
||||||
.calls
|
|
||||||
.iter()
|
|
||||||
.filter(|call| !call.state_reverted)
|
|
||||||
.flat_map(|call| {
|
|
||||||
call.code_changes
|
|
||||||
.iter()
|
|
||||||
.filter(|c| c.address == AMBIENT_CONTRACT)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
code_changes.sort_unstable_by_key(|change| change.ordinal);
|
|
||||||
|
|
||||||
for code_change in code_changes.iter() {
|
|
||||||
match changed_contracts.entry(code_change.address.clone()) {
|
|
||||||
Entry::Occupied(mut e) => {
|
|
||||||
let contract_change = e.get_mut();
|
|
||||||
contract_change.code.clear();
|
|
||||||
contract_change
|
|
||||||
.code
|
|
||||||
.extend_from_slice(&code_change.new_code);
|
|
||||||
}
|
|
||||||
Entry::Vacant(e) => {
|
|
||||||
e.insert(InterimContractChange {
|
|
||||||
address: code_change.address.clone(),
|
|
||||||
balance: Vec::new(),
|
|
||||||
code: code_change.new_code.clone(),
|
|
||||||
slots: HashMap::new(),
|
|
||||||
change: if created_accounts.contains_key(&code_change.address) {
|
|
||||||
tycho::ChangeType::Creation
|
|
||||||
} else {
|
|
||||||
tycho::ChangeType::Update
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there were any changes, add transaction and push the changes
|
|
||||||
if !storage_changes.is_empty() || !balance_changes.is_empty() || !code_changes.is_empty() {
|
|
||||||
tx_change.tx = Some(tycho::Transaction {
|
|
||||||
hash: block_tx.hash.clone(),
|
|
||||||
from: block_tx.from.clone(),
|
|
||||||
to: block_tx.to.clone(),
|
|
||||||
index: block_tx.index as u64,
|
|
||||||
});
|
|
||||||
|
|
||||||
// reuse changed_contracts hash map by draining it, next iteration
|
|
||||||
// will start empty. This avoids a costly reallocation
|
|
||||||
for (_, change) in changed_contracts.drain() {
|
|
||||||
tx_change
|
|
||||||
.contract_changes
|
|
||||||
.push(change.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
block_changes
|
|
||||||
.changes
|
|
||||||
.push(tx_change.clone());
|
|
||||||
|
|
||||||
// clear out the interim contract changes after we pushed those.
|
|
||||||
tx_change.tx = None;
|
|
||||||
tx_change.contract_changes.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
block_changes.block = Some(tycho::Block {
|
|
||||||
number: block.number,
|
|
||||||
hash: block.hash.clone(),
|
|
||||||
parent_hash: block
|
|
||||||
.header
|
|
||||||
.as_ref()
|
|
||||||
.expect("Block header not present")
|
|
||||||
.parent_hash
|
|
||||||
.clone(),
|
|
||||||
ts: block.timestamp_seconds(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(block_changes)
|
|
||||||
}
|
|
||||||
|
|||||||
127
substreams/ethereum-ambient/src/modules/1_map_pool_changes.rs
Normal file
127
substreams/ethereum-ambient/src/modules/1_map_pool_changes.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use substreams_ethereum::pb::eth::{self};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
contracts::{
|
||||||
|
hotproxy::{
|
||||||
|
decode_direct_swap_hotproxy_call, AMBIENT_HOTPROXY_CONTRACT, USER_CMD_HOTPROXY_FN_SIG,
|
||||||
|
},
|
||||||
|
knockout::{decode_knockout_call, AMBIENT_KNOCKOUT_CONTRACT, USER_CMD_KNOCKOUT_FN_SIG},
|
||||||
|
main::{
|
||||||
|
decode_direct_swap_call, decode_pool_init, AMBIENT_CONTRACT, SWAP_FN_SIG,
|
||||||
|
USER_CMD_FN_SIG,
|
||||||
|
},
|
||||||
|
micropaths::{
|
||||||
|
decode_burn_ambient_call, decode_burn_range_call, decode_mint_ambient_call,
|
||||||
|
decode_mint_range_call, decode_sweep_swap_call, AMBIENT_MICROPATHS_CONTRACT,
|
||||||
|
BURN_AMBIENT_FN_SIG, BURN_RANGE_FN_SIG, MINT_AMBIENT_FN_SIG, MINT_RANGE_FN_SIG,
|
||||||
|
SWEEP_SWAP_FN_SIG,
|
||||||
|
},
|
||||||
|
warmpath::{
|
||||||
|
decode_warm_path_user_cmd_call, AMBIENT_WARMPATH_CONTRACT, USER_CMD_WARMPATH_FN_SIG,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
utils::from_u256_to_vec,
|
||||||
|
};
|
||||||
|
use tycho_substreams::{
|
||||||
|
models::{AmbientBalanceDelta, BlockPoolChanges},
|
||||||
|
prelude::Transaction,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
fn map_pool_changes(block: eth::v2::Block) -> Result<BlockPoolChanges, substreams::errors::Error> {
|
||||||
|
let mut protocol_components = Vec::new();
|
||||||
|
let mut balance_deltas = Vec::new();
|
||||||
|
for block_tx in block.transactions() {
|
||||||
|
let tx = Transaction {
|
||||||
|
hash: block_tx.hash.clone(),
|
||||||
|
from: block_tx.from.clone(),
|
||||||
|
to: block_tx.to.clone(),
|
||||||
|
index: block_tx.index as u64,
|
||||||
|
};
|
||||||
|
// extract storage changes
|
||||||
|
let mut storage_changes = block_tx
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.filter(|call| !call.state_reverted)
|
||||||
|
.flat_map(|call| {
|
||||||
|
call.storage_changes
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.address == AMBIENT_CONTRACT)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
storage_changes.sort_unstable_by_key(|change| change.ordinal);
|
||||||
|
|
||||||
|
let block_calls = block_tx
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.filter(|call| !call.state_reverted)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for call in block_calls {
|
||||||
|
if call.input.len() < 4 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let selector: [u8; 4] = call.input[0..4].try_into().unwrap();
|
||||||
|
let address: [u8; 20] = call.address.clone().try_into().unwrap();
|
||||||
|
|
||||||
|
if call.address == AMBIENT_CONTRACT && selector == USER_CMD_FN_SIG {
|
||||||
|
// Extract pool creations
|
||||||
|
if let Some(protocol_component) = decode_pool_init(call, tx.clone())? {
|
||||||
|
protocol_components.push(protocol_component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract TVL changes
|
||||||
|
let result = match (address, selector) {
|
||||||
|
(AMBIENT_CONTRACT, SWAP_FN_SIG) => Some(decode_direct_swap_call(call)?),
|
||||||
|
(AMBIENT_HOTPROXY_CONTRACT, USER_CMD_HOTPROXY_FN_SIG) => {
|
||||||
|
Some(decode_direct_swap_hotproxy_call(call)?)
|
||||||
|
}
|
||||||
|
(AMBIENT_MICROPATHS_CONTRACT, SWEEP_SWAP_FN_SIG) => {
|
||||||
|
Some(decode_sweep_swap_call(call)?)
|
||||||
|
}
|
||||||
|
(AMBIENT_WARMPATH_CONTRACT, USER_CMD_WARMPATH_FN_SIG) => {
|
||||||
|
decode_warm_path_user_cmd_call(call)?
|
||||||
|
}
|
||||||
|
(AMBIENT_MICROPATHS_CONTRACT, MINT_RANGE_FN_SIG) => {
|
||||||
|
Some(decode_mint_range_call(call)?)
|
||||||
|
}
|
||||||
|
(AMBIENT_MICROPATHS_CONTRACT, MINT_AMBIENT_FN_SIG) => {
|
||||||
|
Some(decode_mint_ambient_call(call)?)
|
||||||
|
}
|
||||||
|
(AMBIENT_MICROPATHS_CONTRACT, BURN_RANGE_FN_SIG) => {
|
||||||
|
Some(decode_burn_range_call(call)?)
|
||||||
|
}
|
||||||
|
(AMBIENT_MICROPATHS_CONTRACT, BURN_AMBIENT_FN_SIG) => {
|
||||||
|
Some(decode_burn_ambient_call(call)?)
|
||||||
|
}
|
||||||
|
(AMBIENT_KNOCKOUT_CONTRACT, USER_CMD_KNOCKOUT_FN_SIG) => {
|
||||||
|
Some(decode_knockout_call(call)?)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let (pool_hash, base_flow, quote_flow) = match result {
|
||||||
|
Some((pool_hash, base_flow, quote_flow)) => (pool_hash, base_flow, quote_flow),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
let base_balance_delta = AmbientBalanceDelta {
|
||||||
|
pool_hash: Vec::from(pool_hash),
|
||||||
|
token_type: "base".to_string(),
|
||||||
|
token_delta: from_u256_to_vec(base_flow),
|
||||||
|
ordinal: call.index as u64,
|
||||||
|
tx: Some(tx.clone()),
|
||||||
|
};
|
||||||
|
let quote_balance_delta = AmbientBalanceDelta {
|
||||||
|
pool_hash: Vec::from(pool_hash),
|
||||||
|
token_type: "quote".to_string(),
|
||||||
|
token_delta: from_u256_to_vec(quote_flow),
|
||||||
|
ordinal: call.index as u64,
|
||||||
|
tx: Some(tx.clone()),
|
||||||
|
};
|
||||||
|
balance_deltas.extend([base_balance_delta.clone(), quote_balance_delta.clone()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
balance_deltas.sort_by_key(|delta| (delta.ordinal, delta.token_type.clone()));
|
||||||
|
let pool_changes = BlockPoolChanges { protocol_components, balance_deltas };
|
||||||
|
Ok(pool_changes)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
use substreams::{
|
||||||
|
scalar::BigInt,
|
||||||
|
store::{StoreAdd, StoreAddBigInt, StoreNew},
|
||||||
|
};
|
||||||
|
use tycho_substreams::models::BlockPoolChanges;
|
||||||
|
|
||||||
|
#[substreams::handlers::store]
|
||||||
|
pub fn store_pool_balances(changes: BlockPoolChanges, balance_store: StoreAddBigInt) {
|
||||||
|
let deltas = changes.balance_deltas.clone();
|
||||||
|
for balance_delta in deltas {
|
||||||
|
let pool_hash_hex = hex::encode(&balance_delta.pool_hash);
|
||||||
|
balance_store.add(
|
||||||
|
balance_delta.ordinal,
|
||||||
|
format!("{}:{}", pool_hash_hex, balance_delta.token_type),
|
||||||
|
BigInt::from_signed_bytes_be(&balance_delta.token_delta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
substreams/ethereum-ambient/src/modules/2_store_pools.rs
Normal file
9
substreams/ethereum-ambient/src/modules/2_store_pools.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use substreams::store::{StoreNew, StoreSet, StoreSetProto};
|
||||||
|
use tycho_substreams::models::{BlockPoolChanges, ProtocolComponent};
|
||||||
|
|
||||||
|
#[substreams::handlers::store]
|
||||||
|
pub fn store_pools(changes: BlockPoolChanges, component_store: StoreSetProto<ProtocolComponent>) {
|
||||||
|
for component in changes.protocol_components {
|
||||||
|
component_store.set(0, component.id.clone(), &component);
|
||||||
|
}
|
||||||
|
}
|
||||||
367
substreams/ethereum-ambient/src/modules/3_map_changes.rs
Normal file
367
substreams/ethereum-ambient/src/modules/3_map_changes.rs
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
use num_bigint::BigInt;
|
||||||
|
use std::{
|
||||||
|
collections::{hash_map::Entry, HashMap},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use substreams::pb::substreams::StoreDeltas;
|
||||||
|
|
||||||
|
use substreams_ethereum::pb::eth::{self};
|
||||||
|
|
||||||
|
use crate::contracts::main::AMBIENT_CONTRACT;
|
||||||
|
use substreams::store::{StoreGet, StoreGetProto};
|
||||||
|
use tycho_substreams::prelude::*;
|
||||||
|
|
||||||
|
struct SlotValue {
|
||||||
|
new_value: Vec<u8>,
|
||||||
|
start_value: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SlotValue {
|
||||||
|
fn has_changed(&self) -> bool {
|
||||||
|
self.start_value != self.new_value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// uses a map for slots, protobuf does not
|
||||||
|
// allow bytes in hashmap keys
|
||||||
|
struct InterimContractChange {
|
||||||
|
address: Vec<u8>,
|
||||||
|
balance: Vec<u8>,
|
||||||
|
code: Vec<u8>,
|
||||||
|
slots: HashMap<Vec<u8>, SlotValue>,
|
||||||
|
change: ChangeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InterimContractChange> for ContractChange {
|
||||||
|
fn from(value: InterimContractChange) -> Self {
|
||||||
|
ContractChange {
|
||||||
|
address: value.address,
|
||||||
|
balance: value.balance,
|
||||||
|
code: value.code,
|
||||||
|
slots: value
|
||||||
|
.slots
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(_, value)| value.has_changed())
|
||||||
|
.map(|(slot, value)| ContractSlot { slot, value: value.new_value })
|
||||||
|
.collect(),
|
||||||
|
change: value.change.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts all contract changes relevant to vm simulations
|
||||||
|
///
|
||||||
|
/// This implementation has currently two major limitations:
|
||||||
|
/// 1. It is hardwired to only care about changes to the ambient main contract, this is ok for this
|
||||||
|
/// particular use case but for a more general purpose implementation this is not ideal
|
||||||
|
/// 2. Changes are processed separately, this means that if there are any side effects between each
|
||||||
|
/// other (e.g. if account is deleted and then created again in ethereum all the storage is set
|
||||||
|
/// to 0. So there is a side effect between account creation and contract storage.) these might
|
||||||
|
/// not be properly accounted for. Most of the time this should not be a major issue but may lead
|
||||||
|
/// to wrong results so consume this implementation with care. See example below for a concrete
|
||||||
|
/// case where this is problematic.
|
||||||
|
///
|
||||||
|
/// ## A very contrived example:
|
||||||
|
/// 1. Some existing contract receives a transaction that changes it state, the state is updated
|
||||||
|
/// 2. Next, this contract has self destruct called on itself
|
||||||
|
/// 3. The contract is created again using CREATE2 at the same address
|
||||||
|
/// 4. The contract receives a transaction that changes it state
|
||||||
|
/// 5. We would emit this as as contract creation with slots set from 1 and from 4, although we
|
||||||
|
/// should only emit the slots changed from 4.
|
||||||
|
#[substreams::handlers::map]
|
||||||
|
fn map_changes(
|
||||||
|
block: eth::v2::Block,
|
||||||
|
block_pool_changes: BlockPoolChanges,
|
||||||
|
balance_store: StoreDeltas,
|
||||||
|
pool_store: StoreGetProto<ProtocolComponent>,
|
||||||
|
) -> Result<BlockContractChanges, substreams::errors::Error> {
|
||||||
|
let mut block_changes = BlockContractChanges::default();
|
||||||
|
|
||||||
|
let mut tx_change = TransactionContractChanges::default();
|
||||||
|
|
||||||
|
let mut changed_contracts: HashMap<Vec<u8>, InterimContractChange> = HashMap::new();
|
||||||
|
|
||||||
|
let created_accounts: HashMap<_, _> = block
|
||||||
|
.transactions()
|
||||||
|
.flat_map(|tx| {
|
||||||
|
tx.calls.iter().flat_map(|call| {
|
||||||
|
call.account_creations
|
||||||
|
.iter()
|
||||||
|
.map(|ac| (&ac.account, ac.ordinal))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for block_tx in block.transactions() {
|
||||||
|
// extract storage changes
|
||||||
|
let mut storage_changes = block_tx
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.filter(|call| !call.state_reverted)
|
||||||
|
.flat_map(|call| {
|
||||||
|
call.storage_changes
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.address == AMBIENT_CONTRACT)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
storage_changes.sort_unstable_by_key(|change| change.ordinal);
|
||||||
|
|
||||||
|
// Note: some contracts change slot values and change them back to their
|
||||||
|
// original value before the transactions ends we remember the initial
|
||||||
|
// value before the first change and in the end filter found deltas
|
||||||
|
// that ended up not actually changing anything.
|
||||||
|
for storage_change in storage_changes.iter() {
|
||||||
|
match changed_contracts.entry(storage_change.address.clone()) {
|
||||||
|
// We have already an entry recording a change about this contract
|
||||||
|
// only append the change about this storage slot
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
let contract_change = e.get_mut();
|
||||||
|
match contract_change
|
||||||
|
.slots
|
||||||
|
.entry(storage_change.key.clone())
|
||||||
|
{
|
||||||
|
// The storage slot was already changed before, simply
|
||||||
|
// update new_value
|
||||||
|
Entry::Occupied(mut v) => {
|
||||||
|
let slot_value = v.get_mut();
|
||||||
|
slot_value
|
||||||
|
.new_value
|
||||||
|
.copy_from_slice(&storage_change.new_value);
|
||||||
|
}
|
||||||
|
// The storage slots is being initialised for the first time
|
||||||
|
Entry::Vacant(v) => {
|
||||||
|
v.insert(SlotValue {
|
||||||
|
new_value: storage_change.new_value.clone(),
|
||||||
|
start_value: storage_change.old_value.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Intialise a new contract change after observing a storage change
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
let mut slots = HashMap::new();
|
||||||
|
slots.insert(
|
||||||
|
storage_change.key.clone(),
|
||||||
|
SlotValue {
|
||||||
|
new_value: storage_change.new_value.clone(),
|
||||||
|
start_value: storage_change.old_value.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
e.insert(InterimContractChange {
|
||||||
|
address: storage_change.address.clone(),
|
||||||
|
balance: Vec::new(),
|
||||||
|
code: Vec::new(),
|
||||||
|
slots,
|
||||||
|
change: if created_accounts.contains_key(&storage_change.address) {
|
||||||
|
ChangeType::Creation
|
||||||
|
} else {
|
||||||
|
ChangeType::Update
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract balance changes
|
||||||
|
let mut balance_changes = block_tx
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.filter(|call| !call.state_reverted)
|
||||||
|
.flat_map(|call| {
|
||||||
|
call.balance_changes
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.address == AMBIENT_CONTRACT)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
balance_changes.sort_unstable_by_key(|change| change.ordinal);
|
||||||
|
|
||||||
|
for balance_change in balance_changes.iter() {
|
||||||
|
match changed_contracts.entry(balance_change.address.clone()) {
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
let contract_change = e.get_mut();
|
||||||
|
if let Some(new_balance) = &balance_change.new_value {
|
||||||
|
contract_change.balance.clear();
|
||||||
|
contract_change
|
||||||
|
.balance
|
||||||
|
.extend_from_slice(&new_balance.bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
if let Some(new_balance) = &balance_change.new_value {
|
||||||
|
e.insert(InterimContractChange {
|
||||||
|
address: balance_change.address.clone(),
|
||||||
|
balance: new_balance.bytes.clone(),
|
||||||
|
code: Vec::new(),
|
||||||
|
slots: HashMap::new(),
|
||||||
|
change: if created_accounts.contains_key(&balance_change.address) {
|
||||||
|
ChangeType::Creation
|
||||||
|
} else {
|
||||||
|
ChangeType::Update
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract code changes
|
||||||
|
let mut code_changes = block_tx
|
||||||
|
.calls
|
||||||
|
.iter()
|
||||||
|
.filter(|call| !call.state_reverted)
|
||||||
|
.flat_map(|call| {
|
||||||
|
call.code_changes
|
||||||
|
.iter()
|
||||||
|
.filter(|c| c.address == AMBIENT_CONTRACT)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
code_changes.sort_unstable_by_key(|change| change.ordinal);
|
||||||
|
|
||||||
|
for code_change in code_changes.iter() {
|
||||||
|
match changed_contracts.entry(code_change.address.clone()) {
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
let contract_change = e.get_mut();
|
||||||
|
contract_change.code.clear();
|
||||||
|
contract_change
|
||||||
|
.code
|
||||||
|
.extend_from_slice(&code_change.new_code);
|
||||||
|
}
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
e.insert(InterimContractChange {
|
||||||
|
address: code_change.address.clone(),
|
||||||
|
balance: Vec::new(),
|
||||||
|
code: code_change.new_code.clone(),
|
||||||
|
slots: HashMap::new(),
|
||||||
|
change: if created_accounts.contains_key(&code_change.address) {
|
||||||
|
ChangeType::Creation
|
||||||
|
} else {
|
||||||
|
ChangeType::Update
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there were any changes, add transaction and push the changes
|
||||||
|
if !storage_changes.is_empty() || !balance_changes.is_empty() || !code_changes.is_empty() {
|
||||||
|
tx_change.tx = Some(Transaction {
|
||||||
|
hash: block_tx.hash.clone(),
|
||||||
|
from: block_tx.from.clone(),
|
||||||
|
to: block_tx.to.clone(),
|
||||||
|
index: block_tx.index as u64,
|
||||||
|
});
|
||||||
|
|
||||||
|
// reuse changed_contracts hash map by draining it, next iteration
|
||||||
|
// will start empty. This avoids a costly reallocation
|
||||||
|
for (_, change) in changed_contracts.drain() {
|
||||||
|
tx_change
|
||||||
|
.contract_changes
|
||||||
|
.push(change.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
block_changes
|
||||||
|
.changes
|
||||||
|
.push(tx_change.clone());
|
||||||
|
|
||||||
|
// clear out the interim contract changes after we pushed those.
|
||||||
|
tx_change.tx = None;
|
||||||
|
tx_change.contract_changes.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut grouped_components = HashMap::new();
|
||||||
|
for component in &block_pool_changes.protocol_components {
|
||||||
|
let tx_hash = component
|
||||||
|
.tx
|
||||||
|
.clone()
|
||||||
|
.expect("Transaction is missing")
|
||||||
|
.hash;
|
||||||
|
grouped_components
|
||||||
|
.entry(tx_hash)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(component.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tx_hash, components) in grouped_components {
|
||||||
|
if let Some(tx_change) = block_changes
|
||||||
|
.changes
|
||||||
|
.iter_mut()
|
||||||
|
// TODO: be better than this (quadratic complexity)
|
||||||
|
.find(|tx_change| {
|
||||||
|
tx_change
|
||||||
|
.tx
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |tx| tx.hash == tx_hash)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
tx_change
|
||||||
|
.component_changes
|
||||||
|
.extend(components);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut balance_changes = HashMap::new();
|
||||||
|
balance_store
|
||||||
|
.deltas
|
||||||
|
.into_iter()
|
||||||
|
.zip(block_pool_changes.balance_deltas)
|
||||||
|
.for_each(|(store_delta, balance_delta)| {
|
||||||
|
let pool_hash_hex = hex::encode(balance_delta.pool_hash);
|
||||||
|
let pool = match pool_store.get_last(pool_hash_hex.clone()) {
|
||||||
|
Some(pool) => pool,
|
||||||
|
None => panic!("Pool not found in store for given hash: {}", pool_hash_hex),
|
||||||
|
};
|
||||||
|
let token_type = substreams::key::segment_at(&store_delta.key, 1);
|
||||||
|
let token_index = if token_type == "quote" { 1 } else { 0 };
|
||||||
|
|
||||||
|
// store_delta.new_value is an ASCII string representing an integer
|
||||||
|
let ascii_string =
|
||||||
|
String::from_utf8(store_delta.new_value.clone()).expect("Invalid UTF-8 sequence");
|
||||||
|
let balance = BigInt::from_str(&ascii_string).expect("Failed to parse integer");
|
||||||
|
let big_endian_bytes_balance = balance.to_bytes_be().1;
|
||||||
|
|
||||||
|
let balance_change = BalanceChange {
|
||||||
|
component_id: pool_hash_hex.as_bytes().to_vec(),
|
||||||
|
token: pool.tokens[token_index].clone(),
|
||||||
|
balance: big_endian_bytes_balance.to_vec(),
|
||||||
|
};
|
||||||
|
let tx_hash = balance_delta
|
||||||
|
.tx
|
||||||
|
.expect("Transaction is missing")
|
||||||
|
.hash;
|
||||||
|
balance_changes
|
||||||
|
.entry(tx_hash)
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(balance_change);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (tx_hash, grouped_balance_changes) in balance_changes {
|
||||||
|
if let Some(tx_change) = block_changes
|
||||||
|
.changes
|
||||||
|
.iter_mut()
|
||||||
|
// TODO: be better than this (quadratic complexity)
|
||||||
|
.find(|tx_change| {
|
||||||
|
tx_change
|
||||||
|
.tx
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |tx| tx.hash == tx_hash)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
tx_change
|
||||||
|
.balance_changes
|
||||||
|
.extend(grouped_balance_changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block_changes.block = Some(Block {
|
||||||
|
number: block.number,
|
||||||
|
hash: block.hash.clone(),
|
||||||
|
parent_hash: block
|
||||||
|
.header
|
||||||
|
.as_ref()
|
||||||
|
.expect("Block header not present")
|
||||||
|
.parent_hash
|
||||||
|
.clone(),
|
||||||
|
ts: block.timestamp_seconds(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(block_changes)
|
||||||
|
}
|
||||||
16
substreams/ethereum-ambient/src/modules/mod.rs
Normal file
16
substreams/ethereum-ambient/src/modules/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
pub use map_changes::map_changes;
|
||||||
|
pub use map_pool_changes::map_pool_changes;
|
||||||
|
pub use store_pool_balances::store_pool_balances;
|
||||||
|
pub use store_pools::store_pools;
|
||||||
|
|
||||||
|
#[path = "1_map_pool_changes.rs"]
|
||||||
|
mod map_pool_changes;
|
||||||
|
|
||||||
|
#[path = "2_store_pools.rs"]
|
||||||
|
mod store_pools;
|
||||||
|
|
||||||
|
#[path = "2_store_pool_balances.rs"]
|
||||||
|
mod store_pool_balances;
|
||||||
|
|
||||||
|
#[path = "3_map_changes.rs"]
|
||||||
|
mod map_changes;
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// @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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
// @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,
|
|
||||||
}
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
/// 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.
|
|
||||||
#[prost(bytes="vec", tag="3")]
|
|
||||||
pub component_id: ::prost::alloc::vec::Vec<u8>,
|
|
||||||
}
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This file contains proto definitions specific to the VM integration.
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
/// A set of changes aggregated by transaction.
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct TransactionContractChanges {
|
|
||||||
/// 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.
|
|
||||||
#[prost(message, repeated, tag="2")]
|
|
||||||
pub contract_changes: ::prost::alloc::vec::Vec<ContractChange>,
|
|
||||||
/// 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 BlockContractChanges {
|
|
||||||
/// 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<TransactionContractChanges>,
|
|
||||||
}
|
|
||||||
// @@protoc_insertion_point(module)
|
|
||||||
55
substreams/ethereum-ambient/src/utils.rs
Normal file
55
substreams/ethereum-ambient/src/utils.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
use ethabi::{decode, ethereum_types::U256, ParamType, Token, Uint};
|
||||||
|
use substreams_ethereum::pb::eth::v2::Call;
|
||||||
|
use tiny_keccak::{Hasher, Keccak};
|
||||||
|
|
||||||
|
pub fn encode_pool_hash(token_x: Vec<u8>, token_y: Vec<u8>, pool_idx: Vec<u8>) -> [u8; 32] {
|
||||||
|
let base_address = ethabi::Address::from_slice(&token_x);
|
||||||
|
let quote_address = ethabi::Address::from_slice(&token_y);
|
||||||
|
let pool_idx_uint = Uint::from_big_endian(&pool_idx);
|
||||||
|
|
||||||
|
let encoded = ethabi::encode(&[
|
||||||
|
Token::Address(base_address),
|
||||||
|
Token::Address(quote_address),
|
||||||
|
Token::Uint(pool_idx_uint),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mut hasher = Keccak::v256();
|
||||||
|
hasher.update(&encoded);
|
||||||
|
let mut output = [0u8; 32];
|
||||||
|
hasher.finalize(&mut output);
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_flows_from_output(call: &Call) -> Result<(ethabi::Int, ethabi::Int), anyhow::Error> {
|
||||||
|
if let Ok(external_outputs) = decode(BASE_QUOTE_FLOW_OUTPUT, &call.return_data) {
|
||||||
|
let base_flow = external_outputs[0]
|
||||||
|
.to_owned()
|
||||||
|
.into_int() // Needs conversion into bytes for next step
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert base flow to i128".to_string()))?;
|
||||||
|
|
||||||
|
let quote_flow = external_outputs[1]
|
||||||
|
.to_owned()
|
||||||
|
.into_int() // Needs conversion into bytes for next step
|
||||||
|
.ok_or_else(|| anyhow!("Failed to convert quote flow to i128".to_string()))?;
|
||||||
|
Ok((base_flow, quote_flow))
|
||||||
|
} else {
|
||||||
|
bail!("Failed to decode swap call outputs.".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASE_QUOTE_FLOW_OUTPUT: &[ParamType] = &[
|
||||||
|
// The token base and quote token flows associated with this swap action.
|
||||||
|
// Negative indicates a credit paid to the user (token balance of pool
|
||||||
|
// decreases), positive a debit collected from the user (token balance of pool
|
||||||
|
// increases).
|
||||||
|
ParamType::Int(128), // baseFlow
|
||||||
|
ParamType::Int(128), // quoteFlow
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn from_u256_to_vec(src: U256) -> Vec<u8> {
|
||||||
|
let mut buf = [0u8; 32];
|
||||||
|
src.to_big_endian(&mut buf);
|
||||||
|
buf.to_vec()
|
||||||
|
}
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
specVersion: v0.1.0
|
specVersion: v0.1.0
|
||||||
package:
|
package:
|
||||||
name: "substreams_ethereum_ambient"
|
name: "substreams_ethereum_ambient"
|
||||||
version: v0.3.0
|
version: v0.5.1
|
||||||
|
|
||||||
protobuf:
|
protobuf:
|
||||||
files:
|
files:
|
||||||
- vm.proto
|
- tycho/evm/v1/common.proto
|
||||||
- common.proto
|
- tycho/evm/v1/vm.proto
|
||||||
|
- ambient.proto
|
||||||
importPaths:
|
importPaths:
|
||||||
- ../../proto/tycho/evm/v1/
|
- ./proto/v1
|
||||||
|
- ../../proto/
|
||||||
|
|
||||||
binaries:
|
binaries:
|
||||||
default:
|
default:
|
||||||
@@ -16,9 +18,35 @@ binaries:
|
|||||||
file: ../../target/wasm32-unknown-unknown/substreams/substreams_ethereum_ambient.wasm
|
file: ../../target/wasm32-unknown-unknown/substreams/substreams_ethereum_ambient.wasm
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
- name: map_changes
|
- name: map_pool_changes
|
||||||
kind: map
|
kind: map
|
||||||
|
initialBlock: 17361664
|
||||||
inputs:
|
inputs:
|
||||||
- source: sf.ethereum.type.v2.Block
|
- source: sf.ethereum.type.v2.Block
|
||||||
output:
|
output:
|
||||||
type: proto:tycho.evm.state.v1.BlockContractChanges
|
type: proto:tycho.evm.v1.BlockPoolChanges
|
||||||
|
- name: store_pools
|
||||||
|
kind: store
|
||||||
|
initialBlock: 17361664
|
||||||
|
updatePolicy: set
|
||||||
|
valueType: proto:tycho.evm.v1.ProtocolComponent
|
||||||
|
inputs:
|
||||||
|
- map: map_pool_changes
|
||||||
|
- name: store_pool_balances
|
||||||
|
kind: store
|
||||||
|
initialBlock: 17361664
|
||||||
|
updatePolicy: add
|
||||||
|
valueType: bigint
|
||||||
|
inputs:
|
||||||
|
- map: map_pool_changes
|
||||||
|
- name: map_changes
|
||||||
|
kind: map
|
||||||
|
initialBlock: 17361664
|
||||||
|
inputs:
|
||||||
|
- source: sf.ethereum.type.v2.Block
|
||||||
|
- map: map_pool_changes
|
||||||
|
- store: store_pool_balances
|
||||||
|
mode: deltas
|
||||||
|
- store: store_pools
|
||||||
|
output:
|
||||||
|
type: proto:tycho.evm.v1.BlockContractChanges
|
||||||
|
|||||||
29
substreams/ethereum-uniswap-v2/Cargo.toml
Normal file
29
substreams/ethereum-uniswap-v2/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "substreams-ethereum-uniswap-v2"
|
||||||
|
version = "0.2.1"
|
||||||
|
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
|
||||||
|
tycho-substreams.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
|
||||||
2
substreams/ethereum-uniswap-v2/Makefile
Normal file
2
substreams/ethereum-uniswap-v2/Makefile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
build:
|
||||||
|
cargo build --target wasm32-unknown-unknown --release
|
||||||
125
substreams/ethereum-uniswap-v2/abi/Factory.json
Normal file
125
substreams/ethereum-uniswap-v2/abi/Factory.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
713
substreams/ethereum-uniswap-v2/abi/Pool.json
Normal file
713
substreams/ethereum-uniswap-v2/abi/Pool.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
46
substreams/ethereum-uniswap-v2/arbitrum-uniswap-v2.yaml
Normal file
46
substreams/ethereum-uniswap-v2/arbitrum-uniswap-v2.yaml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
specVersion: v0.1.0
|
||||||
|
package:
|
||||||
|
name: "arbitrum_uniswap_v2"
|
||||||
|
version: v0.2.0
|
||||||
|
|
||||||
|
protobuf:
|
||||||
|
files:
|
||||||
|
- uniswap.proto
|
||||||
|
importPaths:
|
||||||
|
- ./proto/v1
|
||||||
|
|
||||||
|
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
|
||||||
12
substreams/ethereum-uniswap-v2/buf.gen.yaml
Normal file
12
substreams/ethereum-uniswap-v2/buf.gen.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
version: v1
|
||||||
|
plugins:
|
||||||
|
- plugin: buf.build/community/neoeinstein-prost:v0.2.2
|
||||||
|
out: src/pb
|
||||||
|
opt:
|
||||||
|
- file_descriptor_set=false
|
||||||
|
|
||||||
|
- plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
||||||
|
out: src/pb
|
||||||
|
opt:
|
||||||
|
- no_features
|
||||||
12
substreams/ethereum-uniswap-v2/build.rs
Normal file
12
substreams/ethereum-uniswap-v2/build.rs
Normal 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(())
|
||||||
|
}
|
||||||
49
substreams/ethereum-uniswap-v2/ethereum-pancakeswap.yaml
Normal file
49
substreams/ethereum-uniswap-v2/ethereum-pancakeswap.yaml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
specVersion: v0.1.0
|
||||||
|
package:
|
||||||
|
name: "ethereum_pancakeswap"
|
||||||
|
version: v0.2.1
|
||||||
|
|
||||||
|
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
|
||||||
49
substreams/ethereum-uniswap-v2/ethereum-sushiswap.yaml
Normal file
49
substreams/ethereum-uniswap-v2/ethereum-sushiswap.yaml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
specVersion: v0.1.0
|
||||||
|
package:
|
||||||
|
name: "ethereum_sushiswap_v2"
|
||||||
|
version: v0.2.1
|
||||||
|
|
||||||
|
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
|
||||||
49
substreams/ethereum-uniswap-v2/ethereum-uniswap-v2.yaml
Normal file
49
substreams/ethereum-uniswap-v2/ethereum-uniswap-v2.yaml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
specVersion: v0.1.0
|
||||||
|
package:
|
||||||
|
name: "ethereum_uniswap_v2"
|
||||||
|
version: v0.2.1
|
||||||
|
|
||||||
|
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
|
||||||
61
substreams/ethereum-uniswap-v2/proto/v1/uniswap.proto
Normal file
61
substreams/ethereum-uniswap-v2/proto/v1/uniswap.proto
Normal 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 = 10;
|
||||||
|
WithdrawEvent withdraw = 20;
|
||||||
|
SyncEvent sync = 30;
|
||||||
|
SwapEvent swap = 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;
|
||||||
|
}
|
||||||
904
substreams/ethereum-uniswap-v2/src/abi/factory.rs
Normal file
904
substreams/ethereum-uniswap-v2/src/abi/factory.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
substreams/ethereum-uniswap-v2/src/abi/mod.rs
Normal file
4
substreams/ethereum-uniswap-v2/src/abi/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
|
||||||
|
|
||||||
|
pub mod factory;
|
||||||
|
pub mod pool;
|
||||||
3554
substreams/ethereum-uniswap-v2/src/abi/pool.rs
Normal file
3554
substreams/ethereum-uniswap-v2/src/abi/pool.rs
Normal file
File diff suppressed because it is too large
Load Diff
10
substreams/ethereum-uniswap-v2/src/lib.rs
Normal file
10
substreams/ethereum-uniswap-v2/src/lib.rs
Normal 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;
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
use tycho_substreams::prelude::*;
|
||||||
|
|
||||||
|
#[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, ¶ms);
|
||||||
|
|
||||||
|
let tycho_block: Block = (&block).into();
|
||||||
|
|
||||||
|
Ok(BlockChanges { block: Some(tycho_block), changes: new_pools })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pools(block: ð::Block, new_pools: &mut Vec<TransactionChanges>, params: &Params) {
|
||||||
|
// Extract new pools from PairCreated events
|
||||||
|
let mut on_pair_created = |event: PairCreated, _tx: ð::TransactionTrace, _log: ð::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(¶ms.factory_address).unwrap()]);
|
||||||
|
|
||||||
|
eh.on::<PairCreated, _>(&mut on_pair_created);
|
||||||
|
eh.handle_events();
|
||||||
|
}
|
||||||
23
substreams/ethereum-uniswap-v2/src/modules/2_store_pools.rs
Normal file
23
substreams/ethereum-uniswap-v2/src/modules/2_store_pools.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use substreams::store::{StoreNew, StoreSetIfNotExists, StoreSetIfNotExistsProto};
|
||||||
|
|
||||||
|
use crate::store_key::StoreKey;
|
||||||
|
use tycho_substreams::prelude::*;
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
substreams/ethereum-uniswap-v2/src/modules/3_map_pool_events.rs
Normal file
223
substreams/ethereum-uniswap-v2/src/modules/3_map_pool_events.rs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
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, store_key::StoreKey, traits::PoolAddresser};
|
||||||
|
use tycho_substreams::prelude::*;
|
||||||
|
|
||||||
|
// 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: 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: ð::Block,
|
||||||
|
tx_changes: &mut HashMap<Vec<u8>, PartialChanges>,
|
||||||
|
store: &StoreGetProto<ProtocolComponent>,
|
||||||
|
) {
|
||||||
|
let mut on_sync = |event: Sync, _tx: ð::TransactionTrace, _log: ð::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();
|
||||||
|
}
|
||||||
11
substreams/ethereum-uniswap-v2/src/modules/mod.rs
Normal file
11
substreams/ethereum-uniswap-v2/src/modules/mod.rs
Normal 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;
|
||||||
12
substreams/ethereum-uniswap-v2/src/pb/mod.rs
Normal file
12
substreams/ethereum-uniswap-v2/src/pb/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// @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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")]
|
||||||
|
Deposit(super::DepositEvent),
|
||||||
|
#[prost(message, tag="20")]
|
||||||
|
Withdraw(super::WithdrawEvent),
|
||||||
|
#[prost(message, tag="30")]
|
||||||
|
Sync(super::SyncEvent),
|
||||||
|
#[prost(message, tag="40")]
|
||||||
|
Swap(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)
|
||||||
16
substreams/ethereum-uniswap-v2/src/store_key.rs
Normal file
16
substreams/ethereum-uniswap-v2/src/store_key.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
substreams/ethereum-uniswap-v2/src/traits.rs
Normal file
22
substreams/ethereum-uniswap-v2/src/traits.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use ethabi::ethereum_types::Address;
|
||||||
|
use substreams::store::{StoreGet, StoreGetProto};
|
||||||
|
|
||||||
|
use substreams_helper::{common::HasAddresser, hex::Hexable};
|
||||||
|
|
||||||
|
use tycho_substreams::prelude::*;
|
||||||
|
|
||||||
|
use crate::store_key::StoreKey;
|
||||||
|
|
||||||
|
pub struct PoolAddresser<'a> {
|
||||||
|
pub store: &'a StoreGetProto<ProtocolComponent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasAddresser for PoolAddresser<'_> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
29
substreams/ethereum-uniswap-v3/Cargo.toml
Normal file
29
substreams/ethereum-uniswap-v3/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "substreams-ethereum-uniswap-v3"
|
||||||
|
version = "0.2.1"
|
||||||
|
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
|
||||||
|
tycho-substreams.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
|
||||||
2
substreams/ethereum-uniswap-v3/Makefile
Normal file
2
substreams/ethereum-uniswap-v3/Makefile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
build:
|
||||||
|
cargo build --target wasm32-unknown-unknown --profile substreams
|
||||||
198
substreams/ethereum-uniswap-v3/abi/Factory.json
Normal file
198
substreams/ethereum-uniswap-v3/abi/Factory.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
988
substreams/ethereum-uniswap-v3/abi/Pool.json
Normal file
988
substreams/ethereum-uniswap-v3/abi/Pool.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
67
substreams/ethereum-uniswap-v3/arbitrum-uniswap-v3.yaml
Normal file
67
substreams/ethereum-uniswap-v3/arbitrum-uniswap-v3.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
specVersion: v0.1.0
|
||||||
|
package:
|
||||||
|
name: "substreams_arbitrum_uniswap_v3"
|
||||||
|
version: v0.2.1
|
||||||
|
|
||||||
|
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"
|
||||||
12
substreams/ethereum-uniswap-v3/buf.gen.yaml
Normal file
12
substreams/ethereum-uniswap-v3/buf.gen.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
version: v1
|
||||||
|
plugins:
|
||||||
|
- plugin: buf.build/community/neoeinstein-prost:v0.2.2
|
||||||
|
out: src/pb
|
||||||
|
opt:
|
||||||
|
- file_descriptor_set=false
|
||||||
|
|
||||||
|
- plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
||||||
|
out: src/pb
|
||||||
|
opt:
|
||||||
|
- no_features
|
||||||
12
substreams/ethereum-uniswap-v3/build.rs
Normal file
12
substreams/ethereum-uniswap-v3/build.rs
Normal 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(())
|
||||||
|
}
|
||||||
67
substreams/ethereum-uniswap-v3/ethereum-uniswap-v3.yaml
Normal file
67
substreams/ethereum-uniswap-v3/ethereum-uniswap-v3.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
specVersion: v0.1.0
|
||||||
|
package:
|
||||||
|
name: "substreams_ethereum_uniswap_v3"
|
||||||
|
version: v0.2.1
|
||||||
|
|
||||||
|
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"
|
||||||
29
substreams/ethereum-uniswap-v3/proto/v1/uniswap.proto
Normal file
29
substreams/ethereum-uniswap-v3/proto/v1/uniswap.proto
Normal 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;
|
||||||
|
}
|
||||||
1040
substreams/ethereum-uniswap-v3/src/abi/factory.rs
Normal file
1040
substreams/ethereum-uniswap-v3/src/abi/factory.rs
Normal file
File diff suppressed because it is too large
Load Diff
4
substreams/ethereum-uniswap-v3/src/abi/mod.rs
Normal file
4
substreams/ethereum-uniswap-v3/src/abi/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
|
||||||
|
|
||||||
|
pub mod factory;
|
||||||
|
pub mod pool;
|
||||||
5153
substreams/ethereum-uniswap-v3/src/abi/pool.rs
Normal file
5153
substreams/ethereum-uniswap-v3/src/abi/pool.rs
Normal file
File diff suppressed because it is too large
Load Diff
46
substreams/ethereum-uniswap-v3/src/events/burn.rs
Normal file
46
substreams/ethereum-uniswap-v3/src/events/burn.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::StorageChange;
|
||||||
|
use substreams_helper::storage_change::StorageChangesFilter;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::Burn,
|
||||||
|
pb::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{BalanceDelta, EventTrait};
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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![]
|
||||||
|
}
|
||||||
|
}
|
||||||
59
substreams/ethereum-uniswap-v3/src/events/collect.rs
Normal file
59
substreams/ethereum-uniswap-v3/src/events/collect.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::StorageChange;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::Collect,
|
||||||
|
pb::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
use substreams_helper::storage_change::StorageChangesFilter;
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::StorageChange;
|
||||||
|
use substreams_helper::storage_change::StorageChangesFilter;
|
||||||
|
|
||||||
|
use super::{BalanceDelta, EventTrait};
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::CollectProtocol,
|
||||||
|
pb::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
51
substreams/ethereum-uniswap-v3/src/events/flash.rs
Normal file
51
substreams/ethereum-uniswap-v3/src/events/flash.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::StorageChange;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::Flash,
|
||||||
|
pb::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
use substreams_helper::storage_change::StorageChangesFilter;
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
35
substreams/ethereum-uniswap-v3/src/events/initialize.rs
Normal file
35
substreams/ethereum-uniswap-v3/src/events/initialize.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::StorageChange;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::Initialize,
|
||||||
|
pb::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
use substreams_helper::storage_change::StorageChangesFilter;
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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![]
|
||||||
|
}
|
||||||
|
}
|
||||||
58
substreams/ethereum-uniswap-v3/src/events/mint.rs
Normal file
58
substreams/ethereum-uniswap-v3/src/events/mint.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::StorageChange;
|
||||||
|
|
||||||
|
use super::{BalanceDelta, EventTrait};
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::Mint,
|
||||||
|
pb::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
use substreams_helper::storage_change::StorageChangesFilter;
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
148
substreams/ethereum-uniswap-v3/src/events/mod.rs
Normal file
148
substreams/ethereum-uniswap-v3/src/events/mod.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
use substreams_ethereum::{
|
||||||
|
pb::eth::v2::{Log, StorageChange},
|
||||||
|
Event,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::{
|
||||||
|
Burn, Collect, CollectProtocol, Flash, Initialize, Mint, SetFeeProtocol, Swap,
|
||||||
|
},
|
||||||
|
pb::uniswap::v3::{BalanceDelta, Pool},
|
||||||
|
};
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use substreams_ethereum::pb::eth::v2::StorageChange;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
abi::pool::events::SetFeeProtocol,
|
||||||
|
pb::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
use substreams_helper::storage_change::StorageChangesFilter;
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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![]
|
||||||
|
}
|
||||||
|
}
|
||||||
51
substreams/ethereum-uniswap-v3/src/events/swap.rs
Normal file
51
substreams/ethereum-uniswap-v3/src/events/swap.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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::uniswap::v3::Pool,
|
||||||
|
storage::{constants::TRACKED_SLOTS, pool_storage::UniswapPoolStorage},
|
||||||
|
};
|
||||||
|
use tycho_substreams::prelude::Attribute;
|
||||||
|
|
||||||
|
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()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
10
substreams/ethereum-uniswap-v3/src/lib.rs
Normal file
10
substreams/ethereum-uniswap-v3/src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#![allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||||
|
|
||||||
|
mod abi;
|
||||||
|
mod modules;
|
||||||
|
mod pb;
|
||||||
|
mod storage;
|
||||||
|
|
||||||
|
pub use modules::*;
|
||||||
|
|
||||||
|
mod events;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
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;
|
||||||
|
use tycho_substreams::prelude::*;
|
||||||
|
|
||||||
|
#[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: ð::Block,
|
||||||
|
new_pools: &mut Vec<TransactionChanges>,
|
||||||
|
factory_address: &str,
|
||||||
|
) {
|
||||||
|
// Extract new pools from PoolCreated events
|
||||||
|
let mut on_pool_created = |event: PoolCreated, _tx: ð::TransactionTrace, _log: ð::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();
|
||||||
|
}
|
||||||
25
substreams/ethereum-uniswap-v3/src/modules/2_store_pools.rs
Normal file
25
substreams/ethereum-uniswap-v3/src/modules/2_store_pools.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use std::str;
|
||||||
|
|
||||||
|
use substreams::store::{StoreNew, StoreSetIfNotExists, StoreSetIfNotExistsProto};
|
||||||
|
|
||||||
|
use crate::pb::uniswap::v3::Pool;
|
||||||
|
|
||||||
|
use tycho_substreams::prelude::BlockChanges;
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 })
|
||||||
|
}
|
||||||
@@ -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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
234
substreams/ethereum-uniswap-v3/src/modules/5_map_pool_events.rs
Normal file
234
substreams/ethereum-uniswap-v3/src/modules/5_map_pool_events.rs
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
use std::{collections::HashMap, 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::uniswap::v3::Pool,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tycho_substreams::prelude::*;
|
||||||
|
|
||||||
|
#[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()
|
||||||
|
}
|
||||||
18
substreams/ethereum-uniswap-v3/src/modules/mod.rs
Normal file
18
substreams/ethereum-uniswap-v3/src/modules/mod.rs
Normal 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;
|
||||||
8
substreams/ethereum-uniswap-v3/src/pb/mod.rs
Normal file
8
substreams/ethereum-uniswap-v3/src/pb/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// @generated
|
||||||
|
pub mod uniswap {
|
||||||
|
// @@protoc_insertion_point(attribute:uniswap.v3)
|
||||||
|
pub mod v3 {
|
||||||
|
include!("uniswap.v3.rs");
|
||||||
|
// @@protoc_insertion_point(uniswap.v3)
|
||||||
|
}
|
||||||
|
}
|
||||||
41
substreams/ethereum-uniswap-v3/src/pb/uniswap.v3.rs
Normal file
41
substreams/ethereum-uniswap-v3/src/pb/uniswap.v3.rs
Normal 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)
|
||||||
60
substreams/ethereum-uniswap-v3/src/storage/constants.rs
Normal file
60
substreams/ethereum-uniswap-v3/src/storage/constants.rs
Normal 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,
|
||||||
|
];
|
||||||
4
substreams/ethereum-uniswap-v3/src/storage/mod.rs
Normal file
4
substreams/ethereum-uniswap-v3/src/storage/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod pool_storage;
|
||||||
|
|
||||||
|
pub mod constants;
|
||||||
|
mod utils;
|
||||||
130
substreams/ethereum-uniswap-v3/src/storage/pool_storage.rs
Normal file
130
substreams/ethereum-uniswap-v3/src/storage/pool_storage.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
use crate::storage::utils;
|
||||||
|
use tycho_substreams::prelude::{Attribute, ChangeType};
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
165
substreams/ethereum-uniswap-v3/src/storage/utils.rs
Normal file
165
substreams/ethereum-uniswap-v3/src/storage/utils.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,4 +12,6 @@ ignore = [
|
|||||||
"crates/tycho-substreams/src/pb",
|
"crates/tycho-substreams/src/pb",
|
||||||
"ethereum-balancer/src/abi",
|
"ethereum-balancer/src/abi",
|
||||||
"ethereum-curve/src/abi",
|
"ethereum-curve/src/abi",
|
||||||
|
"ethereum-uniswap-v2/src/abi",
|
||||||
|
"ethereum-uniswap-v3/src/abi",
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user