#![cfg_attr(not(feature = "std"), no_std)]
#[macro_use]
mod gas;
mod storage;
mod exec;
mod wasm;
mod rent;
mod benchmarking;
#[cfg(test)]
mod tests;
use crate::exec::ExecutionContext;
use crate::wasm::{WasmLoader, WasmVm};
pub use crate::gas::{Gas, GasMeter};
pub use crate::exec::{ExecResult, ExecReturnValue};
pub use crate::wasm::ReturnCode as RuntimeReturnCode;
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
use sp_core::crypto::UncheckedFrom;
use sp_std::{prelude::*, marker::PhantomData, fmt::Debug};
use codec::{Codec, Encode, Decode};
use sp_runtime::{
traits::{
Hash, StaticLookup, Zero, MaybeSerializeDeserialize, Member, Convert, Saturating,
},
RuntimeDebug,
};
use frame_support::{
decl_module, decl_event, decl_storage, decl_error, ensure,
parameter_types, storage::child::ChildInfo,
dispatch::{DispatchResult, DispatchResultWithPostInfo},
traits::{OnUnbalanced, Currency, Get, Time, Randomness},
};
use frame_system::{ensure_signed, ensure_root};
use pallet_contracts_primitives::{RentProjection, ContractAccessError};
use frame_support::weights::Weight;
pub type CodeHash<T> = <T as frame_system::Trait>::Hash;
pub type TrieId = Vec<u8>;
pub trait ContractAddressFor<CodeHash, AccountId> {
fn contract_address_for(code_hash: &CodeHash, data: &[u8], origin: &AccountId) -> AccountId;
}
#[derive(Encode, Decode, RuntimeDebug)]
pub enum ContractInfo<T: Trait> {
Alive(AliveContractInfo<T>),
Tombstone(TombstoneContractInfo<T>),
}
impl<T: Trait> ContractInfo<T> {
pub fn get_alive(self) -> Option<AliveContractInfo<T>> {
if let ContractInfo::Alive(alive) = self {
Some(alive)
} else {
None
}
}
pub fn as_alive(&self) -> Option<&AliveContractInfo<T>> {
if let ContractInfo::Alive(ref alive) = self {
Some(alive)
} else {
None
}
}
pub fn as_alive_mut(&mut self) -> Option<&mut AliveContractInfo<T>> {
if let ContractInfo::Alive(ref mut alive) = self {
Some(alive)
} else {
None
}
}
pub fn get_tombstone(self) -> Option<TombstoneContractInfo<T>> {
if let ContractInfo::Tombstone(tombstone) = self {
Some(tombstone)
} else {
None
}
}
pub fn as_tombstone(&self) -> Option<&TombstoneContractInfo<T>> {
if let ContractInfo::Tombstone(ref tombstone) = self {
Some(tombstone)
} else {
None
}
}
pub fn as_tombstone_mut(&mut self) -> Option<&mut TombstoneContractInfo<T>> {
if let ContractInfo::Tombstone(ref mut tombstone) = self {
Some(tombstone)
} else {
None
}
}
}
pub type AliveContractInfo<T> =
RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as frame_system::Trait>::BlockNumber>;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
pub trie_id: TrieId,
pub storage_size: u32,
pub empty_pair_count: u32,
pub total_pair_count: u32,
pub code_hash: CodeHash,
pub rent_allowance: Balance,
pub deduct_block: BlockNumber,
pub last_write: Option<BlockNumber>,
}
impl<CodeHash, Balance, BlockNumber> RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
pub fn child_trie_info(&self) -> ChildInfo {
child_trie_info(&self.trie_id[..])
}
}
pub(crate) fn child_trie_info(trie_id: &[u8]) -> ChildInfo {
ChildInfo::new_default(trie_id)
}
pub type TombstoneContractInfo<T> =
RawTombstoneContractInfo<<T as frame_system::Trait>::Hash, <T as frame_system::Trait>::Hashing>;
#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
where
H: Member + MaybeSerializeDeserialize+ Debug
+ AsRef<[u8]> + AsMut<[u8]> + Copy + Default
+ sp_std::hash::Hash + Codec,
Hasher: Hash<Output=H>,
{
fn new(storage_root: &[u8], code_hash: H) -> Self {
let mut buf = Vec::new();
storage_root.using_encoded(|encoded| buf.extend_from_slice(encoded));
buf.extend_from_slice(code_hash.as_ref());
RawTombstoneContractInfo(<Hasher as Hash>::hash(&buf[..]), PhantomData)
}
}
impl<T: Trait> From<AliveContractInfo<T>> for ContractInfo<T> {
fn from(alive_info: AliveContractInfo<T>) -> Self {
Self::Alive(alive_info)
}
}
pub trait TrieIdGenerator<AccountId> {
fn trie_id(account_id: &AccountId) -> TrieId;
}
pub struct TrieIdFromParentCounter<T: Trait>(PhantomData<T>);
impl<T: Trait> TrieIdGenerator<T::AccountId> for TrieIdFromParentCounter<T>
where
T::AccountId: AsRef<[u8]>
{
fn trie_id(account_id: &T::AccountId) -> TrieId {
let new_seed = AccountCounter::mutate(|v| {
*v = v.wrapping_add(1);
*v
});
let mut buf = Vec::new();
buf.extend_from_slice(account_id.as_ref());
buf.extend_from_slice(&new_seed.to_le_bytes()[..]);
T::Hashing::hash(&buf[..]).as_ref().into()
}
}
pub type BalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
parameter_types! {
pub const DefaultSignedClaimHandicap: u32 = 2;
pub const DefaultTombstoneDeposit: u32 = 16;
pub const DefaultStorageSizeOffset: u32 = 8;
pub const DefaultRentByteFee: u32 = 4;
pub const DefaultRentDepositOffset: u32 = 1000;
pub const DefaultSurchargeReward: u32 = 150;
pub const DefaultMaxDepth: u32 = 32;
pub const DefaultMaxValueSize: u32 = 16_384;
}
pub trait Trait: frame_system::Trait {
type Time: Time;
type Randomness: Randomness<Self::Hash>;
type Currency: Currency<Self::AccountId>;
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type DetermineContractAddress: ContractAddressFor<CodeHash<Self>, Self::AccountId>;
type TrieIdGenerator: TrieIdGenerator<Self::AccountId>;
type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
type SignedClaimHandicap: Get<Self::BlockNumber>;
type TombstoneDeposit: Get<BalanceOf<Self>>;
type StorageSizeOffset: Get<u32>;
type RentByteFee: Get<BalanceOf<Self>>;
type RentDepositOffset: Get<BalanceOf<Self>>;
type SurchargeReward: Get<BalanceOf<Self>>;
type MaxDepth: Get<u32>;
type MaxValueSize: Get<u32>;
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
}
pub struct SimpleAddressDeterminer<T: Trait>(PhantomData<T>);
impl<T: Trait> ContractAddressFor<CodeHash<T>, T::AccountId> for SimpleAddressDeterminer<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
fn contract_address_for(code_hash: &CodeHash<T>, data: &[u8], origin: &T::AccountId) -> T::AccountId {
let data_hash = T::Hashing::hash(data);
let mut buf = Vec::new();
buf.extend_from_slice(code_hash.as_ref());
buf.extend_from_slice(data_hash.as_ref());
buf.extend_from_slice(origin.as_ref());
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf[..]))
}
}
decl_error! {
pub enum Error for Module<T: Trait> {
InvalidScheduleVersion,
InvalidSurchargeClaim,
InvalidSourceContract,
InvalidDestinationContract,
InvalidTombstone,
InvalidContractOrigin,
OutOfGas,
OutputBufferTooSmall,
BelowSubsistenceThreshold,
NewContractNotFunded,
TransferFailed,
MaxCallDepthReached,
NotCallable,
CodeTooLarge,
CodeNotFound,
OutOfBounds,
DecodingFailed,
ContractTrapped,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: <T as frame_system::Trait>::Origin {
type Error = Error<T>;
const SignedClaimHandicap: T::BlockNumber = T::SignedClaimHandicap::get();
const TombstoneDeposit: BalanceOf<T> = T::TombstoneDeposit::get();
const StorageSizeOffset: u32 = T::StorageSizeOffset::get();
const RentByteFee: BalanceOf<T> = T::RentByteFee::get();
const RentDepositOffset: BalanceOf<T> = T::RentDepositOffset::get();
const SurchargeReward: BalanceOf<T> = T::SurchargeReward::get();
const MaxDepth: u32 = T::MaxDepth::get();
const MaxValueSize: u32 = T::MaxValueSize::get();
fn deposit_event() = default;
#[weight = 0]
pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult {
ensure_root(origin)?;
if <Module<T>>::current_schedule().version >= schedule.version {
Err(Error::<T>::InvalidScheduleVersion)?
}
Self::deposit_event(RawEvent::ScheduleUpdated(schedule.version));
CurrentSchedule::put(schedule);
Ok(())
}
#[weight = Module::<T>::calc_code_put_costs(&code)]
pub fn put_code(
origin,
code: Vec<u8>
) -> DispatchResult {
ensure_signed(origin)?;
let schedule = <Module<T>>::current_schedule();
ensure!(code.len() as u32 <= schedule.max_code_size, Error::<T>::CodeTooLarge);
let result = wasm::save_code::<T>(code, &schedule);
if let Ok(code_hash) = result {
Self::deposit_event(RawEvent::CodeStored(code_hash));
}
result.map(|_| ()).map_err(Into::into)
}
#[weight = *gas_limit]
pub fn call(
origin,
dest: <T::Lookup as StaticLookup>::Source,
#[compact] value: BalanceOf<T>,
#[compact] gas_limit: Gas,
data: Vec<u8>
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, data)
});
gas_meter.into_dispatch_result(result)
}
#[weight = *gas_limit]
pub fn instantiate(
origin,
#[compact] endowment: BalanceOf<T>,
#[compact] gas_limit: Gas,
code_hash: CodeHash<T>,
data: Vec<u8>
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.instantiate(endowment, gas_meter, &code_hash, data)
.map(|(_address, output)| output)
});
gas_meter.into_dispatch_result(result)
}
#[weight = 0]
fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) {
let origin = origin.into();
let (signed, rewarded) = match (origin, aux_sender) {
(Ok(frame_system::RawOrigin::Signed(account)), None) => {
(true, account)
},
(Ok(frame_system::RawOrigin::None), Some(aux_sender)) => {
(false, aux_sender)
},
_ => Err(Error::<T>::InvalidSurchargeClaim)?,
};
let handicap = if signed {
T::SignedClaimHandicap::get()
} else {
Zero::zero()
};
if rent::snitch_contract_should_be_evicted::<T>(&dest, handicap) {
T::Currency::deposit_into_existing(&rewarded, T::SurchargeReward::get())?;
}
}
}
}
impl<T: Trait> Module<T> {
pub fn bare_call(
origin: T::AccountId,
dest: T::AccountId,
value: BalanceOf<T>,
gas_limit: Gas,
input_data: Vec<u8>,
) -> (ExecResult, Gas) {
let mut gas_meter = GasMeter::new(gas_limit);
(
Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, input_data)
}),
gas_meter.gas_spent(),
)
}
pub fn get_storage(
address: T::AccountId,
key: [u8; 32],
) -> sp_std::result::Result<Option<Vec<u8>>, ContractAccessError> {
let contract_info = ContractInfoOf::<T>::get(&address)
.ok_or(ContractAccessError::DoesntExist)?
.get_alive()
.ok_or(ContractAccessError::IsTombstone)?;
let maybe_value = storage::read_contract_storage(&contract_info.trie_id, &key);
Ok(maybe_value)
}
pub fn rent_projection(
address: T::AccountId,
) -> sp_std::result::Result<RentProjection<T::BlockNumber>, ContractAccessError> {
rent::compute_rent_projection::<T>(&address)
}
}
impl<T: Trait> Module<T> {
fn calc_code_put_costs(code: &Vec<u8>) -> Gas {
<Module<T>>::current_schedule().put_code_per_byte_cost.saturating_mul(code.len() as Gas)
}
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm, WasmLoader>, &mut GasMeter<T>) -> ExecResult,
) -> ExecResult {
let cfg = Config::preload();
let vm = WasmVm::new(&cfg.schedule);
let loader = WasmLoader::new(&cfg.schedule);
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
func(&mut ctx, gas_meter)
}
}
decl_event! {
pub enum Event<T>
where
Balance = BalanceOf<T>,
<T as frame_system::Trait>::AccountId,
<T as frame_system::Trait>::Hash
{
Instantiated(AccountId, AccountId),
Evicted(AccountId, bool),
Restored(AccountId, AccountId, Hash, Balance),
CodeStored(Hash),
ScheduleUpdated(u32),
ContractExecution(AccountId, Vec<u8>),
}
}
decl_storage! {
trait Store for Module<T: Trait> as Contracts {
CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default();
pub PristineCode: map hasher(identity) CodeHash<T> => Option<Vec<u8>>;
pub CodeStorage: map hasher(identity) CodeHash<T> => Option<wasm::PrefabWasmModule>;
pub AccountCounter: u64 = 0;
pub ContractInfoOf: map hasher(twox_64_concat) T::AccountId => Option<ContractInfo<T>>;
}
}
pub struct Config<T: Trait> {
pub schedule: Schedule,
pub existential_deposit: BalanceOf<T>,
pub tombstone_deposit: BalanceOf<T>,
pub max_depth: u32,
pub max_value_size: u32,
}
impl<T: Trait> Config<T> {
fn preload() -> Config<T> {
Config {
schedule: <Module<T>>::current_schedule(),
existential_deposit: T::Currency::minimum_balance(),
tombstone_deposit: T::TombstoneDeposit::get(),
max_depth: T::MaxDepth::get(),
max_value_size: T::MaxValueSize::get(),
}
}
pub fn subsistence_threshold(&self) -> BalanceOf<T> {
self.existential_deposit.saturating_add(self.tombstone_deposit)
}
pub fn subsistence_threshold_uncached() -> BalanceOf<T> {
T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get())
}
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct Schedule {
pub version: u32,
pub put_code_per_byte_cost: Gas,
pub grow_mem_cost: Gas,
pub regular_op_cost: Gas,
pub return_data_per_byte_cost: Gas,
pub event_data_per_byte_cost: Gas,
pub event_per_topic_cost: Gas,
pub event_base_cost: Gas,
pub call_base_cost: Gas,
pub instantiate_base_cost: Gas,
pub dispatch_base_cost: Gas,
pub sandbox_data_read_cost: Gas,
pub sandbox_data_write_cost: Gas,
pub transfer_cost: Gas,
pub instantiate_cost: Gas,
pub max_event_topics: u32,
pub max_stack_height: u32,
pub max_memory_pages: u32,
pub max_table_size: u32,
pub enable_println: bool,
pub max_subject_len: u32,
pub max_code_size: u32,
}
const WASM_INSTRUCTION_COST: Gas = 500_000;
impl Default for Schedule {
fn default() -> Schedule {
Schedule {
version: 0,
put_code_per_byte_cost: WASM_INSTRUCTION_COST,
grow_mem_cost: WASM_INSTRUCTION_COST,
regular_op_cost: WASM_INSTRUCTION_COST,
return_data_per_byte_cost: WASM_INSTRUCTION_COST,
event_data_per_byte_cost: WASM_INSTRUCTION_COST,
event_per_topic_cost: WASM_INSTRUCTION_COST,
event_base_cost: WASM_INSTRUCTION_COST,
call_base_cost: 135 * WASM_INSTRUCTION_COST,
dispatch_base_cost: 135 * WASM_INSTRUCTION_COST,
instantiate_base_cost: 175 * WASM_INSTRUCTION_COST,
sandbox_data_read_cost: WASM_INSTRUCTION_COST,
sandbox_data_write_cost: WASM_INSTRUCTION_COST,
transfer_cost: 100 * WASM_INSTRUCTION_COST,
instantiate_cost: 200 * WASM_INSTRUCTION_COST,
max_event_topics: 4,
max_stack_height: 64 * 1024,
max_memory_pages: 16,
max_table_size: 16 * 1024,
enable_println: false,
max_subject_len: 32,
max_code_size: 512 * 1024,
}
}
}