Skip to content
Snippets Groups Projects
lib.rs 10.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Bryan Chen's avatar
    Bryan Chen committed
    #![cfg_attr(not(feature = "std"), no_std)]
    
    Keith Yeung's avatar
    Keith Yeung committed
    // Disable the following two lints since they originate from an external macro (namely decl_storage)
    #![allow(clippy::string_lit_as_bytes)]
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    mod default_combine_data;
    mod mock;
    mod tests;
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    use codec::{Decode, Encode};
    
    pub use default_combine_data::DefaultCombineData;
    
    use frame_support::{
    
    	decl_error, decl_event, decl_module, decl_storage, ensure,
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    	traits::{ChangeMembers, Get, InitializeMembers, Time},
    
    	weights::{DispatchClass, Pays},
    
    	IterableStorageMap, Parameter,
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    #[cfg(feature = "std")]
    use serde::{Deserialize, Serialize};
    
    use sp_runtime::{
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    	transaction_validity::{
    
    		InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, ValidTransaction,
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    	},
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    	DispatchResult, RuntimeDebug,
    
    use sp_std::{convert::TryInto, 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
    
    use frame_system::{self as system, ensure_none, ensure_root, ensure_signed};
    
    pub use orml_traits::{CombineData, DataProvider, DataProviderExtended, OnNewData, OnRedundantCall};
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    use orml_utilities::OrderedSet;
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    use sp_application_crypto::{KeyTypeId, RuntimeAppPublic};
    pub const ORACLE: KeyTypeId = KeyTypeId(*b"orac");
    
    mod app_sr25519 {
    	use sp_application_crypto::{app_crypto, sr25519};
    	app_crypto!(sr25519, super::ORACLE);
    }
    
    sp_application_crypto::with_pair! {
    	/// An oracle keypair using sr25519 as its crypto.
    	pub type AuthorityPair = app_sr25519::Pair;
    }
    
    /// An oracle signature using sr25519 as its crypto.
    pub type AuthoritySignature = app_sr25519::Signature;
    
    /// An oracle identifier using sr25519 as its crypto.
    pub type AuthorityId = app_sr25519::Public;
    
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    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
    
    
    /// Number of blocks before an unconfirmed unsigned transaction expires.
    pub const EXTRINSIC_LONGVITY: u32 = 100;
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    #[derive(Encode, Decode, RuntimeDebug, Eq, PartialEq, Clone, Copy)]
    #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
    pub struct TimestampedValue<Value, Moment> {
    	pub value: Value,
    	pub timestamp: Moment,
    }
    
    
    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 OnNewData: OnNewData<Self::AccountId, Self::OracleKey, Self::OracleValue>;
    
    	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;
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    
    	/// A configuration for base priority of unsigned transactions.
    	///
    	/// This is exposed so that it can be tuned for particular runtime, when
    	/// multiple pallets send unsigned transactions.
    	type UnsignedPriority: Get<TransactionPriority>;
    
    	/// The identifier type for an authority.
    	type AuthorityId: Member + Parameter + RuntimeAppPublic + Default + 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
    
    		/// Raw values for each oracle operators
    		pub RawValues get(fn raw_values): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::OracleKey => Option<TimestampedValueOf<T>>;
    
    		/// True if Self::values(key) is up to date, otherwise the value is stale
    		pub IsUpdated get(fn is_updated): map hasher(twox_64_concat) T::OracleKey => bool;
    
    		/// Combined value, may not be up to date
    
    		pub Values get(fn values): map hasher(twox_64_concat) T::OracleKey => Option<TimestampedValueOf<T>>;
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    
    		/// If an oracle operator has feed a value in this block
    		HasDispatched: OrderedSet<T::AccountId>;
    
    		// TODO: this shouldn't be required https://github.com/paritytech/substrate/issues/6041
    		/// The current members of the collective. This is stored sorted (just by value).
    		pub Members get(fn members) config(): OrderedSet<T::AccountId>;
    
    		/// Session key for oracle operators
    		pub SessionKeys get(fn session_keys) config(): map hasher(twox_64_concat) T::AccountId => Option<T::AuthorityId>;
    
    		pub Nonces get(fn nonces): map hasher(twox_64_concat) T::AccountId => u32;
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    	}
    }
    
    decl_error! {
    
    	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! {
    	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
    
    		type Error = Error<T>;
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    		fn deposit_event() = default;
    
    Bryan Chen's avatar
    Bryan Chen committed
    
    
    		#[weight = (0, DispatchClass::Operational, Pays::No)]
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		pub fn feed_values(
    			origin,
    			values: Vec<(T::OracleKey, T::OracleValue)>,
    			#[compact] index: u32,
    
    			_block: T::BlockNumber,
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			// since signature verification is done in `validate_unsigned`
    			// we can skip doing it here again.
    			_signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
    		) {
    
    			ensure_none(origin.clone()).or_else(|_| ensure_root(origin))?;
    
    			// validate_unsigned already unsure index is valid
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			let who = Self::members().0[index as usize].clone();
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			Self::do_feed_values(who, values);
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		#[weight = 10_000_000]
    		pub fn set_session_key(origin, key: T::AuthorityId) {
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			let who = ensure_signed(origin)?;
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			ensure!(Self::members().contains(&who), Error::<T>::NoPermission);
    
    			SessionKeys::<T>::insert(who, key);
    
    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>> {
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		Self::members()
    			.0
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    			.iter()
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			.filter_map(|x| Self::raw_values(x, key))
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    			.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>> {
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		if Self::is_updated(key) {
    			<Values<T>>::get(key)
    		} else {
    
    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());
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			IsUpdated::<T>::insert(key, true);
    			Some(timestamped)
    
    Ermal Kaleci's avatar
    Ermal Kaleci committed
    		}
    
    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>> {
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		if Self::is_updated(key) {
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    			Self::values(key)
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		} else {
    			Self::combined(key)
    
    	pub fn get_all_values() -> Vec<(T::OracleKey, Option<TimestampedValueOf<T>>)> {
    		<Values<T>>::iter()
    			.map(|(key, _)| key)
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			.map(|key| {
    				let v = Self::get_no_op(&key);
    				(key, v)
    			})
    
    Shaopeng Wang's avatar
    Shaopeng Wang committed
    	fn combined(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> {
    		let values = Self::read_raw_values(key);
    		T::CombineData::combine_data(key, values, Self::values(key))
    	}
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    	fn do_feed_values(who: T::AccountId, values: Vec<(T::OracleKey, T::OracleValue)>) {
    
    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(&who, &key, timestamped);
    			IsUpdated::<T>::remove(&key);
    
    			T::OnNewData::on_new_data(&who, &key, &value);
    		}
    
    		Self::deposit_event(RawEvent::NewFeedData(who, values));
    	}
    }
    
    impl<T: Trait> InitializeMembers<T::AccountId> for Module<T> {
    	fn initialize_members(members: &[T::AccountId]) {
    		if !members.is_empty() {
    			assert!(Members::<T>::get().0.is_empty(), "Members are already initialized!");
    			Members::<T>::put(OrderedSet::from_sorted_set(members.into()));
    		}
    	}
    }
    
    impl<T: Trait> ChangeMembers<T::AccountId> for Module<T> {
    	fn change_members_sorted(_incoming: &[T::AccountId], outgoing: &[T::AccountId], new: &[T::AccountId]) {
    		// remove session keys and its values
    		for removed in outgoing {
    			SessionKeys::<T>::remove(removed);
    			RawValues::<T>::remove_prefix(removed);
    			Nonces::<T>::remove(removed);
    		}
    
    		Members::<T>::put(OrderedSet::from_sorted_set(new.into()));
    
    		// not bothering to track which key needs recompute, just update all
    		IsUpdated::<T>::remove_all();
    	}
    
    	fn set_prime(_prime: Option<T::AccountId>) {
    		// nothing
    	}
    }
    
    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)
    	}
    }
    
    impl<T: Trait> DataProviderExtended<T::OracleKey, T::OracleValue, T::AccountId> for Module<T> {
    	fn feed_value(who: T::AccountId, key: T::OracleKey, value: T::OracleValue) -> DispatchResult {
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		Self::do_feed_values(who, vec![(key, value)]);
    		Ok(())
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    	}
    
    Bryan Chen's avatar
    Bryan Chen committed
    }
    
    
    impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
    	type Call = Call<T>;
    
    	fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
    
    		if let Call::feed_values(value, index, block, signature) = call {
    			let now = <frame_system::Module<T>>::block_number();
    
    			if now > *block + EXTRINSIC_LONGVITY.into() {
    				return Err(InvalidTransaction::Stale.into());
    			}
    			if now < *block {
    				return Err(InvalidTransaction::Future.into());
    			}
    
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    			let members = Module::<T>::members();
    			let who = members.0.get(*index as usize);
    			if let Some(who) = who {
    				let nonce = Module::<T>::nonces(&who);
    
    				let signature_valid = Module::<T>::session_keys(&who)
    
    					.map(|session_key| {
    						(nonce, block, value).using_encoded(|payload| session_key.verify(&payload, &signature))
    					})
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    					.unwrap_or(false);
    
    				if !signature_valid {
    					return InvalidTransaction::BadProof.into();
    				}
    
    				// ensure account hasn't dispatched an updated yet
    				let ok = HasDispatched::<T>::mutate(|set| set.insert(who.clone()));
    				if !ok {
    					// we already received a feed for this operator
    					return Err(InvalidTransaction::Stale.into());
    				}
    
    				Nonces::<T>::insert(who, nonce + 1);
    
    
    				// make priority less likely to overflow.
    
    				// this ensures tx sent later overrides old one
    				let add_priority = TryInto::<TransactionPriority>::try_into(*block % 1000.into()).unwrap_or(0);
    
    				ValidTransaction::with_tag_prefix("orml-oracle")
    
    					.priority(T::UnsignedPriority::get().saturating_add(add_priority))
    
    					.and_provides((who, nonce))
    
    					.longevity(EXTRINSIC_LONGVITY.into())
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    					.propagate(true)
    					.build()
    			} else {
    				InvalidTransaction::BadProof.into()
    			}
    		} else {
    			InvalidTransaction::Call.into()
    
    Xiliang Chen's avatar
    Xiliang Chen committed
    		}