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 let skip_cold = context.interpreter.gas.remaining()
237 < context.host.gas_params().cold_storage_additional_cost();
238 context
239 .host
240 .sstore_skip_cold_load(target, index, value, skip_cold)?
241 } else {
242 context
243 .host
244 .sstore(target, index, value)
245 .ok_or(InstructionResult::FatalExternalError)?
246 };
247
248 gas_accounting(&mut context, target, &state_load)
249}
250
251pub fn sstore_default_gas_accounting<IT, H>(
253 context: &mut Ictx<'_, H, IT>,
254 _target: Address,
255 state_load: &StateLoad<SStoreResult>,
256) -> Result
257where
258 IT: ITy,
259 H: Host + ?Sized,
260{
261 let spec_id = context.interpreter.runtime_flag.spec_id();
262 let is_istanbul = spec_id.is_enabled_in(ISTANBUL);
263
264 gas!(
266 context.interpreter,
267 context.host.gas_params().sstore_dynamic_gas(
268 is_istanbul,
269 &state_load.data,
270 state_load.is_cold
271 )
272 );
273
274 if context.host.is_amsterdam_eip8037_enabled() {
276 state_gas!(
277 context.interpreter,
278 context.host.gas_params().sstore_state_gas(&state_load.data)
279 );
280
281 let refill = context
286 .host
287 .gas_params()
288 .sstore_state_gas_refill(&state_load.data);
289 if refill > 0 {
290 context.interpreter.gas.refill_reservoir(refill);
291 }
292 }
293
294 context.interpreter.gas.record_refund(
296 context
297 .host
298 .gas_params()
299 .sstore_refund(is_istanbul, &state_load.data),
300 );
301 Ok(())
302}
303
304pub fn tstore<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
307 check!(context.interpreter, CANCUN);
308 require_non_staticcall!(context.interpreter);
309 popn!([index, value], context.interpreter);
310
311 context
312 .host
313 .tstore(context.interpreter.input.target_address(), index, value);
314 Ok(())
315}
316
317pub fn tload<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
320 check!(context.interpreter, CANCUN);
321 popn_top!([], index, context.interpreter);
322
323 *index = context
324 .host
325 .tload(context.interpreter.input.target_address(), *index);
326 Ok(())
327}
328
329pub fn log<const N: usize, H: Host + ?Sized>(context: Ictx<'_, H, impl ITy>) -> Result {
333 require_non_staticcall!(context.interpreter);
334
335 popn!([offset, len], context.interpreter);
336 let len = as_usize_or_fail!(context.interpreter, len);
337 gas!(
338 context.interpreter,
339 context.host.gas_params().log_cost(N as u8, len as u64)
340 );
341 let data = if len == 0 {
342 Bytes::new()
343 } else {
344 let offset = as_usize_or_fail!(context.interpreter, offset);
345 context
347 .interpreter
348 .resize_memory(context.host.gas_params(), offset, len)?;
349 Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
350 };
351 let Some(topics) = context.interpreter.stack.popn::<N>() else {
352 return Err(InstructionResult::StackUnderflow);
353 };
354
355 let log = Log {
356 address: context.interpreter.input.target_address(),
357 data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
358 .expect("LogData should have <=4 topics"),
359 };
360
361 context.host.log(log);
362 Ok(())
363}
364
365pub fn selfdestruct<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
369 require_non_staticcall!(context.interpreter);
370 popn!([target], context.interpreter);
371 let target = target.into_address();
372 let spec = context.interpreter.runtime_flag.spec_id();
373
374 let cold_load_gas = context.host.gas_params().selfdestruct_cold_cost();
375
376 let skip_cold_load = context.interpreter.gas.remaining() < cold_load_gas;
377 let res = context.host.selfdestruct(
378 context.interpreter.input.target_address(),
379 target,
380 skip_cold_load,
381 )?;
382
383 let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
385 res.had_value && !res.target_exists
386 } else {
387 !res.target_exists
388 };
389
390 gas!(
391 context.interpreter,
392 context
393 .host
394 .gas_params()
395 .selfdestruct_cost(should_charge_topup, res.is_cold)
396 );
397
398 if context.host.is_amsterdam_eip8037_enabled() && should_charge_topup {
400 state_gas!(
401 context.interpreter,
402 context.host.gas_params().new_account_state_gas()
403 );
404 }
405
406 if !res.previously_destroyed {
407 context
408 .interpreter
409 .gas
410 .record_refund(context.host.gas_params().selfdestruct_refund());
411 }
412
413 Err(InstructionResult::SelfDestruct)
414}