referrerpolicy=no-referrer-when-downgrade

pallet_revive/vm/
evm.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.
17use crate::{
18	AccountIdOf, BalanceOf, CodeInfo, Config, ContractBlob, DispatchError, Error, H256, LOG_TARGET,
19	Weight,
20	debug::DebugSettings,
21	precompiles::Token,
22	tracing,
23	vm::{BytecodeType, ExecResult, Ext, evm::instructions::exec_instruction},
24	weights::WeightInfo,
25};
26use alloc::vec::Vec;
27use core::{convert::Infallible, ops::ControlFlow};
28use revm::{bytecode::Bytecode, primitives::Bytes};
29
30#[cfg(feature = "runtime-benchmarks")]
31pub mod instructions;
32#[cfg(not(feature = "runtime-benchmarks"))]
33mod instructions;
34
35mod interpreter;
36pub use interpreter::{Halt, Interpreter};
37
38mod ext_bytecode;
39pub use ext_bytecode::ExtBytecode;
40
41mod memory;
42mod stack;
43mod util;
44
45/// Hard-coded value returned by the EVM `DIFFICULTY` opcode.
46///
47/// After Ethereum's Merge (Sept 2022), the `DIFFICULTY` opcode was redefined to return
48/// `prevrandao`, a randomness value from the beacon chain. In Substrate pallet-revive
49/// a fixed constant is returned instead for compatibility with contracts that still read this
50/// opcode. The value is aligned with the difficulty hardcoded for PVM contracts.
51pub(crate) const DIFFICULTY: u64 = 2500000000000000_u64;
52
53/// Cost  for a single unit of EVM gas.
54#[derive(Eq, PartialEq, Debug, Clone, Copy)]
55pub struct EVMGas(pub u64);
56
57impl<T: Config> Token<T> for EVMGas {
58	fn weight(&self) -> Weight {
59		let base_cost = T::WeightInfo::evm_opcode(1).saturating_sub(T::WeightInfo::evm_opcode(0));
60		base_cost.saturating_mul(self.0)
61	}
62}
63
64/// The cost of an EVM op-code that is charged from its own benchmark rather than in [`EVMGas`].
65#[derive(Eq, PartialEq, Debug, Clone, Copy)]
66enum EvmOpcodeCosts {
67	Div,
68	SDiv,
69	Mod,
70	SMod,
71	AddMod,
72	MulMod,
73}
74
75impl<T: Config> Token<T> for EvmOpcodeCosts {
76	fn weight(&self) -> Weight {
77		let per_opcode = |weight_fn: fn(u32) -> Weight| weight_fn(1).saturating_sub(weight_fn(0));
78		// These op-codes are benchmarked in pairs with a `POP`, whose cost isn't theirs.
79		let pop = per_opcode(T::WeightInfo::evm_pop_opcode);
80		match self {
81			Self::Div => per_opcode(T::WeightInfo::evm_div_opcode)
82				.max(per_opcode(T::WeightInfo::evm_div_opcode_one_limb_variant))
83				.max(per_opcode(T::WeightInfo::evm_div_opcode_mixed_variant))
84				.max(per_opcode(T::WeightInfo::evm_div_opcode_fixed_knuth_variant))
85				.saturating_sub(pop),
86			Self::SDiv => per_opcode(T::WeightInfo::evm_sdiv_opcode)
87				.max(per_opcode(T::WeightInfo::evm_sdiv_opcode_one_limb_variant))
88				.max(per_opcode(T::WeightInfo::evm_sdiv_opcode_mixed_variant))
89				.max(per_opcode(T::WeightInfo::evm_sdiv_opcode_fixed_knuth_variant))
90				.saturating_sub(pop),
91			Self::Mod => per_opcode(T::WeightInfo::evm_mod_opcode)
92				.max(per_opcode(T::WeightInfo::evm_mod_opcode_one_limb_variant))
93				.max(per_opcode(T::WeightInfo::evm_mod_opcode_mixed_variant))
94				.max(per_opcode(T::WeightInfo::evm_mod_opcode_fixed_knuth_variant))
95				.saturating_sub(pop),
96			Self::SMod => per_opcode(T::WeightInfo::evm_smod_opcode)
97				.max(per_opcode(T::WeightInfo::evm_smod_opcode_one_limb_variant))
98				.max(per_opcode(T::WeightInfo::evm_smod_opcode_mixed_variant))
99				.max(per_opcode(T::WeightInfo::evm_smod_opcode_fixed_knuth_variant))
100				.saturating_sub(pop),
101			Self::AddMod => per_opcode(T::WeightInfo::evm_addmod_opcode)
102				.max(per_opcode(T::WeightInfo::evm_addmod_opcode_one_limb_variant))
103				.max(per_opcode(T::WeightInfo::evm_addmod_opcode_mixed_variant))
104				.max(per_opcode(T::WeightInfo::evm_addmod_opcode_fixed_knuth_variant))
105				.saturating_sub(pop),
106			Self::MulMod => per_opcode(T::WeightInfo::evm_mulmod_opcode).saturating_sub(pop),
107		}
108	}
109}
110
111impl<T: Config> ContractBlob<T> {
112	/// Create a new contract from EVM init code.
113	pub fn from_evm_init_code(code: Vec<u8>, owner: AccountIdOf<T>) -> Result<Self, DispatchError> {
114		if code.len() > revm::primitives::eip3860::MAX_INITCODE_SIZE &&
115			!DebugSettings::is_unlimited_contract_size_allowed::<T>()
116		{
117			return Err(<Error<T>>::BlobTooLarge.into());
118		}
119
120		let code_len = code.len() as u32;
121		let code_info = CodeInfo {
122			owner,
123			deposit: Default::default(),
124			refcount: 0,
125			code_len,
126			code_type: BytecodeType::Evm,
127			behaviour_version: Default::default(),
128		};
129
130		Bytecode::new_raw_checked(Bytes::from(code.to_vec())).map_err(|err| {
131			log::debug!(target: LOG_TARGET, "failed to create evm bytecode from init code: {err:?}" );
132			<Error<T>>::CodeRejected
133		})?;
134
135		// Code hash is not relevant for init code, since it is not stored on-chain.
136		let code_hash = H256::default();
137		Ok(ContractBlob { code, code_info, code_hash })
138	}
139
140	/// Create a new contract from EVM runtime code.
141	pub fn from_evm_runtime_code(
142		code: Vec<u8>,
143		owner: AccountIdOf<T>,
144	) -> Result<Self, DispatchError> {
145		let code_len = code.len() as u32;
146		let deposit = super::calculate_code_deposit::<T>(code_len);
147		Self::from_evm_runtime_code_with_deposit(code, owner, deposit)
148	}
149
150	/// Create a new contract from EVM runtime code with an explicit owner and
151	/// deposit amount.
152	///
153	/// Used for `Origin::Root` uploads: there is no origin account to attribute
154	/// the deposit to, so the caller passes the pallet's own account as a
155	/// sentinel owner (no user can sign as it, so the code can't be removed via
156	/// the owner-gated path) and a zero deposit (both `charge_deposit` and
157	/// `refund_deposit` short-circuit at amount 0).
158	pub fn from_evm_runtime_code_with_deposit(
159		code: Vec<u8>,
160		owner: AccountIdOf<T>,
161		deposit: BalanceOf<T>,
162	) -> Result<Self, DispatchError> {
163		if code.len() > revm::primitives::eip170::MAX_CODE_SIZE &&
164			!DebugSettings::is_unlimited_contract_size_allowed::<T>()
165		{
166			return Err(<Error<T>>::BlobTooLarge.into());
167		}
168
169		// EIP-3541: reject new contract code (runtime code) starting with the 0xEF byte.
170		// Reserved for EIP-7702 delegation indicators; clashing here would let a
171		// constructor return bytes that subsequent calls would misinterpret as a
172		// delegation pointer.
173		if code.first() == Some(&0xEF) {
174			return Err(<Error<T>>::CodeRejected.into());
175		}
176
177		let code_len = code.len() as u32;
178
179		let code_info = CodeInfo {
180			owner,
181			deposit,
182			refcount: 0,
183			code_len,
184			code_type: BytecodeType::Evm,
185			behaviour_version: Default::default(),
186		};
187
188		Bytecode::new_raw_checked(Bytes::from(code.to_vec())).map_err(|err| {
189			log::debug!(target: LOG_TARGET, "failed to create evm bytecode from code: {err:?}" );
190			<Error<T>>::CodeRejected
191		})?;
192
193		let code_hash = H256(sp_io::hashing::keccak_256(&code));
194		Ok(ContractBlob { code, code_info, code_hash })
195	}
196}
197
198/// Calls the EVM interpreter with the provided bytecode and inputs.
199pub fn call<E: Ext>(bytecode: Bytecode, ext: &mut E, input: Vec<u8>) -> ExecResult {
200	let mut interpreter = Interpreter::new(ExtBytecode::new(bytecode), input, ext);
201	let tracing_enabled = tracing::if_tracing(|t| t.is_execution_tracer()).unwrap_or(false);
202
203	let ControlFlow::Break(halt) = if tracing_enabled {
204		run_plain_with_tracing(&mut interpreter)
205	} else {
206		run_plain(&mut interpreter)
207	};
208	halt.into()
209}
210
211pub fn run_plain<E: Ext>(interpreter: &mut Interpreter<E>) -> ControlFlow<Halt, Infallible> {
212	loop {
213		let opcode = interpreter.bytecode.opcode();
214		interpreter.bytecode.relative_jump(1);
215		exec_instruction(interpreter, opcode)?;
216	}
217}
218
219fn run_plain_with_tracing<E: Ext>(
220	interpreter: &mut Interpreter<E>,
221) -> ControlFlow<Halt, Infallible> {
222	loop {
223		let opcode = interpreter.bytecode.opcode();
224		tracing::if_tracing(|tracer| {
225			let pc = interpreter.bytecode.pc() as u64;
226			tracer.enter_opcode(pc, opcode, interpreter)
227		});
228
229		interpreter.bytecode.relative_jump(1);
230		let res = exec_instruction(interpreter, opcode);
231
232		tracing::if_tracing(|tracer| tracer.exit_step(interpreter, None));
233
234		res?;
235	}
236}