#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, missing_debug_implementations)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::mem;
pub use pallet::*;
use subspace_core_primitives::{crypto, Blake3Hash};
pub mod feed_processor;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;
#[frame_support::pallet]
mod pallet {
use crate::feed_processor::{FeedMetadata, FeedProcessor as FeedProcessorT};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::traits::{CheckedAdd, Hash, One, StaticLookup};
use sp_runtime::ArithmeticError;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type FeedId: Parameter + Member + Default + Copy + PartialOrd + CheckedAdd + One;
type FeedProcessorKind: Parameter + Member + Default + Copy;
#[pallet::constant]
type MaxFeeds: Get<u32>;
fn feed_processor(
feed_processor_kind: Self::FeedProcessorKind,
) -> Box<dyn FeedProcessorT<Self::FeedId>>;
}
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
pub(super) type Object = Vec<u8>;
pub(super) type InitData = Vec<u8>;
#[derive(Debug, Decode, Encode, TypeInfo, Default, PartialEq, Eq)]
pub struct TotalObjectsAndSize {
pub size: u64,
pub count: u64,
}
#[derive(Debug, Decode, Encode, TypeInfo, Default)]
pub struct FeedConfig<FeedProcessorId, AccountId> {
pub active: bool,
pub feed_processor_id: FeedProcessorId,
pub owner: AccountId,
}
#[pallet::storage]
#[pallet::getter(fn metadata)]
pub(super) type Metadata<T: Config> =
StorageMap<_, Identity, T::FeedId, FeedMetadata, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn feed_configs)]
pub(super) type FeedConfigs<T: Config> = StorageMap<
_,
Identity,
T::FeedId,
FeedConfig<T::FeedProcessorKind, T::AccountId>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn feeds)]
pub(super) type Feeds<T: Config> =
StorageMap<_, Identity, T::AccountId, BoundedVec<T::FeedId, T::MaxFeeds>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn totals)]
pub(super) type Totals<T: Config> =
StorageMap<_, Identity, T::FeedId, TotalObjectsAndSize, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn next_feed_id)]
pub(super) type NextFeedId<T: Config> = StorageValue<_, T::FeedId, ValueQuery>;
#[pallet::storage]
pub(super) type SuccessfulPuts<T: Config> = StorageValue<_, Vec<T::Hash>, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
ObjectSubmitted {
feed_id: T::FeedId,
who: T::AccountId,
metadata: FeedMetadata,
object_size: u64,
},
FeedCreated {
feed_id: T::FeedId,
who: T::AccountId,
},
FeedUpdated {
feed_id: T::FeedId,
who: T::AccountId,
},
FeedClosed {
feed_id: T::FeedId,
who: T::AccountId,
},
FeedDeleted {
feed_id: T::FeedId,
who: T::AccountId,
},
OwnershipTransferred {
feed_id: T::FeedId,
old_owner: T::AccountId,
new_owner: T::AccountId,
},
}
#[pallet::error]
pub enum Error<T> {
UnknownFeedId,
FeedClosed,
NotFeedOwner,
MaxFeedsReached,
}
macro_rules! ensure_owner {
( $origin:expr, $feed_id:expr ) => {{
let sender = ensure_signed($origin)?;
let feed_config = FeedConfigs::<T>::get($feed_id).ok_or(Error::<T>::UnknownFeedId)?;
ensure!(feed_config.owner == sender, Error::<T>::NotFeedOwner);
(sender, feed_config)
}};
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
SuccessfulPuts::<T>::kill();
T::DbWeight::get().writes(1)
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight((10_000, Pays::No))]
pub fn create(
origin: OriginFor<T>,
feed_processor_id: T::FeedProcessorKind,
init_data: Option<InitData>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let feed_id = NextFeedId::<T>::get();
let next_feed_id = feed_id
.checked_add(&One::one())
.ok_or(ArithmeticError::Overflow)?;
let feed_processor = T::feed_processor(feed_processor_id);
if let Some(init_data) = init_data {
feed_processor.init(feed_id, init_data.as_slice())?;
}
let mut owned_feeds = Feeds::<T>::get(who.clone()).unwrap_or_default();
owned_feeds
.try_push(feed_id)
.map_err(|_| Error::<T>::MaxFeedsReached)?;
NextFeedId::<T>::set(next_feed_id);
FeedConfigs::<T>::insert(
feed_id,
FeedConfig {
active: true,
feed_processor_id,
owner: who.clone(),
},
);
Feeds::<T>::insert(who.clone(), owned_feeds);
Totals::<T>::insert(feed_id, TotalObjectsAndSize::default());
Self::deposit_event(Event::FeedCreated { feed_id, who });
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight((10_000, Pays::No))]
pub fn update(
origin: OriginFor<T>,
feed_id: T::FeedId,
feed_processor_id: T::FeedProcessorKind,
init_data: Option<InitData>,
) -> DispatchResult {
let (owner, feed_config) = ensure_owner!(origin, feed_id);
let feed_processor = T::feed_processor(feed_processor_id);
if let Some(init_data) = init_data {
feed_processor.init(feed_id, init_data.as_slice())?;
}
FeedConfigs::<T>::insert(
feed_id,
FeedConfig {
active: feed_config.active,
feed_processor_id,
owner: owner.clone(),
},
);
Self::deposit_event(Event::FeedUpdated {
feed_id,
who: owner,
});
Ok(())
}
#[pallet::call_index(2)]
#[pallet::weight((10_000, Pays::No))]
pub fn put(origin: OriginFor<T>, feed_id: T::FeedId, object: Object) -> DispatchResult {
let (owner, feed_config) = ensure_owner!(origin, feed_id);
ensure!(feed_config.active, Error::<T>::FeedClosed);
let object_size = object.len() as u64;
let feed_processor = T::feed_processor(feed_config.feed_processor_id);
let metadata = feed_processor
.put(feed_id, object.as_slice())?
.unwrap_or_default();
Metadata::<T>::insert(feed_id, metadata.clone());
Totals::<T>::mutate(feed_id, |feed_totals| {
feed_totals.size += object_size;
feed_totals.count += 1;
});
Self::deposit_event(Event::ObjectSubmitted {
feed_id,
who: owner,
metadata,
object_size,
});
let uniq = T::Hashing::hash(Call::<T>::put { feed_id, object }.encode().as_slice());
SuccessfulPuts::<T>::append(uniq);
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight((T::DbWeight::get().reads_writes(1, 1), Pays::No))]
pub fn close(origin: OriginFor<T>, feed_id: T::FeedId) -> DispatchResult {
let (owner, mut feed_config) = ensure_owner!(origin, feed_id);
feed_config.active = false;
FeedConfigs::<T>::insert(feed_id, feed_config);
Self::deposit_event(Event::FeedClosed {
feed_id,
who: owner,
});
Ok(())
}
#[pallet::call_index(4)]
#[pallet::weight((T::DbWeight::get().reads_writes(3, 3), Pays::No))]
pub fn transfer(
origin: OriginFor<T>,
feed_id: T::FeedId,
new_owner: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let (owner, mut feed_config) = ensure_owner!(origin, feed_id);
let new_owner = T::Lookup::lookup(new_owner)?;
let mut current_owner_feeds = Feeds::<T>::get(owner.clone()).unwrap_or_default();
current_owner_feeds.retain(|x| *x != feed_id);
feed_config.owner = new_owner.clone();
let mut new_owner_feeds = Feeds::<T>::get(new_owner.clone()).unwrap_or_default();
new_owner_feeds
.try_push(feed_id)
.map_err(|_| Error::<T>::MaxFeedsReached)?;
if current_owner_feeds.is_empty() {
Feeds::<T>::remove(owner.clone());
} else {
Feeds::<T>::insert(owner.clone(), current_owner_feeds);
}
Feeds::<T>::insert(new_owner.clone(), new_owner_feeds);
FeedConfigs::<T>::insert(feed_id, feed_config);
Self::deposit_event(Event::OwnershipTransferred {
feed_id,
old_owner: owner,
new_owner,
});
Ok(())
}
}
}
#[derive(Debug)]
pub struct CallObject {
pub key: Blake3Hash,
pub offset: u32,
}
impl<T: Config> Pallet<T> {
pub fn successful_puts() -> Vec<T::Hash> {
SuccessfulPuts::<T>::get()
}
}
impl<T: Config> Call<T> {
pub fn extract_call_objects(&self) -> Vec<CallObject> {
match self {
Self::put { feed_id, object } => {
let feed_processor_id = match FeedConfigs::<T>::get(feed_id) {
Some(config) => config.feed_processor_id,
None => return vec![],
};
let feed_processor = T::feed_processor(feed_processor_id);
let objects_mappings = feed_processor.object_mappings(*feed_id, object);
let base_offset = 1 + mem::size_of::<T::FeedId>() as u32;
objects_mappings
.into_iter()
.filter_map(|object_mapping| {
let mut co = object_mapping.try_into_call_object(
feed_id,
object.as_slice(),
crypto::blake3_hash,
)?;
co.offset += base_offset;
Some(co)
})
.collect()
}
_ => Default::default(),
}
}
}