referrerpolicy=no-referrer-when-downgrade

pallet_revive/vm/evm/instructions/
host.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use super::{
19	utility::{IntoAddress, IntoU256},
20	Context,
21};
22use crate::vm::Ext;
23use core::cmp::min;
24use revm::{
25	interpreter::{
26		gas::{self, warm_cold_cost, CALL_STIPEND},
27		host::Host,
28		interpreter_types::{InputsTr, RuntimeFlag, StackTr},
29		InstructionResult,
30	},
31	primitives::{hardfork::SpecId::*, Bytes, Log, LogData, B256, BLOCK_HASH_HISTORY, U256},
32};
33
34/// Implements the BALANCE instruction.
35///
36/// Gets the balance of the given account.
37pub fn balance<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
38	popn_top!([], top, context.interpreter);
39	let address = top.into_address();
40	let Some(balance) = context.host.balance(address) else {
41		context.interpreter.halt(InstructionResult::FatalExternalError);
42		return;
43	};
44	let spec_id = context.interpreter.runtime_flag.spec_id();
45	gas_legacy!(
46		context.interpreter,
47		if spec_id.is_enabled_in(BERLIN) {
48			warm_cold_cost(balance.is_cold)
49		} else if spec_id.is_enabled_in(ISTANBUL) {
50			// EIP-1884: Repricing for trie-size-dependent opcodes
51			700
52		} else if spec_id.is_enabled_in(TANGERINE) {
53			400
54		} else {
55			20
56		}
57	);
58	*top = balance.data;
59}
60
61/// EIP-1884: Repricing for trie-size-dependent opcodes
62pub fn selfbalance<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
63	check!(context.interpreter, ISTANBUL);
64	gas_legacy!(context.interpreter, gas::LOW);
65
66	let Some(balance) = context.host.balance(context.interpreter.input.target_address()) else {
67		context.interpreter.halt(InstructionResult::FatalExternalError);
68		return;
69	};
70	push!(context.interpreter, balance.data);
71}
72
73/// Implements the EXTCODESIZE instruction.
74///
75/// Gets the size of an account's code.
76pub fn extcodesize<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
77	popn_top!([], top, context.interpreter);
78	let address = top.into_address();
79	let Some(code) = context.host.load_account_code(address) else {
80		context.interpreter.halt(InstructionResult::FatalExternalError);
81		return;
82	};
83	let spec_id = context.interpreter.runtime_flag.spec_id();
84	if spec_id.is_enabled_in(BERLIN) {
85		gas_legacy!(context.interpreter, warm_cold_cost(code.is_cold));
86	} else if spec_id.is_enabled_in(TANGERINE) {
87		gas_legacy!(context.interpreter, 700);
88	} else {
89		gas_legacy!(context.interpreter, 20);
90	}
91
92	*top = U256::from(code.len());
93}
94
95/// EIP-1052: EXTCODEHASH opcode
96pub fn extcodehash<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
97	check!(context.interpreter, CONSTANTINOPLE);
98	popn_top!([], top, context.interpreter);
99	let address = top.into_address();
100	let Some(code_hash) = context.host.load_account_code_hash(address) else {
101		context.interpreter.halt(InstructionResult::FatalExternalError);
102		return;
103	};
104	let spec_id = context.interpreter.runtime_flag.spec_id();
105	if spec_id.is_enabled_in(BERLIN) {
106		gas_legacy!(context.interpreter, warm_cold_cost(code_hash.is_cold));
107	} else if spec_id.is_enabled_in(ISTANBUL) {
108		gas_legacy!(context.interpreter, 700);
109	} else {
110		gas_legacy!(context.interpreter, 400);
111	}
112	*top = code_hash.into_u256();
113}
114
115/// Implements the EXTCODECOPY instruction.
116///
117/// Copies a portion of an account's code to memory.
118pub fn extcodecopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
119	popn!([address, memory_offset, code_offset, len_u256], context.interpreter);
120	let address = address.into_address();
121	let Some(code) = context.host.load_account_code(address) else {
122		context.interpreter.halt(InstructionResult::FatalExternalError);
123		return;
124	};
125
126	let len = as_usize_or_fail!(context.interpreter, len_u256);
127	gas_or_fail_legacy!(
128		context.interpreter,
129		gas::extcodecopy_cost(context.interpreter.runtime_flag.spec_id(), len, code.is_cold)
130	);
131	if len == 0 {
132		return;
133	}
134	let memory_offset = as_usize_or_fail!(context.interpreter, memory_offset);
135	let code_offset = min(as_usize_saturated!(code_offset), code.len());
136	resize_memory!(context.interpreter, memory_offset, len);
137
138	// Note: This can't panic because we resized memory to fit.
139	context.interpreter.memory.set_data(memory_offset, code_offset, len, &code);
140}
141
142/// Implements the BLOCKHASH instruction.
143///
144/// Gets the hash of one of the 256 most recent complete blocks.
145pub fn blockhash<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
146	gas_legacy!(context.interpreter, gas::BLOCKHASH);
147	popn_top!([], number, context.interpreter);
148
149	let requested_number = *number;
150	let block_number = context.host.block_number();
151
152	let Some(diff) = block_number.checked_sub(requested_number) else {
153		*number = U256::ZERO;
154		return;
155	};
156
157	let diff = as_u64_saturated!(diff);
158
159	// blockhash should push zero if number is same as current block number.
160	if diff == 0 {
161		*number = U256::ZERO;
162		return;
163	}
164
165	*number = if diff <= BLOCK_HASH_HISTORY {
166		let Some(hash) = context.host.block_hash(as_u64_saturated!(requested_number)) else {
167			context.interpreter.halt(InstructionResult::FatalExternalError);
168			return;
169		};
170		U256::from_be_bytes(hash.0)
171	} else {
172		U256::ZERO
173	}
174}
175
176/// Implements the SLOAD instruction.
177///
178/// Loads a word from storage.
179pub fn sload<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
180	popn_top!([], index, context.interpreter);
181
182	let Some(value) = context.host.sload(context.interpreter.input.target_address(), *index) else {
183		context.interpreter.halt(InstructionResult::FatalExternalError);
184		return;
185	};
186
187	gas_legacy!(
188		context.interpreter,
189		gas::sload_cost(context.interpreter.runtime_flag.spec_id(), value.is_cold)
190	);
191	*index = value.data;
192}
193
194/// Implements the SSTORE instruction.
195///
196/// Stores a word to storage.
197pub fn sstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
198	require_non_staticcall!(context.interpreter);
199
200	popn!([index, value], context.interpreter);
201
202	let Some(state_load) =
203		context.host.sstore(context.interpreter.input.target_address(), index, value)
204	else {
205		context.interpreter.halt(InstructionResult::FatalExternalError);
206		return;
207	};
208
209	// EIP-1706 Disable SSTORE with gasleft lower than call stipend
210	if context.interpreter.runtime_flag.spec_id().is_enabled_in(ISTANBUL) &&
211		context.interpreter.gas.remaining() <= CALL_STIPEND
212	{
213		context.interpreter.halt(InstructionResult::ReentrancySentryOOG);
214		return;
215	}
216	gas_legacy!(
217		context.interpreter,
218		gas::sstore_cost(
219			context.interpreter.runtime_flag.spec_id(),
220			&state_load.data,
221			state_load.is_cold
222		)
223	);
224
225	context.interpreter.gas.record_refund(gas::sstore_refund(
226		context.interpreter.runtime_flag.spec_id(),
227		&state_load.data,
228	));
229}
230
231/// EIP-1153: Transient storage opcodes
232/// Store value to transient storage
233pub fn tstore<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
234	check!(context.interpreter, CANCUN);
235	require_non_staticcall!(context.interpreter);
236	gas_legacy!(context.interpreter, gas::WARM_STORAGE_READ_COST);
237
238	popn!([index, value], context.interpreter);
239
240	context.host.tstore(context.interpreter.input.target_address(), index, value);
241}
242
243/// EIP-1153: Transient storage opcodes
244/// Load value from transient storage
245pub fn tload<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
246	check!(context.interpreter, CANCUN);
247	gas_legacy!(context.interpreter, gas::WARM_STORAGE_READ_COST);
248
249	popn_top!([], index, context.interpreter);
250
251	*index = context.host.tload(context.interpreter.input.target_address(), *index);
252}
253
254/// Implements the LOG0-LOG4 instructions.
255///
256/// Appends log record with N topics.
257pub fn log<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) {
258	require_non_staticcall!(context.interpreter);
259
260	popn!([offset, len], context.interpreter);
261	let len = as_usize_or_fail!(context.interpreter, len);
262	gas_or_fail_legacy!(context.interpreter, gas::log_cost(N as u8, len as u64));
263	let data = if len == 0 {
264		Bytes::new()
265	} else {
266		let offset = as_usize_or_fail!(context.interpreter, offset);
267		resize_memory!(context.interpreter, offset, len);
268		Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
269	};
270	if context.interpreter.stack.len() < N {
271		context.interpreter.halt(InstructionResult::StackUnderflow);
272		return;
273	}
274	let Some(topics) = <_ as StackTr>::popn::<N>(&mut context.interpreter.stack) else {
275		context.interpreter.halt(InstructionResult::StackUnderflow);
276		return;
277	};
278
279	let log = Log {
280		address: context.interpreter.input.target_address(),
281		data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
282			.expect("LogData should have <=4 topics"),
283	};
284
285	context.host.log(log);
286}
287
288/// Implements the SELFDESTRUCT instruction.
289///
290/// Halt execution and register account for later deletion.
291pub fn selfdestruct<'ext, E: Ext>(context: Context<'_, 'ext, E>) {
292	require_non_staticcall!(context.interpreter);
293	popn!([target], context.interpreter);
294	let target = target.into_address();
295
296	let Some(res) = context.host.selfdestruct(context.interpreter.input.target_address(), target)
297	else {
298		context.interpreter.halt(InstructionResult::FatalExternalError);
299		return;
300	};
301
302	// EIP-3529: Reduction in refunds
303	if !context.interpreter.runtime_flag.spec_id().is_enabled_in(LONDON) &&
304		!res.previously_destroyed
305	{
306		context.interpreter.gas.record_refund(gas::SELFDESTRUCT)
307	}
308
309	gas_legacy!(
310		context.interpreter,
311		gas::selfdestruct_cost(context.interpreter.runtime_flag.spec_id(), res)
312	);
313
314	context.interpreter.halt(InstructionResult::SelfDestruct);
315}