feat(template): More detailed template.
Add a more detailed protocol implementation in the template. This should allow more ppl to get started quicker. Additionally more people will follow a predetermined structure
This commit is contained in:
54
substreams/Cargo.lock
generated
54
substreams/Cargo.lock
generated
@@ -300,6 +300,23 @@ dependencies = [
|
||||
"tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-template"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ethabi 18.0.0",
|
||||
"hex",
|
||||
"itertools 0.10.5",
|
||||
"num-bigint",
|
||||
"prost 0.11.9",
|
||||
"serde",
|
||||
"serde-sibor",
|
||||
"substreams",
|
||||
"substreams-ethereum",
|
||||
"tycho-substreams 0.2.0 (git+https://github.com/propeller-heads/tycho-protocol-sdk.git?rev=b8aeaa3)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-types"
|
||||
version = "0.13.1"
|
||||
@@ -780,7 +797,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -872,9 +889,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -944,7 +961,7 @@ dependencies = [
|
||||
"itertools 0.12.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1127,22 +1144,32 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.216"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.216"
|
||||
name = "serde-sibor"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
checksum = "859877cf5123a0b2c03e92b01c0b7adefb5ea0b6e6a390f2f7107b85783eee14"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1409,9 +1436,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.90"
|
||||
version = "2.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1462,8 +1489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
"quote", "syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -10,6 +10,7 @@ members = [
|
||||
"ethereum-sfrax",
|
||||
"ethereum-sfraxeth",
|
||||
"ethereum-uniswap-v3-logs-only",
|
||||
"ethereum-template",
|
||||
"ethereum-uniswap-v4"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -58,7 +58,7 @@ pub fn store_components(map: BlockTransactionProtocolComponents, store: StoreSet
|
||||
}
|
||||
|
||||
/// Since the `PoolBalanceChanged` and `Swap` events administer only deltas, we need to leverage a
|
||||
/// map and a store to be able to tally up final balances for tokens in a pool.
|
||||
/// map and a store to be able to tally up final balances for tokens in a pool.
|
||||
#[substreams::handlers::map]
|
||||
pub fn map_relative_balances(
|
||||
block: eth::v2::Block,
|
||||
@@ -253,7 +253,7 @@ pub fn map_protocol_changes(
|
||||
let id = components_store
|
||||
.get_last(format!("pool:0x{}", hex::encode(address)))
|
||||
.unwrap(); // Shouldn't happen because we filter by known components in
|
||||
// `extract_contract_changes_builder`
|
||||
// `extract_contract_changes_builder`
|
||||
change.mark_component_as_updated(&id);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -12,3 +12,15 @@ substreams = "0.5.22"
|
||||
substreams-ethereum = "0.9.9"
|
||||
prost = "0.11"
|
||||
tycho-substreams = { git = "https://github.com/propeller-heads/tycho-protocol-sdk.git", rev = "b8aeaa3" }
|
||||
anyhow = "1.0.95"
|
||||
ethabi = "18.0.0"
|
||||
num-bigint = "0.4.6"
|
||||
hex = "0.4.3"
|
||||
itertools = "0.10.5"
|
||||
serde = "1.0.217"
|
||||
serde-sibor = "0.1.0"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1"
|
||||
substreams-ethereum = "0.9.9"
|
||||
|
||||
222
substreams/ethereum-template/abi/erc20.json
Normal file
222
substreams/ethereum-template/abi/erc20.json
Normal file
@@ -0,0 +1,222 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
49
substreams/ethereum-template/build.rs
Normal file
49
substreams/ethereum-template/build.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use anyhow::Result;
|
||||
use std::{fs, io::Write};
|
||||
use substreams_ethereum::Abigen;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let abi_folder = "abi";
|
||||
let output_folder = "src/abi";
|
||||
|
||||
let abis = fs::read_dir(abi_folder)?;
|
||||
|
||||
let mut files = abis.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Sort the files by their name
|
||||
files.sort_by_key(|a| a.file_name());
|
||||
|
||||
let mut mod_rs_content = String::new();
|
||||
mod_rs_content.push_str("#![allow(clippy::all)]\n");
|
||||
|
||||
for file in files {
|
||||
let file_name = file.file_name();
|
||||
let file_name = file_name.to_string_lossy();
|
||||
|
||||
if !file_name.ends_with(".json") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let contract_name = file_name.split('.').next().unwrap();
|
||||
|
||||
let input_path = format!("{}/{}", abi_folder, file_name);
|
||||
let output_path = format!("{}/{}.rs", output_folder, contract_name);
|
||||
|
||||
mod_rs_content.push_str(&format!("pub mod {};\n", contract_name));
|
||||
|
||||
if std::path::Path::new(&output_path).exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
Abigen::new(contract_name, &input_path)?
|
||||
.generate()?
|
||||
.write_to_file(&output_path)?;
|
||||
}
|
||||
|
||||
let mod_rs_path = format!("{}/mod.rs", output_folder);
|
||||
let mut mod_rs_file = fs::File::create(mod_rs_path)?;
|
||||
|
||||
mod_rs_file.write_all(mod_rs_content.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1257
substreams/ethereum-template/src/abi/erc20.rs
Normal file
1257
substreams/ethereum-template/src/abi/erc20.rs
Normal file
File diff suppressed because it is too large
Load Diff
2
substreams/ethereum-template/src/abi/mod.rs
Normal file
2
substreams/ethereum-template/src/abi/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
#![allow(clippy::all)]
|
||||
pub mod erc20;
|
||||
@@ -1,9 +1,3 @@
|
||||
use substreams_ethereum::pb::eth;
|
||||
mod modules;
|
||||
|
||||
#[substreams::handlers::map]
|
||||
fn map_changes(
|
||||
block: eth::v2::Block,
|
||||
) -> Result<tycho::BlockContractChanges, substreams::errors::Error> {
|
||||
todo!("Not implemented")
|
||||
}
|
||||
mod modules_factory;
|
||||
mod abi;
|
||||
mod pool_factories;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use substreams_ethereum::pb::eth;
|
||||
use tycho_substreams::prelude::*;
|
||||
|
||||
#[substreams::handlers::map]
|
||||
fn map_protocol_changes(
|
||||
block: eth::v2::Block,
|
||||
) -> Result<BlockContractChanges, substreams::errors::Error> {
|
||||
let mut transaction_contract_changes = Vec::<TransactionContractChanges>::new();
|
||||
// TODO: protocol specific logic goes here
|
||||
Ok(BlockContractChanges { block: Some((&block).into()), changes: transaction_contract_changes })
|
||||
}
|
||||
265
substreams/ethereum-template/src/modules_factory.rs
Normal file
265
substreams/ethereum-template/src/modules_factory.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
//! Template for Protocols with contract factories
|
||||
//!
|
||||
//! This template provides foundational maps and store substream modules for indexing a
|
||||
//! protocol where each component (e.g., pool) is deployed to a separate contract. Each
|
||||
//! contract is expected to escrow its ERC-20 token balances.
|
||||
//!
|
||||
//! If your protocol supports native ETH, you may need to adjust the balance tracking
|
||||
//! logic in `map_relative_component_balance` to account for native token handling.
|
||||
//!
|
||||
//! ## Alternative Module
|
||||
//! If your protocol uses a vault-like contract to manage balances, or if pools are
|
||||
//! registered within a singleton contract, refer to the `modules_singleton` module
|
||||
//! for an appropriate alternative.
|
||||
//!
|
||||
//! ## Warning
|
||||
//! This template provides a general framework for indexing a protocol. However, it is
|
||||
//! likely that you will need to adapt the steps to suit your specific use case. Use the
|
||||
//! provided code with care and ensure you fully understand each step before proceeding
|
||||
//! with your implementation.
|
||||
//!
|
||||
//! ## Example Use Case
|
||||
//! For an Uniswap-like protocol where each liquidity pool is deployed as a separate
|
||||
//! contract, you can use this template to:
|
||||
//! - Track relative component balances (e.g., ERC-20 token balances in each pool).
|
||||
//! - Index individual pool contracts as they are created by the factory contract.
|
||||
//!
|
||||
//! Adjustments to the template may include:
|
||||
//! - Handling native ETH balances alongside token balances.
|
||||
//! - Customizing indexing logic for specific factory contract behavior.
|
||||
use std::collections::HashMap;
|
||||
use anyhow::Result;
|
||||
use substreams::pb::substreams::StoreDeltas;
|
||||
use substreams::prelude::*;
|
||||
use substreams_ethereum::Event;
|
||||
use substreams_ethereum::pb::eth;
|
||||
use tycho_substreams::balances::aggregate_balances_changes;
|
||||
use tycho_substreams::contract::extract_contract_changes_builder;
|
||||
use tycho_substreams::prelude::*;
|
||||
use itertools::Itertools;
|
||||
use crate::pool_factories;
|
||||
|
||||
/// Find and create all relevant protocol components
|
||||
///
|
||||
/// This method maps over blocks and instantiates ProtocolComponents with a unique ids
|
||||
/// as well as all necessary metadata for routing and encoding.
|
||||
#[substreams::handlers::map]
|
||||
fn map_protocol_components(
|
||||
block: eth::v2::Block
|
||||
) -> Result<BlockTransactionProtocolComponents> {
|
||||
Ok(BlockTransactionProtocolComponents {
|
||||
tx_components: block
|
||||
.transactions()
|
||||
.filter_map(|tx| {
|
||||
let components = tx
|
||||
.logs_with_calls()
|
||||
.filter_map(|(log, call)| {
|
||||
// TODO: ensure this method is implemented correctly
|
||||
pool_factories::maybe_create_component(
|
||||
call.call,
|
||||
log,
|
||||
tx,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !components.is_empty() {
|
||||
Some(TransactionProtocolComponents { tx: Some(tx.into()), components })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Stores all protocol components in a store.
|
||||
///
|
||||
/// Stores information about components in a key value store. This is only necessary if
|
||||
/// you need to access the whole set of components within your indexing logic.
|
||||
///
|
||||
/// Popular use cases are:
|
||||
/// - Checking if a contract belongs to a component. In this case suggest to use an
|
||||
/// address as the store key so lookup operations are O(1).
|
||||
/// - Tallying up relative balances changes to calcualte absolute erc20 token balances
|
||||
/// per component.
|
||||
///
|
||||
/// Usually you can skip this step if:
|
||||
/// - You are interested in a static set of components only
|
||||
/// - Your protocol emits balance change events with absolute values
|
||||
#[substreams::handlers::store]
|
||||
fn store_protocol_components(map_protocol_components: BlockTransactionProtocolComponents, store: StoreSetRaw) {
|
||||
map_protocol_components.tx_components
|
||||
.into_iter()
|
||||
.for_each(|tx_pc| {
|
||||
tx_pc
|
||||
.components
|
||||
.into_iter()
|
||||
.for_each(|pc| {
|
||||
// Assumes that the component id is a hex encoded contract address
|
||||
let key = pc.id.clone();
|
||||
// we store the components tokens
|
||||
// TODO: proper error handling
|
||||
let val = serde_sibor::to_bytes(&pc.tokens).unwrap();
|
||||
store.set(0, key, &val);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// Extracts balance changes per component
|
||||
///
|
||||
/// This template function uses ERC20 transfer events to extract balance changes. It
|
||||
/// assumes that each component is deployed at a dedicated contract address. If a
|
||||
/// transfer to the component is detected, it's balanced is increased and if a balance
|
||||
/// from the component is detected its balance is decreased.
|
||||
///
|
||||
/// ## Note:
|
||||
/// Changes are necessary if your protocol uses native ETH, uses a vault contract or if
|
||||
/// your component burn or mint tokens without emitting transfer events.
|
||||
///
|
||||
/// You may want to ignore LP tokens if your protocol emits transfer events for these
|
||||
/// here.
|
||||
#[substreams::handlers::map]
|
||||
fn map_relative_component_balance(block: eth::v2::Block, store: StoreGetRaw) -> Result<BlockBalanceDeltas> {
|
||||
let res = block.logs()
|
||||
.filter_map(|log| {
|
||||
crate::abi::erc20::events::Transfer::match_and_decode(log).map(|transfer| {
|
||||
let to_addr = hex::encode(transfer.to.as_slice());
|
||||
let from_addr = hex::encode(transfer.from.as_slice());
|
||||
let tx = log.receipt.transaction;
|
||||
if let Some(val) = store.get_last(&to_addr) {
|
||||
let component_tokens: Vec<Vec<u8>> = serde_sibor::from_bytes(&val).unwrap();
|
||||
if component_tokens.contains(&log.address().to_vec()) {
|
||||
return Some(BalanceDelta {
|
||||
ord: log.ordinal(),
|
||||
tx: Some(tx.into()),
|
||||
token: log.address().to_vec(),
|
||||
delta: transfer.value.to_signed_bytes_be(),
|
||||
component_id: to_addr.into_bytes(),
|
||||
});
|
||||
}
|
||||
} else if let Some(val) = store.get_last(&from_addr) {
|
||||
let component_tokens: Vec<Vec<u8>> = serde_sibor::from_bytes(&val).unwrap();
|
||||
if component_tokens.contains(&log.address().to_vec()) {
|
||||
return Some(BalanceDelta {
|
||||
ord: log.ordinal(),
|
||||
tx: Some(tx.into()),
|
||||
token: log.address().to_vec(),
|
||||
delta: (transfer.value.neg()).to_signed_bytes_be(),
|
||||
component_id: to_addr.into_bytes(),
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(BlockBalanceDeltas { balance_deltas: res })
|
||||
}
|
||||
|
||||
/// Aggregates relative balances values into absolute values
|
||||
///
|
||||
/// Aggregate the relative balances in an additive store since tycho-indexer expects
|
||||
/// absolute balance inputs.
|
||||
///
|
||||
/// ## Note:
|
||||
/// This method should usually not require any changes.
|
||||
#[substreams::handlers::store]
|
||||
pub fn store_balances(deltas: BlockBalanceDeltas, store: StoreAddBigInt) {
|
||||
tycho_substreams::balances::store_balance_changes(deltas, store);
|
||||
}
|
||||
|
||||
/// Aggregates protocol components and balance changes by transaction.
|
||||
///
|
||||
/// This is the main method that will aggregate all changes as well as extract all
|
||||
/// relevant contract storage deltas.
|
||||
///
|
||||
/// ## Note:
|
||||
/// You may have to change this method if your components have any default dynamic
|
||||
/// attributes, or if you need any additional static contracts indexed.
|
||||
#[substreams::handlers::map]
|
||||
fn map_protocol_changes(
|
||||
block: eth::v2::Block,
|
||||
new_components: BlockTransactionProtocolComponents,
|
||||
components_store: StoreGetRaw,
|
||||
balance_store: StoreDeltas,
|
||||
deltas: BlockBalanceDeltas,
|
||||
) -> Result<BlockChanges, substreams::errors::Error> {
|
||||
// We merge contract changes by transaction (identified by transaction index)
|
||||
// making it easy to sort them at the very end.
|
||||
let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new();
|
||||
|
||||
// Aggregate newly created components per tx
|
||||
new_components
|
||||
.tx_components
|
||||
.iter()
|
||||
.for_each(|tx_component| {
|
||||
// initialise builder if not yet present for this tx
|
||||
let tx = tx_component.tx.as_ref().unwrap();
|
||||
let builder = transaction_changes
|
||||
.entry(tx.index)
|
||||
.or_insert_with(|| TransactionChangesBuilder::new(tx));
|
||||
|
||||
// iterate over individual components created within this tx
|
||||
tx_component
|
||||
.components
|
||||
.iter()
|
||||
.for_each(|component| {
|
||||
builder.add_protocol_component(component);
|
||||
// TODO: In case you require to add any dynamic attributes to the
|
||||
// component you can do so here:
|
||||
/*
|
||||
builder.add_entity_change(&EntityChanges {
|
||||
component_id: component.id.clone(),
|
||||
attributes: default_attributes.clone(),
|
||||
});
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
// Aggregate absolute balances per transaction.
|
||||
aggregate_balances_changes(balance_store, deltas)
|
||||
.into_iter()
|
||||
.for_each(|(_, (tx, balances))| {
|
||||
let builder = transaction_changes
|
||||
.entry(tx.index)
|
||||
.or_insert_with(|| TransactionChangesBuilder::new(&tx));
|
||||
balances
|
||||
.values()
|
||||
.for_each(|token_bc_map| {
|
||||
token_bc_map
|
||||
.values()
|
||||
.for_each(|bc| builder.add_balance_change(bc))
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Extract and insert any storage changes that happened for any of the components.
|
||||
extract_contract_changes_builder(
|
||||
&block,
|
||||
|addr| {
|
||||
// we assume that the store holds contract addresses as keys and if it
|
||||
// contains a value, that contract is of relevance.
|
||||
// TODO: if you have any additional static contracts that need to be indexed,
|
||||
// please add them here.
|
||||
components_store
|
||||
.get_last(hex::encode(addr))
|
||||
.is_some()
|
||||
},
|
||||
&mut transaction_changes,
|
||||
);
|
||||
|
||||
|
||||
// Process all `transaction_changes` for final output in the `BlockChanges`,
|
||||
// sorted by transaction index (the key).
|
||||
Ok(BlockChanges {
|
||||
block: Some((&block).into()),
|
||||
changes: transaction_changes
|
||||
.drain()
|
||||
.sorted_unstable_by_key(|(index, _)| *index)
|
||||
.filter_map(|(_, builder)| builder.build())
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
44
substreams/ethereum-template/src/pool_factories.rs
Normal file
44
substreams/ethereum-template/src/pool_factories.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use substreams::hex;
|
||||
use substreams_ethereum::pb::eth::v2::{Call, Log, TransactionTrace};
|
||||
use tycho_substreams::models::{ChangeType, FinancialType, ImplementationType, ProtocolComponent, ProtocolType};
|
||||
|
||||
|
||||
/// Potentially constructs a new ProtocolComponent given a call
|
||||
///
|
||||
/// This method is given each individual call within a transaction, the corresponding
|
||||
/// logs emitted during that call as well as the full transaction trace.
|
||||
///
|
||||
/// If this call creates a component in your protocol please contstruct and return it
|
||||
/// here. Otherwise, simply return None.
|
||||
pub fn maybe_create_component(
|
||||
call: &Call,
|
||||
_log: &Log,
|
||||
tx: &TransactionTrace,
|
||||
) -> Option<ProtocolComponent> {
|
||||
match *call.address {
|
||||
// TODO: replace with your logic
|
||||
hex!("0000000000000000000000000000000000000000") => {
|
||||
Some(ProtocolComponent {
|
||||
id: "".to_string(),
|
||||
tokens: vec![
|
||||
// TODO: add the components tokens
|
||||
],
|
||||
contracts: vec![
|
||||
// TODO: any contracts required during swapping
|
||||
],
|
||||
static_att: vec![
|
||||
// TODO: any additional metadata required, e.g. for swap encoding
|
||||
],
|
||||
change: ChangeType::Creation.into(),
|
||||
protocol_type: Some(ProtocolType {
|
||||
name: "template".to_string(),
|
||||
financial_type: FinancialType::Swap.into(),
|
||||
attribute_schema: vec![],
|
||||
implementation_type: ImplementationType::Vm.into(),
|
||||
}),
|
||||
tx: Some(tx.into()),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user