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