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_primitives::{Address, Bytes, B256, U256};
5use reth_primitives_traits::crypto::secp256k1::public_key_to_address;
6use secp256k1::{
7    rand::{thread_rng, RngCore},
8    Keypair, Secp256k1,
9};
10use seismic_alloy_genesis::GenesisAccount;
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        let storage = seismic_alloy_genesis::convert_fixedbytes_map_to_flagged_storage(storage);
113
114        self.alloc.insert(
115            address,
116            GenesisAccount::default().with_balance(balance).with_storage(Some(storage)),
117        );
118
119        (pair, address)
120    }
121
122    /// Adds an account with code and storage to the genesis alloc.
123    ///
124    /// Returns the key pair for the account and the account's address.
125    pub fn new_account_with_code_and_storage(
126        &mut self,
127        code: Bytes,
128        storage: BTreeMap<B256, B256>,
129    ) -> (Keypair, Address) {
130        let secp = Secp256k1::new();
131        let pair = Keypair::new(&secp, &mut self.rng);
132        let address = public_key_to_address(pair.public_key());
133
134        let storage = seismic_alloy_genesis::convert_fixedbytes_map_to_flagged_storage(storage);
135
136        self.alloc.insert(
137            address,
138            GenesisAccount::default().with_code(Some(code)).with_storage(Some(storage)),
139        );
140
141        (pair, address)
142    }
143
144    /// Adds an account with code to the genesis alloc.
145    ///
146    /// Returns the key pair for the account and the account's address.
147    pub fn new_account_with_code(&mut self, code: Bytes) -> (Keypair, Address) {
148        let secp = Secp256k1::new();
149        let pair = Keypair::new(&secp, &mut self.rng);
150        let address = public_key_to_address(pair.public_key());
151
152        self.alloc.insert(address, GenesisAccount::default().with_code(Some(code)));
153
154        (pair, address)
155    }
156
157    /// Add a funded account to the genesis alloc with the provided address.
158    ///
159    /// Neither the key pair nor the account will be returned, since the address is provided by
160    /// the user and the signer may be unknown.
161    pub fn add_funded_account_with_address(&mut self, address: Address, balance: U256) {
162        self.alloc.insert(address, GenesisAccount::default().with_balance(balance));
163    }
164
165    /// Adds the given [`GenesisAccount`] to the genesis alloc.
166    ///
167    /// Returns the key pair for the account and the account's address.
168    pub fn add_account(&mut self, account: GenesisAccount) -> Address {
169        let secp = Secp256k1::new();
170        let pair = Keypair::new(&secp, &mut self.rng);
171        let address = public_key_to_address(pair.public_key());
172
173        self.alloc.insert(address, account);
174
175        address
176    }
177
178    /// Gets the account for the provided address.
179    ///
180    /// If it does not exist, this returns `None`.
181    pub fn get_account(&self, address: &Address) -> Option<&GenesisAccount> {
182        self.alloc.get(address)
183    }
184
185    /// Gets a mutable version of the account for the provided address, if it exists.
186    pub fn get_account_mut(&mut self, address: &Address) -> Option<&mut GenesisAccount> {
187        self.alloc.get_mut(address)
188    }
189
190    /// Gets an [Entry] for the provided address.
191    pub fn account_entry(&mut self, address: Address) -> Entry<'_, Address, GenesisAccount> {
192        self.alloc.entry(address)
193    }
194
195    /// Build the genesis alloc.
196    pub fn build(self) -> HashMap<Address, GenesisAccount> {
197        self.alloc
198    }
199}
200
201impl Default for GenesisAllocator<'_> {
202    fn default() -> Self {
203        Self { alloc: HashMap::default(), rng: Box::new(thread_rng()) }
204    }
205}
206
207impl fmt::Debug for GenesisAllocator<'_> {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        f.debug_struct("GenesisAllocator").field("alloc", &self.alloc).finish_non_exhaustive()
210    }
211}