1use alloy::primitives::{Address, B256, U256};
10
11pub mod slots {
13 use alloy::primitives::B256;
14
15 pub const EIP1967_IMPLEMENTATION: B256 = B256::new([
17 0x36, 0x08, 0x94, 0xa1, 0x3b, 0xa1, 0xa3, 0x21, 0x06, 0x67, 0xc8, 0x28, 0x49, 0x2d, 0xb9,
18 0x8d, 0xca, 0x3e, 0x20, 0x76, 0xcc, 0x37, 0x35, 0xa9, 0x20, 0xa3, 0xca, 0x50, 0x5d, 0x38,
19 0x2b, 0xbc,
20 ]);
21
22 pub const EIP1967_BEACON: B256 = B256::new([
24 0xa3, 0xf0, 0xad, 0x74, 0xe5, 0x42, 0x3a, 0xeb, 0xfd, 0x80, 0xd3, 0xef, 0x4a, 0xe1, 0x47,
25 0x9b, 0xe7, 0x7d, 0x9a, 0x59, 0x50, 0x79, 0x28, 0xea, 0x95, 0x26, 0x91, 0xf4, 0x76, 0x82,
26 0x65, 0x43,
27 ]);
28
29 pub const EIP1967_ADMIN: B256 = B256::new([
31 0xb5, 0x31, 0x27, 0x68, 0x4a, 0x56, 0x8b, 0x31, 0x73, 0xae, 0x13, 0xb9, 0xf8, 0xa6, 0x01,
32 0x6e, 0x24, 0x3e, 0x63, 0xb6, 0xe8, 0xee, 0x17, 0x66, 0x78, 0x3a, 0x7e, 0x2e, 0x70, 0x72,
33 0xef, 0xfc,
34 ]);
35
36 pub const OZ_LEGACY_IMPLEMENTATION: B256 = B256::new([
38 0x7e, 0x36, 0x7f, 0x9d, 0x1d, 0x41, 0xdb, 0x98, 0x6f, 0xbe, 0x8e, 0x56, 0x2a, 0xf1, 0x89,
39 0x1c, 0xc6, 0x4f, 0x2e, 0x06, 0x3f, 0x73, 0x12, 0xbe, 0x55, 0x4b, 0x12, 0x3b, 0xa7, 0x35,
40 0x87, 0xb0,
41 ]);
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum ProxyType {
48 Eip1167 {
50 implementation: Address,
52 },
53 Eip1967,
55 Eip1822,
57 OpenZeppelinLegacy,
59 Generic,
61}
62
63impl ProxyType {
64 pub fn name(&self) -> &'static str {
65 match self {
66 ProxyType::Eip1167 { .. } => "EIP-1167 Minimal Proxy",
67 ProxyType::Eip1967 => "EIP-1967 Transparent Proxy",
68 ProxyType::Eip1822 => "EIP-1822 UUPS Proxy",
69 ProxyType::OpenZeppelinLegacy => "OpenZeppelin Legacy Proxy",
70 ProxyType::Generic => "Generic Proxy",
71 }
72 }
73
74 pub fn description(&self) -> &'static str {
75 match self {
76 ProxyType::Eip1167 { .. } => {
77 "Minimal proxy clone with implementation address embedded in bytecode"
78 }
79 ProxyType::Eip1967 => "Transparent proxy with implementation stored at EIP-1967 slot",
80 ProxyType::Eip1822 => {
81 "Universal Upgradeable Proxy Standard with upgrade logic in implementation"
82 }
83 ProxyType::OpenZeppelinLegacy => "Legacy OpenZeppelin proxy pattern (pre-EIP-1967)",
84 ProxyType::Generic => "Proxy detected via DELEGATECALL but pattern not recognized",
85 }
86 }
87}
88
89#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
91pub struct ProxyInfo {
92 pub is_proxy: bool,
94 pub proxy_type: Option<ProxyType>,
96 pub implementation: Option<Address>,
99}
100
101pub fn detect_eip1167(bytecode: &[u8]) -> Option<Address> {
106 if bytecode.len() < 45 {
108 return None;
109 }
110
111 let prefix = [0x36, 0x3d, 0x3d, 0x37, 0x3d, 0x3d, 0x3d, 0x36, 0x3d, 0x73];
113 if !bytecode.starts_with(&prefix) {
114 return None;
115 }
116
117 let suffix = [
119 0x5a, 0xf4, 0x3d, 0x82, 0x80, 0x3e, 0x90, 0x3d, 0x91, 0x60, 0x2b, 0x57, 0xfd, 0x5b, 0xf3,
120 ];
121 if !bytecode[30..45].starts_with(&suffix) {
122 return None;
123 }
124
125 let addr_bytes: [u8; 20] = bytecode[10..30].try_into().ok()?;
127 Some(Address::from(addr_bytes))
128}
129
130#[must_use]
135pub fn detect_proxy(bytecode: &[u8]) -> ProxyInfo {
136 if let Some(impl_addr) = detect_eip1167(bytecode) {
138 return ProxyInfo {
139 is_proxy: true,
140 proxy_type: Some(ProxyType::Eip1167 {
141 implementation: impl_addr,
142 }),
143 implementation: Some(impl_addr),
144 };
145 }
146
147 let has_delegatecall = bytecode.contains(&0xf4);
150
151 let has_sload = bytecode.contains(&0x54);
155
156 if has_delegatecall && has_sload && bytecode.len() < 2000 {
157 return ProxyInfo {
159 is_proxy: true,
160 proxy_type: Some(ProxyType::Generic),
161 implementation: None,
162 };
163 }
164
165 ProxyInfo::default()
166}
167
168pub fn address_from_storage(value: B256) -> Option<Address> {
170 let bytes = value.as_slice();
172
173 if bytes[..12].iter().all(|&b| b == 0) {
175 let addr_bytes: [u8; 20] = bytes[12..32].try_into().ok()?;
176 let addr = Address::from(addr_bytes);
177
178 if addr.is_zero() {
180 return None;
181 }
182 Some(addr)
183 } else {
184 None
185 }
186}
187
188pub fn u256_to_b256(value: U256) -> B256 {
190 B256::from(value)
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_detect_eip1167() {
199 let impl_addr = Address::repeat_byte(0x12);
201 let mut bytecode = vec![0x36, 0x3d, 0x3d, 0x37, 0x3d, 0x3d, 0x3d, 0x36, 0x3d, 0x73];
202 bytecode.extend_from_slice(impl_addr.as_slice());
203 bytecode.extend_from_slice(&[
204 0x5a, 0xf4, 0x3d, 0x82, 0x80, 0x3e, 0x90, 0x3d, 0x91, 0x60, 0x2b, 0x57, 0xfd, 0x5b,
205 0xf3,
206 ]);
207
208 let detected = detect_eip1167(&bytecode);
209 assert_eq!(detected, Some(impl_addr));
210 }
211
212 #[test]
213 fn test_detect_eip1167_real() {
214 let bytecode = hex::decode(
216 "363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3"
217 ).unwrap();
218
219 let result = detect_proxy(&bytecode);
220 assert!(result.is_proxy);
221 assert!(matches!(result.proxy_type, Some(ProxyType::Eip1167 { .. })));
222 }
223
224 #[test]
225 fn test_not_proxy() {
226 let bytecode = hex::decode("6060604052600080fd").unwrap();
228 let result = detect_proxy(&bytecode);
229 assert!(!result.is_proxy);
230 assert!(result.proxy_type.is_none());
231 }
232
233 #[test]
234 fn test_address_from_storage() {
235 let addr = Address::repeat_byte(0xab);
237 let mut value = [0u8; 32];
238 value[12..32].copy_from_slice(addr.as_slice());
239 let b256 = B256::from(value);
240
241 assert_eq!(address_from_storage(b256), Some(addr));
242 }
243
244 #[test]
245 fn test_address_from_storage_zero() {
246 let b256 = B256::ZERO;
247 assert_eq!(address_from_storage(b256), None);
248 }
249}