Skip to content
Snippets Groups Projects
lib.rs 7.17 KiB
Newer Older
  • Learn to ignore specific revisions
  • Bryan Chen's avatar
    Bryan Chen committed
    #![cfg_attr(not(feature = "std"), no_std)]
    
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    mod default_combine_data;
    mod mock;
    mod operator_provider;
    mod tests;
    mod timestamped_value;
    
    
    use codec::{Decode, Encode};
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    pub use default_combine_data::DefaultCombineData;
    
    use frame_support::{
    	decl_error, decl_event, decl_module, decl_storage,
    	dispatch::Dispatchable,
    	ensure,
    	traits::Time,
    	weights::{DispatchClass, DispatchInfo, FunctionOf, TransactionPriority},
    	IsSubType, Parameter,
    };
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    pub use operator_provider::OperatorProvider;
    
    use sp_runtime::{
    	traits::{Member, SignedExtension},
    	DispatchResult,
    };
    use sp_std::{prelude::*, vec};
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    // FIXME: `pallet/frame-` prefix should be used for all pallet modules, but currently `frame_system`
    
    // would cause compiling error in `decl_module!` and `construct_runtime!`
    // #3295 https://github.com/paritytech/substrate/issues/3295
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    use frame_system::{self as system, ensure_signed};
    
    pub use orml_traits::{CombineData, DataProvider, OnNewData, OnRedundantCall};
    use sp_runtime::transaction_validity::{
    	InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
    };
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    pub use timestamped_value::TimestampedValue;
    
    type MomentOf<T> = <<T as Trait>::Time as Time>::Moment;
    
    pub type TimestampedValueOf<T> = TimestampedValue<<T as Trait>::OracleValue, MomentOf<T>>;
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    pub trait Trait: frame_system::Trait {
    	type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
    
    	type Call: Parameter + Dispatchable<Origin = <Self as frame_system::Trait>::Origin> + IsSubType<Module<Self>, Self>;
    	type OnNewData: OnNewData<Self::AccountId, Self::OracleKey, Self::OracleValue>;
    	type OnRedundantCall: OnRedundantCall<Self::AccountId>;
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    	type OperatorProvider: OperatorProvider<Self::AccountId>;
    
    	type CombineData: CombineData<Self::OracleKey, TimestampedValueOf<Self>>;
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    	type Time: Time;
    
    	type OracleKey: Parameter + Member;
    	type OracleValue: Parameter + Member + Ord;
    
    Bryan Chen's avatar
    Bryan Chen committed
    }
    
    decl_storage! {
    	trait Store for Module<T: Trait> as Oracle {
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		pub RawValues get(raw_values): double_map hasher(twox_64_concat) T::OracleKey, hasher(twox_64_concat) T::AccountId => Option<TimestampedValueOf<T>>;
    		pub HasUpdate get(has_update): map hasher(twox_64_concat) T::OracleKey => bool;
    		pub Values get(values): map hasher(twox_64_concat) T::OracleKey => Option<TimestampedValueOf<T>>;
    
    		HasDispatched: Vec<T::AccountId>;
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    	}
    }
    
    decl_error! {
    	// Oracle module errors
    
    	pub enum Error for Module<T: Trait> {
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    		NoPermission,
    
    		UpdateAlreadyDispatched,
    
    #[repr(u8)]
    pub enum ValidityError {
    	NoPermission,
    }
    
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    decl_module! {
    
    	#[derive(Encode, Decode)]
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    
    		type Error = Error<T>;
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    		fn deposit_event() = default;
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    		#[weight = FunctionOf(|_: (&T::OracleKey, &T::OracleValue)| 0, DispatchClass::Operational, false)]
    
    		pub fn feed_value(origin, key: T::OracleKey, value: T::OracleValue) {
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    			let who = ensure_signed(origin)?;
    
    			Self::_feed_values(who, vec![(key, value)])?;
    
    		#[weight = FunctionOf(|_: (&Vec<(T::OracleKey, T::OracleValue)>,)| 0, DispatchClass::Operational, false)]
    
    		pub fn feed_values(origin, values: Vec<(T::OracleKey, T::OracleValue)>) {
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			let who = ensure_signed(origin)?;
    
    			Self::_feed_values(who, values)?;
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    		}
    
    
    		fn on_finalize(_n: T::BlockNumber) {
    			// cleanup for next block
    			<HasDispatched<T>>::kill();
    		}
    
    Bryan Chen's avatar
    Bryan Chen committed
    	}
    }
    
    decl_event!(
    	pub enum Event<T> where
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		<T as frame_system::Trait>::AccountId,
    
    		<T as Trait>::OracleKey,
    		<T as Trait>::OracleValue,
    
    Bryan Chen's avatar
    Bryan Chen committed
    	{
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		/// New feed data is submitted (sender, values)
    
    		NewFeedData(AccountId, Vec<(OracleKey, OracleValue)>),
    
    Bryan Chen's avatar
    Bryan Chen committed
    	}
    );
    
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    impl<T: Trait> Module<T> {
    
    	pub fn read_raw_values(key: &T::OracleKey) -> Vec<TimestampedValueOf<T>> {
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    		T::OperatorProvider::operators()
    			.iter()
    			.filter_map(|x| <RawValues<T>>::get(key, x))
    			.collect()
    	}
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    	/// Returns fresh combined value if has update, or latest combined value.
    	///
    	/// Note this will update values storage if has update.
    
    	pub fn get(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> {
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    		if <HasUpdate<T>>::take(key) {
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    			let timestamped = Self::combined(key)?;
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			<Values<T>>::insert(key, timestamped.clone());
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    			return Some(timestamped);
    		}
    		<Values<T>>::get(key)
    
    Bryan Chen's avatar
    Bryan Chen committed
    	}
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    
    	/// Returns fresh combined value if has update, or latest combined value.
    	///
    	/// This is a no-op function which would not change storage.
    	pub fn get_no_op(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> {
    		if Self::has_update(key) {
    			Self::combined(key)
    		} else {
    			Self::values(key)
    		}
    	}
    
    	fn combined(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> {
    		let values = Self::read_raw_values(key);
    		T::CombineData::combine_data(key, values, Self::values(key))
    	}
    
    Bryan Chen's avatar
    Bryan Chen committed
    }
    
    
    #[derive(Encode, Decode, Clone, Eq, PartialEq)]
    pub struct CheckOperator<T: Trait + Send + Sync>(sp_std::marker::PhantomData<T>);
    
    
    impl<T: Trait + Send + Sync> CheckOperator<T> {
    	pub fn new() -> Self {
    		Self(sp_std::marker::PhantomData)
    	}
    }
    
    
    impl<T: Trait + Send + Sync> sp_std::fmt::Debug for CheckOperator<T> {
    	#[cfg(feature = "std")]
    	fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
    		write!(f, "CheckOperator")
    	}
    
    	#[cfg(not(feature = "std"))]
    	fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
    		Ok(())
    	}
    }
    
    impl<T: Trait + Send + Sync> SignedExtension for CheckOperator<T> {
    	const IDENTIFIER: &'static str = "CheckOperator";
    	type AccountId = T::AccountId;
    	type Call = <T as Trait>::Call;
    	type AdditionalSigned = ();
    	type Pre = ();
    	type DispatchInfo = DispatchInfo;
    
    	fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
    		Ok(())
    	}
    
    	fn validate(
    		&self,
    		who: &T::AccountId,
    		call: &Self::Call,
    		_info: Self::DispatchInfo,
    		_len: usize,
    	) -> TransactionValidity {
    		let call = match call.is_sub_type() {
    			Some(call) => call,
    			None => return Ok(ValidTransaction::default()),
    		};
    
    		if let Call::<T>::feed_value(..) | Call::<T>::feed_values(..) = call {
    			ensure!(
    				T::OperatorProvider::can_feed_data(who),
    				TransactionValidityError::Invalid(InvalidTransaction::Custom(ValidityError::NoPermission as u8))
    			);
    
    			return Ok(ValidTransaction {
    				priority: TransactionPriority::max_value(),
    				..Default::default()
    			});
    		}
    		return Ok(ValidTransaction::default());
    	}
    }
    
    
    impl<T: Trait> DataProvider<T::OracleKey, T::OracleValue> for Module<T> {
    	fn get(key: &T::OracleKey) -> Option<T::OracleValue> {
    
    		Self::get(key).map(|timestamped_value| timestamped_value.value)
    	}
    }
    
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    impl<T: Trait> Module<T> {
    
    	fn _feed_values(who: T::AccountId, values: Vec<(T::OracleKey, T::OracleValue)>) -> DispatchResult {
    		ensure!(T::OperatorProvider::can_feed_data(&who), Error::<T>::NoPermission);
    
    		// ensure account hasn't dispatched an updated yet
    		let mut accounts = <HasDispatched<T>>::get();
    		if accounts.contains(&who) {
    			T::OnRedundantCall::multiple_calls_per_block(&who);
    			return Err(Error::<T>::UpdateAlreadyDispatched.into());
    		}
    		accounts.push(who.clone());
    		<HasDispatched<T>>::put(accounts);
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		let now = T::Time::now();
    
    		for (key, value) in &values {
    			let timestamped = TimestampedValue {
    				value: value.clone(),
    				timestamp: now,
    			};
    			<RawValues<T>>::insert(&key, &who, timestamped);
    			<HasUpdate<T>>::insert(&key, true);
    
    
    			T::OnNewData::on_new_data(&who, &key, &value);
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		}
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		Self::deposit_event(RawEvent::NewFeedData(who, values));