1#[cfg(target_arch = "wasm32")]
2mod wasm {
3 use eyre::eyre;
4 use futures::StreamExt;
5 use futures::channel::mpsc;
6 use js_sys::{Function, JsString};
7 use wasm_bindgen::prelude::*;
8 use yevm_base::int;
9 use yevm_core::Int;
10 use yevm_core::cache::Cache;
11 use yevm_core::chain::{Chain, Fetched};
12 use yevm_core::exe::{CallResult, Executor};
13 use yevm_core::state::State;
14 use yevm_core::trace::Trace;
15 use yevm_core::{Call, Tx, call::TxCall, rpc::Rpc};
16
17 #[derive(Debug, thiserror::Error)]
18 #[error("{0}")]
19 pub struct Error(eyre::Report);
20
21 impl From<eyre::Report> for Error {
22 fn from(e: eyre::Report) -> Self {
23 Self(e)
24 }
25 }
26
27 impl From<yevm_core::Error> for Error {
28 fn from(e: yevm_core::Error) -> Self {
29 Self(e.into())
30 }
31 }
32
33 impl From<Error> for JsValue {
34 fn from(e: Error) -> Self {
35 JsValue::from(JsError::new(&e.0.to_string()))
36 }
37 }
38
39 #[wasm_bindgen]
40 pub struct FetchCache(Vec<Fetched>);
41
42 #[wasm_bindgen]
43 pub fn empty() -> FetchCache {
44 FetchCache(vec![])
45 }
46
47 #[wasm_bindgen]
48 pub fn hello(name: JsString) -> JsString {
49 JsString::from(format!("Hello, {name}").as_str())
50 }
51
52 #[wasm_bindgen]
53 pub fn call(json: JsValue) -> Result<JsValue, JsError> {
54 let call: Call = serde_wasm_bindgen::from_value(json)?;
55 Ok(serde_wasm_bindgen::to_value(&call)?)
56 }
57
58 #[wasm_bindgen]
59 pub struct Stream {
60 receiver: mpsc::Receiver<Trace>,
61 tx: Int,
62 rcpt_gas: Int,
63 yevm_gas: Int,
64 rcpt_status: Int,
65 yevm_status: Int,
66 fetches: Vec<Fetched>,
67 }
68
69 #[wasm_bindgen]
70 impl Stream {
71 pub async fn next(&mut self) -> JsValue {
72 release().await;
73 let result = match self.receiver.next().await {
74 Some(step) => {
75 let value = serde_wasm_bindgen::to_value(&step).unwrap_or(JsValue::NULL);
76 release().await;
77 value
78 }
79 None => JsValue::NULL,
80 };
81 release().await;
82 result
83 }
84
85 pub fn fetches(&mut self) -> FetchCache {
86 FetchCache(std::mem::take(&mut self.fetches))
87 }
88
89 pub fn check(&self) -> JsString {
90 let gas_ok = self.yevm_gas == self.rcpt_gas;
91 let status_ok = self.yevm_status == self.rcpt_status;
92 [
93 format!("TX: {}", self.tx),
94 format!(
95 "gas: yevm={} rcpt={}: OK={}",
96 self.yevm_gas.as_u64(),
97 self.rcpt_gas.as_u64(),
98 gas_ok
99 ),
100 if self.rcpt_status > 2.into() {
101 format!(
102 "created: yevm={} rcpt={}: OK={}",
103 self.yevm_status.to::<20>(),
104 self.rcpt_status.to::<20>(),
105 status_ok
106 )
107 } else {
108 format!(
109 "status: yevm={} rcpt={}: OK={}",
110 self.yevm_status.as_u8(),
111 self.rcpt_status.as_u8(),
112 status_ok
113 )
114 },
115 ]
116 .join("\n")
117 .into()
118 }
119 }
120
121 #[wasm_bindgen]
122 pub async fn pick(url: JsString) -> Result<JsString, Error> {
123 let rpc = Rpc::latest(url.into()).await?;
124 let txs = &rpc.block(rpc.block_number).await?.txs;
125 let len = txs.len();
126 let idx = (js_sys::Math::random() * len as f64) as usize;
127 let hash = txs[idx].tx.hash.to_string();
128 Ok(hash.into())
129 }
130
131 #[wasm_bindgen]
132 pub async fn run(
133 url: JsString,
134 txn: JsString,
135 event_filter: u32,
136 fetches: FetchCache,
137 ) -> Result<Stream, Error> {
138 let mut rpc = Rpc::latest(url.into()).await?;
139 let chain_id = rpc.chain_id().await?;
140 let txn: String = txn.into();
141 let (block, index) = if let Some((left, right)) = txn.split_once(':') {
142 let block = left.trim().parse().map_err(|_| eyre!("invalid block"))?;
143 let index = right.trim().parse().map_err(|_| eyre!("invalid index"))?;
144 (block, index)
145 } else if txn.starts_with("0x") {
146 let hash = int(&txn);
147 let receipt = rpc.receipt(hash).await?;
148 let block = receipt.block_number.as_u64();
149 let index = receipt.transaction_index.as_usize();
150 (block, index)
151 } else {
152 return Err(eyre!("invalid tx selector").into());
153 };
154
155 let (call, tx, head, block_data) = {
156 let block = rpc.block(block).await?;
157 let tx = &block.txs[index];
158 let call = tx.call.clone().into();
159 (call, tx.tx.clone(), block.head.clone(), block)
160 };
161 let hash = tx.hash;
162 rpc.reset(head.number.as_u64() - 1, head.parent_hash);
163
164 let (ytx, yrx) = mpsc::channel(1024 * 1024);
165 let mut cache = Cache::with_sender(ytx, event_filter);
166 cache.set_chain_id(chain_id);
167
168 let FetchCache(fetched) = fetches;
169 if fetched.len() > 0 {
170 cache.prefetched(fetched);
171 } else {
172 cache.fetched.push(Fetched::ChainId(chain_id));
173 cache.fetched.push(Fetched::Block(block_data));
174 }
175
176 let mut exe = Executor::new(call);
177 let result = exe.run(tx, head, &mut cache, &rpc).await?;
178 let _ = cache.sender.take();
179
180 let (yevm_status, yevm_gas) = match result {
181 CallResult::Done {
182 status,
183 ret: _,
184 gas,
185 } => (status, gas.finalized.into()),
186 CallResult::Created { acc, code: _, gas } => (acc.to(), gas.finalized.into()),
187 };
188 let receipt = rpc.receipt(hash).await?;
189 let (rcpt_status, rcpt_gas) = (
190 if let Some(acc) = receipt.contract_address {
191 acc.to()
192 } else {
193 receipt.status
194 },
195 receipt.gas_used,
196 );
197 Ok(Stream {
198 receiver: yrx,
199 tx: hash,
200 rcpt_gas,
201 yevm_gas,
202 rcpt_status,
203 yevm_status,
204 fetches: cache.fetched,
205 })
206 }
207
208 #[wasm_bindgen]
209 pub async fn run_with_block(
210 url: JsString,
211 txn: JsString,
212 event_filter: u32,
213 fetches: FetchCache,
214 on_progress: Function,
215 ) -> Result<Stream, Error> {
216 let mut rpc = Rpc::latest(url.into()).await?;
217 let chain_id = rpc.chain_id().await?;
218 let txn: String = txn.into();
219
220 let (block, index) = if let Some((left, right)) = txn.split_once(':') {
221 let block = left.trim().parse().map_err(|_| eyre!("invalid block"))?;
222 let index = right.trim().parse().map_err(|_| eyre!("invalid index"))?;
223 (block, index)
224 } else if txn.starts_with("0x") {
225 let hash = int(&txn);
226 let receipt = rpc.receipt(hash).await?;
227 let block = receipt.block_number.as_u64();
228 let index = receipt.transaction_index.as_usize();
229 (block, index)
230 } else {
231 return Err(eyre!("invalid tx selector").into());
232 };
233
234 let (call, tx, head, prior_txs, block_data) = {
235 let b = rpc.block(block).await?;
236 let full = &b.txs[index];
237 let prior = b.txs[..index].to_vec();
238 (
239 full.call.clone().into(),
240 full.tx.clone(),
241 b.head.clone(),
242 prior,
243 b,
244 )
245 };
246 let hash = tx.hash;
247 rpc.reset(head.number.as_u64() - 1, head.parent_hash);
248
249 let mut cache = Cache::new();
251 cache.set_chain_id(chain_id);
252
253 let FetchCache(fetched) = fetches;
254 if fetched.len() > 0 {
255 cache.prefetched(fetched);
256 } else {
257 cache.fetched.push(Fetched::ChainId(chain_id));
258 cache.fetched.push(Fetched::Block(block_data));
259 }
260
261 let total = prior_txs.len();
262 for (i, prior) in prior_txs.into_iter().enumerate() {
263 let prior_call: Call = prior.call.into();
264 let mut exe = Executor::new(prior_call);
265 let _ = exe.run(prior.tx, head.clone(), &mut cache, &rpc).await;
266 cache.reset();
267 release().await;
268 let _ = on_progress.call2(&JsValue::NULL, &JsValue::from(i + 1), &JsValue::from(total));
269 }
270
271 let (ytx, yrx) = mpsc::channel(1024 * 1024);
273 cache.sender = Some(ytx);
274 cache.filter = event_filter;
275 cache.reset(); let mut exe = Executor::new(call);
278 let result = exe.run(tx, head, &mut cache, &rpc).await?;
279 let _ = cache.sender.take();
280
281 let (yevm_status, yevm_gas) = match result {
282 CallResult::Done {
283 status,
284 ret: _,
285 gas,
286 } => (status, gas.finalized.into()),
287 CallResult::Created { acc, code: _, gas } => (acc.to(), gas.finalized.into()),
288 };
289 let receipt = rpc.receipt(hash).await?;
290 let (rcpt_status, rcpt_gas) = (
291 if let Some(acc) = receipt.contract_address {
292 acc.to()
293 } else {
294 receipt.status
295 },
296 receipt.gas_used,
297 );
298 Ok(Stream {
299 receiver: yrx,
300 tx: hash,
301 rcpt_gas,
302 yevm_gas,
303 rcpt_status,
304 yevm_status,
305 fetches: cache.fetched,
306 })
307 }
308
309 #[wasm_bindgen]
310 pub fn analyse(traces_js: JsValue) -> Result<JsValue, JsError> {
311 let traces: Vec<yevm_core::trace::Trace> = serde_wasm_bindgen::from_value(traces_js)?;
312 let alerts = yevm_lens::analyse(&traces);
313 Ok(serde_wasm_bindgen::to_value(&alerts)?)
314 }
315
316 #[wasm_bindgen]
317 pub async fn simulate(
318 url: JsString,
319 call_json: JsValue,
320 event_filter: u32,
321 ) -> Result<Stream, Error> {
322 let mut rpc = Rpc::latest(url.into()).await?;
323 let chain_id = rpc.chain_id().await?;
324 let head = rpc.block(rpc.block_number).await?.head;
325 rpc.reset(head.number.as_u64(), head.hash);
326
327 let call: Call = serde_wasm_bindgen::from_value::<TxCall>(call_json)
328 .map_err(|e| eyre!("{e}"))?
329 .into();
330
331 let tx = Tx {
334 chain_id: Default::default(),
335 nonce: Int::ZERO,
336 gas_price: head.base_fee,
337 max_fee_per_gas: Int::ZERO,
338 max_priority_fee_per_gas: Int::ZERO,
339 access_list: vec![],
340 authorization_list: vec![],
341 blob_versioned_hashes: vec![],
342 max_fee_per_blob_gas: None,
343 hash: Int::ZERO,
344 index: Int::ZERO,
345 };
346
347 let (ytx, yrx) = mpsc::channel(1024 * 1024);
348 let mut cache = Cache::with_sender(ytx, event_filter);
349 cache.set_chain_id(chain_id);
350
351 let mut exe = Executor::new(call);
352 let result = exe.run(tx, head, &mut cache, &rpc).await?;
353 let _ = cache.sender.take();
354
355 let (yevm_status, yevm_gas) = match result {
356 CallResult::Done {
357 status,
358 ret: _,
359 gas,
360 } => (status, gas.finalized.into()),
361 CallResult::Created { acc, code: _, gas } => (acc.to(), gas.finalized.into()),
362 };
363
364 Ok(Stream {
365 receiver: yrx,
366 tx: Int::ZERO,
367 rcpt_gas: yevm_gas,
368 yevm_gas,
369 rcpt_status: yevm_status,
370 yevm_status,
371 fetches: cache.fetched,
372 })
373 }
374
375 async fn release() {
376 let promise = js_sys::Promise::resolve(&JsValue::NULL);
377 let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
378 }
379}
380
381#[cfg(target_arch = "wasm32")]
382pub use wasm::*;
383
384