pezpallet_revive/vm/
mod.rs

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