diff --git a/Cargo.lock b/Cargo.lock index bdbf6ddac268592e1979dabe5f3357926ecebcd8..451497f7f5c848fff7e67d9c50d80af27221df4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10869,6 +10869,26 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "pallet-staking-rewards" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", +] + [[package]] name = "pallet-staking-runtime-api" version = "14.0.0" diff --git a/Cargo.toml b/Cargo.toml index 01d6ef8e87bdaf4dcf0e4f73dfde59df76f500c4..6bcd830f14c7c0cfac00f34c05aeea80cba203d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -381,6 +381,7 @@ members = [ "substrate/frame/session/benchmarking", "substrate/frame/society", "substrate/frame/staking", + "substrate/frame/staking-rewards", "substrate/frame/staking/reward-curve", "substrate/frame/staking/reward-fn", "substrate/frame/staking/runtime-api", diff --git a/substrate/frame/staking-rewards/Cargo.toml b/substrate/frame/staking-rewards/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..0a693545f764c610f44f9dbefd34a1ffdf33dc75 --- /dev/null +++ b/substrate/frame/staking-rewards/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "pallet-staking-rewards" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage = "https://substrate.io" +repository.workspace = true +description = "FRAME staking rewards pallet" +readme = "README.md" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +frame-support = { path = "../support", default-features = false } +frame-system = { path = "../system", default-features = false } +frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +sp-api = { path = "../../primitives/api", default-features = false } +sp-core = { path = "../../primitives/core", default-features = false } +sp-io = { path = "../../primitives/io", default-features = false } +sp-std = { path = "../../primitives/std", default-features = false } +sp-runtime = { path = "../../primitives/runtime", default-features = false } +sp-arithmetic = { path = "../../primitives/arithmetic", default-features = false } + +[dev-dependencies] +pallet-balances = { path = "../balances" } +pallet-assets = { path = "../assets" } +primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "num-traits", "scale-info"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "pallet-assets/std", + "pallet-balances/std", + "primitive-types/std", + "scale-info/std", + "sp-api/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/staking-rewards/src/lib.rs b/substrate/frame/staking-rewards/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..d100e11cba76a7dd15bf3373d0d1479c6610cb63 --- /dev/null +++ b/substrate/frame/staking-rewards/src/lib.rs @@ -0,0 +1,251 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # FRAME Staking Rewards Pallet +//! +//! A pallet that allows users to stake assets and receive rewards in return. +//! +//! Based on the [AccumulatedRewardsPerShare](https://dev.to/heymarkkop/understanding-sushiswaps-masterchef-staking-rewards-1m6f) algorithm. +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_system::pallet_prelude::BlockNumberFor; +pub use pallet::*; + +use frame_support::{ + traits::{ + fungibles::{Balanced, Inspect, Mutate}, + tokens::Balance, + }, + PalletId, +}; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::{DispatchError, Saturating}; +use sp_std::boxed::Box; + +/// Unique identifier for a staking pool. (staking_asset, reward_asset). +pub type PoolId<AssetId> = (AssetId, AssetId); + +/// Information on a user currently staking in a pool. +#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct PoolStakerInfo<Balance> { + amount: Balance, + rewards: Balance, + reward_debt: Balance, +} + +/// Staking pool. +#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct PoolInfo<Balance, BlockNumber> { + reward_rate_per_block: Balance, + total_tokens_staked: Balance, + accumulated_rewards_per_share: Balance, + last_rewarded_block: BlockNumber, +} + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::AccountIdConversion; + + #[pallet::pallet] + pub struct Pallet<T>(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; + + /// The pallet's id, used for deriving its sovereign account ID. + #[pallet::constant] + type PalletId: Get<PalletId>; + + /// Identifier for each type of asset. + type AssetId: Member + Parameter + Clone + MaybeSerializeDeserialize + MaxEncodedLen; + + /// The type in which the assets are measured. + type Balance: Balance + TypeInfo; + + /// Registry of assets that can be configured to either stake for rewards, or be offered as + /// rewards for staking. + type Assets: Inspect<Self::AccountId, AssetId = Self::AssetId, Balance = Self::Balance> + + Mutate<Self::AccountId> + + Balanced<Self::AccountId>; + } + + /// State of stakers in each pool. + #[pallet::storage] + pub type PoolStakers<T: Config> = StorageDoubleMap< + _, + Blake2_128Concat, + PoolId<T::AssetId>, + Blake2_128Concat, + T::AccountId, + PoolStakerInfo<T::Balance>, + >; + + /// State and configuraiton of each staking pool. + #[pallet::storage] + pub type Pools<T: Config> = StorageMap< + _, + Blake2_128Concat, + PoolId<T::AssetId>, + PoolInfo<T::Balance, BlockNumberFor<T>>, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event<T: Config> { + /// An account staked some tokens in a pool. + Stake { + /// The account. + who: T::AccountId, + /// The pool. + pool_id: PoolId<T::AssetId>, + /// The amount. + amount: T::Balance, + }, + /// An account unstaked some tokens from a pool. + Unstake { + /// The account. + who: T::AccountId, + /// The pool. + pool_id: PoolId<T::AssetId>, + /// The amount. + amount: T::Balance, + }, + /// An account harvested some rewards. + RewardsHarvested { + /// The account. + who: T::AccountId, + /// The pool. + pool_id: PoolId<T::AssetId>, + /// The rewarded tokens. + amount: T::Balance, + }, + /// A new reward pool was created. + PoolCreated { + /// The pool. + pool_id: PoolId<T::AssetId>, + /// The initial reward rate per block. + reward_rate_per_block: T::Balance, + }, + /// A reward pool was deleted. + PoolDeleted { + /// The pool. + pool_id: PoolId<T::AssetId>, + }, + /// A pool reward rate was been changed. + PoolRewardRateChanged { + /// The pool with the changed reward rate. + pool_id: PoolId<T::AssetId>, + /// The new reward rate of the reward rate per block distributed to stakers. + reward_rate_per_block: T::Balance, + }, + /// Funds were withdrawn from the Reward Pool. + RewardPoolWithdrawal { + /// The asset withdrawn. + reward_asset_id: T::AssetId, + /// The caller. + caller: T::AccountId, + /// The acount of reward asset withdrawn. + amount: T::Balance, + }, + } + + #[pallet::error] + pub enum Error<T> { + /// TODO + TODO, + } + + #[pallet::hooks] + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { + fn integrity_test() { + todo!() + } + } + + /// Pallet's callable functions. + #[pallet::call] + impl<T: Config> Pallet<T> { + /// Create a new reward pool. + pub fn create_pool( + _origin: OriginFor<T>, + _staked_asset_id: T::AssetId, + _reward_asset_id: T::AssetId, + ) -> DispatchResult { + todo!() + } + + /// Removes an existing reward pool. + /// + /// TODO decide how to manage clean up of stakers from a removed pool + pub fn remove_pool( + _origin: OriginFor<T>, + _staked_asset_id: T::AssetId, + _reward_asset_id: T::AssetId, + ) -> DispatchResult { + todo!() + } + + /// Stake tokens in a pool. + pub fn stake( + _origin: OriginFor<T>, + _staked_asset_id: T::AssetId, + _reward_asset_id: T::AssetId, + _amount: T::Balance, + ) -> DispatchResult { + todo!() + } + + /// Unstake tokens from a pool. + pub fn unstake( + _origin: OriginFor<T>, + _staked_asset_id: T::AssetId, + _reward_asset_id: T::AssetId, + _amount: T::Balance, + ) -> DispatchResult { + todo!() + } + + /// Harvest unclaimed pool rewards. + pub fn harvest_rewards( + _origin: OriginFor<T>, + _staked_asset_id: T::AssetId, + _reward_asset_id: T::AssetId, + ) -> DispatchResult { + todo!() + } + } + + impl<T: Config> Pallet<T> { + /// The account ID of the reward pot. + fn reward_pool_account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + /// Update pool state in preparation for reward harvesting. + fn update_pool_rewards(_staked_asset_id: T::AssetId, _reward_asset_id: T::AssetId) { + todo!() + } + } +}