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 frame_support::{ensure, error::BadOrigin},
30 metering::{ResourceMeter, State, Token},
31 weights::WeightInfo,
32 AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecConfig, ExecError,
33 HoldReason, Pallet, PristineCode, StorageDeposit, Weight, LOG_TARGET,
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#[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 #[codec(skip)]
51 code_info: CodeInfo<T>,
52 #[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 Pvm,
63 Evm,
65}
66
67#[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 owner: AccountIdOf<T>,
82 #[codec(compact)]
84 deposit: BalanceOf<T>,
85 #[codec(compact)]
87 refcount: u64,
88 code_len: u32,
90 code_type: BytecodeType,
92 behaviour_version: u32,
100}
101
102pub 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 fn identifier(&self) -> &str {
113 match self {
114 Self::Constructor => "deploy",
115 Self::Call => "call",
116 }
117 }
118}
119
120#[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 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 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 &code_info.owner,
171 code_info.deposit,
172 None,
173 )?;
174 *existing = None;
175 <PristineCode<T>>::remove(&code_hash);
176 Ok(())
177 } else {
178 Err(<Error<T>>::CodeNotFound.into())
179 }
180 })
181 }
182
183 pub fn store_code<S: State>(
185 &mut self,
186 exec_config: &ExecConfig<T>,
187 meter: &mut ResourceMeter<T, S>,
188 ) -> Result<BalanceOf<T>, DispatchError> {
189 let code_hash = *self.code_hash();
190 ensure!(code_hash != H256::zero(), <Error<T>>::CodeNotFound);
191
192 <CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
193 match stored_code_info {
194 Some(_) => Ok(Default::default()),
196 None => {
201 let deposit = self.code_info.deposit;
202
203 <Pallet<T>>::charge_deposit(
204 Some(HoldReason::CodeUploadDepositReserve),
205 &self.code_info.owner,
206 &Pallet::<T>::account_id(),
207 deposit,
208 exec_config,
209 )
210 .inspect_err(|err| {
211 log::debug!(target: LOG_TARGET, "failed to hold store code deposit {deposit:?} for owner: {:?}: {err:?}", self.code_info.owner);
212 })?;
213
214 meter.charge_deposit(&StorageDeposit::Charge(deposit))?;
215
216 <PristineCode<T>>::insert(code_hash, &self.code.to_vec());
217 *stored_code_info = Some(self.code_info.clone());
218 Ok(deposit)
219 },
220 }
221 })
222 }
223}
224
225impl<T: Config> CodeInfo<T> {
226 #[cfg(test)]
227 pub fn new(owner: T::AccountId) -> Self {
228 CodeInfo {
229 owner,
230 deposit: Default::default(),
231 refcount: 0,
232 code_len: 0,
233 code_type: BytecodeType::Pvm,
234 behaviour_version: Default::default(),
235 }
236 }
237
238 #[cfg(test)]
240 pub fn refcount(&self) -> u64 {
241 self.refcount
242 }
243
244 pub fn deposit(&self) -> BalanceOf<T> {
246 self.deposit
247 }
248
249 pub fn code_len(&self) -> u64 {
251 self.code_len.into()
252 }
253
254 pub fn is_pvm(&self) -> bool {
256 matches!(self.code_type, BytecodeType::Pvm)
257 }
258
259 pub fn increment_refcount(code_hash: H256) -> DispatchResult {
267 <CodeInfoOf<T>>::mutate(code_hash, |existing| -> Result<(), DispatchError> {
268 if let Some(info) = existing {
269 info.refcount = info
270 .refcount
271 .checked_add(1)
272 .ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?;
273 Ok(())
274 } else {
275 Err(Error::<T>::CodeNotFound.into())
276 }
277 })
278 }
279
280 pub fn decrement_refcount(code_hash: H256) -> Result<CodeRemoved, DispatchError> {
283 <CodeInfoOf<T>>::try_mutate_exists(code_hash, |existing| {
284 let Some(code_info) = existing else { return Err(Error::<T>::CodeNotFound.into()) };
285
286 if code_info.refcount == 1 {
287 <Pallet<T>>::refund_deposit(
288 HoldReason::CodeUploadDepositReserve,
289 &Pallet::<T>::account_id(),
290 &code_info.owner,
291 code_info.deposit,
292 None,
293 )?;
294
295 *existing = None;
296 <PristineCode<T>>::remove(&code_hash);
297
298 Ok(CodeRemoved::Yes)
299 } else {
300 code_info.refcount = code_info
301 .refcount
302 .checked_sub(1)
303 .ok_or_else(|| <Error<T>>::RefcountOverOrUnderflow)?;
304 Ok(CodeRemoved::No)
305 }
306 })
307 }
308}
309
310impl<T: Config> Executable<T> for ContractBlob<T> {
311 fn from_storage<S: State>(
312 code_hash: H256,
313 meter: &mut ResourceMeter<T, S>,
314 ) -> Result<Self, DispatchError> {
315 let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
316 meter.charge_weight_token(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}