feat: ethereum-balancer implementation

This commit is contained in:
0xMochan
2024-01-24 16:28:50 -05:00
parent b37ecaeeb0
commit d8ddc33f23
40 changed files with 25584 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
pub mod yearn_linear_pool_factory;
pub mod composable_stable_pool_factory;
pub mod vault;
pub mod weighted_pool_tokens_factory;
pub mod silo_linear_pool_factory;
pub mod euler_linear_pool_factory;
pub mod weighted_pool_factory;
pub mod managed_pool_factory;
pub mod erc_linear_pool_factory;
pub mod gearbox_linear_pool_factory;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,641 @@
const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error";
/// Contract's functions.
#[allow(dead_code, unused_imports, unused_variables)]
pub mod functions {
use super::INTERNAL_ERR;
#[derive(Debug, Clone, PartialEq)]
pub struct Create {
pub name: String,
pub symbol: String,
pub tokens: Vec<Vec<u8>>,
pub weights: Vec<substreams::scalar::BigInt>,
pub swap_fee_percentage: substreams::scalar::BigInt,
pub oracle_enabled: bool,
pub owner: Vec<u8>,
}
impl Create {
const METHOD_ID: [u8; 4] = [21u8, 150u8, 1u8, 155u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
let maybe_data = call.input.get(4..);
if maybe_data.is_none() {
return Err("no data to decode".to_string());
}
let mut values = ethabi::decode(
&[
ethabi::ParamType::String,
ethabi::ParamType::String,
ethabi::ParamType::Array(
Box::new(ethabi::ParamType::Address),
),
ethabi::ParamType::Array(
Box::new(ethabi::ParamType::Uint(256usize)),
),
ethabi::ParamType::Uint(256usize),
ethabi::ParamType::Bool,
ethabi::ParamType::Address,
],
maybe_data.unwrap(),
)
.map_err(|e| format!("unable to decode call.input: {:?}", e))?;
values.reverse();
Ok(Self {
name: values
.pop()
.expect(INTERNAL_ERR)
.into_string()
.expect(INTERNAL_ERR),
symbol: values
.pop()
.expect(INTERNAL_ERR)
.into_string()
.expect(INTERNAL_ERR),
tokens: values
.pop()
.expect(INTERNAL_ERR)
.into_array()
.expect(INTERNAL_ERR)
.into_iter()
.map(|inner| {
inner.into_address().expect(INTERNAL_ERR).as_bytes().to_vec()
})
.collect(),
weights: values
.pop()
.expect(INTERNAL_ERR)
.into_array()
.expect(INTERNAL_ERR)
.into_iter()
.map(|inner| {
let mut v = [0 as u8; 32];
inner
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
})
.collect(),
swap_fee_percentage: {
let mut v = [0 as u8; 32];
values
.pop()
.expect(INTERNAL_ERR)
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
},
oracle_enabled: values
.pop()
.expect(INTERNAL_ERR)
.into_bool()
.expect(INTERNAL_ERR),
owner: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(
&[
ethabi::Token::String(self.name.clone()),
ethabi::Token::String(self.symbol.clone()),
{
let v = self
.tokens
.iter()
.map(|inner| ethabi::Token::Address(
ethabi::Address::from_slice(&inner),
))
.collect();
ethabi::Token::Array(v)
},
{
let v = self
.weights
.iter()
.map(|inner| ethabi::Token::Uint(
ethabi::Uint::from_big_endian(
match inner.clone().to_bytes_be() {
(num_bigint::Sign::Plus, bytes) => bytes,
(num_bigint::Sign::NoSign, bytes) => bytes,
(num_bigint::Sign::Minus, _) => {
panic!("negative numbers are not supported")
}
}
.as_slice(),
),
))
.collect();
ethabi::Token::Array(v)
},
ethabi::Token::Uint(
ethabi::Uint::from_big_endian(
match self.swap_fee_percentage.clone().to_bytes_be() {
(num_bigint::Sign::Plus, bytes) => bytes,
(num_bigint::Sign::NoSign, bytes) => bytes,
(num_bigint::Sign::Minus, _) => {
panic!("negative numbers are not supported")
}
}
.as_slice(),
),
),
ethabi::Token::Bool(self.oracle_enabled.clone()),
ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)),
],
);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Vec<u8>, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<Vec<u8>, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<Vec<u8>> {
use substreams_ethereum::pb::eth::rpc;
let rpc_calls = rpc::RpcCalls {
calls: vec![
rpc::RpcCall { to_addr : address, data : self.encode(), }
],
};
let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses;
let response = responses
.get(0)
.expect("one response should have existed");
if response.failed {
return None;
}
match Self::output(response.raw.as_ref()) {
Ok(data) => Some(data),
Err(err) => {
use substreams_ethereum::Function;
substreams::log::info!(
"Call output for function `{}` failed to decode with error: {}",
Self::NAME, err
);
None
}
}
}
}
impl substreams_ethereum::Function for Create {
const NAME: &'static str = "create";
fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
Self::match_call(call)
}
fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<Vec<u8>> for Create {
fn output(data: &[u8]) -> Result<Vec<u8>, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GetPauseConfiguration {}
impl GetPauseConfiguration {
const METHOD_ID: [u8; 4] = [45u8, 164u8, 124u8, 64u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Ok(Self {})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(&[]);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<
(substreams::scalar::BigInt, substreams::scalar::BigInt),
String,
> {
Self::output(call.return_data.as_ref())
}
pub fn output(
data: &[u8],
) -> Result<
(substreams::scalar::BigInt, substreams::scalar::BigInt),
String,
> {
let mut values = ethabi::decode(
&[
ethabi::ParamType::Uint(256usize),
ethabi::ParamType::Uint(256usize),
],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
values.reverse();
Ok((
{
let mut v = [0 as u8; 32];
values
.pop()
.expect(INTERNAL_ERR)
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
},
{
let mut v = [0 as u8; 32];
values
.pop()
.expect(INTERNAL_ERR)
.into_uint()
.expect(INTERNAL_ERR)
.to_big_endian(v.as_mut_slice());
substreams::scalar::BigInt::from_unsigned_bytes_be(&v)
},
))
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(
&self,
address: Vec<u8>,
) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> {
use substreams_ethereum::pb::eth::rpc;
let rpc_calls = rpc::RpcCalls {
calls: vec![
rpc::RpcCall { to_addr : address, data : self.encode(), }
],
};
let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses;
let response = responses
.get(0)
.expect("one response should have existed");
if response.failed {
return None;
}
match Self::output(response.raw.as_ref()) {
Ok(data) => Some(data),
Err(err) => {
use substreams_ethereum::Function;
substreams::log::info!(
"Call output for function `{}` failed to decode with error: {}",
Self::NAME, err
);
None
}
}
}
}
impl substreams_ethereum::Function for GetPauseConfiguration {
const NAME: &'static str = "getPauseConfiguration";
fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
Self::match_call(call)
}
fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<
(substreams::scalar::BigInt, substreams::scalar::BigInt),
> for GetPauseConfiguration {
fn output(
data: &[u8],
) -> Result<
(substreams::scalar::BigInt, substreams::scalar::BigInt),
String,
> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GetVault {}
impl GetVault {
const METHOD_ID: [u8; 4] = [141u8, 146u8, 138u8, 248u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Ok(Self {})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(&[]);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Vec<u8>, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<Vec<u8>, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<Vec<u8>> {
use substreams_ethereum::pb::eth::rpc;
let rpc_calls = rpc::RpcCalls {
calls: vec![
rpc::RpcCall { to_addr : address, data : self.encode(), }
],
};
let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses;
let response = responses
.get(0)
.expect("one response should have existed");
if response.failed {
return None;
}
match Self::output(response.raw.as_ref()) {
Ok(data) => Some(data),
Err(err) => {
use substreams_ethereum::Function;
substreams::log::info!(
"Call output for function `{}` failed to decode with error: {}",
Self::NAME, err
);
None
}
}
}
}
impl substreams_ethereum::Function for GetVault {
const NAME: &'static str = "getVault";
fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
Self::match_call(call)
}
fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<Vec<u8>> for GetVault {
fn output(data: &[u8]) -> Result<Vec<u8>, String> {
Self::output(data)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IsPoolFromFactory {
pub pool: Vec<u8>,
}
impl IsPoolFromFactory {
const METHOD_ID: [u8; 4] = [102u8, 52u8, 183u8, 83u8];
pub fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
let maybe_data = call.input.get(4..);
if maybe_data.is_none() {
return Err("no data to decode".to_string());
}
let mut values = ethabi::decode(
&[ethabi::ParamType::Address],
maybe_data.unwrap(),
)
.map_err(|e| format!("unable to decode call.input: {:?}", e))?;
values.reverse();
Ok(Self {
pool: values
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
})
}
pub fn encode(&self) -> Vec<u8> {
let data = ethabi::encode(
&[ethabi::Token::Address(ethabi::Address::from_slice(&self.pool))],
);
let mut encoded = Vec::with_capacity(4 + data.len());
encoded.extend(Self::METHOD_ID);
encoded.extend(data);
encoded
}
pub fn output_call(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<bool, String> {
Self::output(call.return_data.as_ref())
}
pub fn output(data: &[u8]) -> Result<bool, String> {
let mut values = ethabi::decode(
&[ethabi::ParamType::Bool],
data.as_ref(),
)
.map_err(|e| format!("unable to decode output data: {:?}", e))?;
Ok(
values
.pop()
.expect("one output data should have existed")
.into_bool()
.expect(INTERNAL_ERR),
)
}
pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
match call.input.get(0..4) {
Some(signature) => Self::METHOD_ID == signature,
None => false,
}
}
pub fn call(&self, address: Vec<u8>) -> Option<bool> {
use substreams_ethereum::pb::eth::rpc;
let rpc_calls = rpc::RpcCalls {
calls: vec![
rpc::RpcCall { to_addr : address, data : self.encode(), }
],
};
let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses;
let response = responses
.get(0)
.expect("one response should have existed");
if response.failed {
return None;
}
match Self::output(response.raw.as_ref()) {
Ok(data) => Some(data),
Err(err) => {
use substreams_ethereum::Function;
substreams::log::info!(
"Call output for function `{}` failed to decode with error: {}",
Self::NAME, err
);
None
}
}
}
}
impl substreams_ethereum::Function for IsPoolFromFactory {
const NAME: &'static str = "isPoolFromFactory";
fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool {
Self::match_call(call)
}
fn decode(
call: &substreams_ethereum::pb::eth::v2::Call,
) -> Result<Self, String> {
Self::decode(call)
}
fn encode(&self) -> Vec<u8> {
self.encode()
}
}
impl substreams_ethereum::rpc::RPCDecodable<bool> for IsPoolFromFactory {
fn output(data: &[u8]) -> Result<bool, String> {
Self::output(data)
}
}
}
/// Contract's events.
#[allow(dead_code, unused_imports, unused_variables)]
pub mod events {
use super::INTERNAL_ERR;
#[derive(Debug, Clone, PartialEq)]
pub struct PoolCreated {
pub pool: Vec<u8>,
}
impl PoolCreated {
const TOPIC_ID: [u8; 32] = [
131u8,
164u8,
143u8,
188u8,
252u8,
153u8,
19u8,
53u8,
49u8,
78u8,
116u8,
208u8,
73u8,
106u8,
171u8,
106u8,
25u8,
135u8,
233u8,
146u8,
221u8,
200u8,
93u8,
221u8,
188u8,
196u8,
214u8,
221u8,
110u8,
242u8,
233u8,
252u8,
];
pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
if log.topics.len() != 2usize {
return false;
}
if log.data.len() != 0usize {
return false;
}
return log.topics.get(0).expect("bounds already checked").as_ref()
== Self::TOPIC_ID;
}
pub fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
Ok(Self {
pool: ethabi::decode(
&[ethabi::ParamType::Address],
log.topics[1usize].as_ref(),
)
.map_err(|e| {
format!(
"unable to decode param 'pool' from topic of type 'address': {:?}",
e
)
})?
.pop()
.expect(INTERNAL_ERR)
.into_address()
.expect(INTERNAL_ERR)
.as_bytes()
.to_vec(),
})
}
}
impl substreams_ethereum::Event for PoolCreated {
const NAME: &'static str = "PoolCreated";
fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool {
Self::match_log(log)
}
fn decode(
log: &substreams_ethereum::pb::eth::v2::Log,
) -> Result<Self, String> {
Self::decode(log)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,495 @@
use std::collections::HashMap;
use anyhow::Result;
use substreams::pb::substreams::StoreDeltas;
use substreams::store::{
StoreAdd, StoreAddBigInt, StoreGet, StoreGetBigInt, StoreGetProto, StoreNew,
StoreSetIfNotExists, StoreSetIfNotExistsProto,
};
use substreams::{hex, log};
use substreams::key;
use substreams::scalar::BigInt;
use substreams_ethereum::block_view::{CallView, LogView};
use substreams_ethereum::pb::eth;
use substreams_ethereum::pb::eth::v2::{balance_change, Call, Log, TransactionTrace};
use substreams_ethereum::{Event, Function};
use itertools::Itertools;
use pb::tycho::evm::v1::{self as tycho};
mod abi;
mod pb;
const VAULT_ADDRESS: &[u8] = &hex!("BA12222222228d8Ba445958a75a0704d566BF2C8");
/// This trait defines some helpers for serializing and deserializing `Vec<BigInt` which is needed
/// to be able to encode the `normalized_weights` and `weights` `Attribute`s. This should also be
/// handled by any downstream application.
trait SerializableVecBigInt {
fn serialize_bytes(&self) -> Vec<u8>;
fn deserialize_bytes(bytes: &[u8]) -> Vec<BigInt>;
}
impl SerializableVecBigInt for Vec<BigInt> {
fn serialize_bytes(&self) -> Vec<u8> {
self.iter()
.flat_map(|big_int| big_int.to_signed_bytes_be())
.collect()
}
fn deserialize_bytes(bytes: &[u8]) -> Vec<BigInt> {
bytes
.chunks_exact(32)
.map(|chunk| BigInt::from_signed_bytes_be(chunk))
.collect::<Vec<BigInt>>()
}
}
/// This struct purely exists to spoof the `PartialEq` trait for `Transaction` so we can use it in
/// a later groupby operation.
struct TransactionWrapper(tycho::Transaction);
impl PartialEq for TransactionWrapper {
fn eq(&self, other: &Self) -> bool {
self.0.hash == other.0.hash
}
}
/// 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
/// - Linear Pool Factories
/// - 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`
/// Ref: https://docs.balancer.fi/reference/contracts/deployment-addresses/mainnet.html
fn pool_factory_map(pool_addr: &[u8], log: &Log, call: &Call) -> Option<tycho::ProtocolComponent> {
match pool_addr {
&hex!("897888115Ada5773E02aA29F775430BFB5F34c51") => {
let create_call =
abi::weighted_pool_factory::functions::Create::match_and_decode(call)?;
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_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "WeightedPoolFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
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(),
})
}
&hex!("DB8d758BCb971e482B2C45f7F8a7740283A1bd3A") => {
let create_call =
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)?;
Some(tycho::ProtocolComponent {
id: hex::encode(&pool_created.pool),
tokens: create_call.tokens,
contracts: vec![pool_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "ComposableStablePoolFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "amplification_parameter".into(),
value: create_call.amplification_parameter.to_signed_bytes_be(),
change: tycho::ChangeType::Creation.into(),
},
],
change: tycho::ChangeType::Creation.into(),
})
}
&hex!("813EE7a840CE909E7Fea2117A44a90b8063bd4fd") => {
let create_call =
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)?;
Some(tycho::ProtocolComponent {
id: hex::encode(&pool_created.pool),
tokens: vec![create_call.main_token, create_call.wrapped_token],
contracts: vec![pool_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "ERC4626LinearPoolFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
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(),
})
}
&hex!("5F43FBa61f63Fa6bFF101a0A0458cEA917f6B347") => {
let create_call =
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)?;
Some(tycho::ProtocolComponent {
id: hex::encode(&pool_created.pool),
tokens: vec![create_call.main_token, create_call.wrapped_token],
contracts: vec![pool_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "EulerLinearPoolFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
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(),
})
}
&hex!("39A79EB449Fc05C92c39aA6f0e9BfaC03BE8dE5B") => {
let create_call =
abi::gearbox_linear_pool_factory::functions::Create::match_and_decode(call)?;
let pool_created =
abi::gearbox_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_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "GearboxLinearPoolFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
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(),
})
}
// The `ManagedPoolFactory` is a bit ✨ unique ✨, so we'll leave it commented out for now
// Take a look at it's `Create` call to see how the params are structured.
// &hex!("BF904F9F340745B4f0c4702c7B6Ab1e808eA6b93") => {
// let create_call = abi::managed_pool_factory::functions::Create::match_and_decode(call)?;
// let pool_created =
// abi::managed_pool_factory::events::PoolCreated::match_and_decode(log)?;
// Some(tycho::ProtocolComponent {
// id: hex::encode(&pool_created.pool),
// tokens: create_call.tokens,
// contracts: vec![pool_addr.into(), pool_created.pool],
// static_att: vec![
// tycho::Attribute {
// name: "pool_type".into(),
// value: "ManagedPoolFactory".into(),
// change: tycho::ChangeType::Creation.into(),
// },
// tycho::Attribute {
// name: "swap_fee_percentage".into(),
// value: create_call.swap_fee_percentage.to_signed_bytes_be(),
// change: tycho::ChangeType::Creation.into(),
// },
// ],
// change: tycho::ChangeType::Creation.into(),
// })
// }
&hex!("4E11AEec21baF1660b1a46472963cB3DA7811C89") => {
let create_call =
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)?;
Some(tycho::ProtocolComponent {
id: hex::encode(&pool_created.pool),
tokens: vec![create_call.main_token, create_call.wrapped_token],
contracts: vec![pool_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "SiloLinearPoolFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
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(),
})
}
&hex!("5F5222Ffa40F2AEd6380D022184D6ea67C776eE0") => {
let create_call =
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)?;
Some(tycho::ProtocolComponent {
id: hex::encode(&pool_created.pool),
tokens: vec![create_call.main_token, create_call.wrapped_token],
contracts: vec![pool_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "YearnLinearPoolFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
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(),
})
}
// 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.
&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_addr.into(), pool_created.pool],
static_att: vec![
tycho::Attribute {
name: "pool_type".into(),
value: "WeightedPool2TokensFactory".into(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "swap_fee_percentage".into(),
value: create_call.swap_fee_percentage.to_signed_bytes_be(),
change: tycho::ChangeType::Creation.into(),
},
// TODO
tycho::Attribute {
name: "weights".into(),
value: create_call.weights.serialize_bytes(),
change: tycho::ChangeType::Creation.into(),
},
],
change: tycho::ChangeType::Creation.into(),
})
}
_ => None,
}
}
/// Since the `PoolBalanceChanged` 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.
#[substreams::handlers::map]
pub fn map_balance_deltas(block: eth::v2::Block) -> Result<tycho::BalanceDeltas, anyhow::Error> {
Ok(tycho::BalanceDeltas {
balance_deltas: block
.events::<abi::vault::events::PoolBalanceChanged>(&[&VAULT_ADDRESS])
.flat_map(|(event, log)| {
event
.tokens
.iter()
.zip(event.deltas.iter())
.map(|(token, delta)| tycho::BalanceDelta {
ord: log.log.ordinal,
tx: Some(tycho::Transaction {
hash: log.receipt.transaction.hash.clone(),
from: log.receipt.transaction.from.clone(),
to: log.receipt.transaction.to.clone(),
index: Into::<u64>::into(log.receipt.transaction.index).clone(),
}),
token: token.clone(),
delta: delta.to_signed_bytes_be(),
component_id: event.pool_id.into(),
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
})
}
/// It's significant to include both the `pool_id` and the `token_id` for each balance delta as the
/// store key to ensure that there's a unique balance being tallied for each.
#[substreams::handlers::store]
pub fn store_balance_changes(deltas: tycho::BalanceDeltas, store: StoreAddBigInt) {
deltas.balance_deltas.iter().for_each(|delta| {
store.add(
delta.ord,
format!(
"pool:{0}:token:{1}",
hex::encode(&delta.component_id),
hex::encode(&delta.token)
),
BigInt::from_signed_bytes_be(&delta.delta),
);
});
}
/// This is the main map that handles most of the indexing of this substream.
#[substreams::handlers::map]
pub fn map_changes(
block: eth::v2::Block,
deltas: tycho::BalanceDeltas,
store: StoreDeltas, // Note, this map module is using the `deltas` mode for the store.
) -> Result<tycho::BlockContractChanges> {
// Gather contract changes by indexing `PoolCreated` events and analysing the `Create` call
// We store these as a hashmap by tx hash since we need to agg by tx hash later
let mut transaction_contract_changes = block
.transactions()
.flat_map(|tx| {
tx.logs_with_calls()
.filter(|(_, call)| !call.call.state_reverted)
.filter_map(|(log, call)| {
let pool_factory_address = call.call.address.as_slice();
Some((
tx.hash.clone(),
tycho::TransactionContractChanges {
tx: Some(tycho::Transaction {
hash: tx.hash.clone(),
from: tx.from.clone(),
to: tx.to.clone(),
index: Into::<u64>::into(tx.index).clone(),
}),
contract_changes: vec![],
balance_changes: vec![],
component_changes: vec![pool_factory_map(
pool_factory_address,
&log,
&call.call,
)?],
},
))
})
})
.collect::<HashMap<_, _>>();
// Balance changes are gathered by the `StoreDelta` based on `PoolBalanceChanged` creating `BalanceDeltas`
// We essentially just process the changes that occured to the `store` this block
// Then, these balance changes are merged onto the existing map of tx contract changes,
// inserting a new one if it doesn't exist.
store
.deltas
.into_iter()
.zip(deltas.balance_deltas)
.map(|(store_delta, balance_delta)| {
let pool_id = key::segment_at(&store_delta.key, 1);
let token_id = key::segment_at(&store_delta.key, 3);
(
balance_delta.tx.unwrap(),
tycho::BalanceChange {
token: hex::decode(token_id).expect("Token ID not valid hex"),
balance: store_delta.new_value,
component_id: hex::decode(pool_id).expect("Token ID not valid hex"),
},
)
})
// We need to group the balance changes by tx hash for the `TransactionContractChanges` agg
.group_by(|(tx, _)| TransactionWrapper(tx.clone()))
.into_iter()
.for_each(|(tx_wrapped, group)| {
let tx = tx_wrapped.0;
if let Some(tx_change) = transaction_contract_changes.get_mut(&tx.hash) {
tx_change
.balance_changes
.extend(group.map(|(_, change)| change.clone()));
} else {
transaction_contract_changes.insert(
tx.hash.clone(),
tycho::TransactionContractChanges {
tx: Some(tx),
contract_changes: vec![],
component_changes: vec![],
balance_changes: group
.map(|(_, change)| change.clone())
.collect::<Vec<_>>(),
},
);
}
});
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(),
}),
changes: transaction_contract_changes
.into_iter()
.map(|(_, v)| v)
.sorted_unstable_by_key(|tx_change| tx_change.tx.clone().unwrap().index)
.collect::<Vec<_>>(),
})
}

View File

@@ -0,0 +1,28 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pools {
#[prost(message, repeated, tag="1")]
pub pools: ::prost::alloc::vec::Vec<Pool>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pool {
#[prost(bytes="vec", tag="1")]
pub pool_id: ::prost::alloc::vec::Vec<u8>,
#[prost(fixed64, tag="2")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfer {
#[prost(bytes="vec", tag="1")]
pub from: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="2")]
pub to: ::prost::alloc::vec::Vec<u8>,
#[prost(string, tag="3")]
pub token: ::prost::alloc::string::String,
#[prost(string, tag="4")]
pub amount: ::prost::alloc::string::String,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,28 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pools {
#[prost(message, repeated, tag="1")]
pub pools: ::prost::alloc::vec::Vec<Pool>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pool {
#[prost(bytes="vec", tag="1")]
pub pool_id: ::prost::alloc::vec::Vec<u8>,
#[prost(fixed64, tag="2")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfer {
#[prost(bytes="vec", tag="1")]
pub from: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="2")]
pub to: ::prost::alloc::vec::Vec<u8>,
#[prost(string, tag="3")]
pub token: ::prost::alloc::string::String,
#[prost(string, tag="4")]
pub amount: ::prost::alloc::string::String,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,22 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfers {
#[prost(message, repeated, tag="1")]
pub transfers: ::prost::alloc::vec::Vec<Transfer>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfer {
#[prost(string, tag="1")]
pub from: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub to: ::prost::alloc::string::String,
#[prost(uint64, tag="3")]
pub token_id: u64,
#[prost(string, tag="4")]
pub trx_hash: ::prost::alloc::string::String,
#[prost(uint64, tag="5")]
pub ordinal: u64,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,9 @@
pub mod tycho {
pub mod evm {
// @@protoc_insertion_point(attribute:tycho.evm.v1)
pub mod v1 {
include!("tycho.evm.v1.rs");
// @@protoc_insertion_point(tycho.evm.v1)
}
}
}

View File

@@ -0,0 +1,235 @@
// @generated
// This file contains the proto definitions for Substreams common to all integrations.
/// A struct describing a block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Block {
/// The blocks hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The parent blocks hash.
#[prost(bytes="vec", tag="2")]
pub parent_hash: ::prost::alloc::vec::Vec<u8>,
/// The block number.
#[prost(uint64, tag="3")]
pub number: u64,
/// The block timestamp.
#[prost(uint64, tag="4")]
pub ts: u64,
}
/// A struct describing a transaction.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transaction {
/// The transaction hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The sender of the transaction.
#[prost(bytes="vec", tag="2")]
pub from: ::prost::alloc::vec::Vec<u8>,
/// The receiver of the transaction.
#[prost(bytes="vec", tag="3")]
pub to: ::prost::alloc::vec::Vec<u8>,
/// The transactions index within the block.
#[prost(uint64, tag="4")]
pub index: u64,
}
/// A custom struct representing an arbitrary attribute of a protocol component.
/// This is mainly used by the native integration to track the necessary information about the protocol.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Attribute {
/// The name of the attribute.
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
/// The value of the attribute.
#[prost(bytes="vec", tag="2")]
pub value: ::prost::alloc::vec::Vec<u8>,
/// The type of change the attribute underwent.
#[prost(enumeration="ChangeType", tag="3")]
pub change: i32,
}
/// A struct describing a part of the protocol.
/// Note: For example this can be a UniswapV2 pair, that tracks the two ERC20 tokens used by the pair,
/// the component would represent a single contract. In case of VM integration, such component would
/// not need any attributes, because all the relevant info would be tracked via storage slots and balance changes.
/// It can also be a wrapping contract, like WETH, that has a constant price, but it allows swapping tokens.
/// This is why the name ProtocolComponent is used instead of "Pool" or "Pair".
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtocolComponent {
/// A unique identifier for the component within the protocol.
/// Can be e.g. a stringified address or a string describing the trading pair.
#[prost(string, tag="1")]
pub id: ::prost::alloc::string::String,
/// Addresses of the ERC20 tokens used by the component.
#[prost(bytes="vec", repeated, tag="2")]
pub tokens: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Addresses of the contracts used by the component.
/// Usually it is a single contract, but some protocols use multiple contracts.
#[prost(bytes="vec", repeated, tag="3")]
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Attributes of the component. Used mainly be the native integration.
/// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
#[prost(message, repeated, tag="4")]
pub static_att: ::prost::alloc::vec::Vec<Attribute>,
/// Type of change the component underwent.
#[prost(enumeration="ChangeType", tag="5")]
pub change: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ProtocolComponents {
#[prost(message, repeated, tag="1")]
pub components: ::prost::alloc::vec::Vec<ProtocolComponent>,
}
/// A struct for following the changes of Total Value Locked (TVL) of a protocol component.
/// Note that if a ProtocolComponent contains multiple contracts, the TVL is tracked for the component as a whole.
/// E.g. for UniswapV2 pair WETH/USDC, this tracks the USDC and WETH balance of the pair contract.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceChange {
/// The address of the ERC20 token whose balance changed.
#[prost(bytes="vec", tag="1")]
pub token: ::prost::alloc::vec::Vec<u8>,
/// The new balance of the token.
#[prost(bytes="vec", tag="2")]
pub balance: ::prost::alloc::vec::Vec<u8>,
/// The id of the component whose TVL is tracked.
/// If the protocol component includes multiple contracts, the balance change must be aggregated to reflect how much tokens can be traded.
#[prost(bytes="vec", tag="3")]
pub component_id: ::prost::alloc::vec::Vec<u8>,
}
/// A struct for following the changes of Total Value Locked (TVL) of a protocol component.
/// Note that if a ProtocolComponent contains multiple contracts, the TVL is tracked for the component as a whole.
/// E.g. for UniswapV2 pair WETH/USDC, this tracks the USDC and WETH balance of the pair contract.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceDelta {
#[prost(uint64, tag="1")]
pub ord: u64,
/// The tx hash of the transaction that caused the balance change.
#[prost(message, optional, tag="2")]
pub tx: ::core::option::Option<Transaction>,
/// The address of the ERC20 token whose balance changed.
#[prost(bytes="vec", tag="3")]
pub token: ::prost::alloc::vec::Vec<u8>,
/// The delta balance of the token.
#[prost(bytes="vec", tag="4")]
pub delta: ::prost::alloc::vec::Vec<u8>,
/// The id of the component whose TVL is tracked.
/// If the protocol component includes multiple contracts, the balance change must be aggregated to reflect how much tokens can be traded.
#[prost(bytes="vec", tag="5")]
pub component_id: ::prost::alloc::vec::Vec<u8>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceDeltas {
#[prost(message, repeated, tag="1")]
pub balance_deltas: ::prost::alloc::vec::Vec<BalanceDelta>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockBalanceChanges {
#[prost(message, optional, tag="1")]
pub block: ::core::option::Option<Block>,
#[prost(message, repeated, tag="2")]
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
}
/// Enum to specify the type of a change.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ChangeType {
Unspecified = 0,
Update = 1,
Creation = 2,
Deletion = 3,
}
impl ChangeType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
ChangeType::Unspecified => "CHANGE_TYPE_UNSPECIFIED",
ChangeType::Update => "CHANGE_TYPE_UPDATE",
ChangeType::Creation => "CHANGE_TYPE_CREATION",
ChangeType::Deletion => "CHANGE_TYPE_DELETION",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"CHANGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified),
"CHANGE_TYPE_UPDATE" => Some(Self::Update),
"CHANGE_TYPE_CREATION" => Some(Self::Creation),
"CHANGE_TYPE_DELETION" => Some(Self::Deletion),
_ => None,
}
}
}
// 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<u8>,
/// The new value for this storage slot.
#[prost(bytes="vec", tag="3")]
pub value: ::prost::alloc::vec::Vec<u8>,
}
/// 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<u8>,
/// The new native balance of the contract, empty bytes indicates no change.
#[prost(bytes="vec", tag="2")]
pub balance: ::prost::alloc::vec::Vec<u8>,
/// The new code of the contract, empty bytes indicates no change.
#[prost(bytes="vec", tag="3")]
pub code: ::prost::alloc::vec::Vec<u8>,
/// The changes to this contract's slots, empty sequence indicates no change.
#[prost(message, repeated, tag="4")]
pub slots: ::prost::alloc::vec::Vec<ContractSlot>,
/// 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)]
pub struct TransactionContractChanges {
/// The transaction instance that results in the changes.
#[prost(message, optional, tag="1")]
pub tx: ::core::option::Option<Transaction>,
/// 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<ContractChange>,
/// An array of any component changes.
#[prost(message, repeated, tag="3")]
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
/// An array of balance changes to components.
#[prost(message, repeated, tag="4")]
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
}
/// A set of transaction changes within a single block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockContractChanges {
/// The block for which these changes are collectively computed.
#[prost(message, optional, tag="1")]
pub block: ::core::option::Option<Block>,
/// The set of transaction changes observed in the specified block.
#[prost(message, repeated, tag="2")]
pub changes: ::prost::alloc::vec::Vec<TransactionContractChanges>,
}
// @@protoc_insertion_point(module)