chore: clean up ambient (#136)

* fix: remove unnecessary tx field in ProtocolComponent

* chore: move ambient protobuf files to ambient module

* chore: remove dependency on common message types

This allows us to isolate the ambient specific messages within the ambient module

* feat: update ambient substream with new message structs

* chore: update substream configs

And remove use of deprecated BlockContractChanges.

* feat: implement From for AmbientProtocolComponent to ProtocolComponent
This commit is contained in:
Louise Poole
2025-01-27 10:36:13 +02:00
committed by GitHub
parent 28dd2fc858
commit ad0a391f72
18 changed files with 171 additions and 125 deletions

View File

@@ -3,7 +3,6 @@ modules:
- path: proto
excludes:
- proto/sf
- path: substreams/ethereum-ambient/proto
lint:
use:
- BASIC

View File

@@ -90,8 +90,6 @@ message ProtocolComponent {
ChangeType change = 5;
/// Represents the functionality of the component.
ProtocolType protocol_type = 6;
// Transaction where this component was created
Transaction tx = 7;
}
// A struct for following the changes of Total Value Locked (TVL) of a protocol component.

20
substreams/Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aho-corasick"
@@ -1141,7 +1141,7 @@ dependencies = [
"substreams",
"substreams-ethereum",
"tiny-keccak",
"tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)",
"tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=f90c33b)",
]
[[package]]
@@ -1369,6 +1369,22 @@ dependencies = [
"substreams-ethereum",
]
[[package]]
name = "tycho-substreams"
version = "0.2.0"
source = "git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=f90c33b#f90c33bcc229a1bca1fe86ee6fc4024cbdc25aeb"
dependencies = [
"ethabi 18.0.0",
"hex",
"itertools 0.12.1",
"num-bigint",
"prost 0.11.9",
"serde",
"serde_json",
"substreams",
"substreams-ethereum",
]
[[package]]
name = "typenum"
version = "1.17.0"

View File

@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
use substreams_ethereum::pb::eth::v2::{self as sf, StorageChange};
// re-export the protobuf types here.
pub use crate::pb::tycho::{ambient::v1::*, evm::v1::*};
pub use crate::pb::tycho::evm::v1::*;
impl TransactionContractChanges {
/// Creates a new empty `TransactionContractChanges` instance.
@@ -213,7 +213,7 @@ impl ProtocolComponent {
/// ## Parameters
/// - `id`: Identifier for the component.
/// - `tx`: Reference to the associated transaction.
pub fn new(id: &str, tx: &Transaction) -> Self {
pub fn new(id: &str) -> Self {
Self {
id: id.to_string(),
tokens: Vec::new(),
@@ -221,7 +221,6 @@ impl ProtocolComponent {
static_att: Vec::new(),
change: ChangeType::Creation.into(),
protocol_type: None,
tx: Some(tx.clone()),
}
}
@@ -233,7 +232,7 @@ impl ProtocolComponent {
/// ## Parameters
/// - `id`: Contract address to be encoded and set as the component's ID.
/// - `tx`: Reference to the associated transaction.
pub fn at_contract(id: &[u8], tx: &Transaction) -> Self {
pub fn at_contract(id: &[u8]) -> Self {
Self {
id: format!("0x{}", hex::encode(id)),
tokens: Vec::new(),
@@ -241,7 +240,6 @@ impl ProtocolComponent {
static_att: Vec::new(),
change: ChangeType::Creation.into(),
protocol_type: None,
tx: Some(tx.clone()),
}
}

View File

@@ -1,12 +1,5 @@
// @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 {

View File

@@ -95,9 +95,6 @@ pub struct ProtocolComponent {
/// / Represents the functionality of the component.
#[prost(message, optional, tag="6")]
pub protocol_type: ::core::option::Option<ProtocolType>,
/// Transaction where this component was created
#[prost(message, optional, tag = "7")]
pub tx: ::core::option::Option<Transaction>,
}
/// A struct for following the changes of Total Value Locked (TVL) of a protocol component.
/// Note that if a ProtocolComponent contains multiple contracts, the TVL is tracked for the component as a whole.

View File

@@ -8,7 +8,7 @@ name = "substreams_ethereum_ambient"
crate-type = ["cdylib"]
[dependencies]
tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "b8aeaa3" }
tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "f90c33b" }
substreams = "0.5.22"
substreams-ethereum = "0.9.9"
prost = "0.11"

View File

@@ -9,7 +9,7 @@ Modules Description
* **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`.
* **Output**: Emits data of type `proto:tycho.ambient.v1.BlockPoolChanges`.
### `store_pools_balances`
@@ -26,10 +26,10 @@ Modules Description
### `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.
* **Purpose**: This module integrates all the processed information to generate comprehensive `BlockChanges`. 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`.
* **Output**: Emits `proto:tycho.evm.state.v1.BlockChanges`.

View File

@@ -2,8 +2,6 @@ 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.
@@ -14,14 +12,25 @@ message AmbientBalanceDelta {
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;
// Transaction index of the balance change
uint64 tx_index = 5;
}
message AmbientProtocolComponent {
// A unique identifier for the component within the protocol.
string id = 1;
// Addresses of the ERC20 tokens used by the component.
repeated bytes tokens = 2;
// Ambient pool index [static attribute for ambient pools]
bytes pool_index = 3;
// Transaction index for the component creation
uint64 tx_index = 4;
}
// 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 AmbientProtocolComponent new_components = 1;
// Balance changes on this block
repeated AmbientBalanceDelta balance_deltas = 2;
}

View File

@@ -1,14 +1,17 @@
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;
use tycho_substreams::models::{
Attribute, ChangeType, FinancialType, ImplementationType, ProtocolComponent, ProtocolType,
};
use crate::{
pb::tycho::ambient::v1::AmbientProtocolComponent,
utils::{decode_flows_from_output, encode_pool_hash},
};
pub const AMBIENT_CONTRACT: [u8; 20] = hex!("aaaaaaaaa24eeeb8d57d431224f73832bc34f688");
pub const USER_CMD_FN_SIG: [u8; 4] = hex!("a15112f9");
@@ -91,10 +94,11 @@ pub fn decode_direct_swap_call(
bail!("Failed to decode swap call inputs.".to_string());
}
}
pub fn decode_pool_init(
call: &Call,
tx: Transaction,
) -> Result<Option<ProtocolComponent>, anyhow::Error> {
tx_index: u64,
) -> Result<Option<AmbientProtocolComponent>, 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]
@@ -134,28 +138,14 @@ pub fn decode_pool_init(
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 {
let new_component = AmbientProtocolComponent {
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()),
pool_index,
tx_index,
};
Ok(Some(new_component))
} else {
@@ -168,3 +158,25 @@ pub fn decode_pool_init(
bail!("Failed to decode ABI external call.".to_string());
}
}
impl From<AmbientProtocolComponent> for ProtocolComponent {
fn from(component: AmbientProtocolComponent) -> Self {
ProtocolComponent {
id: component.id,
tokens: component.tokens,
contracts: vec![AMBIENT_CONTRACT.to_vec()],
static_att: vec![Attribute {
name: "pool_index".to_string(),
value: component.pool_index,
change: ChangeType::Creation.into(),
}],
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(),
}),
}
}
}

View File

@@ -1,4 +1,5 @@
mod contracts;
mod pb;
pub use modules::*;
mod modules;

View File

@@ -20,24 +20,17 @@ use crate::{
decode_warm_path_user_cmd_call, AMBIENT_WARMPATH_CONTRACT, USER_CMD_WARMPATH_FN_SIG,
},
},
pb::tycho::ambient::v1::{AmbientBalanceDelta, BlockPoolChanges},
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();
let mut protocol_components = 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,
};
let tx_index = block_tx.index as u64;
// extract storage changes
let mut storage_changes = block_tx
.calls
@@ -66,7 +59,7 @@ fn map_pool_changes(block: eth::v2::Block) -> Result<BlockPoolChanges, substream
if call.address == AMBIENT_CONTRACT && selector == USER_CMD_FN_SIG {
// Extract pool creations
if let Some(protocol_component) = decode_pool_init(call, tx.clone())? {
if let Some(protocol_component) = decode_pool_init(call, tx_index)? {
protocol_components.push(protocol_component);
}
}
@@ -109,19 +102,18 @@ fn map_pool_changes(block: eth::v2::Block) -> Result<BlockPoolChanges, substream
token_type: "base".to_string(),
token_delta: from_u256_to_vec(base_flow),
ordinal: call.index as u64,
tx: Some(tx.clone()),
tx_index,
};
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()),
tx_index,
};
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)
Ok(BlockPoolChanges { balance_deltas, new_components: protocol_components })
}

View File

@@ -2,12 +2,12 @@ use substreams::{
scalar::BigInt,
store::{StoreAdd, StoreAddBigInt, StoreNew},
};
use tycho_substreams::models::BlockPoolChanges;
use crate::pb::tycho::ambient::v1::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 {
for balance_delta in changes.balance_deltas {
let pool_hash_hex = hex::encode(&balance_delta.pool_hash);
balance_store.add(
balance_delta.ordinal,

View File

@@ -1,9 +1,12 @@
use substreams::store::{StoreNew, StoreSet, StoreSetProto};
use tycho_substreams::models::{BlockPoolChanges, ProtocolComponent};
use tycho_substreams::models::ProtocolComponent;
use crate::pb::tycho::ambient::v1::BlockPoolChanges;
#[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);
for component in changes.new_components {
let protocol_component: ProtocolComponent = component.into();
component_store.set(0, protocol_component.id.clone(), &protocol_component);
}
}

View File

@@ -3,14 +3,16 @@ use std::{
collections::{hash_map::Entry, HashMap},
str::FromStr,
};
use substreams::pb::substreams::StoreDeltas;
use substreams::{
pb::substreams::StoreDeltas,
store::{StoreGet, StoreGetProto},
};
use substreams_ethereum::pb::eth::{self};
use crate::contracts::main::AMBIENT_CONTRACT;
use substreams::store::{StoreGet, StoreGetProto};
use tycho_substreams::prelude::*;
use crate::{contracts::main::AMBIENT_CONTRACT, pb::tycho::ambient::v1::BlockPoolChanges};
struct SlotValue {
new_value: Vec<u8>,
start_value: Vec<u8>,
@@ -74,10 +76,10 @@ fn map_changes(
block_pool_changes: BlockPoolChanges,
balance_store: StoreDeltas,
pool_store: StoreGetProto<ProtocolComponent>,
) -> Result<BlockContractChanges, substreams::errors::Error> {
let mut block_changes = BlockContractChanges::default();
) -> Result<BlockChanges, substreams::errors::Error> {
let mut block_changes = BlockChanges::default();
let mut tx_change = TransactionContractChanges::default();
let mut transaction_changes = TransactionChanges::default();
let mut changed_contracts: HashMap<Vec<u8>, InterimContractChange> = HashMap::new();
@@ -93,6 +95,13 @@ fn map_changes(
.collect();
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
@@ -244,44 +253,37 @@ fn map_changes(
// 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,
});
transaction_changes.tx = Some(tx.clone());
// 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
transaction_changes
.contract_changes
.push(change.into())
}
block_changes
.changes
.push(tx_change.clone());
.push(transaction_changes.clone());
// clear out the interim contract changes after we pushed those.
tx_change.tx = None;
tx_change.contract_changes.clear();
transaction_changes.tx = None;
transaction_changes
.contract_changes
.clear();
}
}
// extract new protocol components
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;
for component in &block_pool_changes.new_components {
grouped_components
.entry(tx_hash)
.entry(component.tx_index)
.or_insert_with(Vec::new)
.push(component.clone());
}
for (tx_hash, components) in grouped_components {
for (tx_index, components) in grouped_components {
if let Some(tx_change) = block_changes
.changes
.iter_mut()
@@ -290,14 +292,20 @@ fn map_changes(
tx_change
.tx
.as_ref()
.is_some_and(|tx| tx.hash == tx_hash)
.is_some_and(|tx| tx.index == tx_index)
})
{
let new_components = components
.into_iter()
.map(Into::into)
.collect::<Vec<ProtocolComponent>>();
tx_change
.component_changes
.extend(components);
.extend(new_components);
}
}
// extract component balance changes
let mut balance_changes = HashMap::new();
balance_store
.deltas
@@ -323,17 +331,12 @@ fn map_changes(
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)
.entry(balance_delta.tx_index)
.or_insert_with(Vec::new)
.push(balance_change);
});
for (tx_hash, grouped_balance_changes) in balance_changes {
for (tx_index, grouped_balance_changes) in balance_changes {
if let Some(tx_change) = block_changes
.changes
.iter_mut()
@@ -342,7 +345,7 @@ fn map_changes(
tx_change
.tx
.as_ref()
.is_some_and(|tx| tx.hash == tx_hash)
.is_some_and(|tx| tx.index == tx_index)
})
{
tx_change

View File

@@ -0,0 +1,10 @@
// @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)
}
}
}

View File

@@ -15,9 +15,25 @@ pub struct AmbientBalanceDelta {
/// 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>,
/// Transaction index of the balance change
#[prost(uint64, tag="5")]
pub tx_index: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AmbientProtocolComponent {
/// A unique identifier for the component within the protocol.
#[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>>,
/// Ambient pool index [static attribute for ambient pools]
#[prost(bytes="vec", tag="3")]
pub pool_index: ::prost::alloc::vec::Vec<u8>,
/// Transaction index for the component creation
#[prost(uint64, tag="4")]
pub tx_index: u64,
}
/// Ambient pool changes within a single block
#[allow(clippy::derive_partial_eq_without_eq)]
@@ -25,8 +41,8 @@ pub struct AmbientBalanceDelta {
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
pub new_components: ::prost::alloc::vec::Vec<AmbientProtocolComponent>,
/// Balance changes on this block
#[prost(message, repeated, tag="2")]
pub balance_deltas: ::prost::alloc::vec::Vec<AmbientBalanceDelta>,
}

View File

@@ -1,21 +1,20 @@
specVersion: v0.1.0
package:
name: "substreams_ethereum_ambient"
version: v0.5.1
version: v0.6.0
protobuf:
files:
- tycho/evm/v1/common.proto
- tycho/evm/v1/vm.proto
- ambient.proto
importPaths:
- ./proto/v1
- ../../proto/
- ./proto
- ../../proto
binaries:
default:
type: wasm/rust-v1
file: ../../target/wasm32-unknown-unknown/substreams/substreams_ethereum_ambient.wasm
file: ../target/wasm32-unknown-unknown/release/substreams_ethereum_ambient.wasm
modules:
- name: map_pool_changes
@@ -24,7 +23,7 @@ modules:
inputs:
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.BlockPoolChanges
type: proto:tycho.ambient.v1.BlockPoolChanges
- name: store_pools
kind: store
initialBlock: 17361664
@@ -49,4 +48,4 @@ modules:
mode: deltas
- store: store_pools
output:
type: proto:tycho.evm.v1.BlockContractChanges
type: proto:tycho.evm.v1.BlockChanges