Skip to content

Commit

Permalink
Merge pull request #396 from Phoenix-Protocol-Group/revert-395-jakub/…
Browse files Browse the repository at this point in the history
…stake-rewards-optimization

Revert "Stake: Try to optimize the withdraw rewards function"
  • Loading branch information
ueco-jb authored Nov 22, 2024
2 parents 557b6cb + 024c759 commit 77742b0
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 520 deletions.
78 changes: 9 additions & 69 deletions contracts/stake/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use phoenix::ttl::{INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD};
use soroban_sdk::{
contract, contractimpl, contractmeta, log, map, panic_with_error, vec, Address, BytesN, Env,
IntoVal, Symbol, Val, Vec,
Vec,
};

use crate::{
distribution::{
calculate_pending_rewards, calculate_pending_rewards_chunked, get_reward_history,
get_total_staked_history, save_reward_history, save_total_staked_history,
calculate_pending_rewards, get_reward_history, get_total_staked_history,
save_reward_history, save_total_staked_history,
},
error::ContractError,
msg::{ConfigResponse, StakedResponse, WithdrawableReward, WithdrawableRewardsResponse},
Expand Down Expand Up @@ -56,8 +56,6 @@ pub trait StakingTrait {

fn withdraw_rewards(env: Env, sender: Address);

fn withdraw_rewards_chunks(env: Env, sender: Address, reward_token: Address, chunk_size: u32);

// QUERIES

fn query_config(env: Env) -> ConfigResponse;
Expand All @@ -72,13 +70,6 @@ pub trait StakingTrait {

fn query_withdrawable_rewards(env: Env, address: Address) -> WithdrawableRewardsResponse;

fn query_withdrawable_rewards_ch(
env: Env,
address: Address,
chunk_size: u32,
start_day: Option<u64>,
) -> WithdrawableRewardsResponse;

// fn query_distributed_rewards(env: Env, asset: Address) -> u128;

// fn query_undistributed_rewards(env: Env, asset: Address) -> u128;
Expand Down Expand Up @@ -277,45 +268,21 @@ impl StakingTrait for Staking {

let mut stakes = get_stakes(&env, &sender);

for asset in get_distributions(&env).iter() {
for asset in get_distributions(&env) {
let pending_reward = calculate_pending_rewards(&env, &asset, &stakes);
env.events()
.publish(("withdraw_rewards", "reward_token"), &asset);

let init_fn = Symbol::new(&env, "transfer");
let init_args: Vec<Val> =
(env.current_contract_address(), &sender, pending_reward).into_val(&env);
env.invoke_contract::<Val>(&asset, &init_fn, init_args);
token_contract::Client::new(&env, &asset).transfer(
&env.current_contract_address(),
&sender,
&pending_reward,
);
}
stakes.last_reward_time = env.ledger().timestamp();
save_stakes(&env, &sender, &stakes);
}

fn withdraw_rewards_chunks(env: Env, sender: Address, reward_token: Address, chunk_size: u32) {
env.storage()
.instance()
.extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

env.events().publish(("withdraw_rewards", "user"), &sender);

let mut stakes = get_stakes(&env, &sender);

// Calculate pending rewards in chunks
let (pending_rewards, last_reward_day) =
calculate_pending_rewards_chunked(&env, &reward_token, &stakes, chunk_size, None);

// Transfer pending rewards to the user
if pending_rewards > 0 {
let init_fn = Symbol::new(&env, "transfer");
let init_args: Vec<Val> =
(env.current_contract_address(), &sender, pending_rewards).into_val(&env);
env.invoke_contract::<Val>(&reward_token, &init_fn, init_args);
}

stakes.last_reward_time = last_reward_day;
save_stakes(&env, &sender, &stakes);
}

// QUERIES

fn query_config(env: Env) -> ConfigResponse {
Expand Down Expand Up @@ -393,33 +360,6 @@ impl StakingTrait for Staking {
WithdrawableRewardsResponse { rewards }
}

fn query_withdrawable_rewards_ch(
env: Env,
user: Address,
chunk_size: u32,
start_day: Option<u64>,
) -> WithdrawableRewardsResponse {
env.storage()
.instance()
.extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

let stakes = get_stakes(&env, &user);
let mut rewards = vec![&env];

// Iterate over all distributions and calculate withdrawable rewards
for asset in get_distributions(&env) {
let (pending_reward, _) =
calculate_pending_rewards_chunked(&env, &asset, &stakes, chunk_size, start_day);

rewards.push_back(WithdrawableReward {
reward_address: asset,
reward_amount: pending_reward as u128,
});
}

WithdrawableRewardsResponse { rewards }
}

// fn query_distributed_rewards(env: Env, asset: Address) -> u128 {
// let staking_rewards = find_stake_rewards_by_asset(&env, &asset).unwrap();
// let unds_rew_fn_arg: Val = asset.into_val(&env);
Expand Down
101 changes: 25 additions & 76 deletions contracts/stake/src/distribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use soroban_sdk::{contracttype, Address, Env, Map};
use crate::storage::BondingInfo;
use phoenix::ttl::{PERSISTENT_BUMP_AMOUNT, PERSISTENT_LIFETIME_THRESHOLD};

pub const SECONDS_PER_DAY: u64 = 24 * 60 * 60;
const SECONDS_PER_DAY: u64 = 24 * 60 * 60;

#[derive(Clone)]
#[contracttype]
Expand Down Expand Up @@ -75,98 +75,47 @@ pub fn calculate_pending_rewards(
let current_timestamp = env.ledger().timestamp();
let last_reward_day = user_info.last_reward_time;

// Load reward history and total staked history
// Load reward history and total staked history from storage
let reward_history = get_reward_history(env, reward_token);
let total_staked_history = get_total_staked_history(env);

// Get the keys from the reward history map (which are the days)
let reward_keys = reward_history.keys();

let mut pending_rewards: i128 = 0;

// Find the first relevant reward day after `last_reward_day`
// Find the closest timestamp after last_reward_day
if let Some(first_relevant_day) = reward_keys.iter().find(|&day| day > last_reward_day) {
// Iterate over all relevant reward days
for reward_day in reward_keys
for staking_reward_day in reward_keys
.iter()
.skip_while(|&day| day < first_relevant_day)
.take_while(|&day| day <= current_timestamp)
{
// Get the daily reward and total staked for this day
if let (Some(daily_reward), Some(total_staked)) = (
reward_history.get(reward_day),
total_staked_history.get(reward_day),
reward_history.get(staking_reward_day),
total_staked_history.get(staking_reward_day),
) {
if total_staked == 0 {
continue; // Skip if nothing was staked
}

// Process each stake for the current reward day
for stake in user_info.stakes.iter() {
// Calculate the age of the stake in days
let stake_age_days =
((reward_day - stake.stake_timestamp) / SECONDS_PER_DAY).min(60);

// Determine the multiplier based on the stake age
let multiplier = Decimal::from_ratio(stake_age_days, 60);

// Calculate the user's share of the rewards
let user_share = (stake.stake as u128) * daily_reward / total_staked;

// Adjust the reward using the multiplier and add to the total
pending_rewards += (user_share as i128) * multiplier;
if total_staked > 0 {
// Calculate multiplier based on the age of each stake
for stake in user_info.stakes.iter() {
// Calculate the user's share of the total staked amount at the time
let user_share = stake.stake as u128 * daily_reward / total_staked;
let stake_age_days =
(staking_reward_day - stake.stake_timestamp) / SECONDS_PER_DAY;
let multiplier = if stake_age_days >= 60 {
Decimal::one()
} else {
Decimal::from_ratio(stake_age_days, 60)
};

// Apply the multiplier and accumulate the rewards
let adjusted_reward = user_share as i128 * multiplier;
pending_rewards += adjusted_reward;
}
}
}
}
}

pending_rewards
}

pub fn calculate_pending_rewards_chunked(
env: &Env,
reward_token: &Address,
user_info: &BondingInfo,
chunk_size: u32,
start_day: Option<u64>,
) -> (i128, u64) {
let current_timestamp = env.ledger().timestamp();
let last_claim_time = user_info.last_reward_time;

let reward_history = get_reward_history(env, reward_token);
let total_staked_history = get_total_staked_history(env);

let mut reward_keys = soroban_sdk::Vec::new(env);

for day in reward_history.keys().into_iter() {
if day > last_claim_time && day <= current_timestamp {
reward_keys.push_back(day);
}
}

let mut pending_rewards: i128 = 0;
let mut last_reward_day = 0u64;

for reward_day in reward_keys
.into_iter()
.skip_while(|&day| day < start_day.unwrap_or(last_claim_time))
.take(chunk_size as usize)
{
if let (Some(daily_reward), Some(total_staked)) = (
reward_history.get(reward_day),
total_staked_history.get(reward_day),
) {
if total_staked > 0 {
for stake in user_info.stakes.iter() {
let stake_age_days =
((reward_day - stake.stake_timestamp) / SECONDS_PER_DAY).min(60);
let multiplier = Decimal::from_ratio(stake_age_days, 60);

let user_share = (stake.stake as u128) * daily_reward / total_staked;
pending_rewards += (user_share as i128) * multiplier;
}
}
last_reward_day = reward_day;
}
}

(pending_rewards, last_reward_day)
}
Loading

0 comments on commit 77742b0

Please sign in to comment.