diff --git a/Cargo.lock b/Cargo.lock index 90c768c..0e53472 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7138,6 +7138,25 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-rewards" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "order-primitives", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-root-testing" version = "4.0.0" diff --git a/Cargo.toml b/Cargo.toml index 0165bfb..9414045 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -165,3 +165,4 @@ pallet-market = { path = "./pallets/market", default-features = false } pallet-orders = { path = "./pallets/orders", default-features = false } pallet-processor = { path = "./pallets/processor", default-features = false } pallet-regions = { path = "./pallets/regions", default-features = false } +pallet-rewards = { path = "./pallets/rewards", default-features = false } diff --git a/pallets/rewards/Cargo.toml b/pallets/rewards/Cargo.toml new file mode 100644 index 0000000..60c2905 --- /dev/null +++ b/pallets/rewards/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "pallet-rewards" +authors = ["Anonymous"] +description = "Pallet for processing coretime orders" +version = "0.1.0" +license = "GPLv3" +edition = "2021" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { workspace = true } +codec = { workspace = true, default-features = false, features = ["derive"] } +scale-info = { workspace = true, default-features = false, features = ["derive"] } + +# Substrate +frame-benchmarking = { workspace = true, default-features = false, optional = true } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } + +# Local +order-primitives = { workspace = true, default-features = false } + +[dev-dependencies] +serde = { workspace = true } +smallvec = { workspace = true } +pallet-balances = { workspace = true, default-features = false } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", +] +std = [ + "log/std", + "codec/std", + "scale-info/std", + "sp-io/std", + "sp-core/std", + "sp-runtime/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "order-primitives/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/rewards/src/lib.rs b/pallets/rewards/src/lib.rs new file mode 100644 index 0000000..becbe17 --- /dev/null +++ b/pallets/rewards/src/lib.rs @@ -0,0 +1,159 @@ +// This file is part of RegionX. +// +// RegionX 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. +// +// RegionX 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. +// +// You should have received a copy of the GNU General Public License +// along with RegionX. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::fungibles; +use frame_system::WeightInfo; +use order_primitives::{OrderFactory, OrderId, OrderInspect}; +pub use pallet::*; +use sp_runtime::traits::Zero; + +mod types; + +const LOG_TARGET: &str = "runtime::rewards"; + +pub type BalanceOf = <::Assets as fungibles::Inspect< + ::AccountId, +>>::Balance; + +pub type AssetIdOf = <::Assets as fungibles::Inspect< + ::AccountId, +>>::AssetId; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{fungibles::Inspect, tokens::Balance}, + }; + use frame_system::pallet_prelude::*; + use types::PrizePoolDetails; + + /// The module configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Relay chain balance type + type Balance: Balance + + Into<>::Balance> + + Into; + + /// The type should provide access to assets which can be used as reward. + type Assets: Inspect; + + /// Type over which we can access order data. + type Orders: OrderInspect + OrderFactory; + + /// Weight Info + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(_); + + /// Defines the rewards that will be distributed among order contributors if the order is + /// fulfilled on time. + #[pallet::storage] + #[pallet::getter(fn order_rewards)] + pub type PrizePools = + StorageMap<_, Blake2_128Concat, OrderId, PrizePoolDetails, BalanceOf>>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event {} + + #[pallet::error] + #[derive(PartialEq)] + pub enum Error { + /// The caller submitted an extrinsic which wasn't allowed with their origin. + Unallowed, + /// The order expired and there is no point in configuring rewards. + OrderExpired, + /// Order not found. + UnknownOrder, + /// Rewards can only be configured if there is no existing configuration or if the rewards + /// are not yet set and are currently zero. + CantConfigure, + /// The reward pool of an order was not found. + PrizePoolNotFound, + /// The asset the user wanted to use for rewards does not exist. + AssetNotFound, + } + + #[pallet::call] + impl Pallet { + /// The order creator can configure the asset which will be used for rewarding contributors. + #[pallet::call_index(0)] + #[pallet::weight(10_000)] + pub fn configure_rewards( + origin: OriginFor, + order_id: OrderId, + asset_id: AssetIdOf, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + + let order = T::Orders::order(&order_id).ok_or(Error::::UnknownOrder)?; + + ensure!(!T::Orders::order_expired(&order), Error::::OrderExpired); + ensure!(order.creator == caller, Error::::Unallowed); + + let maybe_pool = PrizePools::::get(order_id); + // Rewards can be reconfigured if the amount is still zero. + if let Some(pool) = maybe_pool { + ensure!(pool.amount == Zero::zero(), Error::::CantConfigure); + } + + ensure!(T::Assets::asset_exists(asset_id.clone()) == true, Error::::AssetNotFound); + + PrizePools::::insert(order_id, PrizePoolDetails { asset_id, amount: Zero::zero() }); + + // TODO: deposit event + Ok(()) + } + + /// Add rewards to a reward pool of an order. + /// + /// The added rewards will be of the configured asset kind. + /// + /// ## Parameters + /// + /// - `origin`: Signed origin. Can be called by any account. + /// - `order_id`: The order to which the user wants to contribute rewards. There must exist + /// a reward pool for the specified order. + /// -`amount`: number of tokens the user wants to add to the reward pool. + #[pallet::call_index(1)] + #[pallet::weight(10_000)] + pub fn add_rewards( + origin: OriginFor, + order_id: OrderId, + amount: BalanceOf, + ) -> DispatchResult { + let _caller = ensure_signed(origin)?; + + let Some(pool) = PrizePools::::get(order_id) else { + return Err(Error::::PrizePoolNotFound.into()) + }; + + // TODO: check if user has enough tokens + // TODO: contribute to the pool. + + Ok(()) + } + } +} diff --git a/pallets/rewards/src/types.rs b/pallets/rewards/src/types.rs new file mode 100644 index 0000000..3cab3d3 --- /dev/null +++ b/pallets/rewards/src/types.rs @@ -0,0 +1,26 @@ +// This file is part of RegionX. +// +// RegionX 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. +// +// RegionX 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. +// +// You should have received a copy of the GNU General Public License +// along with RegionX. If not, see . + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +/// Contains reward details of an order. +#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct PrizePoolDetails { + /// The asset which the contributors will receive, + pub asset_id: AssetId, + /// Amount of tokens that will be distributed to contributors. + pub amount: Balance, +}