reth_testing_utils/
genesis_allocator.rs

1//! Helps create a custom genesis alloc by making it easy to add funded accounts with known
2//! signers to the genesis block.
3
4use alloy_genesis::GenesisAccount;
5use alloy_primitives::{Address, Bytes, B256, U256};
6use reth_primitives_traits::crypto::secp256k1::public_key_to_address;
7use secp256k1::{
8    rand::{thread_rng, RngCore},
9    Keypair, Secp256k1,
10};
11use std::{
12    collections::{hash_map::Entry, BTreeMap, HashMap},
13    fmt,
14};
15
16/// This helps create a custom genesis alloc by making it easy to add funded accounts with known
17/// signers to the genesis block.
18///
19/// # Example
20/// ```
21/// # use alloy_primitives::{Address, U256, hex, Bytes};
22/// # use reth_testing_utils::GenesisAllocator;
23/// # use std::str::FromStr;
24/// let mut allocator = GenesisAllocator::default();
25///
26/// // This will add a genesis account to the alloc builder, with the provided balance. The
27/// // signer for the account will be returned.
28/// let (_signer, _addr) = allocator.new_funded_account(U256::from(100_000_000_000_000_000u128));
29///
30/// // You can also provide code for the account.
31/// let code = Bytes::from_str("0x1234").unwrap();
32/// let (_second_signer, _second_addr) =
33///     allocator.new_funded_account_with_code(U256::from(100_000_000_000_000_000u128), code);
34///
35/// // You can also add an account with a specific address.
36/// // This will not return a signer, since the address is provided by the user and the signer
37/// // may be unknown.
38/// let addr = "0Ac1dF02185025F65202660F8167210A80dD5086".parse::<Address>().unwrap();
39/// allocator.add_funded_account_with_address(addr, U256::from(100_000_000_000_000_000u128));
40///
41/// // Once you're done adding accounts, you can build the alloc.
42/// let alloc = allocator.build();
43/// ```
44pub struct GenesisAllocator<'a> {
45    /// The genesis alloc to be built.
46    alloc: HashMap<Address, GenesisAccount>,
47    /// The rng to use for generating key pairs.
48    rng: Box<dyn RngCore + 'a>,
49}
50
51impl<'a> GenesisAllocator<'a> {
52    /// Initialize a new alloc builder with the provided rng.
53    pub fn new_with_rng<R>(rng: &'a mut R) -> Self
54    where
55        R: RngCore,
56    {
57        Self { alloc: HashMap::default(), rng: Box::new(rng) }
58    }
59
60    /// Use the provided rng for generating key pairs.
61    pub fn with_rng<R>(mut self, rng: &'a mut R) -> Self
62    where
63        R: RngCore + std::fmt::Debug,
64    {
65        self.rng = Box::new(rng);
66        self
67    }
68
69    /// Add a funded account to the genesis alloc.
70    ///
71    /// Returns the key pair for the account and the account's address.
72    pub fn new_funded_account(&mut self, balance: U256) -> (Keypair, Address) {
73        let secp = Secp256k1::new();
74        let pair = Keypair::new(&secp, &mut self.rng);
75        let address = public_key_to_address(pair.public_key());
76
77        self.alloc.insert(address, GenesisAccount::default().with_balance(balance));
78
79        (pair, address)
80    }
81
82    /// Add a funded account to the genesis alloc with the provided code.
83    ///
84    /// Returns the key pair for the account and the account's address.
85    pub fn new_funded_account_with_code(
86        &mut self,
87        balance: U256,
88        code: Bytes,
89    ) -> (Keypair, Address) {
90        let secp = Secp256k1::new();
91        let pair = Keypair::new(&secp, &mut self.rng);
92        let address = public_key_to_address(pair.public_key());
93
94        self.alloc
95            .insert(address, GenesisAccount::default().with_balance(balance).with_code(Some(code)));
96
97        (pair, address)
98    }
99
100    /// Adds a funded account to the genesis alloc with the provided storage.
101    ///
102    /// Returns the key pair for the account and the account's address.
103    pub fn new_funded_account_with_storage(
104        &mut self,
105        balance: U256,
106        storage: BTreeMap<B256, B256>,
107    ) -> (Keypair, Address) {
108        let secp = Secp256k1::new();
109        let pair = Keypair::new(&secp, &mut self.rng);
110        let address = public_key_to_address(pair.public_key());
111
112        self.alloc.insert(
113            address,
114            GenesisAccount::default().with_balance(balance).with_storage(Some(storage)),
115        );
116
117        (pair, address)
118    }
119
120    /// Adds an account with code and storage to the genesis alloc.
121    ///
122    /// Returns the key pair for the account and the account's address.
123    pub fn new_account_with_code_and_storage(
124        &mut self,
125        code: Bytes,
126        storage: BTreeMap<B256, B256>,
127    ) -> (Keypair, Address) {
128        let secp = Secp256k1::new();
129        let pair = Keypair::new(&secp, &mut self.rng);
130        let address = public_key_to_address(pair.public_key());
131
132        self.alloc.insert(
133            address,
134            GenesisAccount::default().with_code(Some(code)).with_storage(Some(storage)),
135        );
136
137        (pair, address)
138    }
139
140    /// Adds an account with code to the genesis alloc.
141    ///
142    /// Returns the key pair for the account and the account's address.
143    pub fn new_account_with_code(&mut self, code: Bytes) -> (Keypair, Address) {
144        let secp = Secp256k1::new();
145        let pair = Keypair::new(&secp, &mut self.rng);
146        let address = public_key_to_address(pair.public_key());
147
148        self.alloc.insert(address, GenesisAccount::default().with_code(Some(code)));
149
150        (pair, address)
151    }
152
153    /// Add a funded account to the genesis alloc with the provided address.
154    ///
155    /// Neither the key pair nor the account will be returned, since the address is provided by
156    /// the user and the signer may be unknown.
157    pub fn add_funded_account_with_address(&mut self, address: Address, balance: U256) {
158        self.alloc.insert(address, GenesisAccount::default().with_balance(balance));
159    }
160
161    /// Adds the given [`GenesisAccount`] to the genesis alloc.
162    ///
163    /// Returns the key pair for the account and the account's address.
164    pub fn add_account(&mut self, account: GenesisAccount) -> Address {
165        let secp = Secp256k1::new();
166        let pair = Keypair::new(&secp, &mut self.rng);
167        let address = public_key_to_address(pair.public_key());
168
169        self.alloc.insert(address, account);
170
171        address
172    }
173
174    /// Gets the account for the provided address.
175    ///
176    /// If it does not exist, this returns `None`.
177    pub fn get_account(&self, address: &Address) -> Option<&GenesisAccount> {
178        self.alloc.get(address)
179    }
180
181    /// Gets a mutable version of the account for the provided address, if it exists.
182    pub fn get_account_mut(&mut self, address: &Address) -> Option<&mut GenesisAccount> {
183        self.alloc.get_mut(address)
184    }
185
186    /// Gets an [Entry] for the provided address.
187    pub fn account_entry(&mut self, address: Address) -> Entry<'_, Address, GenesisAccount> {
188        self.alloc.entry(address)
189    }
190
191    /// Build the genesis alloc.
192    pub fn build(self) -> HashMap<Address, GenesisAccount> {
193        self.alloc
194    }
195}
196
197impl Default for GenesisAllocator<'_> {
198    fn default() -> Self {
199        Self { alloc: HashMap::default(), rng: Box::new(thread_rng()) }
200    }
201}
202
203impl fmt::Debug for GenesisAllocator<'_> {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        f.debug_struct("GenesisAllocator").field("alloc", &self.alloc).finish_non_exhaustive()
206    }
207}