1use std::collections::HashMap;
2
3use alloy_primitives::{Address, B256, Log, U256};
4use arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_LAST_CODE_CACHE_FIX;
5use arb_primitives::multigas::MultiGas;
6use revm::Database;
7
8use crate::{
9 evm_api::{CreateResponse, EvmApi, UserOutcomeKind},
10 ink::Gas,
11 multi_gas,
12};
13
14const COLD_SLOAD_COST: u64 = 2100;
16const WARM_STORAGE_READ_COST: u64 = 100;
17const COLD_ACCOUNT_ACCESS_COST: u64 = 2600;
18const WARM_ACCOUNT_ACCESS_COST: u64 = 100;
19
20const WASM_EXT_CODE_COST: u64 = 700;
24
25pub struct SStoreInfo {
29 pub is_cold: bool,
30 pub original_value: U256,
31 pub present_value: U256,
32 pub new_value: U256,
33}
34
35pub trait JournalAccess {
40 fn sload(&mut self, addr: Address, key: U256) -> eyre::Result<(U256, bool)>;
41 fn sstore(&mut self, addr: Address, key: U256, value: U256) -> eyre::Result<SStoreInfo>;
42 fn tload(&mut self, addr: Address, key: U256) -> U256;
43 fn tstore(&mut self, addr: Address, key: U256, value: U256);
44 fn log(&mut self, log: Log);
45 fn account_balance(&mut self, addr: Address) -> eyre::Result<(U256, bool)>;
46 fn account_code(&mut self, addr: Address) -> eyre::Result<(Vec<u8>, bool)>;
47 fn account_codehash(&mut self, addr: Address) -> eyre::Result<(B256, bool)>;
48 fn address_in_access_list(&self, addr: Address) -> bool;
49 fn add_address_to_access_list(&mut self, addr: Address);
50 fn is_account_empty(&mut self, addr: Address) -> eyre::Result<bool>;
51}
52
53impl<DB: Database> JournalAccess for revm::Journal<DB> {
54 fn sload(&mut self, addr: Address, key: U256) -> eyre::Result<(U256, bool)> {
55 let result = self
56 .inner
57 .sload(&mut self.database, addr, key, false)
58 .map_err(|e| eyre::eyre!("sload failed: {e:?}"))?;
59 Ok((result.data, result.is_cold))
60 }
61
62 fn sstore(&mut self, addr: Address, key: U256, value: U256) -> eyre::Result<SStoreInfo> {
63 let result = self
64 .inner
65 .sstore(&mut self.database, addr, key, value, false)
66 .map_err(|e| eyre::eyre!("sstore failed: {e:?}"))?;
67 Ok(SStoreInfo {
68 is_cold: result.is_cold,
69 original_value: result.data.original_value,
70 present_value: result.data.present_value,
71 new_value: result.data.new_value,
72 })
73 }
74
75 fn tload(&mut self, addr: Address, key: U256) -> U256 {
76 self.inner.tload(addr, key)
77 }
78
79 fn tstore(&mut self, addr: Address, key: U256, value: U256) {
80 self.inner.tstore(addr, key, value);
81 }
82
83 fn log(&mut self, log: Log) {
84 self.inner.log(log);
85 }
86
87 fn account_balance(&mut self, addr: Address) -> eyre::Result<(U256, bool)> {
88 let result = self
89 .inner
90 .load_account(&mut self.database, addr)
91 .map_err(|e| eyre::eyre!("load_account failed: {e:?}"))?;
92 Ok((result.data.info.balance, result.is_cold))
93 }
94
95 fn account_code(&mut self, addr: Address) -> eyre::Result<(Vec<u8>, bool)> {
96 let result = self
97 .inner
98 .load_code(&mut self.database, addr)
99 .map_err(|e| eyre::eyre!("load_code failed: {e:?}"))?;
100 let code = result
101 .data
102 .info
103 .code
104 .as_ref()
105 .map(|c: &revm::bytecode::Bytecode| c.original_bytes().to_vec())
106 .unwrap_or_default();
107 Ok((code, result.is_cold))
108 }
109
110 fn account_codehash(&mut self, addr: Address) -> eyre::Result<(B256, bool)> {
111 let result = self
112 .inner
113 .load_account(&mut self.database, addr)
114 .map_err(|e| eyre::eyre!("load_account failed: {e:?}"))?;
115 let is_cold = result.is_cold;
116 let hash = if result.data.info.is_empty() {
118 B256::ZERO
119 } else {
120 result.data.info.code_hash
121 };
122 Ok((hash, is_cold))
123 }
124
125 fn address_in_access_list(&self, addr: Address) -> bool {
126 if self.inner.warm_addresses.is_warm(&addr) {
128 return true;
129 }
130 if let Some(account) = self.inner.state.get(&addr) {
135 return !account.is_cold_transaction_id(self.inner.transaction_id);
136 }
137 false
138 }
139
140 fn add_address_to_access_list(&mut self, addr: Address) {
141 let _ = self.inner.load_account(&mut self.database, addr);
143 }
144
145 fn is_account_empty(&mut self, addr: Address) -> eyre::Result<bool> {
146 let result = self
147 .inner
148 .load_account(&mut self.database, addr)
149 .map_err(|e| eyre::eyre!("load_account failed: {e:?}"))?;
150 let acc = result.data;
151 Ok(acc.info.balance.is_zero()
152 && acc.info.nonce == 0
153 && acc.info.code_hash == revm::primitives::KECCAK_EMPTY)
154 }
155}
156
157pub struct SubCallResult {
161 pub output: Vec<u8>,
162 pub gas_cost: u64,
163 pub success: bool,
164 pub refund: i64,
166 pub pages: (u16, u16),
168}
169
170pub struct SubCreateResult {
172 pub address: Option<Address>,
173 pub output: Vec<u8>,
174 pub gas_cost: u64,
175 pub pages: (u16, u16),
176}
177
178pub type DoCallFn = fn(
187 *mut (),
188 *const (),
189 u8,
190 Address,
191 Address,
192 Address,
193 &[u8],
194 u64,
195 U256,
196 (u16, u16),
197) -> SubCallResult;
198
199pub type DoCreateFn =
201 fn(*mut (), *const (), Address, &[u8], u64, U256, Option<B256>, (u16, u16)) -> SubCreateResult;
202
203struct StorageCacheEntry {
205 value: B256,
207 known: Option<B256>,
209}
210
211impl StorageCacheEntry {
212 fn known(value: B256) -> Self {
213 Self {
214 value,
215 known: Some(value),
216 }
217 }
218
219 fn unknown(value: B256) -> Self {
220 Self { value, known: None }
221 }
222
223 fn dirty(&self) -> bool {
224 self.known != Some(self.value)
225 }
226}
227
228struct StorageCache {
231 slots: HashMap<B256, StorageCacheEntry>,
232 reads: u32,
233 writes: u32,
234}
235
236impl StorageCache {
237 fn new() -> Self {
238 Self {
239 slots: HashMap::new(),
240 reads: 0,
241 writes: 0,
242 }
243 }
244
245 fn read_gas(&mut self) -> Gas {
246 self.reads += 1;
247 match self.reads {
248 0..=32 => Gas(0),
249 33..=128 => Gas(2),
250 _ => Gas(10),
251 }
252 }
253
254 fn write_gas(&mut self) -> Gas {
255 self.writes += 1;
256 match self.writes {
257 0..=8 => Gas(0),
258 9..=64 => Gas(7),
259 _ => Gas(10),
260 }
261 }
262}
263
264pub struct StylusEvmApi {
276 journal: *mut dyn JournalAccess,
278 address: Address,
280 caller: Address,
282 call_value: U256,
284 storage_cache: StorageCache,
286 sstore_refund: i64,
288 return_data: Vec<u8>,
290 read_only: bool,
292 arbos_version: u64,
294 ctx_ptr: *mut (),
296 precompile_ctx_ptr: *const (),
300 do_call: Option<DoCallFn>,
301 do_create: Option<DoCreateFn>,
302 last_code: Option<(Address, Vec<u8>)>,
304 multi_gas: MultiGas,
307 sub_call_gas: u64,
310}
311
312unsafe impl Send for StylusEvmApi {}
322
323impl StylusEvmApi {
324 #[allow(clippy::too_many_arguments)]
332 pub unsafe fn new<DB: Database>(
333 journal: *mut revm::Journal<DB>,
334 address: Address,
335 caller: Address,
336 call_value: U256,
337 read_only: bool,
338 arbos_version: u64,
339 ctx_ptr: *mut (),
340 precompile_ctx_ptr: *const (),
341 do_call: Option<DoCallFn>,
342 do_create: Option<DoCreateFn>,
343 ) -> Self {
344 unsafe {
345 let journal: *mut dyn JournalAccess = {
346 let r: &mut dyn JournalAccess = &mut *journal;
354 core::mem::transmute(r)
355 };
356 Self {
357 journal,
358 address,
359 caller,
360 call_value,
361 storage_cache: StorageCache::new(),
362 sstore_refund: 0,
363 return_data: Vec::new(),
364 read_only,
365 arbos_version,
366 ctx_ptr,
367 precompile_ctx_ptr,
368 do_call,
369 do_create,
370 last_code: None,
371 multi_gas: MultiGas::zero(),
372 sub_call_gas: 0,
373 }
374 }
375 }
376
377 pub fn multi_gas(&self) -> MultiGas {
379 self.multi_gas
380 }
381
382 pub fn sub_call_gas(&self) -> u64 {
385 self.sub_call_gas
386 }
387
388 fn add_multi_gas(&mut self, gas: MultiGas) {
389 self.multi_gas = self.multi_gas.saturating_add(gas);
390 }
391
392 fn record_sub_call(&mut self, base_cost_gas: MultiGas, sub_gas: u64) {
393 self.multi_gas = self.multi_gas.saturating_add(base_cost_gas);
394 self.sub_call_gas = self.sub_call_gas.saturating_add(sub_gas);
395 }
396
397 fn journal(&mut self) -> &mut dyn JournalAccess {
399 unsafe { &mut *self.journal }
405 }
406
407 pub fn sstore_refund(&self) -> i64 {
409 self.sstore_refund
410 }
411}
412
413impl std::fmt::Debug for StylusEvmApi {
414 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415 f.debug_struct("StylusEvmApi")
416 .field("address", &self.address)
417 .field("read_only", &self.read_only)
418 .field("cache_size", &self.storage_cache.slots.len())
419 .finish()
420 }
421}
422
423impl EvmApi for StylusEvmApi {
424 fn get_bytes32(&mut self, key: B256, evm_api_gas_to_use: Gas) -> eyre::Result<(B256, Gas)> {
425 let mut cost = self.storage_cache.read_gas();
426
427 let value = if let Some(entry) = self.storage_cache.slots.get(&key) {
428 entry.value
429 } else {
430 let storage_key = U256::from_be_bytes(key.0);
431 let addr = self.address;
432 let (value_u256, is_cold) = self.journal().sload(addr, storage_key)?;
433 let value = B256::from(value_u256.to_be_bytes());
434
435 let sload_cost = if is_cold {
436 COLD_SLOAD_COST
437 } else {
438 WARM_STORAGE_READ_COST
439 };
440 cost = Gas(cost
441 .0
442 .saturating_add(sload_cost)
443 .saturating_add(evm_api_gas_to_use.0));
444 self.add_multi_gas(multi_gas::state_load(is_cold));
445
446 self.storage_cache
447 .slots
448 .insert(key, StorageCacheEntry::known(value));
449 value
450 };
451
452 Ok((value, cost))
453 }
454
455 fn cache_bytes32(&mut self, key: B256, value: B256) -> eyre::Result<Gas> {
456 let cost = self.storage_cache.write_gas();
457 match self.storage_cache.slots.get_mut(&key) {
458 Some(entry) => entry.value = value,
459 None => {
460 self.storage_cache
461 .slots
462 .insert(key, StorageCacheEntry::unknown(value));
463 }
464 }
465 Ok(cost)
466 }
467
468 fn flush_storage_cache(
469 &mut self,
470 clear: bool,
471 gas_left: Gas,
472 ) -> eyre::Result<(Gas, UserOutcomeKind)> {
473 let dirty: Vec<(B256, B256)> = self
475 .storage_cache
476 .slots
477 .iter()
478 .filter(|(_, v)| v.dirty())
479 .map(|(k, v)| (*k, v.value))
480 .collect();
481
482 if clear {
483 self.storage_cache.slots.clear();
484 } else {
485 for entry in self.storage_cache.slots.values_mut() {
487 entry.known = Some(entry.value);
488 }
489 }
490
491 if dirty.is_empty() {
492 return Ok((Gas(0), UserOutcomeKind::Success));
493 }
494
495 if self.read_only {
496 return Ok((Gas(0), UserOutcomeKind::Failure));
497 }
498
499 let mut total_gas = 0u64;
500 let mut remaining = gas_left.0;
501 let mut is_out_of_gas = false;
502
503 for (key, value) in &dirty {
504 let storage_key = U256::from_be_bytes(key.0);
505 let storage_value = U256::from_be_bytes(value.0);
506
507 let addr = self.address;
508 let info = self.journal().sstore(addr, storage_key, storage_value)?;
509
510 let sstore_cost = sstore_gas_cost(&info);
511 if sstore_cost > remaining {
512 is_out_of_gas = true;
513 total_gas = gas_left.0;
514 break;
515 }
516 remaining -= sstore_cost;
517 total_gas += sstore_cost;
518 self.sstore_refund += sstore_refund(&info);
519 self.add_multi_gas(multi_gas::state_store(
520 info.is_cold,
521 info.original_value,
522 info.present_value,
523 info.new_value,
524 ));
525 }
526
527 if is_out_of_gas || remaining == 0 {
530 const ARBOS_VERSION_DIA: u64 = 50;
531 let outcome = if self.arbos_version < ARBOS_VERSION_DIA {
532 UserOutcomeKind::Failure
533 } else {
534 UserOutcomeKind::OutOfInk
535 };
536 return Ok((Gas(total_gas), outcome));
537 }
538
539 Ok((Gas(total_gas), UserOutcomeKind::Success))
540 }
541
542 fn get_transient_bytes32(&mut self, key: B256) -> eyre::Result<B256> {
543 let storage_key = U256::from_be_bytes(key.0);
544 let addr = self.address;
545 let value = self.journal().tload(addr, storage_key);
546 Ok(B256::from(value.to_be_bytes()))
547 }
548
549 fn set_transient_bytes32(&mut self, key: B256, value: B256) -> eyre::Result<UserOutcomeKind> {
550 if self.read_only {
551 return Ok(UserOutcomeKind::Failure);
552 }
553 let storage_key = U256::from_be_bytes(key.0);
554 let storage_value = U256::from_be_bytes(value.0);
555 let addr = self.address;
556 self.journal().tstore(addr, storage_key, storage_value);
557 Ok(UserOutcomeKind::Success)
558 }
559
560 fn contract_call(
561 &mut self,
562 contract: Address,
563 calldata: &[u8],
564 gas_left: Gas,
565 gas_req: Gas,
566 value: U256,
567 pages: (u16, u16),
568 ) -> eyre::Result<(u32, Gas, UserOutcomeKind, (u16, u16))> {
569 if self.read_only && !value.is_zero() {
570 self.return_data = Vec::new();
571 return Ok((0, Gas(0), UserOutcomeKind::Failure, pages));
572 }
573
574 let do_call = match self.do_call {
575 Some(f) => f,
576 None => {
577 self.return_data = b"sub-calls not available".to_vec();
578 return Ok((
579 self.return_data.len() as u32,
580 Gas(0),
581 UserOutcomeKind::Failure,
582 pages,
583 ));
584 }
585 };
586
587 let (base_cost, oog, call_mg) =
588 wasm_call_cost(self.journal(), contract, &value, gas_left.0);
589 if oog {
590 self.return_data = Vec::new();
591 return Ok((0, Gas(gas_left.0), UserOutcomeKind::Failure, pages));
592 }
593
594 let start_gas = gas_left.0.saturating_sub(base_cost) * 63 / 64;
595 let gas = start_gas.min(gas_req.0);
596
597 let gas = if !value.is_zero() {
598 gas.saturating_add(2300)
599 } else {
600 gas
601 };
602
603 let result = (do_call)(
604 self.ctx_ptr,
605 self.precompile_ctx_ptr,
606 0,
607 contract,
608 self.address,
609 contract,
610 calldata,
611 gas,
612 value,
613 pages,
614 );
615
616 self.return_data = result.output;
617 let cost = base_cost.saturating_add(result.gas_cost);
618 self.record_sub_call(call_mg, result.gas_cost);
619 self.sstore_refund = self.sstore_refund.saturating_add(result.refund);
620
621 let outcome = if result.success {
622 UserOutcomeKind::Success
623 } else {
624 UserOutcomeKind::Failure
625 };
626 Ok((
627 self.return_data.len() as u32,
628 Gas(cost),
629 outcome,
630 result.pages,
631 ))
632 }
633
634 fn delegate_call(
635 &mut self,
636 contract: Address,
637 calldata: &[u8],
638 gas_left: Gas,
639 gas_req: Gas,
640 pages: (u16, u16),
641 ) -> eyre::Result<(u32, Gas, UserOutcomeKind, (u16, u16))> {
642 let do_call = match self.do_call {
643 Some(f) => f,
644 None => {
645 self.return_data = b"sub-calls not available".to_vec();
646 return Ok((
647 self.return_data.len() as u32,
648 Gas(0),
649 UserOutcomeKind::Failure,
650 pages,
651 ));
652 }
653 };
654
655 let (base_cost, oog, call_mg) =
656 wasm_call_cost(self.journal(), contract, &U256::ZERO, gas_left.0);
657 if oog {
658 self.return_data = Vec::new();
659 return Ok((0, Gas(gas_left.0), UserOutcomeKind::Failure, pages));
660 }
661
662 let start_gas = gas_left.0.saturating_sub(base_cost) * 63 / 64;
663 let gas = start_gas.min(gas_req.0);
664
665 let result = (do_call)(
666 self.ctx_ptr,
667 self.precompile_ctx_ptr,
668 1,
669 contract,
670 self.caller,
671 self.address,
672 calldata,
673 gas,
674 self.call_value,
675 pages,
676 );
677
678 self.return_data = result.output;
679 let cost = base_cost.saturating_add(result.gas_cost);
680 self.record_sub_call(call_mg, result.gas_cost);
681 self.sstore_refund = self.sstore_refund.saturating_add(result.refund);
682
683 let outcome = if result.success {
684 UserOutcomeKind::Success
685 } else {
686 UserOutcomeKind::Failure
687 };
688 Ok((
689 self.return_data.len() as u32,
690 Gas(cost),
691 outcome,
692 result.pages,
693 ))
694 }
695
696 fn static_call(
697 &mut self,
698 contract: Address,
699 calldata: &[u8],
700 gas_left: Gas,
701 gas_req: Gas,
702 pages: (u16, u16),
703 ) -> eyre::Result<(u32, Gas, UserOutcomeKind, (u16, u16))> {
704 let do_call = match self.do_call {
705 Some(f) => f,
706 None => {
707 self.return_data = b"sub-calls not available".to_vec();
708 return Ok((
709 self.return_data.len() as u32,
710 Gas(0),
711 UserOutcomeKind::Failure,
712 pages,
713 ));
714 }
715 };
716
717 let (base_cost, oog, call_mg) =
718 wasm_call_cost(self.journal(), contract, &U256::ZERO, gas_left.0);
719 if oog {
720 self.return_data = Vec::new();
721 return Ok((0, Gas(gas_left.0), UserOutcomeKind::Failure, pages));
722 }
723
724 let start_gas = gas_left.0.saturating_sub(base_cost) * 63 / 64;
725 let gas = start_gas.min(gas_req.0);
726
727 let result = (do_call)(
728 self.ctx_ptr,
729 self.precompile_ctx_ptr,
730 2,
731 contract,
732 self.address,
733 contract,
734 calldata,
735 gas,
736 U256::ZERO,
737 pages,
738 );
739
740 self.return_data = result.output;
741 let cost = base_cost.saturating_add(result.gas_cost);
742 self.record_sub_call(call_mg, result.gas_cost);
743 self.sstore_refund = self.sstore_refund.saturating_add(result.refund);
744
745 let outcome = if result.success {
746 UserOutcomeKind::Success
747 } else {
748 UserOutcomeKind::Failure
749 };
750 Ok((
751 self.return_data.len() as u32,
752 Gas(cost),
753 outcome,
754 result.pages,
755 ))
756 }
757
758 fn create1(
759 &mut self,
760 code: Vec<u8>,
761 endowment: U256,
762 gas: Gas,
763 pages: (u16, u16),
764 ) -> eyre::Result<(CreateResponse, u32, Gas, (u16, u16))> {
765 if self.read_only {
766 self.return_data = Vec::new();
767 return Ok((
768 CreateResponse::Fail("write protection".into()),
769 0,
770 Gas(0),
771 pages,
772 ));
773 }
774
775 let do_create = match self.do_create {
776 Some(f) => f,
777 None => {
778 self.return_data = b"creates not available".to_vec();
779 return Ok((
780 CreateResponse::Fail("not available".into()),
781 self.return_data.len() as u32,
782 Gas(0),
783 pages,
784 ));
785 }
786 };
787
788 let base_cost: u64 = 32000;
789 if gas.0 < base_cost {
790 self.return_data = Vec::new();
791 return Ok((
792 CreateResponse::Fail("out of gas".into()),
793 0,
794 Gas(gas.0),
795 pages,
796 ));
797 }
798 let remaining = gas.0 - base_cost;
799 let one_64th = remaining / 64;
800 let call_gas = remaining - one_64th;
801
802 let result = (do_create)(
803 self.ctx_ptr,
804 self.precompile_ctx_ptr,
805 self.address,
806 &code,
807 call_gas,
808 endowment,
809 None,
810 pages,
811 );
812
813 self.return_data = result.output.clone();
814 let cost = base_cost.saturating_add(result.gas_cost);
815 self.record_sub_call(MultiGas::computation_gas(base_cost), result.gas_cost);
816
817 let response = match result.address {
818 Some(addr) => CreateResponse::Success(addr),
819 None => CreateResponse::Success(Address::ZERO),
820 };
821
822 Ok((
823 response,
824 self.return_data.len() as u32,
825 Gas(cost),
826 result.pages,
827 ))
828 }
829
830 fn create2(
831 &mut self,
832 code: Vec<u8>,
833 endowment: U256,
834 salt: B256,
835 gas: Gas,
836 pages: (u16, u16),
837 ) -> eyre::Result<(CreateResponse, u32, Gas, (u16, u16))> {
838 if self.read_only {
839 self.return_data = Vec::new();
840 return Ok((
841 CreateResponse::Fail("write protection".into()),
842 0,
843 Gas(0),
844 pages,
845 ));
846 }
847
848 let do_create = match self.do_create {
849 Some(f) => f,
850 None => {
851 self.return_data = b"creates not available".to_vec();
852 return Ok((
853 CreateResponse::Fail("not available".into()),
854 self.return_data.len() as u32,
855 Gas(0),
856 pages,
857 ));
858 }
859 };
860
861 let keccak_words = (code.len() as u64).div_ceil(32);
862 let keccak_cost = keccak_words.saturating_mul(6);
863 let base_cost = 32000u64.saturating_add(keccak_cost);
864 if gas.0 < base_cost {
865 self.return_data = Vec::new();
866 return Ok((
867 CreateResponse::Fail("out of gas".into()),
868 0,
869 Gas(gas.0),
870 pages,
871 ));
872 }
873 let remaining = gas.0 - base_cost;
874 let one_64th = remaining / 64;
875 let call_gas = remaining - one_64th;
876
877 let result = (do_create)(
878 self.ctx_ptr,
879 self.precompile_ctx_ptr,
880 self.address,
881 &code,
882 call_gas,
883 endowment,
884 Some(salt),
885 pages,
886 );
887
888 self.return_data = result.output.clone();
889 let cost = base_cost.saturating_add(result.gas_cost);
890 self.record_sub_call(MultiGas::computation_gas(base_cost), result.gas_cost);
891
892 let response = match result.address {
893 Some(addr) => CreateResponse::Success(addr),
894 None => CreateResponse::Success(Address::ZERO),
895 };
896
897 Ok((
898 response,
899 self.return_data.len() as u32,
900 Gas(cost),
901 result.pages,
902 ))
903 }
904
905 fn get_return_data(&self) -> Vec<u8> {
906 self.return_data.clone()
907 }
908
909 fn emit_log(&mut self, data: Vec<u8>, topics: u32) -> eyre::Result<()> {
910 if self.read_only {
911 return Err(eyre::eyre!("cannot emit log in static context"));
912 }
913
914 let topic_bytes = (topics as usize) * 32;
915 if data.len() < topic_bytes {
916 return Err(eyre::eyre!("log data too short for {topics} topics"));
917 }
918
919 let mut topic_list = Vec::with_capacity(topics as usize);
920 for i in 0..topics as usize {
921 let start = i * 32;
922 let mut bytes = [0u8; 32];
923 bytes.copy_from_slice(&data[start..start + 32]);
924 topic_list.push(B256::from(bytes));
925 }
926
927 let log_data = data[topic_bytes..].to_vec();
928 self.add_multi_gas(multi_gas::log(topics as u64, log_data.len() as u64));
929
930 let addr = self.address;
931 let log = Log::new(addr, topic_list, log_data.into()).expect("too many log topics");
932
933 self.journal().log(log);
934 Ok(())
935 }
936
937 fn account_balance(&mut self, address: Address) -> eyre::Result<(U256, Gas)> {
938 let (balance, is_cold) = self.journal().account_balance(address)?;
939 let gas_cost = if is_cold {
941 COLD_ACCOUNT_ACCESS_COST
942 } else {
943 WARM_ACCOUNT_ACCESS_COST
944 };
945 self.add_multi_gas(multi_gas::account_touch(is_cold, 0));
946 Ok((balance, Gas(gas_cost)))
947 }
948
949 fn account_code(
950 &mut self,
951 _arbos_version: u64,
952 address: Address,
953 gas_left: Gas,
954 ) -> eyre::Result<(Vec<u8>, Gas)> {
955 if let Some((stored, data)) = self.last_code.as_ref()
956 && *stored == address
957 {
958 return Ok((data.clone(), Gas(0)));
959 }
960 let (code, is_cold) = self.journal().account_code(address)?;
961 let access_cost = if is_cold {
963 COLD_ACCOUNT_ACCESS_COST
964 } else {
965 WARM_ACCOUNT_ACCESS_COST
966 };
967 let gas_cost = WASM_EXT_CODE_COST + access_cost;
968 self.add_multi_gas(multi_gas::account_touch(is_cold, WASM_EXT_CODE_COST));
969 if gas_left.0 < gas_cost {
971 return Ok((Vec::new(), Gas(gas_cost)));
972 }
973 if !code.is_empty() || self.arbos_version < ARBOS_VERSION_STYLUS_LAST_CODE_CACHE_FIX {
974 self.last_code = Some((address, code.clone()));
975 }
976 Ok((code, Gas(gas_cost)))
977 }
978
979 fn account_codehash(&mut self, address: Address) -> eyre::Result<(B256, Gas)> {
980 let (hash, is_cold) = self.journal().account_codehash(address)?;
981 let gas_cost = if is_cold {
983 COLD_ACCOUNT_ACCESS_COST
984 } else {
985 WARM_ACCOUNT_ACCESS_COST
986 };
987 self.add_multi_gas(multi_gas::account_touch(is_cold, 0));
988 Ok((hash, Gas(gas_cost)))
989 }
990
991 fn capture_hostio(
992 &mut self,
993 _name: &str,
994 _args: &[u8],
995 _outs: &[u8],
996 _start_ink: crate::ink::Ink,
997 _end_ink: crate::ink::Ink,
998 ) {
999 }
1001}
1002
1003fn wasm_call_cost(
1010 journal: &mut dyn JournalAccess,
1011 contract: Address,
1012 value: &U256,
1013 budget: u64,
1014) -> (u64, bool, MultiGas) {
1015 let is_cold = !journal.address_in_access_list(contract);
1016 if is_cold {
1017 journal.add_address_to_access_list(contract);
1018 }
1019 let transfers_value = !value.is_zero();
1020 let new_account = transfers_value && journal.is_account_empty(contract).unwrap_or(false);
1021
1022 let mg = multi_gas::call_cost(is_cold, transfers_value, new_account);
1023 let total = mg.single_gas();
1024 if total > budget {
1025 return (total, true, MultiGas::zero());
1026 }
1027 (total, false, mg)
1028}
1029
1030const SSTORE_CLEARS_SCHEDULE: i64 = 4_800; const SSTORE_SET_REFUND: i64 = 19_900; const SSTORE_RESET_REFUND: i64 = 2_800; fn sstore_refund(info: &SStoreInfo) -> i64 {
1037 let original = info.original_value;
1038 let present = info.present_value;
1039 let new = info.new_value;
1040
1041 if new == present {
1043 return 0;
1044 }
1045
1046 if original == present && new.is_zero() {
1048 return SSTORE_CLEARS_SCHEDULE;
1049 }
1050
1051 let mut refund: i64 = 0;
1052
1053 if !original.is_zero() {
1055 if present.is_zero() {
1056 refund -= SSTORE_CLEARS_SCHEDULE;
1058 } else if new.is_zero() {
1059 refund += SSTORE_CLEARS_SCHEDULE;
1061 }
1062 }
1063
1064 if original == new {
1066 if original.is_zero() {
1067 refund += SSTORE_SET_REFUND;
1068 } else {
1069 refund += SSTORE_RESET_REFUND;
1070 }
1071 }
1072
1073 refund
1074}
1075
1076fn sstore_gas_cost(info: &SStoreInfo) -> u64 {
1078 let base = if info.original_value == info.new_value {
1079 WARM_STORAGE_READ_COST
1080 } else if info.original_value == info.present_value {
1081 if info.original_value.is_zero() {
1082 20_000 } else {
1084 2_900 }
1086 } else {
1087 WARM_STORAGE_READ_COST
1088 };
1089
1090 let cold_cost = if info.is_cold { COLD_SLOAD_COST } else { 0 };
1091 base + cold_cost
1092}
1093
1094#[cfg(test)]
1097mod sstore_parity_tests {
1098 use alloy_primitives::U256;
1099
1100 use super::{SStoreInfo, sstore_gas_cost, sstore_refund};
1101
1102 fn info(original: u64, present: u64, new: u64, is_cold: bool) -> SStoreInfo {
1103 SStoreInfo {
1104 is_cold,
1105 original_value: U256::from(original),
1106 present_value: U256::from(present),
1107 new_value: U256::from(new),
1108 }
1109 }
1110
1111 #[test]
1116 fn case_1_noop_untouched_warm() {
1117 assert_eq!(sstore_gas_cost(&info(5, 5, 5, false)), 100);
1118 assert_eq!(sstore_refund(&info(5, 5, 5, false)), 0);
1119 }
1120
1121 #[test]
1124 fn case_2_1_1_create_slot() {
1125 assert_eq!(sstore_gas_cost(&info(0, 0, 5, false)), 20_000);
1126 assert_eq!(sstore_refund(&info(0, 0, 5, false)), 0);
1127 }
1128
1129 #[test]
1132 fn case_2_1_2_update_clean() {
1133 assert_eq!(sstore_gas_cost(&info(5, 5, 10, false)), 2_900);
1134 assert_eq!(sstore_refund(&info(5, 5, 10, false)), 0);
1135 }
1136
1137 #[test]
1140 fn case_2_1_2b_delete_clean() {
1141 assert_eq!(sstore_gas_cost(&info(5, 5, 0, false)), 2_900);
1142 assert_eq!(sstore_refund(&info(5, 5, 0, false)), 4_800);
1143 }
1144
1145 #[test]
1148 fn case_2_2_dirty_update() {
1149 assert_eq!(sstore_gas_cost(&info(5, 10, 15, false)), 100);
1151 assert_eq!(sstore_refund(&info(5, 10, 15, false)), 0);
1152 }
1153
1154 #[test]
1157 fn case_2_2_1_1_un_clear_dirty() {
1158 assert_eq!(sstore_gas_cost(&info(5, 0, 10, false)), 100);
1159 assert_eq!(sstore_refund(&info(5, 0, 10, false)), -4_800);
1160 }
1161
1162 #[test]
1165 fn case_2_2_1_2_clear_dirty() {
1166 assert_eq!(sstore_gas_cost(&info(5, 10, 0, false)), 100);
1168 assert_eq!(sstore_refund(&info(5, 10, 0, false)), 4_800);
1169 }
1170
1171 #[test]
1175 fn case_2_2_2_1_restore_to_zero_original() {
1176 assert_eq!(sstore_gas_cost(&info(0, 5, 0, false)), 100);
1177 assert_eq!(sstore_refund(&info(0, 5, 0, false)), 19_900);
1178 }
1179
1180 #[test]
1185 fn case_2_2_2_2_restore_to_nonzero_original() {
1186 assert_eq!(sstore_gas_cost(&info(5, 10, 5, false)), 100);
1187 assert_eq!(sstore_refund(&info(5, 10, 5, false)), 2_800);
1188 }
1189
1190 #[test]
1194 fn cold_access_adds_2100() {
1195 assert_eq!(sstore_gas_cost(&info(5, 5, 5, true)), 100 + 2_100);
1196 assert_eq!(sstore_gas_cost(&info(5, 5, 10, true)), 2_900 + 2_100);
1197 assert_eq!(sstore_gas_cost(&info(0, 0, 5, true)), 20_000 + 2_100);
1198 assert_eq!(sstore_gas_cost(&info(5, 10, 0, true)), 100 + 2_100);
1199 }
1200
1201 #[test]
1207 fn un_clear_plus_restore_stacks() {
1208 assert_eq!(sstore_gas_cost(&info(5, 0, 5, false)), 100);
1210 assert_eq!(sstore_refund(&info(5, 0, 5, false)), -4_800 + 2_800);
1211 }
1212
1213 #[test]
1216 fn clear_dirty_without_restore() {
1217 assert_eq!(sstore_refund(&info(5, 10, 0, false)), 4_800);
1219 }
1220
1221 #[test]
1226 fn mixed_eight_slot_flush_totals() {
1227 let slots = [
1228 info(0x12e, 0x12f, 0x130, false), info(0x880f, 0x880f, 0x23b5, false), info(0x10906e, 0x10906e, 0x275b, false), info(0, 0, 1, false), info(0x2fea, 0x2fea, 0xf8e2, false), info(0x26658a, 0x26658a, 0x27bd, false), info(0x7e51, 0x2160, 0x7e51, false), info(0x1a5f, 0x1a5f, 0x1c50, false), ];
1237 let total_cost: u64 = slots.iter().map(sstore_gas_cost).sum();
1238 let total_refund: i64 = slots.iter().map(sstore_refund).sum();
1239 assert_eq!(total_cost, 100 + 2_900 * 5 + 20_000 + 100);
1240 assert_eq!(total_cost, 34_700);
1241 assert_eq!(total_refund, 2_800);
1242 }
1243}