1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! Mock types and functions to generate Test environments for ZKEVM tests

use crate::{withdrawal::MockWithdrawal, MockAccount, MockBlock, MockTransaction};
use eth_types::{
    geth_types::{Account, BlockConstants, GethData, Withdrawal},
    Block, Error, GethExecTrace, Transaction, Word,
};
use external_tracer::{trace, TraceConfig};
use itertools::Itertools;

pub use external_tracer::LoggerConfig;

// TODO: merge it with TestContext.
// Here is the issue, https://github.com/privacy-scaling-explorations/zkevm-circuits/issues/1651

/// TestContext2 is an extended struct of TestContext. For more details and usage, see TestContext
/// file.
#[derive(Debug, Clone)]
pub struct TestContext2<const NACC: usize, const NTX: usize, const NWD: usize> {
    /// chain id
    pub chain_id: Word,
    /// Account list
    pub accounts: [Account; NACC],
    /// history hashes contains most recent 256 block hashes in history, where
    /// the latest one is at history_hashes[history_hashes.len() - 1].
    pub history_hashes: Vec<Word>,
    /// Block from geth
    pub eth_block: eth_types::Block<eth_types::Transaction>,
    /// Execution Trace from geth
    pub geth_traces: Vec<eth_types::GethExecTrace>,
}

impl<const NACC: usize, const NTX: usize, const NWD: usize> From<TestContext2<NACC, NTX, NWD>>
    for GethData
{
    fn from(ctx: TestContext2<NACC, NTX, NWD>) -> GethData {
        GethData {
            chain_id: ctx.chain_id,
            history_hashes: ctx.history_hashes,
            eth_block: ctx.eth_block,
            geth_traces: ctx.geth_traces.to_vec(),
            accounts: ctx.accounts.into(),
        }
    }
}

impl<const NACC: usize, const NTX: usize, const NWD: usize> TestContext2<NACC, NTX, NWD> {
    pub fn new_with_logger_config<FAcc, FTx, FWd, Fb>(
        history_hashes: Option<Vec<Word>>,
        acc_fns: FAcc,
        func_tx: FTx,
        func_wd: FWd,
        func_block: Fb,
        logger_config: LoggerConfig,
    ) -> Result<Self, Error>
    where
        FTx: FnOnce(Vec<&mut MockTransaction>, [MockAccount; NACC]),
        FWd: FnOnce(Vec<&mut MockWithdrawal>),
        Fb: FnOnce(&mut MockBlock, Vec<MockTransaction>) -> &mut MockBlock,
        FAcc: FnOnce([&mut MockAccount; NACC]),
    {
        let mut accounts: Vec<MockAccount> = vec![MockAccount::default(); NACC];
        // Build Accounts modifiers
        let account_refs = accounts
            .iter_mut()
            .collect_vec()
            .try_into()
            .expect("Mismatched len err");
        acc_fns(account_refs);
        let accounts: [MockAccount; NACC] = accounts
            .iter_mut()
            .map(|acc| acc.build())
            .collect_vec()
            .try_into()
            .expect("Mismatched acc len");

        let mut transactions = vec![MockTransaction::default(); NTX];
        let tx_refs = transactions.iter_mut().collect();

        // Build Tx modifiers.
        func_tx(tx_refs, accounts.clone());

        let mut withdrawals = vec![MockWithdrawal::default(); NWD];
        let wd_refs = withdrawals.iter_mut().collect();

        // Build Withdrawal modifiers.
        func_wd(wd_refs);

        // Sets the transaction_idx and nonce after building the tx modifiers. Hence, if user has
        // overridden these values above using the tx modifiers, that will be ignored.
        let mut acc_tx_count = vec![0u64; NACC];
        transactions.iter_mut().enumerate().for_each(|(idx, tx)| {
            let idx = u64::try_from(idx).expect("Unexpected idx conversion error");
            tx.transaction_idx(idx);
            if let Some((pos, from_acc)) = accounts
                .iter()
                .find_position(|acc| acc.address == tx.from.address())
            {
                if tx.nonce.is_none() {
                    tx.nonce(from_acc.nonce + acc_tx_count[pos]);
                }
                if !tx.invalid {
                    acc_tx_count[pos] += 1;
                }
            }
        });

        let transactions: Vec<MockTransaction> =
            transactions.iter_mut().map(|tx| tx.build()).collect();

        // Build Block modifiers
        let mut block = MockBlock::default();
        block.transactions.extend_from_slice(&transactions);
        block.withdrawals.extend_from_slice(&withdrawals);
        func_block(&mut block, transactions.clone()).build();

        let chain_id = block.chain_id;
        let block = Block::<Transaction>::from(block);
        let accounts: [Account; NACC] = accounts
            .iter()
            .cloned()
            .map(Account::from)
            .collect_vec()
            .try_into()
            .expect("Mismatched acc len");

        let withdrawals: [Withdrawal; NWD] = withdrawals
            .iter()
            .cloned()
            .map(Withdrawal::from)
            .collect_vec()
            .try_into()
            .expect("Mismatched withdrawal len");

        let geth_traces = gen_geth_traces(
            chain_id,
            block.clone(),
            accounts.to_vec(),
            withdrawals.to_vec(),
            history_hashes.clone(),
            logger_config,
        )?;

        // Don't allow invalid transactions unless explicitly allowed to avoid unrelated tests from
        // passing simply because the test transaction was incorrectly set up.
        for (tx, geth_trace) in transactions.iter().zip(geth_traces.iter()) {
            if !tx.invalid && geth_trace.invalid {
                panic!(
                    "{:?}",
                    Error::TracingError(geth_trace.return_value.clone()).to_string()
                )
            }
            assert_eq!(
                tx.invalid, geth_trace.invalid,
                "tx has unexpected invalid status: {}",
                geth_trace.return_value
            );
        }

        Ok(Self {
            chain_id,
            accounts,
            history_hashes: history_hashes.unwrap_or_default(),
            eth_block: block,
            geth_traces,
        })
    }

    /// Create a new TestContext2 which starts with `NACC` default accounts and
    /// `NTX` default transactions.  Afterwards, we apply the `acc_fns`
    /// function to the accounts, the `func_tx` to the transactions and
    /// the `func_block` to the block, where each of these functions can
    /// mutate their target using the builder pattern. Finally an
    /// execution trace is generated of the resulting input block and state.
    pub fn new<FAcc, FTx, FWd, Fb>(
        history_hashes: Option<Vec<Word>>,
        acc_fns: FAcc,
        func_tx: FTx,
        func_wd: FWd,
        func_block: Fb,
    ) -> Result<Self, Error>
    where
        FTx: FnOnce(Vec<&mut MockTransaction>, [MockAccount; NACC]),
        FWd: FnOnce(Vec<&mut MockWithdrawal>),
        Fb: FnOnce(&mut MockBlock, Vec<MockTransaction>) -> &mut MockBlock,
        FAcc: FnOnce([&mut MockAccount; NACC]),
    {
        Self::new_with_logger_config(
            history_hashes,
            acc_fns,
            func_tx,
            func_wd,
            func_block,
            LoggerConfig::default(),
        )
    }
}

/// Generates execution traces for the transactions included in the provided
/// Block
pub fn gen_geth_traces(
    chain_id: Word,
    block: Block<Transaction>,
    accounts: Vec<Account>,
    withdrawals: Vec<Withdrawal>,
    history_hashes: Option<Vec<Word>>,
    logger_config: LoggerConfig,
) -> Result<Vec<GethExecTrace>, Error> {
    let trace_config = TraceConfig {
        chain_id,
        history_hashes: history_hashes.unwrap_or_default(),
        block_constants: BlockConstants::try_from(&block)?,
        accounts: accounts
            .iter()
            .map(|account| (account.address, account.clone()))
            .collect(),
        transactions: block
            .transactions
            .iter()
            .map(eth_types::geth_types::Transaction::from)
            .collect(),
        withdrawals,
        logger_config,
    };
    let traces = trace(&trace_config)?;
    Ok(traces)
}