use alloy_primitives::U256;
use evm_fork_cache::reactive::{
InvalidationReason, InvalidationRequest, ReactiveContext, ReactiveEffect, ResyncId,
ResyncPriority, ResyncReason, ResyncRequest, ResyncTarget,
};
use super::storage::{
V3StorageLayout, layout_for, v3_tick_bitmap_storage_key_with_base,
v3_tick_info_storage_keys_with_base, v3_word_position,
};
use super::{AdapterEvent, PoolInstanceId, PoolRegistration, PurgeScope};
pub(crate) fn v3_tick_range_effects(
pool: &PoolRegistration,
event: &AdapterEvent,
tick_lower: i32,
tick_upper: i32,
ctx: &ReactiveContext,
instance: Option<&PoolInstanceId>,
) -> Vec<ReactiveEffect> {
let Some(address) = pool.key.address() else {
return Vec::new();
};
let Some(layout) = layout_for(pool) else {
return vec![ReactiveEffect::Invalidate(InvalidationRequest {
scope: PurgeScope::AllStorage.into(),
address,
reason: InvalidationReason::HandlerRequested,
})];
};
let slots = v3_tick_range_slots(&layout, tick_lower, tick_upper);
let block = super::reactive::resync_block(ctx);
vec![ReactiveEffect::Resync(ResyncRequest {
id: ResyncId::new(super::reactive::resync_id(
instance, event, address, &slots, &block, ctx,
)),
reason: ResyncReason::HandlerRequested,
block,
targets: vec![ResyncTarget::StorageSlots { address, slots }],
priority: ResyncPriority::High,
})]
}
pub(crate) fn v3_tick_range_slots(
layout: &V3StorageLayout,
tick_lower: i32,
tick_upper: i32,
) -> Vec<U256> {
let mut slots = Vec::new();
for tick in [tick_lower, tick_upper] {
let keys = v3_tick_info_storage_keys_with_base(tick, layout.ticks_base_slot);
slots.extend_from_slice(&keys);
}
let mut words = [
v3_word_position(tick_lower, layout.tick_spacing),
v3_word_position(tick_upper, layout.tick_spacing),
];
words.sort_unstable();
let mut last_word: Option<i16> = None;
for word in words {
if last_word != Some(word) {
slots.push(v3_tick_bitmap_storage_key_with_base(
word,
layout.tick_bitmap_base_slot,
));
last_word = Some(word);
}
}
slots.push(layout.liquidity_slot);
slots.sort_unstable();
slots.dedup();
slots
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapters::storage::V3StorageLayout;
#[test]
fn slot_set_distinct_words() {
let layout = V3StorageLayout::uniswap(60);
let (tick_lower, tick_upper) = (60, 15_360);
let got = v3_tick_range_slots(&layout, tick_lower, tick_upper);
let mut expected = Vec::new();
for tick in [tick_lower, tick_upper] {
let keys = v3_tick_info_storage_keys_with_base(tick, layout.ticks_base_slot);
expected.extend_from_slice(&keys);
}
for word in [
v3_word_position(tick_lower, layout.tick_spacing),
v3_word_position(tick_upper, layout.tick_spacing),
] {
expected.push(v3_tick_bitmap_storage_key_with_base(
word,
layout.tick_bitmap_base_slot,
));
}
expected.push(layout.liquidity_slot);
expected.sort_unstable();
expected.dedup();
assert_eq!(got, expected);
assert_eq!(got.len(), 11);
}
#[test]
fn slot_set_shared_word_dedupes() {
let layout = V3StorageLayout::uniswap(60);
let (tick_lower, tick_upper) = (60, 180);
assert_eq!(
v3_word_position(tick_lower, layout.tick_spacing),
v3_word_position(tick_upper, layout.tick_spacing),
);
let got = v3_tick_range_slots(&layout, tick_lower, tick_upper);
assert_eq!(got.len(), 10);
}
}