Skip to main content

Last Seen

//! # Lastseen Pallet
//! <!-- Original author of paragraph: @gang
//!
//! ## Overview
//!
//! Pallet that allows a user to feed the price of each asset.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! _ Feed price of each asset.
//! _ Add Oracle Key
//! _ Add Asset List
//! _ Add Asset
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! `feed_values`: Feed price of each asset.
//! `add_oracle_key`: Add Oracle Key.
//! `add_asset_list`: Add Asset List.
//! `add_asset`: Add Asset.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function. #![cfg_attr(not(feature = "std"), no_std)]

#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};

use frame_support::{
dispatch::{ DispatchResult },
ensure, Twox64Concat, Blake2_128Concat,
//pallet_prelude::_,
pallet_prelude::{Encode, Decode, TypeInfo, MaxEncodedLen, IsType, Member, MaybeSerializeDeserialize,
StorageDoubleMap, StorageMap, StorageValue, ValueQuery, BuildGenesisConfig, RuntimeDebug},
sp_runtime::traits::Zero,
traits::{fungibles, fungibles::Inspect, SortedMembers, Time},
Parameter,
};
use sp_runtime::{traits::AtLeast32BitUnsigned, DispatchError};
use sp_std::{prelude::_, vec};

pub use crate::combine_data::{CombineData, DefaultCombineData};
use traits::{
asset::{AssetInterface, TokenType},
oracle::{OracleInspect, OracleInterface},
};

/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::\*;

mod combine_data;

mod types;
pub use types::\*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

pub mod weights;
pub use weights::WeightInfo;

#[frame_support::pallet]
pub mod pallet {
use super::_;
// use frame_support::pallet_prelude::_;
use frame_system::pallet_prelude::\*;

pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
pub type TimestampedValueOf<T> =
TimestampedValue<<T as Config>::OracleValue, <T as Config>::OracleSupply, MomentOf<T>>;
pub type Decimals = u8;
/// Asset id type alias.
pub type AssetIdOf<T> =
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;

#[derive(
Encode,
Decode,
RuntimeDebug,
Eq,
PartialEq,
Clone,
Copy,
Ord,
PartialOrd,
TypeInfo,
MaxEncodedLen,
)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct TimestampedValue<Value, Supply, Moment> {
pub value: Value,
pub circulating_supply: Supply,
pub timestamp: Moment,
}

#[derive(
Encode,
Decode,
RuntimeDebug,
Eq,
PartialEq,
Clone,
Copy,
Ord,
PartialOrd,
TypeInfo,
MaxEncodedLen,
)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct OracleKeyDetail<Key> {
pub key: Key,
pub decimals: Decimals,
}

#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);

/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

type AssetId: Member
+ Parameter
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ Default
+ Zero
+ From<u32>
+ Into<u32>;

/// Provide the implementation to combine raw values to produce
/// aggregated value
type CombineData: CombineData<Self::OracleKey, TimestampedValueOf<Self>>;

/// The data key type
type OracleKey: Parameter + Member + MaxEncodedLen + MaybeSerializeDeserialize + From<u32>;

/// The data value type
type OracleValue: Parameter
+ Member
+ Ord
+ MaxEncodedLen
+ MaybeSerializeDeserialize
+ AtLeast32BitUnsigned
+ TryInto<<Self::Fungibles as Inspect<Self::AccountId>>::AssetId>
+ From<u128>;

/// The data supply type
type OracleSupply: Parameter
+ Member
+ MaxEncodedLen
+ MaybeSerializeDeserialize
+ From<u128>;

/// Oracle operators.
type Members: SortedMembers<Self::AccountId>;

/// Time provider
type Time: Time;

/// Type to access the Assets Pallet.
type Fungibles: fungibles::Inspect<Self::AccountId, AssetId = Self::AssetId>
+ AssetInterface<Self::AssetId, Self::OracleSupply, Self::AccountId, Decimals, TokenType>;

/// Helper trait for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper;

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}

/// Raw values for each oracle operators
#[pallet::storage]
#[pallet::getter(fn raw_values)]
pub type RawValues<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
T::AccountId,
Twox64Concat,
T::OracleKey,
TimestampedValueOf<T>,
>;

/// Up to date combined value from Raw Values
#[pallet::storage]
#[pallet::getter(fn values)]
pub type Values<T: Config> = StorageMap<_, Twox64Concat, T::OracleKey, TimestampedValueOf<T>>;

/// Oracle key details
#[pallet::storage]
#[pallet::getter(fn key_details)]
pub type OracleKeyDetails<T: Config> =
StorageMap<_, Twox64Concat, T::OracleKey, OracleKeyDetail<T::OracleKey>>;

/// If an oracle operator has fed a value in this block
#[pallet::storage]
pub type IsFeed<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
BlockNumberFor<T>, //block number
bool,
ValueQuery,
>;

/// Token deposit list
#[pallet::storage]
#[pallet::getter(fn asset_list)]
pub type AssetsList<T: Config> = StorageValue<_, Vec<(AssetIdOf<T>, T::OracleKey)>, ValueQuery>;

// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// New feed data is submitted.
NewFeedData {
who: T::AccountId,
values: Vec<(T::OracleKey, T::OracleValue, T::OracleSupply)>,
},
// New oracle key is added.
NewOracleKey {
who: T::AccountId,
key: T::OracleKey,
decimals: Decimals,
},
// Add new asset
AddAssetList {
who: T::AccountId,
asset_id: AssetIdOf<T>,
oracle_key: T::OracleKey,
},
}

// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
// Sender does not have permission
NoPermission,
// Feeder has already feeded at this block
AlreadyFeeded,
// No key provides in oracle
NoOracleKeyProvided,
// Oracle key has already added
AlreadyAdded,
// No Token Deposit Provided
AssetIdNotProvided,
}

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub oracles_key: Vec<(T::OracleKey, Decimals, T::AssetId)>,
}

impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { oracles_key: Vec::new() }
}
}

/// initialize value at genisis
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
Pallet::<T>::initialize_oracle_key_detail(&self.oracles_key);
}
}

// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
// feed_values to delete once it works with staking pallet
#[pallet::call_index(0)]
/// The origin can feed values for asset price.
///
/// Parameters:
/// - `values`: A vector of (OracleKey, OracleVaule, OracleSupply) tuples.
///
/// Emits `NewFeedData` event when successful.
pub fn feed_values(
origin: OriginFor<T>,
values: Vec<(T::OracleKey, T::OracleValue, T::OracleSupply)>,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
Self::do_feed_values_old(who, values)?;
Ok(())
}
#[pallet::call_index(1)]
/// The origin can add oracle key.
///
/// Parameters:
/// - `key`: A OracleKey type.
/// - `decimals`: A Decimals type.
///
/// Emits `NewOracleKey` event when successful.
pub fn add_oracle_key(
origin: OriginFor<T>,
key: T::OracleKey,
decimals: Decimals,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;

ensure!(!OracleKeyDetails::<T>::contains_key(&key), Error::<T>::AlreadyAdded);
ensure!(T::Members::contains(&who), Error::<T>::NoPermission);

OracleKeyDetails::<T>::insert(&key, OracleKeyDetail { key: key.clone(), decimals });

Self::deposit_event(Event::NewOracleKey { who, key, decimals });

Ok(())
}

#[pallet::call_index(2)]
/// The origin can add asset list.
///
/// Parameters:
/// - `initial_asset_list`: A vector of (AssetId, OracleKey) tuples.
///
/// Emits `AddAssetList` event when successful.
pub fn add_asset_list(
origin: OriginFor<T>,
initial_asset_list: Vec<(AssetIdOf<T>, T::OracleKey)>,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
ensure!(T::Members::contains(&who), Error::<T>::NoPermission);

for asset in initial_asset_list {
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
T::OracleSupply,
T::AccountId,
Decimals,
TokenType,
>>::asset_exists(asset.0)?;

AssetsList::<T>::append((asset.0, asset.1.clone()));
Self::deposit_event(Event::AddAssetList {
who: who.clone(),
asset_id: asset.0,
oracle_key: asset.1,
});
}

Ok(())
}

#[pallet::call_index(3)]
/// The origin can add asset.
///
/// Parameters:
/// - `asset_id`: A asset id.
/// - `oracle_key`: A OracleKey type.
///
/// Emits `AddAssetList` event when successful.
pub fn add_asset(
origin: OriginFor<T>,
asset_id: AssetIdOf<T>,
oracle_key: T::OracleKey,
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;

<T::Fungibles as AssetInterface<
AssetIdOf<T>,
T::OracleSupply,
T::AccountId,
Decimals,
TokenType,
>>::asset_exists(asset_id)?;

ensure!(T::Members::contains(&who), Error::<T>::NoPermission);
AssetsList::<T>::append((asset_id, oracle_key.clone()));
Self::deposit_event(Event::AddAssetList { who, asset_id, oracle_key });
Ok(())
}
}

impl<T: Config> WorkersFeedData<T::AccountId, T::OracleKey, T::OracleValue, T::OracleSupply>
for Pallet<T>
{
/// The origin can accept a deposit request if they are currently registered.
///
/// Parameters:
/// - `deposit_id`: The deposit request with deposit ID `deposit_id` that is accepted.
///
/// Emits `DepositAccepted` event when successful.
fn feed_values(
proposer: T::AccountId,
values: Vec<(T::OracleKey, T::OracleValue, T::OracleSupply)>,
) -> DispatchResult {
Self::do_feed_values(proposer, values)?;
Ok(())
}
}

}

impl<T: Config> Pallet<T> {
// A function to initialize oracle details
fn initialize_oracle_key_detail(oracle_details: &[(T::OracleKey, Decimals, T::AssetId)]) {
if !oracle_details.is_empty() {
for detail in oracle_details {
let oracle_value = OracleKeyDetail { key: detail.0.clone(), decimals: detail.1 };
OracleKeyDetails::<T>::insert(detail.0.clone(), oracle_value);
AssetsList::<T>::mutate(|list| list.push((detail.2, detail.0.clone())));
}
}
}

pub fn get_raw_values(key: &T::OracleKey) -> Vec<TimestampedValueOf<T>> {
T::Members::sorted_members()
.iter()
.filter_map(|x| Self::raw_values(x, key))
.collect()
}

pub fn get(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> {
Self::values(key)
}

fn combined(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> {
let values = Self::get_raw_values(key);
T::CombineData::combine_data(key, values, Self::values(key))
}

// This is to delete once multiple workers work
fn do_feed_values_old(
who: T::AccountId,
values: Vec<(T::OracleKey, T::OracleValue, T::OracleSupply)>,
) -> DispatchResult {
// ensure feeder is authorized
ensure!(T::Members::contains(&who), Error::<T>::NoPermission);

// ensure account hasn't dispatched an updated yet
ensure!(
!IsFeed::<T>::get(&who, <frame_system::Pallet<T>>::block_number()),
Error::<T>::AlreadyFeeded
);

let now = T::Time::now();

for (key, value, circulating_supply) in &values {
ensure!(OracleKeyDetails::<T>::contains_key(key), Error::<T>::NoOracleKeyProvided);
let timestamped = TimestampedValue {
value: value.clone(),
circulating_supply: circulating_supply.clone(),
timestamp: now,
};
RawValues::<T>::insert(&who, key, timestamped);

// Update `Values` storage if `combined` yielded result.
if let Some(combined) = Self::combined(key) {
Values::<T>::insert(key, combined);
}
}

IsFeed::<T>::insert(who.clone(), <frame_system::Pallet<T>>::block_number(), true);

Self::deposit_event(Event::NewFeedData { who, values });
Ok(())
}

fn do_feed_values(
who: T::AccountId,
values: Vec<(T::OracleKey, T::OracleValue, T::OracleSupply)>,
) -> DispatchResult {
// ensure feeder is authorized

// ensure account hasn't dispatched an updated yet
// ensure!(
// !IsFeed::<T>::get(&who, <frame_system::Pallet<T>>::block_number()),
// Error::<T>::AlreadyFeeded
// );

let now = T::Time::now();

for (key, value, circulating_supply) in &values {
ensure!(OracleKeyDetails::<T>::contains_key(key), Error::<T>::NoOracleKeyProvided);
let timestamped = TimestampedValue {
value: value.clone(),
circulating_supply: circulating_supply.clone(),
timestamp: now,
};
RawValues::<T>::insert(who.clone(), key, timestamped.clone());

// Update `Values` storage if `combined` yielded result.
// if let Some(combined) = Self::combined(key) {
Values::<T>::insert(key, timestamped);
// }
}

// IsFeed::<T>::insert(who.clone(), <frame_system::Pallet<T>>::block_number(), true);

Ok(())
}

/* pub fn get_oracle_key(asset_id: AssetIdOf<T>) -> Option<T::OracleKey> {
for (cur_asset_id, oracle_key) in AssetsList::<T>::get() {
if cur_asset_id == asset_id {
return Some(oracle_key);
}
}
None
} */

}

impl<T: Config> OracleInspect<AssetIdOf<T>, T::OracleKey, T::OracleValue> for Pallet<T> {
fn get_oracle_key(asset_id: AssetIdOf<T>) -> Option<T::OracleKey> {
for (cur_asset_id, oracle_key) in AssetsList::<T>::get() {
if cur_asset_id == asset_id {
return Some(oracle_key);
}
}
None
}

fn get_value_from_u32(key: u32) -> Result<T::OracleValue, DispatchError> {
let oracle_key: T::OracleKey = key.into();
let timestamped_value =
Values::<T>::get(oracle_key).ok_or(Error::<T>::NoOracleKeyProvided)?;
Ok(timestamped_value.value)
}

fn set_value(key: u32, value: T::OracleValue) {
let oracle_key: T::OracleKey = key.into();
let timestamped: TimestampedValue<T::OracleValue, T::OracleSupply, MomentOf<T>> =
TimestampedValue {
value: value.clone(),
circulating_supply: (0u128).into(),
timestamp: T::Time::now(),
};
Values::<T>::insert(oracle_key, timestamped);
}

}

impl<T: Config> OracleInterface<AssetIdOf<T>, T::OracleValue, Decimals> for Pallet<T> {
fn get_oracle_key_value(asset_id: AssetIdOf<T>) -> Option<(T::OracleValue, Decimals)> {
if let Some(oracle_key) = Self::get_oracle_key(asset_id) {
if let Some(oracle_key_detail) = OracleKeyDetails::<T>::get(&oracle_key) {
if let Some(timestamped_value) = Values::<T>::get(&oracle_key) {
Some((timestamped_value.value, oracle_key_detail.decimals))
} else {
None
}
} else {
None
}
} else {
None
}
}

fn get_oracle_asset_list() -> Vec<AssetIdOf<T>> {
AssetsList::<T>::get()
.iter()
.filter(|(asset_id, _)| {
<T::Fungibles as AssetInterface<
AssetIdOf<T>,
T::OracleSupply,
T::AccountId,
Decimals,
TokenType,
>>::is_wrapped_crypto(*asset_id)
})
.map(|(asset_id, _)| *asset_id)
.collect()
}

}