1use serde::{Deserialize, Serialize};
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum ProxyKind {
27 Eip1967Logic,
29 Eip1967Beacon,
31 Eip1822Uups,
33 OzTransparent,
35 Eip1167Clone,
37 GnosisSafe,
39 Unknown,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ProxyInfo {
46 pub proxy_address: String,
48 pub kind: ProxyKind,
50 pub implementation: Option<String>,
52 pub slot: Option<String>,
54}
55
56pub const EIP1967_IMPL_SLOT: &str =
61 "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
62
63pub const EIP1967_ADMIN_SLOT: &str =
66 "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103";
67
68pub const EIP1967_BEACON_SLOT: &str =
71 "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50";
72
73pub const EIP1822_PROXIABLE_SLOT: &str =
76 "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
77
78pub const EIP1167_BYTECODE_PREFIX: &[u8] = &[
80 0x36, 0x3d, 0x3d, 0x37, 0x3d, 0x3d, 0x3d, 0x36, 0x3d, 0x73,
81];
82
83pub const EIP1167_BYTECODE_SUFFIX: &[u8] = &[
85 0x5a, 0xf4, 0x3d, 0x82, 0x80, 0x3e, 0x90, 0x3d, 0x91, 0x60, 0x2b, 0x57, 0xfd, 0x5b, 0xf3,
86];
87
88pub fn detect_eip1167_clone(bytecode: &[u8]) -> Option<String> {
98 if bytecode.len() != 45 {
101 return None;
102 }
103 if &bytecode[..10] != EIP1167_BYTECODE_PREFIX {
104 return None;
105 }
106 if &bytecode[30..] != EIP1167_BYTECODE_SUFFIX {
107 return None;
108 }
109 let addr_bytes = &bytecode[10..30];
110 Some(format!("0x{}", hex::encode(addr_bytes)))
111}
112
113pub fn storage_to_address(slot_value: &str) -> Option<String> {
118 let hex = slot_value.strip_prefix("0x").unwrap_or(slot_value);
119 if hex.len() != 64 {
120 return None;
121 }
122 let prefix = &hex[..24];
124 let addr_hex = &hex[24..];
125
126 if prefix.chars().all(|c| c == '0') && addr_hex != "0".repeat(40) {
127 Some(format!("0x{addr_hex}"))
128 } else {
129 None
130 }
131}
132
133pub fn classify_from_storage(
138 proxy_address: &str,
139 eip1967_impl: Option<&str>,
140 eip1967_beacon: Option<&str>,
141 eip1822_proxiable: Option<&str>,
142) -> ProxyInfo {
143 if let Some(impl_raw) = eip1967_impl {
145 if let Some(impl_addr) = storage_to_address(impl_raw) {
146 return ProxyInfo {
147 proxy_address: proxy_address.to_string(),
148 kind: ProxyKind::Eip1967Logic,
149 implementation: Some(impl_addr),
150 slot: Some(EIP1967_IMPL_SLOT.to_string()),
151 };
152 }
153 }
154
155 if let Some(beacon_raw) = eip1967_beacon {
157 if let Some(beacon_addr) = storage_to_address(beacon_raw) {
158 return ProxyInfo {
159 proxy_address: proxy_address.to_string(),
160 kind: ProxyKind::Eip1967Beacon,
161 implementation: Some(beacon_addr),
164 slot: Some(EIP1967_BEACON_SLOT.to_string()),
165 };
166 }
167 }
168
169 if let Some(uups_raw) = eip1822_proxiable {
171 if let Some(impl_addr) = storage_to_address(uups_raw) {
172 return ProxyInfo {
173 proxy_address: proxy_address.to_string(),
174 kind: ProxyKind::Eip1822Uups,
175 implementation: Some(impl_addr),
176 slot: Some(EIP1822_PROXIABLE_SLOT.to_string()),
177 };
178 }
179 }
180
181 ProxyInfo {
182 proxy_address: proxy_address.to_string(),
183 kind: ProxyKind::Unknown,
184 implementation: None,
185 slot: None,
186 }
187}
188
189pub fn proxy_detection_slots() -> Vec<(&'static str, &'static str)> {
194 vec![
195 ("eip1967_impl", EIP1967_IMPL_SLOT),
196 ("eip1967_beacon", EIP1967_BEACON_SLOT),
197 ("eip1822_proxiable", EIP1822_PROXIABLE_SLOT),
198 ]
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn detect_eip1167_valid() {
207 let mut bytecode = Vec::new();
209 bytecode.extend_from_slice(EIP1167_BYTECODE_PREFIX);
210 let impl_addr = [0xABu8; 20];
212 bytecode.extend_from_slice(&impl_addr);
213 bytecode.extend_from_slice(EIP1167_BYTECODE_SUFFIX);
214
215 let detected = detect_eip1167_clone(&bytecode);
216 assert!(detected.is_some());
217 assert_eq!(detected.unwrap(), format!("0x{}", "ab".repeat(20)));
218 }
219
220 #[test]
221 fn detect_eip1167_wrong_length() {
222 let bytecode = vec![0u8; 44]; assert!(detect_eip1167_clone(&bytecode).is_none());
224 }
225
226 #[test]
227 fn storage_to_address_valid() {
228 let slot =
229 "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045";
230 let addr = storage_to_address(slot).unwrap();
231 assert_eq!(addr, "0xd8da6bf26964af9d7eed9e03e53415d37aa96045");
232 }
233
234 #[test]
235 fn storage_to_address_zero_returns_none() {
236 let slot = "0x0000000000000000000000000000000000000000000000000000000000000000";
237 assert!(storage_to_address(slot).is_none());
238 }
239
240 #[test]
241 fn classify_eip1967_impl() {
242 let impl_slot = "0x000000000000000000000000beefbeefbeefbeefbeefbeefbeefbeefbeefbeef";
243 let info = classify_from_storage("0xproxy", Some(impl_slot), None, None);
244 assert_eq!(info.kind, ProxyKind::Eip1967Logic);
245 assert!(info.implementation.is_some());
246 }
247
248 #[test]
249 fn classify_unknown_when_all_zero() {
250 let zero = "0x0000000000000000000000000000000000000000000000000000000000000000";
251 let info = classify_from_storage("0xproxy", Some(zero), Some(zero), Some(zero));
252 assert_eq!(info.kind, ProxyKind::Unknown);
253 }
254
255 #[test]
256 fn detection_slots_non_empty() {
257 let slots = proxy_detection_slots();
258 assert_eq!(slots.len(), 3);
259 assert_eq!(slots[0].1, EIP1967_IMPL_SLOT);
260 }
261}