orml_oracle/
lib.rs

1//! # Oracle
2//! A module to allow oracle operators to feed external data.
3//!
4//! - [`Config`](./trait.Config.html)
5//! - [`Call`](./enum.Call.html)
6//! - [`Module`](./struct.Module.html)
7//!
8//! ## Overview
9//!
10//! This module exposes capabilities for oracle operators to feed external
11//! offchain data. The raw values can be combined to provide an aggregated
12//! value.
13//!
14//! The data is valid only if fed by an authorized operator.
15//! `pallet_membership` in FRAME can be used to as source of `T::Members`.
16
17#![cfg_attr(not(feature = "std"), no_std)]
18// Disable the following two lints since they originate from an external macro (namely decl_storage)
19#![allow(clippy::string_lit_as_bytes)]
20#![allow(clippy::unused_unit)]
21#![allow(clippy::useless_conversion)]
22
23use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
24
25#[cfg(feature = "std")]
26use serde::{Deserialize, Serialize};
27
28use frame_support::{
29	dispatch::Pays,
30	ensure,
31	pallet_prelude::*,
32	traits::{ChangeMembers, Get, SortedMembers, Time},
33	weights::Weight,
34	Parameter,
35};
36use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
37pub use orml_traits::{CombineData, DataFeeder, DataProvider, DataProviderExtended, OnNewData};
38use orml_utilities::OrderedSet;
39use scale_info::TypeInfo;
40use sp_runtime::{traits::Member, DispatchResult, RuntimeDebug};
41use sp_std::{prelude::*, vec};
42
43pub use crate::default_combine_data::DefaultCombineData;
44
45#[cfg(feature = "runtime-benchmarks")]
46mod benchmarking;
47
48mod default_combine_data;
49mod mock;
50mod tests;
51mod weights;
52
53pub use module::*;
54pub use weights::WeightInfo;
55
56#[cfg(feature = "runtime-benchmarks")]
57/// Helper trait for benchmarking.
58pub trait BenchmarkHelper<OracleKey, OracleValue, L: Get<u32>> {
59	/// Returns a list of `(oracle_key, oracle_value)` pairs to be used for
60	/// benchmarking.
61	///
62	/// NOTE: User should ensure to at least submit two values, otherwise the
63	/// benchmark linear analysis might fail.
64	fn get_currency_id_value_pairs() -> BoundedVec<(OracleKey, OracleValue), L>;
65}
66
67#[cfg(feature = "runtime-benchmarks")]
68impl<OracleKey, OracleValue, L: Get<u32>> BenchmarkHelper<OracleKey, OracleValue, L> for () {
69	fn get_currency_id_value_pairs() -> BoundedVec<(OracleKey, OracleValue), L> {
70		BoundedVec::default()
71	}
72}
73
74#[frame_support::pallet]
75pub mod module {
76	use super::*;
77
78	pub(crate) type MomentOf<T, I = ()> = <<T as Config<I>>::Time as Time>::Moment;
79	pub(crate) type TimestampedValueOf<T, I = ()> = TimestampedValue<<T as Config<I>>::OracleValue, MomentOf<T, I>>;
80
81	#[derive(
82		Encode,
83		Decode,
84		DecodeWithMemTracking,
85		RuntimeDebug,
86		Eq,
87		PartialEq,
88		Clone,
89		Copy,
90		Ord,
91		PartialOrd,
92		TypeInfo,
93		MaxEncodedLen,
94	)]
95	#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
96	pub struct TimestampedValue<Value, Moment> {
97		pub value: Value,
98		pub timestamp: Moment,
99	}
100
101	#[pallet::config]
102	pub trait Config<I: 'static = ()>: frame_system::Config {
103		/// Hook on new data received
104		type OnNewData: OnNewData<Self::AccountId, Self::OracleKey, Self::OracleValue>;
105
106		/// Provide the implementation to combine raw values to produce
107		/// aggregated value
108		type CombineData: CombineData<Self::OracleKey, TimestampedValueOf<Self, I>>;
109
110		/// Time provider
111		type Time: Time;
112
113		/// The data key type
114		type OracleKey: Parameter + Member + MaxEncodedLen;
115
116		/// The data value type
117		type OracleValue: Parameter + Member + Ord + MaxEncodedLen;
118
119		/// The root operator account id, record all sudo feeds on this account.
120		#[pallet::constant]
121		type RootOperatorAccountId: Get<Self::AccountId>;
122
123		/// Oracle operators.
124		type Members: SortedMembers<Self::AccountId>;
125
126		/// Weight information for extrinsics in this module.
127		type WeightInfo: WeightInfo;
128
129		/// Maximum size of HasDispatched
130		#[pallet::constant]
131		type MaxHasDispatchedSize: Get<u32>;
132
133		/// Maximum size the vector used for feed values
134		#[pallet::constant]
135		type MaxFeedValues: Get<u32>;
136
137		#[cfg(feature = "runtime-benchmarks")]
138		type BenchmarkHelper: BenchmarkHelper<Self::OracleKey, Self::OracleValue, Self::MaxFeedValues>;
139	}
140
141	#[pallet::error]
142	pub enum Error<T, I = ()> {
143		/// Sender does not have permission
144		NoPermission,
145		/// Feeder has already fed at this block
146		AlreadyFeeded,
147	}
148
149	#[pallet::event]
150	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
151	pub enum Event<T: Config<I>, I: 'static = ()> {
152		/// New feed data is submitted.
153		NewFeedData {
154			sender: T::AccountId,
155			values: Vec<(T::OracleKey, T::OracleValue)>,
156		},
157	}
158
159	/// Raw values for each oracle operators
160	#[pallet::storage]
161	#[pallet::getter(fn raw_values)]
162	pub type RawValues<T: Config<I>, I: 'static = ()> =
163		StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::OracleKey, TimestampedValueOf<T, I>>;
164
165	/// Up to date combined value from Raw Values
166	#[pallet::storage]
167	#[pallet::getter(fn values)]
168	pub type Values<T: Config<I>, I: 'static = ()> =
169		StorageMap<_, Twox64Concat, <T as Config<I>>::OracleKey, TimestampedValueOf<T, I>>;
170
171	/// If an oracle operator has fed a value in this block
172	#[pallet::storage]
173	pub(crate) type HasDispatched<T: Config<I>, I: 'static = ()> =
174		StorageValue<_, OrderedSet<T::AccountId, T::MaxHasDispatchedSize>, ValueQuery>;
175
176	#[pallet::pallet]
177	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
178
179	#[pallet::hooks]
180	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
181		/// `on_initialize` to return the weight used in `on_finalize`.
182		fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
183			T::WeightInfo::on_finalize()
184		}
185
186		fn on_finalize(_n: BlockNumberFor<T>) {
187			// cleanup for next block
188			<HasDispatched<T, I>>::kill();
189		}
190	}
191
192	#[pallet::call]
193	impl<T: Config<I>, I: 'static> Pallet<T, I> {
194		/// Feed the external value.
195		///
196		/// Require authorized operator.
197		#[pallet::call_index(0)]
198		#[pallet::weight(T::WeightInfo::feed_values(values.len() as u32))]
199		pub fn feed_values(
200			origin: OriginFor<T>,
201			values: BoundedVec<(T::OracleKey, T::OracleValue), T::MaxFeedValues>,
202		) -> DispatchResultWithPostInfo {
203			let feeder = ensure_signed(origin.clone())
204				.map(Some)
205				.or_else(|_| ensure_root(origin).map(|_| None))?;
206
207			let who = Self::ensure_account(feeder)?;
208
209			// ensure account hasn't dispatched an updated yet
210			ensure!(
211				HasDispatched::<T, I>::mutate(|set| set.insert(who.clone())),
212				Error::<T, I>::AlreadyFeeded
213			);
214
215			Self::do_feed_values(who, values.into())?;
216			Ok(Pays::No.into())
217		}
218	}
219}
220
221impl<T: Config<I>, I: 'static> Pallet<T, I> {
222	pub fn read_raw_values(key: &T::OracleKey) -> Vec<TimestampedValueOf<T, I>> {
223		T::Members::sorted_members()
224			.iter()
225			.chain([T::RootOperatorAccountId::get()].iter())
226			.filter_map(|x| Self::raw_values(x, key))
227			.collect()
228	}
229
230	/// Fetch current combined value.
231	pub fn get(key: &T::OracleKey) -> Option<TimestampedValueOf<T, I>> {
232		Self::values(key)
233	}
234
235	#[allow(clippy::complexity)]
236	pub fn get_all_values() -> Vec<(T::OracleKey, Option<TimestampedValueOf<T, I>>)> {
237		<Values<T, I>>::iter().map(|(k, v)| (k, Some(v))).collect()
238	}
239
240	fn combined(key: &T::OracleKey) -> Option<TimestampedValueOf<T, I>> {
241		let values = Self::read_raw_values(key);
242		T::CombineData::combine_data(key, values, Self::values(key))
243	}
244
245	fn ensure_account(who: Option<T::AccountId>) -> Result<T::AccountId, DispatchError> {
246		// ensure feeder is authorized
247		if let Some(who) = who {
248			ensure!(T::Members::contains(&who), Error::<T, I>::NoPermission);
249			Ok(who)
250		} else {
251			Ok(T::RootOperatorAccountId::get())
252		}
253	}
254
255	fn do_feed_values(who: T::AccountId, values: Vec<(T::OracleKey, T::OracleValue)>) -> DispatchResult {
256		let now = T::Time::now();
257		for (key, value) in &values {
258			let timestamped = TimestampedValue {
259				value: value.clone(),
260				timestamp: now,
261			};
262			RawValues::<T, I>::insert(&who, key, timestamped);
263
264			// Update `Values` storage if `combined` yielded result.
265			if let Some(combined) = Self::combined(key) {
266				<Values<T, I>>::insert(key, combined);
267			}
268
269			T::OnNewData::on_new_data(&who, key, value);
270		}
271		Self::deposit_event(Event::NewFeedData { sender: who, values });
272		Ok(())
273	}
274}
275
276impl<T: Config<I>, I: 'static> ChangeMembers<T::AccountId> for Pallet<T, I> {
277	fn change_members_sorted(_incoming: &[T::AccountId], outgoing: &[T::AccountId], _new: &[T::AccountId]) {
278		// remove values
279		for removed in outgoing {
280			let _ = RawValues::<T, I>::clear_prefix(removed, u32::MAX, None);
281		}
282	}
283
284	fn set_prime(_prime: Option<T::AccountId>) {
285		// nothing
286	}
287}
288
289impl<T: Config<I>, I: 'static> DataProvider<T::OracleKey, T::OracleValue> for Pallet<T, I> {
290	fn get(key: &T::OracleKey) -> Option<T::OracleValue> {
291		Self::get(key).map(|timestamped_value| timestamped_value.value)
292	}
293}
294impl<T: Config<I>, I: 'static> DataProviderExtended<T::OracleKey, TimestampedValueOf<T, I>> for Pallet<T, I> {
295	fn get_no_op(key: &T::OracleKey) -> Option<TimestampedValueOf<T, I>> {
296		Self::get(key)
297	}
298
299	#[allow(clippy::complexity)]
300	fn get_all_values() -> Vec<(T::OracleKey, Option<TimestampedValueOf<T, I>>)> {
301		Self::get_all_values()
302	}
303}
304
305impl<T: Config<I>, I: 'static> DataFeeder<T::OracleKey, T::OracleValue, T::AccountId> for Pallet<T, I> {
306	fn feed_value(who: Option<T::AccountId>, key: T::OracleKey, value: T::OracleValue) -> DispatchResult {
307		Self::do_feed_values(Self::ensure_account(who)?, vec![(key, value)])
308	}
309}