Skip to main content

reth_e2e_test_utils/
wallet.rs

1use crate::transaction::TransactionTestContext;
2use alloy_eips::BlockId;
3use alloy_network::Network;
4use alloy_primitives::{Address, Bytes};
5use alloy_provider::Provider;
6use alloy_rpc_types_eth::TransactionRequest;
7use alloy_signer::Signer;
8use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner};
9
10/// Mnemonic of the test accounts funded by the [`test_genesis`](crate::test_genesis).
11pub const TEST_MNEMONIC: &str = "test test test test test test test test test test test junk";
12
13/// One of the accounts of the genesis allocations.
14#[derive(Debug)]
15pub struct Wallet {
16    /// The signer
17    pub inner: PrivateKeySigner,
18    /// The nonce
19    pub inner_nonce: u64,
20    /// The chain id
21    pub chain_id: u64,
22    amount: usize,
23}
24
25impl Wallet {
26    /// Creates a new account from one of the secret/pubkeys of the genesis allocations (test.json)
27    pub fn new(amount: usize) -> Self {
28        Self { inner: test_signer(0), chain_id: 1, amount, inner_nonce: 0 }
29    }
30
31    /// Sets chain id
32    pub const fn with_chain_id(mut self, chain_id: u64) -> Self {
33        self.chain_id = chain_id;
34        self
35    }
36
37    /// Generates a list of wallets
38    pub fn wallet_gen(&self) -> Vec<PrivateKeySigner> {
39        (0..self.amount as u32).map(|idx| self.signer(idx)).collect()
40    }
41
42    /// Returns the signer of the test account at `index`, with the chain id of the wallet.
43    pub fn signer(&self, index: u32) -> PrivateKeySigner {
44        test_signer(index).with_chain_id(Some(self.chain_id))
45    }
46
47    /// Returns the test account at `index`, with the chain id of the wallet and nonce 0.
48    pub fn account(&self, index: u32) -> TestAccount {
49        TestAccount::new(self.signer(index), self.chain_id)
50    }
51}
52
53impl Default for Wallet {
54    fn default() -> Self {
55        Self::new(1)
56    }
57}
58
59/// Returns the signer of the test account at `index`, derived from [`TEST_MNEMONIC`] with the
60/// default Ethereum derivation path `m/44'/60'/0'/0/{index}`.
61pub fn test_signer(index: u32) -> PrivateKeySigner {
62    MnemonicBuilder::<English>::default()
63        .phrase(TEST_MNEMONIC)
64        .index(index)
65        .expect("valid derivation index")
66        .build()
67        .expect("valid test mnemonic")
68}
69
70/// A test account that tracks its nonce, e.g. one of the accounts funded by the
71/// [`test_genesis`](crate::test_genesis).
72///
73/// [`Self::sign_tx_bytes`] fills the nonce, chain id, gas limit and fees of transaction requests
74/// that do not set them, so tests only need to set the fields they care about.
75#[derive(Debug, Clone)]
76pub struct TestAccount {
77    signer: PrivateKeySigner,
78    chain_id: u64,
79    nonce: u64,
80}
81
82impl TestAccount {
83    /// Gas limit of transactions signed by [`Self::sign_tx_bytes`] that do not set one.
84    pub const DEFAULT_GAS_LIMIT: u64 = 1_000_000;
85
86    /// Max fee per gas of transactions signed by [`Self::sign_tx_bytes`] that set no fees.
87    ///
88    /// High enough that transactions are accepted regardless of the base fee of test chains.
89    pub const DEFAULT_MAX_FEE_PER_GAS: u128 = 1_000_000_000_000;
90
91    /// Max priority fee per gas of transactions signed by [`Self::sign_tx_bytes`] that set no
92    /// fees.
93    pub const DEFAULT_MAX_PRIORITY_FEE_PER_GAS: u128 = 1_000_000_000;
94
95    /// Creates a new account for the given signer and chain id, starting at nonce 0.
96    pub const fn new(signer: PrivateKeySigner, chain_id: u64) -> Self {
97        Self { signer, chain_id, nonce: 0 }
98    }
99
100    /// Returns the signer of the account.
101    pub const fn signer(&self) -> &PrivateKeySigner {
102        &self.signer
103    }
104
105    /// Returns the address of the account.
106    pub const fn address(&self) -> Address {
107        self.signer.address()
108    }
109
110    /// Returns the chain id transactions of the account are signed for.
111    pub const fn chain_id(&self) -> u64 {
112        self.chain_id
113    }
114
115    /// Returns the nonce of the next transaction of the account.
116    pub const fn nonce(&self) -> u64 {
117        self.nonce
118    }
119
120    /// Returns the nonce of the next transaction and increments it.
121    pub const fn next_nonce(&mut self) -> u64 {
122        let nonce = self.nonce;
123        self.nonce += 1;
124        nonce
125    }
126
127    /// Sets the nonce to the pending transaction count of the account on the node behind
128    /// `provider`, and returns it.
129    pub async fn sync_nonce<N: Network>(
130        &mut self,
131        provider: &impl Provider<N>,
132    ) -> eyre::Result<u64> {
133        self.nonce =
134            provider.get_transaction_count(self.address()).block_id(BlockId::pending()).await?;
135        Ok(self.nonce)
136    }
137
138    /// Signs the transaction request, returning the EIP-2718 encoded bytes.
139    ///
140    /// Fills unset fields: the nonce with [`Self::next_nonce`], the chain id of the account, the
141    /// gas limit with [`Self::DEFAULT_GAS_LIMIT`] and, unless the request sets a legacy gas price,
142    /// the EIP-1559 fees with [`Self::DEFAULT_MAX_FEE_PER_GAS`] and
143    /// [`Self::DEFAULT_MAX_PRIORITY_FEE_PER_GAS`]. An explicit nonce does not advance the tracked
144    /// nonce. Contract creations must set `to`, e.g. with
145    /// [`TransactionBuilder::into_create`](alloy_network::TransactionBuilder::into_create).
146    ///
147    /// # Panics
148    ///
149    /// If the request can not be built into a signed transaction.
150    pub async fn sign_tx_bytes(&mut self, mut tx: TransactionRequest) -> Bytes {
151        if tx.nonce.is_none() {
152            tx.nonce = Some(self.next_nonce());
153        }
154        tx.chain_id.get_or_insert(self.chain_id);
155        tx.gas.get_or_insert(Self::DEFAULT_GAS_LIMIT);
156        if tx.gas_price.is_none() {
157            tx.max_fee_per_gas.get_or_insert(Self::DEFAULT_MAX_FEE_PER_GAS);
158            tx.max_priority_fee_per_gas.get_or_insert(Self::DEFAULT_MAX_PRIORITY_FEE_PER_GAS);
159        }
160        TransactionTestContext::sign_tx_bytes(self.signer.clone(), tx).await
161    }
162}