Files
tycho-protocol-sdk/substreams/ethereum-uniswap-v4/src/modules/3_map_events.rs
tvinagre af8643ecb4 feat: Implement UniswapV4 Native integration (#125)
* feat: initial data structure + ABI & buf models

* feat: initial modules: identify new pools and events

* feat: map liquidity and balance changes, introduce balance delta calculation from liquidity

* feat: map tick deltas

* chore: undo weird formatting

* chore: undo weird formatting

* feat: map fee changes and aggregate data to final block structure

* feat: reimplement math natively, remove alloy and univ3sdk dependencies

* chore: make clippy happy + misc improvements

* chore: add rust generated files from buf & ABI

* chore: make clippy happy

* feat: add all modules to ethereum-uniswap-v4.yaml

* chore: update yaml file to skip unnecessary buf paths

* fix: update pb mod.rs

* fix: fix hex / utf-8 encoding. working version

* fix: simplify error handling, rename yaml file to match chain & formatting

* fix: fix ChangeType for sqrt_price_x96 on Initialization

* fix: make fee a non-static attribute

* fix: add balance_owner to univ4 pool

* feat: add uniswap-v4 mainnet yaml

* fix(uniswap-v4): miscellaneous fixes for UniswapV4 (#147)

* fix(uniswap-v4): correctly decode swap event deltas.

Deltas are given from a user debt perspective by the event (negative if received by the pool, positive if sent by the pool). In our usecase we need the opposite of this. This commit uses `.neg()` on the amount to apply them correctly.

* fix(uniswap-v4): correctly compute token amounts.

We were using `get_sqrt_ratio_at_tick` at the current tick to compute the amounts. This was incorrect because it provides the price at the tick boundary, but we might be mid-tick, which could lead to erroneous balances.

We now track the current price (updated by `initialization` and `swap` events) and use it when calculating the amounts.

* fix(uniswapv4): do not account for fees in swaps.

Previously we were adding fees as balances. But they are actually not part of the TVL and we aren't accounting for fees in the position changes (withdrawals). This commit addresses this and remove the fees from the token balances.

* refactor: fix pb mod.rs

* refactor: bump version and update sepolia config

* ci: make clippy happy

* refactor: clean unimath tests

* refactor: make logic clearer and improve docstrings

---------

Co-authored-by: zizou <111426680+flopell@users.noreply.github.com>

---------

Co-authored-by: Zizou <111426680+zizou0x@users.noreply.github.com>
Co-authored-by: zizou <111426680+flopell@users.noreply.github.com>
2025-02-05 14:19:09 +01:00

139 lines
5.3 KiB
Rust

use crate::{
abi::pool_manager::events::{Initialize, ModifyLiquidity, ProtocolFeeUpdated, Swap},
pb::uniswap::v4::{
events::{pool_event, pool_event::Type, PoolEvent},
Events, Pool,
},
};
use anyhow::Ok;
use substreams::store::{StoreGet, StoreGetProto};
use substreams_ethereum::{
pb::eth::v2::{self as eth, Log, TransactionTrace},
Event,
};
use substreams_helper::hex::Hexable;
#[substreams::handlers::map]
pub fn map_events(
block: eth::Block,
pools_store: StoreGetProto<Pool>,
) -> Result<Events, anyhow::Error> {
let mut pool_manager_events = block
.transaction_traces
.into_iter()
.filter(|tx| tx.status == 1)
.flat_map(|tx| {
tx.clone()
.receipt
.into_iter()
.flat_map(|receipt| receipt.logs)
.filter_map(|log| log_to_event(&log, tx.clone(), &pools_store))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
pool_manager_events.sort_unstable_by_key(|e| e.log_ordinal);
Ok(Events { pool_events: pool_manager_events })
}
fn log_to_event(
event: &Log,
tx: TransactionTrace,
pools_store: &StoreGetProto<Pool>,
) -> Option<PoolEvent> {
if let Some(init) = Initialize::match_and_decode(event) {
// We need to track initialization again to keep track of pool current tick, which is set on
// initialization and changed on swaps.
let pool_id = init.id.to_vec().to_hex();
let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?;
Some(PoolEvent {
log_ordinal: event.ordinal,
pool_id,
currency0: pool.currency0.to_hex(),
currency1: pool.currency1.to_hex(),
transaction: Some(tx.into()),
r#type: Some(Type::Initialize(pool_event::Initialize {
sqrt_price_x96: init.sqrt_price_x96.to_string(),
tick: init.tick.into(),
fee: init.fee.into(),
tick_spacing: init.tick_spacing.into(),
hooks: init.hooks.to_vec().to_hex(),
})),
})
} else if let Some(swap) = Swap::match_and_decode(event) {
let pool_id = swap.id.to_vec().to_hex();
let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?;
Some(PoolEvent {
log_ordinal: event.ordinal,
pool_id,
currency0: pool.currency0.to_hex(),
currency1: pool.currency1.to_hex(),
transaction: Some(tx.into()),
r#type: Some(Type::Swap(pool_event::Swap {
sender: swap.sender.to_hex(),
amount0: swap.amount0.to_string(),
amount1: swap.amount1.to_string(),
sqrt_price_x96: swap.sqrt_price_x96.to_string(),
liquidity: swap.liquidity.to_string(),
tick: swap.tick.into(),
fee: swap.fee.into(),
})),
})
// Skipped because Donate doesn't seem to affect pool liquidity?
// } else if let Some(flash) = Donate::match_and_decode(event) {
// let pool_id = flash.id.to_vec().to_hex();
// let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?;
// Some(PoolEvent {
// log_ordinal: event.ordinal,
// pool_id,
// currency0: pool.currency0.to_hex(),
// currency1: pool.currency1.to_hex(),
// transaction: Some(tx.into()),
// r#type: Some(Type::Donate(pool_event::Donate {
// sender: flash.sender.to_hex(),
// amount0: flash.amount0.to_string(),
// amount1: flash.amount1.to_string(),
// })),
// })
} else if let Some(modify_liquidity) = ModifyLiquidity::match_and_decode(event) {
let pool_id = modify_liquidity.id.to_vec().to_hex();
let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?;
Some(PoolEvent {
log_ordinal: event.ordinal,
pool_id,
currency0: pool.currency0.to_hex(),
currency1: pool.currency1.to_hex(),
transaction: Some(tx.into()),
r#type: Some(Type::ModifyLiquidity(pool_event::ModifyLiquidity {
sender: modify_liquidity.sender.to_hex(),
tick_lower: modify_liquidity.tick_lower.into(),
tick_upper: modify_liquidity.tick_upper.into(),
liquidity_delta: modify_liquidity
.liquidity_delta
.to_string(),
salt: modify_liquidity.salt.to_vec().to_hex(),
})),
})
} else if let Some(protocol_fee_updated) = ProtocolFeeUpdated::match_and_decode(event) {
let pool_id = protocol_fee_updated
.id
.to_vec()
.to_hex();
let pool = pools_store.get_last(format!("{}:{}", "pool", &pool_id))?;
Some(PoolEvent {
log_ordinal: event.ordinal,
pool_id: pool_id.clone(),
currency0: pool.currency0.to_hex(),
currency1: pool.currency1.to_hex(),
transaction: Some(tx.into()),
r#type: Some(Type::ProtocolFeeUpdated(pool_event::ProtocolFeeUpdated {
pool_id,
protocol_fee: protocol_fee_updated.protocol_fee.into(),
})),
})
} else {
None
}
}