From 1b7340ff922705b9f91cb7d549f92eb8d5bc7dac Mon Sep 17 00:00:00 2001 From: Eugene Kovalev Date: Fri, 15 Sep 2023 22:20:05 +0200 Subject: [PATCH] Add RPC call to query inflation and ROI at block hash --- Cargo.lock | 24 +++++ Cargo.toml | 2 + node/service/Cargo.toml | 2 + node/service/src/client.rs | 4 +- node/service/src/rpc/mod.rs | 6 +- pallets/staking-rewards/rpc/Cargo.toml | 19 ++++ .../rpc/runtime-api/Cargo.toml | 19 ++++ .../rpc/runtime-api/src/lib.rs | 28 ++++++ pallets/staking-rewards/rpc/src/lib.rs | 96 +++++++++++++++++++ pallets/staking-rewards/src/lib.rs | 40 ++++++++ runtime/gear/Cargo.toml | 4 + runtime/gear/src/lib.rs | 6 ++ runtime/vara/Cargo.toml | 2 + runtime/vara/src/lib.rs | 6 ++ 14 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 pallets/staking-rewards/rpc/Cargo.toml create mode 100644 pallets/staking-rewards/rpc/runtime-api/Cargo.toml create mode 100644 pallets/staking-rewards/rpc/runtime-api/src/lib.rs create mode 100644 pallets/staking-rewards/rpc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 108999619f0..a124fae3d5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4134,6 +4134,8 @@ dependencies = [ "pallet-gear-program", "pallet-gear-rpc-runtime-api", "pallet-gear-scheduler", + "pallet-gear-staking-rewards", + "pallet-gear-staking-rewards-rpc-runtime-api", "pallet-gear-voucher", "pallet-grandpa", "pallet-multisig", @@ -4286,6 +4288,8 @@ dependencies = [ "log", "pallet-gear-rpc", "pallet-gear-rpc-runtime-api", + "pallet-gear-staking-rewards-rpc", + "pallet-gear-staking-rewards-rpc-runtime-api", "pallet-im-online", "pallet-staking", "pallet-transaction-payment", @@ -7664,6 +7668,25 @@ dependencies = [ "sp-std 5.0.0 (git+https://github.com/gear-tech/substrate.git?branch=gear-polkadot-v0.9.41-canary-no-sandbox)", ] +[[package]] +name = "pallet-gear-staking-rewards-rpc" +version = "1.0.0" +dependencies = [ + "jsonrpsee", + "pallet-gear-staking-rewards-rpc-runtime-api", + "sp-api", + "sp-blockchain", + "sp-runtime 7.0.0 (git+https://github.com/gear-tech/substrate.git?branch=gear-polkadot-v0.9.41-canary-no-sandbox)", +] + +[[package]] +name = "pallet-gear-staking-rewards-rpc-runtime-api" +version = "1.0.0" +dependencies = [ + "pallet-gear-staking-rewards", + "sp-api", +] + [[package]] name = "pallet-gear-voucher" version = "1.0.0" @@ -13501,6 +13524,7 @@ dependencies = [ "pallet-gear-rpc-runtime-api", "pallet-gear-scheduler", "pallet-gear-staking-rewards", + "pallet-gear-staking-rewards-rpc-runtime-api", "pallet-gear-voucher", "pallet-grandpa", "pallet-identity", diff --git a/Cargo.toml b/Cargo.toml index 108d5011b92..48260257de4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -231,6 +231,8 @@ pallet-gear-rpc = { path = "pallets/gear/rpc" } pallet-gear-rpc-runtime-api = { path = "pallets/gear/rpc/runtime-api", default-features = false } pallet-gear-scheduler = { path = "pallets/gear-scheduler", default-features = false } pallet-gear-staking-rewards = { path = "pallets/staking-rewards", default-features = false } +pallet-gear-staking-rewards-rpc = { path = "pallets/staking-rewards/rpc" } +pallet-gear-staking-rewards-rpc-runtime-api = { path = "pallets/staking-rewards/rpc/runtime-api", default-features = false } pallet-gear-voucher = { path = "pallets/gear-voucher", default-features = false } pallet-gear-bank = { path = "pallets/gear-bank", default-features = false } runtime-common = { package = "gear-runtime-common", path = "runtime/common", default-features = false } diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 79ff6da306f..707bd61836a 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -22,6 +22,8 @@ serde = { workspace = true, features = ["derive"] } # Gear pallet-gear-rpc.workspace = true pallet-gear-rpc-runtime-api = { workspace = true, features = ["std"] } +pallet-gear-staking-rewards-rpc.workspace = true +pallet-gear-staking-rewards-rpc-runtime-api = { workspace = true, features = ["std"] } runtime-primitives = { workspace = true, features = ["std"] } gear-runtime-interface = { workspace = true, features = ["std"] } authorship.workspace = true diff --git a/node/service/src/client.rs b/node/service/src/client.rs index 284686c4cb3..b118fe36453 100644 --- a/node/service/src/client.rs +++ b/node/service/src/client.rs @@ -119,6 +119,7 @@ pub trait RuntimeApiCollection: + sp_offchain::OffchainWorkerApi + sp_session::SessionKeys + pallet_gear_rpc_runtime_api::GearApi + + pallet_gear_staking_rewards_rpc_runtime_api::GearStakingRewardsApi where >::StateBackend: sp_api::StateBackend, { @@ -136,7 +137,8 @@ where + sp_api::Metadata + sp_offchain::OffchainWorkerApi + sp_session::SessionKeys - + pallet_gear_rpc_runtime_api::GearApi, + + pallet_gear_rpc_runtime_api::GearApi + + pallet_gear_staking_rewards_rpc_runtime_api::GearStakingRewardsApi, >::StateBackend: sp_api::StateBackend, { } diff --git a/node/service/src/rpc/mod.rs b/node/service/src/rpc/mod.rs index 527495e6f18..8c1242bb07b 100644 --- a/node/service/src/rpc/mod.rs +++ b/node/service/src/rpc/mod.rs @@ -101,6 +101,7 @@ where + 'static, C::Api: substrate_frame_rpc_system::AccountNonceApi, C::Api: pallet_gear_rpc::GearRuntimeApi, + C::Api: pallet_gear_staking_rewards_rpc::GearStakingRewardsRuntimeApi, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, C::Api: BabeApi, C::Api: BlockBuilder, @@ -110,6 +111,7 @@ where B::State: sc_client_api::backend::StateBackend>, { use pallet_gear_rpc::{Gear, GearApiServer}; + use pallet_gear_staking_rewards_rpc::{GearStakingRewards, GearStakingRewardsApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use runtime_info::{RuntimeInfoApi, RuntimeInfoServer}; use sc_consensus_babe_rpc::{Babe, BabeApiServer}; @@ -192,7 +194,9 @@ where io.merge(Gear::new(client.clone()).into_rpc())?; - io.merge(RuntimeInfoApi::::new(client).into_rpc())?; + io.merge(RuntimeInfoApi::::new(client.clone()).into_rpc())?; + + io.merge(GearStakingRewards::new(client).into_rpc())?; Ok(io) } diff --git a/pallets/staking-rewards/rpc/Cargo.toml b/pallets/staking-rewards/rpc/Cargo.toml new file mode 100644 index 00000000000..9d1240ed403 --- /dev/null +++ b/pallets/staking-rewards/rpc/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pallet-gear-staking-rewards-rpc" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage = "https://gear-tech.io" +repository = "https://github.com/gear-tech/gear" + +[dependencies] +jsonrpsee = { workspace = true, features = ["server", "macros"] } + +# Substrate packages +sp-api.workspace = true +sp-blockchain.workspace = true +sp-runtime.workspace = true + +# Local packages +pallet-gear-staking-rewards-rpc-runtime-api.workspace = true diff --git a/pallets/staking-rewards/rpc/runtime-api/Cargo.toml b/pallets/staking-rewards/rpc/runtime-api/Cargo.toml new file mode 100644 index 00000000000..5c9d8cb65e9 --- /dev/null +++ b/pallets/staking-rewards/rpc/runtime-api/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pallet-gear-staking-rewards-rpc-runtime-api" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage = "https://gear-tech.io" +repository = "https://github.com/gear-tech/gear" + +[dependencies] +sp-api.workspace = true +pallet-gear-staking-rewards.workspace = true + +[features] +default = ["std"] +std = [ + "sp-api/std", + "pallet-gear-staking-rewards/std", +] diff --git a/pallets/staking-rewards/rpc/runtime-api/src/lib.rs b/pallets/staking-rewards/rpc/runtime-api/src/lib.rs new file mode 100644 index 00000000000..04aebbf65da --- /dev/null +++ b/pallets/staking-rewards/rpc/runtime-api/src/lib.rs @@ -0,0 +1,28 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet_gear_staking_rewards::InflationInfo; + +sp_api::decl_runtime_apis! { + pub trait GearStakingRewardsApi { + /// Calculate token economics related data. + fn inflation_info() -> InflationInfo; + } +} diff --git a/pallets/staking-rewards/rpc/src/lib.rs b/pallets/staking-rewards/rpc/src/lib.rs new file mode 100644 index 00000000000..45267e0bfd9 --- /dev/null +++ b/pallets/staking-rewards/rpc/src/lib.rs @@ -0,0 +1,96 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program 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. + +// This program 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 this program. If not, see . + +//! RPC interface for the gear module. + +use jsonrpsee::{ + core::{Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; +pub use pallet_gear_staking_rewards_rpc_runtime_api::GearStakingRewardsApi as GearStakingRewardsRuntimeApi; +use pallet_gear_staking_rewards_rpc_runtime_api::InflationInfo; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::Block as BlockT; +use std::sync::Arc; + +#[rpc(server)] +pub trait GearStakingRewardsApi { + #[method(name = "stakingRewards_inflationInfo")] + fn query_inflation_info(&self, at: Option) -> RpcResult; +} + +/// Provides RPC methods to query token economics related data. +pub struct GearStakingRewards { + /// Shared reference to the client. + client: Arc, + _marker: std::marker::PhantomData

, +} + +impl GearStakingRewards { + /// Creates a new instance of the GearStakingRewards Rpc helper. + pub fn new(client: Arc) -> Self { + Self { + client, + _marker: Default::default(), + } + } +} + +/// Error type of this RPC api. +pub enum Error { + /// The transaction was not decodable. + DecodeError, + /// The call to runtime failed. + RuntimeError, +} + +impl From for i32 { + fn from(e: Error) -> i32 { + match e { + Error::RuntimeError => 1, + Error::DecodeError => 2, + } + } +} + +impl GearStakingRewardsApiServer<::Hash, InflationInfo> + for GearStakingRewards +where + Block: BlockT, + C: 'static + ProvideRuntimeApi + HeaderBackend, + C::Api: GearStakingRewardsRuntimeApi, +{ + fn query_inflation_info(&self, at: Option) -> RpcResult { + let api = self.client.runtime_api(); + let at_hash = at.unwrap_or_else(|| self.client.info().best_hash); + + fn map_err(err: impl std::fmt::Debug, desc: &'static str) -> JsonRpseeError { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + desc, + Some(format!("{err:?}")), + )) + .into() + } + + api.inflation_info(at_hash) + .map_err(|e| map_err(e, "Unable to query inflation info")) + } +} diff --git a/pallets/staking-rewards/src/lib.rs b/pallets/staking-rewards/src/lib.rs index a379e014e8b..039a0ee60df 100644 --- a/pallets/staking-rewards/src/lib.rs +++ b/pallets/staking-rewards/src/lib.rs @@ -59,6 +59,8 @@ use frame_support::{ PalletId, }; use pallet_staking::EraPayout; +use parity_scale_codec::{Decode, Encode}; +pub use scale_info::TypeInfo; use sp_runtime::{ traits::{AccountIdConversion, Saturating, StaticLookup}, PerThing, Perquintill, @@ -79,6 +81,16 @@ pub type NegativeImbalanceOf = <::Currency as Cu >>::NegativeImbalance; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +/// Token economics related details. +#[derive(Clone, Decode, Encode, Eq, PartialEq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug, serde::Deserialize, serde::Serialize))] +pub struct InflationInfo { + /// Inflation + pub inflation: Perquintill, + /// ROI + pub roi: Perquintill, +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -333,11 +345,39 @@ pub mod pallet { .saturating_sub(T::Currency::minimum_balance()) } + /// Return the current total stakeable tokens amount. + /// + /// This value is not calculated but rather updated manually in line with tokenomics model. pub fn total_stakeable_tokens() -> BalanceOf { // Should never be 0 but in theory could (Self::non_stakeable_share().left_from_one() * T::Currency::total_issuance()) .saturating_sub(Self::pool()) } + + /// Calculate actual infaltion and ROI parameters. + pub fn inflation_info() -> InflationInfo { + let total_staked = pallet_staking::Pallet::::eras_total_stake( + pallet_staking::Pallet::::current_era().unwrap_or(0), + ); + let total_issuance = T::Currency::total_issuance(); + + let (payout, _) = inflation::compute_total_payout( + total_staked, + Self::total_stakeable_tokens(), + total_issuance, + Self::ideal_staking_ratio(), + T::MinInflation::get(), + Self::target_inflation(), + T::Falloff::get(), + T::MaxROI::get(), + Perquintill::one(), + ); + + let inflation = Perquintill::from_rational(payout, total_issuance); + let roi = Perquintill::from_rational(payout, total_staked); + + InflationInfo { inflation, roi } + } } } diff --git a/runtime/gear/Cargo.toml b/runtime/gear/Cargo.toml index fdd6f2deb3a..c68acce6ee8 100644 --- a/runtime/gear/Cargo.toml +++ b/runtime/gear/Cargo.toml @@ -70,6 +70,8 @@ pallet-gear-payment.workspace = true pallet-gear-voucher.workspace = true pallet-gear-rpc-runtime-api.workspace = true runtime-primitives.workspace = true +pallet-gear-staking-rewards.workspace = true +pallet-gear-staking-rewards-rpc-runtime-api.workspace = true [build-dependencies] substrate-build-script-utils.workspace = true @@ -126,6 +128,8 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "pallet-gear-staking-rewards-rpc-runtime-api/std", + "pallet-gear-staking-rewards/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/runtime/gear/src/lib.rs b/runtime/gear/src/lib.rs index 6094556ebd9..da93e6fce77 100644 --- a/runtime/gear/src/lib.rs +++ b/runtime/gear/src/lib.rs @@ -806,4 +806,10 @@ impl_runtime_apis_plus_common! { Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() } } + + impl pallet_gear_staking_rewards_rpc_runtime_api::GearStakingRewardsApi for Runtime { + fn inflation_info() -> pallet_gear_staking_rewards::InflationInfo { + unimplemented!(); + } + } } diff --git a/runtime/vara/Cargo.toml b/runtime/vara/Cargo.toml index f179c2bd346..40e450428e2 100644 --- a/runtime/vara/Cargo.toml +++ b/runtime/vara/Cargo.toml @@ -97,6 +97,7 @@ pallet-gear-payment.workspace = true pallet-gear-staking-rewards.workspace = true pallet-gear-voucher.workspace = true pallet-gear-rpc-runtime-api.workspace = true +pallet-gear-staking-rewards-rpc-runtime-api.workspace = true runtime-primitives.workspace = true [dev-dependencies] @@ -143,6 +144,7 @@ std = [ "pallet-gear-program/std", "pallet-gear-staking-rewards/std", "pallet-gear-rpc-runtime-api/std", + "pallet-gear-staking-rewards-rpc-runtime-api/std", "pallet-grandpa/std", "pallet-identity/std", "pallet-im-online/std", diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index fee12da3048..f0446824539 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -1382,4 +1382,10 @@ impl_runtime_apis_plus_common! { Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() } } + + impl pallet_gear_staking_rewards_rpc_runtime_api::GearStakingRewardsApi for Runtime { + fn inflation_info() -> pallet_gear_staking_rewards::InflationInfo { + StakingRewards::inflation_info() + } + } }