pallet_evm_polkavm/
lib.rs

1// This file is part of Frontier.
2
3// Copyright (C) Frontier developers.
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//! # PolkaVM support for EVM Pallet
19
20// Ensure we're `no_std` when compiling for Wasm.
21#![cfg_attr(not(feature = "std"), no_std)]
22#![warn(unused_crate_dependencies)]
23
24extern crate alloc;
25
26pub mod vm;
27mod weights;
28
29use core::marker::PhantomData;
30use fp_evm::{
31	ExitError, ExitRevert, ExitSucceed, IsPrecompileResult, PrecompileFailure, PrecompileHandle,
32	PrecompileOutput, PrecompileSet,
33};
34use sp_core::{H160, H256};
35
36pub use self::{pallet::*, weights::WeightInfo};
37
38pub trait CreateAddressScheme<AccountId> {
39	fn create_address_scheme(caller: AccountId, code: &[u8], salt: H256) -> H160;
40}
41
42pub trait ConvertPolkaVmGas {
43	fn polkavm_gas_to_evm_gas(gas: polkavm::Gas) -> u64;
44	fn evm_gas_to_polkavm_gas(gas: u64) -> polkavm::Gas;
45}
46
47pub struct PolkaVmSet<Inner, T>(pub Inner, PhantomData<T>);
48
49impl<Inner, T> PolkaVmSet<Inner, T> {
50	pub fn new(inner: Inner) -> Self {
51		Self(inner, PhantomData)
52	}
53}
54
55impl<Inner: PrecompileSet, T: Config> PrecompileSet for PolkaVmSet<Inner, T> {
56	fn execute(
57		&self,
58		handle: &mut impl PrecompileHandle,
59	) -> Option<Result<PrecompileOutput, PrecompileFailure>> {
60		let code_address = handle.code_address();
61		let code = pallet_evm::AccountCodes::<T>::get(code_address);
62		if code[0..8] == vm::PREFIX {
63			let mut run = || {
64				let prepared_call: vm::PreparedCall<'_, T, _> = vm::PreparedCall::load(handle)?;
65				prepared_call.call()
66			};
67
68			match run() {
69				Ok(val) => {
70					if val.did_revert() {
71						Some(Err(PrecompileFailure::Revert {
72							exit_status: ExitRevert::Reverted,
73							output: val.data,
74						}))
75					} else {
76						Some(Ok(PrecompileOutput {
77							exit_status: ExitSucceed::Returned,
78							output: val.data,
79						}))
80					}
81				}
82				Err(_) => Some(Err(PrecompileFailure::Error {
83					exit_status: ExitError::Other("polkavm failure".into()),
84				})),
85			}
86		} else {
87			self.0.execute(handle)
88		}
89	}
90
91	fn is_precompile(&self, address: H160, remaining_gas: u64) -> IsPrecompileResult {
92		let code = pallet_evm::AccountCodes::<T>::get(address);
93		if code[0..8] == vm::PREFIX {
94			IsPrecompileResult::Answer {
95				is_precompile: true,
96				extra_cost: 0,
97			}
98		} else {
99			self.0.is_precompile(address, remaining_gas)
100		}
101	}
102}
103
104#[frame_support::pallet]
105pub mod pallet {
106	use super::{ConvertPolkaVmGas, CreateAddressScheme, WeightInfo};
107	use fp_evm::AccountProvider;
108	use frame_support::pallet_prelude::*;
109	use frame_system::pallet_prelude::*;
110	use pallet_evm::{
111		AccountCodes, AccountCodesMetadata, AddressMapping, CodeMetadata, Config as EConfig,
112	};
113	use sp_core::H256;
114
115	const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
116
117	#[pallet::pallet]
118	#[pallet::storage_version(STORAGE_VERSION)]
119	pub struct Pallet<T>(PhantomData<T>);
120
121	#[pallet::config]
122	pub trait Config: frame_system::Config + pallet_evm::Config {
123		type CreateAddressScheme: CreateAddressScheme<<Self as frame_system::Config>::AccountId>;
124		type ConvertPolkaVmGas: ConvertPolkaVmGas;
125		type MaxCodeSize: Get<u32>;
126		type WeightInfo: WeightInfo;
127	}
128
129	#[pallet::error]
130	pub enum Error<T> {
131		/// Maximum code length exceeded.
132		MaxCodeSizeExceeded,
133		/// Not deploying PolkaVM contract.
134		NotPolkaVmContract,
135		/// Contract already exist in state.
136		AlreadyExist,
137	}
138
139	#[pallet::call]
140	impl<T: Config> Pallet<T> {
141		/// Deploy a new PolkaVM contract into the Frontier state.
142		///
143		/// A PolkaVM contract is simply a contract in the Frontier state prefixed
144		/// by `0xef polkavm`. EIP-3541 ensures that no EVM contract starts with
145		/// the prefix.
146		#[pallet::call_index(0)]
147		#[pallet::weight(<T as Config>::WeightInfo::create_polkavm(code.len() as u32))]
148		pub fn create_polkavm(origin: OriginFor<T>, code: Vec<u8>, salt: H256) -> DispatchResult {
149			if code.len() as u32 >= <T as Config>::MaxCodeSize::get() {
150				return Err(Error::<T>::MaxCodeSizeExceeded.into());
151			}
152
153			if code[0..8] != crate::vm::PREFIX {
154				return Err(Error::<T>::NotPolkaVmContract.into());
155			}
156
157			let caller = ensure_signed(origin)?;
158			let address =
159				<T as Config>::CreateAddressScheme::create_address_scheme(caller, &code[..], salt);
160
161			if <AccountCodes<T>>::contains_key(address) {
162				return Err(Error::<T>::AlreadyExist.into());
163			}
164
165			let account_id = <T as EConfig>::AddressMapping::into_account_id(address);
166			<T as EConfig>::AccountProvider::create_account(&account_id);
167
168			let meta = CodeMetadata::from_code(&code);
169			<AccountCodesMetadata<T>>::insert(address, meta);
170			<AccountCodes<T>>::insert(address, code);
171
172			Ok(())
173		}
174	}
175}