From 7db72c284c9284d8bf8a622c97fb6ed439ec1f6c Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:03:26 +0200 Subject: [PATCH 01/10] feat: Update proto files --- proto/tycho/evm/v1/common.proto | 60 ++++++++ proto/tycho/evm/v1/entity.proto | 9 +- proto/tycho/evm/v1/vm.proto | 26 +--- .../tycho-substreams/src/pb/tycho.evm.v1.rs | 128 ++++++++++++------ 4 files changed, 147 insertions(+), 76 deletions(-) diff --git a/proto/tycho/evm/v1/common.proto b/proto/tycho/evm/v1/common.proto index 7f6d369..8da97e7 100644 --- a/proto/tycho/evm/v1/common.proto +++ b/proto/tycho/evm/v1/common.proto @@ -106,3 +106,63 @@ message BalanceChange { // If the protocol component includes multiple contracts, the balance change must be aggregated to reflect how much tokens can be traded. bytes component_id = 3; } + +// Native entities + +// A component is a set of attributes that are associated with a custom entity. +message EntityChanges { + // A unique identifier of the entity within the protocol. + string component_id = 1; + // The set of attributes that are associated with the entity. + repeated Attribute attributes = 2; +} + +// VM entities + +// A key value entry into contract storage. +message ContractSlot { + // A contract's storage slot. + bytes slot = 2; + // The new value for this storage slot. + bytes value = 3; +} + +// Changes made to a single contract's state. +message ContractChange { + // The contract's address + bytes address = 1; + // The new balance of the contract, empty bytes indicates no change. + bytes balance = 2; + // The new code of the contract, empty bytes indicates no change. + bytes code = 3; + // The changes to this contract's slots, empty sequence indicates no change. + repeated ContractSlot slots = 4; + // Whether this is an update, a creation or a deletion. + ChangeType change = 5; +} + +// Aggregate entities + +// A set of changes aggregated by transaction. +message TransactionChanges { + // The transaction instance that results in the changes. + Transaction tx = 1; + // Contains the changes induced by the above transaction, aggregated on a per-contract basis. + // Contains the contract changes induced by the above transaction, usually for tracking VM components. + repeated ContractChange contract_changes = 2; + // Contains the entity changes induced by the above transaction. + // Usually for tracking native components or used for VM extensions (plugins). + repeated EntityChanges entity_changes = 3; + // An array of newly added components. + repeated ProtocolComponent component_changes = 4; + // An array of balance changes to components. + repeated BalanceChange balance_changes = 5; +} + +// A set of transaction changes within a single block. +message BlockChanges { + // The block for which these changes are collectively computed. + Block block = 1; + // The set of transaction changes observed in the specified block. + repeated TransactionChanges changes = 2; +} \ No newline at end of file diff --git a/proto/tycho/evm/v1/entity.proto b/proto/tycho/evm/v1/entity.proto index 14539e4..221b309 100644 --- a/proto/tycho/evm/v1/entity.proto +++ b/proto/tycho/evm/v1/entity.proto @@ -4,16 +4,9 @@ package tycho.evm.v1; import "tycho/evm/v1/common.proto"; +// WARNING: DEPRECATED. Please use common.proto's TransactionChanges and BlockChanges instead. // This file contains the definition for the native integration of Substreams. -// A component is a set of attributes that are associated with a custom entity. -message EntityChanges { - // A unique identifier of the entity within the protocol. - string component_id = 1; - // The set of attributes that are associated with the entity. - repeated Attribute attributes = 2; -} - message TransactionEntityChanges { Transaction tx = 1; repeated EntityChanges entity_changes = 2; diff --git a/proto/tycho/evm/v1/vm.proto b/proto/tycho/evm/v1/vm.proto index a49dcf0..3f36fdd 100644 --- a/proto/tycho/evm/v1/vm.proto +++ b/proto/tycho/evm/v1/vm.proto @@ -4,38 +4,16 @@ package tycho.evm.v1; import "tycho/evm/v1/common.proto"; +// WARNING: DEPRECATED. Please use common.proto's TransactionChanges and BlockChanges instead. // This file contains proto definitions specific to the VM integration. -// A key value entry into contract storage. -message ContractSlot { - // A contract's storage slot. - bytes slot = 2; - // The new value for this storage slot. - bytes value = 3; -} - -// Changes made to a single contract's state. -message ContractChange { - // The contract's address - bytes address = 1; - // The new native balance of the contract, empty bytes indicates no change. - bytes balance = 2; - // The new code of the contract, empty bytes indicates no change. - bytes code = 3; - // The changes to this contract's slots, empty sequence indicates no change. - repeated ContractSlot slots = 4; - // Whether this is an update, a creation or a deletion. - ChangeType change = 5; -} - // A set of changes aggregated by transaction. message TransactionContractChanges { // The transaction instance that results in the changes. Transaction tx = 1; // Contains the changes induced by the above transaction, aggregated on a per-contract basis. - // Must include changes to every contract that is tracked by all ProtocolComponents. repeated ContractChange contract_changes = 2; - // An array of any component changes. + // An array of newly added components. repeated ProtocolComponent component_changes = 3; // An array of balance changes to components. repeated BalanceChange balance_changes = 4; 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 5409eff..39cbcb3 100644 --- a/substreams/crates/tycho-substreams/src/pb/tycho.evm.v1.rs +++ b/substreams/crates/tycho-substreams/src/pb/tycho.evm.v1.rs @@ -115,6 +115,87 @@ pub struct BalanceChange { #[prost(bytes="vec", tag="3")] pub component_id: ::prost::alloc::vec::Vec, } +// Native entities + +/// A component is a set of attributes that are associated with a custom entity. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct EntityChanges { + /// A unique identifier of the entity within the protocol. + #[prost(string, tag="1")] + pub component_id: ::prost::alloc::string::String, + /// The set of attributes that are associated with the entity. + #[prost(message, repeated, tag="2")] + pub attributes: ::prost::alloc::vec::Vec, +} +// VM entities + +/// A key value entry into contract storage. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ContractSlot { + /// A contract's storage slot. + #[prost(bytes="vec", tag="2")] + pub slot: ::prost::alloc::vec::Vec, + /// The new value for this storage slot. + #[prost(bytes="vec", tag="3")] + pub value: ::prost::alloc::vec::Vec, +} +/// 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, + /// The new balance of the contract, empty bytes indicates no change. + #[prost(bytes="vec", tag="2")] + pub balance: ::prost::alloc::vec::Vec, + /// The new code of the contract, empty bytes indicates no change. + #[prost(bytes="vec", tag="3")] + pub code: ::prost::alloc::vec::Vec, + /// The changes to this contract's slots, empty sequence indicates no change. + #[prost(message, repeated, tag="4")] + pub slots: ::prost::alloc::vec::Vec, + /// Whether this is an update, a creation or a deletion. + #[prost(enumeration="ChangeType", tag="5")] + pub change: i32, +} +// Aggregate entities + +/// A set of changes aggregated by transaction. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TransactionChanges { + /// The transaction instance that results in the changes. + #[prost(message, optional, tag="1")] + pub tx: ::core::option::Option, + /// Contains the changes induced by the above transaction, aggregated on a per-contract basis. + /// Contains the contract changes induced by the above transaction, usually for tracking VM components. + #[prost(message, repeated, tag="2")] + pub contract_changes: ::prost::alloc::vec::Vec, + /// Contains the entity changes induced by the above transaction. + /// Usually for tracking native components or used for VM extensions (plugins). + #[prost(message, repeated, tag="3")] + pub entity_changes: ::prost::alloc::vec::Vec, + /// An array of newly added components. + #[prost(message, repeated, tag="4")] + pub component_changes: ::prost::alloc::vec::Vec, + /// An array of balance changes to components. + #[prost(message, repeated, tag="5")] + pub balance_changes: ::prost::alloc::vec::Vec, +} +/// A set of transaction changes within a single block. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockChanges { + /// The block for which these changes are collectively computed. + #[prost(message, optional, tag="1")] + pub block: ::core::option::Option, + /// The set of transaction changes observed in the specified block. + #[prost(message, repeated, tag="2")] + pub changes: ::prost::alloc::vec::Vec, +} /// Enum to specify the type of a change. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] @@ -206,19 +287,9 @@ impl ImplementationType { } } } +// WARNING: DEPRECATED. Please use common.proto's TransactionChanges and BlockChanges instead. // This file contains the definition for the native integration of Substreams. -/// A component is a set of attributes that are associated with a custom entity. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EntityChanges { - /// A unique identifier of the entity within the protocol. - #[prost(string, tag="1")] - pub component_id: ::prost::alloc::string::String, - /// The set of attributes that are associated with the entity. - #[prost(message, repeated, tag="2")] - pub attributes: ::prost::alloc::vec::Vec, -} #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TransactionEntityChanges { @@ -293,39 +364,9 @@ pub struct BlockTransactionProtocolComponents { #[prost(message, repeated, tag="1")] pub tx_components: ::prost::alloc::vec::Vec, } +// WARNING: DEPRECATED. Please use common.proto's TransactionChanges and BlockChanges instead. // 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, - /// The new value for this storage slot. - #[prost(bytes="vec", tag="3")] - pub value: ::prost::alloc::vec::Vec, -} -/// 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, - /// The new native balance of the contract, empty bytes indicates no change. - #[prost(bytes="vec", tag="2")] - pub balance: ::prost::alloc::vec::Vec, - /// The new code of the contract, empty bytes indicates no change. - #[prost(bytes="vec", tag="3")] - pub code: ::prost::alloc::vec::Vec, - /// The changes to this contract's slots, empty sequence indicates no change. - #[prost(message, repeated, tag="4")] - pub slots: ::prost::alloc::vec::Vec, - /// 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)] @@ -334,10 +375,9 @@ pub struct TransactionContractChanges { #[prost(message, optional, tag="1")] pub tx: ::core::option::Option, /// Contains the changes induced by the above transaction, aggregated on a per-contract basis. - /// Must include changes to every contract that is tracked by all ProtocolComponents. #[prost(message, repeated, tag="2")] pub contract_changes: ::prost::alloc::vec::Vec, - /// An array of any component changes. + /// An array of newly added components. #[prost(message, repeated, tag="3")] pub component_changes: ::prost::alloc::vec::Vec, /// An array of balance changes to components. From c48532a5c4c5253e8f7e927c278f9df40c0335d3 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:04:39 +0200 Subject: [PATCH 02/10] feat: Update protobuf messages for Balancer, `add pool_id` and `balance_owner` as entity changes --- .../crates/tycho-substreams/src/contract.rs | 13 ++-- .../crates/tycho-substreams/src/models.rs | 14 ++-- substreams/ethereum-balancer/src/modules.rs | 75 +++++++++++++++---- substreams/ethereum-balancer/substreams.yaml | 6 +- 4 files changed, 76 insertions(+), 32 deletions(-) diff --git a/substreams/crates/tycho-substreams/src/contract.rs b/substreams/crates/tycho-substreams/src/contract.rs index 250da36..7a00283 100644 --- a/substreams/crates/tycho-substreams/src/contract.rs +++ b/substreams/crates/tycho-substreams/src/contract.rs @@ -83,9 +83,8 @@ impl From for tycho::ContractChange { /// model. /// * `inclusion_predicate` - A closure that determines if a contract's address is of interest for /// the collection of changes. Only contracts satisfying this predicate are included. -/// * `transaction_contract_changes` - A mutable reference to a map where extracted contract changes -/// are stored. Keyed by transaction index, it aggregates changes into -/// `tycho::TransactionContractChanges`. +/// * `transaction_changes` - A mutable reference to a map where extracted contract changes are +/// stored. Keyed by transaction index, it aggregates changes into `tycho::TransactionChanges`. /// /// ## Panics /// Panics if the provided block is not an extended block model, as indicated by its detail level. @@ -94,7 +93,7 @@ impl From for tycho::ContractChange { /// The function iterates over transactions and their calls within the block, collecting contract /// changes (storage, balance, code) that pass the inclusion predicate. Changes are then sorted by /// their ordinals to maintain the correct sequence of events. Aggregated changes for each contract -/// are stored in `transaction_contract_changes`, categorized by transaction index. +/// are stored in `transaction_changes`, categorized by transaction index. /// /// Contracts created within the block are tracked to differentiate between new and existing /// contracts. The aggregation process respects transaction boundaries, ensuring that changes are @@ -102,7 +101,7 @@ impl From for tycho::ContractChange { pub fn extract_contract_changes bool>( block: ð::v2::Block, inclusion_predicate: F, - transaction_contract_changes: &mut HashMap, + transaction_changes: &mut HashMap, ) { if block.detail_level != Into::::into(DetailLevel::DetaillevelExtended) { panic!("Only extended blocks are supported"); @@ -209,9 +208,9 @@ pub fn extract_contract_changes bool>( !balance_changes.is_empty() || !code_changes.is_empty() { - transaction_contract_changes + transaction_changes .entry(block_tx.index.into()) - .or_insert_with(|| tycho::TransactionContractChanges::new(&(block_tx.into()))) + .or_insert_with(|| tycho::TransactionChanges::new(&(block_tx.into()))) .contract_changes .extend( changed_contracts diff --git a/substreams/crates/tycho-substreams/src/models.rs b/substreams/crates/tycho-substreams/src/models.rs index fa0fa07..a176d2d 100644 --- a/substreams/crates/tycho-substreams/src/models.rs +++ b/substreams/crates/tycho-substreams/src/models.rs @@ -6,12 +6,14 @@ pub use crate::pb::tycho::evm::v1::*; impl TransactionContractChanges { /// Creates a new empty `TransactionContractChanges` instance. pub fn new(tx: &Transaction) -> Self { - Self { - tx: Some(tx.clone()), - contract_changes: vec![], - component_changes: vec![], - balance_changes: vec![], - } + Self { tx: Some(tx.clone()), ..Default::default() } + } +} + +impl TransactionChanges { + /// Creates a new empty `TransactionChanges` instance. + pub fn new(tx: &Transaction) -> Self { + Self { tx: Some(tx.clone()), ..Default::default() } } } diff --git a/substreams/ethereum-balancer/src/modules.rs b/substreams/ethereum-balancer/src/modules.rs index 565c672..62fcdf6 100644 --- a/substreams/ethereum-balancer/src/modules.rs +++ b/substreams/ethereum-balancer/src/modules.rs @@ -24,7 +24,6 @@ pub fn map_components(block: eth::v2::Block) -> Result Result { +) -> Result { // We merge contract changes by transaction (identified by transaction index) making it easy to // sort them at the very end. - let mut transaction_contract_changes: HashMap<_, TransactionContractChanges> = HashMap::new(); + let mut transaction_changes: HashMap<_, TransactionChanges> = HashMap::new(); // `ProtocolComponents` are gathered from `map_pools_created` which just need a bit of work to - // convert into `TransactionContractChanges` + // convert into `TransactionChanges` grouped_components .tx_components .iter() .for_each(|tx_component| { let tx = tx_component.tx.as_ref().unwrap(); - transaction_contract_changes + transaction_changes .entry(tx.index) - .or_insert_with(|| TransactionContractChanges::new(tx)) + .or_insert_with(|| TransactionChanges::new(tx)) .component_changes .extend_from_slice(&tx_component.components); }); + block + .transactions() + .flat_map(|tx| { + let components = tx + .logs_with_calls() + .filter(|(log, _)| log.address == VAULT_ADDRESS) + .filter_map(|(log, _)| { + let registered = abi::vault::events::PoolRegistered::match_and_decode(log)?; + substreams::log::info!("{:?}", log); + Some(( + tx.clone(), + EntityChanges { + component_id: hex::encode(registered.pool_address), + attributes: vec![ + Attribute { + name: "pool_id".to_string(), + value: format!("0x{}", hex::encode(registered.pool_id)) + .as_bytes() + .to_vec(), + change: ChangeType::Update.into(), + }, + Attribute { + name: "balance_owner".to_string(), + value: "0xBA12222222228d8Ba445958a75a0704d566BF2C8" + .to_string() + .as_bytes() + .to_vec(), + change: ChangeType::Creation.into(), + }, + ], + }, + )) + }); + components + }) + .for_each(|(tx, state_change)| { + transaction_changes + .entry(tx.index.into()) + .or_insert_with(|| TransactionChanges::new(&(&tx).into())) + .entity_changes + .push(state_change); + }); + // Balance changes are gathered by the `StoreDelta` based on `PoolBalanceChanged` creating // `BlockBalanceDeltas`. We essentially just process the changes that occurred to the `store` // this block. Then, these balance changes are merged onto the existing map of tx contract @@ -170,9 +212,9 @@ pub fn map_protocol_changes( aggregate_balances_changes(balance_store, deltas) .into_iter() .for_each(|(_, (tx, balances))| { - transaction_contract_changes + transaction_changes .entry(tx.index) - .or_insert_with(|| TransactionContractChanges::new(&tx)) + .or_insert_with(|| TransactionChanges::new(&tx)) .balance_changes .extend(balances.into_values()); }); @@ -185,20 +227,21 @@ pub fn map_protocol_changes( .get_last(format!("pool:0x{0}", hex::encode(addr))) .is_some() }, - &mut transaction_contract_changes, + &mut transaction_changes, ); - // Process all `transaction_contract_changes` for final output in the `BlockContractChanges`, + // Process all `transaction_changes` for final output in the `BlockChanges`, // sorted by transaction index (the key). - Ok(BlockContractChanges { + Ok(BlockChanges { block: Some((&block).into()), - changes: transaction_contract_changes + changes: transaction_changes .drain() .sorted_unstable_by_key(|(index, _)| *index) .filter_map(|(_, change)| { if change.contract_changes.is_empty() && change.component_changes.is_empty() && - change.balance_changes.is_empty() + change.balance_changes.is_empty() && + change.entity_changes.is_empty() { None } else { diff --git a/substreams/ethereum-balancer/substreams.yaml b/substreams/ethereum-balancer/substreams.yaml index 99810e2..5d61749 100644 --- a/substreams/ethereum-balancer/substreams.yaml +++ b/substreams/ethereum-balancer/substreams.yaml @@ -23,7 +23,7 @@ modules: inputs: - source: sf.ethereum.type.v2.Block output: - type: proto:tycho.evm.v1.GroupedTransactionProtocolComponents + type: proto:tycho.evm.v1.BlockTransactionProtocolComponents - name: store_components kind: store @@ -40,7 +40,7 @@ modules: - source: sf.ethereum.type.v2.Block - store: store_components output: - type: proto:tycho.evm.v1.BalanceDeltas + type: proto:tycho.evm.v1.BlockBalanceDeltas - name: store_balances kind: store @@ -61,4 +61,4 @@ modules: - store: store_balances mode: deltas # This is the key property that simplifies `BalanceChange` handling output: - type: proto:tycho.evm.v1.BlockContractChanges + type: proto:tycho.evm.v1.BlockChanges From 50ac14f70ad4ef86f8cabba427e0ead8498746cb Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:03:41 +0200 Subject: [PATCH 03/10] style: make clippy happy --- substreams/ethereum-balancer/src/pool_factories.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/substreams/ethereum-balancer/src/pool_factories.rs b/substreams/ethereum-balancer/src/pool_factories.rs index df03123..6563c81 100644 --- a/substreams/ethereum-balancer/src/pool_factories.rs +++ b/substreams/ethereum-balancer/src/pool_factories.rs @@ -30,16 +30,16 @@ impl SerializableVecBigInt for Vec { } /// This is the main function that handles the creation of `ProtocolComponent`s with `Attribute`s -/// based on the specific factory address. There's 3 factory groups that are represented here: +/// based on the specific factory address. There's 3 factory groups that are represented here: /// - Weighted Pool Factories /// - Linear Pool Factories /// - Stable Pool Factories /// /// (Balancer does have a bit more (esp. in the deprecated section) that could be implemented as -/// desired.) +/// desired.) /// We use the specific ABIs to decode both the log event and corresponding call to gather -/// `PoolCreated` event information alongside the `Create` call data that provide us details to -/// fulfill both the required details + any extra `Attributes` +/// `PoolCreated` event information alongside the `Create` call data that provide us details to +/// fulfill both the required details + any extra `Attributes` /// Ref: https://docs.balancer.fi/reference/contracts/deployment-addresses/mainnet.html pub fn address_map( pool_factory_address: &[u8], From 7ce86db2d64a44b05e7f3c96ea4579b5835706e7 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:43:48 +0200 Subject: [PATCH 04/10] fix: correct change type for pool_id --- substreams/ethereum-balancer/src/modules.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substreams/ethereum-balancer/src/modules.rs b/substreams/ethereum-balancer/src/modules.rs index 62fcdf6..6c78aa4 100644 --- a/substreams/ethereum-balancer/src/modules.rs +++ b/substreams/ethereum-balancer/src/modules.rs @@ -181,7 +181,7 @@ pub fn map_protocol_changes( value: format!("0x{}", hex::encode(registered.pool_id)) .as_bytes() .to_vec(), - change: ChangeType::Update.into(), + change: ChangeType::Creation.into(), }, Attribute { name: "balance_owner".to_string(), From f0b82691f86c6dea28a1ed4fbebb88405f5bbdb5 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:46:00 +0200 Subject: [PATCH 05/10] chore: bump versions --- substreams/Cargo.lock | 2 +- substreams/ethereum-balancer/Cargo.toml | 6 +++--- substreams/ethereum-balancer/substreams.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/substreams/Cargo.lock b/substreams/Cargo.lock index 03514a5..36656f6 100644 --- a/substreams/Cargo.lock +++ b/substreams/Cargo.lock @@ -210,7 +210,7 @@ dependencies = [ [[package]] name = "ethereum-balancer" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "bytes", diff --git a/substreams/ethereum-balancer/Cargo.toml b/substreams/ethereum-balancer/Cargo.toml index 2aca957..9bc359a 100644 --- a/substreams/ethereum-balancer/Cargo.toml +++ b/substreams/ethereum-balancer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ethereum-balancer" -version = "0.1.0" +version = "0.2.0" edition = "2021" [lib] @@ -8,8 +8,8 @@ name = "ethereum_balancer" crate-type = ["cdylib"] [dependencies] -substreams.workspace = true -substreams-ethereum.workspace = true +substreams.workspace = true +substreams-ethereum.workspace = true prost.workspace = true prost-types.workspace = true hex-literal.workspace = true diff --git a/substreams/ethereum-balancer/substreams.yaml b/substreams/ethereum-balancer/substreams.yaml index 5d61749..050e8fe 100644 --- a/substreams/ethereum-balancer/substreams.yaml +++ b/substreams/ethereum-balancer/substreams.yaml @@ -1,7 +1,7 @@ specVersion: v0.1.0 package: name: "ethereum_balancer" - version: v0.1.0 + version: v0.2.0 protobuf: files: From 84a3642f95b0bc92ae7ea0fadcd720e737af9bd5 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:47:49 +0200 Subject: [PATCH 06/10] refactor: remove substreams logs --- substreams/ethereum-balancer/src/modules.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substreams/ethereum-balancer/src/modules.rs b/substreams/ethereum-balancer/src/modules.rs index 6c78aa4..81749b4 100644 --- a/substreams/ethereum-balancer/src/modules.rs +++ b/substreams/ethereum-balancer/src/modules.rs @@ -170,7 +170,6 @@ pub fn map_protocol_changes( .filter(|(log, _)| log.address == VAULT_ADDRESS) .filter_map(|(log, _)| { let registered = abi::vault::events::PoolRegistered::match_and_decode(log)?; - substreams::log::info!("{:?}", log); Some(( tx.clone(), EntityChanges { From 47e6f083387a0bb04b5daadf9d12d6020eb76d75 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:08:00 +0200 Subject: [PATCH 07/10] docs: add changelog --- substreams/crates/tycho-substreams/Changelog.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 substreams/crates/tycho-substreams/Changelog.md diff --git a/substreams/crates/tycho-substreams/Changelog.md b/substreams/crates/tycho-substreams/Changelog.md new file mode 100644 index 0000000..353b55d --- /dev/null +++ b/substreams/crates/tycho-substreams/Changelog.md @@ -0,0 +1,12 @@ +# Changelog + +## 0.2.0 + +### Updated + +- Protobuf struct updated to align with recent changes in the indexer. + +### Changed + +- Removed the distinction between VM and native implementations. Now, there is a single implementation type that can extract both contracts and protocol state. +- Enabled the attachment of dynamic attributes to protocol components. From a2e951aff30e26a580d1bc01c01f13642bf3cfed Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:27:08 +0200 Subject: [PATCH 08/10] refactor: make pool_id a static attribute --- substreams/ethereum-balancer/src/modules.rs | 64 ++++++----------- .../ethereum-balancer/src/pool_factories.rs | 72 ++++++++++++++++--- 2 files changed, 82 insertions(+), 54 deletions(-) diff --git a/substreams/ethereum-balancer/src/modules.rs b/substreams/ethereum-balancer/src/modules.rs index 81749b4..c9b66a5 100644 --- a/substreams/ethereum-balancer/src/modules.rs +++ b/substreams/ethereum-balancer/src/modules.rs @@ -12,7 +12,7 @@ use tycho_substreams::{ balances::aggregate_balances_changes, contract::extract_contract_changes, prelude::*, }; -const VAULT_ADDRESS: &[u8] = &hex!("BA12222222228d8Ba445958a75a0704d566BF2C8"); +pub const VAULT_ADDRESS: &[u8] = &hex!("BA12222222228d8Ba445958a75a0704d566BF2C8"); #[substreams::handlers::map] pub fn map_components(block: eth::v2::Block) -> Result { @@ -29,7 +29,7 @@ pub fn map_components(block: eth::v2::Block) -> Result>(); @@ -160,48 +160,26 @@ pub fn map_protocol_changes( .or_insert_with(|| TransactionChanges::new(tx)) .component_changes .extend_from_slice(&tx_component.components); - }); - - block - .transactions() - .flat_map(|tx| { - let components = tx - .logs_with_calls() - .filter(|(log, _)| log.address == VAULT_ADDRESS) - .filter_map(|(log, _)| { - let registered = abi::vault::events::PoolRegistered::match_and_decode(log)?; - Some(( - tx.clone(), - EntityChanges { - component_id: hex::encode(registered.pool_address), - attributes: vec![ - Attribute { - name: "pool_id".to_string(), - value: format!("0x{}", hex::encode(registered.pool_id)) - .as_bytes() - .to_vec(), - change: ChangeType::Creation.into(), - }, - Attribute { - name: "balance_owner".to_string(), - value: "0xBA12222222228d8Ba445958a75a0704d566BF2C8" - .to_string() - .as_bytes() - .to_vec(), - change: ChangeType::Creation.into(), - }, - ], - }, - )) + tx_component + .components + .iter() + .for_each(|component| { + transaction_changes + .entry(tx.index) + .or_insert_with(|| TransactionChanges::new(tx)) + .entity_changes + .push(EntityChanges { + component_id: component.id.clone(), + attributes: vec![Attribute { + name: "balance_owner".to_string(), + value: "0xBA12222222228d8Ba445958a75a0704d566BF2C8" + .to_string() + .as_bytes() + .to_vec(), + change: ChangeType::Creation.into(), + }], + }); }); - components - }) - .for_each(|(tx, state_change)| { - transaction_changes - .entry(tx.index.into()) - .or_insert_with(|| TransactionChanges::new(&(&tx).into())) - .entity_changes - .push(state_change); }); // Balance changes are gathered by the `StoreDelta` based on `PoolBalanceChanged` creating diff --git a/substreams/ethereum-balancer/src/pool_factories.rs b/substreams/ethereum-balancer/src/pool_factories.rs index 6563c81..8734a51 100644 --- a/substreams/ethereum-balancer/src/pool_factories.rs +++ b/substreams/ethereum-balancer/src/pool_factories.rs @@ -1,7 +1,7 @@ -use crate::abi; +use crate::{abi, modules::VAULT_ADDRESS}; use substreams::{hex, scalar::BigInt}; use substreams_ethereum::{ - pb::eth::v2::{Call, Log}, + pb::eth::v2::{Call, Log, TransactionTrace}, Event, Function, }; use tycho_substreams::prelude::*; @@ -29,6 +29,19 @@ impl SerializableVecBigInt for Vec { } } +/// Helper function to get pool_registered event +fn get_pool_registered( + tx: &TransactionTrace, + pool_address: &Vec, +) -> abi::vault::events::PoolRegistered { + tx.logs_with_calls() + .filter(|(log, _)| log.address == VAULT_ADDRESS) + .filter_map(|(log, _)| abi::vault::events::PoolRegistered::match_and_decode(log)) + .find(|pool| pool.pool_address == *pool_address) + .unwrap() + .clone() +} + /// This is the main function that handles the creation of `ProtocolComponent`s with `Attribute`s /// based on the specific factory address. There's 3 factory groups that are represented here: /// - Weighted Pool Factories @@ -45,7 +58,7 @@ pub fn address_map( pool_factory_address: &[u8], log: &Log, call: &Call, - tx: &Transaction, + tx: &TransactionTrace, ) -> Option { match *pool_factory_address { hex!("897888115Ada5773E02aA29F775430BFB5F34c51") => { @@ -53,9 +66,10 @@ pub fn address_map( abi::weighted_pool_factory::functions::Create::match_and_decode(call)?; let pool_created = abi::weighted_pool_factory::events::PoolCreated::match_and_decode(log)?; + let pool_registered = get_pool_registered(tx, &pool_created.pool); Some( - ProtocolComponent::at_contract(&pool_created.pool, tx) + ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) .with_tokens(&create_call.tokens) .with_attributes(&[ ("pool_type", "WeightedPoolFactory".as_bytes()), @@ -65,6 +79,10 @@ pub fn address_map( .normalized_weights .serialize_bytes(), ), + ( + "pool_id", + format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), + ), ]) .as_swap_type("balancer_pool", ImplementationType::Vm), ) @@ -74,11 +92,18 @@ pub fn address_map( abi::composable_stable_pool_factory::functions::Create::match_and_decode(call)?; let pool_created = abi::composable_stable_pool_factory::events::PoolCreated::match_and_decode(log)?; + let pool_registered = get_pool_registered(tx, &pool_created.pool); Some( - ProtocolComponent::at_contract(&pool_created.pool, tx) + ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) .with_tokens(&create_call.tokens) - .with_attributes(&[("pool_type", "ComposableStablePoolFactory".as_bytes())]) + .with_attributes(&[ + ("pool_type", "ComposableStablePoolFactory".as_bytes()), + ( + "pool_id", + format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), + ), + ]) .as_swap_type("balancer_pool", ImplementationType::Vm), ) } @@ -87,9 +112,10 @@ pub fn address_map( abi::erc_linear_pool_factory::functions::Create::match_and_decode(call)?; let pool_created = abi::erc_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; + let pool_registered = get_pool_registered(tx, &pool_created.pool); Some( - ProtocolComponent::at_contract(&pool_created.pool, tx) + ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "ERC4626LinearPoolFactory".as_bytes()), @@ -99,6 +125,10 @@ pub fn address_map( .upper_target .to_signed_bytes_be(), ), + ( + "pool_id", + format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), + ), ]) .as_swap_type("balancer_pool", ImplementationType::Vm), ) @@ -108,9 +138,10 @@ pub fn address_map( abi::euler_linear_pool_factory::functions::Create::match_and_decode(call)?; let pool_created = abi::euler_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; + let pool_registered = get_pool_registered(tx, &pool_created.pool); Some( - ProtocolComponent::at_contract(&pool_created.pool, tx) + ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "EulerLinearPoolFactory".as_bytes()), @@ -120,6 +151,10 @@ pub fn address_map( .upper_target .to_signed_bytes_be(), ), + ( + "pool_id", + format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), + ), ]) .as_swap_type("balancer_pool", ImplementationType::Vm), ) @@ -177,9 +212,10 @@ pub fn address_map( abi::silo_linear_pool_factory::functions::Create::match_and_decode(call)?; let pool_created = abi::silo_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; + let pool_registered = get_pool_registered(tx, &pool_created.pool); Some( - ProtocolComponent::at_contract(&pool_created.pool, tx) + ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "SiloLinearPoolFactory".as_bytes()), @@ -189,6 +225,10 @@ pub fn address_map( .upper_target .to_signed_bytes_be(), ), + ( + "pool_id", + format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), + ), ]) .as_swap_type("balancer_pool", ImplementationType::Vm), ) @@ -198,9 +238,10 @@ pub fn address_map( abi::yearn_linear_pool_factory::functions::Create::match_and_decode(call)?; let pool_created = abi::yearn_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; + let pool_registered = get_pool_registered(tx, &pool_created.pool); Some( - ProtocolComponent::at_contract(&pool_created.pool, tx) + ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "YearnLinearPoolFactory".as_bytes()), @@ -210,6 +251,10 @@ pub fn address_map( .upper_target .to_signed_bytes_be(), ), + ( + "pool_id", + format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), + ), ]) .as_swap_type("balancer_pool", ImplementationType::Vm), ) @@ -221,13 +266,18 @@ pub fn address_map( abi::weighted_pool_tokens_factory::functions::Create::match_and_decode(call)?; let pool_created = abi::weighted_pool_tokens_factory::events::PoolCreated::match_and_decode(log)?; + let pool_registered = get_pool_registered(tx, &pool_created.pool); Some( - ProtocolComponent::at_contract(&pool_created.pool, tx) + ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) .with_tokens(&create_call.tokens) .with_attributes(&[ ("pool_type", "WeightedPool2TokensFactory".as_bytes()), ("weights", &create_call.weights.serialize_bytes()), + ( + "pool_id", + format!("0x{}", hex::encode(pool_registered.pool_id)).as_bytes(), + ), ]) .as_swap_type("balancer_pool", ImplementationType::Vm), ) From 1bd5e39956a27a50b5d602dcd01c3c6991605915 Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:46:11 +0200 Subject: [PATCH 09/10] feat: Add Balancer vault indexing --- substreams/ethereum-balancer/src/modules.rs | 3 ++- substreams/ethereum-balancer/src/pool_factories.rs | 7 +++++++ substreams/ethereum-balancer/substreams.yaml | 10 +++++----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/substreams/ethereum-balancer/src/modules.rs b/substreams/ethereum-balancer/src/modules.rs index c9b66a5..90297a1 100644 --- a/substreams/ethereum-balancer/src/modules.rs +++ b/substreams/ethereum-balancer/src/modules.rs @@ -202,7 +202,8 @@ pub fn map_protocol_changes( |addr| { components_store .get_last(format!("pool:0x{0}", hex::encode(addr))) - .is_some() + .is_some() || + addr.eq(VAULT_ADDRESS) }, &mut transaction_changes, ); diff --git a/substreams/ethereum-balancer/src/pool_factories.rs b/substreams/ethereum-balancer/src/pool_factories.rs index 8734a51..9cb4910 100644 --- a/substreams/ethereum-balancer/src/pool_factories.rs +++ b/substreams/ethereum-balancer/src/pool_factories.rs @@ -70,6 +70,7 @@ pub fn address_map( Some( ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) + .with_contracts(&[pool_created.pool, VAULT_ADDRESS.to_vec()]) .with_tokens(&create_call.tokens) .with_attributes(&[ ("pool_type", "WeightedPoolFactory".as_bytes()), @@ -96,6 +97,7 @@ pub fn address_map( Some( ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) + .with_contracts(&[pool_created.pool, VAULT_ADDRESS.to_vec()]) .with_tokens(&create_call.tokens) .with_attributes(&[ ("pool_type", "ComposableStablePoolFactory".as_bytes()), @@ -116,6 +118,7 @@ pub fn address_map( Some( ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) + .with_contracts(&[pool_created.pool, VAULT_ADDRESS.to_vec()]) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "ERC4626LinearPoolFactory".as_bytes()), @@ -142,6 +145,7 @@ pub fn address_map( Some( ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) + .with_contracts(&[pool_created.pool, VAULT_ADDRESS.to_vec()]) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "EulerLinearPoolFactory".as_bytes()), @@ -216,6 +220,7 @@ pub fn address_map( Some( ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) + .with_contracts(&[pool_created.pool, VAULT_ADDRESS.to_vec()]) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "SiloLinearPoolFactory".as_bytes()), @@ -242,6 +247,7 @@ pub fn address_map( Some( ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) + .with_contracts(&[pool_created.pool, VAULT_ADDRESS.to_vec()]) .with_tokens(&[create_call.main_token, create_call.wrapped_token]) .with_attributes(&[ ("pool_type", "YearnLinearPoolFactory".as_bytes()), @@ -270,6 +276,7 @@ pub fn address_map( Some( ProtocolComponent::at_contract(&pool_created.pool, &(tx.into())) + .with_contracts(&[pool_created.pool, VAULT_ADDRESS.to_vec()]) .with_tokens(&create_call.tokens) .with_attributes(&[ ("pool_type", "WeightedPool2TokensFactory".as_bytes()), diff --git a/substreams/ethereum-balancer/substreams.yaml b/substreams/ethereum-balancer/substreams.yaml index 050e8fe..10c949c 100644 --- a/substreams/ethereum-balancer/substreams.yaml +++ b/substreams/ethereum-balancer/substreams.yaml @@ -19,7 +19,7 @@ binaries: modules: - name: map_components kind: map - initialBlock: 12369300 + initialBlock: 12272146 inputs: - source: sf.ethereum.type.v2.Block output: @@ -27,7 +27,7 @@ modules: - name: store_components kind: store - initialBlock: 12369300 + initialBlock: 12272146 updatePolicy: add valueType: int64 inputs: @@ -35,7 +35,7 @@ modules: - name: map_relative_balances kind: map - initialBlock: 12369300 # An arbitrary block that should change based on your requirements + initialBlock: 12272146 inputs: - source: sf.ethereum.type.v2.Block - store: store_components @@ -44,7 +44,7 @@ modules: - name: store_balances kind: store - initialBlock: 12369300 + initialBlock: 12272146 updatePolicy: add valueType: bigint inputs: @@ -52,7 +52,7 @@ modules: - name: map_protocol_changes kind: map - initialBlock: 12369300 + initialBlock: 12272146 inputs: - source: sf.ethereum.type.v2.Block - map: map_components From bd0a077917ccf3a2a9a67771a341138111fc6e6d Mon Sep 17 00:00:00 2001 From: Florian Pellissier <111426680+flopell@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:04:04 +0200 Subject: [PATCH 10/10] refactor: Use new hybrid structs for Curve --- substreams/ethereum-curve/src/modules.rs | 32 ++++++++++++----------- substreams/ethereum-curve/substreams.yaml | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/substreams/ethereum-curve/src/modules.rs b/substreams/ethereum-curve/src/modules.rs index b8ef29f..59f75c9 100644 --- a/substreams/ethereum-curve/src/modules.rs +++ b/substreams/ethereum-curve/src/modules.rs @@ -142,8 +142,8 @@ pub fn store_balances(deltas: BlockBalanceDeltas, store: StoreAddBigInt) { } /// This is the main map that handles most of the indexing of this substream. -/// Every contract change is grouped by transaction index via the `transaction_contract_changes` -/// map. Each block of code will extend the `TransactionContractChanges` struct with the +/// Every change is grouped by transaction index via the `transaction_changes` +/// map. Each block of code will extend the `TransactionChanges` struct with the /// cooresponding changes (balance, component, contract), inserting a new one if it doesn't exist. /// At the very end, the map can easily be sorted by index to ensure the final /// `BlockContractChanges` is ordered by transactions properly. @@ -154,25 +154,26 @@ pub fn map_protocol_changes( deltas: BlockBalanceDeltas, components_store: StoreGetString, balance_store: StoreDeltas, // Note, this map module is using the `deltas` mode for the store. -) -> Result { +) -> Result { // We merge contract changes by transaction (identified by transaction index) making it easy to // sort them at the very end. - let mut transaction_contract_changes: HashMap<_, TransactionContractChanges> = HashMap::new(); + let mut transaction_changes: HashMap<_, TransactionChanges> = HashMap::new(); // `ProtocolComponents` are gathered from `map_pools_created` which just need a bit of work to - // convert into `TransactionContractChanges` + // convert into `TransactionChanges` grouped_components .tx_components .into_iter() .for_each(|tx_component| { let tx = tx_component.tx.as_ref().unwrap(); - transaction_contract_changes + transaction_changes .entry(tx.index) - .or_insert_with(|| TransactionContractChanges { + .or_insert_with(|| TransactionChanges { tx: Some(tx.clone()), contract_changes: vec![], component_changes: vec![], balance_changes: vec![], + entity_changes: vec![], }) .component_changes .extend_from_slice( @@ -214,19 +215,20 @@ pub fn map_protocol_changes( }, ) }) - // We need to group the balance changes by tx hash for the `TransactionContractChanges` agg + // We need to group the balance changes by tx hash for the `TransactionChanges` agg .chunk_by(|(tx, _)| TransactionWrapper(tx.clone())) .into_iter() .for_each(|(tx_wrapped, group)| { let tx = tx_wrapped.0; - transaction_contract_changes + transaction_changes .entry(tx.index) - .or_insert_with(|| TransactionContractChanges { + .or_insert_with(|| TransactionChanges { tx: Some(tx.clone()), contract_changes: vec![], component_changes: vec![], balance_changes: vec![], + entity_changes: vec![], }) .balance_changes .extend(group.map(|(_, change)| change)); @@ -242,10 +244,10 @@ pub fn map_protocol_changes( .get_last(format!("pool:{0}", hex::encode(addr))) .is_some() }, - &mut transaction_contract_changes, + &mut transaction_changes, ); - for change in transaction_contract_changes.values_mut() { + for change in transaction_changes.values_mut() { for balance_change in change.balance_changes.iter_mut() { replace_eth_address(&mut balance_change.token); } @@ -257,9 +259,9 @@ pub fn map_protocol_changes( } } - // Process all `transaction_contract_changes` for final output in the `BlockContractChanges`, + // Process all `transaction_changes` for final output in the `BlockContractChanges`, // sorted by transaction index (the key). - Ok(BlockContractChanges { + Ok(BlockChanges { block: Some(Block { number: block.number, hash: block.hash.clone(), @@ -271,7 +273,7 @@ pub fn map_protocol_changes( .clone(), ts: block.timestamp_seconds(), }), - changes: transaction_contract_changes + changes: transaction_changes .drain() .sorted_unstable_by_key(|(index, _)| *index) .filter_map(|(_, change)| { diff --git a/substreams/ethereum-curve/substreams.yaml b/substreams/ethereum-curve/substreams.yaml index d1a8d6b..dc1ae65 100644 --- a/substreams/ethereum-curve/substreams.yaml +++ b/substreams/ethereum-curve/substreams.yaml @@ -63,7 +63,7 @@ modules: - store: store_balances mode: deltas # This is the key property that simplifies `BalanceChange` handling output: - type: proto:tycho.evm.v1.BlockContractChanges + type: proto:tycho.evm.v1.BlockChanges params: map_components: "address=bebc44782c7db0a1a60cb6fe97d0b483032ff1c7&tx_hash=20793bbf260912aae189d5d261ff003c9b9166da8191d8f9d63ff1c7722f3ac6&tokens[]=6b175474e89094c44da98b954eedeac495271d0f&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&attribute_keys[]=name&attribute_vals[]=3pool&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=dc24316b9ae028f1497c275eb9192a3ea0f67022&tx_hash=fac67ecbd423a5b915deff06045ec9343568edaec34ae95c43d35f2c018afdaa&tokens[]=eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&tokens[]=ae7ab96520de3a18e5e111b5eaab095312d7fe84&attribute_keys[]=name&attribute_vals[]=steth&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=d51a44d3fae010294c616388b506acda1bfaae46&tx_hash=dafb6385ed988ce8aacecfe1d97b38ea5e60b1ebce74d2423f71ddd621680138&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&tokens[]=2260fac5e5542a773aa44fbcfedf7c193bc2c599&tokens[]=c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&attribute_keys[]=name&attribute_vals[]=tricrypto2&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=a5407eae9ba41422680e2e00537571bcc53efbfd&tx_hash=51aca4a03a395de8855fa2ca59b7febe520c2a223e69c502066162f7c1a95ec2&tokens[]=6b175474e89094c44da98b954eedeac495271d0f&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&tokens[]=dac17f958d2ee523a2206206994597c13d831ec7&tokens[]=57ab1ec28d129707052df4df418d58a2d46d5f51&attribute_keys[]=name&attribute_vals[]=susd&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000,address=dcef968d416a41cdac0ed8702fac8128a64241a2&tx_hash=1f4254004ce9e19d4eb742ee5a69d30f29085902d976f73e97c44150225ef775&tokens[]=853d955acef822db058eb8505911ed77f175b99e&tokens[]=a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&attribute_keys[]=name&attribute_vals[]=fraxusdc&attribute_keys[]=factory_name&attribute_vals[]=NA&attribute_keys[]=factory&attribute_vals[]=0x0000000000000000000000000000000000000000"