diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index d7f48c17f8726793061b73b4ad25b2e4630df78c..bed09f135de322c2b6a5be51b5acdc67bc5f63bd 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3568,6 +3568,7 @@ dependencies = [ "pallet-society", "pallet-staking", "pallet-staking-reward-curve", + "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-sudo", "pallet-timestamp", @@ -6129,6 +6130,7 @@ dependencies = [ name = "pallet-nomination-pools-runtime-api" version = "1.0.0-dev" dependencies = [ + "pallet-nomination-pools", "parity-scale-codec", "sp-api", "sp-std", @@ -6486,6 +6488,14 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "pallet-staking-runtime-api" +version = "4.0.0-dev" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + [[package]] name = "pallet-state-trie-migration" version = "4.0.0-dev" diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index aaa1c2a2111832e3d127370264837709da19d8af..a86ec3146832b472ebd59c1b7e840e5064d50ad6 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -141,6 +141,7 @@ members = [ "frame/staking", "frame/staking/reward-curve", "frame/staking/reward-fn", + "frame/staking/runtime-api", "frame/state-trie-migration", "frame/sudo", "frame/root-offences", diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 1d2e6f057d51741a2d9efe189b806ca89c52a64c..e27e4f7f8a5451be0986b36fb1c4146ee9bea9b6 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -97,6 +97,7 @@ pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = ". pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" } +pallet-staking-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/runtime-api" } pallet-state-trie-migration = { version = "4.0.0-dev", default-features = false, path = "../../../frame/state-trie-migration" } pallet-scheduler = { version = "4.0.0-dev", default-features = false, path = "../../../frame/scheduler" } pallet-society = { version = "4.0.0-dev", default-features = false, path = "../../../frame/society" } @@ -175,6 +176,7 @@ std = [ "sp-runtime/std", "sp-staking/std", "pallet-staking/std", + "pallet-staking-runtime-api/std", "pallet-state-trie-migration/std", "sp-session/std", "pallet-sudo/std", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 2c7969ebcd6b9c014a9a561ac3920a1f2664750d..0b887ff7d7a9f421e336ab5a8285cb081943add5 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1986,8 +1986,22 @@ impl_runtime_apis! { } impl pallet_nomination_pools_runtime_api::NominationPoolsApi<Block, AccountId, Balance> for Runtime { - fn pending_rewards(member_account: AccountId) -> Balance { - NominationPools::pending_rewards(member_account).unwrap_or_default() + fn pending_rewards(who: AccountId) -> Balance { + NominationPools::api_pending_rewards(who).unwrap_or_default() + } + + fn points_to_balance(pool_id: pallet_nomination_pools::PoolId, points: Balance) -> Balance { + NominationPools::api_points_to_balance(pool_id, points) + } + + fn balance_to_points(pool_id: pallet_nomination_pools::PoolId, new_funds: Balance) -> Balance { + NominationPools::api_balance_to_points(pool_id, new_funds) + } + } + + impl pallet_staking_runtime_api::StakingApi<Block, Balance> for Runtime { + fn nominations_quota(balance: Balance) -> u32 { + Staking::api_nominations_quota(balance) } } diff --git a/substrate/frame/nomination-pools/runtime-api/Cargo.toml b/substrate/frame/nomination-pools/runtime-api/Cargo.toml index d1e4fbb30df5216f2a5ba29488b9476fa8dc40ac..5e290232a115ed657830c2592a19d49b3216881a 100644 --- a/substrate/frame/nomination-pools/runtime-api/Cargo.toml +++ b/substrate/frame/nomination-pools/runtime-api/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../" } [features] default = ["std"] @@ -23,4 +24,5 @@ std = [ "codec/std", "sp-api/std", "sp-std/std", + "pallet-nomination-pools/std", ] diff --git a/substrate/frame/nomination-pools/runtime-api/src/lib.rs b/substrate/frame/nomination-pools/runtime-api/src/lib.rs index aa3ca57ca5b8bf1ac7b5ed8a0214aa9014553d52..94573bfdb2e339219200e29214a0da9cabe17202 100644 --- a/substrate/frame/nomination-pools/runtime-api/src/lib.rs +++ b/substrate/frame/nomination-pools/runtime-api/src/lib.rs @@ -21,13 +21,22 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Codec; +use pallet_nomination_pools::PoolId; sp_api::decl_runtime_apis! { /// Runtime api for accessing information about nomination pools. pub trait NominationPoolsApi<AccountId, Balance> - where AccountId: Codec, Balance: Codec + where + AccountId: Codec, + Balance: Codec, { /// Returns the pending rewards for the member that the AccountId was given for. - fn pending_rewards(member: AccountId) -> Balance; + fn pending_rewards(who: AccountId) -> Balance; + + /// Returns the equivalent balance of `points` for a given pool. + fn points_to_balance(pool_id: PoolId, points: Balance) -> Balance; + + /// Returns the equivalent points of `new_funds` for a given pool. + fn balance_to_points(pool_id: PoolId, new_funds: Balance) -> Balance; } } diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index 3cb8abedda2fb4caeb382fd84e1c2a6f4dff9169..9c52cf71c6b30b3856dd992258e80d2a4b00c49f 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -2142,24 +2142,6 @@ pub mod pallet { } impl<T: Config> Pallet<T> { - /// Returns the pending rewards for the specified `member_account`. - /// - /// In the case of error, `None` is returned. - pub fn pending_rewards(member_account: T::AccountId) -> Option<BalanceOf<T>> { - if let Some(pool_member) = PoolMembers::<T>::get(member_account) { - if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id) - .zip(BondedPools::<T>::get(pool_member.pool_id)) - { - let current_reward_counter = reward_pool - .current_reward_counter(pool_member.pool_id, bonded_pool.points) - .ok()?; - return pool_member.pending_rewards(current_reward_counter).ok() - } - } - - None - } - /// The amount of bond that MUST REMAIN IN BONDED in ALL POOLS. /// /// It is the responsibility of the depositor to put these funds into the pool initially. Upon @@ -2579,6 +2561,50 @@ impl<T: Config> Pallet<T> { } } +impl<T: Config> Pallet<T> { + /// Returns the pending rewards for the specified `who` account. + /// + /// In the case of error, `None` is returned. Used by runtime API. + pub fn api_pending_rewards(who: T::AccountId) -> Option<BalanceOf<T>> { + if let Some(pool_member) = PoolMembers::<T>::get(who) { + if let Some((reward_pool, bonded_pool)) = RewardPools::<T>::get(pool_member.pool_id) + .zip(BondedPools::<T>::get(pool_member.pool_id)) + { + let current_reward_counter = reward_pool + .current_reward_counter(pool_member.pool_id, bonded_pool.points) + .ok()?; + return pool_member.pending_rewards(current_reward_counter).ok() + } + } + + None + } + + /// Returns the points to balance conversion for a specified pool. + /// + /// If the pool ID does not exist, it returns 0 ratio points to balance. Used by runtime API. + pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf<T>) -> BalanceOf<T> { + if let Some(pool) = BondedPool::<T>::get(pool_id) { + pool.points_to_balance(points) + } else { + Zero::zero() + } + } + + /// Returns the equivalent `new_funds` balance to point conversion for a specified pool. + /// + /// If the pool ID does not exist, returns 0 ratio balance to points. Used by runtime API. + pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> { + if let Some(pool) = BondedPool::<T>::get(pool_id) { + let bonded_balance = + T::Staking::active_stake(&pool.bonded_account()).unwrap_or(Zero::zero()); + Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds) + } else { + Zero::zero() + } + } +} + impl<T: Config> OnStakerSlash<T::AccountId, BalanceOf<T>> for Pallet<T> { fn on_slash( pool_account: &T::AccountId, diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 7d5d418bbf2c86046ed2d17641441bfa344c3d92..be52996a587f7ab321384e5d75d314b9ac574bac 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -195,6 +195,46 @@ mod bonded_pool { }) } + #[test] + fn api_points_to_balance_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(BondedPool::<Runtime>::get(1).is_some()); + assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 10); + + // slash half of the pool's balance. expected result of `fn api_points_to_balance` + // to be 1/2 of the pool's balance. + StakingMock::set_bonded_balance( + default_bonded_account(), + Pools::depositor_min_bond() / 2, + ); + assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 5); + + // if pool does not exist, points to balance ratio is 0. + assert_eq!(BondedPool::<Runtime>::get(2), None); + assert_eq!(Pallet::<Runtime>::api_points_to_balance(2, 10), 0); + }) + } + + #[test] + fn api_balance_to_points_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 0), 0); + assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 10); + + // slash half of the pool's balance. expect result of `fn api_balance_to_points` + // to be 2 * of the balance to add to the pool. + StakingMock::set_bonded_balance( + default_bonded_account(), + Pools::depositor_min_bond() / 2, + ); + assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 20); + + // if pool does not exist, balance to points ratio is 0. + assert_eq!(BondedPool::<Runtime>::get(2), None); + assert_eq!(Pallet::<Runtime>::api_points_to_balance(2, 10), 0); + }) + } + #[test] fn ok_to_join_with_works() { ExtBuilder::default().build_and_execute(|| { @@ -1305,51 +1345,51 @@ mod claim_payout { ExtBuilder::default().build_and_execute(|| { let ed = Balances::minimum_balance(); - assert_eq!(Pools::pending_rewards(10), Some(0)); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); - assert_eq!(Pools::pending_rewards(10), Some(30)); - assert_eq!(Pools::pending_rewards(20), None); + assert_eq!(Pools::api_pending_rewards(10), Some(30)); + assert_eq!(Pools::api_pending_rewards(20), None); Balances::make_free_balance_be(&20, ed + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - assert_eq!(Pools::pending_rewards(10), Some(30)); - assert_eq!(Pools::pending_rewards(20), Some(0)); + assert_eq!(Pools::api_pending_rewards(10), Some(30)); + assert_eq!(Pools::api_pending_rewards(20), Some(0)); Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); - assert_eq!(Pools::pending_rewards(10), Some(30 + 50)); - assert_eq!(Pools::pending_rewards(20), Some(50)); - assert_eq!(Pools::pending_rewards(30), None); + assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50)); + assert_eq!(Pools::api_pending_rewards(20), Some(50)); + assert_eq!(Pools::api_pending_rewards(30), None); Balances::make_free_balance_be(&30, ed + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); - assert_eq!(Pools::pending_rewards(10), Some(30 + 50)); - assert_eq!(Pools::pending_rewards(20), Some(50)); - assert_eq!(Pools::pending_rewards(30), Some(0)); + assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50)); + assert_eq!(Pools::api_pending_rewards(20), Some(50)); + assert_eq!(Pools::api_pending_rewards(30), Some(0)); Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); - assert_eq!(Pools::pending_rewards(10), Some(30 + 50 + 20)); - assert_eq!(Pools::pending_rewards(20), Some(50 + 20)); - assert_eq!(Pools::pending_rewards(30), Some(20)); + assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50 + 20)); + assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20)); + assert_eq!(Pools::api_pending_rewards(30), Some(20)); // 10 should claim 10, 20 should claim nothing. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); - assert_eq!(Pools::pending_rewards(10), Some(0)); - assert_eq!(Pools::pending_rewards(20), Some(50 + 20)); - assert_eq!(Pools::pending_rewards(30), Some(20)); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); + assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20)); + assert_eq!(Pools::api_pending_rewards(30), Some(20)); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); - assert_eq!(Pools::pending_rewards(10), Some(0)); - assert_eq!(Pools::pending_rewards(20), Some(0)); - assert_eq!(Pools::pending_rewards(30), Some(20)); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); + assert_eq!(Pools::api_pending_rewards(20), Some(0)); + assert_eq!(Pools::api_pending_rewards(30), Some(20)); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); - assert_eq!(Pools::pending_rewards(10), Some(0)); - assert_eq!(Pools::pending_rewards(20), Some(0)); - assert_eq!(Pools::pending_rewards(30), Some(0)); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); + assert_eq!(Pools::api_pending_rewards(20), Some(0)); + assert_eq!(Pools::api_pending_rewards(30), Some(0)); }); } diff --git a/substrate/frame/staking/runtime-api/Cargo.toml b/substrate/frame/staking/runtime-api/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9923b881c38d891eb71d010c3405da17ab2ea450 --- /dev/null +++ b/substrate/frame/staking/runtime-api/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pallet-staking-runtime-api" +version = "4.0.0-dev" +authors = ["Parity Technologies <admin@parity.io>"] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "RPC runtime API for transaction payment FRAME pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-api/std", +] diff --git a/substrate/frame/staking/runtime-api/README.md b/substrate/frame/staking/runtime-api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a999e519f8cbfae23ba0742d6849a533022fbadb --- /dev/null +++ b/substrate/frame/staking/runtime-api/README.md @@ -0,0 +1,3 @@ +Runtime API definition for the staking pallet. + +License: Apache-2.0 diff --git a/substrate/frame/staking/runtime-api/src/lib.rs b/substrate/frame/staking/runtime-api/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..378599c665506007ddae5ea227829d4af2483103 --- /dev/null +++ b/substrate/frame/staking/runtime-api/src/lib.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 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. + +//! Runtime API definition for the staking pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; + +sp_api::decl_runtime_apis! { + pub trait StakingApi<Balance> + where + Balance: Codec, + { + /// Returns the nominations quota for a nominator with a given balance. + fn nominations_quota(balance: Balance) -> u32; + } +} diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 6a35e2b8615659e31981adc4f99d3f4378126bd1..fc0bf082c7cafee11fbdd60285379882034deac2 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -972,6 +972,20 @@ impl<T: Config> Pallet<T> { } } +impl<T: Config> Pallet<T> { + /// Returns the current nominations quota for nominators. + /// + /// Used by the runtime API. + /// Note: for now, this api runtime will always return value of `T::MaxNominations` and thus it + /// is redundant. However, with the upcoming changes in + /// <https://github.com/paritytech/substrate/pull/12970>, the nominations quota will change + /// depending on the nominators balance. We're introducing this runtime API now to prepare the + /// community to use it before rolling out PR#12970. + pub fn api_nominations_quota(_balance: BalanceOf<T>) -> u32 { + T::MaxNominations::get() + } +} + impl<T: Config> ElectionDataProvider for Pallet<T> { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor<T>;