use std::fmt;
use std::sync::LazyLock;
use alloy_primitives::{Address, B256, Bytes, U256};
use revm::state::Bytecode;
use super::types::V3Metadata;
const UNISWAP_V3_MIN_TICK: i32 = -887272;
const UNISWAP_V3_MAX_TICK: i32 = 887272;
const UNISWAP_V2_PAIR_RUNTIME_BYTES: &[u8] =
include_bytes!("bytecodes/uniswap_v2_pair_runtime.bin");
const UNISWAP_V3_POOL_TEMPLATE_BYTES: &[u8] =
include_bytes!("bytecodes/uniswap_v3_pool_template.bin");
static UNISWAP_V2_PAIR_RUNTIME: LazyLock<Bytes> =
LazyLock::new(|| Bytes::from_static(UNISWAP_V2_PAIR_RUNTIME_BYTES));
static UNISWAP_V2_PAIR_RUNTIME_CODE_HASH: LazyLock<B256> =
LazyLock::new(|| runtime_code_hash(&UNISWAP_V2_PAIR_RUNTIME));
static UNISWAP_V3_POOL_TEMPLATE: LazyLock<V3RuntimeBytecodeTemplate> = LazyLock::new(|| {
V3RuntimeBytecodeTemplate::new(
Bytes::from_static(UNISWAP_V3_POOL_TEMPLATE_BYTES),
UNISWAP_V3_IMMUTABLE_PATCHES,
)
});
const V3_POOL_ADDRESS_PATCHES: [BytecodePatch; 1] = [BytecodePatch::new(11259, 32)];
const V3_FACTORY_PATCHES: [BytecodePatch; 3] = [
BytecodePatch::new(8315, 32),
BytecodePatch::new(8829, 32),
BytecodePatch::new(10457, 32),
];
const V3_TOKEN0_PATCHES: [BytecodePatch; 6] = [
BytecodePatch::new(2258, 32),
BytecodePatch::new(4853, 32),
BytecodePatch::new(6740, 32),
BytecodePatch::new(7822, 32),
BytecodePatch::new(9150, 32),
BytecodePatch::new(15650, 32),
];
const V3_TOKEN1_PATCHES: [BytecodePatch; 6] = [
BytecodePatch::new(4551, 32),
BytecodePatch::new(6789, 32),
BytecodePatch::new(7924, 32),
BytecodePatch::new(9284, 32),
BytecodePatch::new(10529, 32),
BytecodePatch::new(15979, 32),
];
const V3_FEE_PATCHES: [BytecodePatch; 4] = [
BytecodePatch::new(3311, 32),
BytecodePatch::new(6603, 32),
BytecodePatch::new(6658, 32),
BytecodePatch::new(10565, 32),
];
const V3_TICK_SPACING_PATCHES: [BytecodePatch; 4] = [
BytecodePatch::new(3072, 32),
BytecodePatch::new(10493, 32),
BytecodePatch::new(19402, 32),
BytecodePatch::new(19452, 32),
];
const V3_MAX_LIQUIDITY_PER_TICK_PATCHES: [BytecodePatch; 3] = [
BytecodePatch::new(8174, 32),
BytecodePatch::new(19295, 32),
BytecodePatch::new(19350, 32),
];
const UNISWAP_V3_IMMUTABLE_PATCHES: V3ImmutablePatches = V3ImmutablePatches {
pool_address: &V3_POOL_ADDRESS_PATCHES,
factory: &V3_FACTORY_PATCHES,
token0: &V3_TOKEN0_PATCHES,
token1: &V3_TOKEN1_PATCHES,
fee: &V3_FEE_PATCHES,
tick_spacing: &V3_TICK_SPACING_PATCHES,
max_liquidity_per_tick: &V3_MAX_LIQUIDITY_PER_TICK_PATCHES,
};
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AdapterCodeSeed {
pub address: Address,
pub runtime_bytecode: Bytes,
pub code_hash: B256,
}
impl AdapterCodeSeed {
pub fn new(address: Address, runtime_bytecode: impl Into<Bytes>) -> Self {
let runtime_bytecode = runtime_bytecode.into();
let code_hash = runtime_code_hash(&runtime_bytecode);
Self {
address,
runtime_bytecode,
code_hash,
}
}
pub fn with_code_hash(
address: Address,
runtime_bytecode: impl Into<Bytes>,
code_hash: B256,
) -> Self {
Self {
address,
runtime_bytecode: runtime_bytecode.into(),
code_hash,
}
}
}
pub fn uniswap_v2_pair_code_seed(address: Address) -> AdapterCodeSeed {
AdapterCodeSeed::with_code_hash(
address,
uniswap_v2_pair_runtime_bytecode(),
uniswap_v2_pair_runtime_code_hash(),
)
}
pub fn v3_code_seed_from_metadata(
address: Address,
metadata: &V3Metadata,
) -> Result<Option<AdapterCodeSeed>, BytecodeTemplateError> {
let Some(factory) = metadata.factory else {
return Ok(None);
};
let Some(token0) = metadata.token0 else {
return Ok(None);
};
let Some(token1) = metadata.token1 else {
return Ok(None);
};
let Some(fee) = metadata.fee else {
return Ok(None);
};
let Some(tick_spacing) = metadata
.tick_spacing
.or_else(|| metadata.storage_layout.map(|layout| layout.tick_spacing))
else {
return Ok(None);
};
uniswap_v3_code_seed(
address,
&V3ImmutablePatchValues {
pool_address: Some(address),
factory: Some(factory),
token0: Some(token0),
token1: Some(token1),
fee: Some(fee),
tick_spacing: Some(tick_spacing),
max_liquidity_per_tick: uniswap_v3_max_liquidity_per_tick(tick_spacing),
},
)
.map(Some)
}
pub fn uniswap_v3_code_seed(
address: Address,
values: &V3ImmutablePatchValues,
) -> Result<AdapterCodeSeed, BytecodeTemplateError> {
let runtime = uniswap_v3_pool_template().render(values)?;
Ok(AdapterCodeSeed::new(address, runtime))
}
pub fn uniswap_v2_pair_runtime_bytecode() -> Bytes {
UNISWAP_V2_PAIR_RUNTIME.clone()
}
pub fn uniswap_v2_pair_runtime_code_hash() -> B256 {
*UNISWAP_V2_PAIR_RUNTIME_CODE_HASH
}
pub fn uniswap_v3_pool_template() -> V3RuntimeBytecodeTemplate {
UNISWAP_V3_POOL_TEMPLATE.clone()
}
pub fn uniswap_v3_max_liquidity_per_tick(tick_spacing: i32) -> Option<U256> {
if tick_spacing <= 0 {
return None;
}
let tick_spacing = tick_spacing as i128;
let min_tick = (UNISWAP_V3_MIN_TICK as i128 / tick_spacing) * tick_spacing;
let max_tick = (UNISWAP_V3_MAX_TICK as i128 / tick_spacing) * tick_spacing;
let tick_count = ((max_tick - min_tick) / tick_spacing) + 1;
if tick_count <= 0 {
return None;
}
Some(U256::from(u128::MAX) / U256::from(tick_count as u128))
}
fn runtime_code_hash(runtime_bytecode: &Bytes) -> B256 {
Bytecode::new_raw(runtime_bytecode.clone()).hash_slow()
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct BytecodePatch {
pub offset: usize,
pub length: usize,
}
impl BytecodePatch {
pub const fn new(offset: usize, length: usize) -> Self {
Self { offset, length }
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V3ImmutablePatches {
pub pool_address: &'static [BytecodePatch],
pub factory: &'static [BytecodePatch],
pub token0: &'static [BytecodePatch],
pub token1: &'static [BytecodePatch],
pub fee: &'static [BytecodePatch],
pub tick_spacing: &'static [BytecodePatch],
pub max_liquidity_per_tick: &'static [BytecodePatch],
}
impl V3ImmutablePatches {
pub fn with_pool_address(mut self, patches: &'static [BytecodePatch]) -> Self {
self.pool_address = patches;
self
}
pub fn with_factory(mut self, patches: &'static [BytecodePatch]) -> Self {
self.factory = patches;
self
}
pub fn with_token0(mut self, patches: &'static [BytecodePatch]) -> Self {
self.token0 = patches;
self
}
pub fn with_token1(mut self, patches: &'static [BytecodePatch]) -> Self {
self.token1 = patches;
self
}
pub fn with_fee(mut self, patches: &'static [BytecodePatch]) -> Self {
self.fee = patches;
self
}
pub fn with_tick_spacing(mut self, patches: &'static [BytecodePatch]) -> Self {
self.tick_spacing = patches;
self
}
pub fn with_max_liquidity_per_tick(mut self, patches: &'static [BytecodePatch]) -> Self {
self.max_liquidity_per_tick = patches;
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct V3RuntimeBytecodeTemplate {
pub runtime_bytecode: Bytes,
pub immutables: V3ImmutablePatches,
}
impl V3RuntimeBytecodeTemplate {
pub fn new(runtime_bytecode: impl Into<Bytes>, immutables: V3ImmutablePatches) -> Self {
Self {
runtime_bytecode: runtime_bytecode.into(),
immutables,
}
}
pub fn render(&self, values: &V3ImmutablePatchValues) -> Result<Bytes, BytecodeTemplateError> {
let mut bytecode = self.runtime_bytecode.to_vec();
patch_optional_address(
&mut bytecode,
"pool_address",
self.immutables.pool_address,
values.pool_address,
)?;
patch_optional_address(
&mut bytecode,
"factory",
self.immutables.factory,
values.factory,
)?;
patch_optional_address(
&mut bytecode,
"token0",
self.immutables.token0,
values.token0,
)?;
patch_optional_address(
&mut bytecode,
"token1",
self.immutables.token1,
values.token1,
)?;
patch_optional_u32(&mut bytecode, "fee", self.immutables.fee, values.fee)?;
patch_optional_i32(
&mut bytecode,
"tick_spacing",
self.immutables.tick_spacing,
values.tick_spacing,
)?;
patch_optional_u256(
&mut bytecode,
"max_liquidity_per_tick",
self.immutables.max_liquidity_per_tick,
values.max_liquidity_per_tick,
)?;
Ok(Bytes::from(bytecode))
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct V3ImmutablePatchValues {
pub pool_address: Option<Address>,
pub factory: Option<Address>,
pub token0: Option<Address>,
pub token1: Option<Address>,
pub fee: Option<u32>,
pub tick_spacing: Option<i32>,
pub max_liquidity_per_tick: Option<U256>,
}
impl V3ImmutablePatchValues {
pub fn with_pool_address(mut self, pool_address: Address) -> Self {
self.pool_address = Some(pool_address);
self
}
pub fn with_factory(mut self, factory: Address) -> Self {
self.factory = Some(factory);
self
}
pub fn with_token0(mut self, token0: Address) -> Self {
self.token0 = Some(token0);
self
}
pub fn with_token1(mut self, token1: Address) -> Self {
self.token1 = Some(token1);
self
}
pub fn with_fee(mut self, fee: u32) -> Self {
self.fee = Some(fee);
self
}
pub fn with_tick_spacing(mut self, tick_spacing: i32) -> Self {
self.tick_spacing = Some(tick_spacing);
self
}
pub fn with_max_liquidity_per_tick(mut self, max_liquidity_per_tick: U256) -> Self {
self.max_liquidity_per_tick = Some(max_liquidity_per_tick);
self
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BytecodeTemplateError {
MissingImmutable {
field: &'static str,
},
InvalidPatchLength {
field: &'static str,
length: usize,
},
PatchOutOfBounds {
field: &'static str,
offset: usize,
length: usize,
bytecode_len: usize,
},
}
impl fmt::Display for BytecodeTemplateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingImmutable { field } => {
write!(f, "missing V3 bytecode immutable `{field}`")
}
Self::InvalidPatchLength { field, length } => {
write!(
f,
"invalid V3 bytecode patch length for `{field}`: {length}"
)
}
Self::PatchOutOfBounds {
field,
offset,
length,
bytecode_len,
} => write!(
f,
"V3 bytecode patch for `{field}` is out of bounds: offset {offset}, length {length}, bytecode length {bytecode_len}"
),
}
}
}
impl std::error::Error for BytecodeTemplateError {}
fn patch_optional_address(
bytecode: &mut [u8],
field: &'static str,
patches: &[BytecodePatch],
value: Option<Address>,
) -> Result<(), BytecodeTemplateError> {
if patches.is_empty() {
return Ok(());
}
let value = value.ok_or(BytecodeTemplateError::MissingImmutable { field })?;
let mut word = [0u8; 32];
word[12..].copy_from_slice(value.as_slice());
patch_word(bytecode, field, patches, word)
}
fn patch_optional_u32(
bytecode: &mut [u8],
field: &'static str,
patches: &[BytecodePatch],
value: Option<u32>,
) -> Result<(), BytecodeTemplateError> {
if patches.is_empty() {
return Ok(());
}
let value = value.ok_or(BytecodeTemplateError::MissingImmutable { field })?;
patch_word(
bytecode,
field,
patches,
U256::from(value).to_be_bytes::<32>(),
)
}
fn patch_optional_i32(
bytecode: &mut [u8],
field: &'static str,
patches: &[BytecodePatch],
value: Option<i32>,
) -> Result<(), BytecodeTemplateError> {
if patches.is_empty() {
return Ok(());
}
let value = value.ok_or(BytecodeTemplateError::MissingImmutable { field })?;
let mut word = if value < 0 { [0xFF; 32] } else { [0u8; 32] };
word[28..].copy_from_slice(&value.to_be_bytes());
patch_word(bytecode, field, patches, word)
}
fn patch_optional_u256(
bytecode: &mut [u8],
field: &'static str,
patches: &[BytecodePatch],
value: Option<U256>,
) -> Result<(), BytecodeTemplateError> {
if patches.is_empty() {
return Ok(());
}
let value = value.ok_or(BytecodeTemplateError::MissingImmutable { field })?;
patch_word(bytecode, field, patches, value.to_be_bytes::<32>())
}
fn patch_word(
bytecode: &mut [u8],
field: &'static str,
patches: &[BytecodePatch],
word: [u8; 32],
) -> Result<(), BytecodeTemplateError> {
for patch in patches {
if patch.length == 0 || patch.length > 32 {
return Err(BytecodeTemplateError::InvalidPatchLength {
field,
length: patch.length,
});
}
let end = patch.offset.checked_add(patch.length).ok_or(
BytecodeTemplateError::PatchOutOfBounds {
field,
offset: patch.offset,
length: patch.length,
bytecode_len: bytecode.len(),
},
)?;
if end > bytecode.len() {
return Err(BytecodeTemplateError::PatchOutOfBounds {
field,
offset: patch.offset,
length: patch.length,
bytecode_len: bytecode.len(),
});
}
let src = 32 - patch.length;
bytecode[patch.offset..end].copy_from_slice(&word[src..]);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn computes_uniswap_v3_max_liquidity_per_tick() {
assert_eq!(
uniswap_v3_max_liquidity_per_tick(10),
Some(U256::from(1917569901783203986719870431555990_u128))
);
assert_eq!(
uniswap_v3_max_liquidity_per_tick(60),
Some(U256::from(11505743598341114571880798222544994_u128))
);
assert_eq!(uniswap_v3_max_liquidity_per_tick(0), None);
}
#[test]
fn embedded_artifacts_decode() {
assert_eq!(uniswap_v2_pair_runtime_bytecode().len(), 11293);
assert_eq!(uniswap_v3_pool_template().runtime_bytecode.len(), 22142);
}
}