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