1#![cfg_attr(not(feature = "std"), no_std)]
18#![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")]
56pub trait BenchmarkHelper<OracleKey, OracleValue, L: Get<u32>> {
58 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 type OnNewData: OnNewData<Self::AccountId, Self::OracleKey, Self::OracleValue>;
93
94 type CombineData: CombineData<Self::OracleKey, TimestampedValueOf<Self, I>>;
97
98 type Time: Time;
100
101 type OracleKey: Parameter + Member + MaxEncodedLen;
103
104 type OracleValue: Parameter + Member + Ord + MaxEncodedLen;
106
107 #[pallet::constant]
109 type RootOperatorAccountId: Get<Self::AccountId>;
110
111 type Members: SortedMembers<Self::AccountId>;
113
114 type WeightInfo: WeightInfo;
116
117 #[pallet::constant]
119 type MaxHasDispatchedSize: Get<u32>;
120
121 #[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 NoPermission,
133 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 NewFeedData {
142 sender: T::AccountId,
143 values: Vec<(T::OracleKey, T::OracleValue)>,
144 },
145 }
146
147 #[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 #[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 #[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 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
171 T::WeightInfo::on_finalize()
172 }
173
174 fn on_finalize(_n: BlockNumberFor<T>) {
175 <HasDispatched<T, I>>::kill();
177 }
178 }
179
180 #[pallet::call]
181 impl<T: Config<I>, I: 'static> Pallet<T, I> {
182 #[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!(
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 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 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 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 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 }
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}