1use crate::InstructionContext as Ictx;
2use crate::{
3 instructions::utility::{IntoAddress, IntoU256},
4 interpreter_types::{InputsTr, InterpreterTypes as ITy, MemoryTr, RuntimeFlag, StackTr},
5 Gas, Host, InstructionExecResult as Result, InstructionResult,
6};
7use context_interface::{
8 context::{SStoreResult, StateLoad},
9 host::LoadError,
10 journaled_state::AccountInfoLoad,
11};
12use core::cmp::min;
13use primitives::{
14 hardfork::SpecId::{self, *},
15 Address, Bytes, Log, LogData, B256, BLOCK_HASH_HISTORY, U256,
16};
17
18fn load_account<'a, H: Host + ?Sized>(
22 gas: &mut Gas,
23 host: &'a mut H,
24 address: primitives::Address,
25 load_code: bool,
26) -> core::result::Result<AccountInfoLoad<'a>, LoadError> {
27 let cold_load_gas = host.gas_params().cold_account_additional_cost();
28 let skip_cold_load = gas.remaining() < cold_load_gas;
29 let account = host.load_account_info_skip_cold_load(address, load_code, skip_cold_load)?;
30 if account.is_cold && !gas.record_regular_cost(cold_load_gas) {
31 return Err(LoadError::ColdLoadSkipped);
32 }
33 Ok(account)
34}
35
36pub fn balance<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
40 popn_top!([], top, context.interpreter);
41 let address = top.into_address();
42 let account = load_account(&mut context.interpreter.gas, context.host, address, false)?;
43 *top = account.balance;
44 Ok(())
45}
46
47pub fn selfbalance<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
49 check!(context.interpreter, ISTANBUL);
50
51 let balance = context
52 .host
53 .balance(context.interpreter.input.target_address())
54 .ok_or(InstructionResult::FatalExternalError)?;
55 push!(context.interpreter, balance.data);
56 Ok(())
57}
58
59pub fn extcodesize<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
63 popn_top!([], top, context.interpreter);
64 let address = top.into_address();
65 let account = load_account(&mut context.interpreter.gas, context.host, address, true)?;
66 *top = U256::from(account.code.as_ref().unwrap().len());
68 Ok(())
69}
70
71pub fn extcodehash<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
73 check!(context.interpreter, PETERSBURG);
74 popn_top!([], top, context.interpreter);
75 let address = top.into_address();
76 let account = load_account(&mut context.interpreter.gas, context.host, address, false)?;
77 let code_hash = if account.is_empty() {
79 B256::ZERO
80 } else {
81 account.code_hash
82 };
83 *top = code_hash.into_u256();
84 Ok(())
85}
86
87pub fn extcodecopy<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
91 popn!(
92 [address, memory_offset, code_offset, len_u256],
93 context.interpreter
94 );
95 let address = address.into_address();
96
97 let len = as_usize_or_fail!(context.interpreter, len_u256);
98 gas!(
99 context.interpreter,
100 context.host.gas_params().extcodecopy(len)
101 );
102
103 let mut memory_offset_usize = 0;
104 if len != 0 {
106 memory_offset_usize = as_usize_or_fail!(context.interpreter, memory_offset);
108 context
110 .interpreter
111 .resize_memory(context.host.gas_params(), memory_offset_usize, len)?;
112 }
113
114 let account = load_account(&mut context.interpreter.gas, context.host, address, true)?;
115 let code = account.code.as_ref().unwrap().original_bytes();
116
117 let code_offset_usize = min(as_usize_saturated!(code_offset), code.len());
118
119 context
122 .interpreter
123 .memory
124 .set_data(memory_offset_usize, code_offset_usize, len, &code);
125 Ok(())
126}
127
128pub fn blockhash<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
132 popn_top!([], number, context.interpreter);
133
134 let requested_number = *number;
135 let block_number = context.host.block_number();
136
137 let Some(diff) = block_number.checked_sub(requested_number) else {
138 *number = U256::ZERO;
139 return Ok(());
140 };
141
142 let diff = as_u64_saturated!(diff);
143
144 if diff == 0 {
146 *number = U256::ZERO;
147 return Ok(());
148 }
149
150 *number = if diff <= BLOCK_HASH_HISTORY {
151 let hash = context
152 .host
153 .block_hash(as_u64_saturated!(requested_number))
154 .ok_or(InstructionResult::FatalExternalError)?;
155 U256::from_be_bytes(hash.0)
156 } else {
157 U256::ZERO
158 };
159 Ok(())
160}
161
162pub fn sload<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
166 popn_top!([], index, context.interpreter);
167 let spec_id = context.interpreter.runtime_flag.spec_id();
168 let target = context.interpreter.input.target_address();
169
170 if spec_id.is_enabled_in(BERLIN) {
171 let additional_cold_cost = context.host.gas_params().cold_storage_additional_cost();
172 let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost;
173 let storage = context
174 .host
175 .sload_skip_cold_load(target, *index, skip_cold)?;
176 if storage.is_cold {
177 gas!(context.interpreter, additional_cold_cost);
178 }
179 *index = storage.data;
180 } else {
181 let storage = context
182 .host
183 .sload(target, *index)
184 .ok_or(InstructionResult::FatalExternalError)?;
185 *index = storage.data;
186 };
187 Ok(())
188}
189
190pub fn sstore<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
194 sstore_with_gas_accounting(context, sstore_default_gas_accounting)
195}
196
197pub fn sstore_with_gas_accounting<'a, IT, H, F>(
204 mut context: Ictx<'a, H, IT>,
205 gas_accounting: F,
206) -> Result
207where
208 IT: ITy,
209 H: Host + ?Sized,
210 F: for<'ctx, 'load> FnOnce(
211 &'ctx mut Ictx<'a, H, IT>,
212 Address,
213 &'load StateLoad<SStoreResult>,
214 ) -> Result,
215{
216 require_non_staticcall!(context.interpreter);
217 popn!([index, value], context.interpreter);
218
219 let target = context.interpreter.input.target_address();
220 let spec_id = context.interpreter.runtime_flag.spec_id();
221
222 if spec_id.is_enabled_in(ISTANBUL)
225 && context.interpreter.gas.remaining() <= context.host.gas_params().call_stipend()
226 {
227 return Err(InstructionResult::ReentrancySentryOOG);
228 }
229
230 gas!(
231 context.interpreter,
232 context.host.gas_params().sstore_static_gas()
233 );
234
235 let state_load = if spec_id.is_enabled_in(BERLIN) {
236 context
242 .host
243 .sstore_skip_cold_load(target, index, value, false)?
244 } else {
245 context
246 .host
247 .sstore(target, index, value)
248 .ok_or(InstructionResult::FatalExternalError)?
249 };
250
251 gas_accounting(&mut context, target, &state_load)
252}
253
254pub fn sstore_default_gas_accounting<IT, H>(
256 context: &mut Ictx<'_, H, IT>,
257 _target: Address,
258 state_load: &StateLoad<SStoreResult>,
259) -> Result
260where
261 IT: ITy,
262 H: Host + ?Sized,
263{
264 let spec_id = context.interpreter.runtime_flag.spec_id();
265 let is_istanbul = spec_id.is_enabled_in(ISTANBUL);
266
267 gas!(
269 context.interpreter,
270 context.host.gas_params().sstore_dynamic_gas(
271 is_istanbul,
272 &state_load.data,
273 state_load.is_cold
274 )
275 );
276
277 if context.host.is_amsterdam_eip8037_enabled() {
279 state_gas!(
280 context.interpreter,
281 context.host.gas_params().sstore_state_gas(&state_load.data)
282 );
283
284 let refill = context
289 .host
290 .gas_params()
291 .sstore_state_gas_refill(&state_load.data);
292 if refill > 0 {
293 context.interpreter.gas.refill_reservoir(refill);
294 }
295 }
296
297 context.interpreter.gas.record_refund(
299 context
300 .host
301 .gas_params()
302 .sstore_refund(is_istanbul, &state_load.data),
303 );
304 Ok(())
305}
306
307pub fn tstore<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
310 check!(context.interpreter, CANCUN);
311 require_non_staticcall!(context.interpreter);
312 popn!([index, value], context.interpreter);
313
314 context
315 .host
316 .tstore(context.interpreter.input.target_address(), index, value);
317 Ok(())
318}
319
320pub fn tload<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
323 check!(context.interpreter, CANCUN);
324 popn_top!([], index, context.interpreter);
325
326 *index = context
327 .host
328 .tload(context.interpreter.input.target_address(), *index);
329 Ok(())
330}
331
332pub fn log<const N: usize, H: Host + ?Sized>(context: Ictx<'_, H, impl ITy>) -> Result {
336 require_non_staticcall!(context.interpreter);
337
338 popn!([offset, len], context.interpreter);
339 let len = as_usize_or_fail!(context.interpreter, len);
340 gas!(
341 context.interpreter,
342 context.host.gas_params().log_cost(N as u8, len as u64)
343 );
344 let data = if len == 0 {
345 Bytes::new()
346 } else {
347 let offset = as_usize_or_fail!(context.interpreter, offset);
348 context
350 .interpreter
351 .resize_memory(context.host.gas_params(), offset, len)?;
352 Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
353 };
354 let Some(topics) = context.interpreter.stack.popn::<N>() else {
355 return Err(InstructionResult::StackUnderflow);
356 };
357
358 let log = Log {
359 address: context.interpreter.input.target_address(),
360 data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
361 .expect("LogData should have <=4 topics"),
362 };
363
364 context.host.log(log);
365 Ok(())
366}
367
368pub fn selfdestruct<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
372 require_non_staticcall!(context.interpreter);
373 popn!([target], context.interpreter);
374 let target = target.into_address();
375 let spec = context.interpreter.runtime_flag.spec_id();
376
377 let cold_load_gas = context.host.gas_params().selfdestruct_cold_cost();
378
379 let skip_cold_load = context.interpreter.gas.remaining() < cold_load_gas;
380 let res = context.host.selfdestruct(
381 context.interpreter.input.target_address(),
382 target,
383 skip_cold_load,
384 )?;
385
386 let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
388 res.had_value && !res.target_exists
389 } else {
390 !res.target_exists
391 };
392
393 gas!(
394 context.interpreter,
395 context
396 .host
397 .gas_params()
398 .selfdestruct_cost(should_charge_topup, res.is_cold)
399 );
400
401 if context.host.is_amsterdam_eip8037_enabled() && should_charge_topup {
403 state_gas!(
404 context.interpreter,
405 context.host.gas_params().new_account_state_gas()
406 );
407 }
408
409 if !res.previously_destroyed {
410 context
411 .interpreter
412 .gas
413 .record_refund(context.host.gas_params().selfdestruct_refund());
414 }
415
416 Err(InstructionResult::SelfDestruct)
417}