#![doc = include_str!("../README.md")]
#![allow(rustdoc::private_intra_doc_links)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "1024")]
extern crate alloc;
mod address;
mod benchmarking;
mod call_builder;
mod exec;
mod gas;
mod impl_fungibles;
mod limits;
mod primitives;
mod storage;
#[cfg(test)]
mod tests;
mod transient_storage;
mod wasm;
pub mod evm;
pub mod precompiles;
pub mod test_utils;
pub mod tracing;
pub mod weights;
use crate::{
evm::{
runtime::GAS_PRICE, CallTracer, GasEncoder, GenericTransaction, Trace, Tracer, TracerType,
TYPE_EIP1559,
},
exec::{AccountIdOf, ExecError, Executable, Key, Stack as ExecStack},
gas::GasMeter,
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
wasm::{CodeInfo, RuntimeCosts, WasmBlob},
};
use alloc::{boxed::Box, format, vec};
use codec::{Codec, Decode, Encode};
use environmental::*;
use frame_support::{
dispatch::{
DispatchErrorWithPostInfo, DispatchResultWithPostInfo, GetDispatchInfo, Pays,
PostDispatchInfo, RawOrigin,
},
ensure,
pallet_prelude::DispatchClass,
traits::{
fungible::{Inspect, Mutate, MutateHold},
tokens::{Fortitude::Polite, Preservation::Preserve},
ConstU32, ConstU64, EnsureOrigin, Get, IsType, OriginTrait, Time,
},
weights::WeightMeter,
BoundedVec, RuntimeDebugNoBound,
};
use frame_system::{
ensure_signed,
pallet_prelude::{BlockNumberFor, OriginFor},
Pallet as System,
};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero},
AccountId32, DispatchError,
};
pub use crate::{
address::{
create1, create2, is_eth_derived, AccountId32Mapper, AddressMapper, TestAccountMapper,
},
exec::{MomentOf, Origin},
pallet::*,
};
pub use codec;
pub use frame_support::{self, dispatch::DispatchInfo, weights::Weight};
pub use frame_system::{self, limits::BlockWeights};
pub use pallet_transaction_payment;
pub use primitives::*;
pub use sp_core::{H160, H256, U256};
pub use sp_runtime;
pub use weights::WeightInfo;
#[cfg(doc)]
pub use crate::wasm::SyscallDoc;
pub type BalanceOf<T> =
<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
type TrieId = BoundedVec<u8, ConstU32<128>>;
type CodeVec = BoundedVec<u8, ConstU32<{ limits::code::BLOB_BYTES }>>;
type ImmutableData = BoundedVec<u8, ConstU32<{ limits::IMMUTABLE_BYTES }>>;
const SENTINEL: u32 = u32::MAX;
const LOG_TARGET: &str = "runtime::revive";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, traits::FindAuthor};
use frame_system::pallet_prelude::*;
use sp_core::U256;
use sp_runtime::Perbill;
pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config(with_default)]
pub trait Config: frame_system::Config {
type Time: Time;
#[pallet::no_default]
type Currency: Inspect<Self::AccountId>
+ Mutate<Self::AccountId>
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
#[pallet::no_default_bounds]
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::no_default_bounds]
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo;
#[pallet::no_default_bounds]
type RuntimeHoldReason: From<HoldReason>;
#[pallet::no_default_bounds]
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
type WeightInfo: WeightInfo;
#[pallet::no_default_bounds]
#[allow(private_bounds)]
type Precompiles: precompiles::Precompiles<Self>;
type FindAuthor: FindAuthor<Self::AccountId>;
#[pallet::constant]
#[pallet::no_default_bounds]
type DepositPerByte: Get<BalanceOf<Self>>;
#[pallet::constant]
#[pallet::no_default_bounds]
type DepositPerItem: Get<BalanceOf<Self>>;
#[pallet::constant]
type CodeHashLockupDepositPercent: Get<Perbill>;
#[pallet::no_default]
type AddressMapper: AddressMapper<Self>;
#[pallet::constant]
type UnsafeUnstableInterface: Get<bool>;
#[pallet::no_default_bounds]
type UploadOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
#[pallet::no_default_bounds]
type InstantiateOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
type RuntimeMemory: Get<u32>;
type PVFMemory: Get<u32>;
#[pallet::constant]
type ChainId: Get<u64>;
#[pallet::constant]
type NativeToEthRatio: Get<u32>;
#[pallet::no_default_bounds]
type EthGasEncoder: GasEncoder<BalanceOf<Self>>;
}
pub mod config_preludes {
use super::*;
use frame_support::{
derive_impl,
traits::{ConstBool, ConstU32},
};
use frame_system::EnsureSigned;
use sp_core::parameter_types;
type Balance = u64;
const UNITS: Balance = 10_000_000_000;
const CENTS: Balance = UNITS / 100;
pub const fn deposit(items: u32, bytes: u32) -> Balance {
items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS
}
parameter_types! {
pub const DepositPerItem: Balance = deposit(1, 0);
pub const DepositPerByte: Balance = deposit(0, 1);
pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0);
}
pub struct TestDefaultConfig;
impl Time for TestDefaultConfig {
type Moment = u64;
fn now() -> Self::Moment {
0u64
}
}
impl<T: From<u64>> Convert<Weight, T> for TestDefaultConfig {
fn convert(w: Weight) -> T {
w.ref_time().into()
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
impl frame_system::DefaultConfig for TestDefaultConfig {}
#[frame_support::register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
#[inject_runtime_type]
type RuntimeEvent = ();
#[inject_runtime_type]
type RuntimeHoldReason = ();
#[inject_runtime_type]
type RuntimeCall = ();
type Precompiles = ();
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type DepositPerByte = DepositPerByte;
type DepositPerItem = DepositPerItem;
type Time = Self;
type UnsafeUnstableInterface = ConstBool<true>;
type UploadOrigin = EnsureSigned<Self::AccountId>;
type InstantiateOrigin = EnsureSigned<Self::AccountId>;
type WeightInfo = ();
type WeightPrice = Self;
type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>;
type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>;
type ChainId = ConstU64<42>;
type NativeToEthRatio = ConstU32<1>;
type EthGasEncoder = ();
type FindAuthor = ();
}
}
#[pallet::event]
pub enum Event<T: Config> {
ContractEmitted {
contract: H160,
data: Vec<u8>,
topics: Vec<H256>,
},
}
#[pallet::error]
#[repr(u8)]
pub enum Error<T> {
InvalidSchedule = 0x01,
InvalidCallFlags = 0x02,
OutOfGas = 0x03,
TransferFailed = 0x04,
MaxCallDepthReached = 0x05,
ContractNotFound = 0x06,
CodeNotFound = 0x07,
CodeInfoNotFound = 0x08,
OutOfBounds = 0x09,
DecodingFailed = 0x0A,
ContractTrapped = 0x0B,
ValueTooLarge = 0x0C,
TerminatedWhileReentrant = 0x0D,
InputForwarded = 0x0E,
TooManyTopics = 0x0F,
DuplicateContract = 0x12,
TerminatedInConstructor = 0x13,
ReentranceDenied = 0x14,
ReenteredPallet = 0x15,
StateChangeDenied = 0x16,
StorageDepositNotEnoughFunds = 0x17,
StorageDepositLimitExhausted = 0x18,
CodeInUse = 0x19,
ContractReverted = 0x1A,
CodeRejected = 0x1B,
BlobTooLarge = 0x1C,
StaticMemoryTooLarge = 0x1D,
BasicBlockTooLarge = 0x1E,
InvalidInstruction = 0x1F,
MaxDelegateDependenciesReached = 0x20,
DelegateDependencyNotFound = 0x21,
DelegateDependencyAlreadyExists = 0x22,
CannotAddSelfAsDelegateDependency = 0x23,
OutOfTransientStorage = 0x24,
InvalidSyscall = 0x25,
InvalidStorageFlags = 0x26,
ExecutionFailed = 0x27,
BalanceConversionFailed = 0x28,
DecimalPrecisionLoss = 0x29,
InvalidImmutableAccess = 0x2A,
AccountUnmapped = 0x2B,
AccountAlreadyMapped = 0x2C,
InvalidGenericTransaction = 0x2D,
RefcountOverOrUnderflow = 0x2E,
UnsupportedPrecompileAddress = 0x2F,
}
#[pallet::composite_enum]
pub enum HoldReason {
CodeUploadDepositReserve,
StorageDepositReserve,
AddressMapping,
}
#[pallet::storage]
pub(crate) type PristineCode<T: Config> = StorageMap<_, Identity, H256, CodeVec>;
#[pallet::storage]
pub(crate) type CodeInfoOf<T: Config> = StorageMap<_, Identity, H256, CodeInfo<T>>;
#[pallet::storage]
pub(crate) type ContractInfoOf<T: Config> = StorageMap<_, Identity, H160, ContractInfo<T>>;
#[pallet::storage]
pub(crate) type ImmutableDataOf<T: Config> = StorageMap<_, Identity, H160, ImmutableData>;
#[pallet::storage]
pub(crate) type DeletionQueue<T: Config> = StorageMap<_, Twox64Concat, u32, TrieId>;
#[pallet::storage]
pub(crate) type DeletionQueueCounter<T: Config> =
StorageValue<_, DeletionQueueManager<T>, ValueQuery>;
#[pallet::storage]
pub(crate) type OriginalAccount<T: Config> = StorageMap<_, Identity, H160, AccountId32>;
#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub mapped_accounts: Vec<T::AccountId>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
for id in &self.mapped_accounts {
if let Err(err) = T::AddressMapper::map(id) {
log::error!(target: LOG_TARGET, "Failed to map account {id:?}: {err:?}");
}
}
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_idle(_block: BlockNumberFor<T>, limit: Weight) -> Weight {
let mut meter = WeightMeter::with_limit(limit);
ContractInfo::<T>::process_deletion_queue_batch(&mut meter);
meter.consumed()
}
fn integrity_test() {
use limits::code::STATIC_MEMORY_BYTES;
assert!(T::ChainId::get() > 0, "ChainId must be greater than 0");
let max_runtime_mem: u32 = T::RuntimeMemory::get();
let max_call_depth =
limits::CALL_STACK_DEPTH.checked_add(1).expect("CallStack size is too big");
let max_transient_storage_size = limits::TRANSIENT_STORAGE_BYTES
.checked_mul(2)
.expect("MaxTransientStorageSize is too large");
const TOTAL_MEMORY_DEVIDER: u32 = 2;
const MEMORY_ALLOCATOR_INEFFICENCY_DEVIDER: u32 = 4;
let static_memory_limit = max_runtime_mem
.saturating_div(TOTAL_MEMORY_DEVIDER)
.saturating_sub(max_transient_storage_size)
.saturating_div(max_call_depth)
.saturating_sub(STATIC_MEMORY_BYTES)
.saturating_div(MEMORY_ALLOCATOR_INEFFICENCY_DEVIDER);
assert!(
STATIC_MEMORY_BYTES < static_memory_limit,
"Given `CallStack` height {:?}, `STATIC_MEMORY_LIMIT` should be set less than {:?} \
(current value is {:?}), to avoid possible runtime oom issues.",
max_call_depth,
static_memory_limit,
STATIC_MEMORY_BYTES,
);
let max_block_ref_time = T::BlockWeights::get()
.get(DispatchClass::Normal)
.max_total
.unwrap_or_else(|| T::BlockWeights::get().max_block)
.ref_time();
let max_payload_size = limits::PAYLOAD_BYTES;
let max_key_size =
Key::try_from_var(alloc::vec![0u8; limits::STORAGE_KEY_BYTES as usize])
.expect("Key of maximal size shall be created")
.hash()
.len() as u32;
let max_immutable_key_size = T::AccountId::max_encoded_len() as u32;
let max_immutable_size: u32 = ((max_block_ref_time /
(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::SetImmutableData(
limits::IMMUTABLE_BYTES,
))
.ref_time()))
.saturating_mul(limits::IMMUTABLE_BYTES.saturating_add(max_immutable_key_size) as u64))
.try_into()
.expect("Immutable data size too big");
let max_storage_size: u32 = ((max_block_ref_time /
(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::SetStorage {
new_bytes: max_payload_size,
old_bytes: 0,
})
.ref_time()))
.saturating_mul(max_payload_size.saturating_add(max_key_size) as u64))
.saturating_add(max_immutable_size.into())
.try_into()
.expect("Storage size too big");
let max_pvf_mem: u32 = T::PVFMemory::get();
let storage_size_limit = max_pvf_mem.saturating_sub(max_runtime_mem) / 2;
assert!(
max_storage_size < storage_size_limit,
"Maximal storage size {} exceeds the storage limit {}",
max_storage_size,
storage_size_limit
);
let max_events_size: u32 = ((max_block_ref_time /
(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::DepositEvent {
num_topic: 0,
len: max_payload_size,
})
.saturating_add(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::HostFn))
.ref_time()))
.saturating_mul(max_payload_size as u64))
.try_into()
.expect("Events size too big");
assert!(
max_events_size < storage_size_limit,
"Maximal events size {} exceeds the events limit {}",
max_events_size,
storage_size_limit
);
}
}
#[pallet::call]
impl<T: Config> Pallet<T>
where
BalanceOf<T>: Into<U256> + TryFrom<U256>,
MomentOf<T>: Into<U256>,
T::Hash: frame_support::traits::IsType<H256>,
{
#[allow(unused_variables)]
#[pallet::call_index(0)]
#[pallet::weight(Weight::MAX)]
pub fn eth_transact(origin: OriginFor<T>, payload: Vec<u8>) -> DispatchResultWithPostInfo {
Err(frame_system::Error::CallFiltered::<T>.into())
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))]
pub fn call(
origin: OriginFor<T>,
dest: H160,
#[pallet::compact] value: BalanceOf<T>,
gas_limit: Weight,
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo {
let mut output = Self::bare_call(
origin,
dest,
value,
gas_limit,
DepositLimit::Balance(storage_deposit_limit),
data,
);
if let Ok(return_value) = &output.result {
if return_value.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
dispatch_result(output.result, output.gas_consumed, T::WeightInfo::call())
}
#[pallet::call_index(2)]
#[pallet::weight(
T::WeightInfo::instantiate(data.len() as u32).saturating_add(*gas_limit)
)]
pub fn instantiate(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
gas_limit: Weight,
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
code_hash: sp_core::H256,
data: Vec<u8>,
salt: Option<[u8; 32]>,
) -> DispatchResultWithPostInfo {
let data_len = data.len() as u32;
let mut output = Self::bare_instantiate(
origin,
value,
gas_limit,
DepositLimit::Balance(storage_deposit_limit),
Code::Existing(code_hash),
data,
salt,
);
if let Ok(retval) = &output.result {
if retval.result.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
dispatch_result(
output.result.map(|result| result.result),
output.gas_consumed,
T::WeightInfo::instantiate(data_len),
)
}
#[pallet::call_index(3)]
#[pallet::weight(
T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32)
.saturating_add(*gas_limit)
)]
pub fn instantiate_with_code(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
gas_limit: Weight,
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
code: Vec<u8>,
data: Vec<u8>,
salt: Option<[u8; 32]>,
) -> DispatchResultWithPostInfo {
let code_len = code.len() as u32;
let data_len = data.len() as u32;
let mut output = Self::bare_instantiate(
origin,
value,
gas_limit,
DepositLimit::Balance(storage_deposit_limit),
Code::Upload(code),
data,
salt,
);
if let Ok(retval) = &output.result {
if retval.result.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
dispatch_result(
output.result.map(|result| result.result),
output.gas_consumed,
T::WeightInfo::instantiate_with_code(code_len, data_len),
)
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))]
pub fn upload_code(
origin: OriginFor<T>,
code: Vec<u8>,
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
) -> DispatchResult {
Self::bare_upload_code(origin, code, storage_deposit_limit).map(|_| ())
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::remove_code())]
pub fn remove_code(
origin: OriginFor<T>,
code_hash: sp_core::H256,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
<WasmBlob<T>>::remove(&origin, code_hash)?;
Ok(Pays::No.into())
}
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::set_code())]
pub fn set_code(
origin: OriginFor<T>,
dest: H160,
code_hash: sp_core::H256,
) -> DispatchResult {
ensure_root(origin)?;
<ContractInfoOf<T>>::try_mutate(&dest, |contract| {
let contract = if let Some(contract) = contract {
contract
} else {
return Err(<Error<T>>::ContractNotFound.into());
};
<CodeInfo<T>>::increment_refcount(code_hash)?;
<CodeInfo<T>>::decrement_refcount(contract.code_hash)?;
contract.code_hash = code_hash;
Ok(())
})
}
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::map_account())]
pub fn map_account(origin: OriginFor<T>) -> DispatchResult {
let origin = ensure_signed(origin)?;
T::AddressMapper::map(&origin)
}
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::unmap_account())]
pub fn unmap_account(origin: OriginFor<T>) -> DispatchResult {
let origin = ensure_signed(origin)?;
T::AddressMapper::unmap(&origin)
}
#[pallet::call_index(9)]
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
(
T::WeightInfo::dispatch_as_fallback_account().saturating_add(dispatch_info.call_weight),
dispatch_info.class
)
})]
pub fn dispatch_as_fallback_account(
origin: OriginFor<T>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let unmapped_account =
T::AddressMapper::to_fallback_account_id(&T::AddressMapper::to_address(&origin));
call.dispatch(RawOrigin::Signed(unmapped_account).into())
}
}
}
fn dispatch_result<R>(
result: Result<R, DispatchError>,
gas_consumed: Weight,
base_weight: Weight,
) -> DispatchResultWithPostInfo {
let post_info = PostDispatchInfo {
actual_weight: Some(gas_consumed.saturating_add(base_weight)),
pays_fee: Default::default(),
};
result
.map(|_| post_info)
.map_err(|e| DispatchErrorWithPostInfo { post_info, error: e })
}
impl<T: Config> Pallet<T>
where
BalanceOf<T>: Into<U256> + TryFrom<U256> + Bounded,
MomentOf<T>: Into<U256>,
T::Hash: frame_support::traits::IsType<H256>,
{
pub fn bare_call(
origin: OriginFor<T>,
dest: H160,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: DepositLimit<BalanceOf<T>>,
data: Vec<u8>,
) -> ContractResult<ExecReturnValue, BalanceOf<T>> {
let mut gas_meter = GasMeter::new(gas_limit);
let mut storage_deposit = Default::default();
let try_call = || {
let origin = Origin::from_runtime_origin(origin)?;
let mut storage_meter = match storage_deposit_limit {
DepositLimit::Balance(limit) => StorageMeter::new(limit),
DepositLimit::UnsafeOnlyForDryRun =>
StorageMeter::new_unchecked(BalanceOf::<T>::max_value()),
};
let result = ExecStack::<T, WasmBlob<T>>::run_call(
origin.clone(),
dest,
&mut gas_meter,
&mut storage_meter,
Self::convert_native_to_evm(value),
data,
storage_deposit_limit.is_unchecked(),
)?;
storage_deposit = storage_meter
.try_into_deposit(&origin, storage_deposit_limit.is_unchecked())
.inspect_err(|err| {
log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}");
})?;
Ok(result)
};
let result = Self::run_guarded(try_call);
ContractResult {
result: result.map_err(|r| r.error),
gas_consumed: gas_meter.gas_consumed(),
gas_required: gas_meter.gas_required(),
storage_deposit,
}
}
pub fn prepare_dry_run(account: &T::AccountId) {
frame_system::Pallet::<T>::inc_account_nonce(account);
}
pub fn bare_instantiate(
origin: OriginFor<T>,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: DepositLimit<BalanceOf<T>>,
code: Code,
data: Vec<u8>,
salt: Option<[u8; 32]>,
) -> ContractResult<InstantiateReturnValue, BalanceOf<T>> {
let mut gas_meter = GasMeter::new(gas_limit);
let mut storage_deposit = Default::default();
let unchecked_deposit_limit = storage_deposit_limit.is_unchecked();
let mut storage_deposit_limit = match storage_deposit_limit {
DepositLimit::Balance(limit) => limit,
DepositLimit::UnsafeOnlyForDryRun => BalanceOf::<T>::max_value(),
};
let try_instantiate = || {
let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?;
let (executable, upload_deposit) = match code {
Code::Upload(code) => {
let upload_account = T::UploadOrigin::ensure_origin(origin)?;
let (executable, upload_deposit) = Self::try_upload_code(
upload_account,
code,
storage_deposit_limit,
unchecked_deposit_limit,
)?;
storage_deposit_limit.saturating_reduce(upload_deposit);
(executable, upload_deposit)
},
Code::Existing(code_hash) =>
(WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()),
};
let instantiate_origin = Origin::from_account_id(instantiate_account.clone());
let mut storage_meter = if unchecked_deposit_limit {
StorageMeter::new_unchecked(storage_deposit_limit)
} else {
StorageMeter::new(storage_deposit_limit)
};
let result = ExecStack::<T, WasmBlob<T>>::run_instantiate(
instantiate_account,
executable,
&mut gas_meter,
&mut storage_meter,
Self::convert_native_to_evm(value),
data,
salt.as_ref(),
unchecked_deposit_limit,
);
storage_deposit = storage_meter
.try_into_deposit(&instantiate_origin, unchecked_deposit_limit)?
.saturating_add(&StorageDeposit::Charge(upload_deposit));
result
};
let output = Self::run_guarded(try_instantiate);
ContractResult {
result: output
.map(|(addr, result)| InstantiateReturnValue { result, addr })
.map_err(|e| e.error),
gas_consumed: gas_meter.gas_consumed(),
gas_required: gas_meter.gas_required(),
storage_deposit,
}
}
pub fn dry_run_eth_transact(
mut tx: GenericTransaction,
gas_limit: Weight,
tx_fee: impl Fn(Call<T>, DispatchInfo) -> BalanceOf<T>,
) -> Result<EthTransactInfo<BalanceOf<T>>, EthTransactError>
where
<T as frame_system::Config>::RuntimeCall:
Dispatchable<Info = frame_support::dispatch::DispatchInfo>,
<T as Config>::RuntimeCall: From<crate::Call<T>>,
<T as Config>::RuntimeCall: Encode,
T::Nonce: Into<U256>,
T::Hash: frame_support::traits::IsType<H256>,
{
log::trace!(target: LOG_TARGET, "dry_run_eth_transact: {tx:?} gas_limit: {gas_limit:?}");
let from = tx.from.unwrap_or_default();
let origin = T::AddressMapper::to_account_id(&from);
Self::prepare_dry_run(&origin);
let storage_deposit_limit = if tx.gas.is_some() {
DepositLimit::Balance(BalanceOf::<T>::max_value())
} else {
DepositLimit::UnsafeOnlyForDryRun
};
if tx.nonce.is_none() {
tx.nonce = Some(<System<T>>::account_nonce(&origin).into());
}
if tx.chain_id.is_none() {
tx.chain_id = Some(T::ChainId::get().into());
}
if tx.gas_price.is_none() {
tx.gas_price = Some(GAS_PRICE.into());
}
if tx.max_priority_fee_per_gas.is_none() {
tx.max_priority_fee_per_gas = Some(GAS_PRICE.into());
}
if tx.max_fee_per_gas.is_none() {
tx.max_fee_per_gas = Some(GAS_PRICE.into());
}
if tx.gas.is_none() {
tx.gas = Some(Self::evm_block_gas_limit());
}
if tx.r#type.is_none() {
tx.r#type = Some(TYPE_EIP1559.into());
}
let evm_value = tx.value.unwrap_or_default();
let native_value = match Self::convert_evm_to_native(evm_value, ConversionPrecision::Exact)
{
Ok(v) => v,
Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())),
};
let input = tx.input.clone().to_vec();
let extract_error = |err| {
if err == Error::<T>::TransferFailed.into() ||
err == Error::<T>::StorageDepositNotEnoughFunds.into() ||
err == Error::<T>::StorageDepositLimitExhausted.into()
{
let balance = Self::evm_balance(&from);
return Err(EthTransactError::Message(
format!("insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})",
tx.gas.unwrap_or_default()))
);
}
return Err(EthTransactError::Message(format!(
"Failed to instantiate contract: {err:?}"
)));
};
let (mut result, dispatch_info) = match tx.to {
Some(dest) => {
let result = crate::Pallet::<T>::bare_call(
T::RuntimeOrigin::signed(origin),
dest,
native_value,
gas_limit,
storage_deposit_limit,
input.clone(),
);
let data = match result.result {
Ok(return_value) => {
if return_value.did_revert() {
return Err(EthTransactError::Data(return_value.data));
}
return_value.data
},
Err(err) => {
log::debug!(target: LOG_TARGET, "Failed to execute call: {err:?}");
return extract_error(err)
},
};
let result = EthTransactInfo {
gas_required: result.gas_required,
storage_deposit: result.storage_deposit.charge_or_zero(),
data,
eth_gas: Default::default(),
};
let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values(
result.gas_required,
result.storage_deposit,
);
let dispatch_call: <T as Config>::RuntimeCall = crate::Call::<T>::call {
dest,
value: native_value,
gas_limit,
storage_deposit_limit,
data: input.clone(),
}
.into();
(result, dispatch_call.get_dispatch_info())
},
None => {
let (code, data) = match polkavm::ProgramBlob::blob_length(&input) {
Some(blob_len) => blob_len
.try_into()
.ok()
.and_then(|blob_len| (input.split_at_checked(blob_len)))
.unwrap_or_else(|| (&input[..], &[][..])),
_ => {
log::debug!(target: LOG_TARGET, "Failed to extract polkavm blob length");
(&input[..], &[][..])
},
};
let result = crate::Pallet::<T>::bare_instantiate(
T::RuntimeOrigin::signed(origin),
native_value,
gas_limit,
storage_deposit_limit,
Code::Upload(code.to_vec()),
data.to_vec(),
None,
);
let returned_data = match result.result {
Ok(return_value) => {
if return_value.result.did_revert() {
return Err(EthTransactError::Data(return_value.result.data));
}
return_value.result.data
},
Err(err) => {
log::debug!(target: LOG_TARGET, "Failed to instantiate: {err:?}");
return extract_error(err)
},
};
let result = EthTransactInfo {
gas_required: result.gas_required,
storage_deposit: result.storage_deposit.charge_or_zero(),
data: returned_data,
eth_gas: Default::default(),
};
let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values(
result.gas_required,
result.storage_deposit,
);
let dispatch_call: <T as Config>::RuntimeCall =
crate::Call::<T>::instantiate_with_code {
value: native_value,
gas_limit,
storage_deposit_limit,
code: code.to_vec(),
data: data.to_vec(),
salt: None,
}
.into();
(result, dispatch_call.get_dispatch_info())
},
};
let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else {
return Err(EthTransactError::Message("Invalid transaction".into()));
};
let eth_dispatch_call =
crate::Call::<T>::eth_transact { payload: unsigned_tx.dummy_signed_payload() };
let fee = tx_fee(eth_dispatch_call, dispatch_info);
let raw_gas = Self::evm_fee_to_gas(fee);
let eth_gas =
T::EthGasEncoder::encode(raw_gas, result.gas_required, result.storage_deposit);
log::trace!(target: LOG_TARGET, "bare_eth_call: raw_gas: {raw_gas:?} eth_gas: {eth_gas:?}");
result.eth_gas = eth_gas;
Ok(result)
}
pub fn evm_balance(address: &H160) -> U256 {
let account = T::AddressMapper::to_account_id(&address);
Self::convert_native_to_evm(T::Currency::reducible_balance(&account, Preserve, Polite))
}
pub fn evm_fee_to_gas(fee: BalanceOf<T>) -> U256 {
let fee = Self::convert_native_to_evm(fee);
let gas_price = GAS_PRICE.into();
let (quotient, remainder) = fee.div_mod(gas_price);
if remainder.is_zero() {
quotient
} else {
quotient + U256::one()
}
}
fn evm_gas_to_fee(gas: U256, gas_price: U256) -> Result<BalanceOf<T>, Error<T>> {
let fee = gas.saturating_mul(gas_price);
Self::convert_evm_to_native(fee, ConversionPrecision::RoundUp)
}
pub fn evm_gas_from_weight(weight: Weight) -> U256 {
let fee = T::WeightPrice::convert(weight);
Self::evm_fee_to_gas(fee)
}
pub fn evm_block_gas_limit() -> U256 {
let max_block_weight = T::BlockWeights::get()
.get(DispatchClass::Normal)
.max_total
.unwrap_or_else(|| T::BlockWeights::get().max_block);
Self::evm_gas_from_weight(max_block_weight)
}
pub fn evm_gas_price() -> U256 {
GAS_PRICE.into()
}
pub fn evm_tracer(tracer_type: TracerType) -> Tracer {
match tracer_type {
TracerType::CallTracer(config) => CallTracer::new(
config.unwrap_or_default(),
Self::evm_gas_from_weight as fn(Weight) -> U256,
)
.into(),
}
}
pub fn bare_upload_code(
origin: OriginFor<T>,
code: Vec<u8>,
storage_deposit_limit: BalanceOf<T>,
) -> CodeUploadResult<BalanceOf<T>> {
let origin = T::UploadOrigin::ensure_origin(origin)?;
let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, false)?;
Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit })
}
pub fn get_storage(address: H160, key: [u8; 32]) -> GetStorageResult {
let contract_info =
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
let maybe_value = contract_info.read(&Key::from_fixed(key));
Ok(maybe_value)
}
pub fn get_storage_var_key(address: H160, key: Vec<u8>) -> GetStorageResult {
let contract_info =
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
let maybe_value = contract_info.read(
&Key::try_from_var(key)
.map_err(|_| ContractAccessError::KeyDecodingFailed)?
.into(),
);
Ok(maybe_value)
}
fn try_upload_code(
origin: T::AccountId,
code: Vec<u8>,
storage_deposit_limit: BalanceOf<T>,
skip_transfer: bool,
) -> Result<(WasmBlob<T>, BalanceOf<T>), DispatchError> {
let mut module = WasmBlob::from_code(code, origin)?;
let deposit = module.store_code(skip_transfer)?;
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
Ok((module, deposit))
}
fn run_guarded<R, F: FnOnce() -> Result<R, ExecError>>(f: F) -> Result<R, ExecError> {
executing_contract::using_once(&mut false, || {
executing_contract::with(|f| {
if *f {
return Err(())
}
*f = true;
Ok(())
})
.expect("Returns `Ok` if called within `using_once`. It is syntactically obvious that this is the case; qed")
.map_err(|_| <Error<T>>::ReenteredPallet.into())
.map(|_| f())
.and_then(|r| r)
})
}
fn convert_native_to_evm(value: BalanceOf<T>) -> U256 {
value.into().saturating_mul(T::NativeToEthRatio::get().into())
}
fn convert_evm_to_native(
value: U256,
precision: ConversionPrecision,
) -> Result<BalanceOf<T>, Error<T>> {
if value.is_zero() {
return Ok(Zero::zero())
}
let (quotient, remainder) = value.div_mod(T::NativeToEthRatio::get().into());
match (precision, remainder.is_zero()) {
(ConversionPrecision::Exact, false) => Err(Error::<T>::DecimalPrecisionLoss),
(_, true) => quotient.try_into().map_err(|_| Error::<T>::BalanceConversionFailed),
(_, false) => quotient
.saturating_add(U256::one())
.try_into()
.map_err(|_| Error::<T>::BalanceConversionFailed),
}
}
}
impl<T: Config> Pallet<T> {
fn min_balance() -> BalanceOf<T> {
<T::Currency as Inspect<AccountIdOf<T>>>::minimum_balance()
}
fn deposit_event(event: Event<T>) {
<frame_system::Pallet<T>>::deposit_event(<T as Config>::RuntimeEvent::from(event))
}
}
environmental!(executing_contract: bool);
sp_api::decl_runtime_apis! {
#[api_version(1)]
pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber> where
AccountId: Codec,
Balance: Codec,
Nonce: Codec,
BlockNumber: Codec,
{
fn block_gas_limit() -> U256;
fn balance(address: H160) -> U256;
fn gas_price() -> U256;
fn nonce(address: H160) -> Nonce;
fn call(
origin: AccountId,
dest: H160,
value: Balance,
gas_limit: Option<Weight>,
storage_deposit_limit: Option<Balance>,
input_data: Vec<u8>,
) -> ContractResult<ExecReturnValue, Balance>;
fn instantiate(
origin: AccountId,
value: Balance,
gas_limit: Option<Weight>,
storage_deposit_limit: Option<Balance>,
code: Code,
data: Vec<u8>,
salt: Option<[u8; 32]>,
) -> ContractResult<InstantiateReturnValue, Balance>;
fn eth_transact(tx: GenericTransaction) -> Result<EthTransactInfo<Balance>, EthTransactError>;
fn upload_code(
origin: AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<Balance>,
) -> CodeUploadResult<Balance>;
fn get_storage(
address: H160,
key: [u8; 32],
) -> GetStorageResult;
fn get_storage_var_key(
address: H160,
key: Vec<u8>,
) -> GetStorageResult;
fn trace_block(
block: Block,
config: TracerType
) -> Vec<(u32, Trace)>;
fn trace_tx(
block: Block,
tx_index: u32,
config: TracerType
) -> Option<Trace>;
fn trace_call(tx: GenericTransaction, config: TracerType) -> Result<Trace, EthTransactError>;
}
}
#[macro_export]
macro_rules! impl_runtime_apis_plus_revive {
($Runtime: ty, $Executive: ty, $EthExtra: ty, $($rest:tt)*) => {
impl_runtime_apis! {
$($rest)*
impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber> for $Runtime {
fn balance(address: $crate::H160) -> $crate::U256 {
$crate::Pallet::<Self>::evm_balance(&address)
}
fn block_gas_limit() -> $crate::U256 {
$crate::Pallet::<Self>::evm_block_gas_limit()
}
fn gas_price() -> $crate::U256 {
$crate::Pallet::<Self>::evm_gas_price()
}
fn nonce(address: $crate::H160) -> Nonce {
use $crate::AddressMapper;
let account = <Self as $crate::Config>::AddressMapper::to_account_id(&address);
$crate::frame_system::Pallet::<Self>::account_nonce(account)
}
fn eth_transact(
tx: $crate::evm::GenericTransaction,
) -> Result<$crate::EthTransactInfo<Balance>, $crate::EthTransactError> {
use $crate::{
codec::Encode, evm::runtime::EthExtra, frame_support::traits::Get,
sp_runtime::traits::TransactionExtension,
sp_runtime::traits::Block as BlockT
};
let tx_fee = |pallet_call, mut dispatch_info: $crate::DispatchInfo| {
let call =
<Self as $crate::frame_system::Config>::RuntimeCall::from(pallet_call);
dispatch_info.extension_weight =
<$EthExtra>::get_eth_extension(0, 0u32.into()).weight(&call);
let uxt: <Block as BlockT>::Extrinsic =
$crate::sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into();
$crate::pallet_transaction_payment::Pallet::<Self>::compute_fee(
uxt.encoded_size() as u32,
&dispatch_info,
0u32.into(),
)
};
let blockweights: $crate::BlockWeights =
<Self as $crate::frame_system::Config>::BlockWeights::get();
$crate::Pallet::<Self>::dry_run_eth_transact(tx, blockweights.max_block, tx_fee)
}
fn call(
origin: AccountId,
dest: $crate::H160,
value: Balance,
gas_limit: Option<$crate::Weight>,
storage_deposit_limit: Option<Balance>,
input_data: Vec<u8>,
) -> $crate::ContractResult<$crate::ExecReturnValue, Balance> {
use $crate::frame_support::traits::Get;
let blockweights: $crate::BlockWeights =
<Self as $crate::frame_system::Config>::BlockWeights::get();
$crate::Pallet::<Self>::prepare_dry_run(&origin);
$crate::Pallet::<Self>::bare_call(
<Self as $crate::frame_system::Config>::RuntimeOrigin::signed(origin),
dest,
value,
gas_limit.unwrap_or(blockweights.max_block),
$crate::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
input_data,
)
}
fn instantiate(
origin: AccountId,
value: Balance,
gas_limit: Option<$crate::Weight>,
storage_deposit_limit: Option<Balance>,
code: $crate::Code,
data: Vec<u8>,
salt: Option<[u8; 32]>,
) -> $crate::ContractResult<$crate::InstantiateReturnValue, Balance> {
use $crate::frame_support::traits::Get;
let blockweights: $crate::BlockWeights =
<Self as $crate::frame_system::Config>::BlockWeights::get();
$crate::Pallet::<Self>::prepare_dry_run(&origin);
$crate::Pallet::<Self>::bare_instantiate(
<Self as $crate::frame_system::Config>::RuntimeOrigin::signed(origin),
value,
gas_limit.unwrap_or(blockweights.max_block),
$crate::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)),
code,
data,
salt,
)
}
fn upload_code(
origin: AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<Balance>,
) -> $crate::CodeUploadResult<Balance> {
let origin =
<Self as $crate::frame_system::Config>::RuntimeOrigin::signed(origin);
$crate::Pallet::<Self>::bare_upload_code(
origin,
code,
storage_deposit_limit.unwrap_or(u128::MAX),
)
}
fn get_storage_var_key(
address: $crate::H160,
key: Vec<u8>,
) -> $crate::GetStorageResult {
$crate::Pallet::<Self>::get_storage_var_key(address, key)
}
fn get_storage(address: $crate::H160, key: [u8; 32]) -> $crate::GetStorageResult {
$crate::Pallet::<Self>::get_storage(address, key)
}
fn trace_block(
block: Block,
tracer_type: $crate::evm::TracerType,
) -> Vec<(u32, $crate::evm::Trace)> {
use $crate::{sp_runtime::traits::Block, tracing::trace};
let mut tracer = $crate::Pallet::<Self>::evm_tracer(tracer_type);
let mut traces = vec![];
let (header, extrinsics) = block.deconstruct();
<$Executive>::initialize_block(&header);
for (index, ext) in extrinsics.into_iter().enumerate() {
let t = tracer.as_tracing();
trace(t, || {
let _ = <$Executive>::apply_extrinsic(ext);
});
if let Some(tx_trace) = tracer.collect_trace() {
traces.push((index as u32, tx_trace));
}
}
traces
}
fn trace_tx(
block: Block,
tx_index: u32,
tracer_type: $crate::evm::TracerType,
) -> Option<$crate::evm::Trace> {
use $crate::{sp_runtime::traits::Block, tracing::trace};
let mut tracer = $crate::Pallet::<Self>::evm_tracer(tracer_type);
let (header, extrinsics) = block.deconstruct();
<$Executive>::initialize_block(&header);
for (index, ext) in extrinsics.into_iter().enumerate() {
if index as u32 == tx_index {
let t = tracer.as_tracing();
trace(t, || {
let _ = <$Executive>::apply_extrinsic(ext);
});
break;
} else {
let _ = <$Executive>::apply_extrinsic(ext);
}
}
tracer.collect_trace()
}
fn trace_call(
tx: $crate::evm::GenericTransaction,
tracer_type: $crate::evm::TracerType,
) -> Result<$crate::evm::Trace, $crate::EthTransactError> {
use $crate::tracing::trace;
let mut tracer = $crate::Pallet::<Self>::evm_tracer(tracer_type);
let t = tracer.as_tracing();
let result = trace(t, || Self::eth_transact(tx));
if let Some(trace) = tracer.collect_trace() {
Ok(trace)
} else if let Err(err) = result {
Err(err)
} else {
Ok(tracer.empty_trace())
}
}
}
}
};
}