#![expect(clippy::doc_markdown)]
use alloy::primitives::{keccak256, Address, U256};
pub mod composers;
pub mod config;
pub mod encoders;
pub mod grammar_ledger;
pub mod grammar_plan;
pub mod grammar_shape;
pub mod grammar_walker;
pub const WETH9_BALANCE_OF_SLOT: u64 = 3;
pub const POOL_MANAGER_ERC6909_BALANCE_OF_SLOT: u64 = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WarmupSlots {
pub weth_balance: U256,
pub erc6909_weth: U256,
pub erc6909_native: U256,
}
#[must_use]
pub fn mapping_slot(base_slot: U256, key: U256) -> U256 {
let mut preimage = [0u8; 64];
preimage[0..32].copy_from_slice(&key.to_be_bytes::<32>());
preimage[32..64].copy_from_slice(&base_slot.to_be_bytes::<32>());
U256::from_be_bytes(keccak256(preimage).0)
}
#[must_use]
pub fn nested_mapping_slot(base_slot: U256, key1: U256, key2: U256) -> U256 {
mapping_slot(mapping_slot(base_slot, key1), key2)
}
const fn u256_from_hex(hex: &str) -> U256 {
let bytes = hex.as_bytes();
let mut b = [0u8; 32];
let mut i = 0;
while i < 32 {
let hi = hex_digit(bytes[2 * i]);
let lo = hex_digit(bytes[2 * i + 1]);
b[i] = hi * 16 + lo;
i += 1;
}
U256::from_be_bytes(b)
}
const fn hex_digit(c: u8) -> u8 {
match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
b'A'..=b'F' => c - b'A' + 10,
_ => 0,
}
}
const MASK_160: U256 =
u256_from_hex("000000000000000000000000ffffffffffffffffffffffffffffffffffffffff");
#[must_use]
pub fn erc6909_id(currency: Address) -> U256 {
U256::from_be_bytes(currency.into_word().0) & MASK_160
}
#[must_use]
pub fn compute_simulation_warmup_slots(executor: Address, weth: Address) -> WarmupSlots {
let executor_slot_key = U256::from_be_bytes(executor.into_word().0);
let weth_id = erc6909_id(weth);
let native_id = U256::ZERO;
let base_slot = U256::from(WETH9_BALANCE_OF_SLOT);
let pm_slot = U256::from(POOL_MANAGER_ERC6909_BALANCE_OF_SLOT);
WarmupSlots {
weth_balance: mapping_slot(base_slot, executor_slot_key),
erc6909_weth: nested_mapping_slot(pm_slot, executor_slot_key, weth_id),
erc6909_native: nested_mapping_slot(pm_slot, executor_slot_key, native_id),
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::primitives::{address, Address};
const WETH: Address = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
const WETH_ID: U256 =
u256_from_hex("000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
type Fixture = (
Address, U256, &'static str, U256, &'static str, U256, &'static str, );
const FIXTURES: &[Fixture] = &[
(
address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
u256_from_hex("ca0453669a7127ce38f304ce121e552d78c30286022ebefeef6884684816084d"),
"ca0453669a7127ce38f304ce121e552d78c30286022ebefeef6884684816084d",
u256_from_hex("c87651b1e38cc90cedd910b68ad3a33c54f8132e8e50beaae3ca68aafafe854a"),
"c87651b1e38cc90cedd910b68ad3a33c54f8132e8e50beaae3ca68aafafe854a",
u256_from_hex("27b77f9e86613e8da78f13fe38575cf532b4167ab8d8d5303c689cc9fd0ed7ff"),
"27b77f9e86613e8da78f13fe38575cf532b4167ab8d8d5303c689cc9fd0ed7ff",
),
(
address!("dead000000000000000000000000000000000001"),
u256_from_hex("f35974400be343ad66717b2a38de57c05ec39411b31023551415263aad6916c0"),
"f35974400be343ad66717b2a38de57c05ec39411b31023551415263aad6916c0",
u256_from_hex("95c453f6b7d6cf8b9823c709b3f0fedc6e6a884d3f9f5bdc510e8822c1fb31d5"),
"95c453f6b7d6cf8b9823c709b3f0fedc6e6a884d3f9f5bdc510e8822c1fb31d5",
u256_from_hex("b2e80833314f92ac980f4c8d9255290f04bbb025949387963ffc8a62ffb875c1"),
"b2e80833314f92ac980f4c8d9255290f04bbb025949387963ffc8a62ffb875c1",
),
(
address!("dead000000000000000000000000000000000002"),
u256_from_hex("f189b9f9855f3e9ba1ed2b62d0daf3b7a19d5a80bc3be3cf7a43f4d3f7324366"),
"f189b9f9855f3e9ba1ed2b62d0daf3b7a19d5a80bc3be3cf7a43f4d3f7324366",
u256_from_hex("5dff84ecba6fdee6e82672cd2d229a766608bc3d85c630594b0ac4bd1082be3c"),
"5dff84ecba6fdee6e82672cd2d229a766608bc3d85c630594b0ac4bd1082be3c",
u256_from_hex("394274c6b1bd4f5ef5f3123f467627e5d50a0fcb9c065c4b31a2b16774aaf2a6"),
"394274c6b1bd4f5ef5f3123f467627e5d50a0fcb9c065c4b31a2b16774aaf2a6",
),
];
#[test]
fn parity_vs_python_oracle() {
for &(executor, weth_slot, weth_hex, erc_weth, erc_weth_hex, erc_native, erc_native_hex) in
FIXTURES
{
let slots = compute_simulation_warmup_slots(executor, WETH);
assert_eq!(
slots.weth_balance, weth_slot,
"WETH balance slot (executor {executor:?})"
);
assert_eq!(
slots.erc6909_weth, erc_weth,
"ERC6909 WETH slot (executor {executor:?})"
);
assert_eq!(
slots.erc6909_native, erc_native,
"ERC6909 native slot (executor {executor:?})"
);
assert_eq!(
format!("{:064x}", slots.weth_balance),
weth_hex,
"WETH balance slot hex (executor {executor:?})"
);
assert_eq!(
format!("{:064x}", slots.erc6909_weth),
erc_weth_hex,
"ERC6909 WETH slot hex (executor {executor:?})"
);
assert_eq!(
format!("{:064x}", slots.erc6909_native),
erc_native_hex,
"ERC6909 native slot hex (executor {executor:?})"
);
}
}
#[test]
fn mapping_slot_layout_is_key_then_base_slot() {
let executor = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let executor_u = U256::from_be_bytes(executor.into_word().0);
let got = mapping_slot(U256::from(3u64), executor_u);
let mut preimage = [0u8; 64];
preimage[0..32].copy_from_slice(&executor.into_word().0);
preimage[32..64].copy_from_slice(&U256::from(3u64).to_be_bytes::<32>());
let want = U256::from_be_bytes(keccak256(preimage).0);
assert_eq!(got, want);
assert_eq!(
got,
u256_from_hex("ca0453669a7127ce38f304ce121e552d78c30286022ebefeef6884684816084d")
);
}
#[test]
fn nested_mapping_slot_composition() {
let base = U256::from(4u64);
let k1 = U256::from_be_bytes(
address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
.into_word()
.0,
);
let k2 = WETH_ID;
let got = nested_mapping_slot(base, k1, k2);
let want = mapping_slot(mapping_slot(base, k1), k2);
assert_eq!(got, want);
assert_eq!(
got,
u256_from_hex("c87651b1e38cc90cedd910b68ad3a33c54f8132e8e50beaae3ca68aafafe854a")
);
}
#[test]
fn nested_native_slot_is_double_hashed() {
let base = U256::from(4u64);
let executor = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let k1 = U256::from_be_bytes(executor.into_word().0);
let native = nested_mapping_slot(base, k1, U256::ZERO);
let single = mapping_slot(base, k1);
assert_ne!(native, single);
assert_eq!(
native,
u256_from_hex("27b77f9e86613e8da78f13fe38575cf532b4167ab8d8d5303c689cc9fd0ed7ff")
);
}
#[test]
fn erc6909_id_is_uint160_of_currency() {
assert_eq!(erc6909_id(WETH), WETH_ID);
assert_eq!(
erc6909_id(WETH),
U256::from_be_bytes(WETH.into_word().0) & MASK_160
);
assert_eq!(erc6909_id(Address::ZERO), U256::ZERO);
}
#[test]
fn property_mapping_slot_deterministic() {
use proptest::prelude::*;
proptest!(|(base in 0u64..u64::MAX, key in 0u64..u64::MAX)| {
let a = mapping_slot(U256::from(base), U256::from(key));
let b = mapping_slot(U256::from(base), U256::from(key));
prop_assert_eq!(a, b);
});
}
#[test]
fn property_mapping_slot_spread() {
use proptest::prelude::*;
proptest!(|(base in 0u64..u64::MAX, key in 0u64..u64::MAX)| {
let slot = mapping_slot(U256::from(base), U256::from(key));
prop_assert!(slot > U256::from(u64::MAX));
});
}
#[test]
fn property_mapping_slot_no_collision_over_sweep() {
let base = U256::from(3u64);
let mut seen = std::collections::HashSet::new();
for key in 0u64..1000 {
assert!(
seen.insert(mapping_slot(base, U256::from(key))),
"collision at base=3, key={key}"
);
}
}
}