referrerpolicy=no-referrer-when-downgrade

pallet_revive/vm/
mod.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
18//! This module provides a means for executing contracts
19//! represented in vm bytecode.
20
21pub mod evm;
22pub mod pvm;
23mod runtime_costs;
24
25pub use runtime_costs::{RuntimeCosts, StorageAccessKind};
26
27use crate::{
28	AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecConfig, ExecError,
29	HoldReason, LOG_TARGET, Pallet, PristineCode, StorageDeposit, Weight, deposit_payment,
30	exec::{ExecResult, Executable, ExportedFunction, Ext},
31	frame_support::ensure,
32	metering::{ResourceMeter, State, Token},
33	weights::WeightInfo,
34};
35use alloc::vec::Vec;
36use codec::{Decode, Encode, MaxEncodedLen};
37use frame_support::dispatch::DispatchResult;
38use pallet_revive_uapi::ReturnErrorCode;
39use sp_core::{Get, H256};
40use sp_runtime::{DispatchError, Saturating, traits::BadOrigin};
41
42/// Validated Vm module ready for execution.
43/// This data structure is immutable once created and stored.
44#[derive(Encode, Decode, scale_info::TypeInfo)]
45#[codec(mel_bound())]
46#[scale_info(skip_type_params(T))]
47pub struct ContractBlob<T: Config> {
48	code: Vec<u8>,
49	// This isn't needed for contract execution and is not stored alongside it.
50	#[codec(skip)]
51	code_info: CodeInfo<T>,
52	// This is for not calculating the hash every time we need it.
53	#[codec(skip)]
54	code_hash: H256,
55}
56
57#[derive(
58	PartialEq, Eq, Debug, Copy, Clone, Encode, Decode, MaxEncodedLen, scale_info::TypeInfo,
59)]
60pub enum BytecodeType {
61	/// The code is a PVM bytecode.
62	Pvm,
63	/// The code is an EVM bytecode.
64	Evm,
65}
66
67/// Contract code related data, such as:
68///
69/// - owner of the contract, i.e. account uploaded its code,
70/// - storage deposit amount,
71/// - reference count,
72///
73/// It is stored in a separate storage entry to avoid loading the code when not necessary.
74#[derive(
75	frame_support::DebugNoBound, Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen,
76)]
77#[codec(mel_bound())]
78#[scale_info(skip_type_params(T))]
79pub struct CodeInfo<T: Config> {
80	/// The account that has uploaded the contract code and hence is allowed to remove it.
81	owner: AccountIdOf<T>,
82	/// The amount of balance that was deposited by the owner in order to store it on-chain.
83	#[codec(compact)]
84	deposit: BalanceOf<T>,
85	/// The number of instantiated contracts that use this as their code.
86	#[codec(compact)]
87	refcount: u64,
88	/// Length of the code in bytes.
89	code_len: u32,
90	/// Bytecode type
91	code_type: BytecodeType,
92	/// The behaviour version that this contract operates under.
93	///
94	/// Whenever any observeable change (with the exception of weights) are made we need
95	/// to make sure that already deployed contracts will not be affected. We do this by
96	/// exposing the old behaviour depending on the set behaviour version of the contract.
97	///
98	/// As of right now this is a reserved field that is always set to 0.
99	behaviour_version: u32,
100}
101
102/// Calculate the deposit required for storing code and its metadata.
103pub fn calculate_code_deposit<T: Config>(code_len: u32) -> BalanceOf<T> {
104	let bytes_added = code_len.saturating_add(<CodeInfo<T>>::max_encoded_len() as u32);
105	T::DepositPerByte::get()
106		.saturating_mul(bytes_added.into())
107		.saturating_add(T::DepositPerItem::get().saturating_mul(2u32.into()))
108}
109
110impl ExportedFunction {
111	/// The vm export name for the function.
112	fn identifier(&self) -> &str {
113		match self {
114			Self::Constructor => "deploy",
115			Self::Call => "call",
116		}
117	}
118}
119
120/// Cost of code loading from storage.
121#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
122#[derive(Clone, Copy)]
123struct CodeLoadToken {
124	code_len: u32,
125	code_type: BytecodeType,
126}
127
128impl CodeLoadToken {
129	fn from_code_info<T: Config>(code_info: &CodeInfo<T>) -> Self {
130		Self { code_len: code_info.code_len, code_type: code_info.code_type }
131	}
132}
133
134impl<T: Config> Token<T> for CodeLoadToken {
135	fn weight(&self) -> Weight {
136		match self.code_type {
137			// the proof size impact is accounted for in the `call_with_pvm_code_per_byte`
138			// strictly speaking we are double charging for the first BASIC_BLOCK_SIZE
139			// instructions here. Let's consider this as a safety margin.
140			BytecodeType::Pvm => T::WeightInfo::call_with_pvm_code_per_byte(self.code_len)
141				.saturating_sub(T::WeightInfo::call_with_pvm_code_per_byte(0))
142				.saturating_add(
143					T::WeightInfo::basic_block_compilation(1)
144						.saturating_sub(T::WeightInfo::basic_block_compilation(0))
145						.set_proof_size(0),
146				),
147			BytecodeType::Evm => T::WeightInfo::call_with_evm_code_per_byte(self.code_len)
148				.saturating_sub(T::WeightInfo::call_with_evm_code_per_byte(0)),
149		}
150	}
151}
152
153#[cfg(test)]
154pub fn code_load_weight(code_len: u32) -> Weight {
155	Token::<crate::tests::Test>::weight(&CodeLoadToken { code_len, code_type: BytecodeType::Pvm })
156}
157
158impl<T: Config> ContractBlob<T> {
159	/// Remove the code from storage and refund the deposit to its owner.
160	///
161	/// Applies all necessary checks before removing the code.
162	pub fn remove(origin: &T::AccountId, code_hash: H256) -> DispatchResult {
163		<CodeInfoOf<T>>::try_mutate_exists(&code_hash, |existing| {
164			if let Some(code_info) = existing {
165				ensure!(code_info.refcount == 0, <Error<T>>::CodeInUse);
166				ensure!(&code_info.owner == origin, BadOrigin);
167				<Pallet<T>>::refund_deposit(
168					HoldReason::CodeUploadDepositReserve,
169					&Pallet::<T>::account_id(),
170					deposit_payment::Funds::Balance(&code_info.owner),
171					code_info.deposit,
172				)?;
173				*existing = None;
174				<PristineCode<T>>::remove(&code_hash);
175				Ok(())
176			} else {
177				Err(<Error<T>>::CodeNotFound.into())
178			}
179		})
180	}
181
182	/// Puts the module blob into storage, and returns the deposit collected for the storage.
183	pub fn store_code<S: State>(
184		&mut self,
185		exec_config: &ExecConfig<T>,
186		meter: &mut ResourceMeter<T, S>,
187	) -> Result<BalanceOf<T>, DispatchError> {
188		let code_hash = *self.code_hash();
189		ensure!(code_hash != H256::zero(), <Error<T>>::CodeNotFound);
190
191		<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
192			match stored_code_info {
193				// Contract code is already stored in storage. Nothing to be done here.
194				Some(_) => Ok(Default::default()),
195				// Upload a new contract code.
196				// We need to store the code and its code_info, and collect the deposit.
197				// This `None` case happens only with freshly uploaded modules. This means that
198				// the `owner` is always the origin of the current transaction.
199				None => {
200					let deposit = self.code_info.deposit;
201
202					<Pallet<T>>::charge_deposit(
203							HoldReason::CodeUploadDepositReserve,
204							&self.code_info.owner,
205							&Pallet::<T>::account_id(),
206							deposit,
207							exec_config,
208						)
209					 .inspect_err(|err| {
210							log::debug!(target: LOG_TARGET, "failed to hold store code deposit {deposit:?} for owner: {:?}: {err:?}", self.code_info.owner);
211					})?;
212
213					meter.charge_deposit(&StorageDeposit::Charge(deposit))?;
214
215					<PristineCode<T>>::insert(code_hash, &self.code.to_vec());
216					*stored_code_info = Some(self.code_info.clone());
217					Ok(deposit)
218				},
219			}
220		})
221	}
222}
223
224impl<T: Config> CodeInfo<T> {
225	#[cfg(test)]
226	pub fn new(owner: T::AccountId) -> Self {
227		CodeInfo {
228			owner,
229			deposit: Default::default(),
230			refcount: 0,
231			code_len: 0,
232			code_type: BytecodeType::Pvm,
233			behaviour_version: Default::default(),
234		}
235	}
236
237	#[cfg(any(feature = "runtime-benchmarks", test))]
238	pub fn new_with_deposit(owner: T::AccountId, deposit: BalanceOf<T>) -> Self {
239		CodeInfo {
240			owner,
241			deposit,
242			refcount: 0,
243			code_len: 0,
244			code_type: BytecodeType::Pvm,
245			behaviour_version: Default::default(),
246		}
247	}
248
249	/// Override the refcount. Test-only — used to engineer overflow scenarios on
250	/// `increment_refcount`.
251	#[cfg(test)]
252	pub fn set_refcount(&mut self, refcount: u64) {
253		self.refcount = refcount;
254	}
255
256	/// Returns reference count of the module.
257	#[cfg(test)]
258	pub fn refcount(&self) -> u64 {
259		self.refcount
260	}
261
262	/// Returns the deposit of the module.
263	pub fn deposit(&self) -> BalanceOf<T> {
264		self.deposit
265	}
266
267	/// Returns the account that uploaded the module.
268	pub fn owner(&self) -> &AccountIdOf<T> {
269		&self.owner
270	}
271
272	/// Returns the code length.
273	pub fn code_len(&self) -> u64 {
274		self.code_len.into()
275	}
276
277	/// Returns true if the executable is a PVM blob.
278	pub fn is_pvm(&self) -> bool {
279		matches!(self.code_type, BytecodeType::Pvm)
280	}
281
282	/// Returns the number of times the specified contract exists on the call stack. Delegated calls
283	/// Increment the reference count of a stored code by one.
284	///
285	/// # Errors
286	///
287	/// [`Error::CodeNotFound`] is returned if no stored code found having the specified
288	/// `code_hash`.
289	pub fn increment_refcount(code_hash: H256) -> DispatchResult {
290		<CodeInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
291			if let Some(info) = existing {
292				info.refcount = info
293					.refcount
294					.checked_add(1)
295					.ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?;
296				Ok(())
297			} else {
298				Err(Error::<T>::CodeNotFound.into())
299			}
300		})
301	}
302
303	/// Decrement the reference count of a stored code by one.
304	/// Remove the code from storage when the reference count is zero.
305	pub fn decrement_refcount(code_hash: H256) -> Result<CodeRemoved, DispatchError> {
306		<CodeInfoOf<T>>::try_mutate_exists(code_hash, |existing| {
307			let Some(code_info) = existing else { return Err(Error::<T>::CodeNotFound.into()) };
308
309			if code_info.refcount == 1 {
310				<Pallet<T>>::refund_deposit(
311					HoldReason::CodeUploadDepositReserve,
312					&Pallet::<T>::account_id(),
313					deposit_payment::Funds::Balance(&code_info.owner),
314					code_info.deposit,
315				)?;
316
317				*existing = None;
318				<PristineCode<T>>::remove(&code_hash);
319
320				Ok(CodeRemoved::Yes)
321			} else {
322				code_info.refcount = code_info
323					.refcount
324					.checked_sub(1)
325					.ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?;
326				Ok(CodeRemoved::No)
327			}
328		})
329	}
330}
331
332impl<T: Config> Executable<T> for ContractBlob<T> {
333	fn from_storage<S: State>(
334		code_hash: H256,
335		meter: &mut ResourceMeter<T, S>,
336	) -> Result<Self, DispatchError> {
337		let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
338		meter.charge_weight_token(CodeLoadToken::from_code_info(&code_info))?;
339		let code = <PristineCode<T>>::get(&code_hash).ok_or(Error::<T>::CodeNotFound)?;
340		Ok(Self { code, code_info, code_hash })
341	}
342
343	fn from_evm_init_code(code: Vec<u8>, owner: AccountIdOf<T>) -> Result<Self, DispatchError> {
344		ContractBlob::from_evm_init_code(code, owner)
345	}
346
347	fn execute<E: Ext<T = T>>(
348		self,
349		ext: &mut E,
350		function: ExportedFunction,
351		input_data: Vec<u8>,
352	) -> ExecResult {
353		if self.code_info().is_pvm() {
354			let prepared_call =
355				self.prepare_call(pvm::Runtime::new(ext, input_data), function, 0)?;
356			prepared_call.call()
357		} else if T::AllowEVMBytecode::get() {
358			use revm::bytecode::Bytecode;
359			let bytecode = Bytecode::new_raw(self.code.into());
360			evm::call(bytecode, ext, input_data)
361		} else {
362			Err(Error::<T>::CodeRejected.into())
363		}
364	}
365
366	fn code(&self) -> &[u8] {
367		self.code.as_ref()
368	}
369
370	fn code_hash(&self) -> &H256 {
371		&self.code_hash
372	}
373
374	fn code_info(&self) -> &CodeInfo<T> {
375		&self.code_info
376	}
377}
378
379/// Fallible conversion of a `ExecError` to `ReturnErrorCode`.
380///
381/// This is used when converting the error returned from a subcall in order to decide
382/// whether to trap the caller or allow handling of the error.
383pub(crate) fn exec_error_into_return_code<E: Ext>(
384	from: ExecError,
385) -> Result<ReturnErrorCode, DispatchError> {
386	use crate::exec::ErrorOrigin::Callee;
387	use ReturnErrorCode::*;
388
389	let transfer_failed = Error::<E::T>::TransferFailed.into();
390	let out_of_gas = Error::<E::T>::OutOfGas.into();
391	let out_of_deposit = Error::<E::T>::StorageDepositLimitExhausted.into();
392	let duplicate_contract = Error::<E::T>::DuplicateContract.into();
393	let unsupported_precompile = Error::<E::T>::UnsupportedPrecompileAddress.into();
394
395	// errors in the callee do not trap the caller
396	match (from.error, from.origin) {
397		(err, _) if err == transfer_failed => Ok(TransferFailed),
398		(err, _) if err == duplicate_contract => Ok(DuplicateContractAddress),
399		(err, _) if err == unsupported_precompile => Err(err),
400		(err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources),
401		(_, Callee) => Ok(CalleeTrapped),
402		(err, _) => Err(err),
403	}
404}