diff --git a/Cargo.lock b/Cargo.lock index 4bec29fb..dabec43e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9714,6 +9714,30 @@ dependencies = [ "tangle-primitives", ] +[[package]] +name = "pallet-rewards-rpc" +version = "1.2.5" +dependencies = [ + "jsonrpsee 0.23.2", + "pallet-rewards-rpc-runtime-api", + "parity-scale-codec", + "sp-api", + "sp-blockchain", + "sp-runtime", + "tangle-primitives", +] + +[[package]] +name = "pallet-rewards-rpc-runtime-api" +version = "1.2.5" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-runtime", + "sp-std", + "tangle-primitives", +] + [[package]] name = "pallet-scheduler" version = "38.0.0" @@ -15770,6 +15794,7 @@ dependencies = [ "log", "pallet-airdrop-claims", "pallet-im-online", + "pallet-rewards-rpc", "pallet-services-rpc", "pallet-transaction-payment", "pallet-transaction-payment-rpc", @@ -15945,6 +15970,7 @@ dependencies = [ "pallet-preimage", "pallet-proxy", "pallet-rewards", + "pallet-rewards-rpc-runtime-api", "pallet-scheduler", "pallet-services", "pallet-services-rpc-runtime-api", @@ -16076,6 +16102,7 @@ dependencies = [ "pallet-preimage", "pallet-proxy", "pallet-rewards", + "pallet-rewards-rpc-runtime-api", "pallet-scheduler", "pallet-services", "pallet-services-rpc-runtime-api", diff --git a/Cargo.toml b/Cargo.toml index b61ad255..f9822470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,8 @@ members = [ "pallets/*", "pallets/services/rpc", "pallets/services/rpc/runtime-api", + "pallets/rewards/rpc", + "pallets/rewards/rpc/runtime-api", "pallets/tangle-lst/benchmarking", "pallets/multi-asset-delegation/fuzzer", "precompiles/pallet-democracy", @@ -127,6 +129,8 @@ pallet-multi-asset-delegation = { path = "pallets/multi-asset-delegation", defau pallet-tangle-lst-benchmarking = { path = "pallets/tangle-lst/benchmarking", default-features = false } pallet-oracle = { path = "pallets/oracle", default-features = false } pallet-rewards = { path = "pallets/rewards", default-features = false } +pallet-rewards-rpc-runtime-api = { path = "pallets/rewards/rpc/runtime-api", default-features = false } +pallet-rewards-rpc = { path = "pallets/rewards/rpc" } k256 = { version = "0.13.3", default-features = false } p256 = { version = "0.13.2", default-features = false } diff --git a/node/Cargo.toml b/node/Cargo.toml index 0d035f98..41b47f03 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -62,6 +62,7 @@ sp-keystore = { workspace = true, features = ["std"] } sp-runtime = { workspace = true, features = ["std"] } sp-timestamp = { workspace = true, features = ["std"] } pallet-services-rpc = { workspace = true } +pallet-rewards-rpc = { workspace = true } sp-consensus-grandpa = { workspace = true } sp-offchain = { workspace = true } pallet-airdrop-claims = { workspace = true } diff --git a/node/src/rpc/mod.rs b/node/src/rpc/mod.rs index 620b7205..f3afb2b5 100644 --- a/node/src/rpc/mod.rs +++ b/node/src/rpc/mod.rs @@ -123,6 +123,7 @@ where AccountId, AssetId, >, + C::Api: pallet_rewards_rpc::RewardsRuntimeApi, C::Api: fp_rpc::ConvertTransactionRuntimeApi, C::Api: fp_rpc::EthereumRuntimeRPCApi, C::Api: rpc_primitives_debug::DebugRuntimeApi, @@ -141,6 +142,7 @@ where B::State: sc_client_api::backend::StateBackend, CIDP: sp_inherents::CreateInherentDataProviders + Send + Sync + 'static, { + use pallet_rewards_rpc::{RewardsApiServer, RewardsClient}; use pallet_services_rpc::{ServicesApiServer, ServicesClient}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use sc_consensus_babe_rpc::{Babe, BabeApiServer}; @@ -161,6 +163,7 @@ where io.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge(ServicesClient::new(client.clone()).into_rpc())?; + io.merge(RewardsClient::new(client.clone()).into_rpc())?; if let Some(babe) = babe { let BabeDeps { babe_worker_handle, keystore } = babe; diff --git a/pallets/rewards/rpc/Cargo.toml b/pallets/rewards/rpc/Cargo.toml new file mode 100644 index 00000000..b57d233b --- /dev/null +++ b/pallets/rewards/rpc/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pallet-rewards-rpc" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +jsonrpsee = { workspace = true, features = ["client-core", "server", "macros"] } +pallet-rewards-rpc-runtime-api = { path = "./runtime-api", default-features = false } +parity-scale-codec = { workspace = true } +sp-api = { workspace = true } +sp-blockchain = { workspace = true } +sp-runtime = { workspace = true } +tangle-primitives = { workspace = true } diff --git a/pallets/rewards/rpc/runtime-api/Cargo.toml b/pallets/rewards/rpc/runtime-api/Cargo.toml new file mode 100644 index 00000000..9a687660 --- /dev/null +++ b/pallets/rewards/rpc/runtime-api/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pallet-rewards-rpc-runtime-api" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { version = "3.6.12", default-features = false, features = ["derive"] } +sp-api = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +tangle-primitives = { workspace = true, default-features = false } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "sp-api/std", + "sp-runtime/std", + "tangle-primitives/std", + "sp-std/std", +] diff --git a/pallets/rewards/rpc/runtime-api/src/lib.rs b/pallets/rewards/rpc/runtime-api/src/lib.rs new file mode 100644 index 00000000..73ba7567 --- /dev/null +++ b/pallets/rewards/rpc/runtime-api/src/lib.rs @@ -0,0 +1,48 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle 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. +// +// Tangle 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 Tangle. If not, see . + +//! Runtime API definition for rewards pallet. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::type_complexity)] +use parity_scale_codec::Codec; +use sp_runtime::{traits::MaybeDisplay, Serialize}; + +pub type BlockNumberOf = + <::HeaderT as sp_runtime::traits::Header>::Number; + +sp_api::decl_runtime_apis! { + pub trait RewardsApi + where + AccountId: Codec + MaybeDisplay + Serialize, + AssetId: Codec + MaybeDisplay + Serialize, + Balance: Codec + MaybeDisplay + Serialize, + { + /// Query all the rewards that this operator is providing along with their blueprints. + /// + /// ## Arguments + /// - `operator`: The operator account id. + /// ## Return + /// - [`RpcRewardsWithBlueprint`]: A list of rewards with their blueprints. + fn query_user_rewards( + account_id: AccountId, + asset_id: tangle_primitives::services::Asset + ) -> Result< + Balance, + sp_runtime::DispatchError, + >; + } +} diff --git a/pallets/rewards/rpc/src/lib.rs b/pallets/rewards/rpc/src/lib.rs new file mode 100644 index 00000000..d1dcac67 --- /dev/null +++ b/pallets/rewards/rpc/src/lib.rs @@ -0,0 +1,110 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2024 Tangle Foundation. +// +// Tangle 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. +// +// Tangle 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 Tangle. If not, see . + +#![allow(clippy::unnecessary_mut_passed)] +#![allow(clippy::type_complexity)] + +use jsonrpsee::{ + core::RpcResult, + proc_macros::rpc, + types::error::{ErrorObject, ErrorObjectOwned}, +}; +use parity_scale_codec::Codec; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::{ + traits::{Block as BlockT, MaybeDisplay}, + Serialize, +}; +use std::sync::Arc; +use tangle_primitives::Balance; + +pub use pallet_rewards_rpc_runtime_api::RewardsApi as RewardsRuntimeApi; + +/// RewardsClient RPC methods. +#[rpc(client, server)] +pub trait RewardsApi +where + AccountId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, + AssetId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, +{ + #[method(name = "rewards_queryUserRewards")] + fn query_user_rewards( + &self, + account_id: AccountId, + asset_id: tangle_primitives::services::Asset, + at: Option, + ) -> RpcResult; +} + +/// Provides RPC methods to query a dispatchable's class, weight and fee. +pub struct RewardsClient { + /// Shared reference to the client. + client: Arc, + _marker: std::marker::PhantomData

, +} + +impl RewardsClient { + /// Creates a new instance of the RewardsClient Rpc helper. + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } + } +} + +impl RewardsApiServer<::Hash, AccountId, AssetId> + for RewardsClient +where + Block: BlockT, + AccountId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, + AssetId: Codec + MaybeDisplay + core::fmt::Debug + Send + Sync + 'static + Serialize, + C: HeaderBackend + ProvideRuntimeApi + Send + Sync + 'static, + C::Api: RewardsRuntimeApi, +{ + fn query_user_rewards( + &self, + account_id: AccountId, + asset_id: tangle_primitives::services::Asset, + at: Option<::Hash>, + ) -> RpcResult { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + match api.query_user_rewards(at, account_id, asset_id) { + Ok(Ok(res)) => Ok(res), + Ok(Err(e)) => Err(map_err(format!("{:?}", e), "Unable to query user rewards")), + Err(e) => Err(map_err(format!("{:?}", e), "Unable to query user rewards")), + } + } +} + +fn map_err(error: impl ToString, desc: &'static str) -> ErrorObjectOwned { + ErrorObject::owned(Error::RuntimeError.into(), desc, Some(error.to_string())) +} + +/// Error type of this RPC api. +#[derive(Debug)] +pub enum Error { + /// The call to runtime failed. + RuntimeError, +} + +impl From for i32 { + fn from(e: Error) -> i32 { + match e { + Error::RuntimeError => 1, + } + } +} diff --git a/pallets/rewards/src/functions.rs b/pallets/rewards/src/functions.rs index 3a83b1b0..6c19531b 100644 --- a/pallets/rewards/src/functions.rs +++ b/pallets/rewards/src/functions.rs @@ -85,6 +85,37 @@ impl Pallet { Ok(()) } + pub fn calculate_rewards( + account_id: &T::AccountId, + asset: Asset, + ) -> Result<(BalanceOf, BalanceOf), DispatchError> { + // find the vault for the asset id + // if the asset is not in a reward vault, do nothing + let vault_id = + AssetLookupRewardVaults::::get(asset).ok_or(Error::::AssetNotInVault)?; + + // lets read the user deposits from the delegation manager + let deposit_info = + T::DelegationManager::get_user_deposit_with_locks(&account_id.clone(), asset) + .ok_or(Error::::NoRewardsAvailable)?; + + // read the asset reward config + let reward_config = RewardConfigStorage::::get(vault_id); + + // find the total vault score + let total_score = TotalRewardVaultScore::::get(vault_id); + + // get the users last claim + let last_claim = UserClaimedReward::::get(account_id, vault_id); + + Self::calculate_deposit_rewards_with_lock_multiplier( + total_score, + deposit_info, + reward_config.ok_or(Error::::RewardConfigNotFound)?, + last_claim, + ) + } + /// Calculates and pays out rewards for a given account and asset. /// /// This function orchestrates the reward calculation and payout process by: @@ -116,27 +147,7 @@ impl Pallet { let vault_id = AssetLookupRewardVaults::::get(asset).ok_or(Error::::AssetNotInVault)?; - // lets read the user deposits from the delegation manager - let deposit_info = - T::DelegationManager::get_user_deposit_with_locks(&account_id.clone(), asset) - .ok_or(Error::::NoRewardsAvailable)?; - - // read the asset reward config - let reward_config = RewardConfigStorage::::get(vault_id); - - // find the total vault score - let total_score = TotalRewardVaultScore::::get(vault_id); - - // get the users last claim - let last_claim = UserClaimedReward::::get(account_id, vault_id); - - let (total_rewards, rewards_to_be_paid) = - Self::calculate_deposit_rewards_with_lock_multiplier( - total_score, - deposit_info, - reward_config.ok_or(Error::::RewardConfigNotFound)?, - last_claim, - )?; + let (total_rewards, rewards_to_be_paid) = Self::calculate_rewards(account_id, asset)?; // mint new TNT rewards and trasnfer to the user let _ = T::Currency::deposit_creating(account_id, rewards_to_be_paid); diff --git a/runtime/mainnet/Cargo.toml b/runtime/mainnet/Cargo.toml index 6aadcbbc..d3d9535a 100644 --- a/runtime/mainnet/Cargo.toml +++ b/runtime/mainnet/Cargo.toml @@ -88,6 +88,7 @@ pallet-tangle-lst = { workspace = true } pallet-airdrop-claims = { workspace = true } pallet-services = { workspace = true } pallet-services-rpc-runtime-api = { workspace = true } +pallet-rewards-rpc-runtime-api = { workspace = true } tangle-primitives = { workspace = true, features = ["verifying"] } tangle-crypto-primitives = { workspace = true } pallet-multi-asset-delegation = { workspace = true } @@ -231,6 +232,7 @@ std = [ "pallet-services/std", "pallet-multi-asset-delegation/std", "pallet-services-rpc-runtime-api/std", + "pallet-rewards-rpc-runtime-api/std", "pallet-rewards/std", # Frontier diff --git a/runtime/mainnet/src/lib.rs b/runtime/mainnet/src/lib.rs index f8071633..8feef54f 100644 --- a/runtime/mainnet/src/lib.rs +++ b/runtime/mainnet/src/lib.rs @@ -1922,6 +1922,16 @@ impl_runtime_apis! { } } + impl pallet_rewards_rpc_runtime_api::RewardsApi for Runtime { + fn query_user_rewards( + account_id: AccountId, + asset_id: tangle_primitives::services::Asset, + ) -> Result { + let (rewards, _) = Rewards::calculate_rewards(&account_id, asset_id)?; + Ok(rewards) + } + } + impl fg_primitives::GrandpaApi for Runtime { fn grandpa_authorities() -> GrandpaAuthorityList { Grandpa::grandpa_authorities() diff --git a/runtime/testnet/Cargo.toml b/runtime/testnet/Cargo.toml index 48aef3de..38798cf5 100644 --- a/runtime/testnet/Cargo.toml +++ b/runtime/testnet/Cargo.toml @@ -89,12 +89,13 @@ pallet-utility = { workspace = true } pallet-multisig = { workspace = true } pallet-vesting = { workspace = true } pallet-tangle-lst = { workspace = true } -pallet-rewards = { workspace = true } # Tangle dependencies pallet-airdrop-claims = { workspace = true } pallet-services = { workspace = true } pallet-services-rpc-runtime-api = { workspace = true } +pallet-rewards = { workspace = true } +pallet-rewards-rpc-runtime-api = { workspace = true } tangle-primitives = { workspace = true, features = ["verifying"] } tangle-crypto-primitives = { workspace = true } pallet-multi-asset-delegation = { workspace = true } @@ -257,6 +258,7 @@ std = [ "pallet-multi-asset-delegation/std", "pallet-tangle-lst/std", "pallet-services-rpc-runtime-api/std", + "pallet-rewards-rpc-runtime-api/std", "pallet-rewards/std", # Frontier diff --git a/runtime/testnet/src/lib.rs b/runtime/testnet/src/lib.rs index 645fc1cc..d1416499 100644 --- a/runtime/testnet/src/lib.rs +++ b/runtime/testnet/src/lib.rs @@ -1582,6 +1582,16 @@ impl_runtime_apis! { } } + impl pallet_rewards_rpc_runtime_api::RewardsApi for Runtime { + fn query_user_rewards( + account_id: AccountId, + asset_id: tangle_primitives::services::Asset, + ) -> Result { + let (rewards, _) = Rewards::calculate_rewards(&account_id, asset_id)?; + Ok(rewards) + } + } + impl fp_rpc::EthereumRuntimeRPCApi for Runtime { fn chain_id() -> u64 { ::ChainId::get()