Skip to content

Commit e92ca10

Browse files
committed
Progress on the implementation
1 parent 806a3b4 commit e92ca10

File tree

3 files changed

+186
-4
lines changed

3 files changed

+186
-4
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pallets/price-aggregator/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ sp-arithmetic = { workspace = true }
2121
sp-runtime = { workspace = true }
2222
sp-std = { workspace = true }
2323

24+
orml-traits = { workspace = true }
25+
2426
frame-benchmarking = { workspace = true, optional = true }
2527

2628
[dev-dependencies]
@@ -41,6 +43,7 @@ std = [
4143
"pallet-balances/std",
4244
"astar-primitives/std",
4345
"sp-arithmetic/std",
46+
"orml-traits/std",
4447
]
4548
runtime-benchmarks = [
4649
"frame-benchmarking",

pallets/price-aggregator/src/lib.rs

+182-4
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,91 @@
1616
// You should have received a copy of the GNU General Public License
1717
// along with Astar. If not, see <http://www.gnu.org/licenses/>.
1818

19-
//! TODO
19+
//! # Price Aggregator Pallet
20+
//!
21+
//! ## Overview
22+
//!
23+
//! ##
2024
2125
#![cfg_attr(not(feature = "std"), no_std)]
2226

2327
use frame_support::{pallet_prelude::*, traits::OnRuntimeUpgrade};
2428
use frame_system::{ensure_root, pallet_prelude::*};
2529
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+
};
2735
use sp_std::marker::PhantomData;
2836

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+
}
30101

31102
#[frame_support::pallet]
32103
pub mod pallet {
33-
34104
use super::*;
35105

36106
/// The current storage version.
@@ -44,6 +114,16 @@ pub mod pallet {
44114
pub trait Config: frame_system::Config {
45115
/// The overarching event type.
46116
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>;
47127
}
48128

49129
#[pallet::event]
@@ -57,11 +137,109 @@ pub mod pallet {
57137
// TODO
58138
}
59139

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>;
60148

61149
#[pallet::call]
62150
impl<T: Config> Pallet<T> {
63151
// TODO
64152
}
65153

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+
}
66172

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+
}
67245
}

0 commit comments

Comments
 (0)