diff --git a/proto/buf.lock b/buf.lock
similarity index 73%
rename from proto/buf.lock
rename to buf.lock
index c91b581..4f98143 100644
--- a/proto/buf.lock
+++ b/buf.lock
@@ -1,2 +1,2 @@
# Generated by buf. DO NOT EDIT.
-version: v1
+version: v2
diff --git a/buf.yaml b/buf.yaml
new file mode 100644
index 0000000..6ee1fb4
--- /dev/null
+++ b/buf.yaml
@@ -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
diff --git a/proto/buf.yaml b/proto/buf.yaml
deleted file mode 100644
index d4ff52d..0000000
--- a/proto/buf.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-version: v1
-breaking:
- use:
- - FILE
-lint:
- use:
- - BASIC
diff --git a/substreams/Cargo.lock b/substreams/Cargo.lock
index c3164d5..3132472 100644
--- a/substreams/Cargo.lock
+++ b/substreams/Cargo.lock
@@ -29,6 +29,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
[[package]]
name = "bigdecimal"
version = "0.3.1"
@@ -126,6 +132,12 @@ dependencies = [
"crypto-common",
]
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
[[package]]
name = "either"
version = "1.10.0"
@@ -934,6 +946,18 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "substreams-entity-change"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2c7fca123abff659d15ed30da5b605fa954a29e912c94260c488d0d18f9107d"
+dependencies = [
+ "base64",
+ "prost 0.11.9",
+ "prost-types 0.11.9",
+ "substreams",
+]
+
[[package]]
name = "substreams-ethereum"
version = "0.9.9"
@@ -965,6 +989,24 @@ dependencies = [
"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]]
name = "substreams-ethereum-core"
version = "0.9.9"
@@ -997,6 +1039,66 @@ dependencies = [
"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]]
name = "substreams-macro"
version = "0.5.13"
diff --git a/substreams/Cargo.toml b/substreams/Cargo.toml
index 904aedb..720b54e 100644
--- a/substreams/Cargo.toml
+++ b/substreams/Cargo.toml
@@ -1,5 +1,13 @@
[workspace]
-members = ["ethereum-balancer", "ethereum-curve", "crates/tycho-substreams"]
+members = [
+ "ethereum-balancer",
+ "ethereum-curve",
+ "crates/tycho-substreams",
+ "crates/substreams-helper",
+ "ethereum-ambient",
+ "ethereum-uniswap-v2",
+ "ethereum-uniswap-v3",
+]
resolver = "2"
@@ -9,9 +17,11 @@ substreams = "0.5"
prost = "0.11"
prost-types = "0.12.3"
hex-literal = "0.4.1"
+anyhow = "1.0.75"
hex = "0.4.3"
ethabi = "18.0.0"
tycho-substreams = { path = "crates/tycho-substreams" }
+substreams-helper = { path = "crates/substreams-helper" }
serde = "1.0.204"
serde_json = "1.0.120"
diff --git a/substreams/crates/substreams-helper/.gitignore b/substreams/crates/substreams-helper/.gitignore
new file mode 100644
index 0000000..03314f7
--- /dev/null
+++ b/substreams/crates/substreams-helper/.gitignore
@@ -0,0 +1 @@
+Cargo.lock
diff --git a/substreams/crates/substreams-helper/Cargo.toml b/substreams/crates/substreams-helper/Cargo.toml
new file mode 100644
index 0000000..41624d6
--- /dev/null
+++ b/substreams/crates/substreams-helper/Cargo.toml
@@ -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
diff --git a/substreams/crates/substreams-helper/Makefile b/substreams/crates/substreams-helper/Makefile
new file mode 100644
index 0000000..f991fe2
--- /dev/null
+++ b/substreams/crates/substreams-helper/Makefile
@@ -0,0 +1,7 @@
+.PHONY: build
+build:
+ cargo build --target wasm32-unknown-unknown --release
+
+.PHONY: publish
+publish:
+ cargo publish --target wasm32-unknown-unknown
diff --git a/substreams/crates/substreams-helper/rust-toolchain.toml b/substreams/crates/substreams-helper/rust-toolchain.toml
new file mode 100644
index 0000000..98d17b4
--- /dev/null
+++ b/substreams/crates/substreams-helper/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.75.0"
+components = [ "rustfmt" ]
+targets = [ "wasm32-unknown-unknown" ]
diff --git a/substreams/crates/substreams-helper/src/common.rs b/substreams/crates/substreams-helper/src/common.rs
new file mode 100644
index 0000000..e0a83ac
--- /dev/null
+++ b/substreams/crates/substreams-helper/src/common.rs
@@ -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
{
+ 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 HasAddresser for StoreGetProto {
+ 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
+ }
+}
diff --git a/substreams/crates/substreams-helper/src/event_handler.rs b/substreams/crates/substreams-helper/src/event_handler.rs
new file mode 100644
index 0000000..2b550c2
--- /dev/null
+++ b/substreams/crates/substreams-helper/src/event_handler.rs
@@ -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::(&mut on_transfer);
+/// eh.on::(&mut on_approval);
+/// eh.on::(&mut on_mint);
+/// eh.on::(&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 = vec![];
+/// {
+/// let mut on_transfer = |/*...*/| {
+/// // this handler modifies `balances`
+/// balances.push(some_balance);
+/// };
+/// let eh = EventHandler::new(&block);
+/// eh.on::(&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>,
+ addresses: Option>,
+}
+
+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(&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);
+ }
+ }
+ }
+}
diff --git a/substreams/crates/substreams-helper/src/hex.rs b/substreams/crates/substreams-helper/src/hex.rs
new file mode 100644
index 0000000..c056ba7
--- /dev/null
+++ b/substreams/crates/substreams-helper/src/hex.rs
@@ -0,0 +1,20 @@
+use ethabi::ethereum_types::Address;
+use substreams::Hex;
+
+pub trait Hexable {
+ fn to_hex(&self) -> String;
+}
+
+impl Hexable for Vec {
+ 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()
+ }
+}
diff --git a/substreams/crates/substreams-helper/src/lib.rs b/substreams/crates/substreams-helper/src/lib.rs
new file mode 100644
index 0000000..8e66ac5
--- /dev/null
+++ b/substreams/crates/substreams-helper/src/lib.rs
@@ -0,0 +1,5 @@
+pub mod common;
+
+pub mod event_handler;
+pub mod hex;
+pub mod storage_change;
diff --git a/substreams/crates/substreams-helper/src/storage_change.rs b/substreams/crates/substreams-helper/src/storage_change.rs
new file mode 100644
index 0000000..bdc0ce8
--- /dev/null
+++ b/substreams/crates/substreams-helper/src/storage_change.rs
@@ -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 {
+ fn filter_by_address(&self, contract_addr: &[u8; 20]) -> Vec<&StorageChange> {
+ self.iter()
+ .filter(|change| change.address == contract_addr)
+ .collect()
+ }
+}
diff --git a/substreams/crates/tycho-substreams/Readme.md b/substreams/crates/tycho-substreams/Readme.md
index 20f868b..44394fc 100644
--- a/substreams/crates/tycho-substreams/Readme.md
+++ b/substreams/crates/tycho-substreams/Readme.md
@@ -4,15 +4,12 @@ Some shared functionality that is used to create tycho substream packages.
## 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.
-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:
```bash
-buf generate \
- --path tycho \
- --template ../substreams/crates/tycho-substreams/buf.gen.yaml \
- --output ../substreams/crates/tycho-substreams/
-```
\ No newline at end of file
+buf generate --template substreams/crates/tycho-substreams/buf.gen.yaml --output substreams/crates/tycho-substreams/
+```
diff --git a/substreams/crates/tycho-substreams/buf.gen.yaml b/substreams/crates/tycho-substreams/buf.gen.yaml
index 07f4f81..bc6db15 100644
--- a/substreams/crates/tycho-substreams/buf.gen.yaml
+++ b/substreams/crates/tycho-substreams/buf.gen.yaml
@@ -1,12 +1,10 @@
-version: v1
+version: v2
plugins:
- - plugin: buf.build/community/neoeinstein-prost:v0.2.2
+ - remote: buf.build/community/neoeinstein-prost:v0.2.2
out: src/pb
opt:
- file_descriptor_set=false
- type_attribute=.tycho.evm.v1.Transaction=#[derive(Eq\, Hash)]
-
- - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1
+ - remote: buf.build/community/neoeinstein-prost-crate:v0.3.1
out: src/pb
- opt:
- - no_features
+ opt: no_features
diff --git a/substreams/crates/tycho-substreams/src/models.rs b/substreams/crates/tycho-substreams/src/models.rs
index 333f172..359b2af 100644
--- a/substreams/crates/tycho-substreams/src/models.rs
+++ b/substreams/crates/tycho-substreams/src/models.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use substreams_ethereum::pb::eth::v2::{self as sf, StorageChange};
// re-export the protobuf types here.
-pub use crate::pb::tycho::evm::v1::*;
+pub use crate::pb::tycho::{ambient::v1::*, evm::v1::*};
impl TransactionContractChanges {
/// Creates a new empty `TransactionContractChanges` instance.
diff --git a/substreams/crates/tycho-substreams/src/pb/mod.rs b/substreams/crates/tycho-substreams/src/pb/mod.rs
index 43d8838..d6a992a 100644
--- a/substreams/crates/tycho-substreams/src/pb/mod.rs
+++ b/substreams/crates/tycho-substreams/src/pb/mod.rs
@@ -1,5 +1,12 @@
// @generated
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 {
// @@protoc_insertion_point(attribute:tycho.evm.v1)
pub mod v1 {
diff --git a/substreams/crates/tycho-substreams/src/pb/tycho.ambient.v1.rs b/substreams/crates/tycho-substreams/src/pb/tycho.ambient.v1.rs
new file mode 100644
index 0000000..5bfe909
--- /dev/null
+++ b/substreams/crates/tycho-substreams/src/pb/tycho.ambient.v1.rs
@@ -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,
+ /// 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,
+ /// 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,
+}
+/// 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,
+ /// Balance changes to pools in this block
+ #[prost(message, repeated, tag="2")]
+ pub balance_deltas: ::prost::alloc::vec::Vec,
+}
+// @@protoc_insertion_point(module)
diff --git a/substreams/crates/tycho-substreams/src/pb/tycho.evm.v1.rs b/substreams/crates/tycho-substreams/src/pb/tycho.evm.v1.rs
index 39cbcb3..7f8c737 100644
--- a/substreams/crates/tycho-substreams/src/pb/tycho.evm.v1.rs
+++ b/substreams/crates/tycho-substreams/src/pb/tycho.evm.v1.rs
@@ -84,7 +84,8 @@ pub struct ProtocolComponent {
/// Usually it is a single contract, but some protocols use multiple contracts.
#[prost(bytes="vec", repeated, tag="3")]
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>,
- /// 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.
#[prost(message, repeated, tag="4")]
pub static_att: ::prost::alloc::vec::Vec,
@@ -186,6 +187,7 @@ pub struct TransactionChanges {
pub balance_changes: ::prost::alloc::vec::Vec,
}
/// 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)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockChanges {
diff --git a/substreams/ethereum-ambient/Cargo.lock b/substreams/ethereum-ambient/Cargo.lock
index 03629cf..6d24354 100644
--- a/substreams/ethereum-ambient/Cargo.lock
+++ b/substreams/ethereum-ambient/Cargo.lock
@@ -180,24 +180,7 @@ version = "17.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b"
dependencies = [
- "ethereum-types 0.13.1",
- "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",
+ "ethereum-types",
"hex",
"once_cell",
"regex",
@@ -215,22 +198,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef"
dependencies = [
"crunchy",
- "fixed-hash 0.7.0",
+ "fixed-hash",
"impl-rlp",
- "impl-serde 0.3.2",
- "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",
+ "impl-serde",
"tiny-keccak",
]
@@ -240,25 +210,11 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6"
dependencies = [
- "ethbloom 0.12.1",
- "fixed-hash 0.7.0",
+ "ethbloom",
+ "fixed-hash",
"impl-rlp",
- "impl-serde 0.3.2",
- "primitive-types 0.11.1",
- "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",
+ "impl-serde",
+ "primitive-types",
"uint",
]
@@ -280,18 +236,6 @@ dependencies = [
"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]]
name = "fixedbitset"
version = "0.4.2"
@@ -391,15 +335,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "impl-serde"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "impl-trait-for-tuples"
version = "0.2.2"
@@ -584,23 +519,10 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a"
dependencies = [
- "fixed-hash 0.7.0",
+ "fixed-hash",
"impl-codec",
"impl-rlp",
- "impl-serde 0.3.2",
- "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",
+ "impl-serde",
"uint",
]
@@ -883,7 +805,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a176f39a6e09553c17a287edacd1854e5686fd20ffea3c9655dfc44d94b35e"
dependencies = [
"anyhow",
- "ethabi 17.2.0",
+ "ethabi",
"heck",
"hex",
"prettyplease",
@@ -895,12 +817,8 @@ dependencies = [
[[package]]
name = "substreams-ethereum-ambient"
-version = "0.3.0"
+version = "0.4.0"
dependencies = [
- "anyhow",
- "bytes",
- "ethabi 18.0.0",
- "hex",
"hex-literal 0.4.1",
"prost",
"substreams",
@@ -914,7 +832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4700cfe408b75634a3c6b3a0caf7bddba4879601d2085c811485ea54cbde2d"
dependencies = [
"bigdecimal",
- "ethabi 17.2.0",
+ "ethabi",
"getrandom",
"num-bigint",
"prost",
@@ -929,7 +847,7 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40d6d278d926fe3f0775d996ee2b5e1dc822c1b4bf4f7bf07c7fbb5bce6c79a9"
dependencies = [
- "ethabi 17.2.0",
+ "ethabi",
"heck",
"hex",
"num-bigint",
diff --git a/substreams/ethereum-ambient/Cargo.toml b/substreams/ethereum-ambient/Cargo.toml
index f7d246b..4738c67 100644
--- a/substreams/ethereum-ambient/Cargo.toml
+++ b/substreams/ethereum-ambient/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "substreams-ethereum-ambient"
-version = "0.3.0"
+version = "0.5.1"
edition = "2021"
[lib]
@@ -8,11 +8,15 @@ name = "substreams_ethereum_ambient"
crate-type = ["cdylib"]
[dependencies]
-substreams = "0.5"
-substreams-ethereum = "0.9"
-prost = "0.11"
-hex-literal = "0.4.1"
-ethabi = "18.0.0"
-hex = "0.4.2"
+tycho-substreams.workspace = true
+substreams.workspace = true
+substreams-ethereum.workspace = true
+prost.workspace = true
+hex-literal.workspace = true
+ethabi.workspace = true
+hex.workspace = true
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"
diff --git a/substreams/ethereum-ambient/Readme.md b/substreams/ethereum-ambient/Readme.md
new file mode 100644
index 0000000..af70922
--- /dev/null
+++ b/substreams/ethereum-ambient/Readme.md
@@ -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`.
diff --git a/substreams/ethereum-ambient/buf.gen.yaml b/substreams/ethereum-ambient/buf.gen.yaml
index d2e6544..b028921 100644
--- a/substreams/ethereum-ambient/buf.gen.yaml
+++ b/substreams/ethereum-ambient/buf.gen.yaml
@@ -1,12 +1,8 @@
-
-version: v1
+version: v2
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
+ - remote: buf.build/community/neoeinstein-prost:v0.2.2
+ out: src/pb
+ opt: file_descriptor_set=false
+ - remote: buf.build/community/neoeinstein-prost-crate:v0.3.1
+ out: src/pb
+ opt: no_features
diff --git a/substreams/ethereum-ambient/proto/ambient.proto b/substreams/ethereum-ambient/proto/ambient.proto
new file mode 100644
index 0000000..22afbbf
--- /dev/null
+++ b/substreams/ethereum-ambient/proto/ambient.proto
@@ -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;
+}
\ No newline at end of file
diff --git a/substreams/ethereum-ambient/src/contracts/hotproxy.rs b/substreams/ethereum-ambient/src/contracts/hotproxy.rs
new file mode 100644
index 0000000..c084ea1
--- /dev/null
+++ b/substreams/ethereum-ambient/src/contracts/hotproxy.rs
@@ -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
+ .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());
+ }
+}
diff --git a/substreams/ethereum-ambient/src/contracts/knockout.rs b/substreams/ethereum-ambient/src/contracts/knockout.rs
new file mode 100644
index 0000000..7a5234d
--- /dev/null
+++ b/substreams/ethereum-ambient/src/contracts/knockout.rs
@@ -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
+ .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());
+ }
+}
diff --git a/substreams/ethereum-ambient/src/contracts/main.rs b/substreams/ethereum-ambient/src/contracts/main.rs
new file mode 100644
index 0000000..2b273b5
--- /dev/null
+++ b/substreams/ethereum-ambient/src/contracts/main.rs
@@ -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