1use alloy_primitives::{Address, Log, B256, U256};
2use arbos::programs::memory::MemoryModel;
3use revm::Database;
4
5use crate::{
6 evm_api::{CreateResponse, EvmApi, UserOutcomeKind},
7 ink::Gas,
8 pages,
9};
10
11const COLD_SLOAD_COST: u64 = 2100;
13const WARM_STORAGE_READ_COST: u64 = 100;
14const COLD_ACCOUNT_ACCESS_COST: u64 = 2600;
15const WARM_ACCOUNT_ACCESS_COST: u64 = 100;
16
17const WASM_EXT_CODE_COST: u64 = 700;
21
22pub struct SStoreInfo {
26 pub is_cold: bool,
27 pub original_value: U256,
28 pub present_value: U256,
29 pub new_value: U256,
30}
31
32pub trait JournalAccess {
37 fn sload(&mut self, addr: Address, key: U256) -> eyre::Result<(U256, bool)>;
38 fn sstore(&mut self, addr: Address, key: U256, value: U256) -> eyre::Result<SStoreInfo>;
39 fn tload(&mut self, addr: Address, key: U256) -> U256;
40 fn tstore(&mut self, addr: Address, key: U256, value: U256);
41 fn log(&mut self, log: Log);
42 fn account_balance(&mut self, addr: Address) -> eyre::Result<(U256, bool)>;
43 fn account_code(&mut self, addr: Address) -> eyre::Result<(Vec<u8>, bool)>;
44 fn account_codehash(&mut self, addr: Address) -> eyre::Result<(B256, bool)>;
45 fn address_in_access_list(&self, addr: Address) -> bool;
46 fn add_address_to_access_list(&mut self, addr: Address);
47 fn is_account_empty(&mut self, addr: Address) -> eyre::Result<bool>;
48}
49
50impl<DB: Database> JournalAccess for revm::Journal<DB> {
51 fn sload(&mut self, addr: Address, key: U256) -> eyre::Result<(U256, bool)> {
52 let result = self
53 .inner
54 .sload(&mut self.database, addr, key, false)
55 .map_err(|e| eyre::eyre!("sload failed: {e:?}"))?;
56 Ok((result.data, result.is_cold))
57 }
58
59 fn sstore(&mut self, addr: Address, key: U256, value: U256) -> eyre::Result<SStoreInfo> {
60 let result = self
61 .inner
62 .sstore(&mut self.database, addr, key, value, false)
63 .map_err(|e| eyre::eyre!("sstore failed: {e:?}"))?;
64 Ok(SStoreInfo {
65 is_cold: result.is_cold,
66 original_value: result.data.original_value,
67 present_value: result.data.present_value,
68 new_value: result.data.new_value,
69 })
70 }
71
72 fn tload(&mut self, addr: Address, key: U256) -> U256 {
73 self.inner.tload(addr, key)
74 }
75
76 fn tstore(&mut self, addr: Address, key: U256, value: U256) {
77 self.inner.tstore(addr, key, value);
78 }
79
80 fn log(&mut self, log: Log) {
81 self.inner.log(log);
82 }
83
84 fn account_balance(&mut self, addr: Address) -> eyre::Result<(U256, bool)> {
85 let result = self
86 .inner
87 .load_account(&mut self.database, addr)
88 .map_err(|e| eyre::eyre!("load_account failed: {e:?}"))?;
89 Ok((result.data.info.balance, result.is_cold))
90 }
91
92 fn account_code(&mut self, addr: Address) -> eyre::Result<(Vec<u8>, bool)> {
93 let result = self
94 .inner
95 .load_code(&mut self.database, addr)
96 .map_err(|e| eyre::eyre!("load_code failed: {e:?}"))?;
97 let code = result
98 .data
99 .info
100 .code
101 .as_ref()
102 .map(|c: &revm::bytecode::Bytecode| c.original_bytes().to_vec())
103 .unwrap_or_default();
104 Ok((code, result.is_cold))
105 }
106
107 fn account_codehash(&mut self, addr: Address) -> eyre::Result<(B256, bool)> {
108 let result = self
109 .inner
110 .load_account(&mut self.database, addr)
111 .map_err(|e| eyre::eyre!("load_account failed: {e:?}"))?;
112 Ok((result.data.info.code_hash, result.is_cold))
113 }
114
115 fn address_in_access_list(&self, addr: Address) -> bool {
116 self.inner.state.contains_key(&addr) || self.inner.warm_addresses.is_warm(&addr)
118 }
119
120 fn add_address_to_access_list(&mut self, addr: Address) {
121 let _ = self.inner.load_account(&mut self.database, addr);
123 }
124
125 fn is_account_empty(&mut self, addr: Address) -> eyre::Result<bool> {
126 let result = self
127 .inner
128 .load_account(&mut self.database, addr)
129 .map_err(|e| eyre::eyre!("load_account failed: {e:?}"))?;
130 let acc = result.data;
131 Ok(acc.info.balance.is_zero()
132 && acc.info.nonce == 0
133 && acc.info.code_hash == revm::primitives::KECCAK_EMPTY)
134 }
135}
136
137pub struct SubCallResult {
141 pub output: Vec<u8>,
142 pub gas_cost: u64,
143 pub success: bool,
144}
145
146pub struct SubCreateResult {
148 pub address: Option<Address>,
149 pub output: Vec<u8>,
150 pub gas_cost: u64,
151}
152
153pub type DoCallFn = fn(*mut (), u8, Address, Address, &[u8], u64, U256) -> SubCallResult;
158
159pub type DoCreateFn = fn(*mut (), Address, &[u8], u64, U256, Option<B256>) -> SubCreateResult;
164
165pub struct StylusEvmApi {
177 journal: *mut dyn JournalAccess,
179 address: Address,
181 caller: Address,
183 call_value: U256,
185 storage_cache: Vec<(B256, B256)>,
187 return_data: Vec<u8>,
189 read_only: bool,
191 free_pages: u16,
193 page_gas: u16,
194 ctx_ptr: *mut (),
196 do_call: Option<DoCallFn>,
197 do_create: Option<DoCreateFn>,
198}
199
200unsafe impl Send for StylusEvmApi {}
202
203impl StylusEvmApi {
204 pub unsafe fn new<DB: Database>(
212 journal: *mut revm::Journal<DB>,
213 address: Address,
214 caller: Address,
215 call_value: U256,
216 read_only: bool,
217 free_pages: u16,
218 page_gas: u16,
219 ctx_ptr: *mut (),
220 do_call: Option<DoCallFn>,
221 do_create: Option<DoCreateFn>,
222 ) -> Self {
223 let journal: *mut dyn JournalAccess = {
228 let r: &mut (dyn JournalAccess + '_) = &mut *journal;
229 #[allow(clippy::unnecessary_cast)]
230 {
231 r as *mut (dyn JournalAccess + '_) as *mut dyn JournalAccess
232 }
233 };
234 Self {
235 journal,
236 address,
237 caller,
238 call_value,
239 storage_cache: Vec::new(),
240 return_data: Vec::new(),
241 read_only,
242 free_pages,
243 page_gas,
244 ctx_ptr,
245 do_call,
246 do_create,
247 }
248 }
249
250 fn journal(&mut self) -> &mut dyn JournalAccess {
252 unsafe { &mut *self.journal }
253 }
254}
255
256impl std::fmt::Debug for StylusEvmApi {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 f.debug_struct("StylusEvmApi")
259 .field("address", &self.address)
260 .field("read_only", &self.read_only)
261 .field("cache_size", &self.storage_cache.len())
262 .finish()
263 }
264}
265
266impl EvmApi for StylusEvmApi {
267 fn get_bytes32(&mut self, key: B256, _evm_api_gas_to_use: Gas) -> eyre::Result<(B256, Gas)> {
268 let storage_key = U256::from_be_bytes(key.0);
269 let addr = self.address;
270 let (value_u256, is_cold) = self.journal().sload(addr, storage_key)?;
271 let value = B256::from(value_u256.to_be_bytes());
272 let gas_cost = if is_cold {
273 COLD_SLOAD_COST
274 } else {
275 WARM_STORAGE_READ_COST
276 };
277 Ok((value, Gas(gas_cost)))
278 }
279
280 fn cache_bytes32(&mut self, key: B256, value: B256) -> eyre::Result<Gas> {
281 self.storage_cache.push((key, value));
282 Ok(Gas(0))
283 }
284
285 fn flush_storage_cache(
286 &mut self,
287 clear: bool,
288 gas_left: Gas,
289 ) -> eyre::Result<(Gas, UserOutcomeKind)> {
290 let entries: Vec<(B256, B256)> = if clear {
291 std::mem::take(&mut self.storage_cache)
292 } else {
293 self.storage_cache.clone()
294 };
295
296 if self.read_only && !entries.is_empty() {
297 return Ok((Gas(0), UserOutcomeKind::Failure));
298 }
299
300 let mut total_gas = 0u64;
301 let mut remaining = gas_left.0;
302
303 for (key, value) in &entries {
304 let storage_key = U256::from_be_bytes(key.0);
305 let storage_value = U256::from_be_bytes(value.0);
306
307 let addr = self.address;
308 let info = self.journal().sstore(addr, storage_key, storage_value)?;
309
310 let sstore_cost = sstore_gas_cost(&info);
311 if sstore_cost > remaining {
312 return Ok((Gas(total_gas), UserOutcomeKind::OutOfInk));
313 }
314 remaining -= sstore_cost;
315 total_gas += sstore_cost;
316 }
317
318 Ok((Gas(total_gas), UserOutcomeKind::Success))
319 }
320
321 fn get_transient_bytes32(&mut self, key: B256) -> eyre::Result<B256> {
322 let storage_key = U256::from_be_bytes(key.0);
323 let addr = self.address;
324 let value = self.journal().tload(addr, storage_key);
325 Ok(B256::from(value.to_be_bytes()))
326 }
327
328 fn set_transient_bytes32(&mut self, key: B256, value: B256) -> eyre::Result<UserOutcomeKind> {
329 if self.read_only {
330 return Ok(UserOutcomeKind::Failure);
331 }
332 let storage_key = U256::from_be_bytes(key.0);
333 let storage_value = U256::from_be_bytes(value.0);
334 let addr = self.address;
335 self.journal().tstore(addr, storage_key, storage_value);
336 Ok(UserOutcomeKind::Success)
337 }
338
339 fn contract_call(
340 &mut self,
341 contract: Address,
342 calldata: &[u8],
343 gas_left: Gas,
344 gas_req: Gas,
345 value: U256,
346 ) -> eyre::Result<(u32, Gas, UserOutcomeKind)> {
347 if self.read_only && !value.is_zero() {
348 self.return_data = Vec::new();
349 return Ok((0, Gas(0), UserOutcomeKind::Failure));
350 }
351
352 let do_call = match self.do_call {
353 Some(f) => f,
354 None => {
355 self.return_data = b"sub-calls not available".to_vec();
356 return Ok((
357 self.return_data.len() as u32,
358 Gas(0),
359 UserOutcomeKind::Failure,
360 ));
361 }
362 };
363
364 let (base_cost, oog) = wasm_call_cost(self.journal(), contract, &value, gas_left.0);
366 if oog {
367 self.return_data = Vec::new();
368 return Ok((0, Gas(gas_left.0), UserOutcomeKind::Failure));
369 }
370
371 let start_gas = gas_left.0.saturating_sub(base_cost) * 63 / 64;
373 let gas = start_gas.min(gas_req.0);
374
375 let gas = if !value.is_zero() {
377 gas.saturating_add(2300) } else {
379 gas
380 };
381
382 let result = (do_call)(
383 self.ctx_ptr,
384 0, contract,
386 self.address, calldata,
388 gas,
389 value,
390 );
391
392 self.return_data = result.output;
393 let cost = base_cost.saturating_add(result.gas_cost);
395
396 let outcome = if result.success {
397 UserOutcomeKind::Success
398 } else {
399 UserOutcomeKind::Failure
400 };
401 Ok((self.return_data.len() as u32, Gas(cost), outcome))
402 }
403
404 fn delegate_call(
405 &mut self,
406 contract: Address,
407 calldata: &[u8],
408 gas_left: Gas,
409 gas_req: Gas,
410 ) -> eyre::Result<(u32, Gas, UserOutcomeKind)> {
411 let do_call = match self.do_call {
412 Some(f) => f,
413 None => {
414 self.return_data = b"sub-calls not available".to_vec();
415 return Ok((
416 self.return_data.len() as u32,
417 Gas(0),
418 UserOutcomeKind::Failure,
419 ));
420 }
421 };
422
423 let (base_cost, oog) = wasm_call_cost(self.journal(), contract, &U256::ZERO, gas_left.0);
425 if oog {
426 self.return_data = Vec::new();
427 return Ok((0, Gas(gas_left.0), UserOutcomeKind::Failure));
428 }
429
430 let start_gas = gas_left.0.saturating_sub(base_cost) * 63 / 64;
431 let gas = start_gas.min(gas_req.0);
432
433 let result = (do_call)(
434 self.ctx_ptr,
435 1, contract,
437 self.caller, calldata,
439 gas,
440 self.call_value, );
442
443 self.return_data = result.output;
444 let cost = base_cost.saturating_add(result.gas_cost);
446
447 let outcome = if result.success {
448 UserOutcomeKind::Success
449 } else {
450 UserOutcomeKind::Failure
451 };
452 Ok((self.return_data.len() as u32, Gas(cost), outcome))
453 }
454
455 fn static_call(
456 &mut self,
457 contract: Address,
458 calldata: &[u8],
459 gas_left: Gas,
460 gas_req: Gas,
461 ) -> eyre::Result<(u32, Gas, UserOutcomeKind)> {
462 let do_call = match self.do_call {
463 Some(f) => f,
464 None => {
465 self.return_data = b"sub-calls not available".to_vec();
466 return Ok((
467 self.return_data.len() as u32,
468 Gas(0),
469 UserOutcomeKind::Failure,
470 ));
471 }
472 };
473
474 let (base_cost, oog) = wasm_call_cost(self.journal(), contract, &U256::ZERO, gas_left.0);
475 if oog {
476 self.return_data = Vec::new();
477 return Ok((0, Gas(gas_left.0), UserOutcomeKind::Failure));
478 }
479
480 let start_gas = gas_left.0.saturating_sub(base_cost) * 63 / 64;
481 let gas = start_gas.min(gas_req.0);
482
483 let result = (do_call)(
484 self.ctx_ptr,
485 2, contract,
487 self.address,
488 calldata,
489 gas,
490 U256::ZERO,
491 );
492
493 self.return_data = result.output;
494 let cost = base_cost.saturating_add(result.gas_cost);
496
497 let outcome = if result.success {
498 UserOutcomeKind::Success
499 } else {
500 UserOutcomeKind::Failure
501 };
502 Ok((self.return_data.len() as u32, Gas(cost), outcome))
503 }
504
505 fn create1(
506 &mut self,
507 code: Vec<u8>,
508 endowment: U256,
509 gas: Gas,
510 ) -> eyre::Result<(CreateResponse, u32, Gas)> {
511 if self.read_only {
512 self.return_data = Vec::new();
513 return Ok((CreateResponse::Fail("write protection".into()), 0, Gas(0)));
514 }
515
516 let do_create = match self.do_create {
517 Some(f) => f,
518 None => {
519 self.return_data = b"creates not available".to_vec();
520 return Ok((
521 CreateResponse::Fail("not available".into()),
522 self.return_data.len() as u32,
523 Gas(0),
524 ));
525 }
526 };
527
528 let base_cost: u64 = 32000;
530 if gas.0 < base_cost {
531 self.return_data = Vec::new();
532 return Ok((CreateResponse::Fail("out of gas".into()), 0, Gas(gas.0)));
533 }
534 let remaining = gas.0 - base_cost;
535
536 let one_64th = remaining / 64;
538 let call_gas = remaining - one_64th;
539
540 let result = (do_create)(
541 self.ctx_ptr,
542 self.address,
543 &code,
544 call_gas,
545 endowment,
546 None, );
548
549 self.return_data = result.output.clone();
550 let cost = base_cost.saturating_add(result.gas_cost);
552
553 let response = match result.address {
554 Some(addr) => CreateResponse::Success(addr),
555 None => {
556 if self.return_data.is_empty() {
558 CreateResponse::Fail("create failed".into())
559 } else {
560 CreateResponse::Fail("reverted".into())
561 }
562 }
563 };
564
565 Ok((response, self.return_data.len() as u32, Gas(cost)))
566 }
567
568 fn create2(
569 &mut self,
570 code: Vec<u8>,
571 endowment: U256,
572 salt: B256,
573 gas: Gas,
574 ) -> eyre::Result<(CreateResponse, u32, Gas)> {
575 if self.read_only {
576 self.return_data = Vec::new();
577 return Ok((CreateResponse::Fail("write protection".into()), 0, Gas(0)));
578 }
579
580 let do_create = match self.do_create {
581 Some(f) => f,
582 None => {
583 self.return_data = b"creates not available".to_vec();
584 return Ok((
585 CreateResponse::Fail("not available".into()),
586 self.return_data.len() as u32,
587 Gas(0),
588 ));
589 }
590 };
591
592 let keccak_words = (code.len() as u64).div_ceil(32);
594 let keccak_cost = keccak_words.saturating_mul(6); let base_cost = 32000u64.saturating_add(keccak_cost);
596 if gas.0 < base_cost {
597 self.return_data = Vec::new();
598 return Ok((CreateResponse::Fail("out of gas".into()), 0, Gas(gas.0)));
599 }
600 let remaining = gas.0 - base_cost;
601
602 let one_64th = remaining / 64;
603 let call_gas = remaining - one_64th;
604
605 let result = (do_create)(
606 self.ctx_ptr,
607 self.address,
608 &code,
609 call_gas,
610 endowment,
611 Some(salt),
612 );
613
614 self.return_data = result.output.clone();
615 let cost = base_cost.saturating_add(result.gas_cost);
617
618 let response = match result.address {
619 Some(addr) => CreateResponse::Success(addr),
620 None => {
621 if self.return_data.is_empty() {
622 CreateResponse::Fail("create failed".into())
623 } else {
624 CreateResponse::Fail("reverted".into())
625 }
626 }
627 };
628
629 Ok((response, self.return_data.len() as u32, Gas(cost)))
630 }
631
632 fn get_return_data(&self) -> Vec<u8> {
633 self.return_data.clone()
634 }
635
636 fn emit_log(&mut self, data: Vec<u8>, topics: u32) -> eyre::Result<()> {
637 if self.read_only {
638 return Err(eyre::eyre!("cannot emit log in static context"));
639 }
640
641 let topic_bytes = (topics as usize) * 32;
642 if data.len() < topic_bytes {
643 return Err(eyre::eyre!("log data too short for {topics} topics"));
644 }
645
646 let mut topic_list = Vec::with_capacity(topics as usize);
647 for i in 0..topics as usize {
648 let start = i * 32;
649 let mut bytes = [0u8; 32];
650 bytes.copy_from_slice(&data[start..start + 32]);
651 topic_list.push(B256::from(bytes));
652 }
653
654 let log_data = data[topic_bytes..].to_vec();
655
656 let addr = self.address;
657 let log = Log::new(addr, topic_list, log_data.into()).expect("too many log topics");
658
659 self.journal().log(log);
660 Ok(())
661 }
662
663 fn account_balance(&mut self, address: Address) -> eyre::Result<(U256, Gas)> {
664 let (balance, is_cold) = self.journal().account_balance(address)?;
665 let gas_cost = if is_cold {
667 COLD_ACCOUNT_ACCESS_COST
668 } else {
669 WARM_ACCOUNT_ACCESS_COST
670 };
671 Ok((balance, Gas(gas_cost)))
672 }
673
674 fn account_code(
675 &mut self,
676 _arbos_version: u64,
677 address: Address,
678 gas_left: Gas,
679 ) -> eyre::Result<(Vec<u8>, Gas)> {
680 let (code, is_cold) = self.journal().account_code(address)?;
681 let access_cost = if is_cold {
683 COLD_ACCOUNT_ACCESS_COST
684 } else {
685 WARM_ACCOUNT_ACCESS_COST
686 };
687 let gas_cost = WASM_EXT_CODE_COST + access_cost;
688 if gas_left.0 < gas_cost {
690 return Ok((Vec::new(), Gas(gas_cost)));
691 }
692 Ok((code, Gas(gas_cost)))
693 }
694
695 fn account_codehash(&mut self, address: Address) -> eyre::Result<(B256, Gas)> {
696 let (hash, is_cold) = self.journal().account_codehash(address)?;
697 let gas_cost = if is_cold {
699 COLD_ACCOUNT_ACCESS_COST
700 } else {
701 WARM_ACCOUNT_ACCESS_COST
702 };
703 Ok((hash, Gas(gas_cost)))
704 }
705
706 fn add_pages(&mut self, new_pages: u16) -> eyre::Result<Gas> {
707 let (prev_open, prev_ever) = pages::add_stylus_pages(new_pages);
709 let model = MemoryModel::new(self.free_pages, self.page_gas);
710 let cost = model.gas_cost(new_pages, prev_open, prev_ever);
711 Ok(Gas(cost))
712 }
713
714 fn capture_hostio(
715 &mut self,
716 _name: &str,
717 _args: &[u8],
718 _outs: &[u8],
719 _start_ink: crate::ink::Ink,
720 _end_ink: crate::ink::Ink,
721 ) {
722 }
724}
725
726fn wasm_call_cost(
731 journal: &mut dyn JournalAccess,
732 contract: Address,
733 value: &U256,
734 budget: u64,
735) -> (u64, bool) {
736 let mut total: u64 = 0;
737
738 total += WARM_ACCOUNT_ACCESS_COST; if total > budget {
741 return (total, true);
742 }
743
744 let warm = journal.address_in_access_list(contract);
746 if !warm {
747 journal.add_address_to_access_list(contract);
748 let cold_cost = COLD_ACCOUNT_ACCESS_COST - WARM_ACCOUNT_ACCESS_COST; total = total.saturating_add(cold_cost);
750 if total > budget {
751 return (total, true);
752 }
753 }
754
755 let transfers_value = !value.is_zero();
756 if transfers_value {
757 if let Ok(empty) = journal.is_account_empty(contract) {
759 if empty {
760 total = total.saturating_add(25000); if total > budget {
762 return (total, true);
763 }
764 }
765 }
766 total = total.saturating_add(9000); if total > budget {
769 return (total, true);
770 }
771 }
772
773 (total, false)
774}
775
776fn sstore_gas_cost(info: &SStoreInfo) -> u64 {
778 let base = if info.original_value == info.new_value {
779 WARM_STORAGE_READ_COST
780 } else if info.original_value == info.present_value {
781 if info.original_value.is_zero() {
782 20_000 } else {
784 2_900 }
786 } else {
787 WARM_STORAGE_READ_COST
788 };
789
790 let cold_cost = if info.is_cold { COLD_SLOAD_COST } else { 0 };
791 base + cold_cost
792}