1pub 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#[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 #[codec(skip)]
52 code_info: CodeInfo<T>,
53 #[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 Pvm,
64 Evm,
66}
67
68#[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 owner: AccountIdOf<T>,
83 #[codec(compact)]
85 deposit: BalanceOf<T>,
86 #[codec(compact)]
88 refcount: u64,
89 code_len: u32,
91 code_type: BytecodeType,
93 behaviour_version: u32,
101}
102
103pub 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 fn identifier(&self) -> &str {
114 match self {
115 Self::Constructor => "deploy",
116 Self::Call => "call",
117 }
118 }
119}
120
121#[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 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 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 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 Some(_) => Ok(Default::default()),
197 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 #[cfg(test)]
243 pub fn refcount(&self) -> u64 {
244 self.refcount
245 }
246
247 pub fn deposit(&self) -> BalanceOf<T> {
249 self.deposit
250 }
251
252 pub fn code_len(&self) -> u64 {
254 self.code_len.into()
255 }
256
257 pub fn is_pvm(&self) -> bool {
259 matches!(self.code_type, BytecodeType::Pvm)
260 }
261
262 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 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
357pub(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 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}