foundry_cheatcodes/evm/
fork.rs

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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
use crate::{
    json::json_value_to_token, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt,
    Result, Vm::*,
};
use alloy_dyn_abi::DynSolValue;
use alloy_primitives::{B256, U256};
use alloy_provider::Provider;
use alloy_rpc_types::Filter;
use alloy_sol_types::SolValue;
use foundry_common::provider::ProviderBuilder;
use foundry_evm_core::fork::CreateFork;

impl Cheatcode for activeForkCall {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self {} = self;
        ccx.ecx
            .db
            .active_fork_id()
            .map(|id| id.abi_encode())
            .ok_or_else(|| fmt_err!("no active fork"))
    }
}

impl Cheatcode for createFork_0Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { urlOrAlias } = self;
        create_fork(ccx, urlOrAlias, None)
    }
}

impl Cheatcode for createFork_1Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { urlOrAlias, blockNumber } = self;
        create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
    }
}

impl Cheatcode for createFork_2Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { urlOrAlias, txHash } = self;
        create_fork_at_transaction(ccx, urlOrAlias, txHash)
    }
}

impl Cheatcode for createSelectFork_0Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { urlOrAlias } = self;
        create_select_fork(ccx, urlOrAlias, None)
    }
}

impl Cheatcode for createSelectFork_1Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { urlOrAlias, blockNumber } = self;
        create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to()))
    }
}

impl Cheatcode for createSelectFork_2Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { urlOrAlias, txHash } = self;
        create_select_fork_at_transaction(ccx, urlOrAlias, txHash)
    }
}

impl Cheatcode for rollFork_0Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { blockNumber } = self;
        persist_caller(ccx);
        ccx.ecx.db.roll_fork(
            None,
            (*blockNumber).to(),
            &mut ccx.ecx.env,
            &mut ccx.ecx.journaled_state,
        )?;
        Ok(Default::default())
    }
}

impl Cheatcode for rollFork_1Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { txHash } = self;
        persist_caller(ccx);
        ccx.ecx.db.roll_fork_to_transaction(
            None,
            *txHash,
            &mut ccx.ecx.env,
            &mut ccx.ecx.journaled_state,
        )?;
        Ok(Default::default())
    }
}

impl Cheatcode for rollFork_2Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { forkId, blockNumber } = self;
        persist_caller(ccx);
        ccx.ecx.db.roll_fork(
            Some(*forkId),
            (*blockNumber).to(),
            &mut ccx.ecx.env,
            &mut ccx.ecx.journaled_state,
        )?;
        Ok(Default::default())
    }
}

impl Cheatcode for rollFork_3Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { forkId, txHash } = self;
        persist_caller(ccx);
        ccx.ecx.db.roll_fork_to_transaction(
            Some(*forkId),
            *txHash,
            &mut ccx.ecx.env,
            &mut ccx.ecx.journaled_state,
        )?;
        Ok(Default::default())
    }
}

impl Cheatcode for selectForkCall {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { forkId } = self;
        persist_caller(ccx);
        check_broadcast(ccx.state)?;

        ccx.ecx.db.select_fork(*forkId, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?;
        Ok(Default::default())
    }
}

impl Cheatcode for transact_0Call {
    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
        let Self { txHash } = *self;
        transact(ccx, executor, txHash, None)
    }
}

impl Cheatcode for transact_1Call {
    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
        let Self { forkId, txHash } = *self;
        transact(ccx, executor, txHash, Some(forkId))
    }
}

impl Cheatcode for allowCheatcodesCall {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { account } = self;
        ccx.ecx.db.allow_cheatcode_access(*account);
        Ok(Default::default())
    }
}

impl Cheatcode for makePersistent_0Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { account } = self;
        ccx.ecx.db.add_persistent_account(*account);
        Ok(Default::default())
    }
}

impl Cheatcode for makePersistent_1Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { account0, account1 } = self;
        ccx.ecx.db.add_persistent_account(*account0);
        ccx.ecx.db.add_persistent_account(*account1);
        Ok(Default::default())
    }
}

impl Cheatcode for makePersistent_2Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { account0, account1, account2 } = self;
        ccx.ecx.db.add_persistent_account(*account0);
        ccx.ecx.db.add_persistent_account(*account1);
        ccx.ecx.db.add_persistent_account(*account2);
        Ok(Default::default())
    }
}

impl Cheatcode for makePersistent_3Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { accounts } = self;
        for account in accounts {
            ccx.ecx.db.add_persistent_account(*account);
        }
        Ok(Default::default())
    }
}

impl Cheatcode for revokePersistent_0Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { account } = self;
        ccx.ecx.db.remove_persistent_account(account);
        Ok(Default::default())
    }
}

impl Cheatcode for revokePersistent_1Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { accounts } = self;
        for account in accounts {
            ccx.ecx.db.remove_persistent_account(account);
        }
        Ok(Default::default())
    }
}

impl Cheatcode for isPersistentCall {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { account } = self;
        Ok(ccx.ecx.db.is_persistent(account).abi_encode())
    }
}

impl Cheatcode for rpc_0Call {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { method, params } = self;
        let url =
            ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
        rpc_call(&url, method, params)
    }
}

impl Cheatcode for rpc_1Call {
    fn apply(&self, state: &mut Cheatcodes) -> Result {
        let Self { urlOrAlias, method, params } = self;
        let url = state.config.rpc_endpoint(urlOrAlias)?.url()?;
        rpc_call(&url, method, params)
    }
}

impl Cheatcode for eth_getLogsCall {
    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
        let Self { fromBlock, toBlock, target, topics } = self;
        let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock))
        else {
            bail!("blocks in block range must be less than 2^64 - 1")
        };

        if topics.len() > 4 {
            bail!("topics array must contain at most 4 elements")
        }

        let url =
            ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?;
        let provider = ProviderBuilder::new(&url).build()?;
        let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block);
        for (i, &topic) in topics.iter().enumerate() {
            filter.topics[i] = topic.into();
        }

        let logs = foundry_common::block_on(provider.get_logs(&filter))
            .map_err(|e| fmt_err!("failed to get logs: {e}"))?;

        let eth_logs = logs
            .into_iter()
            .map(|log| EthGetLogs {
                emitter: log.address(),
                topics: log.topics().to_vec(),
                data: log.inner.data.data,
                blockHash: log.block_hash.unwrap_or_default(),
                blockNumber: log.block_number.unwrap_or_default(),
                transactionHash: log.transaction_hash.unwrap_or_default(),
                transactionIndex: log.transaction_index.unwrap_or_default(),
                logIndex: U256::from(log.log_index.unwrap_or_default()),
                removed: log.removed,
            })
            .collect::<Vec<_>>();

        Ok(eth_logs.abi_encode())
    }
}

/// Creates and then also selects the new fork
fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option<u64>) -> Result {
    check_broadcast(ccx.state)?;

    let fork = create_fork_request(ccx, url_or_alias, block)?;
    let id = ccx.ecx.db.create_select_fork(fork, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?;
    Ok(id.abi_encode())
}

/// Creates a new fork
fn create_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option<u64>) -> Result {
    let fork = create_fork_request(ccx, url_or_alias, block)?;
    let id = ccx.ecx.db.create_fork(fork)?;
    Ok(id.abi_encode())
}

/// Creates and then also selects the new fork at the given transaction
fn create_select_fork_at_transaction(
    ccx: &mut CheatsCtxt,
    url_or_alias: &str,
    transaction: &B256,
) -> Result {
    check_broadcast(ccx.state)?;

    let fork = create_fork_request(ccx, url_or_alias, None)?;
    let id = ccx.ecx.db.create_select_fork_at_transaction(
        fork,
        &mut ccx.ecx.env,
        &mut ccx.ecx.journaled_state,
        *transaction,
    )?;
    Ok(id.abi_encode())
}

/// Creates a new fork at the given transaction
fn create_fork_at_transaction(
    ccx: &mut CheatsCtxt,
    url_or_alias: &str,
    transaction: &B256,
) -> Result {
    let fork = create_fork_request(ccx, url_or_alias, None)?;
    let id = ccx.ecx.db.create_fork_at_transaction(fork, *transaction)?;
    Ok(id.abi_encode())
}

/// Creates the request object for a new fork request
fn create_fork_request(
    ccx: &mut CheatsCtxt,
    url_or_alias: &str,
    block: Option<u64>,
) -> Result<CreateFork> {
    persist_caller(ccx);

    let rpc_endpoint = ccx.state.config.rpc_endpoint(url_or_alias)?;
    let url = rpc_endpoint.url()?;
    let mut evm_opts = ccx.state.config.evm_opts.clone();
    evm_opts.fork_block_number = block;
    evm_opts.fork_retries = rpc_endpoint.config.retries;
    evm_opts.fork_retry_backoff = rpc_endpoint.config.retry_backoff;
    if let Some(Ok(auth)) = rpc_endpoint.auth {
        evm_opts.fork_headers = Some(vec![format!("Authorization: {auth}")]);
    }
    let fork = CreateFork {
        enable_caching: !ccx.state.config.no_storage_caching &&
            ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url),
        url,
        env: (*ccx.ecx.env).clone(),
        evm_opts,
    };
    Ok(fork)
}

fn check_broadcast(state: &Cheatcodes) -> Result<()> {
    if state.broadcast.is_none() {
        Ok(())
    } else {
        Err(fmt_err!("cannot select forks during a broadcast"))
    }
}

fn transact(
    ccx: &mut CheatsCtxt,
    executor: &mut dyn CheatcodesExecutor,
    transaction: B256,
    fork_id: Option<U256>,
) -> Result {
    ccx.ecx.db.transact(
        fork_id,
        transaction,
        (*ccx.ecx.env).clone(),
        &mut ccx.ecx.journaled_state,
        &mut *executor.get_inspector(ccx.state),
    )?;
    Ok(Default::default())
}

// Helper to add the caller of fork cheat code as persistent account (in order to make sure that the
// state of caller contract is not lost when fork changes).
// Applies to create, select and roll forks actions.
// https://github.com/foundry-rs/foundry/issues/8004
fn persist_caller(ccx: &mut CheatsCtxt) {
    ccx.ecx.db.add_persistent_account(ccx.caller);
}

/// Performs an Ethereum JSON-RPC request to the given endpoint.
fn rpc_call(url: &str, method: &str, params: &str) -> Result {
    let provider = ProviderBuilder::new(url).build()?;
    let params_json: serde_json::Value = serde_json::from_str(params)?;
    let result =
        foundry_common::block_on(provider.raw_request(method.to_string().into(), params_json))
            .map_err(|err| fmt_err!("{method:?}: {err}"))?;
    let result_as_tokens = convert_to_bytes(
        &json_value_to_token(&result).map_err(|err| fmt_err!("failed to parse result: {err}"))?,
    );

    Ok(result_as_tokens.abi_encode())
}

/// Convert fixed bytes and address values to bytes in order to prevent encoding issues.
fn convert_to_bytes(token: &DynSolValue) -> DynSolValue {
    match token {
        // Convert fixed bytes to prevent encoding issues.
        // See: <https://github.com/foundry-rs/foundry/issues/8287>
        DynSolValue::FixedBytes(bytes, size) => {
            DynSolValue::Bytes(bytes.as_slice()[..*size].to_vec())
        }
        DynSolValue::Address(addr) => DynSolValue::Bytes(addr.to_vec()),
        //  Convert tuple values to prevent encoding issues.
        // See: <https://github.com/foundry-rs/foundry/issues/7858>
        DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()),
        val => val.clone(),
    }
}