diff --git a/crates/tycho-substreams/src/contract.rs b/crates/tycho-substreams/src/contract.rs index 79ad454..633ef35 100644 --- a/crates/tycho-substreams/src/contract.rs +++ b/crates/tycho-substreams/src/contract.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use substreams_ethereum::pb::eth; +use substreams_ethereum::pb::eth::v2::StorageChange; use crate::pb::tycho::evm::v1::{self as tycho}; @@ -15,6 +16,12 @@ struct SlotValue { start_value: Vec, } +impl From<&StorageChange> for SlotValue { + fn from(change: &StorageChange) -> Self { + Self { new_value: change.new_value.clone(), start_value: change.old_value.clone() } + } +} + impl SlotValue { fn has_changed(&self) -> bool { self.start_value != self.new_value @@ -30,6 +37,22 @@ struct InterimContractChange { change: tycho::ChangeType, } +impl InterimContractChange { + fn new(address: &[u8], creation: bool) -> Self { + Self { + address: address.to_vec(), + balance: vec![], + code: vec![], + slots: Default::default(), + change: if creation { + tycho::ChangeType::Creation.into() + } else { + tycho::ChangeType::Update.into() + }, + } + } +} + impl From for tycho::ContractChange { fn from(value: InterimContractChange) -> Self { tycho::ContractChange { @@ -90,28 +113,20 @@ pub fn extract_contract_changes bool>( storage_changes .iter() .filter(|changes| inclusion_predicate(&changes.address)) - .for_each(|storage_change| { + .for_each(|&storage_change| { let contract_change = changed_contracts .entry(storage_change.address.clone()) - .or_insert_with(|| InterimContractChange { - address: storage_change.address.clone(), - balance: Vec::new(), - code: Vec::new(), - slots: HashMap::new(), - change: if created_accounts.contains_key(&storage_change.address) { - tycho::ChangeType::Creation - } else { - tycho::ChangeType::Update - }, + .or_insert_with(|| { + InterimContractChange::new( + &storage_change.address, + created_accounts.contains_key(&storage_change.address), + ) }); let slot_value = contract_change .slots .entry(storage_change.key.clone()) - .or_insert_with(|| SlotValue { - new_value: storage_change.new_value.clone(), - start_value: storage_change.old_value.clone(), - }); + .or_insert_with(|| storage_change.into()); slot_value .new_value @@ -124,16 +139,11 @@ pub fn extract_contract_changes bool>( .for_each(|balance_change| { let contract_change = changed_contracts .entry(balance_change.address.clone()) - .or_insert_with(|| InterimContractChange { - address: balance_change.address.clone(), - balance: Vec::new(), - code: Vec::new(), - slots: HashMap::new(), - change: if created_accounts.contains_key(&balance_change.address) { - tycho::ChangeType::Creation - } else { - tycho::ChangeType::Update - }, + .or_insert_with(|| { + InterimContractChange::new( + &balance_change.address, + created_accounts.contains_key(&balance_change.address), + ) }); if let Some(new_balance) = &balance_change.new_value { @@ -150,16 +160,11 @@ pub fn extract_contract_changes bool>( .for_each(|code_change| { let contract_change = changed_contracts .entry(code_change.address.clone()) - .or_insert_with(|| InterimContractChange { - address: code_change.address.clone(), - balance: Vec::new(), - code: Vec::new(), - slots: HashMap::new(), - change: if created_accounts.contains_key(&code_change.address) { - tycho::ChangeType::Creation - } else { - tycho::ChangeType::Update - }, + .or_insert_with(|| { + InterimContractChange::new( + &code_change.address, + created_accounts.contains_key(&code_change.address), + ) }); contract_change.code.clear(); @@ -174,17 +179,7 @@ pub fn extract_contract_changes bool>( { transaction_contract_changes .entry(block_tx.index.into()) - .or_insert_with(|| tycho::TransactionContractChanges { - tx: Some(tycho::Transaction { - hash: block_tx.hash.clone(), - from: block_tx.from.clone(), - to: block_tx.to.clone(), - index: block_tx.index as u64, - }), - contract_changes: vec![], - component_changes: vec![], - balance_changes: vec![], - }) + .or_insert_with(|| tycho::TransactionContractChanges::new(&(block_tx.into()))) .contract_changes .extend( changed_contracts diff --git a/crates/tycho-substreams/src/pb/mod.rs b/crates/tycho-substreams/src/pb/mod.rs index f5cd0b0..ceced59 100644 --- a/crates/tycho-substreams/src/pb/mod.rs +++ b/crates/tycho-substreams/src/pb/mod.rs @@ -3,6 +3,7 @@ pub mod tycho { pub mod evm { // @@protoc_insertion_point(attribute:tycho.evm.v1) pub mod v1 { + use substreams_ethereum::pb::eth::v2::{self as sf}; include!("tycho.evm.v1.rs"); // @@protoc_insertion_point(tycho.evm.v1) @@ -16,6 +17,104 @@ pub mod tycho { } } } + + impl From<&sf::TransactionTrace> for Transaction { + fn from(tx: &sf::TransactionTrace) -> Self { + Self { + hash: tx.hash.clone(), + from: tx.from.clone(), + to: tx.to.clone(), + index: tx.index.into(), + } + } + } + + impl From<&sf::Block> for Block { + fn from(block: &sf::Block) -> Self { + Self { + number: block.number, + hash: block.hash.clone(), + parent_hash: block + .header + .as_ref() + .expect("Block header not present") + .parent_hash + .clone(), + ts: block.timestamp_seconds(), + } + } + } + + impl ProtocolComponent { + pub fn new(id: &str, tx: &Transaction) -> Self { + Self { + id: id.to_string(), + tokens: vec![], + contracts: vec![], + static_att: vec![], + change: ChangeType::Creation.into(), + protocol_type: None, + tx: Some(tx.clone()), + } + } + + pub fn at_contract(id: &[u8], tx: &Transaction) -> Self { + Self { + id: format!("0x{}", hex::encode(id)), + tokens: vec![], + contracts: vec![id.to_vec()], + static_att: vec![], + change: ChangeType::Creation.into(), + protocol_type: None, + tx: Some(tx.clone()), + } + } + + pub fn with_tokens>(mut self, tokens: &[B]) -> Self { + self.tokens = tokens + .iter() + .map(|e| e.as_ref().to_vec()) + .collect::>>(); + self + } + + pub fn with_contracts>(mut self, contracts: &[B]) -> Self { + self.contracts = contracts + .iter() + .map(|e| e.as_ref().to_vec()) + .collect::>>(); + self + } + + pub fn with_attributes, V: AsRef<[u8]>>( + mut self, + attributes: &[(K, V)], + ) -> Self { + self.static_att = attributes + .iter() + .map(|(k, v)| Attribute { + name: k.as_ref().to_string(), + value: v.as_ref().to_vec(), + change: ChangeType::Creation.into(), + }) + .collect::>(); + self + } + + pub fn as_swap_type( + mut self, + name: &str, + implementation_type: ImplementationType, + ) -> Self { + self.protocol_type = Some(ProtocolType { + name: name.to_string(), + financial_type: FinancialType::Swap.into(), + attribute_schema: vec![], + implementation_type: implementation_type.into(), + }); + self + } + } } } } diff --git a/substreams/ethereum-balancer/src/modules.rs b/substreams/ethereum-balancer/src/modules.rs index a3658ad..81411f4 100644 --- a/substreams/ethereum-balancer/src/modules.rs +++ b/substreams/ethereum-balancer/src/modules.rs @@ -34,24 +34,14 @@ pub fn map_pools_created(block: eth::v2::Block) -> Result>(); if !components.is_empty() { Some(TransactionProtocolComponents { - tx: Some(tycho::Transaction { - hash: tx.hash.clone(), - from: tx.from.clone(), - to: tx.to.clone(), - index: Into::::into(tx.index), - }), + tx: Some(tx.into()), components, }) } else { @@ -106,12 +96,7 @@ pub fn map_balance_deltas( for (token, delta) in ev.tokens.iter().zip(ev.deltas.iter()) { deltas.push(BalanceDelta { ord: vault_log.ordinal(), - tx: Some(tycho::Transaction { - hash: vault_log.receipt.transaction.hash.clone(), - from: vault_log.receipt.transaction.from.clone(), - to: vault_log.receipt.transaction.to.clone(), - index: vault_log.receipt.transaction.index.into(), - }), + tx: Some(vault_log.receipt.transaction.into()), token: token.to_vec(), delta: delta.to_signed_bytes_be(), component_id: component_id.clone(), @@ -133,24 +118,14 @@ pub fn map_balance_deltas( deltas.extend_from_slice(&[ BalanceDelta { ord: vault_log.ordinal(), - tx: Some(tycho::Transaction { - hash: vault_log.receipt.transaction.hash.clone(), - from: vault_log.receipt.transaction.from.clone(), - to: vault_log.receipt.transaction.to.clone(), - index: vault_log.receipt.transaction.index.into(), - }), + tx: Some(vault_log.receipt.transaction.into()), token: ev.token_in.to_vec(), delta: ev.amount_in.to_signed_bytes_be(), component_id: component_id.clone(), }, BalanceDelta { ord: vault_log.ordinal(), - tx: Some(tycho::Transaction { - hash: vault_log.receipt.transaction.hash.clone(), - from: vault_log.receipt.transaction.from.clone(), - to: vault_log.receipt.transaction.to.clone(), - index: vault_log.receipt.transaction.index.into(), - }), + tx: Some(vault_log.receipt.transaction.into()), token: ev.token_out.to_vec(), delta: ev.amount_out.neg().to_signed_bytes_be(), component_id, @@ -234,17 +209,7 @@ pub fn map_changes( // Process all `transaction_contract_changes` for final output in the `BlockContractChanges`, // sorted by transaction index (the key). Ok(tycho::BlockContractChanges { - block: Some(tycho::Block { - number: block.number, - hash: block.hash.clone(), - parent_hash: block - .header - .as_ref() - .expect("Block header not present") - .parent_hash - .clone(), - ts: block.timestamp_seconds(), - }), + block: Some((&block).into()), changes: transaction_contract_changes .drain() .sorted_unstable_by_key(|(index, _)| *index) diff --git a/substreams/ethereum-balancer/src/pool_factories.rs b/substreams/ethereum-balancer/src/pool_factories.rs index 27c5ced..2ef1bc8 100644 --- a/substreams/ethereum-balancer/src/pool_factories.rs +++ b/substreams/ethereum-balancer/src/pool_factories.rs @@ -3,9 +3,7 @@ use substreams_ethereum::{Event, Function}; use crate::abi; use substreams::hex; -use tycho_substreams::pb::tycho::evm::v1::{ - self as tycho, FinancialType, ImplementationType, ProtocolType, Transaction, -}; +use tycho_substreams::pb::tycho::evm::v1::{ImplementationType, ProtocolComponent, Transaction}; use substreams::scalar::BigInt; @@ -39,16 +37,16 @@ impl SerializableVecBigInt for Vec { /// - Stable Pool Factories /// (Balancer does have a bit more (esp. in the deprecated section) that could be implemented as /// desired.) -/// We use the specific ABIs to decode both the log event and cooresponding call to gather -/// `PoolCreated` event information alongside the `Create` calldata that provide us details to -/// fufill both the required details + any extra `Attributes` +/// 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` /// Ref: https://docs.balancer.fi/reference/contracts/deployment-addresses/mainnet.html pub fn address_map( pool_factory_address: &[u8], log: &Log, call: &Call, tx: &Transaction, -) -> Option { +) -> Option { match *pool_factory_address { hex!("897888115Ada5773E02aA29F775430BFB5F34c51") => { let create_call = @@ -56,31 +54,18 @@ pub fn address_map( let pool_created = abi::weighted_pool_factory::events::PoolCreated::match_and_decode(log)?; - Some(tycho::ProtocolComponent { - id: hex::encode(&pool_created.pool), - tokens: create_call.tokens, - contracts: vec![pool_created.pool], - static_att: vec![ - tycho::Attribute { - name: "pool_type".into(), - value: "WeightedPoolFactory".into(), - change: tycho::ChangeType::Creation.into(), - }, - tycho::Attribute { - name: "normalized_weights".into(), - value: create_call.normalized_weights.serialize_bytes(), - change: tycho::ChangeType::Creation.into(), - }, - ], - change: tycho::ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "balancer_pool".to_string(), - financial_type: FinancialType::Swap.into(), - attribute_schema: vec![], - implementation_type: ImplementationType::Vm.into(), - }), - tx: Some(tx.clone()), - }) + Some( + ProtocolComponent::at_contract(&pool_created.pool, &tx) + .with_tokens(&create_call.tokens) + .with_attributes(&[ + ("pool_type", "WeightedPoolFactory".as_bytes()), + ( + "normalized_weights", + &create_call.normalized_weights.serialize_bytes(), + ), + ]) + .as_swap_type("balancer_pool", ImplementationType::Vm), + ) } hex!("DB8d758BCb971e482B2C45f7F8a7740283A1bd3A") => { let create_call = @@ -88,24 +73,12 @@ pub fn address_map( let pool_created = abi::composable_stable_pool_factory::events::PoolCreated::match_and_decode(log)?; - Some(tycho::ProtocolComponent { - id: hex::encode(&pool_created.pool), - tokens: create_call.tokens, - contracts: vec![pool_created.pool], - static_att: vec![tycho::Attribute { - name: "pool_type".into(), - value: "ComposableStablePoolFactory".into(), - change: tycho::ChangeType::Creation.into(), - }], - change: tycho::ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "balancer_pool".to_string(), - financial_type: FinancialType::Swap.into(), - attribute_schema: vec![], - implementation_type: ImplementationType::Vm.into(), - }), - tx: Some(tx.clone()), - }) + Some( + ProtocolComponent::at_contract(&pool_created.pool, &tx) + .with_tokens(&create_call.tokens) + .with_attributes(&[("pool_type", "ComposableStablePoolFactory".as_bytes())]) + .as_swap_type("balancer_pool", ImplementationType::Vm), + ) } hex!("813EE7a840CE909E7Fea2117A44a90b8063bd4fd") => { let create_call = @@ -113,33 +86,18 @@ pub fn address_map( let pool_created = abi::erc_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; - Some(tycho::ProtocolComponent { - id: hex::encode(&pool_created.pool), - tokens: vec![create_call.main_token, create_call.wrapped_token], - contracts: vec![pool_created.pool], - static_att: vec![ - tycho::Attribute { - name: "pool_type".into(), - value: "ERC4626LinearPoolFactory".into(), - change: tycho::ChangeType::Creation.into(), - }, - tycho::Attribute { - name: "upper_target".into(), - value: create_call.upper_target.to_signed_bytes_be(), - change: tycho::ChangeType::Creation.into(), - }, - // Note, `lower_target` is generally hardcoded for all pools, not located in call data - // Note, rate provider might be provided as `create.protocol_id`, but as a BigInt. needs investigation - ], - change: tycho::ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "balancer_pool".to_string(), - financial_type: FinancialType::Swap.into(), - attribute_schema: vec![], - implementation_type: ImplementationType::Vm.into(), - }), - tx: Some(tx.clone()), - }) + Some( + ProtocolComponent::at_contract(&pool_created.pool, &tx) + .with_tokens(&[create_call.main_token, create_call.wrapped_token]) + .with_attributes(&[ + ("pool_type", "ERC4626LinearPoolFactory".as_bytes()), + ( + "upper_target", + &create_call.upper_target.to_signed_bytes_be(), + ), + ]) + .as_swap_type("balancer_pool", ImplementationType::Vm), + ) } hex!("5F43FBa61f63Fa6bFF101a0A0458cEA917f6B347") => { let create_call = @@ -147,31 +105,18 @@ pub fn address_map( let pool_created = abi::euler_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; - Some(tycho::ProtocolComponent { - id: hex::encode(&pool_created.pool), - tokens: vec![create_call.main_token, create_call.wrapped_token], - contracts: vec![pool_created.pool], - static_att: vec![ - tycho::Attribute { - name: "pool_type".into(), - value: "EulerLinearPoolFactory".into(), - change: tycho::ChangeType::Creation.into(), - }, - tycho::Attribute { - name: "upper_target".into(), - value: create_call.upper_target.to_signed_bytes_be(), - change: tycho::ChangeType::Creation.into(), - }, - ], - change: tycho::ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "balancer_pool".to_string(), - financial_type: FinancialType::Swap.into(), - attribute_schema: vec![], - implementation_type: ImplementationType::Vm.into(), - }), - tx: Some(tx.clone()), - }) + Some( + ProtocolComponent::at_contract(&pool_created.pool, &tx) + .with_tokens(&[create_call.main_token, create_call.wrapped_token]) + .with_attributes(&[ + ("pool_type", "EulerLinearPoolFactory".as_bytes()), + ( + "upper_target", + &create_call.upper_target.to_signed_bytes_be(), + ), + ]) + .as_swap_type("balancer_pool", ImplementationType::Vm), + ) } // ❌ Reading the deployed factory for Gearbox showcases that it's currently disabled // hex!("39A79EB449Fc05C92c39aA6f0e9BfaC03BE8dE5B") => { @@ -226,31 +171,18 @@ pub fn address_map( let pool_created = abi::silo_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; - Some(tycho::ProtocolComponent { - id: hex::encode(&pool_created.pool), - tokens: vec![create_call.main_token, create_call.wrapped_token], - contracts: vec![pool_created.pool], - static_att: vec![ - tycho::Attribute { - name: "pool_type".into(), - value: "SiloLinearPoolFactory".into(), - change: tycho::ChangeType::Creation.into(), - }, - tycho::Attribute { - name: "upper_target".into(), - value: create_call.upper_target.to_signed_bytes_be(), - change: tycho::ChangeType::Creation.into(), - }, - ], - change: tycho::ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "balancer_pool".to_string(), - financial_type: FinancialType::Swap.into(), - attribute_schema: vec![], - implementation_type: ImplementationType::Vm.into(), - }), - tx: Some(tx.clone()), - }) + Some( + ProtocolComponent::at_contract(&pool_created.pool, &tx) + .with_tokens(&[create_call.main_token, create_call.wrapped_token]) + .with_attributes(&[ + ("pool_type", "SiloLinearPoolFactory".as_bytes()), + ( + "upper_target", + &create_call.upper_target.to_signed_bytes_be(), + ), + ]) + .as_swap_type("balancer_pool", ImplementationType::Vm), + ) } hex!("5F5222Ffa40F2AEd6380D022184D6ea67C776eE0") => { let create_call = @@ -258,65 +190,36 @@ pub fn address_map( let pool_created = abi::yearn_linear_pool_factory::events::PoolCreated::match_and_decode(log)?; - Some(tycho::ProtocolComponent { - id: hex::encode(&pool_created.pool), - tokens: vec![create_call.main_token, create_call.wrapped_token], - contracts: vec![pool_created.pool], - static_att: vec![ - tycho::Attribute { - name: "pool_type".into(), - value: "YearnLinearPoolFactory".into(), - change: tycho::ChangeType::Creation.into(), - }, - tycho::Attribute { - name: "upper_target".into(), - value: create_call.upper_target.to_signed_bytes_be(), - change: tycho::ChangeType::Creation.into(), - }, - ], - change: tycho::ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "balancer_pool".to_string(), - financial_type: FinancialType::Swap.into(), - attribute_schema: vec![], - implementation_type: ImplementationType::Vm.into(), - }), - tx: Some(tx.clone()), - }) + Some( + ProtocolComponent::at_contract(&pool_created.pool, &tx) + .with_tokens(&[create_call.main_token, create_call.wrapped_token]) + .with_attributes(&[ + ("pool_type", "YearnLinearPoolFactory".as_bytes()), + ( + "upper_target", + &create_call.upper_target.to_signed_bytes_be(), + ), + ]) + .as_swap_type("balancer_pool", ImplementationType::Vm), + ) } - // The `WeightedPool2TokenFactory` is a deprecated contract but we've included it since one - // of the highest TVL pools, 80BAL-20WETH, is able to be tracked. + // The `WeightedPool2TokenFactory` is a deprecated contract, but we've included + // it to be able to track one of the highest TVL pools: 80BAL-20WETH. hex!("A5bf2ddF098bb0Ef6d120C98217dD6B141c74EE0") => { let create_call = 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)?; - Some(tycho::ProtocolComponent { - id: hex::encode(&pool_created.pool), - tokens: create_call.tokens, - contracts: vec![pool_created.pool], - static_att: vec![ - tycho::Attribute { - name: "pool_type".into(), - value: "WeightedPool2TokensFactory".into(), - change: tycho::ChangeType::Creation.into(), - }, - tycho::Attribute { - name: "weights".into(), - value: create_call.weights.serialize_bytes(), - change: tycho::ChangeType::Creation.into(), - }, - ], - change: tycho::ChangeType::Creation.into(), - protocol_type: Some(ProtocolType { - name: "balancer_pool".to_string(), - financial_type: FinancialType::Swap.into(), - attribute_schema: vec![], - implementation_type: ImplementationType::Vm.into(), - }), - tx: Some(tx.clone()), - }) + Some( + ProtocolComponent::at_contract(&pool_created.pool, &tx) + .with_tokens(&create_call.tokens) + .with_attributes(&[ + ("pool_type", "WeightedPool2TokensFactory".as_bytes()), + ("weights", &create_call.weights.serialize_bytes()), + ]) + .as_swap_type("balancer_pool", ImplementationType::Vm), + ) } _ => None, }