#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit="256")]
#[macro_use]
mod gas;
mod storage;
mod exec;
mod wasm;
mod rent;
mod benchmarking;
mod schedule;
pub mod chain_extension;
pub mod weights;
#[cfg(test)]
mod tests;
pub use crate::{
gas::{Gas, GasMeter},
wasm::ReturnCode as RuntimeReturnCode,
weights::WeightInfo,
schedule::{Schedule, HostFnWeights, InstructionWeights, Limits},
};
use crate::{
exec::ExecutionContext,
wasm::{WasmLoader, WasmVm},
rent::Rent,
storage::Storage,
};
use tet_core::crypto::UncheckedFrom;
use tetcore_std::{prelude::*, marker::PhantomData, fmt::Debug};
use codec::{Codec, Encode, Decode};
use tp_runtime::{
traits::{
Hash, StaticLookup, Zero, MaybeSerializeDeserialize, Member, Convert, Saturating,
},
RuntimeDebug, Perbill,
};
use fabric_support::{
decl_module, decl_event, decl_storage, decl_error, ensure,
storage::child::ChildInfo,
dispatch::{DispatchResult, DispatchResultWithPostInfo},
traits::{OnUnbalanced, Currency, Get, Time, Randomness},
weights::Pays,
};
use fabric_system::{ensure_signed, ensure_root, Module as System};
use noble_contracts_primitives::{
RentProjectionResult, GetStorageResult, ContractAccessError, ContractExecResult, ExecResult,
};
use fabric_support::weights::Weight;
pub type CodeHash<T> = <T as fabric_system::Config>::Hash;
pub type TrieId = Vec<u8>;
#[derive(Encode, Decode, RuntimeDebug)]
pub enum ContractInfo<T: Config> {
Alive(AliveContractInfo<T>),
Tombstone(TombstoneContractInfo<T>),
}
impl<T: Config> 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 fabric_system::Config>::BlockNumber>;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
pub trie_id: TrieId,
pub storage_size: u32,
pub pair_count: u32,
pub code_hash: CodeHash,
pub rent_allowance: Balance,
pub rent_payed: 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 fabric_system::Config>::Hash, <T as fabric_system::Config>::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
+ tetcore_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: Config> From<AliveContractInfo<T>> for ContractInfo<T> {
fn from(alive_info: AliveContractInfo<T>) -> Self {
Self::Alive(alive_info)
}
}
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as fabric_system::Config>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> =
<<T as Config>::Currency as Currency<<T as fabric_system::Config>::AccountId>>::NegativeImbalance;
pub trait Config: fabric_system::Config {
type Time: Time;
type Randomness: Randomness<Self::Hash>;
type Currency: Currency<Self::AccountId>;
type Event: From<Event<Self>> + Into<<Self as fabric_system::Config>::Event>;
type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
type SignedClaimHandicap: Get<Self::BlockNumber>;
type TombstoneDeposit: Get<BalanceOf<Self>>;
type DepositPerContract: Get<BalanceOf<Self>>;
type DepositPerStorageByte: Get<BalanceOf<Self>>;
type DepositPerStorageItem: Get<BalanceOf<Self>>;
type RentFraction: Get<Perbill>;
type SurchargeReward: Get<BalanceOf<Self>>;
type MaxDepth: Get<u32>;
type MaxValueSize: Get<u32>;
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
type WeightInfo: WeightInfo;
type ChainExtension: chain_extension::ChainExtension;
type DeletionQueueDepth: Get<u32>;
type DeletionWeightLimit: Get<Weight>;
}
decl_error! {
pub enum Error for Module<T: Config>
where
T::AccountId: UncheckedFrom<T::Hash>,
T::AccountId: AsRef<[u8]>,
{
InvalidScheduleVersion,
InvalidSurchargeClaim,
InvalidSourceContract,
InvalidDestinationContract,
InvalidTombstone,
InvalidContractOrigin,
OutOfGas,
OutputBufferTooSmall,
BelowSubsistenceThreshold,
NewContractNotFunded,
TransferFailed,
MaxCallDepthReached,
NotCallable,
CodeTooLarge,
CodeNotFound,
OutOfBounds,
DecodingFailed,
ContractTrapped,
ValueTooLarge,
ReentranceDenied,
InputAlreadyRead,
RandomSubjectTooLong,
TooManyTopics,
DuplicateTopics,
NoChainExtension,
DeletionQueueFull,
ContractNotEvictable,
StorageExhausted,
}
}
decl_module! {
pub struct Module<T: Config> for enum Call
where
origin: T::Origin,
T::AccountId: UncheckedFrom<T::Hash>,
T::AccountId: AsRef<[u8]>,
{
type Error = Error<T>;
const SignedClaimHandicap: T::BlockNumber = T::SignedClaimHandicap::get();
const TombstoneDeposit: BalanceOf<T> = T::TombstoneDeposit::get();
const DepositPerContract: BalanceOf<T> = T::DepositPerContract::get();
const DepositPerStorageByte: BalanceOf<T> = T::DepositPerStorageByte::get();
const DepositPerStorageItem: BalanceOf<T> = T::DepositPerStorageItem::get();
const RentFraction: Perbill = T::RentFraction::get();
const SurchargeReward: BalanceOf<T> = T::SurchargeReward::get();
const MaxDepth: u32 = T::MaxDepth::get();
const MaxValueSize: u32 = T::MaxValueSize::get();
const DeletionQueueDepth: u32 = T::DeletionQueueDepth::get();
const DeletionWeightLimit: Weight = T::DeletionWeightLimit::get();
fn deposit_event() = default;
fn on_initialize() -> Weight {
let weight_limit = T::BlockWeights::get().max_block
.saturating_sub(System::<T>::block_weight().total())
.min(T::DeletionWeightLimit::get());
Storage::<T>::process_deletion_queue_batch(weight_limit)
.saturating_add(T::WeightInfo::on_initialize())
}
#[weight = T::WeightInfo::update_schedule()]
pub fn update_schedule(origin, schedule: Schedule<T>) -> 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 = T::WeightInfo::put_code(code.len() as u32 / 1024)]
pub fn put_code(
origin,
code: Vec<u8>
) -> DispatchResult {
ensure_signed(origin)?;
let schedule = <Module<T>>::current_schedule();
ensure!(code.len() as u32 <= schedule.limits.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 = T::WeightInfo::call().saturating_add(*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 =
T::WeightInfo::instantiate(
data.len() as u32 / 1024,
salt.len() as u32 / 1024,
).saturating_add(*gas_limit)
]
pub fn instantiate(
origin,
#[compact] endowment: BalanceOf<T>,
#[compact] gas_limit: Gas,
code_hash: CodeHash<T>,
data: Vec<u8>,
salt: 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, &salt)
.map(|(_address, output)| output)
});
gas_meter.into_dispatch_result(result)
}
#[weight = T::WeightInfo::claim_surcharge()]
pub fn claim_surcharge(
origin,
dest: T::AccountId,
aux_sender: Option<T::AccountId>
) -> DispatchResultWithPostInfo {
let origin = origin.into();
let (signed, rewarded) = match (origin, aux_sender) {
(Ok(fabric_system::RawOrigin::Signed(account)), None) => {
(true, account)
},
(Ok(fabric_system::RawOrigin::None), Some(aux_sender)) => {
(false, aux_sender)
},
_ => Err(Error::<T>::InvalidSurchargeClaim)?,
};
let handicap = if signed {
T::SignedClaimHandicap::get()
} else {
Zero::zero()
};
if let Some(rent_payed) = Rent::<T>::try_eviction(&dest, handicap)? {
T::Currency::deposit_into_existing(
&rewarded,
T::SurchargeReward::get().min(rent_payed),
)
.map(|_| Pays::No.into())
.map_err(Into::into)
} else {
Err(Error::<T>::ContractNotEvictable.into())
}
}
}
}
impl<T: Config> Module<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
pub fn bare_call(
origin: T::AccountId,
dest: T::AccountId,
value: BalanceOf<T>,
gas_limit: Gas,
input_data: Vec<u8>,
) -> ContractExecResult {
let mut gas_meter = GasMeter::new(gas_limit);
let exec_result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| {
ctx.call(dest, value, gas_meter, input_data)
});
let gas_consumed = gas_meter.gas_spent();
ContractExecResult {
exec_result,
gas_consumed,
}
}
pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult {
let contract_info = ContractInfoOf::<T>::get(&address)
.ok_or(ContractAccessError::DoesntExist)?
.get_alive()
.ok_or(ContractAccessError::IsTombstone)?;
let maybe_value = Storage::<T>::read(&contract_info.trie_id, &key);
Ok(maybe_value)
}
pub fn rent_projection(address: T::AccountId) -> RentProjectionResult<T::BlockNumber> {
Rent::<T>::compute_projection(&address)
}
#[cfg(feature = "runtime-benchmarks")]
pub fn put_code_raw(code: Vec<u8>) -> DispatchResult {
let schedule = <Module<T>>::current_schedule();
let result = wasm::save_code_raw::<T>(code, &schedule);
result.map(|_| ()).map_err(Into::into)
}
pub fn contract_address(
deploying_address: &T::AccountId,
code_hash: &CodeHash<T>,
salt: &[u8],
) -> T::AccountId
{
let buf: Vec<_> = deploying_address.as_ref().iter()
.chain(code_hash.as_ref())
.chain(salt)
.cloned()
.collect();
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf))
}
}
impl<T: Config> Module<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(&mut ExecutionContext<T, WasmVm<T>, WasmLoader<T>>, &mut GasMeter<T>) -> ExecResult,
) -> ExecResult {
let cfg = ConfigCache::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 fabric_system::Config>::AccountId,
<T as fabric_system::Config>::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: Config> as Contracts
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
CurrentSchedule get(fn current_schedule) config(): Schedule<T> = Default::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 DeletionQueue: Vec<storage::DeletedContract>;
}
}
pub struct ConfigCache<T: Config> {
pub schedule: Schedule<T>,
pub existential_deposit: BalanceOf<T>,
pub tombstone_deposit: BalanceOf<T>,
pub max_depth: u32,
pub max_value_size: u32,
}
impl<T: Config> ConfigCache<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
fn preload() -> ConfigCache<T> {
ConfigCache {
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())
}
}