1pub mod io;
23use std::{
24 hash::{DefaultHasher, Hash, Hasher},
25 sync::Arc,
26};
27
28use alloy_eips::Encodable2718;
29use alloy_evm::IntoTxEnv;
30use alloy_primitives::{keccak256, Log};
31use alloy_rpc_types::{Filter, FilteredParams};
32use alloy_sol_types::{sol, SolCall, SolEvent};
33use alloy_trie::root::ordered_trie_root_with_encoder;
34use eyre::OptionExt;
35use io::EvmSketchInput;
36use reth_primitives::EthPrimitives;
37use revm::{context::TxEnv, database::CacheDB};
38use revm_primitives::{Address, Bytes, TxKind, B256, U256};
39use rsp_client_executor::io::{TrieDB, WitnessInput};
40
41mod anchor;
42pub use anchor::{
43 get_beacon_root_from_state, rebuild_merkle_root, Anchor, BeaconAnchor, BeaconAnchorId,
44 BeaconBlockField, BeaconStateAnchor, BeaconWithHeaderAnchor, ChainedBeaconAnchor, HeaderAnchor,
45 HISTORY_BUFFER_LENGTH,
46};
47
48mod errors;
49pub use errors::ClientError;
50
51pub use rsp_primitives::genesis::Genesis;
52
53use crate::io::Primitives;
54
55#[derive(Debug, Clone)]
59pub struct ContractInput {
60 pub contract_address: Address,
62 pub caller_address: Address,
64 pub calldata: ContractCalldata,
66}
67
68#[derive(Debug, Clone)]
72pub enum ContractCalldata {
73 Call(Bytes),
74 Create(Bytes),
75}
76
77impl ContractCalldata {
78 pub fn to_bytes(&self) -> Bytes {
80 match self {
81 Self::Call(calldata) => calldata.clone(),
82 Self::Create(calldata) => calldata.clone(),
83 }
84 }
85}
86
87impl ContractInput {
88 pub fn new_call<C: SolCall>(
90 contract_address: Address,
91 caller_address: Address,
92 calldata: C,
93 ) -> Self {
94 Self {
95 contract_address,
96 caller_address,
97 calldata: ContractCalldata::Call(calldata.abi_encode().into()),
98 }
99 }
100
101 pub fn new_create(caller_address: Address, calldata: Bytes) -> Self {
106 Self {
107 contract_address: Address::ZERO,
108 caller_address,
109 calldata: ContractCalldata::Create(calldata),
110 }
111 }
112}
113
114impl IntoTxEnv<TxEnv> for &ContractInput {
115 fn into_tx_env(self) -> TxEnv {
116 TxEnv {
117 caller: self.caller_address,
118 data: self.calldata.to_bytes(),
119 gas_price: 0,
121 kind: match self.calldata {
122 ContractCalldata::Create(_) => TxKind::Create,
123 ContractCalldata::Call(_) => TxKind::Call(self.contract_address),
124 },
125 chain_id: None,
126 ..Default::default()
127 }
128 }
129}
130
131#[cfg(feature = "optimism")]
132impl IntoTxEnv<op_revm::OpTransaction<TxEnv>> for &ContractInput {
133 fn into_tx_env(self) -> op_revm::OpTransaction<TxEnv> {
134 op_revm::OpTransaction { base: self.into_tx_env(), ..Default::default() }
135 }
136}
137
138sol! {
139 #[derive(Debug)]
140 enum AnchorType { BlockHash, Eip4788, Consensus }
141
142 #[derive(Debug)]
146 struct ContractPublicValues {
147 uint256 id;
148 bytes32 anchorHash;
149 AnchorType anchorType;
150 bytes32 genesisHash;
151 address callerAddress;
152 address contractAddress;
153 bytes contractCalldata;
154 bytes contractOutput;
155 }
156}
157
158impl ContractPublicValues {
159 pub fn new(
164 call: ContractInput,
165 output: Bytes,
166 id: U256,
167 anchor: B256,
168 anchor_type: AnchorType,
169 genesis_hash: B256,
170 ) -> Self {
171 Self {
172 id,
173 anchorHash: anchor,
174 anchorType: anchor_type,
175 genesisHash: genesis_hash,
176 contractAddress: call.contract_address,
177 callerAddress: call.caller_address,
178 contractCalldata: call.calldata.to_bytes(),
179 contractOutput: output,
180 }
181 }
182}
183
184#[derive(Debug)]
186pub struct ClientExecutor<'a, P: Primitives> {
187 pub anchor: &'a Anchor,
189 pub chain_spec: Arc<P::ChainSpec>,
191 pub witness_db: TrieDB<'a>,
193 pub logs: Vec<Log>,
195 pub genesis_hash: B256,
197}
198
199impl<'a> ClientExecutor<'a, EthPrimitives> {
200 pub fn eth(state_sketch: &'a EvmSketchInput) -> Result<Self, ClientError> {
202 Self::new(state_sketch)
203 }
204}
205
206#[cfg(feature = "optimism")]
207impl<'a> ClientExecutor<'a, reth_optimism_primitives::OpPrimitives> {
208 pub fn optimism(state_sketch: &'a EvmSketchInput) -> Result<Self, ClientError> {
210 Self::new(state_sketch)
211 }
212}
213
214impl<'a, P: Primitives> ClientExecutor<'a, P> {
215 fn new(state_sketch: &'a EvmSketchInput) -> Result<Self, ClientError> {
217 let chain_spec = P::build_spec(&state_sketch.genesis)?;
218 let genesis_hash = hash_genesis(&state_sketch.genesis);
219 let header = state_sketch.anchor.header();
220 let sealed_headers = state_sketch.sealed_headers().collect::<Vec<_>>();
221
222 P::validate_header(&sealed_headers[0], chain_spec.clone())
223 .expect("the header is not valid");
224
225 assert_eq!(header.state_root, state_sketch.state.state_root(), "State root mismatch");
227
228 let mut previous_header = header;
230 for ancestor in sealed_headers.iter().skip(1) {
231 let ancestor_hash = ancestor.hash();
232
233 P::validate_header(ancestor, chain_spec.clone())
234 .unwrap_or_else(|_| panic!("the ancestor {} header in not valid", ancestor.number));
235 assert_eq!(
236 previous_header.parent_hash, ancestor_hash,
237 "block {} is not the parent of {}",
238 ancestor.number, previous_header.number
239 );
240 previous_header = ancestor;
241 }
242
243 if let Some(receipts) = &state_sketch.receipts {
244 let root = ordered_trie_root_with_encoder(receipts, |r, out| r.encode_2718(out));
246 assert_eq!(state_sketch.anchor.header().receipts_root, root, "Receipts root mismatch");
247 }
248
249 let logs = state_sketch
250 .receipts
251 .as_ref()
252 .unwrap_or(&vec![])
253 .iter()
254 .flat_map(|r| r.logs().to_vec())
255 .collect();
256
257 Ok(Self {
258 anchor: &state_sketch.anchor,
259 chain_spec,
260 witness_db: state_sketch.witness_db(&sealed_headers)?,
261 logs,
262 genesis_hash,
263 })
264 }
265
266 pub fn execute(&self, call: ContractInput) -> eyre::Result<ContractPublicValues> {
270 let cache_db = CacheDB::new(&self.witness_db);
271 let tx_output =
272 P::transact(&call, cache_db, self.anchor.header(), U256::ZERO, self.chain_spec.clone())
273 .unwrap();
274 let tx_output_bytes = tx_output.result.output().ok_or_eyre("Error decoding result")?;
275 let resolved = self.anchor.resolve();
276
277 let public_values = ContractPublicValues::new(
278 call,
279 tx_output_bytes.clone(),
280 resolved.id,
281 resolved.hash,
282 self.anchor.ty(),
283 self.genesis_hash,
284 );
285
286 Ok(public_values)
287 }
288
289 pub fn get_logs<E: SolEvent>(&self, filter: Filter) -> Result<Vec<Log<E>>, ClientError> {
293 let params = FilteredParams::new(Some(filter));
294
295 self.logs
296 .iter()
297 .filter(|log| params.filter_address(&log.address) && params.filter_topics(log.topics()))
298 .map(|log| E::decode_log(log))
299 .collect::<Result<_, _>>()
300 .map_err(Into::into)
301 }
302}
303
304pub fn hash_genesis(genesis: &Genesis) -> B256 {
305 let mut s = DefaultHasher::new();
306 genesis.hash(&mut s);
307 let hash = s.finish();
308
309 keccak256(hash.to_be_bytes())
310}