anvil/eth/backend/
cheats.rs

1//! Support for "cheat codes" / bypass functions
2
3use alloy_evm::precompiles::{Precompile, PrecompileInput};
4use alloy_primitives::{
5    Address, Bytes,
6    map::{AddressHashSet, foldhash::HashMap},
7};
8use parking_lot::RwLock;
9use revm::precompile::{
10    PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult, secp256k1::ec_recover_run,
11    utilities::right_pad,
12};
13use std::{borrow::Cow, sync::Arc};
14
15/// ID for the [`CheatEcrecover::precompile_id`] precompile.
16static PRECOMPILE_ID_CHEAT_ECRECOVER: PrecompileId =
17    PrecompileId::Custom(Cow::Borrowed("cheat_ecrecover"));
18
19/// Manages user modifications that may affect the node's behavior
20///
21/// Contains the state of executed, non-eth standard cheat code RPC
22#[derive(Clone, Debug, Default)]
23pub struct CheatsManager {
24    /// shareable state
25    state: Arc<RwLock<CheatsState>>,
26}
27
28impl CheatsManager {
29    /// Sets the account to impersonate
30    ///
31    /// Returns `true` if the account is already impersonated
32    pub fn impersonate(&self, addr: Address) -> bool {
33        trace!(target: "cheats", %addr, "start impersonating");
34        // When somebody **explicitly** impersonates an account we need to store it so we are able
35        // to return it from `eth_accounts`. That's why we do not simply call `is_impersonated()`
36        // which does not check that list when auto impersonation is enabled.
37        !self.state.write().impersonated_accounts.insert(addr)
38    }
39
40    /// Removes the account that from the impersonated set
41    pub fn stop_impersonating(&self, addr: &Address) {
42        trace!(target: "cheats", %addr, "stop impersonating");
43        self.state.write().impersonated_accounts.remove(addr);
44    }
45
46    /// Returns true if the `addr` is currently impersonated
47    pub fn is_impersonated(&self, addr: Address) -> bool {
48        if self.auto_impersonate_accounts() {
49            true
50        } else {
51            self.state.read().impersonated_accounts.contains(&addr)
52        }
53    }
54
55    /// Returns true is auto impersonation is enabled
56    pub fn auto_impersonate_accounts(&self) -> bool {
57        self.state.read().auto_impersonate_accounts
58    }
59
60    /// Sets the auto impersonation flag which if set to true will make the `is_impersonated`
61    /// function always return true
62    pub fn set_auto_impersonate_account(&self, enabled: bool) {
63        trace!(target: "cheats", "Auto impersonation set to {:?}", enabled);
64        self.state.write().auto_impersonate_accounts = enabled
65    }
66
67    /// Returns all accounts that are currently being impersonated.
68    pub fn impersonated_accounts(&self) -> AddressHashSet {
69        self.state.read().impersonated_accounts.clone()
70    }
71
72    /// Registers an override so that `ecrecover(signature)` returns `addr`.
73    pub fn add_recover_override(&self, sig: Bytes, addr: Address) {
74        self.state.write().signature_overrides.insert(sig, addr);
75    }
76
77    /// If an override exists for `sig`, returns the address; otherwise `None`.
78    pub fn get_recover_override(&self, sig: &Bytes) -> Option<Address> {
79        self.state.read().signature_overrides.get(sig).copied()
80    }
81
82    /// Returns true if any ecrecover overrides have been registered.
83    pub fn has_recover_overrides(&self) -> bool {
84        !self.state.read().signature_overrides.is_empty()
85    }
86}
87
88/// Container type for all the state variables
89#[derive(Clone, Debug, Default)]
90pub struct CheatsState {
91    /// All accounts that are currently impersonated
92    pub impersonated_accounts: AddressHashSet,
93    /// If set to true will make the `is_impersonated` function always return true
94    pub auto_impersonate_accounts: bool,
95    /// Overrides for ecrecover: Signature => Address
96    pub signature_overrides: HashMap<Bytes, Address>,
97}
98
99impl CheatEcrecover {
100    pub fn new(cheats: Arc<CheatsManager>) -> Self {
101        Self { cheats }
102    }
103}
104
105impl Precompile for CheatEcrecover {
106    fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult {
107        if !self.cheats.has_recover_overrides() {
108            return ec_recover_run(input.data, input.gas);
109        }
110
111        const ECRECOVER_BASE: u64 = 3_000;
112        if input.gas < ECRECOVER_BASE {
113            return Err(PrecompileError::OutOfGas);
114        }
115        let padded = right_pad::<128>(input.data);
116        let v = padded[63];
117        let mut sig_bytes = [0u8; 65];
118        sig_bytes[..64].copy_from_slice(&padded[64..128]);
119        sig_bytes[64] = v;
120        let sig_bytes_wrapped = Bytes::copy_from_slice(&sig_bytes);
121        if let Some(addr) = self.cheats.get_recover_override(&sig_bytes_wrapped) {
122            let mut out = [0u8; 32];
123            out[12..].copy_from_slice(addr.as_slice());
124            return Ok(PrecompileOutput::new(ECRECOVER_BASE, Bytes::copy_from_slice(&out)));
125        }
126        ec_recover_run(input.data, input.gas)
127    }
128
129    fn precompile_id(&self) -> &PrecompileId {
130        &PRECOMPILE_ID_CHEAT_ECRECOVER
131    }
132
133    fn is_pure(&self) -> bool {
134        false
135    }
136}
137
138/// A custom ecrecover precompile that supports cheat-based signature overrides.
139#[derive(Clone, Debug)]
140pub struct CheatEcrecover {
141    cheats: Arc<CheatsManager>,
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn impersonate_returns_false_then_true() {
150        let mgr = CheatsManager::default();
151        let addr = Address::from([1u8; 20]);
152        assert!(!mgr.impersonate(addr));
153        assert!(mgr.impersonate(addr));
154    }
155}