1use alloy_evm::{
2 eth::EthEvmContext, precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory,
3};
4use alloy_primitives::{Address, Bytes, B256, U256};
5use arb_precompiles::register_arb_precompiles;
6use arb_stylus::{
7 config::StylusConfig, ink::Gas as StylusGas, meter::MeteredMachine, run::RunProgram,
8 StylusEvmApi,
9};
10use arbos::programs::types::EvmData;
11use core::fmt::Debug;
12use revm::{
13 context::result::EVMError,
14 context_interface::{
15 host::LoadError,
16 result::{HaltReason, ResultAndState},
17 },
18 handler::{instructions::EthInstructions, EthFrame, PrecompileProvider},
19 inspector::NoOpInspector,
20 interpreter::{
21 interpreter::EthInterpreter,
22 interpreter_types::{InputsTr, ReturnData, RuntimeFlag, StackTr},
23 CallInput, CallInputs, CallScheme, Gas as EvmGas, Host, InstructionContext,
24 InstructionResult, InterpreterResult, InterpreterTypes,
25 },
26 primitives::hardfork::SpecId,
27};
28
29use crate::transaction::ArbTransaction;
30
31const BLOBBASEFEE_OPCODE: u8 = 0x4a;
33
34const SELFDESTRUCT_OPCODE: u8 = 0xff;
36
37const NUMBER_OPCODE: u8 = 0x43;
39
40const BLOCKHASH_OPCODE: u8 = 0x40;
42
43const BALANCE_OPCODE: u8 = 0x31;
45
46fn arb_number<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
53 let l1_block = arb_precompiles::get_l1_block_number_for_evm();
54 if !ctx.interpreter.stack.push(U256::from(l1_block)) {
55 ctx.interpreter.halt(InstructionResult::StackOverflow);
56 }
57}
58
59fn arb_blockhash<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
66 use revm::interpreter::InstructionResult;
67
68 let requested = match ctx.interpreter.stack.pop() {
69 Some(v) => v,
70 None => {
71 ctx.interpreter.halt(InstructionResult::StackUnderflow);
72 return;
73 }
74 };
75
76 let l1_block_number = U256::from(arb_precompiles::get_l1_block_number_for_evm());
77
78 let Some(diff) = l1_block_number.checked_sub(requested) else {
79 if !ctx.interpreter.stack.push(U256::ZERO) {
80 ctx.interpreter.halt(InstructionResult::StackOverflow);
81 }
82 return;
83 };
84
85 let diff_u64: u64 = diff.try_into().unwrap_or(u64::MAX);
86 if diff_u64 == 0 || diff_u64 > 256 {
87 if !ctx.interpreter.stack.push(U256::ZERO) {
88 ctx.interpreter.halt(InstructionResult::StackOverflow);
89 }
90 return;
91 }
92
93 let requested_u64: u64 = requested.try_into().unwrap_or(u64::MAX);
94 match ctx.host.block_hash(requested_u64) {
95 Some(hash) => {
96 if !ctx.interpreter.stack.push(U256::from_be_bytes(hash.0)) {
97 ctx.interpreter.halt(InstructionResult::StackOverflow);
98 }
99 }
100 None => {
101 ctx.interpreter.halt_fatal();
102 }
103 }
104}
105
106fn arb_balance<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
114 let addr_u256 = match ctx.interpreter.stack.pop() {
116 Some(v) => v,
117 None => {
118 ctx.interpreter
119 .halt(revm::interpreter::InstructionResult::StackUnderflow);
120 return;
121 }
122 };
123
124 let addr = alloy_primitives::Address::from_word(alloy_primitives::B256::from(
125 addr_u256.to_be_bytes::<32>(),
126 ));
127
128 let spec_id = ctx.interpreter.runtime_flag.spec_id();
130 if spec_id.is_enabled_in(revm::primitives::hardfork::SpecId::BERLIN) {
131 let Some(state_load) = ctx.host.balance(addr) else {
133 ctx.interpreter.halt_fatal();
134 return;
135 };
136 let gas_cost = if state_load.is_cold { 2600u64 } else { 100u64 };
138 if !ctx.interpreter.gas.record_cost(gas_cost) {
139 ctx.interpreter
140 .halt(revm::interpreter::InstructionResult::OutOfGas);
141 return;
142 }
143
144 let balance = if addr == arb_precompiles::get_current_tx_sender() {
146 state_load
147 .data
148 .saturating_sub(arb_precompiles::get_poster_balance_correction())
149 } else {
150 state_load.data
151 };
152
153 if !ctx.interpreter.stack.push(balance) {
154 ctx.interpreter
155 .halt(revm::interpreter::InstructionResult::StackOverflow);
156 }
157 } else {
158 let Some(state_load) = ctx.host.balance(addr) else {
160 ctx.interpreter.halt_fatal();
161 return;
162 };
163
164 let balance = if addr == arb_precompiles::get_current_tx_sender() {
165 state_load
166 .data
167 .saturating_sub(arb_precompiles::get_poster_balance_correction())
168 } else {
169 state_load.data
170 };
171
172 if !ctx.interpreter.stack.push(balance) {
173 ctx.interpreter
174 .halt(revm::interpreter::InstructionResult::StackOverflow);
175 }
176 }
177}
178
179const SELFBALANCE_OPCODE: u8 = 0x47;
181
182fn arb_selfbalance<WIRE: InterpreterTypes, H: Host + ?Sized>(ctx: InstructionContext<'_, H, WIRE>) {
185 let target = ctx.interpreter.input.target_address();
186
187 let Some(state_load) = ctx.host.balance(target) else {
188 ctx.interpreter.halt_fatal();
189 return;
190 };
191
192 let balance = if target == arb_precompiles::get_current_tx_sender() {
194 state_load
195 .data
196 .saturating_sub(arb_precompiles::get_poster_balance_correction())
197 } else {
198 state_load.data
199 };
200
201 if !ctx.interpreter.stack.push(balance) {
202 ctx.interpreter
203 .halt(revm::interpreter::InstructionResult::StackOverflow);
204 }
205}
206
207fn arb_blob_basefee<WIRE: InterpreterTypes, H: Host + ?Sized>(
209 ctx: InstructionContext<'_, H, WIRE>,
210) {
211 ctx.interpreter.halt(InstructionResult::OpcodeNotFound);
212}
213
214fn arb_selfdestruct<WIRE: InterpreterTypes, H: Host + ?Sized>(
217 ctx: InstructionContext<'_, H, WIRE>,
218) {
219 if ctx.interpreter.runtime_flag.is_static() {
220 ctx.interpreter
221 .halt(InstructionResult::StateChangeDuringStaticCall);
222 return;
223 }
224
225 let acting_addr = ctx.interpreter.input.target_address();
227 match ctx.host.load_account_code(acting_addr) {
228 Some(code_load) => {
229 if arb_stylus::is_stylus_program(&code_load.data) {
230 ctx.interpreter.halt(InstructionResult::Revert);
231 return;
232 }
233 }
234 None => {
235 ctx.interpreter.halt_fatal();
236 return;
237 }
238 }
239
240 let Some(raw) = ctx.interpreter.stack.pop() else {
244 ctx.interpreter.halt(InstructionResult::StackUnderflow);
245 return;
246 };
247 let target = Address::from_word(alloy_primitives::B256::from(raw.to_be_bytes()));
248
249 let spec = ctx.interpreter.runtime_flag.spec_id();
250 let cold_load_gas = ctx.host.gas_params().selfdestruct_cold_cost();
251 let skip_cold_load = ctx.interpreter.gas.remaining() < cold_load_gas;
252
253 let res = match ctx.host.selfdestruct(acting_addr, target, skip_cold_load) {
254 Ok(res) => res,
255 Err(LoadError::ColdLoadSkipped) => {
256 ctx.interpreter.halt_oog();
257 return;
258 }
259 Err(LoadError::DBError) => {
260 ctx.interpreter.halt_fatal();
261 return;
262 }
263 };
264
265 let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
267 res.had_value && !res.target_exists
268 } else {
269 !res.target_exists
270 };
271
272 let gas_cost = ctx
273 .host
274 .gas_params()
275 .selfdestruct_cost(should_charge_topup, res.is_cold);
276 if !ctx.interpreter.gas.record_cost(gas_cost) {
277 ctx.interpreter.halt_oog();
278 return;
279 }
280
281 if !res.previously_destroyed {
282 ctx.interpreter
283 .gas
284 .record_refund(ctx.host.gas_params().selfdestruct_refund());
285 }
286
287 ctx.interpreter.halt(InstructionResult::SelfDestruct);
288}
289
290pub use arb_stylus::pages::{
296 add_stylus_pages, get_stylus_pages, pop_stylus_program, push_stylus_program,
297 reset_stylus_pages, set_stylus_pages_open,
298};
299
300use arb_precompiles::storage_slot::{
303 derive_subspace_key, map_slot, map_slot_b256, ARBOS_STATE_ADDRESS, PROGRAMS_DATA_KEY,
304 PROGRAMS_PARAMS_KEY, PROGRAMS_SUBSPACE, ROOT_STORAGE_KEY,
305};
306use arbos::programs::{memory::MemoryModel, params::StylusParams, Program};
307
308fn sload_arbos<DB: Database>(journal: &mut revm::Journal<DB>, slot: U256) -> Option<U256> {
310 let _ = journal
311 .inner
312 .load_account(&mut journal.database, ARBOS_STATE_ADDRESS)
313 .ok()?;
314 let result = journal
315 .inner
316 .sload(&mut journal.database, ARBOS_STATE_ADDRESS, slot, false)
317 .ok()?;
318 Some(result.data)
319}
320
321fn read_params_word<DB: Database>(journal: &mut revm::Journal<DB>) -> Option<[u8; 32]> {
323 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
324 let params_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_PARAMS_KEY);
325 let slot = map_slot(params_key.as_slice(), 0);
326 sload_arbos(journal, slot).map(|v| v.to_be_bytes::<32>())
327}
328
329fn read_program_word<DB: Database>(
331 journal: &mut revm::Journal<DB>,
332 code_hash: B256,
333) -> Option<B256> {
334 let programs_key = derive_subspace_key(ROOT_STORAGE_KEY, PROGRAMS_SUBSPACE);
335 let data_key = derive_subspace_key(programs_key.as_slice(), PROGRAMS_DATA_KEY);
336 let slot = map_slot_b256(data_key.as_slice(), &code_hash);
337 sload_arbos(journal, slot).map(|v| B256::from(v.to_be_bytes::<32>()))
338}
339
340fn parse_stylus_params(word: &[u8; 32], arbos_version: u64) -> StylusParams {
343 StylusParams {
344 arbos_version,
345 version: u16::from_be_bytes([word[0], word[1]]),
346 ink_price: (word[2] as u32) << 16 | (word[3] as u32) << 8 | word[4] as u32,
347 max_stack_depth: u32::from_be_bytes([word[5], word[6], word[7], word[8]]),
348 free_pages: u16::from_be_bytes([word[9], word[10]]),
349 page_gas: u16::from_be_bytes([word[11], word[12]]),
350 page_ramp: arbos::programs::params::INITIAL_PAGE_RAMP,
351 page_limit: u16::from_be_bytes([word[13], word[14]]),
352 min_init_gas: word[15],
353 min_cached_init_gas: word[16],
354 init_cost_scalar: word[17],
355 cached_cost_scalar: word[18],
356 expiry_days: u16::from_be_bytes([word[19], word[20]]),
357 keepalive_days: u16::from_be_bytes([word[21], word[22]]),
358 block_cache_size: u16::from_be_bytes([word[23], word[24]]),
359 max_wasm_size: 0,
361 max_fragment_count: 0,
362 }
363}
364
365fn stylus_call_gas_cost(params: &StylusParams, program: &Program, pages_open: u16) -> u64 {
367 let model = MemoryModel::new(params.free_pages, params.page_gas);
368 let mut cost = model.gas_cost(program.footprint, pages_open, pages_open);
369
370 let cached = program.cached;
371 if cached || program.version > 1 {
372 cost = cost.saturating_add(program.cached_gas(params));
373 }
374 if !cached {
375 cost = cost.saturating_add(program.init_gas(params));
376 }
377 cost
378}
379
380use arb_stylus::evm_api_impl::{SubCallResult, SubCreateResult};
383
384fn stylus_call_trampoline<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
390 ctx: *mut (),
391 call_type: u8,
392 contract: Address,
393 caller: Address,
394 input: &[u8],
395 gas: u64,
396 value: U256,
397) -> SubCallResult
398where
399 BlockEnv: revm::context::Block,
400 TxEnv: revm::context::Transaction,
401 CfgEnv: revm::context::Cfg,
402 DB: Database,
403{
404 let context = unsafe {
405 &mut *(ctx as *mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>)
406 };
407
408 let is_static = call_type == 2;
409 let is_delegate = call_type == 1;
410
411 let checkpoint = context.journaled_state.inner.checkpoint();
413
414 if !is_delegate && !value.is_zero() {
416 let transfer_result = context.journaled_state.inner.transfer(
417 &mut context.journaled_state.database,
418 caller,
419 contract,
420 value,
421 );
422 if transfer_result.is_err() {
423 context.journaled_state.inner.checkpoint_revert(checkpoint);
424 return SubCallResult {
425 output: Vec::new(),
426 gas_cost: 0,
427 success: false,
428 };
429 }
430 }
431
432 let code_address = contract;
434
435 let bytecode = match context
437 .journaled_state
438 .inner
439 .load_code(&mut context.journaled_state.database, code_address)
440 {
441 Ok(acc) => acc
442 .data
443 .info
444 .code
445 .as_ref()
446 .map(|c| c.original_bytes())
447 .unwrap_or_default(),
448 Err(_) => {
449 context.journaled_state.inner.checkpoint_revert(checkpoint);
450 return SubCallResult {
451 output: Vec::new(),
452 gas_cost: 0,
453 success: false,
454 };
455 }
456 };
457
458 if bytecode.is_empty() {
460 context.journaled_state.inner.checkpoint_commit();
461 return SubCallResult {
462 output: Vec::new(),
463 gas_cost: 0,
464 success: true,
465 };
466 }
467
468 let target_address = if is_delegate { caller } else { contract };
470
471 let call_scheme = match call_type {
473 0 => CallScheme::Call,
474 1 => CallScheme::DelegateCall,
475 2 => CallScheme::StaticCall,
476 _ => CallScheme::Call,
477 };
478
479 let call_value = if is_delegate {
480 revm::interpreter::CallValue::Apparent(value)
481 } else {
482 revm::interpreter::CallValue::Transfer(value)
483 };
484
485 let sub_inputs = CallInputs {
486 input: CallInput::Bytes(input.to_vec().into()),
487 gas_limit: gas,
488 target_address,
489 bytecode_address: code_address,
490 caller,
491 value: call_value,
492 scheme: call_scheme,
493 is_static,
494 return_memory_offset: 0..0,
495 known_bytecode: None,
496 };
497
498 {
500 arb_precompiles::set_evm_depth(context.journaled_state.inner.depth);
501 let mut precompiles = alloy_evm::precompiles::PrecompilesMap::new(Default::default());
502 <alloy_evm::precompiles::PrecompilesMap as PrecompileProvider<
503 revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
504 >>::set_spec(&mut precompiles, context.cfg.spec());
505 register_arb_precompiles(&mut precompiles);
506 let mut arb_map = ArbPrecompilesMap(precompiles);
507 let dispatch_result = <ArbPrecompilesMap as PrecompileProvider<
508 revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
509 >>::run(&mut arb_map, context, &sub_inputs);
510
511 match dispatch_result {
512 Ok(Some(result)) => {
513 let success = result.result.is_ok();
514 let output = result.output.to_vec();
515 let gas_used = gas.saturating_sub(result.gas.remaining());
516 if success {
517 context.journaled_state.inner.checkpoint_commit();
518 } else {
519 context.journaled_state.inner.checkpoint_revert(checkpoint);
520 }
521 return SubCallResult {
522 output,
523 gas_cost: gas_used,
524 success,
525 };
526 }
527 Ok(None) => {
528 }
530 Err(_) => {
531 context.journaled_state.inner.checkpoint_revert(checkpoint);
532 return SubCallResult {
533 output: Vec::new(),
534 gas_cost: 0,
535 success: false,
536 };
537 }
538 }
539 }
540
541 let result = run_evm_bytecode(context, &sub_inputs, &bytecode, gas);
543 let success = result.result.is_ok();
544 let output = result.output.to_vec();
545 let gas_used = gas.saturating_sub(result.gas.remaining());
546 if success {
547 context.journaled_state.inner.checkpoint_commit();
548 } else {
549 context.journaled_state.inner.checkpoint_revert(checkpoint);
550 }
551 SubCallResult {
552 output,
553 gas_cost: gas_used,
554 success,
555 }
556}
557
558fn stylus_create_trampoline<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
560 ctx: *mut (),
561 caller: Address,
562 code: &[u8],
563 gas: u64,
564 endowment: U256,
565 salt: Option<B256>,
566) -> SubCreateResult
567where
568 BlockEnv: revm::context::Block,
569 TxEnv: revm::context::Transaction,
570 CfgEnv: revm::context::Cfg,
571 DB: Database,
572{
573 let context = unsafe {
574 &mut *(ctx as *mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>)
575 };
576
577 let checkpoint = context.journaled_state.inner.checkpoint();
578
579 let caller_nonce = {
581 let acc = context
582 .journaled_state
583 .inner
584 .load_account(&mut context.journaled_state.database, caller);
585 acc.map(|a| a.data.info.nonce).unwrap_or(0)
586 };
587
588 let created_address = if let Some(salt) = salt {
589 let code_hash = alloy_primitives::keccak256(code);
591 let mut buf = Vec::with_capacity(1 + 20 + 32 + 32);
592 buf.push(0xff);
593 buf.extend_from_slice(caller.as_slice());
594 buf.extend_from_slice(salt.as_slice());
595 buf.extend_from_slice(code_hash.as_slice());
596 Address::from_slice(&alloy_primitives::keccak256(&buf)[12..])
597 } else {
598 use alloy_rlp::Encodable;
600 let mut rlp_buf = Vec::with_capacity(64);
601 alloy_rlp::Header {
602 list: true,
603 payload_length: caller.length() + caller_nonce.length(),
604 }
605 .encode(&mut rlp_buf);
606 caller.encode(&mut rlp_buf);
607 caller_nonce.encode(&mut rlp_buf);
608 Address::from_slice(&alloy_primitives::keccak256(&rlp_buf)[12..])
609 };
610
611 let _ = context
613 .journaled_state
614 .inner
615 .load_account(&mut context.journaled_state.database, caller);
616 if let Some(acc) = context.journaled_state.inner.state.get_mut(&caller) {
617 acc.info.nonce += 1;
618 context
619 .journaled_state
620 .inner
621 .nonce_bump_journal_entry(caller);
622 }
623
624 if !endowment.is_zero()
626 && context
627 .journaled_state
628 .inner
629 .transfer(
630 &mut context.journaled_state.database,
631 caller,
632 created_address,
633 endowment,
634 )
635 .is_err()
636 {
637 context.journaled_state.inner.checkpoint_revert(checkpoint);
638 return SubCreateResult {
639 address: None,
640 output: Vec::new(),
641 gas_cost: gas,
642 };
643 }
644
645 let init_inputs = CallInputs {
647 input: CallInput::Bytes(code.to_vec().into()),
648 gas_limit: gas,
649 target_address: created_address,
650 bytecode_address: created_address,
651 caller,
652 value: revm::interpreter::CallValue::Transfer(endowment),
653 scheme: CallScheme::Call,
654 is_static: false,
655 return_memory_offset: 0..0,
656 known_bytecode: None,
657 };
658
659 let result = run_evm_bytecode(context, &init_inputs, code, gas);
660 let success = result.result.is_ok();
661 let gas_used = gas.saturating_sub(result.gas.remaining());
662
663 if success {
664 let deployed_code = result.output.to_vec();
666 let code_hash = alloy_primitives::keccak256(&deployed_code);
667 let bytecode = revm::bytecode::Bytecode::new_raw(deployed_code.into());
668 let _ = context
670 .journaled_state
671 .inner
672 .load_account(&mut context.journaled_state.database, created_address);
673 context
674 .journaled_state
675 .inner
676 .set_code_with_hash(created_address, bytecode, code_hash);
677 context.journaled_state.inner.checkpoint_commit();
678 SubCreateResult {
679 address: Some(created_address),
680 output: Vec::new(), gas_cost: gas_used,
682 }
683 } else {
684 let output = result.output.to_vec();
685 context.journaled_state.inner.checkpoint_revert(checkpoint);
686 SubCreateResult {
687 address: None,
688 output, gas_cost: gas_used,
690 }
691 }
692}
693
694fn run_evm_bytecode<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
700 context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
701 inputs: &CallInputs,
702 bytecode: &[u8],
703 gas_limit: u64,
704) -> InterpreterResult
705where
706 BlockEnv: revm::context::Block,
707 TxEnv: revm::context::Transaction,
708 CfgEnv: revm::context::Cfg,
709 DB: Database,
710{
711 use revm::{
712 bytecode::Bytecode,
713 interpreter::{
714 interpreter::{ExtBytecode, InputsImpl},
715 FrameInput, InterpreterAction, SharedMemory,
716 },
717 };
718
719 let code = Bytecode::new_raw(bytecode.to_vec().into());
720 let ext_bytecode = ExtBytecode::new(code);
721
722 let call_value = inputs.value.get();
723 let interp_input = InputsImpl {
724 target_address: inputs.target_address,
725 bytecode_address: Some(inputs.bytecode_address),
726 caller_address: inputs.caller,
727 input: inputs.input.clone(),
728 call_value,
729 };
730
731 let spec = context.cfg.spec();
732
733 let mut interpreter = revm::interpreter::Interpreter::new(
734 SharedMemory::new(),
735 ext_bytecode,
736 interp_input,
737 inputs.is_static,
738 spec.clone().into(),
739 gas_limit,
740 );
741
742 type Ctx<B, T, C, D, Ch> = revm::Context<B, T, C, D, revm::Journal<D>, Ch>;
744 let mut instructions = EthInstructions::<
745 EthInterpreter,
746 Ctx<BlockEnv, TxEnv, CfgEnv, DB, Chain>,
747 >::new_mainnet_with_spec(spec.into());
748 instructions.insert_instruction(
749 BLOBBASEFEE_OPCODE,
750 revm::interpreter::Instruction::new(arb_blob_basefee, 2),
751 );
752 instructions.insert_instruction(
753 SELFDESTRUCT_OPCODE,
754 revm::interpreter::Instruction::new(arb_selfdestruct, 5000),
755 );
756
757 loop {
759 let action = interpreter.run_plain(&instructions.instruction_table, context);
760
761 match action {
762 InterpreterAction::Return(result) => {
763 return result;
764 }
765 InterpreterAction::NewFrame(FrameInput::Call(sub_call)) => {
766 let sub_result = stylus_call_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
768 context as *mut _ as *mut (),
769 match sub_call.scheme {
770 CallScheme::Call | CallScheme::CallCode => 0,
771 CallScheme::DelegateCall => 1,
772 CallScheme::StaticCall => 2,
773 },
774 sub_call.target_address,
775 sub_call.caller,
776 match &sub_call.input {
777 CallInput::Bytes(b) => b,
778 CallInput::SharedBuffer(_) => &[],
779 },
780 sub_call.gas_limit,
781 sub_call.value.get(),
782 );
783
784 let gas_remaining = sub_call.gas_limit.saturating_sub(sub_result.gas_cost);
786 let ins_result = if sub_result.success {
787 InstructionResult::Return
788 } else {
789 InstructionResult::Revert
790 };
791
792 let output: Bytes = sub_result.output.into();
793 let returned_len = output.len();
794 let mem_start = sub_call.return_memory_offset.start;
795 let mem_length = sub_call.return_memory_offset.len();
796 let target_len = mem_length.min(returned_len);
797
798 interpreter.return_data.set_buffer(output);
799
800 let item = if ins_result.is_ok() {
801 U256::from(1)
802 } else {
803 U256::ZERO
804 };
805 let _ = interpreter.stack.push(item);
806
807 if ins_result.is_ok_or_revert() {
808 interpreter.gas.erase_cost(gas_remaining);
809 if target_len > 0 {
810 interpreter
811 .memory
812 .set(mem_start, &interpreter.return_data.buffer()[..target_len]);
813 }
814 }
815
816 if ins_result.is_ok() {
817 }
819 }
820 InterpreterAction::NewFrame(FrameInput::Create(sub_create)) => {
821 let salt = match sub_create.scheme() {
823 revm::interpreter::CreateScheme::Create2 { salt } => {
824 Some(B256::from(salt.to_be_bytes()))
825 }
826 _ => None,
827 };
828
829 let sub_result = stylus_create_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
830 context as *mut _ as *mut (),
831 sub_create.caller(),
832 sub_create.init_code(),
833 sub_create.gas_limit(),
834 sub_create.value(),
835 salt,
836 );
837
838 let gas_remaining = sub_create.gas_limit().saturating_sub(sub_result.gas_cost);
839 let created_addr = sub_result.address;
840
841 let ins_result = if created_addr.is_some() {
842 InstructionResult::Return
843 } else if !sub_result.output.is_empty() {
844 InstructionResult::Revert
845 } else {
846 InstructionResult::CreateInitCodeStartingEF00
847 };
848
849 let output: Bytes = sub_result.output.into();
850 interpreter.return_data.set_buffer(output);
851
852 let item = match created_addr {
854 Some(addr) => addr.into_word().into(),
855 None => U256::ZERO,
856 };
857 let _ = interpreter.stack.push(item);
858
859 if ins_result.is_ok_or_revert() {
860 interpreter.gas.erase_cost(gas_remaining);
861 }
862 }
863 InterpreterAction::NewFrame(FrameInput::Empty) => {
864 return InterpreterResult::new(
866 InstructionResult::Revert,
867 Bytes::new(),
868 EvmGas::new(0),
869 );
870 }
871 }
872 }
873}
874
875fn execute_stylus_program<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
882 context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
883 inputs: &CallInputs,
884 bytecode: &[u8],
885) -> InterpreterResult
886where
887 BlockEnv: revm::context::Block,
888 TxEnv: revm::context::Transaction,
889 CfgEnv: revm::context::Cfg,
890 DB: Database,
891{
892 use arbos::programs::types::UserOutcome;
893
894 let zero_gas = || EvmGas::new(0);
895
896 let (module_bytes, _version_byte) = match arb_stylus::strip_stylus_prefix(bytecode) {
898 Ok(v) => v,
899 Err(_) => {
900 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
901 }
902 };
903
904 let code_hash = alloy_primitives::keccak256(bytecode);
905 let arbos_version = arb_precompiles::get_arbos_version();
906 let block_timestamp = arb_precompiles::get_block_timestamp();
907
908 let params_word = match read_params_word(&mut context.journaled_state) {
910 Some(w) => w,
911 None => {
912 tracing::warn!(target: "stylus", "failed to read StylusParams from storage");
913 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
914 }
915 };
916 let params = parse_stylus_params(¶ms_word, arbos_version);
917
918 let program_word = match read_program_word(&mut context.journaled_state, code_hash) {
919 Some(w) => w,
920 None => {
921 tracing::warn!(target: "stylus", codehash = %code_hash, "failed to read program data");
922 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
923 }
924 };
925 let program = Program::from_storage(program_word, block_timestamp);
926
927 if program.version == 0 || program.version != params.version {
929 tracing::warn!(target: "stylus", codehash = %code_hash, program_ver = program.version, params_ver = params.version, "program version mismatch");
930 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
931 }
932 let expiry_seconds = (params.expiry_days as u64) * 24 * 3600;
933 if program.age_seconds > expiry_seconds {
934 tracing::warn!(target: "stylus", codehash = %code_hash, "program expired");
935 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
936 }
937
938 let (pages_open, _pages_ever) = get_stylus_pages();
940 let upfront_cost = stylus_call_gas_cost(¶ms, &program, pages_open);
941 let total_gas = inputs.gas_limit;
942
943 if total_gas < upfront_cost {
944 return InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), zero_gas());
945 }
946 let gas_for_wasm = total_gas - upfront_cost;
947
948 let stylus_config = StylusConfig::new(params.version, params.max_stack_depth, params.ink_price);
949
950 let target_addr = inputs.target_address;
952 let is_delegate = matches!(
953 inputs.scheme,
954 CallScheme::DelegateCall | CallScheme::CallCode
955 );
956 let reentrant = if !is_delegate {
957 push_stylus_program(target_addr)
958 } else {
959 false
960 };
961
962 let mut evm_data = build_evm_data(context, inputs);
964 evm_data.reentrant = reentrant as u32;
965 evm_data.cached = program.cached;
966 evm_data.module_hash = code_hash;
967
968 let (prev_open, _prev_ever) = add_stylus_pages(program.footprint);
970
971 let journal_ptr = &mut context.journaled_state as *mut revm::Journal<DB>;
973 let is_static = inputs.is_static || matches!(inputs.scheme, CallScheme::StaticCall);
974 let ctx_ptr = context as *mut _ as *mut ();
975 let caller = inputs.caller;
976 let call_value = inputs.value.get();
977 let evm_api = unsafe {
978 StylusEvmApi::new(
979 journal_ptr,
980 target_addr,
981 caller,
982 call_value,
983 is_static,
984 params.free_pages,
985 params.page_gas,
986 ctx_ptr,
987 Some(stylus_call_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>),
988 Some(stylus_create_trampoline::<BlockEnv, TxEnv, CfgEnv, DB, Chain>),
989 )
990 };
991
992 let long_term_tag = if program.cached { 1u32 } else { 0u32 };
994 let mut instance = if let Some((module, store)) =
995 arb_stylus::cache::InitCache::get(code_hash, params.version, long_term_tag, false)
996 {
997 let compile = arb_stylus::CompileConfig::version(params.version, false);
998 let env = arb_stylus::env::WasmEnv::new(compile, Some(stylus_config), evm_api, evm_data);
999 match arb_stylus::NativeInstance::from_module(module, store, env) {
1000 Ok(inst) => inst,
1001 Err(e) => {
1002 tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "failed from cached module");
1003 set_stylus_pages_open(prev_open);
1004 if !is_delegate {
1005 pop_stylus_program(target_addr);
1006 }
1007 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1008 }
1009 }
1010 } else {
1011 let compile = arb_stylus::CompileConfig::version(params.version, false);
1013 match arb_stylus::NativeInstance::from_bytes(
1014 module_bytes,
1015 evm_api,
1016 evm_data,
1017 &compile,
1018 stylus_config,
1019 ) {
1020 Ok(inst) => inst,
1021 Err(e) => {
1022 tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "failed to compile WASM");
1023 set_stylus_pages_open(prev_open);
1024 if !is_delegate {
1025 pop_stylus_program(target_addr);
1026 }
1027 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1028 }
1029 }
1030 };
1031
1032 let ink = stylus_config.pricing.gas_to_ink(StylusGas(gas_for_wasm));
1034
1035 let calldata: &[u8] = match &inputs.input {
1037 CallInput::Bytes(bytes) => bytes,
1038 CallInput::SharedBuffer(_) => &[],
1039 };
1040
1041 let outcome = match instance.run_main(calldata, stylus_config, ink) {
1043 Ok(outcome) => outcome,
1044 Err(e) => {
1045 tracing::warn!(target: "stylus", codehash = %code_hash, err = %e, "WASM execution failed");
1046 set_stylus_pages_open(prev_open);
1047 if !is_delegate {
1048 pop_stylus_program(target_addr);
1049 }
1050 return InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas());
1051 }
1052 };
1053
1054 set_stylus_pages_open(prev_open);
1056 if !is_delegate {
1057 pop_stylus_program(target_addr);
1058 }
1059
1060 let ink_left = match instance.ink_left() {
1062 arb_stylus::MachineMeter::Ready(ink_val) => ink_val,
1063 arb_stylus::MachineMeter::Exhausted => arb_stylus::Ink(0),
1064 };
1065 let gas_left = stylus_config.pricing.ink_to_gas(ink_left).0;
1066
1067 let output: Bytes = instance.env().outs.clone().into();
1069 let gas_left = if !output.is_empty()
1070 && arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS_FIXES
1071 {
1072 let evm_cost = arbos::programs::types::evm_memory_cost(output.len() as u64);
1073 if total_gas < evm_cost {
1074 0
1075 } else {
1076 gas_left.min(total_gas - evm_cost)
1077 }
1078 } else {
1079 gas_left
1080 };
1081
1082 let gas_result = EvmGas::new(gas_left);
1083
1084 match outcome {
1086 UserOutcome::Success => {
1087 InterpreterResult::new(InstructionResult::Return, output, gas_result)
1088 }
1089 UserOutcome::Revert => {
1090 InterpreterResult::new(InstructionResult::Revert, output, gas_result)
1091 }
1092 UserOutcome::OutOfInk => {
1093 InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), zero_gas())
1094 }
1095 UserOutcome::OutOfStack => {
1096 InterpreterResult::new(InstructionResult::CallTooDeep, Bytes::new(), zero_gas())
1097 }
1098 UserOutcome::Failure => {
1099 InterpreterResult::new(InstructionResult::Revert, Bytes::new(), zero_gas())
1100 }
1101 }
1102}
1103
1104fn build_evm_data<BlockEnv, TxEnv, CfgEnv, DB, Chain>(
1106 context: &revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1107 inputs: &CallInputs,
1108) -> EvmData
1109where
1110 BlockEnv: revm::context::Block,
1111 TxEnv: revm::context::Transaction,
1112 CfgEnv: revm::context::Cfg,
1113 DB: Database,
1114{
1115 let basefee = U256::from(context.block.basefee());
1116 let gas_price = U256::from(context.tx.gas_price());
1117 let value = inputs.value.get();
1118
1119 EvmData {
1120 arbos_version: arb_precompiles::get_arbos_version(),
1121 block_basefee: B256::from(basefee.to_be_bytes()),
1122 chain_id: context.cfg.chain_id(),
1123 block_coinbase: context.block.beneficiary(),
1124 block_gas_limit: context.block.gas_limit(),
1125 block_number: context.block.number().saturating_to(),
1126 block_timestamp: context.block.timestamp().saturating_to(),
1127 contract_address: inputs.target_address,
1128 module_hash: alloy_primitives::keccak256(b""),
1129 msg_sender: inputs.caller,
1130 msg_value: B256::from(value.to_be_bytes()),
1131 tx_gas_price: B256::from(gas_price.to_be_bytes()),
1132 tx_origin: context.tx.caller(),
1133 reentrant: 0,
1134 cached: false,
1135 tracing: false,
1136 }
1137}
1138
1139#[derive(Clone, Debug)]
1145pub struct ArbPrecompilesMap(pub PrecompilesMap);
1146
1147impl<BlockEnv, TxEnv, CfgEnv, DB, Chain>
1148 PrecompileProvider<revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>>
1149 for ArbPrecompilesMap
1150where
1151 BlockEnv: revm::context::Block,
1152 TxEnv: revm::context::Transaction,
1153 CfgEnv: revm::context::Cfg,
1154 DB: Database,
1155{
1156 type Output = InterpreterResult;
1157
1158 fn set_spec(&mut self, spec: CfgEnv::Spec) -> bool {
1159 <PrecompilesMap as PrecompileProvider<
1160 revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1161 >>::set_spec(&mut self.0, spec)
1162 }
1163
1164 fn run(
1165 &mut self,
1166 context: &mut revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1167 inputs: &CallInputs,
1168 ) -> Result<Option<Self::Output>, String> {
1169 arb_precompiles::set_evm_depth(context.journaled_state.inner.depth);
1171
1172 if let result @ Some(_) = <PrecompilesMap as PrecompileProvider<
1174 revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1175 >>::run(&mut self.0, context, inputs)?
1176 {
1177 return Ok(result);
1178 }
1179
1180 let arbos_version = arb_precompiles::get_arbos_version();
1182 if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_STYLUS {
1183 let code_opt = context
1185 .journaled_state
1186 .inner
1187 .load_code(
1188 &mut context.journaled_state.database,
1189 inputs.bytecode_address,
1190 )
1191 .ok()
1192 .and_then(|acc| acc.data.info.code.as_ref().map(|c| c.original_bytes()));
1193
1194 if let Some(bytecode) = code_opt {
1195 if arb_stylus::is_stylus_program(&bytecode) {
1196 return Ok(Some(execute_stylus_program(context, inputs, &bytecode)));
1197 }
1198 }
1199 }
1200
1201 Ok(None)
1202 }
1203
1204 fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
1205 <PrecompilesMap as PrecompileProvider<
1206 revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1207 >>::warm_addresses(&self.0)
1208 }
1209
1210 fn contains(&self, address: &Address) -> bool {
1211 <PrecompilesMap as PrecompileProvider<
1212 revm::Context<BlockEnv, TxEnv, CfgEnv, DB, revm::Journal<DB>, Chain>,
1213 >>::contains(&self.0, address)
1214 }
1215}
1216
1217pub struct ArbEvm<DB: Database, I> {
1221 inner: alloy_evm::EthEvm<DB, I, ArbPrecompilesMap>,
1222}
1223
1224impl<DB, I> ArbEvm<DB, I>
1225where
1226 DB: Database,
1227{
1228 pub fn new(inner: alloy_evm::EthEvm<DB, I, ArbPrecompilesMap>) -> Self {
1229 Self { inner }
1230 }
1231
1232 pub fn into_inner(self) -> alloy_evm::EthEvm<DB, I, ArbPrecompilesMap> {
1233 self.inner
1234 }
1235}
1236
1237impl<DB, I> Evm for ArbEvm<DB, I>
1238where
1239 DB: Database,
1240 I: revm::inspector::Inspector<EthEvmContext<DB>>,
1241{
1242 type DB = DB;
1243 type Tx = ArbTransaction;
1244 type Error = EVMError<<DB as revm::Database>::Error>;
1245 type HaltReason = HaltReason;
1246 type Spec = SpecId;
1247 type Precompiles = PrecompilesMap;
1248 type Inspector = I;
1249 type BlockEnv = revm::context::BlockEnv;
1250
1251 fn block(&self) -> &revm::context::BlockEnv {
1252 self.inner.block()
1253 }
1254
1255 fn chain_id(&self) -> u64 {
1256 self.inner.chain_id()
1257 }
1258
1259 fn transact_raw(
1260 &mut self,
1261 tx: Self::Tx,
1262 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
1263 self.inner.transact_raw(tx.into_inner())
1264 }
1265
1266 fn transact_system_call(
1267 &mut self,
1268 caller: Address,
1269 contract: Address,
1270 data: Bytes,
1271 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
1272 self.inner.transact_system_call(caller, contract, data)
1273 }
1274
1275 fn finish(self) -> (Self::DB, EvmEnv<Self::Spec>) {
1276 self.inner.finish()
1277 }
1278
1279 fn set_inspector_enabled(&mut self, enabled: bool) {
1280 self.inner.set_inspector_enabled(enabled)
1281 }
1282
1283 fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
1284 let (db, inspector, arb_precompiles) = self.inner.components();
1285 (db, inspector, &arb_precompiles.0)
1286 }
1287
1288 fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
1289 let (db, inspector, arb_precompiles) = self.inner.components_mut();
1290 (db, inspector, &mut arb_precompiles.0)
1291 }
1292}
1293
1294#[derive(Default, Debug, Clone, Copy)]
1298pub struct ArbEvmFactory(pub alloy_evm::EthEvmFactory);
1299
1300impl ArbEvmFactory {
1301 pub fn new() -> Self {
1302 Self::default()
1303 }
1304}
1305
1306fn build_arb_evm<DB: Database, I>(
1307 inner: revm::context::Evm<
1308 EthEvmContext<DB>,
1309 I,
1310 EthInstructions<EthInterpreter, EthEvmContext<DB>>,
1311 PrecompilesMap,
1312 EthFrame,
1313 >,
1314 inspect: bool,
1315) -> ArbEvm<DB, I> {
1316 let revm::context::Evm {
1317 ctx,
1318 inspector,
1319 mut instruction,
1320 mut precompiles,
1321 frame_stack: _,
1322 } = inner;
1323
1324 instruction.insert_instruction(
1325 BLOBBASEFEE_OPCODE,
1326 revm::interpreter::Instruction::new(arb_blob_basefee, 2),
1327 );
1328 instruction.insert_instruction(
1329 SELFDESTRUCT_OPCODE,
1330 revm::interpreter::Instruction::new(arb_selfdestruct, 5000),
1331 );
1332 instruction.insert_instruction(
1335 NUMBER_OPCODE,
1336 revm::interpreter::Instruction::new(arb_number, 2),
1337 );
1338 instruction.insert_instruction(
1341 BLOCKHASH_OPCODE,
1342 revm::interpreter::Instruction::new(arb_blockhash, 20),
1343 );
1344 instruction.insert_instruction(
1349 BALANCE_OPCODE,
1350 revm::interpreter::Instruction::new(arb_balance, 0),
1351 );
1352 instruction.insert_instruction(
1353 SELFBALANCE_OPCODE,
1354 revm::interpreter::Instruction::new(arb_selfbalance, 5),
1355 );
1356 register_arb_precompiles(&mut precompiles);
1357 let arb_precompiles = ArbPrecompilesMap(precompiles);
1358
1359 let revm_evm =
1360 revm::context::Evm::new_with_inspector(ctx, inspector, instruction, arb_precompiles);
1361 let eth_evm = alloy_evm::eth::EthEvm::new(revm_evm, inspect);
1362 ArbEvm::new(eth_evm)
1363}
1364
1365impl EvmFactory for ArbEvmFactory {
1366 type Evm<DB: Database, I: revm::inspector::Inspector<EthEvmContext<DB>>> = ArbEvm<DB, I>;
1367 type Context<DB: Database> = EthEvmContext<DB>;
1368 type Tx = ArbTransaction;
1369 type Error<DBError: core::error::Error + Send + Sync + 'static> = EVMError<DBError>;
1370 type HaltReason = HaltReason;
1371 type Spec = SpecId;
1372 type Precompiles = PrecompilesMap;
1373 type BlockEnv = revm::context::BlockEnv;
1374
1375 fn create_evm<DB: Database>(
1376 &self,
1377 db: DB,
1378 input: EvmEnv<Self::Spec>,
1379 ) -> Self::Evm<DB, NoOpInspector> {
1380 let eth_evm = self.0.create_evm(db, input);
1381 build_arb_evm(eth_evm.into_inner(), false)
1382 }
1383
1384 fn create_evm_with_inspector<DB: Database, I: revm::inspector::Inspector<Self::Context<DB>>>(
1385 &self,
1386 db: DB,
1387 input: EvmEnv<Self::Spec>,
1388 inspector: I,
1389 ) -> Self::Evm<DB, I> {
1390 let eth_evm = self.0.create_evm_with_inspector(db, input, inspector);
1391 build_arb_evm(eth_evm.into_inner(), true)
1392 }
1393}