Skip to main content

revm_trace/utils/
proxy_utils.rs

1//! Proxy contract analysis and implementation resolution
2//!
3//! This module provides utilities for:
4//! - Detecting proxy contracts
5//! - Resolving implementation addresses
6//! - Supporting multiple proxy patterns:
7//!   - EIP-1967 (Transparent Proxy)
8//!   - EIP-1822 (UUPS Proxy)
9//!   - OpenZeppelin Proxy
10//!   - Beacon Proxy
11
12use crate::{
13    errors::{EvmError, RuntimeError},
14    evm::TraceEvm,
15};
16use alloy::primitives::{Address, U256};
17use anyhow::Result;
18use once_cell::sync::Lazy;
19use revm::{context_interface::ContextTr, database::Database};
20use std::str::FromStr;
21
22/// Slot for EIP-1967 implementation address
23///
24/// Calculated as: keccak256("eip1967.proxy.implementation") - 1
25const EIP_1967_LOGIC_SLOT: &str =
26    "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
27
28/// Storage slot for EIP-1967 beacon address
29///
30/// Calculated as: keccak256("eip1967.proxy.beacon") - 1
31const EIP_1967_BEACON_SLOT: &str =
32    "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50";
33
34/// Storage slot for OpenZeppelin implementation address
35///
36/// Calculated as: keccak256("eip1967.proxy.implementation") - 1
37const OZ_IMPLEMENTATION_SLOT: &str =
38    "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3";
39
40/// Storage slot for EIP-1822 implementation address
41///
42/// Calculated as: keccak256("eip1822.proxy.implementation") - 1
43const EIP_1822_LOGIC_SLOT: &str =
44    "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
45
46/// Storage slots for different proxy patterns
47static IMPLEMENTATION_SLOTS: Lazy<Vec<U256>> = Lazy::new(|| {
48    vec![
49        // EIP-1967 implementation slot
50        U256::from_str(EIP_1967_LOGIC_SLOT).unwrap(),
51        // EIP-1967 beacon slot
52        U256::from_str(EIP_1967_BEACON_SLOT).unwrap(),
53        // OpenZeppelin implementation slot
54        U256::from_str(OZ_IMPLEMENTATION_SLOT).unwrap(),
55        // EIP-1822 implementation slot
56        U256::from_str(EIP_1822_LOGIC_SLOT).unwrap(),
57    ]
58});
59
60/// Attempts to find the implementation address for a proxy contract
61///
62/// Checks multiple proxy patterns to find the implementation contract address.
63/// Supports the following proxy patterns:
64/// - EIP-1967 Transparent Proxy
65/// - EIP-1967 Beacon Proxy
66/// - OpenZeppelin Legacy Proxy
67/// - EIP-1822 Universal Upgradeable Proxy (UUPS)
68///
69/// # Arguments
70/// * `evm` - Configured EVM instance for state access
71/// * `contract` - Address of the potential proxy contract
72///
73/// # Returns
74/// * `Ok(Some(Address))` - Implementation address if found
75/// * `Ok(None)` - If no implementation is found (might not be a proxy)
76/// * `Err(_)` - If there's an error accessing contract state
77///
78/// # Example
79/// ```no_run
80/// use revm_trace::utils::proxy_utils::get_implementation;
81/// use revm_trace::create_evm;
82/// use alloy::primitives::address;
83///
84/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
85/// let mut evm = create_evm("https://eth.llamarpc.com").await?;
86///
87/// // USDT proxy contract
88/// let proxy = address!("dac17f958d2ee523a2206206994597c13d831ec7");
89///
90/// if let Some(implementation) = get_implementation(&mut evm, proxy)? {
91///     println!("Implementation found at: {}", implementation);
92/// } else {
93///     println!("No implementation found (not a proxy)");
94/// }
95/// # Ok(())
96/// # }
97/// ```
98///
99/// # Implementation Details
100/// The function:
101/// 1. Checks each known implementation slot in order
102/// 2. For non-zero values, attempts to convert to an address
103/// 3. Verifies the address has deployed code
104/// 4. Returns the first valid implementation found
105///
106/// # Common Proxy Patterns
107/// - EIP-1967: Modern transparent proxy pattern
108/// - EIP-1822: Universal Upgradeable Proxy Standard (UUPS)
109/// - OpenZeppelin: Legacy proxy implementation
110/// - Beacon: Proxy pattern for multiple contracts sharing same implementation
111pub fn get_implementation<DB, INSP>(
112    evm: &mut TraceEvm<DB, INSP>,
113    proxy: Address,
114) -> Result<Option<Address>, EvmError>
115where
116    DB: Database,
117{
118    // First verify if the contract exists
119    if evm
120        .db()
121        .basic(proxy)
122        .map_err(|e| {
123            RuntimeError::AccountAccess(format!("Get contract {proxy} state failed: {e}"))
124        })?
125        .is_none()
126    {
127        return Ok(None);
128    }
129    // Check each possible implementation slot
130    for &slot in IMPLEMENTATION_SLOTS.iter() {
131        let value = evm.db().storage(proxy, slot).map_err(|e| {
132            RuntimeError::SlotAccess(format!(
133                "Get contract {proxy} slot {slot} state failed: {e}"
134            ))
135        })?;
136        if value != U256::ZERO {
137            let impl_address = Address::from_slice(&value.to_be_bytes::<32>()[12..32]);
138
139            // Only verify if the implementation account exists
140            if let Some(impl_acc) = evm.db().basic(impl_address).map_err(|e| {
141                RuntimeError::AccountAccess(format!(
142                    "Get implementation {impl_address} state failed: {e}"
143                ))
144            })? {
145                // Check if account has code without loading it
146                if !impl_acc.code_hash.is_zero() {
147                    return Ok(Some(impl_address));
148                }
149            }
150        }
151    }
152
153    Ok(None)
154}