Skip to main content

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	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, error::BadOrigin},
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};
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	/// Returns reference count of the module.
250	#[cfg(test)]
251	pub fn refcount(&self) -> u64 {
252		self.refcount
253	}
254
255	/// Returns the deposit of the module.
256	pub fn deposit(&self) -> BalanceOf<T> {
257		self.deposit
258	}
259
260	/// Returns the account that uploaded the module.
261	pub fn owner(&self) -> &AccountIdOf<T> {
262		&self.owner
263	}
264
265	/// Returns the code length.
266	pub fn code_len(&self) -> u64 {
267		self.code_len.into()
268	}
269
270	/// Returns true if the executable is a PVM blob.
271	pub fn is_pvm(&self) -> bool {
272		matches!(self.code_type, BytecodeType::Pvm)
273	}
274
275	/// Returns the number of times the specified contract exists on the call stack. Delegated calls
276	/// Increment the reference count of a stored code by one.
277	///
278	/// # Errors
279	///
280	/// [`Error::CodeNotFound`] is returned if no stored code found having the specified
281	/// `code_hash`.
282	pub fn increment_refcount(code_hash: H256) -> DispatchResult {
283		<CodeInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
284			if let Some(info) = existing {
285				info.refcount = info
286					.refcount
287					.checked_add(1)
288					.ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?;
289				Ok(())
290			} else {
291				Err(Error::<T>::CodeNotFound.into())
292			}
293		})
294	}
295
296	/// Decrement the reference count of a stored code by one.
297	/// Remove the code from storage when the reference count is zero.
298	pub fn decrement_refcount(code_hash: H256) -> Result<CodeRemoved, DispatchError> {
299		<CodeInfoOf<T>>::try_mutate_exists(code_hash, |existing| {
300			let Some(code_info) = existing else { return Err(Error::<T>::CodeNotFound.into()) };
301
302			if code_info.refcount == 1 {
303				<Pallet<T>>::refund_deposit(
304					HoldReason::CodeUploadDepositReserve,
305					&Pallet::<T>::account_id(),
306					deposit_payment::Funds::Balance(&code_info.owner),
307					code_info.deposit,
308				)?;
309
310				*existing = None;
311				<PristineCode<T>>::remove(&code_hash);
312
313				Ok(CodeRemoved::Yes)
314			} else {
315				code_info.refcount = code_info
316					.refcount
317					.checked_sub(1)
318					.ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?;
319				Ok(CodeRemoved::No)
320			}
321		})
322	}
323}
324
325impl<T: Config> Executable<T> for ContractBlob<T> {
326	fn from_storage<S: State>(
327		code_hash: H256,
328		meter: &mut ResourceMeter<T, S>,
329	) -> Result<Self, DispatchError> {
330		let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
331		meter.charge_weight_token(CodeLoadToken::from_code_info(&code_info))?;
332		let code = <PristineCode<T>>::get(&code_hash).ok_or(Error::<T>::CodeNotFound)?;
333		Ok(Self { code, code_info, code_hash })
334	}
335
336	fn from_evm_init_code(code: Vec<u8>, owner: AccountIdOf<T>) -> Result<Self, DispatchError> {
337		ContractBlob::from_evm_init_code(code, owner)
338	}
339
340	fn execute<E: Ext<T = T>>(
341		self,
342		ext: &mut E,
343		function: ExportedFunction,
344		input_data: Vec<u8>,
345	) -> ExecResult {
346		if self.code_info().is_pvm() {
347			let prepared_call =
348				self.prepare_call(pvm::Runtime::new(ext, input_data), function, 0)?;
349			prepared_call.call()
350		} else if T::AllowEVMBytecode::get() {
351			use revm::bytecode::Bytecode;
352			let bytecode = Bytecode::new_raw(self.code.into());
353			evm::call(bytecode, ext, input_data)
354		} else {
355			Err(Error::<T>::CodeRejected.into())
356		}
357	}
358
359	fn code(&self) -> &[u8] {
360		self.code.as_ref()
361	}
362
363	fn code_hash(&self) -> &H256 {
364		&self.code_hash
365	}
366
367	fn code_info(&self) -> &CodeInfo<T> {
368		&self.code_info
369	}
370}
371
372/// Fallible conversion of a `ExecError` to `ReturnErrorCode`.
373///
374/// This is used when converting the error returned from a subcall in order to decide
375/// whether to trap the caller or allow handling of the error.
376pub(crate) fn exec_error_into_return_code<E: Ext>(
377	from: ExecError,
378) -> Result<ReturnErrorCode, DispatchError> {
379	use crate::exec::ErrorOrigin::Callee;
380	use ReturnErrorCode::*;
381
382	let transfer_failed = Error::<E::T>::TransferFailed.into();
383	let out_of_gas = Error::<E::T>::OutOfGas.into();
384	let out_of_deposit = Error::<E::T>::StorageDepositLimitExhausted.into();
385	let duplicate_contract = Error::<E::T>::DuplicateContract.into();
386	let unsupported_precompile = Error::<E::T>::UnsupportedPrecompileAddress.into();
387
388	// errors in the callee do not trap the caller
389	match (from.error, from.origin) {
390		(err, _) if err == transfer_failed => Ok(TransferFailed),
391		(err, _) if err == duplicate_contract => Ok(DuplicateContractAddress),
392		(err, _) if err == unsupported_precompile => Err(err),
393		(err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources),
394		(_, Callee) => Ok(CalleeTrapped),
395		(err, _) => Err(err),
396	}
397}