16
16
// You should have received a copy of the GNU General Public License
17
17
// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18
18
19
- //! TODO
19
+ //! # Price Aggregator Pallet
20
+ //!
21
+ //! ## Overview
22
+ //!
23
+ //! ##
20
24
21
25
#![ cfg_attr( not( feature = "std" ) , no_std) ]
22
26
23
27
use frame_support:: { pallet_prelude:: * , traits:: OnRuntimeUpgrade } ;
24
28
use frame_system:: { ensure_root, pallet_prelude:: * } ;
25
29
pub use pallet:: * ;
26
- use sp_arithmetic:: { fixed_point:: FixedU128 , traits:: Zero , FixedPointNumber } ;
30
+ use sp_arithmetic:: {
31
+ fixed_point:: FixedU128 ,
32
+ traits:: { CheckedAdd , Saturating , Zero } ,
33
+ FixedPointNumber ,
34
+ } ;
27
35
use sp_std:: marker:: PhantomData ;
28
36
29
- use astar_primitives:: oracle:: PriceProvider ;
37
+ pub use orml_traits:: OnNewData ;
38
+
39
+ use astar_primitives:: { oracle:: PriceProvider , AccountId } ;
40
+
41
+ // TODO: move to primitives
42
+ #[ derive( Encode , Decode , MaxEncodedLen , Clone , Copy , Debug , PartialEq , Eq , TypeInfo ) ]
43
+ pub enum CurrencyId {
44
+ ASTR ,
45
+ }
46
+ pub type CurrencyAmount = FixedU128 ;
47
+
48
+ /// Trait for processing accumulated currency values within a single block.
49
+ ///
50
+ /// This can be anything from median, average, or more complex calculation.
51
+ pub trait ProcessBlockValues {
52
+ /// Process the accumulated values and return the result.
53
+ ///
54
+ /// In case of an error, return an error message.
55
+ fn process ( values : & [ CurrencyAmount ] ) -> Result < CurrencyAmount , & ' static str > ;
56
+ }
57
+
58
+ const LOG_TARGET : & str = "price-aggregator" ;
59
+
60
+ /// Used to aggregate the accumulated values over some time period.
61
+ ///
62
+ /// To avoid having a large memory footprint, values are summed up into a single accumulator.
63
+ /// Number of summed up values is tracked separately.
64
+ #[ derive( Encode , Decode , MaxEncodedLen , Default , Clone , Copy , Debug , PartialEq , Eq , TypeInfo ) ]
65
+ pub struct ValueAggregator {
66
+ /// Total accumulated value amount.
67
+ total : CurrencyAmount ,
68
+ /// Number of values accumulated.
69
+ count : u32 ,
70
+ }
71
+
72
+ impl ValueAggregator {
73
+ /// Attempts to add a value to the aggregator.
74
+ ///
75
+ /// Returns an error if the addition would cause an overflow in the accumulator or the counter.
76
+ pub fn try_add ( & mut self , value : CurrencyAmount ) -> Result < ( ) , & ' static str > {
77
+ self . total = self
78
+ . total
79
+ . checked_add ( & value)
80
+ . ok_or ( "Failed to add value to the aggregator due to overflow." ) ?;
81
+
82
+ self . count = self
83
+ . count
84
+ . checked_add ( 1 )
85
+ . ok_or ( "Failed to increment count in the aggregator due to overflow." ) ?;
86
+
87
+ Ok ( ( ) )
88
+ }
89
+
90
+ /// Returns the average of the accumulated values.
91
+ pub fn average ( & self ) -> CurrencyAmount {
92
+ if self . count == 0 {
93
+ return CurrencyAmount :: zero ( ) ;
94
+ }
95
+
96
+ // TODO: maybe this can be written in a way that preserves more precision?
97
+ self . total
98
+ . saturating_mul ( FixedU128 :: from_rational ( 1 , self . count . into ( ) ) )
99
+ }
100
+ }
30
101
31
102
#[ frame_support:: pallet]
32
103
pub mod pallet {
33
-
34
104
use super :: * ;
35
105
36
106
/// The current storage version.
@@ -44,6 +114,16 @@ pub mod pallet {
44
114
pub trait Config : frame_system:: Config {
45
115
/// The overarching event type.
46
116
type RuntimeEvent : From < Event < Self > > + IsType < <Self as frame_system:: Config >:: RuntimeEvent > ;
117
+
118
+ /// Maximum number of distinct currency values we can store during a single block.
119
+ #[ pallet:: constant]
120
+ type MaxValuesPerBlock : Get < u32 > ;
121
+
122
+ /// Used to process accumulated values in the current block.
123
+ type ProcessBlockValues : ProcessBlockValues ;
124
+
125
+ /// Native currency ID that this pallet is supposed to track.
126
+ type NativeCurrencyId : Get < CurrencyId > ;
47
127
}
48
128
49
129
#[ pallet:: event]
@@ -57,11 +137,109 @@ pub mod pallet {
57
137
// TODO
58
138
}
59
139
140
+ /// Storage for the accumulated native currency price in the current block.
141
+ #[ pallet:: storage]
142
+ pub type CurrentValues < T : Config > =
143
+ StorageValue < _ , BoundedVec < CurrencyAmount , T :: MaxValuesPerBlock > , ValueQuery > ;
144
+
145
+ /// Used to store the aggregated processed block values during some time period.
146
+ #[ pallet:: storage]
147
+ pub type IntermediateValueAggregator < T : Config > = StorageValue < _ , ValueAggregator , ValueQuery > ;
60
148
61
149
#[ pallet:: call]
62
150
impl < T : Config > Pallet < T > {
63
151
// TODO
64
152
}
65
153
154
+ #[ pallet:: hooks]
155
+ impl < T : Config > Hooks < BlockNumberFor < T > > for Pallet < T > {
156
+ fn on_initialize ( _now : BlockNumberFor < T > ) -> Weight {
157
+ // TODO: benchmarks & account for the possible changes in the on_finalize
158
+ Weight :: zero ( )
159
+ }
160
+
161
+ fn on_finalize ( _now : BlockNumberFor < T > ) {
162
+ // 1. Process the accumulated native currency values in the current block.
163
+ Self :: process_block_aggregated_values ( ) ;
164
+
165
+ // 2. Check if we need to push the average aggregated value to the storage.
166
+ let is_average_value_push_time = false ; // TODO, clearly
167
+ if is_average_value_push_time {
168
+ Self :: process_intermediate_aggregated_values ( ) ;
169
+ }
170
+ }
171
+ }
66
172
173
+ impl < T : Config > Pallet < T > {
174
+ /// Used to process the native currency values accumulated in the current block.
175
+ ///
176
+ /// Guarantees that the accumulated values are cleared after processing.
177
+ /// In case of an error during processing, intermediate aggregated value is not updated.
178
+ fn process_block_aggregated_values ( ) {
179
+ // 1. Take the accumulated block values, clearing the existing storage.
180
+ let accumulated_values = CurrentValues :: < T > :: take ( ) ;
181
+
182
+ // 2. Attempt to process accumulated block values.
183
+ let processed_value = match T :: ProcessBlockValues :: process (
184
+ accumulated_values. as_slice ( ) ,
185
+ ) {
186
+ Ok ( value) => value,
187
+ Err ( message) => {
188
+ log:: error!(
189
+ target: LOG_TARGET ,
190
+ "Failed to process the accumulated native currency values in the current block. \
191
+ Reason: {:?}",
192
+ message
193
+ ) ;
194
+
195
+ // Nothing to do if we have no valid value to store.
196
+ return ;
197
+ }
198
+ } ;
199
+
200
+ // 3. Attempt to store the processed value.
201
+ IntermediateValueAggregator :: < T > :: mutate ( |aggregator| {
202
+ match aggregator. try_add ( processed_value) {
203
+ Ok ( ( ) ) => { }
204
+ Err ( message) => {
205
+ log:: error!(
206
+ target: LOG_TARGET ,
207
+ "Failed to add the processed native currency value to the intermediate storage. \
208
+ Reason: {:?}",
209
+ message
210
+ ) ;
211
+ }
212
+ }
213
+ } ) ;
214
+ }
215
+
216
+ /// Used to process the intermediate aggregated values, and push them to the moving average storage.
217
+ fn process_intermediate_aggregated_values ( ) {
218
+ let average_value = IntermediateValueAggregator :: < T > :: take ( ) . average ( ) ;
219
+ }
220
+ }
221
+
222
+ impl < T : Config > OnNewData < T :: AccountId , CurrencyId , CurrencyAmount > for Pallet < T > {
223
+ fn on_new_data ( who : & T :: AccountId , key : & CurrencyId , value : & CurrencyAmount ) {
224
+ // TODO
225
+ // Do we need to prevent same account posting multiple values in the same block? Or will the other pallet take care of that?
226
+
227
+ // Ignore any currency that is not native currency.
228
+ if T :: NativeCurrencyId :: get ( ) != * key {
229
+ return ;
230
+ }
231
+
232
+ CurrentValues :: < T > :: mutate ( |v| match v. try_push ( * value) {
233
+ Ok ( ( ) ) => { }
234
+ Err ( _) => {
235
+ log:: error!(
236
+ target: LOG_TARGET ,
237
+ "Failed to push native currency value into the ongoing block due to exceeded capacity. \
238
+ Value was submitted by: {:?}",
239
+ who
240
+ ) ;
241
+ }
242
+ } ) ;
243
+ }
244
+ }
67
245
}
0 commit comments