1#![cfg_attr(not(feature = "std"), no_std)]
18#![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")]
57pub trait BenchmarkHelper<OracleKey, OracleValue, L: Get<u32>> {
59 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 type OnNewData: OnNewData<Self::AccountId, Self::OracleKey, Self::OracleValue>;
105
106 type CombineData: CombineData<Self::OracleKey, TimestampedValueOf<Self, I>>;
109
110 type Time: Time;
112
113 type OracleKey: Parameter + Member + MaxEncodedLen;
115
116 type OracleValue: Parameter + Member + Ord + MaxEncodedLen;
118
119 #[pallet::constant]
121 type RootOperatorAccountId: Get<Self::AccountId>;
122
123 type Members: SortedMembers<Self::AccountId>;
125
126 type WeightInfo: WeightInfo;
128
129 #[pallet::constant]
131 type MaxHasDispatchedSize: Get<u32>;
132
133 #[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 NoPermission,
145 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 NewFeedData {
154 sender: T::AccountId,
155 values: Vec<(T::OracleKey, T::OracleValue)>,
156 },
157 }
158
159 #[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 #[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 #[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 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
183 T::WeightInfo::on_finalize()
184 }
185
186 fn on_finalize(_n: BlockNumberFor<T>) {
187 <HasDispatched<T, I>>::kill();
189 }
190 }
191
192 #[pallet::call]
193 impl<T: Config<I>, I: 'static> Pallet<T, I> {
194 #[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!(
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 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 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 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 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 }
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}