From a1addfb23af13a7a542ab51cf35b67e3f02acc05 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 11 Apr 2023 08:54:51 +0200 Subject: [PATCH 01/16] add price trait & mock --- libs/mocks/src/lib.rs | 2 + libs/mocks/src/prices.rs | 99 +++++++++++++++++++++++++++++++++++++++ libs/traits/src/lib.rs | 2 + libs/traits/src/prices.rs | 44 +++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 libs/mocks/src/prices.rs create mode 100644 libs/traits/src/prices.rs diff --git a/libs/mocks/src/lib.rs b/libs/mocks/src/lib.rs index e7ec9e27e3..b649ae2f70 100644 --- a/libs/mocks/src/lib.rs +++ b/libs/mocks/src/lib.rs @@ -1,11 +1,13 @@ mod fees; mod permissions; mod pools; +mod prices; mod rewards; pub use fees::pallet as pallet_mock_fees; pub use permissions::pallet as pallet_mock_permissions; pub use pools::pallet as pallet_mock_pools; +pub use prices::pallet as pallet_mock_prices; pub use rewards::pallet as pallet_mock_rewards; #[cfg(test)] diff --git a/libs/mocks/src/prices.rs b/libs/mocks/src/prices.rs new file mode 100644 index 0000000000..99f92e6a5f --- /dev/null +++ b/libs/mocks/src/prices.rs @@ -0,0 +1,99 @@ +#[frame_support::pallet] +pub mod pallet { + use cfg_traits::prices::{PriceCache, PriceRegistry}; + use frame_support::pallet_prelude::*; + use mock_builder::{execute_call, register_call}; + + #[pallet::config] + pub trait Config: frame_system::Config { + type PriceId; + type CollectionId; + type Cache: PriceCache; + type Price; + type Moment; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type CallIds = StorageMap< + _, + Blake2_128Concat, + ::Output, + mock_builder::CallId, + >; + + impl Pallet { + pub fn mock_price(f: impl Fn(T::PriceId) -> DispatchResult + 'static) { + register_call!(f); + } + + pub fn mock_cache( + f: impl Fn(T::CollectionId) -> Result + 'static, + ) { + register_call!(f); + } + + pub fn mock_register_price_id( + f: impl Fn(T::PriceId, T::CollectionId) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b)| f(a, b)); + } + + pub fn mock_unregister_price_id( + f: impl Fn(T::PriceId, T::CollectionId) -> DispatchResult + 'static, + ) { + register_call!(move |(a, b)| f(a, b)); + } + } + + impl PriceRegistry for Pallet { + type Cache = T::Cache; + type CollectionId = T::CollectionId; + type Moment = T::Moment; + type Price = T::Price; + type PriceId = T::PriceId; + + fn price(a: T::PriceId) -> Result<(T::Price, T::Moment), DispatchError> { + execute_call!(a) + } + + fn cache(a: T::CollectionId) -> Result { + execute_call!(a) + } + + fn register_price_id(a: T::PriceId, b: T::CollectionId) -> DispatchResult { + execute_call!((a, b)) + } + + fn unregister_price_id(a: T::PriceId, b: T::CollectionId) -> DispatchResult { + execute_call!((a, b)) + } + } + + #[cfg(feature = "std")] + pub mod util { + use std::collections::HashMap; + + use super::*; + + pub struct MockPriceCache(pub HashMap); + + impl PriceCache for MockPriceCache + where + T::PriceId: std::hash::Hash + Eq, + T::Price: Clone, + T::Moment: Clone, + { + fn price(&self, price_id: T::PriceId) -> Result<(T::Price, T::Moment), DispatchError> { + Ok(self + .0 + .get(&price_id) + .ok_or(DispatchError::CannotLookup)? + .clone()) + } + } + } +} diff --git a/libs/traits/src/lib.rs b/libs/traits/src/lib.rs index 4b193d01dd..0ef61d824a 100644 --- a/libs/traits/src/lib.rs +++ b/libs/traits/src/lib.rs @@ -41,6 +41,8 @@ pub mod ops; /// Traits related to rewards. pub mod rewards; +pub mod prices; + /// A trait used for loosely coupling the claim pallet with a reward mechanism. /// /// ## Overview diff --git a/libs/traits/src/prices.rs b/libs/traits/src/prices.rs new file mode 100644 index 0000000000..897ffb5fd6 --- /dev/null +++ b/libs/traits/src/prices.rs @@ -0,0 +1,44 @@ +use sp_runtime::{DispatchError, DispatchResult}; + +/// Abstraction that represnets a storage where +/// you can subscribe to price updates and collect them +pub trait PriceRegistry { + /// A price identification + type PriceId; + + /// A collection identification + type CollectionId; + + /// A collection of prices + type Cache: PriceCache; + + /// Represents a price + type Price; + + /// Represents a timestamp + type Moment; + + /// Get the price for a price id + fn price(price_id: Self::PriceId) -> Result<(Self::Price, Self::Moment), DispatchError>; + + /// Retrives a collection of prices with all prices associated to a collection id + fn cache(collection_id: Self::CollectionId) -> Result; + + /// Start listening price changes for a price id in a collection id + fn register_price_id( + price_id: Self::PriceId, + collection_id: Self::CollectionId, + ) -> DispatchResult; + + /// Start listening price changes for a price id in a collection id + fn unregister_price_id( + price_id: Self::PriceId, + collection_id: Self::CollectionId, + ) -> DispatchResult; +} + +/// Abstration to represent a cached collection of prices in memory +pub trait PriceCache { + /// Return the last price along with the moment it was updated last time + fn price(&self, price_id: PriceId) -> Result<(Price, Moment), DispatchError>; +} From 591f924eabf851f6677543732bf6a4b9029fd75f Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 17 Apr 2023 12:35:22 +0200 Subject: [PATCH 02/16] add pallet-pool-price-feed basic structure --- Cargo.lock | 17 ++++++ Cargo.toml | 1 + libs/traits/src/prices.rs | 8 ++- pallets/pool-price-feed/Cargo.toml | 55 ++++++++++++++++++ pallets/pool-price-feed/src/lib.rs | 92 ++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 pallets/pool-price-feed/Cargo.toml create mode 100644 pallets/pool-price-feed/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1fec657027..844e56dc1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7583,6 +7583,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-pool-price-feed" +version = "1.0.0" +dependencies = [ + "cfg-mocks", + "cfg-traits", + "frame-support", + "frame-system", + "parity-scale-codec 3.4.0", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-pool-registry" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 6123a7a7f0..cc373b866f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "pallets/permissions", "pallets/pool-system", "pallets/pool-registry", + "pallets/pool-price-feed", "pallets/restricted-tokens", "pallets/transfer-allowlist", "pallets/rewards", diff --git a/libs/traits/src/prices.rs b/libs/traits/src/prices.rs index 897ffb5fd6..1e30f8a68c 100644 --- a/libs/traits/src/prices.rs +++ b/libs/traits/src/prices.rs @@ -1,6 +1,6 @@ use sp_runtime::{DispatchError, DispatchResult}; -/// Abstraction that represnets a storage where +/// Abstraction that represents a storage where /// you can subscribe to price updates and collect them pub trait PriceRegistry { /// A price identification @@ -18,7 +18,8 @@ pub trait PriceRegistry { /// Represents a timestamp type Moment; - /// Get the price for a price id + /// Return the last price value for a price id + /// along with the moment it was updated last time fn price(price_id: Self::PriceId) -> Result<(Self::Price, Self::Moment), DispatchError>; /// Retrives a collection of prices with all prices associated to a collection id @@ -39,6 +40,7 @@ pub trait PriceRegistry { /// Abstration to represent a cached collection of prices in memory pub trait PriceCache { - /// Return the last price along with the moment it was updated last time + /// Return the last price value for a price id + /// along with the moment it was updated last time fn price(&self, price_id: PriceId) -> Result<(Price, Moment), DispatchError>; } diff --git a/pallets/pool-price-feed/Cargo.toml b/pallets/pool-price-feed/Cargo.toml new file mode 100644 index 0000000000..ee9a6504d7 --- /dev/null +++ b/pallets/pool-price-feed/Cargo.toml @@ -0,0 +1,55 @@ +[package] +authors = ["Centrifuge "] +description = "Pallet to feed pools with prices" +edition = "2021" +license = "LGPL-3.0" +name = "pallet-pool-price-feed" +repository = "https://github.com/centrifuge/centrifuge-chain" +version = "1.0.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", default-features = false, version = "3.0.0", features = ["derive"] } +scale-info = { version = "2.3.0", default-features = false, features = ["derive"] } + +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.37" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.37" } +sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.37" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.37" } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.37" } + +cfg-traits = { path = "../../libs/traits", default-features = false } + +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" } + +cfg-mocks = { path = "../../libs/mocks" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-arithmetic/std", + "sp-runtime/std", + "sp-std/std", + "cfg-traits/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "cfg-traits/runtime-benchmarks", + "cfg-mocks/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "cfg-traits/try-runtime", + "cfg-mocks/try-runtime", +] diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs new file mode 100644 index 0000000000..a9827ba3fe --- /dev/null +++ b/pallets/pool-price-feed/src/lib.rs @@ -0,0 +1,92 @@ +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use cfg_traits::prices::{PriceCache, PriceRegistry}; + use frame_support::pallet_prelude::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// A price identification + type PriceId: Parameter + MaxEncodedLen; + + /// A pool identification + type PoolId: Parameter + MaxEncodedLen; + + /// Represents a price + type Price: Parameter + MaxEncodedLen; + + /// Represents a timestamp + type Moment: Parameter + MaxEncodedLen; + + /// Max size of a price collection + #[pallet::constant] + type MaxCollectionSize: Get; + } + + #[pallet::storage] + pub(crate) type ListeningPriceId = + StorageDoubleMap<_, Blake2_128Concat, T::PriceId, Blake2_128Concat, T::PoolId, ()>; + + #[pallet::storage] + pub(crate) type PoolPrices = StorageMap< + _, + Blake2_128Concat, + T::PoolId, + BoundedVec<(T::Price, T::Moment), T::MaxCollectionSize>, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event {} + + #[pallet::error] + pub enum Error {} + + impl PriceRegistry for Pallet { + type Cache = PriceCacheVec; + type CollectionId = T::PoolId; + type Moment = T::Moment; + type Price = T::Price; + type PriceId = T::PriceId; + + fn price(price_id: Self::PriceId) -> Result<(Self::Price, Self::Moment), DispatchError> { + todo!() + } + + fn cache(collection_id: Self::CollectionId) -> Result { + todo!() + } + + fn register_price_id( + price_id: Self::PriceId, + collection_id: Self::CollectionId, + ) -> DispatchResult { + todo!() + } + + fn unregister_price_id( + price_id: Self::PriceId, + collection_id: Self::CollectionId, + ) -> DispatchResult { + todo!() + } + } + + pub struct PriceCacheVec(BoundedVec<(T::Price, T::Moment), T::MaxCollectionSize>); + + impl PriceCache for PriceCacheVec { + fn price(&self, price_id: T::PriceId) -> Result<(T::Price, T::Moment), DispatchError> { + todo!() + } + } +} From e0534dbd4bd2d6901364b9e688beebb874bd6d26 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 17 Apr 2023 16:21:31 +0200 Subject: [PATCH 03/16] add OnNewData trait --- Cargo.lock | 1 + pallets/pool-price-feed/Cargo.toml | 3 +++ pallets/pool-price-feed/src/lib.rs | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 844e56dc1a..55a95f2be9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7591,6 +7591,7 @@ dependencies = [ "cfg-traits", "frame-support", "frame-system", + "orml-traits", "parity-scale-codec 3.4.0", "scale-info", "sp-arithmetic", diff --git a/pallets/pool-price-feed/Cargo.toml b/pallets/pool-price-feed/Cargo.toml index ee9a6504d7..b4815530e2 100644 --- a/pallets/pool-price-feed/Cargo.toml +++ b/pallets/pool-price-feed/Cargo.toml @@ -20,6 +20,8 @@ sp-arithmetic = { git = "https://github.com/paritytech/substrate", default-featu sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.37" } sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.37" } +orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = true, branch = "polkadot-v0.9.37" } + cfg-traits = { path = "../../libs/traits", default-features = false } [dev-dependencies] @@ -39,6 +41,7 @@ std = [ "sp-runtime/std", "sp-std/std", "cfg-traits/std", + "orml-traits/std", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs index a9827ba3fe..0842aa91bf 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/pool-price-feed/src/lib.rs @@ -4,6 +4,7 @@ pub use pallet::*; pub mod pallet { use cfg_traits::prices::{PriceCache, PriceRegistry}; use frame_support::pallet_prelude::*; + use orml_traits::OnNewData; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -82,6 +83,12 @@ pub mod pallet { } } + impl OnNewData for Pallet { + fn on_new_data(who: &T::AccountId, price_id: &T::PriceId, price: &T::Price) { + todo!() + } + } + pub struct PriceCacheVec(BoundedVec<(T::Price, T::Moment), T::MaxCollectionSize>); impl PriceCache for PriceCacheVec { From 0d2f18dd0a708687f7665bbfde3944d7ade25b68 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 17 Apr 2023 19:32:08 +0200 Subject: [PATCH 04/16] add PriceRegistry trait implementation --- libs/traits/src/prices.rs | 11 ++- pallets/pool-price-feed/src/lib.rs | 109 ++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 31 deletions(-) diff --git a/libs/traits/src/prices.rs b/libs/traits/src/prices.rs index 1e30f8a68c..fcad5ed111 100644 --- a/libs/traits/src/prices.rs +++ b/libs/traits/src/prices.rs @@ -18,9 +18,9 @@ pub trait PriceRegistry { /// Represents a timestamp type Moment; - /// Return the last price value for a price id - /// along with the moment it was updated last time - fn price(price_id: Self::PriceId) -> Result<(Self::Price, Self::Moment), DispatchError>; + /// Return the last price value for a price id along with the moment it was updated last time + fn price(price_id: Self::PriceId) + -> Result, DispatchError>; /// Retrives a collection of prices with all prices associated to a collection id fn cache(collection_id: Self::CollectionId) -> Result; @@ -40,7 +40,6 @@ pub trait PriceRegistry { /// Abstration to represent a cached collection of prices in memory pub trait PriceCache { - /// Return the last price value for a price id - /// along with the moment it was updated last time - fn price(&self, price_id: PriceId) -> Result<(Price, Moment), DispatchError>; + /// Return the last price value for a price id along with the moment it was updated last time + fn price(&self, price_id: PriceId) -> Result, DispatchError>; } diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs index 0842aa91bf..ef58091f32 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/pool-price-feed/src/lib.rs @@ -4,7 +4,16 @@ pub use pallet::*; pub mod pallet { use cfg_traits::prices::{PriceCache, PriceRegistry}; use frame_support::pallet_prelude::*; - use orml_traits::OnNewData; + use orml_traits::{DataProviderExtended, OnNewData, TimestampedValue}; + use sp_runtime::DispatchError; + + /// Type that contains price information + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)] + #[scale_info(skip_type_params(T))] + pub struct PriceInfo { + price_id: T::PriceId, + value: Option<(T::Price, T::Moment)>, + } const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -20,30 +29,37 @@ pub mod pallet { /// A price identification type PriceId: Parameter + MaxEncodedLen; - /// A pool identification - type PoolId: Parameter + MaxEncodedLen; + /// A collection identification + type CollectionId: Parameter + MaxEncodedLen; /// Represents a price - type Price: Parameter + MaxEncodedLen; + type Price: Parameter + MaxEncodedLen + Ord; /// Represents a timestamp type Moment: Parameter + MaxEncodedLen; + /// Data provider for initializing price values + type DataProvider: DataProviderExtended< + Self::PriceId, + TimestampedValue, + >; + /// Max size of a price collection #[pallet::constant] type MaxCollectionSize: Get; } #[pallet::storage] - pub(crate) type ListeningPriceId = - StorageDoubleMap<_, Blake2_128Concat, T::PriceId, Blake2_128Concat, T::PoolId, ()>; + pub(crate) type Listening = + StorageDoubleMap<_, Blake2_128Concat, T::PriceId, Blake2_128Concat, T::CollectionId, ()>; #[pallet::storage] pub(crate) type PoolPrices = StorageMap< _, Blake2_128Concat, - T::PoolId, - BoundedVec<(T::Price, T::Moment), T::MaxCollectionSize>, + T::CollectionId, + BoundedVec, T::MaxCollectionSize>, + ValueQuery, >; #[pallet::event] @@ -51,49 +67,92 @@ pub mod pallet { pub enum Event {} #[pallet::error] - pub enum Error {} + pub enum Error { + /// The collection was not found + CollectionNotFound, + + /// The used price ID is not listened + PriceIdNotRegistered, + + /// Collection size exceeded + MaxCollectionSize, + } impl PriceRegistry for Pallet { type Cache = PriceCacheVec; - type CollectionId = T::PoolId; + type CollectionId = T::CollectionId; type Moment = T::Moment; type Price = T::Price; type PriceId = T::PriceId; - fn price(price_id: Self::PriceId) -> Result<(Self::Price, Self::Moment), DispatchError> { - todo!() + fn price(price_id: T::PriceId) -> Result, DispatchError> { + Ok(T::DataProvider::get_no_op(&price_id) + .map(|timestamped| (timestamped.value, timestamped.timestamp))) } - fn cache(collection_id: Self::CollectionId) -> Result { - todo!() + fn cache(collection_id: T::CollectionId) -> Result { + let collection = PoolPrices::::get(collection_id); + + if collection.is_empty() { + return Err(Error::::CollectionNotFound.into()); + } + + Ok(PriceCacheVec(collection)) } fn register_price_id( - price_id: Self::PriceId, - collection_id: Self::CollectionId, + price_id: T::PriceId, + collection_id: T::CollectionId, ) -> DispatchResult { - todo!() + Listening::::insert(price_id, collection_id, ()); + PoolPrices::::try_mutate(collection_id, |collection| -> Result<_, DispatchError> { + if let None = collection.iter().find(|info| info.price_id == price_id) { + collection + .try_push(PriceInfo { + price_id, + value: Self::price(price_id)?, + }) + .map_err(|_| Error::::MaxCollectionSize)?; + } + + Ok(()) + }) } fn unregister_price_id( - price_id: Self::PriceId, - collection_id: Self::CollectionId, + price_id: T::PriceId, + collection_id: T::CollectionId, ) -> DispatchResult { - todo!() + Listening::::remove(price_id, collection_id); + PoolPrices::::try_mutate(collection_id, |collection| -> Result<_, DispatchError> { + collection + .iter() + .position(|info| info.price_id == price_id) + .map(|index| collection.swap_remove(index)); + + Ok(()) + }) } } impl OnNewData for Pallet { - fn on_new_data(who: &T::AccountId, price_id: &T::PriceId, price: &T::Price) { - todo!() + fn on_new_data(_: &T::AccountId, price_id: &T::PriceId, price: &T::Price) { + //todo } } - pub struct PriceCacheVec(BoundedVec<(T::Price, T::Moment), T::MaxCollectionSize>); + pub struct PriceCacheVec(BoundedVec, T::MaxCollectionSize>); impl PriceCache for PriceCacheVec { - fn price(&self, price_id: T::PriceId) -> Result<(T::Price, T::Moment), DispatchError> { - todo!() + fn price( + &self, + price_id: T::PriceId, + ) -> Result, DispatchError> { + self.0 + .iter() + .find(|info| info.price_id == price_id) + .map(|info| info.value.clone()) + .ok_or(Error::::PriceIdNotRegistered.into()) } } } From 1702c37cc4983695c5760839a49b6e572d443b05 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 18 Apr 2023 09:23:44 +0200 Subject: [PATCH 05/16] finish implementation --- libs/traits/src/prices.rs | 15 ++-- pallets/pool-price-feed/src/lib.rs | 129 +++++++++++++++++------------ 2 files changed, 83 insertions(+), 61 deletions(-) diff --git a/libs/traits/src/prices.rs b/libs/traits/src/prices.rs index fcad5ed111..1a4661212a 100644 --- a/libs/traits/src/prices.rs +++ b/libs/traits/src/prices.rs @@ -19,27 +19,26 @@ pub trait PriceRegistry { type Moment; /// Return the last price value for a price id along with the moment it was updated last time - fn price(price_id: Self::PriceId) - -> Result, DispatchError>; + fn price(price_id: &Self::PriceId) -> Option<(Self::Price, Self::Moment)>; /// Retrives a collection of prices with all prices associated to a collection id - fn cache(collection_id: Self::CollectionId) -> Result; + fn cache(collection_id: &Self::CollectionId) -> Self::Cache; /// Start listening price changes for a price id in a collection id fn register_price_id( - price_id: Self::PriceId, - collection_id: Self::CollectionId, + price_id: &Self::PriceId, + collection_id: &Self::CollectionId, ) -> DispatchResult; /// Start listening price changes for a price id in a collection id fn unregister_price_id( - price_id: Self::PriceId, - collection_id: Self::CollectionId, + price_id: &Self::PriceId, + collection_id: &Self::CollectionId, ) -> DispatchResult; } /// Abstration to represent a cached collection of prices in memory pub trait PriceCache { /// Return the last price value for a price id along with the moment it was updated last time - fn price(&self, price_id: PriceId) -> Result, DispatchError>; + fn price(&self, price_id: &PriceId) -> Result, DispatchError>; } diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs index ef58091f32..ce439f0b74 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/pool-price-feed/src/lib.rs @@ -3,9 +3,12 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use cfg_traits::prices::{PriceCache, PriceRegistry}; - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, storage::bounded_btree_set::BoundedBTreeSet}; use orml_traits::{DataProviderExtended, OnNewData, TimestampedValue}; - use sp_runtime::DispatchError; + use sp_runtime::{ + traits::{EnsureAddAssign, EnsureSubAssign}, + DispatchError, + }; /// Type that contains price information #[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)] @@ -13,6 +16,7 @@ pub mod pallet { pub struct PriceInfo { price_id: T::PriceId, value: Option<(T::Price, T::Moment)>, + count: u32, } const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -24,19 +28,17 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// A price identification type PriceId: Parameter + MaxEncodedLen; /// A collection identification - type CollectionId: Parameter + MaxEncodedLen; + type CollectionId: Parameter + MaxEncodedLen + Ord; /// Represents a price - type Price: Parameter + MaxEncodedLen + Ord; + type Price: Parameter + MaxEncodedLen + Ord + Copy; /// Represents a timestamp - type Moment: Parameter + MaxEncodedLen; + type Moment: Parameter + MaxEncodedLen + Copy; /// Data provider for initializing price values type DataProvider: DataProviderExtended< @@ -47,12 +49,23 @@ pub mod pallet { /// Max size of a price collection #[pallet::constant] type MaxCollectionSize: Get; + + /// Max number of collections + #[pallet::constant] + type MaxCollections: Get; } + /// Storage that holds the collection ids where a price id is registered #[pallet::storage] - pub(crate) type Listening = - StorageDoubleMap<_, Blake2_128Concat, T::PriceId, Blake2_128Concat, T::CollectionId, ()>; + pub(crate) type Listening = StorageMap< + _, + Blake2_128Concat, + T::PriceId, + BoundedBTreeSet, + ValueQuery, + >; + /// Type that contains the price information associated to a collection. #[pallet::storage] pub(crate) type PoolPrices = StorageMap< _, @@ -62,20 +75,16 @@ pub mod pallet { ValueQuery, >; - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event {} - #[pallet::error] pub enum Error { - /// The collection was not found - CollectionNotFound, + /// The used price ID is not in the collection. + PriceIdNotInCollection, - /// The used price ID is not listened - PriceIdNotRegistered, - - /// Collection size exceeded + /// Max collection size exceeded MaxCollectionSize, + + /// Max collection number exceeded + MaxCollectionNumber, } impl PriceRegistry for Pallet { @@ -85,50 +94,56 @@ pub mod pallet { type Price = T::Price; type PriceId = T::PriceId; - fn price(price_id: T::PriceId) -> Result, DispatchError> { - Ok(T::DataProvider::get_no_op(&price_id) - .map(|timestamped| (timestamped.value, timestamped.timestamp))) + fn price(price_id: &T::PriceId) -> Option<(T::Price, T::Moment)> { + T::DataProvider::get_no_op(&price_id) + .map(|timestamped| (timestamped.value, timestamped.timestamp)) } - fn cache(collection_id: T::CollectionId) -> Result { - let collection = PoolPrices::::get(collection_id); - - if collection.is_empty() { - return Err(Error::::CollectionNotFound.into()); - } - - Ok(PriceCacheVec(collection)) + fn cache(collection_id: &T::CollectionId) -> Self::Cache { + PriceCacheVec(PoolPrices::::get(collection_id)) } fn register_price_id( - price_id: T::PriceId, - collection_id: T::CollectionId, + price_id: &T::PriceId, + collection_id: &T::CollectionId, ) -> DispatchResult { - Listening::::insert(price_id, collection_id, ()); + Listening::::try_mutate(price_id, |ids| { + ids.try_insert(collection_id.clone()) + .map_err(|_| Error::::MaxCollectionSize) + })?; PoolPrices::::try_mutate(collection_id, |collection| -> Result<_, DispatchError> { - if let None = collection.iter().find(|info| info.price_id == price_id) { - collection + match collection + .iter_mut() + .find(|info| &info.price_id == price_id) + { + Some(info) => info.count.ensure_add_assign(1).map_err(|e| e.into()), + None => collection .try_push(PriceInfo { - price_id, - value: Self::price(price_id)?, + price_id: price_id.clone(), + value: Self::price(price_id), + count: 1, }) - .map_err(|_| Error::::MaxCollectionSize)?; + .map_err(|_| Error::::MaxCollectionSize.into()), } - - Ok(()) }) } fn unregister_price_id( - price_id: T::PriceId, - collection_id: T::CollectionId, + price_id: &T::PriceId, + collection_id: &T::CollectionId, ) -> DispatchResult { - Listening::::remove(price_id, collection_id); - PoolPrices::::try_mutate(collection_id, |collection| -> Result<_, DispatchError> { - collection - .iter() - .position(|info| info.price_id == price_id) - .map(|index| collection.swap_remove(index)); + PoolPrices::::mutate(collection_id, |collection| -> Result<_, DispatchError> { + let (index, info) = collection + .iter_mut() + .enumerate() + .find(|(_, info)| &info.price_id == price_id) + .ok_or(Error::::PriceIdNotInCollection)?; + + info.count.ensure_sub_assign(1)?; + if info.count == 0 { + collection.swap_remove(index); + Listening::::mutate(price_id, |ids| ids.remove(collection_id)); + } Ok(()) }) @@ -136,23 +151,31 @@ pub mod pallet { } impl OnNewData for Pallet { - fn on_new_data(_: &T::AccountId, price_id: &T::PriceId, price: &T::Price) { - //todo + fn on_new_data(_: &T::AccountId, price_id: &T::PriceId, _: &T::Price) { + for collection_id in Listening::::get(price_id) { + PoolPrices::::mutate(collection_id, |collection| { + collection + .iter_mut() + .find(|info| &info.price_id == price_id) + .map(|info| info.value = Self::price(price_id)) + }); + } } } + /// A collection cached in memory pub struct PriceCacheVec(BoundedVec, T::MaxCollectionSize>); impl PriceCache for PriceCacheVec { fn price( &self, - price_id: T::PriceId, + price_id: &T::PriceId, ) -> Result, DispatchError> { self.0 .iter() - .find(|info| info.price_id == price_id) + .find(|info| &info.price_id == price_id) .map(|info| info.value.clone()) - .ok_or(Error::::PriceIdNotRegistered.into()) + .ok_or(Error::::PriceIdNotInCollection.into()) } } } From c0c6026a50f888c4500359530c358d4de6db8702 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 18 Apr 2023 09:43:36 +0200 Subject: [PATCH 06/16] using a BTreeMap --- pallets/pool-price-feed/src/lib.rs | 57 ++++++++++++++++-------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs index ce439f0b74..9c1bbdc7ff 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/pool-price-feed/src/lib.rs @@ -3,19 +3,24 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use cfg_traits::prices::{PriceCache, PriceRegistry}; - use frame_support::{pallet_prelude::*, storage::bounded_btree_set::BoundedBTreeSet}; + use frame_support::{ + pallet_prelude::*, + storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet}, + }; use orml_traits::{DataProviderExtended, OnNewData, TimestampedValue}; use sp_runtime::{ traits::{EnsureAddAssign, EnsureSubAssign}, DispatchError, }; - /// Type that contains price information + /// Type that contains price information associated to a collection #[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)] #[scale_info(skip_type_params(T))] pub struct PriceInfo { - price_id: T::PriceId, + /// If it has been feeded with a value, it contains the price and the moment it was updated value: Option<(T::Price, T::Moment)>, + + /// Counts how many times this price has been registered for the collection it belongs count: u32, } @@ -29,7 +34,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { /// A price identification - type PriceId: Parameter + MaxEncodedLen; + type PriceId: Parameter + MaxEncodedLen + Ord; /// A collection identification type CollectionId: Parameter + MaxEncodedLen + Ord; @@ -71,7 +76,7 @@ pub mod pallet { _, Blake2_128Concat, T::CollectionId, - BoundedVec, T::MaxCollectionSize>, + BoundedBTreeMap, T::MaxCollectionSize>, ValueQuery, >; @@ -88,7 +93,7 @@ pub mod pallet { } impl PriceRegistry for Pallet { - type Cache = PriceCacheVec; + type Cache = CachedCollection; type CollectionId = T::CollectionId; type Moment = T::Moment; type Price = T::Price; @@ -100,7 +105,7 @@ pub mod pallet { } fn cache(collection_id: &T::CollectionId) -> Self::Cache { - PriceCacheVec(PoolPrices::::get(collection_id)) + CachedCollection(PoolPrices::::get(collection_id)) } fn register_price_id( @@ -112,17 +117,17 @@ pub mod pallet { .map_err(|_| Error::::MaxCollectionSize) })?; PoolPrices::::try_mutate(collection_id, |collection| -> Result<_, DispatchError> { - match collection - .iter_mut() - .find(|info| &info.price_id == price_id) - { + match collection.get_mut(price_id) { Some(info) => info.count.ensure_add_assign(1).map_err(|e| e.into()), None => collection - .try_push(PriceInfo { - price_id: price_id.clone(), - value: Self::price(price_id), - count: 1, - }) + .try_insert( + price_id.clone(), + PriceInfo { + value: Self::price(price_id), + count: 1, + }, + ) + .map(|_| ()) .map_err(|_| Error::::MaxCollectionSize.into()), } }) @@ -133,15 +138,13 @@ pub mod pallet { collection_id: &T::CollectionId, ) -> DispatchResult { PoolPrices::::mutate(collection_id, |collection| -> Result<_, DispatchError> { - let (index, info) = collection - .iter_mut() - .enumerate() - .find(|(_, info)| &info.price_id == price_id) + let info = collection + .get_mut(price_id) .ok_or(Error::::PriceIdNotInCollection)?; info.count.ensure_sub_assign(1)?; if info.count == 0 { - collection.swap_remove(index); + collection.remove(price_id); Listening::::mutate(price_id, |ids| ids.remove(collection_id)); } @@ -155,8 +158,7 @@ pub mod pallet { for collection_id in Listening::::get(price_id) { PoolPrices::::mutate(collection_id, |collection| { collection - .iter_mut() - .find(|info| &info.price_id == price_id) + .get_mut(price_id) .map(|info| info.value = Self::price(price_id)) }); } @@ -164,16 +166,17 @@ pub mod pallet { } /// A collection cached in memory - pub struct PriceCacheVec(BoundedVec, T::MaxCollectionSize>); + pub struct CachedCollection( + BoundedBTreeMap, T::MaxCollectionSize>, + ); - impl PriceCache for PriceCacheVec { + impl PriceCache for CachedCollection { fn price( &self, price_id: &T::PriceId, ) -> Result, DispatchError> { self.0 - .iter() - .find(|info| &info.price_id == price_id) + .get(price_id) .map(|info| info.value.clone()) .ok_or(Error::::PriceIdNotInCollection.into()) } From f8c5249c5e63a90d8a9ecafc4e0b6307a31a9ff2 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 18 Apr 2023 10:19:37 +0200 Subject: [PATCH 07/16] counter to Listening storage --- libs/mocks/src/prices.rs | 31 +++++++---- pallets/pool-price-feed/src/lib.rs | 85 ++++++++++++------------------ 2 files changed, 55 insertions(+), 61 deletions(-) diff --git a/libs/mocks/src/prices.rs b/libs/mocks/src/prices.rs index 99f92e6a5f..f82d602615 100644 --- a/libs/mocks/src/prices.rs +++ b/libs/mocks/src/prices.rs @@ -26,24 +26,24 @@ pub mod pallet { >; impl Pallet { - pub fn mock_price(f: impl Fn(T::PriceId) -> DispatchResult + 'static) { + pub fn mock_price(f: impl Fn(&T::PriceId) -> DispatchResult + 'static) { register_call!(f); } pub fn mock_cache( - f: impl Fn(T::CollectionId) -> Result + 'static, + f: impl Fn(&T::CollectionId) -> Result + 'static, ) { register_call!(f); } pub fn mock_register_price_id( - f: impl Fn(T::PriceId, T::CollectionId) -> DispatchResult + 'static, + f: impl Fn(&T::PriceId, &T::CollectionId) -> DispatchResult + 'static, ) { register_call!(move |(a, b)| f(a, b)); } pub fn mock_unregister_price_id( - f: impl Fn(T::PriceId, T::CollectionId) -> DispatchResult + 'static, + f: impl Fn(&T::PriceId, &T::CollectionId) -> DispatchResult + 'static, ) { register_call!(move |(a, b)| f(a, b)); } @@ -56,19 +56,25 @@ pub mod pallet { type Price = T::Price; type PriceId = T::PriceId; - fn price(a: T::PriceId) -> Result<(T::Price, T::Moment), DispatchError> { + fn price(a: &T::PriceId) -> Option<(T::Price, T::Moment)> { + let a = unsafe { std::mem::transmute::<_, &'static T::PriceId>(a) }; execute_call!(a) } - fn cache(a: T::CollectionId) -> Result { + fn cache(a: &T::CollectionId) -> T::Cache { + let a = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(a) }; execute_call!(a) } - fn register_price_id(a: T::PriceId, b: T::CollectionId) -> DispatchResult { + fn register_price_id(a: &T::PriceId, b: &T::CollectionId) -> DispatchResult { + let a = unsafe { std::mem::transmute::<_, &'static T::PriceId>(a) }; + let b = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(b) }; execute_call!((a, b)) } - fn unregister_price_id(a: T::PriceId, b: T::CollectionId) -> DispatchResult { + fn unregister_price_id(a: &T::PriceId, b: &T::CollectionId) -> DispatchResult { + let a = unsafe { std::mem::transmute::<_, &'static T::PriceId>(a) }; + let b = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(b) }; execute_call!((a, b)) } } @@ -79,7 +85,9 @@ pub mod pallet { use super::*; - pub struct MockPriceCache(pub HashMap); + pub struct MockPriceCache( + pub HashMap>, + ); impl PriceCache for MockPriceCache where @@ -87,7 +95,10 @@ pub mod pallet { T::Price: Clone, T::Moment: Clone, { - fn price(&self, price_id: T::PriceId) -> Result<(T::Price, T::Moment), DispatchError> { + fn price( + &self, + price_id: &T::PriceId, + ) -> Result, DispatchError> { Ok(self .0 .get(&price_id) diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs index 9c1bbdc7ff..3cca1e1ceb 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/pool-price-feed/src/lib.rs @@ -3,26 +3,14 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use cfg_traits::prices::{PriceCache, PriceRegistry}; - use frame_support::{ - pallet_prelude::*, - storage::{bounded_btree_map::BoundedBTreeMap, bounded_btree_set::BoundedBTreeSet}, - }; + use frame_support::{pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap}; use orml_traits::{DataProviderExtended, OnNewData, TimestampedValue}; use sp_runtime::{ traits::{EnsureAddAssign, EnsureSubAssign}, DispatchError, }; - /// Type that contains price information associated to a collection - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound)] - #[scale_info(skip_type_params(T))] - pub struct PriceInfo { - /// If it has been feeded with a value, it contains the price and the moment it was updated - value: Option<(T::Price, T::Moment)>, - - /// Counts how many times this price has been registered for the collection it belongs - count: u32, - } + type PriceValueOf = Option<(::Price, ::Moment)>; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -60,23 +48,23 @@ pub mod pallet { type MaxCollections: Get; } - /// Storage that holds the collection ids where a price id is registered + /// Storage that contains the registering information #[pallet::storage] pub(crate) type Listening = StorageMap< _, Blake2_128Concat, T::PriceId, - BoundedBTreeSet, + BoundedBTreeMap, ValueQuery, >; - /// Type that contains the price information associated to a collection. + /// Storage that contains the price values of a collection. #[pallet::storage] pub(crate) type PoolPrices = StorageMap< _, Blake2_128Concat, T::CollectionId, - BoundedBTreeMap, T::MaxCollectionSize>, + BoundedBTreeMap, T::MaxCollectionSize>, ValueQuery, >; @@ -99,7 +87,7 @@ pub mod pallet { type Price = T::Price; type PriceId = T::PriceId; - fn price(price_id: &T::PriceId) -> Option<(T::Price, T::Moment)> { + fn price(price_id: &T::PriceId) -> PriceValueOf { T::DataProvider::get_no_op(&price_id) .map(|timestamped| (timestamped.value, timestamped.timestamp)) } @@ -112,23 +100,19 @@ pub mod pallet { price_id: &T::PriceId, collection_id: &T::CollectionId, ) -> DispatchResult { - Listening::::try_mutate(price_id, |ids| { - ids.try_insert(collection_id.clone()) - .map_err(|_| Error::::MaxCollectionSize) - })?; - PoolPrices::::try_mutate(collection_id, |collection| -> Result<_, DispatchError> { - match collection.get_mut(price_id) { - Some(info) => info.count.ensure_add_assign(1).map_err(|e| e.into()), - None => collection - .try_insert( - price_id.clone(), - PriceInfo { - value: Self::price(price_id), - count: 1, - }, - ) - .map(|_| ()) - .map_err(|_| Error::::MaxCollectionSize.into()), + Listening::::try_mutate(price_id, |counters| match counters.get_mut(collection_id) { + Some(counter) => counter.ensure_add_assign(1).map_err(|e| e.into()), + None => { + counters + .try_insert(collection_id.clone(), 0) + .map_err(|_| Error::::MaxCollectionNumber)?; + + PoolPrices::::try_mutate(collection_id, |collection| { + collection + .try_insert(price_id.clone(), Self::price(price_id)) + .map(|_| ()) + .map_err(|_| Error::::MaxCollectionSize.into()) + }) } }) } @@ -137,15 +121,17 @@ pub mod pallet { price_id: &T::PriceId, collection_id: &T::CollectionId, ) -> DispatchResult { - PoolPrices::::mutate(collection_id, |collection| -> Result<_, DispatchError> { - let info = collection - .get_mut(price_id) + Listening::::mutate(price_id, |counters| { + let counter = counters + .get_mut(collection_id) .ok_or(Error::::PriceIdNotInCollection)?; - info.count.ensure_sub_assign(1)?; - if info.count == 0 { - collection.remove(price_id); - Listening::::mutate(price_id, |ids| ids.remove(collection_id)); + counter.ensure_sub_assign(1)?; + if *counter == 0 { + counters.remove(collection_id); + PoolPrices::::mutate(collection_id, |collection| { + collection.remove(price_id) + }); } Ok(()) @@ -155,11 +141,11 @@ pub mod pallet { impl OnNewData for Pallet { fn on_new_data(_: &T::AccountId, price_id: &T::PriceId, _: &T::Price) { - for collection_id in Listening::::get(price_id) { + for collection_id in Listening::::get(price_id).keys() { PoolPrices::::mutate(collection_id, |collection| { collection .get_mut(price_id) - .map(|info| info.value = Self::price(price_id)) + .map(|value| *value = Self::price(price_id)) }); } } @@ -167,17 +153,14 @@ pub mod pallet { /// A collection cached in memory pub struct CachedCollection( - BoundedBTreeMap, T::MaxCollectionSize>, + BoundedBTreeMap, T::MaxCollectionSize>, ); impl PriceCache for CachedCollection { - fn price( - &self, - price_id: &T::PriceId, - ) -> Result, DispatchError> { + fn price(&self, price_id: &T::PriceId) -> Result, DispatchError> { self.0 .get(price_id) - .map(|info| info.value.clone()) + .map(|value| value.clone()) .ok_or(Error::::PriceIdNotInCollection.into()) } } From 81391c4fd6272087a1e34b8b827a9aebbe9f94c2 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 18 Apr 2023 10:27:51 +0200 Subject: [PATCH 08/16] clippy --- pallets/pool-price-feed/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs index 3cca1e1ceb..002e432941 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/pool-price-feed/src/lib.rs @@ -28,10 +28,10 @@ pub mod pallet { type CollectionId: Parameter + MaxEncodedLen + Ord; /// Represents a price - type Price: Parameter + MaxEncodedLen + Ord + Copy; + type Price: Parameter + MaxEncodedLen + Ord; /// Represents a timestamp - type Moment: Parameter + MaxEncodedLen + Copy; + type Moment: Parameter + MaxEncodedLen; /// Data provider for initializing price values type DataProvider: DataProviderExtended< @@ -88,7 +88,7 @@ pub mod pallet { type PriceId = T::PriceId; fn price(price_id: &T::PriceId) -> PriceValueOf { - T::DataProvider::get_no_op(&price_id) + T::DataProvider::get_no_op(price_id) .map(|timestamped| (timestamped.value, timestamped.timestamp)) } @@ -160,8 +160,8 @@ pub mod pallet { fn price(&self, price_id: &T::PriceId) -> Result, DispatchError> { self.0 .get(price_id) - .map(|value| value.clone()) - .ok_or(Error::::PriceIdNotInCollection.into()) + .cloned() + .ok_or_else(|| Error::::PriceIdNotInCollection.into()) } } } From 448a506825d6624c5e94d78e2a219b341433bcb4 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 18 Apr 2023 11:06:12 +0200 Subject: [PATCH 09/16] rename cache to collection --- libs/mocks/src/prices.rs | 18 ++++++++---------- libs/traits/src/prices.rs | 8 ++++---- pallets/pool-price-feed/src/lib.rs | 8 ++++---- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/libs/mocks/src/prices.rs b/libs/mocks/src/prices.rs index f82d602615..d958d1a7c8 100644 --- a/libs/mocks/src/prices.rs +++ b/libs/mocks/src/prices.rs @@ -1,6 +1,6 @@ #[frame_support::pallet] pub mod pallet { - use cfg_traits::prices::{PriceCache, PriceRegistry}; + use cfg_traits::prices::{PriceCollection, PriceRegistry}; use frame_support::pallet_prelude::*; use mock_builder::{execute_call, register_call}; @@ -8,7 +8,7 @@ pub mod pallet { pub trait Config: frame_system::Config { type PriceId; type CollectionId; - type Cache: PriceCache; + type Collection: PriceCollection; type Price; type Moment; } @@ -26,13 +26,11 @@ pub mod pallet { >; impl Pallet { - pub fn mock_price(f: impl Fn(&T::PriceId) -> DispatchResult + 'static) { + pub fn mock_price(f: impl Fn(&T::PriceId) -> Option<(T::Price, T::Moment)> + 'static) { register_call!(f); } - pub fn mock_cache( - f: impl Fn(&T::CollectionId) -> Result + 'static, - ) { + pub fn mock_cache(f: impl Fn(&T::CollectionId) -> T::Collection + 'static) { register_call!(f); } @@ -50,7 +48,7 @@ pub mod pallet { } impl PriceRegistry for Pallet { - type Cache = T::Cache; + type Collection = T::Collection; type CollectionId = T::CollectionId; type Moment = T::Moment; type Price = T::Price; @@ -61,7 +59,7 @@ pub mod pallet { execute_call!(a) } - fn cache(a: &T::CollectionId) -> T::Cache { + fn collection(a: &T::CollectionId) -> T::Collection { let a = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(a) }; execute_call!(a) } @@ -85,11 +83,11 @@ pub mod pallet { use super::*; - pub struct MockPriceCache( + pub struct MockPriceCollection( pub HashMap>, ); - impl PriceCache for MockPriceCache + impl PriceCollection for MockPriceCollection where T::PriceId: std::hash::Hash + Eq, T::Price: Clone, diff --git a/libs/traits/src/prices.rs b/libs/traits/src/prices.rs index 1a4661212a..c00c80e9a4 100644 --- a/libs/traits/src/prices.rs +++ b/libs/traits/src/prices.rs @@ -10,7 +10,7 @@ pub trait PriceRegistry { type CollectionId; /// A collection of prices - type Cache: PriceCache; + type Collection: PriceCollection; /// Represents a price type Price; @@ -22,7 +22,7 @@ pub trait PriceRegistry { fn price(price_id: &Self::PriceId) -> Option<(Self::Price, Self::Moment)>; /// Retrives a collection of prices with all prices associated to a collection id - fn cache(collection_id: &Self::CollectionId) -> Self::Cache; + fn collection(collection_id: &Self::CollectionId) -> Self::Collection; /// Start listening price changes for a price id in a collection id fn register_price_id( @@ -37,8 +37,8 @@ pub trait PriceRegistry { ) -> DispatchResult; } -/// Abstration to represent a cached collection of prices in memory -pub trait PriceCache { +/// Abstration to represent a collection of prices in memory +pub trait PriceCollection { /// Return the last price value for a price id along with the moment it was updated last time fn price(&self, price_id: &PriceId) -> Result, DispatchError>; } diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/pool-price-feed/src/lib.rs index 002e432941..bc70fc0524 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/pool-price-feed/src/lib.rs @@ -2,7 +2,7 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { - use cfg_traits::prices::{PriceCache, PriceRegistry}; + use cfg_traits::prices::{PriceCollection, PriceRegistry}; use frame_support::{pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap}; use orml_traits::{DataProviderExtended, OnNewData, TimestampedValue}; use sp_runtime::{ @@ -81,7 +81,7 @@ pub mod pallet { } impl PriceRegistry for Pallet { - type Cache = CachedCollection; + type Collection = CachedCollection; type CollectionId = T::CollectionId; type Moment = T::Moment; type Price = T::Price; @@ -92,7 +92,7 @@ pub mod pallet { .map(|timestamped| (timestamped.value, timestamped.timestamp)) } - fn cache(collection_id: &T::CollectionId) -> Self::Cache { + fn collection(collection_id: &T::CollectionId) -> Self::Collection { CachedCollection(PoolPrices::::get(collection_id)) } @@ -156,7 +156,7 @@ pub mod pallet { BoundedBTreeMap, T::MaxCollectionSize>, ); - impl PriceCache for CachedCollection { + impl PriceCollection for CachedCollection { fn price(&self, price_id: &T::PriceId) -> Result, DispatchError> { self.0 .get(price_id) From 6e21fe830be391a639da9b0f89cac775b66846e8 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 18 Apr 2023 15:19:52 +0200 Subject: [PATCH 10/16] rename pallet and price by data --- Cargo.lock | 36 ++++---- Cargo.toml | 2 +- libs/mocks/src/{prices.rs => data.rs} | 54 ++++++------ libs/mocks/src/lib.rs | 4 +- libs/traits/src/data.rs | 44 ++++++++++ libs/traits/src/lib.rs | 2 +- libs/traits/src/prices.rs | 44 ---------- .../Cargo.toml | 4 +- .../src/lib.rs | 88 +++++++++---------- 9 files changed, 138 insertions(+), 140 deletions(-) rename libs/mocks/src/{prices.rs => data.rs} (52%) create mode 100644 libs/traits/src/data.rs delete mode 100644 libs/traits/src/prices.rs rename pallets/{pool-price-feed => collection-data-feed}/Cargo.toml (95%) rename pallets/{pool-price-feed => collection-data-feed}/src/lib.rs (53%) diff --git a/Cargo.lock b/Cargo.lock index 55a95f2be9..7dd5d78755 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6850,6 +6850,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-collection-data-feed" +version = "1.0.0" +dependencies = [ + "cfg-mocks", + "cfg-traits", + "frame-support", + "frame-system", + "orml-traits", + "parity-scale-codec 3.4.0", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-collective" version = "4.0.0-dev" @@ -7583,24 +7601,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-pool-price-feed" -version = "1.0.0" -dependencies = [ - "cfg-mocks", - "cfg-traits", - "frame-support", - "frame-system", - "orml-traits", - "parity-scale-codec 3.4.0", - "scale-info", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-pool-registry" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index cc373b866f..fe47ffb156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ members = [ "pallets/permissions", "pallets/pool-system", "pallets/pool-registry", - "pallets/pool-price-feed", + "pallets/collection-data-feed", "pallets/restricted-tokens", "pallets/transfer-allowlist", "pallets/rewards", diff --git a/libs/mocks/src/prices.rs b/libs/mocks/src/data.rs similarity index 52% rename from libs/mocks/src/prices.rs rename to libs/mocks/src/data.rs index d958d1a7c8..abb7bbfa7f 100644 --- a/libs/mocks/src/prices.rs +++ b/libs/mocks/src/data.rs @@ -1,15 +1,15 @@ #[frame_support::pallet] pub mod pallet { - use cfg_traits::prices::{PriceCollection, PriceRegistry}; + use cfg_traits::data::{DataCollection, DataRegistry}; use frame_support::pallet_prelude::*; use mock_builder::{execute_call, register_call}; #[pallet::config] pub trait Config: frame_system::Config { - type PriceId; + type DataId; type CollectionId; - type Collection: PriceCollection; - type Price; + type Collection: DataCollection; + type Data; type Moment; } @@ -26,7 +26,7 @@ pub mod pallet { >; impl Pallet { - pub fn mock_price(f: impl Fn(&T::PriceId) -> Option<(T::Price, T::Moment)> + 'static) { + pub fn mock_get(f: impl Fn(&T::DataId) -> Option<(T::Data, T::Moment)> + 'static) { register_call!(f); } @@ -34,28 +34,28 @@ pub mod pallet { register_call!(f); } - pub fn mock_register_price_id( - f: impl Fn(&T::PriceId, &T::CollectionId) -> DispatchResult + 'static, + pub fn mock_register_data_id( + f: impl Fn(&T::DataId, &T::CollectionId) -> DispatchResult + 'static, ) { register_call!(move |(a, b)| f(a, b)); } - pub fn mock_unregister_price_id( - f: impl Fn(&T::PriceId, &T::CollectionId) -> DispatchResult + 'static, + pub fn mock_unregister_data_id( + f: impl Fn(&T::DataId, &T::CollectionId) -> DispatchResult + 'static, ) { register_call!(move |(a, b)| f(a, b)); } } - impl PriceRegistry for Pallet { + impl DataRegistry for Pallet { type Collection = T::Collection; type CollectionId = T::CollectionId; + type Data = T::Data; + type DataId = T::DataId; type Moment = T::Moment; - type Price = T::Price; - type PriceId = T::PriceId; - fn price(a: &T::PriceId) -> Option<(T::Price, T::Moment)> { - let a = unsafe { std::mem::transmute::<_, &'static T::PriceId>(a) }; + fn get(a: &T::DataId) -> Option<(T::Data, T::Moment)> { + let a = unsafe { std::mem::transmute::<_, &'static T::DataId>(a) }; execute_call!(a) } @@ -64,14 +64,14 @@ pub mod pallet { execute_call!(a) } - fn register_price_id(a: &T::PriceId, b: &T::CollectionId) -> DispatchResult { - let a = unsafe { std::mem::transmute::<_, &'static T::PriceId>(a) }; + fn register_data_id(a: &T::DataId, b: &T::CollectionId) -> DispatchResult { + let a = unsafe { std::mem::transmute::<_, &'static T::DataId>(a) }; let b = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(b) }; execute_call!((a, b)) } - fn unregister_price_id(a: &T::PriceId, b: &T::CollectionId) -> DispatchResult { - let a = unsafe { std::mem::transmute::<_, &'static T::PriceId>(a) }; + fn unregister_data_id(a: &T::DataId, b: &T::CollectionId) -> DispatchResult { + let a = unsafe { std::mem::transmute::<_, &'static T::DataId>(a) }; let b = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(b) }; execute_call!((a, b)) } @@ -83,23 +83,23 @@ pub mod pallet { use super::*; - pub struct MockPriceCollection( - pub HashMap>, + pub struct MockDataCollection( + pub HashMap>, ); - impl PriceCollection for MockPriceCollection + impl DataCollection for MockDataCollection where - T::PriceId: std::hash::Hash + Eq, - T::Price: Clone, + T::DataId: std::hash::Hash + Eq, + T::Data: Clone, T::Moment: Clone, { - fn price( + fn data( &self, - price_id: &T::PriceId, - ) -> Result, DispatchError> { + data_id: &T::DataId, + ) -> Result, DispatchError> { Ok(self .0 - .get(&price_id) + .get(&data_id) .ok_or(DispatchError::CannotLookup)? .clone()) } diff --git a/libs/mocks/src/lib.rs b/libs/mocks/src/lib.rs index b649ae2f70..a7d6942c6c 100644 --- a/libs/mocks/src/lib.rs +++ b/libs/mocks/src/lib.rs @@ -1,13 +1,13 @@ +mod data; mod fees; mod permissions; mod pools; -mod prices; mod rewards; +pub use data::pallet as pallet_mock_data; pub use fees::pallet as pallet_mock_fees; pub use permissions::pallet as pallet_mock_permissions; pub use pools::pallet as pallet_mock_pools; -pub use prices::pallet as pallet_mock_prices; pub use rewards::pallet as pallet_mock_rewards; #[cfg(test)] diff --git a/libs/traits/src/data.rs b/libs/traits/src/data.rs new file mode 100644 index 0000000000..c58b538599 --- /dev/null +++ b/libs/traits/src/data.rs @@ -0,0 +1,44 @@ +use sp_runtime::{DispatchError, DispatchResult}; + +/// Abstraction that represents a storage where +/// you can subscribe to data updates and collect them +pub trait DataRegistry { + /// A data identification + type DataId; + + /// A collection identification + type CollectionId; + + /// A collection of datas + type Collection: DataCollection; + + /// Represents a data + type Data; + + /// Represents a timestamp + type Moment; + + /// Return the last data value for a data id along with the moment it was updated last time + fn get(data_id: &Self::DataId) -> Option<(Self::Data, Self::Moment)>; + + /// Retrives a collection of datas with all datas associated to a collection id + fn collection(collection_id: &Self::CollectionId) -> Self::Collection; + + /// Start listening data changes for a data id in a collection id + fn register_data_id( + data_id: &Self::DataId, + collection_id: &Self::CollectionId, + ) -> DispatchResult; + + /// Start listening data changes for a data id in a collection id + fn unregister_data_id( + data_id: &Self::DataId, + collection_id: &Self::CollectionId, + ) -> DispatchResult; +} + +/// Abstration to represent a collection of datas in memory +pub trait DataCollection { + /// Return the last data value for a data id along with the moment it was updated last time + fn data(&self, data_id: &DataId) -> Result, DispatchError>; +} diff --git a/libs/traits/src/lib.rs b/libs/traits/src/lib.rs index 0ef61d824a..d3aaab56f4 100644 --- a/libs/traits/src/lib.rs +++ b/libs/traits/src/lib.rs @@ -41,7 +41,7 @@ pub mod ops; /// Traits related to rewards. pub mod rewards; -pub mod prices; +pub mod data; /// A trait used for loosely coupling the claim pallet with a reward mechanism. /// diff --git a/libs/traits/src/prices.rs b/libs/traits/src/prices.rs deleted file mode 100644 index c00c80e9a4..0000000000 --- a/libs/traits/src/prices.rs +++ /dev/null @@ -1,44 +0,0 @@ -use sp_runtime::{DispatchError, DispatchResult}; - -/// Abstraction that represents a storage where -/// you can subscribe to price updates and collect them -pub trait PriceRegistry { - /// A price identification - type PriceId; - - /// A collection identification - type CollectionId; - - /// A collection of prices - type Collection: PriceCollection; - - /// Represents a price - type Price; - - /// Represents a timestamp - type Moment; - - /// Return the last price value for a price id along with the moment it was updated last time - fn price(price_id: &Self::PriceId) -> Option<(Self::Price, Self::Moment)>; - - /// Retrives a collection of prices with all prices associated to a collection id - fn collection(collection_id: &Self::CollectionId) -> Self::Collection; - - /// Start listening price changes for a price id in a collection id - fn register_price_id( - price_id: &Self::PriceId, - collection_id: &Self::CollectionId, - ) -> DispatchResult; - - /// Start listening price changes for a price id in a collection id - fn unregister_price_id( - price_id: &Self::PriceId, - collection_id: &Self::CollectionId, - ) -> DispatchResult; -} - -/// Abstration to represent a collection of prices in memory -pub trait PriceCollection { - /// Return the last price value for a price id along with the moment it was updated last time - fn price(&self, price_id: &PriceId) -> Result, DispatchError>; -} diff --git a/pallets/pool-price-feed/Cargo.toml b/pallets/collection-data-feed/Cargo.toml similarity index 95% rename from pallets/pool-price-feed/Cargo.toml rename to pallets/collection-data-feed/Cargo.toml index b4815530e2..c92d59d4bf 100644 --- a/pallets/pool-price-feed/Cargo.toml +++ b/pallets/collection-data-feed/Cargo.toml @@ -1,9 +1,9 @@ [package] authors = ["Centrifuge "] -description = "Pallet to feed pools with prices" +description = "Pallet to collect data from a feeder entity" edition = "2021" license = "LGPL-3.0" -name = "pallet-pool-price-feed" +name = "pallet-collection-data-feed" repository = "https://github.com/centrifuge/centrifuge-chain" version = "1.0.0" diff --git a/pallets/pool-price-feed/src/lib.rs b/pallets/collection-data-feed/src/lib.rs similarity index 53% rename from pallets/pool-price-feed/src/lib.rs rename to pallets/collection-data-feed/src/lib.rs index bc70fc0524..a219f9be82 100644 --- a/pallets/pool-price-feed/src/lib.rs +++ b/pallets/collection-data-feed/src/lib.rs @@ -2,7 +2,7 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { - use cfg_traits::prices::{PriceCollection, PriceRegistry}; + use cfg_traits::data::{DataCollection, DataRegistry}; use frame_support::{pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap}; use orml_traits::{DataProviderExtended, OnNewData, TimestampedValue}; use sp_runtime::{ @@ -10,7 +10,7 @@ pub mod pallet { DispatchError, }; - type PriceValueOf = Option<(::Price, ::Moment)>; + type DataValueOf = Option<(::Data, ::Moment)>; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); @@ -21,25 +21,25 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// A price identification - type PriceId: Parameter + MaxEncodedLen + Ord; + /// A data identification + type DataId: Parameter + MaxEncodedLen + Ord; /// A collection identification type CollectionId: Parameter + MaxEncodedLen + Ord; - /// Represents a price - type Price: Parameter + MaxEncodedLen + Ord; + /// Represents a data + type Data: Parameter + MaxEncodedLen + Ord; /// Represents a timestamp type Moment: Parameter + MaxEncodedLen; - /// Data provider for initializing price values + /// Data provider for initializing data values type DataProvider: DataProviderExtended< - Self::PriceId, - TimestampedValue, + Self::DataId, + TimestampedValue, >; - /// Max size of a price collection + /// Max size of a data collection #[pallet::constant] type MaxCollectionSize: Get; @@ -53,25 +53,25 @@ pub mod pallet { pub(crate) type Listening = StorageMap< _, Blake2_128Concat, - T::PriceId, + T::DataId, BoundedBTreeMap, ValueQuery, >; - /// Storage that contains the price values of a collection. + /// Storage that contains the data values of a collection. #[pallet::storage] - pub(crate) type PoolPrices = StorageMap< + pub(crate) type Collection = StorageMap< _, Blake2_128Concat, T::CollectionId, - BoundedBTreeMap, T::MaxCollectionSize>, + BoundedBTreeMap, T::MaxCollectionSize>, ValueQuery, >; #[pallet::error] pub enum Error { - /// The used price ID is not in the collection. - PriceIdNotInCollection, + /// The used data ID is not in the collection. + DataIdNotInCollection, /// Max collection size exceeded MaxCollectionSize, @@ -80,36 +80,36 @@ pub mod pallet { MaxCollectionNumber, } - impl PriceRegistry for Pallet { + impl DataRegistry for Pallet { type Collection = CachedCollection; type CollectionId = T::CollectionId; + type Data = T::Data; + type DataId = T::DataId; type Moment = T::Moment; - type Price = T::Price; - type PriceId = T::PriceId; - fn price(price_id: &T::PriceId) -> PriceValueOf { - T::DataProvider::get_no_op(price_id) + fn get(data_id: &T::DataId) -> DataValueOf { + T::DataProvider::get_no_op(data_id) .map(|timestamped| (timestamped.value, timestamped.timestamp)) } fn collection(collection_id: &T::CollectionId) -> Self::Collection { - CachedCollection(PoolPrices::::get(collection_id)) + CachedCollection(Collection::::get(collection_id)) } - fn register_price_id( - price_id: &T::PriceId, + fn register_data_id( + data_id: &T::DataId, collection_id: &T::CollectionId, ) -> DispatchResult { - Listening::::try_mutate(price_id, |counters| match counters.get_mut(collection_id) { + Listening::::try_mutate(data_id, |counters| match counters.get_mut(collection_id) { Some(counter) => counter.ensure_add_assign(1).map_err(|e| e.into()), None => { counters .try_insert(collection_id.clone(), 0) .map_err(|_| Error::::MaxCollectionNumber)?; - PoolPrices::::try_mutate(collection_id, |collection| { + Collection::::try_mutate(collection_id, |collection| { collection - .try_insert(price_id.clone(), Self::price(price_id)) + .try_insert(data_id.clone(), Self::get(data_id)) .map(|_| ()) .map_err(|_| Error::::MaxCollectionSize.into()) }) @@ -117,21 +117,19 @@ pub mod pallet { }) } - fn unregister_price_id( - price_id: &T::PriceId, + fn unregister_data_id( + data_id: &T::DataId, collection_id: &T::CollectionId, ) -> DispatchResult { - Listening::::mutate(price_id, |counters| { + Listening::::mutate(data_id, |counters| { let counter = counters .get_mut(collection_id) - .ok_or(Error::::PriceIdNotInCollection)?; + .ok_or(Error::::DataIdNotInCollection)?; counter.ensure_sub_assign(1)?; if *counter == 0 { counters.remove(collection_id); - PoolPrices::::mutate(collection_id, |collection| { - collection.remove(price_id) - }); + Collection::::mutate(collection_id, |collection| collection.remove(data_id)); } Ok(()) @@ -139,13 +137,13 @@ pub mod pallet { } } - impl OnNewData for Pallet { - fn on_new_data(_: &T::AccountId, price_id: &T::PriceId, _: &T::Price) { - for collection_id in Listening::::get(price_id).keys() { - PoolPrices::::mutate(collection_id, |collection| { + impl OnNewData for Pallet { + fn on_new_data(_: &T::AccountId, data_id: &T::DataId, _: &T::Data) { + for collection_id in Listening::::get(data_id).keys() { + Collection::::mutate(collection_id, |collection| { collection - .get_mut(price_id) - .map(|value| *value = Self::price(price_id)) + .get_mut(data_id) + .map(|value| *value = Self::get(data_id)) }); } } @@ -153,15 +151,15 @@ pub mod pallet { /// A collection cached in memory pub struct CachedCollection( - BoundedBTreeMap, T::MaxCollectionSize>, + BoundedBTreeMap, T::MaxCollectionSize>, ); - impl PriceCollection for CachedCollection { - fn price(&self, price_id: &T::PriceId) -> Result, DispatchError> { + impl DataCollection for CachedCollection { + fn data(&self, data_id: &T::DataId) -> Result, DispatchError> { self.0 - .get(price_id) + .get(data_id) .cloned() - .ok_or_else(|| Error::::PriceIdNotInCollection.into()) + .ok_or_else(|| Error::::DataIdNotInCollection.into()) } } } From c80e9406bd29c1383352ec9b679568bedf8a9fbf Mon Sep 17 00:00:00 2001 From: lemunozm Date: Wed, 19 Apr 2023 11:04:52 +0200 Subject: [PATCH 11/16] add tests --- Cargo.lock | 21 ++- libs/mocks/src/data.rs | 4 +- libs/traits/src/data.rs | 2 +- pallets/collection-data-feed/Cargo.toml | 5 +- pallets/collection-data-feed/src/lib.rs | 22 ++- pallets/collection-data-feed/src/mock.rs | 138 +++++++++++++++ pallets/collection-data-feed/src/tests.rs | 205 ++++++++++++++++++++++ 7 files changed, 381 insertions(+), 16 deletions(-) create mode 100644 pallets/collection-data-feed/src/mock.rs create mode 100644 pallets/collection-data-feed/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 7dd5d78755..8eb213a2c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6397,6 +6397,24 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "orml-oracle" +version = "0.4.1-dev" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library?branch=polkadot-v0.9.37#16b6c1149a15674d21c87244b7988a667e2c14d9" +dependencies = [ + "frame-support", + "frame-system", + "orml-traits", + "orml-utilities", + "parity-scale-codec 3.4.0", + "scale-info", + "serde", + "sp-application-crypto", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "orml-tokens" version = "0.4.1-dev" @@ -6854,11 +6872,12 @@ dependencies = [ name = "pallet-collection-data-feed" version = "1.0.0" dependencies = [ - "cfg-mocks", "cfg-traits", "frame-support", "frame-system", + "orml-oracle", "orml-traits", + "pallet-timestamp", "parity-scale-codec 3.4.0", "scale-info", "sp-arithmetic", diff --git a/libs/mocks/src/data.rs b/libs/mocks/src/data.rs index abb7bbfa7f..41b0cf0705 100644 --- a/libs/mocks/src/data.rs +++ b/libs/mocks/src/data.rs @@ -93,13 +93,13 @@ pub mod pallet { T::Data: Clone, T::Moment: Clone, { - fn data( + fn get( &self, data_id: &T::DataId, ) -> Result, DispatchError> { Ok(self .0 - .get(&data_id) + .get(data_id) .ok_or(DispatchError::CannotLookup)? .clone()) } diff --git a/libs/traits/src/data.rs b/libs/traits/src/data.rs index c58b538599..cc204184c2 100644 --- a/libs/traits/src/data.rs +++ b/libs/traits/src/data.rs @@ -40,5 +40,5 @@ pub trait DataRegistry { /// Abstration to represent a collection of datas in memory pub trait DataCollection { /// Return the last data value for a data id along with the moment it was updated last time - fn data(&self, data_id: &DataId) -> Result, DispatchError>; + fn get(&self, data_id: &DataId) -> Result, DispatchError>; } diff --git a/pallets/collection-data-feed/Cargo.toml b/pallets/collection-data-feed/Cargo.toml index c92d59d4bf..f841d1590d 100644 --- a/pallets/collection-data-feed/Cargo.toml +++ b/pallets/collection-data-feed/Cargo.toml @@ -28,7 +28,8 @@ cfg-traits = { path = "../../libs/traits", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" } -cfg-mocks = { path = "../../libs/mocks" } +orml-oracle = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v0.9.37" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.37" } [features] default = ["std"] @@ -48,11 +49,9 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "cfg-traits/runtime-benchmarks", - "cfg-mocks/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "cfg-traits/try-runtime", - "cfg-mocks/try-runtime", ] diff --git a/pallets/collection-data-feed/src/lib.rs b/pallets/collection-data-feed/src/lib.rs index a219f9be82..96932fb49d 100644 --- a/pallets/collection-data-feed/src/lib.rs +++ b/pallets/collection-data-feed/src/lib.rs @@ -1,10 +1,16 @@ pub use pallet::*; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + #[frame_support::pallet] pub mod pallet { use cfg_traits::data::{DataCollection, DataRegistry}; use frame_support::{pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap}; - use orml_traits::{DataProviderExtended, OnNewData, TimestampedValue}; + use orml_traits::{DataProviderExtended, OnNewData}; use sp_runtime::{ traits::{EnsureAddAssign, EnsureSubAssign}, DispatchError, @@ -34,10 +40,7 @@ pub mod pallet { type Moment: Parameter + MaxEncodedLen; /// Data provider for initializing data values - type DataProvider: DataProviderExtended< - Self::DataId, - TimestampedValue, - >; + type DataProvider: DataProviderExtended; /// Max size of a data collection #[pallet::constant] @@ -89,7 +92,6 @@ pub mod pallet { fn get(data_id: &T::DataId) -> DataValueOf { T::DataProvider::get_no_op(data_id) - .map(|timestamped| (timestamped.value, timestamped.timestamp)) } fn collection(collection_id: &T::CollectionId) -> Self::Collection { @@ -104,7 +106,7 @@ pub mod pallet { Some(counter) => counter.ensure_add_assign(1).map_err(|e| e.into()), None => { counters - .try_insert(collection_id.clone(), 0) + .try_insert(collection_id.clone(), 1) .map_err(|_| Error::::MaxCollectionNumber)?; Collection::::try_mutate(collection_id, |collection| { @@ -121,7 +123,7 @@ pub mod pallet { data_id: &T::DataId, collection_id: &T::CollectionId, ) -> DispatchResult { - Listening::::mutate(data_id, |counters| { + Listening::::try_mutate(data_id, |counters| { let counter = counters .get_mut(collection_id) .ok_or(Error::::DataIdNotInCollection)?; @@ -139,6 +141,8 @@ pub mod pallet { impl OnNewData for Pallet { fn on_new_data(_: &T::AccountId, data_id: &T::DataId, _: &T::Data) { + // Input Data parameter could not correspond with the data comming from `DataProvider`. + // This implementation use `DataProvider` as a source of truth for Data values. for collection_id in Listening::::get(data_id).keys() { Collection::::mutate(collection_id, |collection| { collection @@ -155,7 +159,7 @@ pub mod pallet { ); impl DataCollection for CachedCollection { - fn data(&self, data_id: &T::DataId) -> Result, DispatchError> { + fn get(&self, data_id: &T::DataId) -> Result, DispatchError> { self.0 .get(data_id) .cloned() diff --git a/pallets/collection-data-feed/src/mock.rs b/pallets/collection-data-feed/src/mock.rs new file mode 100644 index 0000000000..510f3f02d7 --- /dev/null +++ b/pallets/collection-data-feed/src/mock.rs @@ -0,0 +1,138 @@ +use frame_support::traits::{ConstU16, ConstU32, ConstU64, IsInVec}; +use orml_oracle::{CombineData, DataProviderExtended}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +use crate::pallet as pallet_collection_data_feed; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +pub const BLOCK_TIME_MS: Moment = 10000; +pub const ORACLE_MEMBER: u64 = 42; + +pub type CollectionId = u16; +pub type DataId = u32; +pub type Data = u128; +pub type Moment = u64; +pub type AccountId = u64; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Timer: pallet_timestamp, + Oracle: orml_oracle, + CollectionDataFeed: pallet_collection_data_feed, + } +); + +frame_support::parameter_types! { + pub const MaxCollectionSize: u32 = 5; + pub const MaxCollections: u32 = 3; + pub const RootMember: AccountId = 23; + pub static Members: Vec = vec![ORACLE_MEMBER]; + pub const MaxHasDispatchedSize: u32 = 1; +} + +impl frame_system::Config for Runtime { + type AccountData = (); + type AccountId = AccountId; + type BaseCallFilter = frame_support::traits::Everything; + type BlockHashCount = ConstU64<250>; + type BlockLength = (); + type BlockNumber = u64; + type BlockWeights = (); + type DbWeight = (); + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = u64; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type SS58Prefix = ConstU16<42>; + type SystemWeightInfo = (); + type Version = (); +} + +impl pallet_timestamp::Config for Runtime { + type MinimumPeriod = ConstU64; + type Moment = Moment; + type OnTimestampSet = (); + type WeightInfo = (); +} + +type OracleValue = orml_oracle::TimestampedValue; + +pub struct LastData; +impl CombineData for LastData { + fn combine_data( + _: &DataId, + values: Vec, + _: Option, + ) -> Option { + values + .into_iter() + .max_by(|v1, v2| v1.timestamp.cmp(&v2.timestamp)) + } +} + +// This part is forced because of https://github.com/open-web3-stack/open-runtime-module-library/issues/904 +pub struct DataProviderBridge; +impl DataProviderExtended for DataProviderBridge { + fn get_no_op(key: &DataId) -> Option<(Data, Moment)> { + Oracle::get_no_op(key).map(|OracleValue { value, timestamp }| (value, timestamp)) + } + + fn get_all_values() -> Vec<(DataId, Option<(Data, Moment)>)> { + unimplemented!("unused by this pallet") + } +} + +impl orml_oracle::Config for Runtime { + type CombineData = LastData; + type MaxHasDispatchedSize = MaxHasDispatchedSize; + type Members = IsInVec; + type OnNewData = CollectionDataFeed; + type OracleKey = DataId; + type OracleValue = Data; + type RootOperatorAccountId = RootMember; + type RuntimeEvent = RuntimeEvent; + type Time = Timer; + type WeightInfo = (); +} + +impl pallet_collection_data_feed::Config for Runtime { + type CollectionId = CollectionId; + type Data = Data; + type DataId = DataId; + type DataProvider = DataProviderBridge; + type MaxCollectionSize = MaxCollectionSize; + type MaxCollections = MaxCollections; + type Moment = Moment; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + sp_io::TestExternalities::new(storage) +} + +pub fn advance_time(elapsed: u64) { + Timer::set_timestamp(Timer::get() + elapsed); +} diff --git a/pallets/collection-data-feed/src/tests.rs b/pallets/collection-data-feed/src/tests.rs new file mode 100644 index 0000000000..dbfdbb1a80 --- /dev/null +++ b/pallets/collection-data-feed/src/tests.rs @@ -0,0 +1,205 @@ +use cfg_traits::data::{DataCollection, DataRegistry}; +use frame_support::{assert_noop, assert_ok, pallet_prelude::Hooks}; +use orml_traits::DataFeeder; + +use super::{mock::*, pallet::Error}; + +const COLLECTION_ID: CollectionId = 1; +const DATA_ID: DataId = 10; + +#[test] +fn get_no_fed_data() { + new_test_ext().execute_with(|| { + assert_eq!(CollectionDataFeed::get(&DATA_ID), None); + }); +} + +#[test] +fn get_fed_data() { + new_test_ext().execute_with(|| { + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + + assert_eq!(CollectionDataFeed::get(&DATA_ID), Some((100, Timer::now()))); + + Oracle::on_finalize(0); + advance_time(BLOCK_TIME_MS); + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 200)); + + assert_eq!(CollectionDataFeed::get(&DATA_ID), Some((200, Timer::now()))); + }); +} + +#[test] +fn feed_and_then_register() { + new_test_ext().execute_with(|| { + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + assert_ok!( + CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), + Some((100, Timer::now())) + ); + + Oracle::on_finalize(0); + advance_time(BLOCK_TIME_MS); + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 200)); + + assert_ok!( + CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), + Some((200, Timer::now())) + ); + }); +} + +#[test] +fn register_and_then_feed() { + new_test_ext().execute_with(|| { + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + + assert_ok!( + CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), + Some((100, Timer::now())) + ); + + Oracle::on_finalize(0); + advance_time(BLOCK_TIME_MS); + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 200)); + + assert_ok!( + CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), + Some((200, Timer::now())) + ); + }); +} + +#[test] +fn data_not_registered_in_collection() { + new_test_ext().execute_with(|| { + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + Oracle::on_finalize(0); + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID + 1, 200)); + + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + let collection = CollectionDataFeed::collection(&COLLECTION_ID); + assert_noop!( + collection.get(&(DATA_ID + 1)), + Error::::DataIdNotInCollection + ); + }); +} + +#[test] +fn data_not_registered_after_unregister() { + new_test_ext().execute_with(|| { + assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + assert_ok!(CollectionDataFeed::unregister_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + let collection = CollectionDataFeed::collection(&COLLECTION_ID); + assert_noop!( + collection.get(&DATA_ID), + Error::::DataIdNotInCollection + ); + }); +} + +#[test] +fn unregister_without_register() { + new_test_ext().execute_with(|| { + assert_noop!( + CollectionDataFeed::unregister_data_id(&DATA_ID, &COLLECTION_ID), + Error::::DataIdNotInCollection + ); + }); +} + +#[test] +fn register_twice() { + new_test_ext().execute_with(|| { + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + assert_ok!(CollectionDataFeed::unregister_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + assert_ok!(CollectionDataFeed::unregister_data_id( + &DATA_ID, + &COLLECTION_ID + )); + + assert_noop!( + CollectionDataFeed::unregister_data_id(&DATA_ID, &COLLECTION_ID), + Error::::DataIdNotInCollection + ); + }); +} + +#[test] +fn max_collection_number() { + new_test_ext().execute_with(|| { + let max = MaxCollections::get() as CollectionId; + for i in 0..max { + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &(COLLECTION_ID + i) + )); + } + + assert_noop!( + CollectionDataFeed::register_data_id(&DATA_ID, &(COLLECTION_ID + max)), + Error::::MaxCollectionNumber + ); + }); +} + +#[test] +fn max_collection_size() { + new_test_ext().execute_with(|| { + let max = MaxCollectionSize::get(); + for i in 0..max { + assert_ok!(CollectionDataFeed::register_data_id( + &(DATA_ID + i), + &COLLECTION_ID + )); + } + + assert_noop!( + CollectionDataFeed::register_data_id(&(DATA_ID + max), &COLLECTION_ID), + Error::::MaxCollectionSize + ); + + // Other collections can still be registered + assert_ok!(CollectionDataFeed::register_data_id( + &DATA_ID, + &(COLLECTION_ID + 1) + )); + }); +} From 80e75ff5759809ceb9b30271d8209be11900f696 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Wed, 19 Apr 2023 11:15:12 +0200 Subject: [PATCH 12/16] test simplification --- pallets/collection-data-feed/src/mock.rs | 4 --- pallets/collection-data-feed/src/tests.rs | 33 ++++++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/pallets/collection-data-feed/src/mock.rs b/pallets/collection-data-feed/src/mock.rs index 510f3f02d7..4d2e929256 100644 --- a/pallets/collection-data-feed/src/mock.rs +++ b/pallets/collection-data-feed/src/mock.rs @@ -132,7 +132,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { sp_io::TestExternalities::new(storage) } - -pub fn advance_time(elapsed: u64) { - Timer::set_timestamp(Timer::get() + elapsed); -} diff --git a/pallets/collection-data-feed/src/tests.rs b/pallets/collection-data-feed/src/tests.rs index dbfdbb1a80..d4a2350ef8 100644 --- a/pallets/collection-data-feed/src/tests.rs +++ b/pallets/collection-data-feed/src/tests.rs @@ -7,6 +7,15 @@ use super::{mock::*, pallet::Error}; const COLLECTION_ID: CollectionId = 1; const DATA_ID: DataId = 10; +fn advance_time(elapsed: u64) { + Timer::set_timestamp(Timer::get() + elapsed); +} + +fn feed(data_id: DataId, data: Data) { + Oracle::on_finalize(0); // For testing we want any call to feed_value() + Oracle::feed_value(ORACLE_MEMBER, data_id, data).unwrap(); +} + #[test] fn get_no_fed_data() { new_test_ext().execute_with(|| { @@ -17,13 +26,12 @@ fn get_no_fed_data() { #[test] fn get_fed_data() { new_test_ext().execute_with(|| { - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + feed(DATA_ID, 100); assert_eq!(CollectionDataFeed::get(&DATA_ID), Some((100, Timer::now()))); - Oracle::on_finalize(0); advance_time(BLOCK_TIME_MS); - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 200)); + feed(DATA_ID, 200); assert_eq!(CollectionDataFeed::get(&DATA_ID), Some((200, Timer::now()))); }); @@ -32,7 +40,8 @@ fn get_fed_data() { #[test] fn feed_and_then_register() { new_test_ext().execute_with(|| { - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + feed(DATA_ID, 100); + assert_ok!(CollectionDataFeed::register_data_id( &DATA_ID, &COLLECTION_ID @@ -43,9 +52,8 @@ fn feed_and_then_register() { Some((100, Timer::now())) ); - Oracle::on_finalize(0); advance_time(BLOCK_TIME_MS); - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 200)); + feed(DATA_ID, 200); assert_ok!( CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), @@ -62,16 +70,15 @@ fn register_and_then_feed() { &COLLECTION_ID )); - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + feed(DATA_ID, 100); assert_ok!( CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), Some((100, Timer::now())) ); - Oracle::on_finalize(0); advance_time(BLOCK_TIME_MS); - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 200)); + feed(DATA_ID, 200); assert_ok!( CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), @@ -83,9 +90,8 @@ fn register_and_then_feed() { #[test] fn data_not_registered_in_collection() { new_test_ext().execute_with(|| { - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); - Oracle::on_finalize(0); - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID + 1, 200)); + feed(DATA_ID, 100); + feed(DATA_ID + 1, 100); assert_ok!(CollectionDataFeed::register_data_id( &DATA_ID, @@ -103,7 +109,8 @@ fn data_not_registered_in_collection() { #[test] fn data_not_registered_after_unregister() { new_test_ext().execute_with(|| { - assert_ok!(Oracle::feed_value(ORACLE_MEMBER, DATA_ID, 100)); + feed(DATA_ID, 100); + assert_ok!(CollectionDataFeed::register_data_id( &DATA_ID, &COLLECTION_ID From 553288cf06e7b1a34369e35a8b149f3ee36df366 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Wed, 19 Apr 2023 13:40:07 +0200 Subject: [PATCH 13/16] fix clippy --- libs/mocks/src/data.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libs/mocks/src/data.rs b/libs/mocks/src/data.rs index 41b0cf0705..1a4cba822a 100644 --- a/libs/mocks/src/data.rs +++ b/libs/mocks/src/data.rs @@ -83,9 +83,8 @@ pub mod pallet { use super::*; - pub struct MockDataCollection( - pub HashMap>, - ); + pub type Value = (::Data, ::Moment); + pub struct MockDataCollection(pub HashMap>>); impl DataCollection for MockDataCollection where @@ -93,10 +92,7 @@ pub mod pallet { T::Data: Clone, T::Moment: Clone, { - fn get( - &self, - data_id: &T::DataId, - ) -> Result, DispatchError> { + fn get(&self, data_id: &T::DataId) -> Result>, DispatchError> { Ok(self .0 .get(data_id) From 3f9dadef09bbf21e6e25c7995773f50b83424e57 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Thu, 20 Apr 2023 15:50:50 +0200 Subject: [PATCH 14/16] simplify trait --- libs/mocks/src/data.rs | 22 +++++------- libs/traits/src/data.rs | 46 +++++++++---------------- pallets/collection-data-feed/src/lib.rs | 13 ++++--- 3 files changed, 32 insertions(+), 49 deletions(-) diff --git a/libs/mocks/src/data.rs b/libs/mocks/src/data.rs index 1a4cba822a..6be10cb5b1 100644 --- a/libs/mocks/src/data.rs +++ b/libs/mocks/src/data.rs @@ -8,9 +8,8 @@ pub mod pallet { pub trait Config: frame_system::Config { type DataId; type CollectionId; - type Collection: DataCollection; + type Collection: DataCollection; type Data; - type Moment; } #[pallet::pallet] @@ -26,7 +25,7 @@ pub mod pallet { >; impl Pallet { - pub fn mock_get(f: impl Fn(&T::DataId) -> Option<(T::Data, T::Moment)> + 'static) { + pub fn mock_get(f: impl Fn(&T::DataId) -> T::Data + 'static) { register_call!(f); } @@ -47,14 +46,11 @@ pub mod pallet { } } - impl DataRegistry for Pallet { + impl DataRegistry for Pallet { type Collection = T::Collection; - type CollectionId = T::CollectionId; type Data = T::Data; - type DataId = T::DataId; - type Moment = T::Moment; - fn get(a: &T::DataId) -> Option<(T::Data, T::Moment)> { + fn get(a: &T::DataId) -> T::Data { let a = unsafe { std::mem::transmute::<_, &'static T::DataId>(a) }; execute_call!(a) } @@ -83,16 +79,16 @@ pub mod pallet { use super::*; - pub type Value = (::Data, ::Moment); - pub struct MockDataCollection(pub HashMap>>); + pub struct MockDataCollection(pub HashMap); - impl DataCollection for MockDataCollection + impl DataCollection for MockDataCollection where T::DataId: std::hash::Hash + Eq, T::Data: Clone, - T::Moment: Clone, { - fn get(&self, data_id: &T::DataId) -> Result>, DispatchError> { + type Data = Result; + + fn get(&self, data_id: &T::DataId) -> Self::Data { Ok(self .0 .get(data_id) diff --git a/libs/traits/src/data.rs b/libs/traits/src/data.rs index cc204184c2..c2fe36c884 100644 --- a/libs/traits/src/data.rs +++ b/libs/traits/src/data.rs @@ -1,44 +1,32 @@ -use sp_runtime::{DispatchError, DispatchResult}; +use sp_runtime::DispatchResult; /// Abstraction that represents a storage where /// you can subscribe to data updates and collect them -pub trait DataRegistry { - /// A data identification - type DataId; - - /// A collection identification - type CollectionId; - - /// A collection of datas - type Collection: DataCollection; +pub trait DataRegistry { + /// A collection of data + type Collection: DataCollection; /// Represents a data type Data; - /// Represents a timestamp - type Moment; - - /// Return the last data value for a data id along with the moment it was updated last time - fn get(data_id: &Self::DataId) -> Option<(Self::Data, Self::Moment)>; + /// Return the last data value for a data id + fn get(data_id: &DataId) -> Self::Data; - /// Retrives a collection of datas with all datas associated to a collection id - fn collection(collection_id: &Self::CollectionId) -> Self::Collection; + /// Retrives a collection of data with all data associated to a collection id + fn collection(collection_id: &CollectionId) -> Self::Collection; /// Start listening data changes for a data id in a collection id - fn register_data_id( - data_id: &Self::DataId, - collection_id: &Self::CollectionId, - ) -> DispatchResult; + fn register_data_id(data_id: &DataId, collection_id: &CollectionId) -> DispatchResult; /// Start listening data changes for a data id in a collection id - fn unregister_data_id( - data_id: &Self::DataId, - collection_id: &Self::CollectionId, - ) -> DispatchResult; + fn unregister_data_id(data_id: &DataId, collection_id: &CollectionId) -> DispatchResult; } -/// Abstration to represent a collection of datas in memory -pub trait DataCollection { - /// Return the last data value for a data id along with the moment it was updated last time - fn get(&self, data_id: &DataId) -> Result, DispatchError>; +/// Abstration to represent a collection of data in memory +pub trait DataCollection { + /// Represents a data + type Data; + + /// Return the last data value for a data id + fn get(&self, data_id: &DataId) -> Self::Data; } diff --git a/pallets/collection-data-feed/src/lib.rs b/pallets/collection-data-feed/src/lib.rs index 96932fb49d..f2a81e2d6f 100644 --- a/pallets/collection-data-feed/src/lib.rs +++ b/pallets/collection-data-feed/src/lib.rs @@ -83,12 +83,9 @@ pub mod pallet { MaxCollectionNumber, } - impl DataRegistry for Pallet { + impl DataRegistry for Pallet { type Collection = CachedCollection; - type CollectionId = T::CollectionId; - type Data = T::Data; - type DataId = T::DataId; - type Moment = T::Moment; + type Data = DataValueOf; fn get(data_id: &T::DataId) -> DataValueOf { T::DataProvider::get_no_op(data_id) @@ -158,8 +155,10 @@ pub mod pallet { BoundedBTreeMap, T::MaxCollectionSize>, ); - impl DataCollection for CachedCollection { - fn get(&self, data_id: &T::DataId) -> Result, DispatchError> { + impl DataCollection for CachedCollection { + type Data = Result, DispatchError>; + + fn get(&self, data_id: &T::DataId) -> Self::Data { self.0 .get(data_id) .cloned() From dbeb4818c81bb96fc86c3a632faf9f379f2e702a Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 24 Apr 2023 10:42:45 +0200 Subject: [PATCH 15/16] Add doc intro to lib --- pallets/collection-data-feed/src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pallets/collection-data-feed/src/lib.rs b/pallets/collection-data-feed/src/lib.rs index f2a81e2d6f..589b159c9b 100644 --- a/pallets/collection-data-feed/src/lib.rs +++ b/pallets/collection-data-feed/src/lib.rs @@ -1,3 +1,17 @@ +// Copyright 2021 Centrifuge GmbH (centrifuge.io). +// This file is part of Centrifuge chain project. + +// Centrifuge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version (see http://www.gnu.org/licenses). + +// Centrifuge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +//! Collects data from a feeder entity into collections to fastly read data in one memory access. pub use pallet::*; #[cfg(test)] From 6e49ab83ad9e5ba15ac3e20ca2f75d98ed8d551d Mon Sep 17 00:00:00 2001 From: lemunozm Date: Mon, 24 Apr 2023 19:00:20 +0200 Subject: [PATCH 16/16] minor function rename --- libs/mocks/src/data.rs | 8 +-- libs/traits/src/data.rs | 4 +- pallets/collection-data-feed/src/lib.rs | 10 +--- pallets/collection-data-feed/src/tests.rs | 59 ++++++----------------- 4 files changed, 24 insertions(+), 57 deletions(-) diff --git a/libs/mocks/src/data.rs b/libs/mocks/src/data.rs index 6be10cb5b1..17f487cc1c 100644 --- a/libs/mocks/src/data.rs +++ b/libs/mocks/src/data.rs @@ -33,13 +33,13 @@ pub mod pallet { register_call!(f); } - pub fn mock_register_data_id( + pub fn mock_register_id( f: impl Fn(&T::DataId, &T::CollectionId) -> DispatchResult + 'static, ) { register_call!(move |(a, b)| f(a, b)); } - pub fn mock_unregister_data_id( + pub fn mock_unregister_id( f: impl Fn(&T::DataId, &T::CollectionId) -> DispatchResult + 'static, ) { register_call!(move |(a, b)| f(a, b)); @@ -60,13 +60,13 @@ pub mod pallet { execute_call!(a) } - fn register_data_id(a: &T::DataId, b: &T::CollectionId) -> DispatchResult { + fn register_id(a: &T::DataId, b: &T::CollectionId) -> DispatchResult { let a = unsafe { std::mem::transmute::<_, &'static T::DataId>(a) }; let b = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(b) }; execute_call!((a, b)) } - fn unregister_data_id(a: &T::DataId, b: &T::CollectionId) -> DispatchResult { + fn unregister_id(a: &T::DataId, b: &T::CollectionId) -> DispatchResult { let a = unsafe { std::mem::transmute::<_, &'static T::DataId>(a) }; let b = unsafe { std::mem::transmute::<_, &'static T::CollectionId>(b) }; execute_call!((a, b)) diff --git a/libs/traits/src/data.rs b/libs/traits/src/data.rs index c2fe36c884..f86833f233 100644 --- a/libs/traits/src/data.rs +++ b/libs/traits/src/data.rs @@ -16,10 +16,10 @@ pub trait DataRegistry { fn collection(collection_id: &CollectionId) -> Self::Collection; /// Start listening data changes for a data id in a collection id - fn register_data_id(data_id: &DataId, collection_id: &CollectionId) -> DispatchResult; + fn register_id(data_id: &DataId, collection_id: &CollectionId) -> DispatchResult; /// Start listening data changes for a data id in a collection id - fn unregister_data_id(data_id: &DataId, collection_id: &CollectionId) -> DispatchResult; + fn unregister_id(data_id: &DataId, collection_id: &CollectionId) -> DispatchResult; } /// Abstration to represent a collection of data in memory diff --git a/pallets/collection-data-feed/src/lib.rs b/pallets/collection-data-feed/src/lib.rs index 589b159c9b..7f5bc38c8a 100644 --- a/pallets/collection-data-feed/src/lib.rs +++ b/pallets/collection-data-feed/src/lib.rs @@ -109,10 +109,7 @@ pub mod pallet { CachedCollection(Collection::::get(collection_id)) } - fn register_data_id( - data_id: &T::DataId, - collection_id: &T::CollectionId, - ) -> DispatchResult { + fn register_id(data_id: &T::DataId, collection_id: &T::CollectionId) -> DispatchResult { Listening::::try_mutate(data_id, |counters| match counters.get_mut(collection_id) { Some(counter) => counter.ensure_add_assign(1).map_err(|e| e.into()), None => { @@ -130,10 +127,7 @@ pub mod pallet { }) } - fn unregister_data_id( - data_id: &T::DataId, - collection_id: &T::CollectionId, - ) -> DispatchResult { + fn unregister_id(data_id: &T::DataId, collection_id: &T::CollectionId) -> DispatchResult { Listening::::try_mutate(data_id, |counters| { let counter = counters .get_mut(collection_id) diff --git a/pallets/collection-data-feed/src/tests.rs b/pallets/collection-data-feed/src/tests.rs index d4a2350ef8..70b0921e8b 100644 --- a/pallets/collection-data-feed/src/tests.rs +++ b/pallets/collection-data-feed/src/tests.rs @@ -42,10 +42,7 @@ fn feed_and_then_register() { new_test_ext().execute_with(|| { feed(DATA_ID, 100); - assert_ok!(CollectionDataFeed::register_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::register_id(&DATA_ID, &COLLECTION_ID)); assert_ok!( CollectionDataFeed::collection(&COLLECTION_ID).get(&DATA_ID), @@ -65,10 +62,7 @@ fn feed_and_then_register() { #[test] fn register_and_then_feed() { new_test_ext().execute_with(|| { - assert_ok!(CollectionDataFeed::register_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::register_id(&DATA_ID, &COLLECTION_ID)); feed(DATA_ID, 100); @@ -93,10 +87,7 @@ fn data_not_registered_in_collection() { feed(DATA_ID, 100); feed(DATA_ID + 1, 100); - assert_ok!(CollectionDataFeed::register_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::register_id(&DATA_ID, &COLLECTION_ID)); let collection = CollectionDataFeed::collection(&COLLECTION_ID); assert_noop!( @@ -111,15 +102,9 @@ fn data_not_registered_after_unregister() { new_test_ext().execute_with(|| { feed(DATA_ID, 100); - assert_ok!(CollectionDataFeed::register_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::register_id(&DATA_ID, &COLLECTION_ID)); - assert_ok!(CollectionDataFeed::unregister_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::unregister_id(&DATA_ID, &COLLECTION_ID)); let collection = CollectionDataFeed::collection(&COLLECTION_ID); assert_noop!( @@ -133,7 +118,7 @@ fn data_not_registered_after_unregister() { fn unregister_without_register() { new_test_ext().execute_with(|| { assert_noop!( - CollectionDataFeed::unregister_data_id(&DATA_ID, &COLLECTION_ID), + CollectionDataFeed::unregister_id(&DATA_ID, &COLLECTION_ID), Error::::DataIdNotInCollection ); }); @@ -142,28 +127,16 @@ fn unregister_without_register() { #[test] fn register_twice() { new_test_ext().execute_with(|| { - assert_ok!(CollectionDataFeed::register_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::register_id(&DATA_ID, &COLLECTION_ID)); - assert_ok!(CollectionDataFeed::register_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::register_id(&DATA_ID, &COLLECTION_ID)); - assert_ok!(CollectionDataFeed::unregister_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::unregister_id(&DATA_ID, &COLLECTION_ID)); - assert_ok!(CollectionDataFeed::unregister_data_id( - &DATA_ID, - &COLLECTION_ID - )); + assert_ok!(CollectionDataFeed::unregister_id(&DATA_ID, &COLLECTION_ID)); assert_noop!( - CollectionDataFeed::unregister_data_id(&DATA_ID, &COLLECTION_ID), + CollectionDataFeed::unregister_id(&DATA_ID, &COLLECTION_ID), Error::::DataIdNotInCollection ); }); @@ -174,14 +147,14 @@ fn max_collection_number() { new_test_ext().execute_with(|| { let max = MaxCollections::get() as CollectionId; for i in 0..max { - assert_ok!(CollectionDataFeed::register_data_id( + assert_ok!(CollectionDataFeed::register_id( &DATA_ID, &(COLLECTION_ID + i) )); } assert_noop!( - CollectionDataFeed::register_data_id(&DATA_ID, &(COLLECTION_ID + max)), + CollectionDataFeed::register_id(&DATA_ID, &(COLLECTION_ID + max)), Error::::MaxCollectionNumber ); }); @@ -192,19 +165,19 @@ fn max_collection_size() { new_test_ext().execute_with(|| { let max = MaxCollectionSize::get(); for i in 0..max { - assert_ok!(CollectionDataFeed::register_data_id( + assert_ok!(CollectionDataFeed::register_id( &(DATA_ID + i), &COLLECTION_ID )); } assert_noop!( - CollectionDataFeed::register_data_id(&(DATA_ID + max), &COLLECTION_ID), + CollectionDataFeed::register_id(&(DATA_ID + max), &COLLECTION_ID), Error::::MaxCollectionSize ); // Other collections can still be registered - assert_ok!(CollectionDataFeed::register_data_id( + assert_ok!(CollectionDataFeed::register_id( &DATA_ID, &(COLLECTION_ID + 1) ));