#![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 debug;
mod exec;
mod impl_fungibles;
mod limits;
mod metering;
mod primitives;
mod storage;
#[cfg(test)]
mod tests;
mod transient_storage;
mod vm;
mod weightinfo_extension;
pub mod evm;
pub mod migrations;
pub mod mock;
pub mod precompiles;
pub mod test_utils;
pub mod tracing;
pub mod weights;
use crate::{
evm::{
block_hash::EthereumBlockBuilderIR, block_storage, fees::InfoT as FeeInfo,
runtime::SetWeightLimit, CallTracer, CreateCallMode, GenericTransaction, PrestateTracer,
Trace, Tracer, TracerType, TYPE_EIP1559,
},
exec::{AccountIdOf, ExecError, ReentrancyProtection, Stack as ExecStack},
storage::{AccountType, DeletionQueueManager},
tracing::if_tracing,
vm::{pvm::extract_code_and_data, CodeInfo, RuntimeCosts},
weightinfo_extension::OnFinalizeBlockParts,
};
use alloc::{boxed::Box, format, vec};
use codec::{Codec, Decode, Encode};
use environmental::*;
use frame_support::{
dispatch::{
DispatchErrorWithPostInfo, DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo,
Pays, PostDispatchInfo, RawOrigin,
},
ensure,
pallet_prelude::DispatchClass,
traits::{
fungible::{Balanced, Inspect, Mutate, MutateHold},
tokens::Balance,
ConstU32, ConstU64, EnsureOrigin, Get, IsSubType, IsType, OriginTrait,
},
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, UniqueSaturatedFrom,
UniqueSaturatedInto, Zero,
},
AccountId32, DispatchError, FixedPointNumber, FixedU128, SaturatedConversion,
};
pub use crate::{
address::{
create1, create2, is_eth_derived, AccountId32Mapper, AddressMapper, TestAccountMapper,
},
debug::DebugSettings,
evm::{
block_hash::ReceiptGasInfo, Address as EthAddress, Block as EthBlock, DryRunConfig,
ReceiptInfo,
},
exec::{CallResources, DelegateInfo, Executable, Key, MomentOf, Origin as ExecOrigin},
metering::{
EthTxInfo, FrameMeter, ResourceMeter, Token as WeightToken, TransactionLimits,
TransactionMeter,
},
pallet::{genesis, *},
storage::{AccountInfo, ContractInfo},
vm::{BytecodeType, ContractBlob},
};
pub use codec;
pub use frame_support::{self, dispatch::DispatchInfo, traits::Time, weights::Weight};
pub use frame_system::{self, limits::BlockWeights};
pub use primitives::*;
pub use sp_core::{keccak_256, H160, H256, U256};
pub use sp_runtime;
pub use weights::WeightInfo;
#[cfg(doc)]
pub use crate::vm::pvm::SyscallDoc;
pub type BalanceOf<T> = <T as Config>::Balance;
type TrieId = BoundedVec<u8, ConstU32<128>>;
type ImmutableData = BoundedVec<u8, ConstU32<{ limits::IMMUTABLE_BYTES }>>;
type CallOf<T> = <T as Config>::RuntimeCall;
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<Moment: Into<U256>>;
#[pallet::no_default]
type Balance: Balance
+ TryFrom<U256>
+ Into<U256>
+ Bounded
+ UniqueSaturatedInto<u64>
+ UniqueSaturatedFrom<u64>
+ UniqueSaturatedInto<u128>;
#[pallet::no_default]
type Currency: Inspect<Self::AccountId, Balance = Self::Balance>
+ Mutate<Self::AccountId>
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
+ Balanced<Self::AccountId>;
#[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 = OriginFor<Self>,
Info = DispatchInfo,
PostInfo = PostDispatchInfo,
> + IsType<<Self as frame_system::Config>::RuntimeCall>
+ From<Call<Self>>
+ IsSubType<Call<Self>>
+ GetDispatchInfo;
#[pallet::no_default_bounds]
type RuntimeOrigin: IsType<OriginFor<Self>>
+ From<Origin<Self>>
+ Into<Result<Origin<Self>, OriginFor<Self>>>;
#[pallet::no_default_bounds]
type RuntimeHoldReason: From<HoldReason>;
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]
#[pallet::no_default_bounds]
type DepositPerChildTrieItem: Get<BalanceOf<Self>>;
#[pallet::constant]
type CodeHashLockupDepositPercent: Get<Perbill>;
#[pallet::no_default]
type AddressMapper: AddressMapper<Self>;
#[pallet::constant]
type AllowEVMBytecode: Get<bool>;
#[pallet::no_default_bounds]
type UploadOrigin: EnsureOrigin<OriginFor<Self>, Success = Self::AccountId>;
#[pallet::no_default_bounds]
type InstantiateOrigin: EnsureOrigin<OriginFor<Self>, 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 FeeInfo: FeeInfo<Self>;
#[pallet::constant]
type MaxEthExtrinsicWeight: Get<FixedU128>;
#[pallet::constant]
type DebugEnabled: Get<bool>;
#[pallet::constant]
#[pallet::no_default_bounds]
type GasScale: Get<u32>;
}
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;
pub const DOLLARS: Balance = 1_000_000_000_000;
pub const CENTS: Balance = DOLLARS / 100;
pub const MILLICENTS: Balance = CENTS / 1_000;
pub const fn deposit(items: u32, bytes: u32) -> Balance {
items as Balance * 20 * CENTS + (bytes as Balance) * MILLICENTS
}
parameter_types! {
pub const DepositPerItem: Balance = deposit(1, 0);
pub const DepositPerChildTrieItem: Balance = deposit(1, 0) / 100;
pub const DepositPerByte: Balance = deposit(0, 1);
pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0);
pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(9, 10);
pub const GasScale: u32 = 10u32;
}
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 = ();
#[inject_runtime_type]
type RuntimeOrigin = ();
type Precompiles = ();
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type DepositPerByte = DepositPerByte;
type DepositPerItem = DepositPerItem;
type DepositPerChildTrieItem = DepositPerChildTrieItem;
type Time = Self;
type AllowEVMBytecode = ConstBool<true>;
type UploadOrigin = EnsureSigned<Self::AccountId>;
type InstantiateOrigin = EnsureSigned<Self::AccountId>;
type WeightInfo = ();
type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>;
type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>;
type ChainId = ConstU64<42>;
type NativeToEthRatio = ConstU32<1_000_000>;
type FindAuthor = ();
type FeeInfo = ();
type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight;
type DebugEnabled = ConstBool<false>;
type GasScale = GasScale;
}
}
#[pallet::event]
pub enum Event<T: Config> {
ContractEmitted {
contract: H160,
data: Vec<u8>,
topics: Vec<H256>,
},
Instantiated { deployer: H160, contract: H160 },
EthExtrinsicRevert { dispatch_error: DispatchError },
}
#[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,
InvalidImmutableAccess = 0x2A,
AccountUnmapped = 0x2B,
AccountAlreadyMapped = 0x2C,
InvalidGenericTransaction = 0x2D,
RefcountOverOrUnderflow = 0x2E,
UnsupportedPrecompileAddress = 0x2F,
CallDataTooLarge = 0x30,
ReturnDataTooLarge = 0x31,
InvalidJump = 0x32,
StackUnderflow = 0x33,
StackOverflow = 0x34,
TxFeeOverdraw = 0x35,
EvmConstructorNonEmptyData = 0x36,
EvmConstructedFromHash = 0x37,
StorageRefundNotEnoughFunds = 0x38,
StorageRefundLocked = 0x39,
PrecompileDelegateDenied = 0x40,
EcdsaRecoveryFailed = 0x41,
#[cfg(feature = "runtime-benchmarks")]
BenchmarkingError = 0xFF,
}
#[pallet::composite_enum]
pub enum HoldReason {
CodeUploadDepositReserve,
StorageDepositReserve,
AddressMapping,
}
#[derive(
PartialEq,
Eq,
Clone,
MaxEncodedLen,
Encode,
Decode,
DecodeWithMemTracking,
TypeInfo,
RuntimeDebug,
)]
#[pallet::origin]
pub enum Origin<T: Config> {
EthTransaction(T::AccountId),
}
#[pallet::storage]
#[pallet::unbounded]
pub(crate) type PristineCode<T: Config> = StorageMap<_, Identity, H256, Vec<u8>>;
#[pallet::storage]
pub(crate) type CodeInfoOf<T: Config> = StorageMap<_, Identity, H256, CodeInfo<T>>;
#[pallet::storage]
pub(crate) type AccountInfoOf<T: Config> = StorageMap<_, Identity, H160, AccountInfo<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::storage]
#[pallet::unbounded]
pub(crate) type EthereumBlock<T> = StorageValue<_, EthBlock, ValueQuery>;
#[pallet::storage]
pub(crate) type BlockHash<T: Config> =
StorageMap<_, Identity, BlockNumberFor<T>, H256, ValueQuery>;
#[pallet::storage]
#[pallet::unbounded]
pub(crate) type ReceiptInfoData<T: Config> = StorageValue<_, Vec<ReceiptGasInfo>, ValueQuery>;
#[pallet::storage]
#[pallet::unbounded]
pub(crate) type EthBlockBuilderIR<T: Config> =
StorageValue<_, EthereumBlockBuilderIR<T>, ValueQuery>;
#[pallet::storage]
#[pallet::unbounded]
pub(crate) type EthBlockBuilderFirstValues<T: Config> =
StorageValue<_, Option<(Vec<u8>, Vec<u8>)>, ValueQuery>;
#[pallet::storage]
pub(crate) type DebugSettingsOf<T: Config> = StorageValue<_, DebugSettings, ValueQuery>;
pub mod genesis {
use super::*;
use crate::evm::Bytes32;
#[derive(Clone, PartialEq, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ContractData {
pub code: Vec<u8>,
pub storage: alloc::collections::BTreeMap<Bytes32, Bytes32>,
}
#[derive(PartialEq, Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Account<T: Config> {
pub address: H160,
#[serde(default)]
pub balance: U256,
#[serde(default)]
pub nonce: T::Nonce,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub contract_data: Option<ContractData>,
}
}
#[pallet::genesis_config]
#[derive(Debug, PartialEq, frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub mapped_accounts: Vec<T::AccountId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub accounts: Vec<genesis::Account<T>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub debug_settings: Option<DebugSettings>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
use crate::{exec::Key, vm::ContractBlob};
use frame_support::traits::fungible::Mutate;
if !System::<T>::account_exists(&Pallet::<T>::account_id()) {
let _ = T::Currency::mint_into(
&Pallet::<T>::account_id(),
T::Currency::minimum_balance(),
);
}
for id in &self.mapped_accounts {
if let Err(err) = T::AddressMapper::map_no_deposit(id) {
log::error!(target: LOG_TARGET, "Failed to map account {id:?}: {err:?}");
}
}
let owner = Pallet::<T>::account_id();
for genesis::Account { address, balance, nonce, contract_data } in &self.accounts {
let account_id = T::AddressMapper::to_account_id(address);
if !System::<T>::account_exists(&account_id) {
let _ = T::Currency::mint_into(&account_id, T::Currency::minimum_balance());
}
frame_system::Account::<T>::mutate(&account_id, |info| {
info.nonce = (*nonce).into();
});
match contract_data {
None => {
AccountInfoOf::<T>::insert(
address,
AccountInfo { account_type: AccountType::EOA, dust: 0 },
);
},
Some(genesis::ContractData { code, storage }) => {
let blob = if code.starts_with(&polkavm_common::program::BLOB_MAGIC) {
ContractBlob::<T>::from_pvm_code( code.clone(), owner.clone()).inspect_err(|err| {
log::error!(target: LOG_TARGET, "Failed to create PVM ContractBlob for {address:?}: {err:?}");
})
} else {
ContractBlob::<T>::from_evm_runtime_code(code.clone(), account_id).inspect_err(|err| {
log::error!(target: LOG_TARGET, "Failed to create EVM ContractBlob for {address:?}: {err:?}");
})
};
let Ok(blob) = blob else {
continue;
};
let code_hash = *blob.code_hash();
let Ok(info) = <ContractInfo<T>>::new(&address, 0u32.into(), code_hash)
.inspect_err(|err| {
log::error!(target: LOG_TARGET, "Failed to create ContractInfo for {address:?}: {err:?}");
})
else {
continue;
};
AccountInfoOf::<T>::insert(
address,
AccountInfo { account_type: info.clone().into(), dust: 0 },
);
<PristineCode<T>>::insert(blob.code_hash(), code);
<CodeInfoOf<T>>::insert(blob.code_hash(), blob.code_info().clone());
for (k, v) in storage {
let _ = info.write(&Key::from_fixed(k.0), Some(v.0.to_vec()), None, false).inspect_err(|err| {
log::error!(target: LOG_TARGET, "Failed to write genesis storage for {address:?} at key {k:?}: {err:?}");
});
}
},
}
let _ = Pallet::<T>::set_evm_balance(address, *balance).inspect_err(|err| {
log::error!(target: LOG_TARGET, "Failed to set EVM balance for {address:?}: {err:?}");
});
}
block_storage::on_finalize_build_eth_block::<T>(
frame_system::Pallet::<T>::block_number(),
);
if let Some(settings) = self.debug_settings.as_ref() {
settings.write_to_storage::<T>()
}
}
}
#[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 on_initialize(_n: BlockNumberFor<T>) -> Weight {
block_storage::on_initialize::<T>();
System::<T>::account_exists(&Pallet::<T>::account_id());
<T as Config>::WeightInfo::on_finalize_block_fixed()
}
fn on_finalize(block_number: BlockNumberFor<T>) {
block_storage::on_finalize_build_eth_block::<T>(block_number);
}
fn integrity_test() {
assert!(T::ChainId::get() > 0, "ChainId must be greater than 0");
assert!(T::GasScale::get() > 0u32.into(), "GasScale must not be 0");
T::FeeInfo::integrity_test();
let max_runtime_mem: u64 = T::RuntimeMemory::get().into();
const TOTAL_MEMORY_DEVIDER: u64 = 2;
let max_block_weight = T::BlockWeights::get()
.get(DispatchClass::Normal)
.max_total
.unwrap_or_else(|| T::BlockWeights::get().max_block);
let max_key_size: u64 =
Key::try_from_var(alloc::vec![0u8; limits::STORAGE_KEY_BYTES as usize])
.expect("Key of maximal size shall be created")
.hash()
.len()
.try_into()
.unwrap();
let max_immutable_key_size: u64 = T::AccountId::max_encoded_len().try_into().unwrap();
let max_immutable_size: u64 = max_block_weight
.checked_div_per_component(&<RuntimeCosts as WeightToken<T>>::weight(
&RuntimeCosts::SetImmutableData(limits::IMMUTABLE_BYTES),
))
.unwrap()
.saturating_mul(
u64::from(limits::IMMUTABLE_BYTES)
.saturating_add(max_immutable_key_size)
.into(),
);
let max_pvf_mem: u64 = T::PVFMemory::get().into();
let storage_size_limit = max_pvf_mem.saturating_sub(max_runtime_mem) / 2;
let max_events_size = max_block_weight
.checked_div_per_component(
&(<RuntimeCosts as WeightToken<T>>::weight(&RuntimeCosts::DepositEvent {
num_topic: 0,
len: limits::EVENT_BYTES,
})
.saturating_add(<RuntimeCosts as WeightToken<T>>::weight(
&RuntimeCosts::HostFn,
))),
)
.unwrap()
.saturating_mul(limits::EVENT_BYTES.into());
assert!(
max_events_size <= storage_size_limit,
"Maximal events size {} exceeds the events limit {}",
max_events_size,
storage_size_limit
);
let max_eth_block_builder_bytes =
block_storage::block_builder_bytes_usage(max_events_size.try_into().unwrap());
log::debug!(
target: LOG_TARGET,
"Integrity check: max_eth_block_builder_bytes={} KB using max_events_size={} KB",
max_eth_block_builder_bytes / 1024,
max_events_size / 1024,
);
let memory_left = i128::from(max_runtime_mem)
.saturating_div(TOTAL_MEMORY_DEVIDER.into())
.saturating_sub(limits::MEMORY_REQUIRED.into())
.saturating_sub(max_eth_block_builder_bytes.into());
log::debug!(target: LOG_TARGET, "Integrity check: memory_left={} KB", memory_left / 1024);
assert!(
memory_left >= 0,
"Runtime does not have enough memory for current limits. Additional runtime memory required: {} KB",
memory_left.saturating_mul(TOTAL_MEMORY_DEVIDER.into()).abs() / 1024
);
let max_storage_size = max_block_weight
.checked_div_per_component(
&<RuntimeCosts as WeightToken<T>>::weight(&RuntimeCosts::SetStorage {
new_bytes: limits::STORAGE_BYTES,
old_bytes: 0,
})
.saturating_mul(u64::from(limits::STORAGE_BYTES).saturating_add(max_key_size)),
)
.unwrap()
.saturating_add(max_immutable_size.into())
.saturating_add(max_eth_block_builder_bytes.into());
assert!(
max_storage_size <= storage_size_limit,
"Maximal storage size {} exceeds the storage limit {}",
max_storage_size,
storage_size_limit
);
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[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 as Config>::WeightInfo::call().saturating_add(*weight_limit))]
pub fn call(
origin: OriginFor<T>,
dest: H160,
#[pallet::compact] value: BalanceOf<T>,
weight_limit: Weight,
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo {
Self::ensure_non_contract_if_signed(&origin)?;
let mut output = Self::bare_call(
origin,
dest,
Pallet::<T>::convert_native_to_evm(value),
TransactionLimits::WeightAndDeposit {
weight_limit,
deposit_limit: storage_deposit_limit,
},
data,
ExecConfig::new_substrate_tx(),
);
if let Ok(return_value) = &output.result {
if return_value.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
dispatch_result(
output.result,
output.weight_consumed,
<T as Config>::WeightInfo::call(),
)
}
#[pallet::call_index(2)]
#[pallet::weight(
<T as Config>::WeightInfo::instantiate(data.len() as u32).saturating_add(*weight_limit)
)]
pub fn instantiate(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
weight_limit: Weight,
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
code_hash: sp_core::H256,
data: Vec<u8>,
salt: Option<[u8; 32]>,
) -> DispatchResultWithPostInfo {
Self::ensure_non_contract_if_signed(&origin)?;
let data_len = data.len() as u32;
let mut output = Self::bare_instantiate(
origin,
Pallet::<T>::convert_native_to_evm(value),
TransactionLimits::WeightAndDeposit {
weight_limit,
deposit_limit: storage_deposit_limit,
},
Code::Existing(code_hash),
data,
salt,
ExecConfig::new_substrate_tx(),
);
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.weight_consumed,
<T as Config>::WeightInfo::instantiate(data_len),
)
}
#[pallet::call_index(3)]
#[pallet::weight(
<T as Config>::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32)
.saturating_add(*weight_limit)
)]
pub fn instantiate_with_code(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
weight_limit: Weight,
#[pallet::compact] storage_deposit_limit: BalanceOf<T>,
code: Vec<u8>,
data: Vec<u8>,
salt: Option<[u8; 32]>,
) -> DispatchResultWithPostInfo {
Self::ensure_non_contract_if_signed(&origin)?;
let code_len = code.len() as u32;
let data_len = data.len() as u32;
let mut output = Self::bare_instantiate(
origin,
Pallet::<T>::convert_native_to_evm(value),
TransactionLimits::WeightAndDeposit {
weight_limit,
deposit_limit: storage_deposit_limit,
},
Code::Upload(code),
data,
salt,
ExecConfig::new_substrate_tx(),
);
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.weight_consumed,
<T as Config>::WeightInfo::instantiate_with_code(code_len, data_len),
)
}
#[pallet::call_index(10)]
#[pallet::weight(
<T as Config>::WeightInfo::eth_instantiate_with_code(code.len() as u32, data.len() as u32, Pallet::<T>::has_dust(*value).into())
.saturating_add(*weight_limit)
)]
pub fn eth_instantiate_with_code(
origin: OriginFor<T>,
value: U256,
weight_limit: Weight,
eth_gas_limit: U256,
code: Vec<u8>,
data: Vec<u8>,
transaction_encoded: Vec<u8>,
effective_gas_price: U256,
encoded_len: u32,
) -> DispatchResultWithPostInfo {
let signer = Self::ensure_eth_signed(origin)?;
let origin = OriginFor::<T>::signed(signer.clone());
Self::ensure_non_contract_if_signed(&origin)?;
let mut call = Call::<T>::eth_instantiate_with_code {
value,
weight_limit,
eth_gas_limit,
code: code.clone(),
data: data.clone(),
transaction_encoded: transaction_encoded.clone(),
effective_gas_price,
encoded_len,
}
.into();
let info = T::FeeInfo::dispatch_info(&call);
let base_info = T::FeeInfo::base_dispatch_info(&mut call);
drop(call);
block_storage::with_ethereum_context::<T>(transaction_encoded, || {
let extra_weight = base_info.total_weight();
let output = Self::bare_instantiate(
origin,
value,
TransactionLimits::EthereumGas {
eth_gas_limit: eth_gas_limit.saturated_into(),
maybe_weight_limit: Some(weight_limit),
eth_tx_info: EthTxInfo::new(encoded_len, extra_weight),
},
Code::Upload(code),
data,
None,
ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight),
);
block_storage::EthereumCallResult::new::<T>(
signer,
output.map_result(|r| r.result),
base_info.call_weight,
encoded_len,
&info,
effective_gas_price,
)
})
}
#[pallet::call_index(11)]
#[pallet::weight(
T::WeightInfo::eth_call(Pallet::<T>::has_dust(*value).into())
.saturating_add(*weight_limit)
.saturating_add(T::WeightInfo::on_finalize_block_per_tx(transaction_encoded.len() as u32))
)]
pub fn eth_call(
origin: OriginFor<T>,
dest: H160,
value: U256,
weight_limit: Weight,
eth_gas_limit: U256,
data: Vec<u8>,
transaction_encoded: Vec<u8>,
effective_gas_price: U256,
encoded_len: u32,
) -> DispatchResultWithPostInfo {
let signer = Self::ensure_eth_signed(origin)?;
let origin = OriginFor::<T>::signed(signer.clone());
Self::ensure_non_contract_if_signed(&origin)?;
let mut call = Call::<T>::eth_call {
dest,
value,
weight_limit,
eth_gas_limit,
data: data.clone(),
transaction_encoded: transaction_encoded.clone(),
effective_gas_price,
encoded_len,
}
.into();
let info = T::FeeInfo::dispatch_info(&call);
let base_info = T::FeeInfo::base_dispatch_info(&mut call);
drop(call);
block_storage::with_ethereum_context::<T>(transaction_encoded, || {
let extra_weight = base_info.total_weight();
let output = Self::bare_call(
origin,
dest,
value,
TransactionLimits::EthereumGas {
eth_gas_limit: eth_gas_limit.saturated_into(),
maybe_weight_limit: Some(weight_limit),
eth_tx_info: EthTxInfo::new(encoded_len, extra_weight),
},
data,
ExecConfig::new_eth_tx(effective_gas_price, encoded_len, extra_weight),
);
block_storage::EthereumCallResult::new::<T>(
signer,
output,
base_info.call_weight,
encoded_len,
&info,
effective_gas_price,
)
})
}
#[pallet::call_index(12)]
#[pallet::weight(T::WeightInfo::eth_substrate_call(transaction_encoded.len() as u32).saturating_add(call.get_dispatch_info().call_weight))]
pub fn eth_substrate_call(
origin: OriginFor<T>,
call: Box<<T as Config>::RuntimeCall>,
transaction_encoded: Vec<u8>,
) -> DispatchResultWithPostInfo {
let signer = Self::ensure_eth_signed(origin)?;
let weight_overhead =
T::WeightInfo::eth_substrate_call(transaction_encoded.len() as u32);
block_storage::with_ethereum_context::<T>(transaction_encoded, || {
let call_weight = call.get_dispatch_info().call_weight;
let mut call_result = call.dispatch(RawOrigin::Signed(signer).into());
match &mut call_result {
Ok(post_info) | Err(DispatchErrorWithPostInfo { post_info, .. }) => {
post_info.actual_weight = Some(
post_info
.actual_weight
.unwrap_or_else(|| call_weight)
.saturating_add(weight_overhead),
);
},
}
block_storage::EthereumCallResult {
receipt_gas_info: ReceiptGasInfo::default(),
result: call_result,
}
})
}
#[pallet::call_index(4)]
#[pallet::weight(<T as Config>::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::ensure_non_contract_if_signed(&origin)?;
Self::bare_upload_code(origin, code, storage_deposit_limit).map(|_| ())
}
#[pallet::call_index(5)]
#[pallet::weight(<T as Config>::WeightInfo::remove_code())]
pub fn remove_code(
origin: OriginFor<T>,
code_hash: sp_core::H256,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
<ContractBlob<T>>::remove(&origin, code_hash)?;
Ok(Pays::No.into())
}
#[pallet::call_index(6)]
#[pallet::weight(<T as Config>::WeightInfo::set_code())]
pub fn set_code(
origin: OriginFor<T>,
dest: H160,
code_hash: sp_core::H256,
) -> DispatchResult {
ensure_root(origin)?;
<AccountInfoOf<T>>::try_mutate(&dest, |account| {
let Some(account) = account else {
return Err(<Error<T>>::ContractNotFound.into());
};
let AccountType::Contract(ref mut contract) = account.account_type else {
return Err(<Error<T>>::ContractNotFound.into());
};
<CodeInfo<T>>::increment_refcount(code_hash)?;
let _ = <CodeInfo<T>>::decrement_refcount(contract.code_hash)?;
contract.code_hash = code_hash;
Ok(())
})
}
#[pallet::call_index(7)]
#[pallet::weight(<T as Config>::WeightInfo::map_account())]
pub fn map_account(origin: OriginFor<T>) -> DispatchResult {
Self::ensure_non_contract_if_signed(&origin)?;
let origin = ensure_signed(origin)?;
T::AddressMapper::map(&origin)
}
#[pallet::call_index(8)]
#[pallet::weight(<T as Config>::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 as Config>::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 {
Self::ensure_non_contract_if_signed(&origin)?;
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>,
weight_consumed: Weight,
base_weight: Weight,
) -> DispatchResultWithPostInfo {
let post_info = PostDispatchInfo {
actual_weight: Some(weight_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> {
pub fn bare_call(
origin: OriginFor<T>,
dest: H160,
evm_value: U256,
transaction_limits: TransactionLimits<T>,
data: Vec<u8>,
exec_config: ExecConfig<T>,
) -> ContractResult<ExecReturnValue, BalanceOf<T>> {
let mut transaction_meter = match TransactionMeter::new(transaction_limits) {
Ok(transaction_meter) => transaction_meter,
Err(error) => return ContractResult { result: Err(error), ..Default::default() },
};
let mut storage_deposit = Default::default();
let try_call = || {
let origin = ExecOrigin::from_runtime_origin(origin)?;
let result = ExecStack::<T, ContractBlob<T>>::run_call(
origin.clone(),
dest,
&mut transaction_meter,
evm_value,
data,
&exec_config,
)?;
storage_deposit = transaction_meter
.execute_postponed_deposits(&origin, &exec_config)
.inspect_err(|err| {
log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}");
})?;
Ok(result)
};
let result = Self::run_guarded(try_call);
log::trace!(target: LOG_TARGET, "Bare call ends: \
result={result:?}, \
weight_consumed={:?}, \
weight_required={:?}, \
storage_deposit={:?}, \
gas_consumed={:?}, \
max_storage_deposit={:?}",
transaction_meter.weight_consumed(),
transaction_meter.weight_required(),
storage_deposit,
transaction_meter.total_consumed_gas(),
transaction_meter.deposit_required()
);
ContractResult {
result: result.map_err(|r| r.error),
weight_consumed: transaction_meter.weight_consumed(),
weight_required: transaction_meter.weight_required(),
storage_deposit,
gas_consumed: transaction_meter.total_consumed_gas(),
max_storage_deposit: transaction_meter.deposit_required(),
}
}
pub fn prepare_dry_run(account: &T::AccountId) {
frame_system::Pallet::<T>::inc_account_nonce(account);
}
pub fn bare_instantiate(
origin: OriginFor<T>,
evm_value: U256,
transaction_limits: TransactionLimits<T>,
code: Code,
data: Vec<u8>,
salt: Option<[u8; 32]>,
exec_config: ExecConfig<T>,
) -> ContractResult<InstantiateReturnValue, BalanceOf<T>> {
let mut transaction_meter = match TransactionMeter::new(transaction_limits) {
Ok(transaction_meter) => transaction_meter,
Err(error) => return ContractResult { result: Err(error), ..Default::default() },
};
let mut storage_deposit = Default::default();
let try_instantiate = || {
let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?;
if_tracing(|t| t.instantiate_code(&code, salt.as_ref()));
let executable = match code {
Code::Upload(code) if code.starts_with(&polkavm_common::program::BLOB_MAGIC) => {
let upload_account = T::UploadOrigin::ensure_origin(origin)?;
let executable = Self::try_upload_code(
upload_account,
code,
BytecodeType::Pvm,
&mut transaction_meter,
&exec_config,
)?;
executable
},
Code::Upload(code) =>
if T::AllowEVMBytecode::get() {
ensure!(data.is_empty(), <Error<T>>::EvmConstructorNonEmptyData);
let origin = T::UploadOrigin::ensure_origin(origin)?;
let executable = ContractBlob::from_evm_init_code(code, origin)?;
executable
} else {
return Err(<Error<T>>::CodeRejected.into())
},
Code::Existing(code_hash) => {
let executable = ContractBlob::from_storage(code_hash, &mut transaction_meter)?;
ensure!(executable.code_info().is_pvm(), <Error<T>>::EvmConstructedFromHash);
executable
},
};
let instantiate_origin = ExecOrigin::from_account_id(instantiate_account.clone());
let result = ExecStack::<T, ContractBlob<T>>::run_instantiate(
instantiate_account,
executable,
&mut transaction_meter,
evm_value,
data,
salt.as_ref(),
&exec_config,
);
storage_deposit = transaction_meter
.execute_postponed_deposits(&instantiate_origin, &exec_config)
.inspect_err(|err| {
log::debug!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}");
})?;
result
};
let output = Self::run_guarded(try_instantiate);
log::trace!(target: LOG_TARGET, "Bare instantiate ends: weight_consumed={:?}\
weight_required={:?} \
storage_deposit={:?} \
gas_consumed={:?} \
max_storage_deposit={:?}",
transaction_meter.weight_consumed(),
transaction_meter.weight_required(),
storage_deposit,
transaction_meter.total_consumed_gas(),
transaction_meter.deposit_required()
);
ContractResult {
result: output
.map(|(addr, result)| InstantiateReturnValue { result, addr })
.map_err(|e| e.error),
weight_consumed: transaction_meter.weight_consumed(),
weight_required: transaction_meter.weight_required(),
storage_deposit,
gas_consumed: transaction_meter.total_consumed_gas(),
max_storage_deposit: transaction_meter.deposit_required(),
}
}
pub fn dry_run_eth_transact(
mut tx: GenericTransaction,
dry_run_config: DryRunConfig<<<T as Config>::Time as Time>::Moment>,
) -> Result<EthTransactInfo<BalanceOf<T>>, EthTransactError>
where
T::Nonce: Into<U256>,
CallOf<T>: SetWeightLimit,
{
log::debug!(target: LOG_TARGET, "dry_run_eth_transact: {tx:?}");
let origin = T::AddressMapper::to_account_id(&tx.from.unwrap_or_default());
Self::prepare_dry_run(&origin);
let base_fee = Self::evm_base_fee();
let effective_gas_price = tx.effective_gas_price(base_fee).unwrap_or(base_fee);
if effective_gas_price < base_fee {
Err(EthTransactError::Message(format!(
"Effective gas price {effective_gas_price:?} lower than base fee {base_fee:?}"
)))?;
}
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());
}
tx.gas_price = Some(effective_gas_price);
tx.max_priority_fee_per_gas = Some(0.into());
if tx.max_fee_per_gas.is_none() {
tx.max_fee_per_gas = Some(effective_gas_price);
}
let gas = tx.gas;
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 value = tx.value.unwrap_or_default();
let input = tx.input.clone().to_vec();
let from = tx.from;
let to = tx.to;
let mut call_info = tx
.into_call::<T>(CreateCallMode::DryRun)
.map_err(|err| EthTransactError::Message(format!("Invalid call: {err:?}")))?;
let base_info = T::FeeInfo::base_dispatch_info(&mut call_info.call);
let base_weight = base_info.total_weight();
let exec_config =
ExecConfig::new_eth_tx(effective_gas_price, call_info.encoded_len, base_weight)
.with_dry_run(dry_run_config);
let fees = call_info.tx_fee.saturating_add(call_info.storage_deposit);
if let Some(from) = &from {
let fees = if gas.is_some() { fees } else { Zero::zero() };
let balance = Self::evm_balance(from);
if balance < Pallet::<T>::convert_native_to_evm(fees).saturating_add(value) {
return Err(EthTransactError::Message(format!(
"insufficient funds for gas * price + value ({fees:?}): address {from:?} have {balance:?} (supplied gas {gas:?})",
)));
}
}
T::FeeInfo::deposit_txfee(T::Currency::issue(fees));
let extract_error = |err| {
if err == Error::<T>::StorageDepositNotEnoughFunds.into() {
Err(EthTransactError::Message(format!("Not enough gas supplied: {err:?}")))
} else {
Err(EthTransactError::Message(format!("failed to run contract: {err:?}")))
}
};
let transaction_limits = TransactionLimits::EthereumGas {
eth_gas_limit: call_info.eth_gas_limit.saturated_into(),
maybe_weight_limit: None,
eth_tx_info: EthTxInfo::new(call_info.encoded_len, base_weight),
};
let mut dry_run = match to {
Some(dest) => {
if dest == RUNTIME_PALLETS_ADDR {
let Ok(dispatch_call) = <CallOf<T>>::decode(&mut &input[..]) else {
return Err(EthTransactError::Message(format!(
"Failed to decode pallet-call {input:?}"
)));
};
if let Err(result) =
dispatch_call.clone().dispatch(RawOrigin::Signed(origin).into())
{
return Err(EthTransactError::Message(format!(
"Failed to dispatch call: {:?}",
result.error,
)));
};
Default::default()
} else {
let result = crate::Pallet::<T>::bare_call(
OriginFor::<T>::signed(origin),
dest,
value,
transaction_limits,
input.clone(),
exec_config,
);
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);
},
};
EthTransactInfo {
weight_required: result.weight_required,
storage_deposit: result.storage_deposit.charge_or_zero(),
max_storage_deposit: result.max_storage_deposit.charge_or_zero(),
data,
eth_gas: Default::default(),
}
}
},
None => {
let (code, data) = if input.starts_with(&polkavm_common::program::BLOB_MAGIC) {
extract_code_and_data(&input).unwrap_or_else(|| (input, Default::default()))
} else {
(input, vec![])
};
let result = crate::Pallet::<T>::bare_instantiate(
OriginFor::<T>::signed(origin),
value,
transaction_limits,
Code::Upload(code.clone()),
data.clone(),
None,
exec_config,
);
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);
},
};
EthTransactInfo {
weight_required: result.weight_required,
storage_deposit: result.storage_deposit.charge_or_zero(),
max_storage_deposit: result.max_storage_deposit.charge_or_zero(),
data: returned_data,
eth_gas: Default::default(),
}
},
};
call_info.call.set_weight_limit(dry_run.weight_required);
let total_weight = T::FeeInfo::dispatch_info(&call_info.call).total_weight();
let max_weight = Self::evm_max_extrinsic_weight();
if total_weight.any_gt(max_weight) {
log::debug!(target: LOG_TARGET, "Transaction weight estimate exceeds extrinsic maximum: \
total_weight={total_weight:?} \
max_weight={max_weight:?}",
);
Err(EthTransactError::Message(format!(
"\
The transaction consumes more than the allowed weight. \
needed={total_weight} \
allowed={max_weight} \
overweight_by={}\
",
total_weight.saturating_sub(max_weight),
)))?;
}
let transaction_fee = T::FeeInfo::tx_fee(call_info.encoded_len, &call_info.call);
let available_fee = T::FeeInfo::remaining_txfee();
if transaction_fee > available_fee {
Err(EthTransactError::Message(format!(
"Not enough gas supplied: Off by: {:?}",
transaction_fee.saturating_sub(available_fee),
)))?;
}
let total_cost = transaction_fee.saturating_add(dry_run.max_storage_deposit);
let total_cost_wei = Pallet::<T>::convert_native_to_evm(total_cost);
let (mut eth_gas, rest) = total_cost_wei.div_mod(base_fee);
if !rest.is_zero() {
eth_gas = eth_gas.saturating_add(1_u32.into());
}
log::debug!(target: LOG_TARGET, "\
dry_run_eth_transact finished: \
weight_limit={}, \
total_weight={total_weight}, \
max_weight={max_weight}, \
weight_left={}, \
eth_gas={eth_gas}, \
encoded_len={}, \
tx_fee={transaction_fee:?}, \
storage_deposit={:?}, \
max_storage_deposit={:?}\
",
dry_run.weight_required,
max_weight.saturating_sub(total_weight),
call_info.encoded_len,
dry_run.storage_deposit,
dry_run.max_storage_deposit,
);
dry_run.eth_gas = eth_gas;
Ok(dry_run)
}
pub fn evm_balance(address: &H160) -> U256 {
let balance = AccountInfo::<T>::balance_of((*address).into());
Self::convert_native_to_evm(balance)
}
pub fn eth_block() -> EthBlock {
EthereumBlock::<T>::get()
}
pub fn eth_block_hash_from_number(number: U256) -> Option<H256> {
let number = BlockNumberFor::<T>::try_from(number).ok()?;
let hash = <BlockHash<T>>::get(number);
if hash == H256::zero() {
None
} else {
Some(hash)
}
}
pub fn eth_receipt_data() -> Vec<ReceiptGasInfo> {
ReceiptInfoData::<T>::get()
}
pub fn set_evm_balance(address: &H160, evm_value: U256) -> Result<(), Error<T>> {
let (balance, dust) = Self::new_balance_with_dust(evm_value)
.map_err(|_| <Error<T>>::BalanceConversionFailed)?;
let account_id = T::AddressMapper::to_account_id(&address);
T::Currency::set_balance(&account_id, balance);
AccountInfoOf::<T>::mutate(&address, |account| {
if let Some(account) = account {
account.dust = dust;
} else {
*account = Some(AccountInfo { dust, ..Default::default() });
}
});
Ok(())
}
pub fn new_balance_with_dust(
evm_value: U256,
) -> Result<(BalanceOf<T>, u32), BalanceConversionError> {
let ed = T::Currency::minimum_balance();
let balance_with_dust = BalanceWithDust::<BalanceOf<T>>::from_value::<T>(evm_value)?;
let (value, dust) = balance_with_dust.deconstruct();
Ok((ed.saturating_add(value), dust))
}
pub fn evm_nonce(address: &H160) -> u32
where
T::Nonce: Into<u32>,
{
let account = T::AddressMapper::to_account_id(&address);
System::<T>::account_nonce(account).into()
}
pub fn evm_block_gas_limit() -> U256 {
u64::MAX.into()
}
pub fn evm_max_extrinsic_weight() -> Weight {
let factor = <T as Config>::MaxEthExtrinsicWeight::get();
let max_weight = <T as frame_system::Config>::BlockWeights::get()
.get(DispatchClass::Normal)
.max_extrinsic
.unwrap_or_else(|| <T as frame_system::Config>::BlockWeights::get().max_block);
Weight::from_parts(
factor.saturating_mul_int(max_weight.ref_time()),
factor.saturating_mul_int(max_weight.proof_size()),
)
}
pub fn evm_base_fee() -> U256 {
let gas_scale = <T as Config>::GasScale::get();
let multiplier = T::FeeInfo::next_fee_multiplier();
multiplier
.saturating_mul_int::<u128>(T::NativeToEthRatio::get().into())
.saturating_mul(gas_scale.saturated_into())
.into()
}
pub fn evm_tracer(tracer_type: TracerType) -> Tracer<T>
where
T::Nonce: Into<u32>,
{
match tracer_type {
TracerType::CallTracer(config) => CallTracer::new(config.unwrap_or_default()).into(),
TracerType::PrestateTracer(config) =>
PrestateTracer::new(config.unwrap_or_default()).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 bytecode_type = if code.starts_with(&polkavm_common::program::BLOB_MAGIC) {
BytecodeType::Pvm
} else {
if !T::AllowEVMBytecode::get() {
return Err(<Error<T>>::CodeRejected.into())
}
BytecodeType::Evm
};
let mut meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit {
weight_limit: Default::default(),
deposit_limit: storage_deposit_limit,
})?;
let module = Self::try_upload_code(
origin,
code,
bytecode_type,
&mut meter,
&ExecConfig::new_substrate_tx(),
)?;
Ok(CodeUploadReturnValue {
code_hash: *module.code_hash(),
deposit: meter.deposit_consumed().charge_or_zero(),
})
}
pub fn get_storage(address: H160, key: [u8; 32]) -> GetStorageResult {
let contract_info =
AccountInfo::<T>::load_contract(&address).ok_or(ContractAccessError::DoesntExist)?;
let maybe_value = contract_info.read(&Key::from_fixed(key));
Ok(maybe_value)
}
pub fn get_immutables(address: H160) -> Option<ImmutableData> {
let immutable_data = <ImmutableDataOf<T>>::get(address);
immutable_data
}
pub fn set_immutables(address: H160, data: ImmutableData) -> Result<(), ContractAccessError> {
AccountInfo::<T>::load_contract(&address).ok_or(ContractAccessError::DoesntExist)?;
<ImmutableDataOf<T>>::insert(address, data);
Ok(())
}
pub fn get_storage_var_key(address: H160, key: Vec<u8>) -> GetStorageResult {
let contract_info =
AccountInfo::<T>::load_contract(&address).ok_or(ContractAccessError::DoesntExist)?;
let maybe_value = contract_info.read(
&Key::try_from_var(key)
.map_err(|_| ContractAccessError::KeyDecodingFailed)?
.into(),
);
Ok(maybe_value)
}
pub fn convert_native_to_evm(value: impl Into<BalanceWithDust<BalanceOf<T>>>) -> U256 {
let (value, dust) = value.into().deconstruct();
value
.into()
.saturating_mul(T::NativeToEthRatio::get().into())
.saturating_add(dust.into())
}
pub fn set_storage(address: H160, key: [u8; 32], value: Option<Vec<u8>>) -> SetStorageResult {
let contract_info =
AccountInfo::<T>::load_contract(&address).ok_or(ContractAccessError::DoesntExist)?;
contract_info
.write(&Key::from_fixed(key), value, None, false)
.map_err(ContractAccessError::StorageWriteFailed)
}
pub fn set_storage_var_key(
address: H160,
key: Vec<u8>,
value: Option<Vec<u8>>,
) -> SetStorageResult {
let contract_info =
AccountInfo::<T>::load_contract(&address).ok_or(ContractAccessError::DoesntExist)?;
contract_info
.write(
&Key::try_from_var(key)
.map_err(|_| ContractAccessError::KeyDecodingFailed)?
.into(),
value,
None,
false,
)
.map_err(ContractAccessError::StorageWriteFailed)
}
pub fn account_id() -> T::AccountId {
use frame_support::PalletId;
use sp_runtime::traits::AccountIdConversion;
PalletId(*b"py/reviv").into_account_truncating()
}
pub fn block_author() -> H160 {
use frame_support::traits::FindAuthor;
let digest = <frame_system::Pallet<T>>::digest();
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
T::FindAuthor::find_author(pre_runtime_digests)
.map(|account_id| T::AddressMapper::to_address(&account_id))
.unwrap_or_default()
}
pub fn code(address: &H160) -> Vec<u8> {
use precompiles::{All, Precompiles};
if let Some(code) = <All<T>>::code(address.as_fixed_bytes()) {
return code.into()
}
AccountInfo::<T>::load_contract(&address)
.and_then(|contract| <PristineCode<T>>::get(contract.code_hash))
.map(|code| code.into())
.unwrap_or_default()
}
pub fn try_upload_code(
origin: T::AccountId,
code: Vec<u8>,
code_type: BytecodeType,
meter: &mut TransactionMeter<T>,
exec_config: &ExecConfig<T>,
) -> Result<ContractBlob<T>, DispatchError> {
let mut module = match code_type {
BytecodeType::Pvm => ContractBlob::from_pvm_code(code, origin)?,
BytecodeType::Evm => ContractBlob::from_evm_runtime_code(code, origin)?,
};
module.store_code(exec_config, meter)?;
Ok(module)
}
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 charge_deposit(
hold_reason: Option<HoldReason>,
from: &T::AccountId,
to: &T::AccountId,
amount: BalanceOf<T>,
exec_config: &ExecConfig<T>,
) -> DispatchResult {
use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
if amount.is_zero() {
return Ok(());
}
match (exec_config.collect_deposit_from_hold.is_some(), hold_reason) {
(true, hold_reason) => {
T::FeeInfo::withdraw_txfee(amount)
.ok_or(())
.and_then(|credit| T::Currency::resolve(to, credit).map_err(|_| ()))
.and_then(|_| {
if let Some(hold_reason) = hold_reason {
T::Currency::hold(&hold_reason.into(), to, amount).map_err(|_| ())?;
}
Ok(())
})
.map_err(|_| Error::<T>::StorageDepositNotEnoughFunds)?;
},
(false, Some(hold_reason)) => {
T::Currency::transfer_and_hold(
&hold_reason.into(),
from,
to,
amount,
Precision::Exact,
Preservation::Preserve,
Fortitude::Polite,
)
.map_err(|_| Error::<T>::StorageDepositNotEnoughFunds)?;
},
(false, None) => {
T::Currency::transfer(from, to, amount, Preservation::Preserve)
.map_err(|_| Error::<T>::StorageDepositNotEnoughFunds)?;
},
}
Ok(())
}
fn refund_deposit(
hold_reason: HoldReason,
from: &T::AccountId,
to: &T::AccountId,
amount: BalanceOf<T>,
exec_config: Option<&ExecConfig<T>>,
) -> Result<(), DispatchError> {
use frame_support::traits::{
fungible::InspectHold,
tokens::{Fortitude, Precision, Preservation, Restriction},
};
if amount.is_zero() {
return Ok(());
}
let hold_reason = hold_reason.into();
let result = if exec_config.map(|c| c.collect_deposit_from_hold.is_some()).unwrap_or(false)
{
T::Currency::release(&hold_reason, from, amount, Precision::Exact)
.and_then(|amount| {
T::Currency::withdraw(
from,
amount,
Precision::Exact,
Preservation::Preserve,
Fortitude::Polite,
)
})
.map(T::FeeInfo::deposit_txfee)
} else {
T::Currency::transfer_on_hold(
&hold_reason,
from,
to,
amount,
Precision::Exact,
Restriction::Free,
Fortitude::Polite,
)
.map(|_| ())
};
result.map_err(|_| {
let available = T::Currency::balance_on_hold(&hold_reason, from);
if available < amount {
log::error!(
target: LOG_TARGET,
"Failed to refund storage deposit {:?} from contract {:?} to origin {:?}. Not enough deposit: {:?}. This is a bug.",
amount, from, to, available,
);
Error::<T>::StorageRefundNotEnoughFunds.into()
} else {
log::warn!(
target: LOG_TARGET,
"Failed to refund storage deposit {:?} from contract {:?} to origin {:?}. First remove locks (staking, governance) from the contracts account.",
amount, from, to,
);
Error::<T>::StorageRefundLocked.into()
}
})
}
fn has_dust(value: U256) -> bool {
value % U256::from(<T>::NativeToEthRatio::get()) != U256::zero()
}
fn has_balance(value: U256) -> bool {
value >= U256::from(<T>::NativeToEthRatio::get())
}
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))
}
fn ensure_eth_signed(origin: OriginFor<T>) -> Result<AccountIdOf<T>, DispatchError> {
match <T as Config>::RuntimeOrigin::from(origin).into() {
Ok(Origin::EthTransaction(signer)) => Ok(signer),
_ => Err(BadOrigin.into()),
}
}
fn ensure_non_contract_if_signed(origin: &OriginFor<T>) -> DispatchResult {
if DebugSettings::bypass_eip_3607::<T>() {
return Ok(())
}
let Some(address) = origin
.as_system_ref()
.and_then(|o| o.as_signed())
.map(<T::AddressMapper as AddressMapper<T>>::to_address)
else {
return Ok(())
};
if exec::is_precompile::<T, ContractBlob<T>>(&address) ||
<AccountInfo<T>>::is_contract(&address)
{
log::debug!(
target: crate::LOG_TARGET,
"EIP-3607: reject tx as pre-compile or account exist at {address:?}",
);
Err(DispatchError::BadOrigin)
} else {
Ok(())
}
}
}
pub const RUNTIME_PALLETS_ADDR: H160 =
H160(hex_literal::hex!("6d6f646c70792f70616464720000000000000000"));
environmental!(executing_contract: bool);
sp_api::decl_runtime_apis! {
#[api_version(1)]
pub trait ReviveApi<AccountId, Balance, Nonce, BlockNumber, Moment> where
AccountId: Codec,
Balance: Codec,
Nonce: Codec,
BlockNumber: Codec,
Moment: Codec,
{
fn eth_block() -> EthBlock;
fn eth_block_hash(number: U256) -> Option<H256>;
fn eth_receipt_data() -> Vec<ReceiptGasInfo>;
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 eth_transact_with_config(
tx: GenericTransaction,
config: DryRunConfig<Moment>,
) -> 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>;
fn block_author() -> H160;
fn address(account_id: AccountId) -> H160;
fn account_id(address: H160) -> AccountId;
fn runtime_pallets_address() -> H160;
fn code(address: H160) -> Vec<u8>;
fn new_balance_with_dust(balance: U256) -> Result<(Balance, u32), BalanceConversionError>;
}
}
#[macro_export]
macro_rules! impl_runtime_apis_plus_revive_traits {
($Runtime: ty, $Revive: ident, $Executive: ty, $EthExtra: ty, $($rest:tt)*) => {
type __ReviveMacroMoment = <<$Runtime as $crate::Config>::Time as $crate::Time>::Moment;
impl $crate::evm::runtime::SetWeightLimit for RuntimeCall {
fn set_weight_limit(&mut self, new_weight_limit: Weight) -> Weight {
use $crate::pallet::Call as ReviveCall;
match self {
Self::$Revive(
ReviveCall::eth_call{ weight_limit, .. } |
ReviveCall::eth_instantiate_with_code{ weight_limit, .. }
) => {
let old = *weight_limit;
*weight_limit = new_weight_limit;
old
},
_ => Weight::default(),
}
}
}
impl_runtime_apis! {
$($rest)*
impl pallet_revive::ReviveApi<Block, AccountId, Balance, Nonce, BlockNumber, __ReviveMacroMoment> for $Runtime
{
fn eth_block() -> $crate::EthBlock {
$crate::Pallet::<Self>::eth_block()
}
fn eth_block_hash(number: $crate::U256) -> Option<$crate::H256> {
$crate::Pallet::<Self>::eth_block_hash_from_number(number)
}
fn eth_receipt_data() -> Vec<$crate::ReceiptGasInfo> {
$crate::Pallet::<Self>::eth_receipt_data()
}
fn balance(address: $crate::H160) -> $crate::U256 {
$crate::Pallet::<Self>::evm_balance(&address)
}
fn block_author() -> $crate::H160 {
$crate::Pallet::<Self>::block_author()
}
fn block_gas_limit() -> $crate::U256 {
$crate::Pallet::<Self>::evm_block_gas_limit()
}
fn gas_price() -> $crate::U256 {
$crate::Pallet::<Self>::evm_base_fee()
}
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 address(account_id: AccountId) -> $crate::H160 {
use $crate::AddressMapper;
<Self as $crate::Config>::AddressMapper::to_address(&account_id)
}
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
};
$crate::Pallet::<Self>::dry_run_eth_transact(tx, Default::default())
}
fn eth_transact_with_config(
tx: $crate::evm::GenericTransaction,
config: $crate::DryRunConfig<__ReviveMacroMoment>,
) -> 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
};
$crate::Pallet::<Self>::dry_run_eth_transact(tx, config)
}
fn call(
origin: AccountId,
dest: $crate::H160,
value: Balance,
weight_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,
$crate::Pallet::<Self>::convert_native_to_evm(value),
$crate::TransactionLimits::WeightAndDeposit {
weight_limit: weight_limit.unwrap_or(blockweights.max_block),
deposit_limit: storage_deposit_limit.unwrap_or(u128::MAX),
},
input_data,
$crate::ExecConfig::new_substrate_tx().with_dry_run(Default::default()),
)
}
fn instantiate(
origin: AccountId,
value: Balance,
weight_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),
$crate::Pallet::<Self>::convert_native_to_evm(value),
$crate::TransactionLimits::WeightAndDeposit {
weight_limit: weight_limit.unwrap_or(blockweights.max_block),
deposit_limit: storage_deposit_limit.unwrap_or(u128::MAX),
},
code,
data,
salt,
$crate::ExecConfig::new_substrate_tx().with_dry_run(Default::default()),
)
}
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 traces = vec![];
let (header, extrinsics) = block.deconstruct();
<$Executive>::initialize_block(&header);
for (index, ext) in extrinsics.into_iter().enumerate() {
let mut tracer = $crate::Pallet::<Self>::evm_tracer(tracer_type.clone());
let t = tracer.as_tracing();
let _ = trace(t, || <$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();
let _ = trace(t, || <$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.clone());
let t = tracer.as_tracing();
t.watch_address(&tx.from.unwrap_or_default());
t.watch_address(&$crate::Pallet::<Self>::block_author());
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($crate::Pallet::<Self>::evm_tracer(tracer_type).empty_trace())
}
}
fn runtime_pallets_address() -> $crate::H160 {
$crate::RUNTIME_PALLETS_ADDR
}
fn code(address: $crate::H160) -> Vec<u8> {
$crate::Pallet::<Self>::code(&address)
}
fn account_id(address: $crate::H160) -> AccountId {
use $crate::AddressMapper;
<Self as $crate::Config>::AddressMapper::to_account_id(&address)
}
fn new_balance_with_dust(balance: $crate::U256) -> Result<(Balance, u32), $crate::BalanceConversionError> {
$crate::Pallet::<Self>::new_balance_with_dust(balance)
}
}
}
};
}