1use std::sync::Arc;
23
24use alloy_consensus::Header;
25use alloy_eips::Encodable2718;
26use alloy_evm::IntoTxEnv;
27use alloy_primitives::{keccak256, Log};
28use alloy_rpc_types::{Filter, FilteredParams};
29use alloy_sol_types::{sol, SolCall, SolEvent, SolValue};
30use alloy_trie::root::ordered_trie_root_with_encoder;
31use eyre::bail;
32use io::EvmSketchInput;
33use reth_chainspec::EthChainSpec;
34use reth_primitives::EthPrimitives;
35use revm::{
36 context::{result::ExecutionResult, TxEnv},
37 database::CacheDB,
38};
39use revm_primitives::{hardfork::SpecId, Address, Bytes, TxKind, B256, U256};
40use rsp_client_executor::io::{TrieDB, WitnessInput};
41
42mod anchor;
43pub use anchor::{
44 get_beacon_root_from_state, rebuild_merkle_root, Anchor, BeaconAnchor, BeaconAnchorId,
45 BeaconStateAnchor, BeaconWithHeaderAnchor, ChainedBeaconAnchor, HeaderAnchor,
46 BLOCK_HASH_LEAF_INDEX, HISTORY_BUFFER_LENGTH, STATE_ROOT_LEAF_INDEX,
47};
48
49pub mod io;
50
51mod errors;
52pub use errors::ClientError;
53
54pub use rsp_primitives::genesis::Genesis;
55
56use crate::{anchor::ResolvedAnchor, io::Primitives};
57
58#[derive(Debug, Clone)]
62pub struct ContractInput {
63 pub contract_address: Address,
65 pub caller_address: Address,
67 pub calldata: ContractCalldata,
69}
70
71#[derive(Debug, Clone)]
75pub enum ContractCalldata {
76 Call(Bytes),
77 Create(Bytes),
78}
79
80impl ContractCalldata {
81 pub fn to_bytes(&self) -> Bytes {
83 match self {
84 Self::Call(calldata) => calldata.clone(),
85 Self::Create(calldata) => calldata.clone(),
86 }
87 }
88}
89
90impl ContractInput {
91 pub fn new_call<C: SolCall>(
93 contract_address: Address,
94 caller_address: Address,
95 calldata: C,
96 ) -> Self {
97 Self {
98 contract_address,
99 caller_address,
100 calldata: ContractCalldata::Call(calldata.abi_encode().into()),
101 }
102 }
103
104 pub fn new_create(caller_address: Address, calldata: Bytes) -> Self {
109 Self {
110 contract_address: Address::ZERO,
111 caller_address,
112 calldata: ContractCalldata::Create(calldata),
113 }
114 }
115}
116
117impl IntoTxEnv<TxEnv> for &ContractInput {
118 fn into_tx_env(self) -> TxEnv {
119 TxEnv {
120 caller: self.caller_address,
121 data: self.calldata.to_bytes(),
122 gas_price: 0,
124 kind: match self.calldata {
125 ContractCalldata::Create(_) => TxKind::Create,
126 ContractCalldata::Call(_) => TxKind::Call(self.contract_address),
127 },
128 chain_id: None,
129 ..Default::default()
130 }
131 }
132}
133
134#[cfg(feature = "optimism")]
135impl IntoTxEnv<op_revm::OpTransaction<TxEnv>> for &ContractInput {
136 fn into_tx_env(self) -> op_revm::OpTransaction<TxEnv> {
137 op_revm::OpTransaction { base: self.into_tx_env(), ..Default::default() }
138 }
139}
140
141sol! {
142 #[derive(Debug)]
143 enum AnchorType { BlockHash, Timestamp, Slot }
144
145 #[derive(Debug)]
149 struct ContractPublicValues {
150 uint256 id;
151 bytes32 anchorHash;
152 AnchorType anchorType;
153 bytes32 chainConfigHash;
154 address callerAddress;
155 address contractAddress;
156 bytes contractCalldata;
157 bytes contractOutput;
158 }
159
160 #[derive(Debug)]
161 struct ChainConfig {
162 uint chainId;
163 string activeForkName;
164 }
165}
166
167impl ContractPublicValues {
168 pub fn new(
173 call: ContractInput,
174 output: Bytes,
175 id: U256,
176 anchor: B256,
177 anchor_type: AnchorType,
178 chain_config_hash: B256,
179 ) -> Self {
180 Self {
181 id,
182 anchorHash: anchor,
183 anchorType: anchor_type,
184 chainConfigHash: chain_config_hash,
185 contractAddress: call.contract_address,
186 callerAddress: call.caller_address,
187 contractCalldata: call.calldata.to_bytes(),
188 contractOutput: output,
189 }
190 }
191}
192
193#[derive(Debug)]
195pub struct ClientExecutor<'a, P: Primitives> {
196 pub header: &'a Header,
198 pub anchor: ResolvedAnchor,
200 pub chain_spec: Arc<P::ChainSpec>,
202 pub witness_db: TrieDB<'a>,
204 pub logs: Option<Vec<Log>>,
206 pub chain_config_hash: B256,
209}
210
211impl<'a> ClientExecutor<'a, EthPrimitives> {
212 pub fn eth(state_sketch: &'a EvmSketchInput) -> Result<Self, ClientError> {
214 Self::new(state_sketch)
215 }
216}
217
218#[cfg(feature = "optimism")]
219impl<'a> ClientExecutor<'a, reth_optimism_primitives::OpPrimitives> {
220 pub fn optimism(state_sketch: &'a EvmSketchInput) -> Result<Self, ClientError> {
222 Self::new(state_sketch)
223 }
224}
225
226impl<'a, P: Primitives> ClientExecutor<'a, P> {
227 fn new(sketch_input: &'a EvmSketchInput) -> Result<Self, ClientError> {
229 let chain_spec = P::build_spec(&sketch_input.genesis)?;
230 let header = sketch_input.anchor.header();
231 let chain_config_hash = Self::hash_chain_config(chain_spec.as_ref(), header);
232
233 let sealed_headers = sketch_input.sealed_headers().collect::<Vec<_>>();
234
235 P::validate_header(&sealed_headers[0], chain_spec.clone())
236 .expect("the header is not valid");
237
238 assert_eq!(header.state_root, sketch_input.state.state_root(), "State root mismatch");
240
241 let mut previous_header = header;
243 for ancestor in sealed_headers.iter().skip(1) {
244 let ancestor_hash = ancestor.hash();
245
246 P::validate_header(ancestor, chain_spec.clone())
247 .unwrap_or_else(|_| panic!("the ancestor {} header in not valid", ancestor.number));
248 assert_eq!(
249 previous_header.parent_hash, ancestor_hash,
250 "block {} is not the parent of {}",
251 ancestor.number, previous_header.number
252 );
253 previous_header = ancestor;
254 }
255
256 let header = sketch_input.anchor.header();
257 let anchor = sketch_input.anchor.resolve();
258
259 if let Some(receipts) = &sketch_input.receipts {
260 let root = ordered_trie_root_with_encoder(receipts, |r, out| r.encode_2718(out));
262 assert_eq!(sketch_input.anchor.header().receipts_root, root, "Receipts root mismatch");
263 }
264
265 let logs = sketch_input
266 .receipts
267 .as_ref()
268 .map(|receipts| receipts.iter().flat_map(|r| r.logs().to_vec()).collect());
269
270 Ok(Self {
271 header,
272 anchor,
273 chain_spec,
274 witness_db: sketch_input.witness_db(&sealed_headers)?,
275 logs,
276 chain_config_hash,
277 })
278 }
279
280 pub fn execute(&self, call: ContractInput) -> eyre::Result<ContractPublicValues> {
290 let cache_db = CacheDB::new(&self.witness_db);
291 let tx_output =
292 P::transact(&call, cache_db, self.header, U256::ZERO, self.chain_spec.clone()).unwrap();
293
294 let tx_output_bytes = match tx_output.result {
295 ExecutionResult::Success { output, .. } => output.data().clone(),
296 ExecutionResult::Revert { output, .. } => bail!("Execution reverted: {output}"),
297 ExecutionResult::Halt { reason, .. } => bail!("Execution halted : {reason:?}"),
298 };
299
300 let public_values = ContractPublicValues::new(
301 call,
302 tx_output_bytes,
303 self.anchor.id,
304 self.anchor.hash,
305 self.anchor.ty,
306 self.chain_config_hash,
307 );
308
309 Ok(public_values)
310 }
311
312 pub fn execute_and_commit(&self, call: ContractInput) {
317 let public_values = self.execute(call).unwrap();
318 sp1_zkvm::io::commit_slice(&public_values.abi_encode());
319 }
320
321 pub fn get_logs<E: SolEvent>(&self, filter: Filter) -> Result<Vec<Log<E>>, ClientError> {
325 if let Some(logs) = &self.logs {
326 let params = FilteredParams::new(Some(filter));
327
328 logs.iter()
329 .filter(|log| {
330 params.filter_address(&log.address) && params.filter_topics(log.topics())
331 })
332 .map(|log| E::decode_log(log))
333 .collect::<Result<_, _>>()
334 .map_err(Into::into)
335 } else {
336 Err(ClientError::LogsNotPrefetched)
337 }
338 }
339
340 fn hash_chain_config(chain_spec: &P::ChainSpec, execution_header: &Header) -> B256 {
341 let chain_config = ChainConfig {
342 chainId: U256::from(chain_spec.chain_id()),
343 activeForkName: P::active_fork_name(chain_spec, execution_header),
344 };
345
346 keccak256(chain_config.abi_encode_packed())
347 }
348}
349
350pub fn verifiy_chain_config_eth(
354 chain_config_hash: B256,
355 chain_id: u64,
356 active_fork: SpecId,
357) -> Result<(), ClientError> {
358 let chain_config =
359 ChainConfig { chainId: U256::from(chain_id), activeForkName: active_fork.to_string() };
360
361 let hash = keccak256(chain_config.abi_encode_packed());
362
363 if chain_config_hash == hash {
364 Ok(())
365 } else {
366 Err(ClientError::InvalidChainConfig)
367 }
368}
369
370#[cfg(feature = "optimism")]
371pub fn verifiy_chain_config_optimism(
373 chain_config_hash: B256,
374 chain_id: u64,
375 active_fork: op_revm::OpSpecId,
376) -> Result<(), ClientError> {
377 let active_fork: &'static str = active_fork.into();
378 let chain_config =
379 ChainConfig { chainId: U256::from(chain_id), activeForkName: active_fork.to_string() };
380
381 let hash = keccak256(chain_config.abi_encode_packed());
382
383 if chain_config_hash == hash {
384 Ok(())
385 } else {
386 Err(ClientError::InvalidChainConfig)
387 }
388}