From 0f47a7bacb96e18a930c425d91bba3b8264502a6 Mon Sep 17 00:00:00 2001 From: yxf Date: Wed, 11 Nov 2020 16:54:05 +0800 Subject: [PATCH 1/2] Add staking rpc api --- Cargo.lock | 36 +++++++++++-- node/Cargo.toml | 1 + node/src/rpc.rs | 6 +++ pallets/staking/Cargo.toml | 3 +- pallets/staking/rpc/Cargo.toml | 21 ++++++++ pallets/staking/rpc/runtime-api/Cargo.toml | 20 +++++++ pallets/staking/rpc/runtime-api/src/lib.rs | 12 +++++ pallets/staking/rpc/src/lib.rs | 63 ++++++++++++++++++++++ pallets/staking/src/lib.rs | 22 +++++++- primitives/Cargo.toml | 2 +- runtime/Cargo.toml | 3 +- runtime/src/lib.rs | 14 +++++ 12 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 pallets/staking/rpc/Cargo.toml create mode 100644 pallets/staking/rpc/runtime-api/Cargo.toml create mode 100644 pallets/staking/rpc/runtime-api/src/lib.rs create mode 100644 pallets/staking/rpc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ddc17cf..25f9089 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3757,11 +3757,37 @@ dependencies = [ "frame-system", "log", "parity-scale-codec", + "sp-api", "sp-core", "sp-runtime", "sp-std", ] +[[package]] +name = "pallet-staking-rpc" +version = "0.1.0" +dependencies = [ + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "pallet-staking-rpc-runtime-api", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", +] + +[[package]] +name = "pallet-staking-rpc-runtime-api" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + [[package]] name = "pallet-sudo" version = "2.0.0" @@ -5881,18 +5907,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.115" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.115" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" dependencies = [ "proc-macro2", "quote", @@ -7471,6 +7497,7 @@ dependencies = [ "hex-literal 0.2.1", "jsonrpc-core", "log", + "pallet-staking-rpc", "pallet-transaction-payment-rpc", "sc-basic-authorship", "sc-cli", @@ -7523,6 +7550,7 @@ dependencies = [ "pallet-rewards", "pallet-session", "pallet-staking", + "pallet-staking-rpc-runtime-api", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", diff --git a/node/Cargo.toml b/node/Cargo.toml index d60cb57..c43929d 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -53,6 +53,7 @@ frame-benchmarking-cli = { version = "2.0.0" } uart-runtime = { path = "../runtime" } +pallet-staking-rpc = { path = "../pallets/staking/rpc" } [build-dependencies] substrate-build-script-utils = { version = "2.0.0" } diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 30997cb..d1add08 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -34,11 +34,13 @@ pub fn create_full( C: Send + Sync + 'static, C::Api: substrate_frame_rpc_system::AccountNonceApi, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, + C::Api: pallet_staking_rpc::StakingRuntimeApi, C::Api: BlockBuilder, P: TransactionPool + 'static, { use substrate_frame_rpc_system::{FullSystem, SystemApi}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; + use pallet_staking_rpc::{Staking, StakingApi}; let mut io = jsonrpc_core::IoHandler::default(); let FullDeps { @@ -55,6 +57,10 @@ pub fn create_full( TransactionPaymentApi::to_delegate(TransactionPayment::new(client.clone())) ); + io.extend_with( + StakingApi::to_delegate(Staking::new(client.clone())) + ); + // Extend this RPC with a custom API by using the following syntax. // `YourRpcStruct` should have a reference to a client, which is needed // to call into the runtime. diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml index c78b736..353c52c 100644 --- a/pallets/staking/Cargo.toml +++ b/pallets/staking/Cargo.toml @@ -12,4 +12,5 @@ frame-support = { version = "2.0.0", default-features = false } frame-system = { version = "2.0.0", default-features = false } sp-std = { version = "2.0.0", default-features = false } sp-core = { version = "2.0.0", default-features = false } -sp-runtime = { version = "2.0.0", default-features = false } \ No newline at end of file +sp-runtime = { version = "2.0.0", default-features = false } +sp-api = { version = "2.0.0", default-features = false } \ No newline at end of file diff --git a/pallets/staking/rpc/Cargo.toml b/pallets/staking/rpc/Cargo.toml new file mode 100644 index 0000000..f3949f0 --- /dev/null +++ b/pallets/staking/rpc/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pallet-staking-rpc" +version = "0.1.0" +authors = ["yxf "] +edition = "2018" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.3.1" } +jsonrpc-core = "15.0.0" +jsonrpc-core-client = "15.0.0" +jsonrpc-derive = "15.0.0" +sp-core = { version = "2.0.0" } +sp-rpc = { version = "2.0.0" } +serde = { version = "1.0.101", features = ["derive"] } +sp-runtime = { version = "2.0.0" } +sp-api = { version = "2.0.0" } +sp-blockchain = { version = "2.0.0" } +pallet-staking-rpc-runtime-api = { version = "0.1.0", path = "./runtime-api" } diff --git a/pallets/staking/rpc/runtime-api/Cargo.toml b/pallets/staking/rpc/runtime-api/Cargo.toml new file mode 100644 index 0000000..f1d7183 --- /dev/null +++ b/pallets/staking/rpc/runtime-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pallet-staking-rpc-runtime-api" +version = "0.1.0" +authors = ["yxf "] +edition = "2018" + + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +sp-api = { version = "2.0.0", default-features = false } +codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false } + +[features] +default = ["std"] +std = [ + "sp-api/std", + "codec/std", +] diff --git a/pallets/staking/rpc/runtime-api/src/lib.rs b/pallets/staking/rpc/runtime-api/src/lib.rs new file mode 100644 index 0000000..4be79ee --- /dev/null +++ b/pallets/staking/rpc/runtime-api/src/lib.rs @@ -0,0 +1,12 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +sp_api::decl_runtime_apis! { + pub trait StakingApi where + AccountId: codec::Codec, + Balance: codec::Codec + { + fn staking_module_account_id() -> AccountId; + fn pool_account_id(id: u32) -> AccountId; + fn pending_rewards(pool_id: u32, account_id: AccountId) -> Balance; + } +} \ No newline at end of file diff --git a/pallets/staking/rpc/src/lib.rs b/pallets/staking/rpc/src/lib.rs new file mode 100644 index 0000000..fdd9cf3 --- /dev/null +++ b/pallets/staking/rpc/src/lib.rs @@ -0,0 +1,63 @@ +use std::sync::Arc; +use codec::{Codec, HasCompact}; +use jsonrpc_derive::rpc; +use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; +use sp_blockchain::HeaderBackend; +use sp_runtime::{generic::BlockId, traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}}; +use sp_api::ProvideRuntimeApi; +pub use pallet_staking_rpc_runtime_api::StakingApi as StakingRuntimeApi; + +#[rpc] +pub trait StakingApi { + #[rpc(name = "staking_pendingRewards")] + fn pending_rewards( + &self, + account_id: AccountId + ) -> Result; + + #[rpc(name = "staking_poolAccountId")] + fn pool_account_id(&self) -> Result; +} + +pub struct Staking { + client: Arc, + _marker: std::marker::PhantomData

, +} + +impl Staking { + pub fn new(client: Arc) -> Self { + Staking { client, _marker: Default::default() } + } +} + + +impl StakingApi + for Staking +where + Block: BlockT, + C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, + C::Api: StakingRuntimeApi, + Balance: Codec + MaybeDisplay + MaybeFromStr + HasCompact, + AccountId: Codec +{ + + fn pending_rewards(&self, account_id: AccountId) -> Result { + let api = self.client.runtime_api(); + let at = BlockId::hash(self.client.info().best_hash); + api.pending_rewards(&at, 0, account_id).map_err(|e| RpcError { + code: ErrorCode::InternalError, + message: "Unable to query pending rewards".into(), + data: Some(format!("{:?}", e).into()), + }) + } + + fn pool_account_id(&self) -> Result { + let api = self.client.runtime_api(); + let at = BlockId::hash(self.client.info().best_hash); + api.staking_module_account_id(&at).map_err(|e| RpcError { + code: ErrorCode::InternalError, + message: "Unable to query pool account_id".into(), + data: Some(format!("{:?}", e).into()), + }) + } +} \ No newline at end of file diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index 2eef347..d34cb16 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -20,7 +20,6 @@ use sp_runtime::{ use codec::{Encode, Decode}; use sp_std::prelude::*; - pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub type AccountId = ::AccountId; @@ -291,5 +290,26 @@ impl Module { // TODO: adjust rewards of staking T::RewardPerBlock::get() } + + /// Pending rewards of staker + pub fn pending_rewards(pool_id: T::Id, account_id: T::AccountId) -> BalanceOf { + let staker_key = (pool_id, account_id); + let staker = Self::stakers(&staker_key); + let pool = Self::pools(pool_id); + + let block_number = frame_system::Module::::block_number(); + let reward_per_block: BalanceOf = Self::reward_per_block(); + let factor: BalanceOf = T::AmpFactor::get(); + + let delta = block_number.saturating_sub(pool.last_reward_block); + let blocks: BalanceOf = T::ConvertNumberToBalance::convert(delta); + let rewards = reward_per_block.saturating_mul(blocks); + if pool.total_balance == Zero::zero() { + return Zero::zero(); + } + let rewards_per_share = rewards.saturating_mul(factor) / pool.total_balance; + let acc_rewards_per_share = pool.acc_rewards_per_share.saturating_add(rewards_per_share); + staker.amount.saturating_mul(acc_rewards_per_share) / factor - staker.debt + } } \ No newline at end of file diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 36eacbc..54f79ec 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -21,4 +21,4 @@ std = [ "sp-runtime/std", "sp-core/std", "sp-std/std", -] +] \ No newline at end of file diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 753c08b..3f9b5d5 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -42,9 +42,10 @@ pallet-collective = { version = "2.0.0", default-features = false } pallet-treasury = { version = "2.0.0", default-features = false } pallet-identity = { version = "2.0.0", default-features = false } -# Used for the node template's RPCs +# Used for RPCs frame-system-rpc-runtime-api = { version = "2.0.0", default-features = false } pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0", default-features = false } +pallet-staking-rpc-runtime-api = { version = "0.1.0", default-features = false, path = "../pallets/staking/rpc/runtime-api"} # Uni-Arts pallets pallet-certificate = { path = "../pallets/certificate", default-features = false } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b75bc1c..f2a6064 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -773,4 +773,18 @@ impl_runtime_apis! { TransactionPayment::query_info(uxt, len) } } + + impl pallet_staking_rpc_runtime_api::StakingApi for Runtime { + fn staking_module_account_id() -> AccountId { + Staking::account_id() + } + + fn pool_account_id(id: u32) -> AccountId { + Staking::pool_account_id(id) + } + + fn pending_rewards(pool_id: u32, account_id: AccountId) -> Balance { + Staking::pending_rewards(pool_id, account_id) + } + } } From f3db8b84eca79b0304937f9b5f0315717a568a0e Mon Sep 17 00:00:00 2001 From: yxf Date: Wed, 11 Nov 2020 18:16:16 +0800 Subject: [PATCH 2/2] Fixed Balance json serde --- Cargo.lock | 4 ++++ pallets/staking/rpc/runtime-api/Cargo.toml | 13 +++++++++-- pallets/staking/rpc/runtime-api/src/lib.rs | 26 ++++++++++++++++++++++ pallets/staking/rpc/src/lib.rs | 18 +++++++++------ 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25f9089..0e5abea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3784,8 +3784,12 @@ dependencies = [ name = "pallet-staking-rpc-runtime-api" version = "0.1.0" dependencies = [ + "frame-support", "parity-scale-codec", + "serde", "sp-api", + "sp-runtime", + "sp-std", ] [[package]] diff --git a/pallets/staking/rpc/runtime-api/Cargo.toml b/pallets/staking/rpc/runtime-api/Cargo.toml index f1d7183..bd1d682 100644 --- a/pallets/staking/rpc/runtime-api/Cargo.toml +++ b/pallets/staking/rpc/runtime-api/Cargo.toml @@ -9,12 +9,21 @@ edition = "2018" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.101", optional = true, features = ["derive"] } sp-api = { version = "2.0.0", default-features = false } -codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false } +sp-std = { version = "2.0.0", default-features = false } +sp-runtime = { version = "2.0.0", default-features = false } +frame-support = { version = "2.0.0", default-features = false } + [features] default = ["std"] std = [ + "serde", "sp-api/std", "codec/std", -] + "sp-std/std", + "sp-runtime/std", + "frame-support/std", +] \ No newline at end of file diff --git a/pallets/staking/rpc/runtime-api/src/lib.rs b/pallets/staking/rpc/runtime-api/src/lib.rs index 4be79ee..e9dbd77 100644 --- a/pallets/staking/rpc/runtime-api/src/lib.rs +++ b/pallets/staking/rpc/runtime-api/src/lib.rs @@ -1,5 +1,31 @@ #![cfg_attr(not(feature = "std"), no_std)] +use sp_std::prelude::*; +use codec::{Encode, Decode}; +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize, Serializer, Deserializer}; + +#[derive(Eq, PartialEq, Encode, Decode, Default)] +#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] +pub struct Reward( + #[cfg_attr(feature = "std", serde(bound(serialize = "Balance: std::fmt::Display")))] + #[cfg_attr(feature = "std", serde(serialize_with = "serialize_as_string"))] + #[cfg_attr(feature = "std", serde(bound(deserialize = "Balance: std::str::FromStr")))] + #[cfg_attr(feature = "std", serde(deserialize_with = "deserialize_from_string"))] + pub Balance +); + +#[cfg(feature = "std")] +fn serialize_as_string(t: &T, serializer: S) -> Result { + serializer.serialize_str(&t.to_string()) +} + +#[cfg(feature = "std")] +fn deserialize_from_string<'de, D: Deserializer<'de>, T: std::str::FromStr>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + s.parse::().map_err(|_| serde::de::Error::custom("Parse from string failed")) +} + sp_api::decl_runtime_apis! { pub trait StakingApi where AccountId: codec::Codec, diff --git a/pallets/staking/rpc/src/lib.rs b/pallets/staking/rpc/src/lib.rs index fdd9cf3..70aa225 100644 --- a/pallets/staking/rpc/src/lib.rs +++ b/pallets/staking/rpc/src/lib.rs @@ -1,19 +1,20 @@ use std::sync::Arc; -use codec::{Codec, HasCompact}; +use codec::{Codec}; use jsonrpc_derive::rpc; use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; use sp_blockchain::HeaderBackend; use sp_runtime::{generic::BlockId, traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}}; use sp_api::ProvideRuntimeApi; +use pallet_staking_rpc_runtime_api::Reward; pub use pallet_staking_rpc_runtime_api::StakingApi as StakingRuntimeApi; #[rpc] -pub trait StakingApi { +pub trait StakingApi { #[rpc(name = "staking_pendingRewards")] fn pending_rewards( &self, account_id: AccountId - ) -> Result; + ) -> Result; #[rpc(name = "staking_poolAccountId")] fn pool_account_id(&self) -> Result; @@ -31,24 +32,27 @@ impl Staking { } -impl StakingApi +impl StakingApi> for Staking where Block: BlockT, C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, C::Api: StakingRuntimeApi, - Balance: Codec + MaybeDisplay + MaybeFromStr + HasCompact, + Balance: Codec + MaybeDisplay + MaybeFromStr + std::default::Default + std::fmt::Debug, AccountId: Codec { - fn pending_rewards(&self, account_id: AccountId) -> Result { + fn pending_rewards(&self, account_id: AccountId) -> Result> { let api = self.client.runtime_api(); let at = BlockId::hash(self.client.info().best_hash); api.pending_rewards(&at, 0, account_id).map_err(|e| RpcError { code: ErrorCode::InternalError, message: "Unable to query pending rewards".into(), data: Some(format!("{:?}", e).into()), - }) + }).map(|value| Reward(value)) + + // println!("balance = {:?}", balance); + // Ok(Reward { value: Default::default() }) } fn pool_account_id(&self) -> Result {