Ekubo TWAMM & MEV-resist integration (#192)

* Add Ekubo TWAMM support

* Change order of words

* Account TWAMM order balances

* Fix tracking wrong component balance deltas

Swapped and PositionUpdated are the only events affecting pool TVL

* Fix fee addition

Fees are a .64 instead of a .128 since v2 & the result is rounded

* Consistent naming

* cargo fmt

* Add method for selecting store method from change type

* Only store the affected sale rate delta on OrderUpdated events

* Remove unnecessary parameterization

* Index Ekubo MEV-resist pools

* cargo clippy
This commit is contained in:
die-herdplatte
2025-06-30 16:45:08 +02:00
committed by GitHub
parent ef6c826a8a
commit 1ff97ff43f
28 changed files with 3750 additions and 313 deletions

View File

@@ -14,7 +14,7 @@ use tycho_substreams::{
use crate::pb::ekubo::{
block_transaction_events::transaction_events::pool_log::Event, BlockTransactionEvents,
LiquidityChanges, TickDeltas,
LiquidityChanges, OrderSaleRateDeltas, SaleRateChanges, TickDeltas,
};
/// Aggregates protocol components and balance changes by transaction.
@@ -30,8 +30,12 @@ fn map_protocol_changes(
balances_store_deltas: StoreDeltas,
ticks_map_deltas: TickDeltas,
ticks_store_deltas: StoreDeltas,
order_sale_rate_map_deltas: OrderSaleRateDeltas,
order_sale_rate_store_deltas: StoreDeltas,
liquidity_changes: LiquidityChanges,
liquidity_store_deltas: StoreDeltas,
sale_rate_changes: SaleRateChanges,
sale_rate_store_deltas: StoreDeltas,
) -> Result<BlockChanges, substreams::errors::Error> {
let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new();
@@ -90,21 +94,11 @@ fn map_protocol_changes(
.into_iter()
.zip(ticks_map_deltas.deltas)
.for_each(|(store_delta, tick_delta)| {
let new_value_bigint = BigInt::from_store_bytes(&store_delta.new_value);
let (old_value, new_value) = (
BigInt::from_store_bytes(&store_delta.old_value),
BigInt::from_store_bytes(&store_delta.new_value),
);
let is_creation = BigInt::from_store_bytes(&store_delta.old_value).is_zero();
let attribute = Attribute {
name: format!("ticks/{}", tick_delta.tick_index),
value: new_value_bigint.to_signed_bytes_be(),
change: if is_creation {
ChangeType::Creation.into()
} else if new_value_bigint.is_zero() {
ChangeType::Deletion.into()
} else {
ChangeType::Update.into()
},
};
let tx = tick_delta.transaction.unwrap();
let builder = transaction_changes
.entry(tx.index)
@@ -112,7 +106,39 @@ fn map_protocol_changes(
builder.add_entity_change(&EntityChanges {
component_id: tick_delta.pool_id.to_hex(),
attributes: vec![attribute],
attributes: vec![Attribute {
name: format!("ticks/{}", tick_delta.tick_index),
value: new_value.to_signed_bytes_be(),
change: change_type_from_delta(&old_value, &new_value).into(),
}],
});
});
// TWAMM order sale rate deltas
order_sale_rate_store_deltas
.deltas
.into_iter()
.zip(order_sale_rate_map_deltas.deltas)
.for_each(|(store_delta, sale_rate_delta)| {
let tx = sale_rate_delta.transaction.unwrap();
let builder = transaction_changes
.entry(tx.index)
.or_insert_with(|| TransactionChangesBuilder::new(&tx.into()));
let (old_value, new_value) = (
BigInt::from_store_bytes(&store_delta.old_value),
BigInt::from_store_bytes(&store_delta.new_value),
);
let token = if sale_rate_delta.is_token1 { "token1" } else { "token0" };
builder.add_entity_change(&EntityChanges {
component_id: sale_rate_delta.pool_id.to_hex(),
attributes: vec![Attribute {
name: format!("orders/{}/{}", token, sale_rate_delta.time),
value: new_value.to_signed_bytes_be(),
change: change_type_from_delta(&old_value, &new_value).into(),
}],
});
});
@@ -127,22 +153,50 @@ fn map_protocol_changes(
.entry(tx.index)
.or_insert_with(|| TransactionChangesBuilder::new(&tx.into()));
let new_value_bigint = BigInt::from_str(key::segment_at(
&String::from_utf8(store_delta.new_value).unwrap(),
1,
))
.unwrap();
builder.add_entity_change(&EntityChanges {
component_id: change.pool_id.to_hex(),
attributes: vec![Attribute {
name: "liquidity".to_string(),
value: new_value_bigint.to_signed_bytes_be(),
value: bigint_from_set_sum_store_delta_value(store_delta.new_value)
.to_signed_bytes_be(),
change: ChangeType::Update.into(),
}],
});
});
// TWAMM active sale rates
sale_rate_store_deltas
.deltas
.chunks(2)
.zip(sale_rate_changes.changes)
.for_each(|(store_deltas, change)| {
let tx = change.transaction.unwrap();
let builder = transaction_changes
.entry(tx.index)
.or_insert_with(|| TransactionChangesBuilder::new(&tx.into()));
let (token0_sale_rate, token1_sale_rate) = (
bigint_from_set_sum_store_delta_value(store_deltas[0].new_value.clone()),
bigint_from_set_sum_store_delta_value(store_deltas[1].new_value.clone()),
);
builder.add_entity_change(&EntityChanges {
component_id: change.pool_id.to_hex(),
attributes: vec![
Attribute {
name: "token0_sale_rate".to_string(),
value: token0_sale_rate.to_bytes_be().1,
change: ChangeType::Update.into(),
},
Attribute {
name: "token1_sale_rate".to_string(),
value: token1_sale_rate.to_bytes_be().1,
change: ChangeType::Update.into(),
},
],
});
});
// Remaining event changes not subject to special treatment
block_tx_events
.block_transaction_events
@@ -156,12 +210,17 @@ fn map_protocol_changes(
.flat_map(move |log| {
let tx = tx.clone();
maybe_attribute_updates(log.event.unwrap()).map(|attrs| {
(
tx,
EntityChanges { component_id: log.pool_id.to_hex(), attributes: attrs },
)
})
maybe_attribute_updates(log.event.unwrap(), block_tx_events.timestamp).map(
|attrs| {
(
tx,
EntityChanges {
component_id: log.pool_id.to_hex(),
attributes: attrs,
},
)
},
)
})
})
.for_each(|(tx, entity_changes)| {
@@ -181,23 +240,39 @@ fn map_protocol_changes(
})
}
fn maybe_attribute_updates(ev: Event) -> Option<Vec<Attribute>> {
fn maybe_attribute_updates(ev: Event, timestamp: u64) -> Option<Vec<Attribute>> {
match ev {
Event::Swapped(swapped) => Some(vec![
Event::Swapped(ev) => Some(vec![
Attribute {
name: "tick".into(),
value: swapped
.tick_after
.to_be_bytes()
.to_vec(),
value: ev.tick_after.to_be_bytes().to_vec(),
change: ChangeType::Update.into(),
},
Attribute {
name: "sqrt_ratio".into(),
value: swapped.sqrt_ratio_after,
value: ev.sqrt_ratio_after,
change: ChangeType::Update.into(),
},
]),
Event::VirtualOrdersExecuted(_) => Some(vec![Attribute {
name: "last_execution_time".to_string(),
value: timestamp.to_be_bytes().to_vec(),
change: ChangeType::Update.into(),
}]),
_ => None,
}
}
fn change_type_from_delta(old_value: &BigInt, new_value: &BigInt) -> ChangeType {
if old_value.is_zero() {
ChangeType::Creation
} else if new_value.is_zero() {
ChangeType::Deletion
} else {
ChangeType::Update
}
}
fn bigint_from_set_sum_store_delta_value(value: Vec<u8>) -> BigInt {
BigInt::from_str(key::segment_at(&String::from_utf8(value).unwrap(), 1)).unwrap()
}