Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add rpc to query user rewards #878

Merged
merged 3 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
3 changes: 3 additions & 0 deletions node/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ where
AccountId,
AssetId,
>,
C::Api: pallet_rewards_rpc::RewardsRuntimeApi<Block, AccountId, AssetId, Balance>,
C::Api: fp_rpc::ConvertTransactionRuntimeApi<Block>,
C::Api: fp_rpc::EthereumRuntimeRPCApi<Block>,
C::Api: rpc_primitives_debug::DebugRuntimeApi<Block>,
Expand All @@ -141,6 +142,7 @@ where
B::State: sc_client_api::backend::StateBackend<sp_runtime::traits::BlakeTwo256>,
CIDP: sp_inherents::CreateInherentDataProviders<Block, ()> + 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};
Expand All @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions pallets/rewards/rpc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
28 changes: 28 additions & 0 deletions pallets/rewards/rpc/runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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",
]
48 changes: 48 additions & 0 deletions pallets/rewards/rpc/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

//! 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<Block> =
<<Block as sp_runtime::traits::HeaderProvider>::HeaderT as sp_runtime::traits::Header>::Number;

sp_api::decl_runtime_apis! {
pub trait RewardsApi<AccountId, AssetId, Balance>
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<AssetId>
) -> Result<
Balance,
sp_runtime::DispatchError,
>;
}
}
110 changes: 110 additions & 0 deletions pallets/rewards/rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

#![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<BlockHash, AccountId, AssetId>
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<AssetId>,
at: Option<BlockHash>,
) -> RpcResult<Balance>;
}

/// Provides RPC methods to query a dispatchable's class, weight and fee.
pub struct RewardsClient<C, P> {
/// Shared reference to the client.
client: Arc<C>,
_marker: std::marker::PhantomData<P>,
}

impl<C, P> RewardsClient<C, P> {
/// Creates a new instance of the RewardsClient Rpc helper.
pub fn new(client: Arc<C>) -> Self {
Self { client, _marker: Default::default() }
}
}

impl<C, Block, AccountId, AssetId> RewardsApiServer<<Block as BlockT>::Hash, AccountId, AssetId>
for RewardsClient<C, Block>
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<Block> + ProvideRuntimeApi<Block> + Send + Sync + 'static,
C::Api: RewardsRuntimeApi<Block, AccountId, AssetId, Balance>,
{
fn query_user_rewards(
&self,
account_id: AccountId,
asset_id: tangle_primitives::services::Asset<AssetId>,
at: Option<<Block as BlockT>::Hash>,
) -> RpcResult<Balance> {
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<Error> for i32 {
fn from(e: Error) -> i32 {
match e {
Error::RuntimeError => 1,
}
}
}
53 changes: 32 additions & 21 deletions pallets/rewards/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,37 @@ impl<T: Config> Pallet<T> {
Ok(())
}

pub fn calculate_rewards(
account_id: &T::AccountId,
asset: Asset<T::AssetId>,
) -> Result<(BalanceOf<T>, BalanceOf<T>), DispatchError> {
// find the vault for the asset id
// if the asset is not in a reward vault, do nothing
let vault_id =
AssetLookupRewardVaults::<T>::get(asset).ok_or(Error::<T>::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::<T>::NoRewardsAvailable)?;

// read the asset reward config
let reward_config = RewardConfigStorage::<T>::get(vault_id);

// find the total vault score
let total_score = TotalRewardVaultScore::<T>::get(vault_id);

// get the users last claim
let last_claim = UserClaimedReward::<T>::get(account_id, vault_id);

Self::calculate_deposit_rewards_with_lock_multiplier(
total_score,
deposit_info,
reward_config.ok_or(Error::<T>::RewardConfigNotFound)?,
last_claim,
)
}

/// Calculates and pays out rewards for a given account and asset.
///
/// This function orchestrates the reward calculation and payout process by:
Expand Down Expand Up @@ -116,27 +147,7 @@ impl<T: Config> Pallet<T> {
let vault_id =
AssetLookupRewardVaults::<T>::get(asset).ok_or(Error::<T>::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::<T>::NoRewardsAvailable)?;

// read the asset reward config
let reward_config = RewardConfigStorage::<T>::get(vault_id);

// find the total vault score
let total_score = TotalRewardVaultScore::<T>::get(vault_id);

// get the users last claim
let last_claim = UserClaimedReward::<T>::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::<T>::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);
Expand Down
2 changes: 2 additions & 0 deletions runtime/mainnet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading