From b0ed5d240daf0ed397a23244198fac0084c474cc Mon Sep 17 00:00:00 2001 From: bucurdavid Date: Wed, 6 Nov 2024 15:23:03 +0200 Subject: [PATCH 1/2] feat: support vault bond & remove combined liveliness --- .../core-sol-bond-stake-sc/src/constants.rs | 2 +- programs/core-sol-bond-stake-sc/src/errors.rs | 2 + .../src/instructions/bond.rs | 34 +- .../src/instructions/claim_rewards.rs | 61 +- .../src/instructions/initialize_address.rs | 2 +- .../src/instructions/mod.rs | 2 + .../src/instructions/renew.rs | 58 +- .../src/instructions/stake_rewards.rs | 73 +- .../src/instructions/topup.rs | 66 +- .../src/instructions/vault_address_update.rs | 59 + .../src/instructions/withdraw.rs | 54 +- programs/core-sol-bond-stake-sc/src/lib.rs | 42 +- .../src/libraries/rewards.rs | 66 -- .../src/states/address_bonds_rewards.rs | 6 +- .../core-sol-bond-stake-sc/src/states/bond.rs | 3 +- programs/core-sol-bond-stake-sc/src/utils.rs | 15 + tests/core-sol-bond-stake-sc.ts | 1024 +++++++++-------- 17 files changed, 723 insertions(+), 846 deletions(-) create mode 100644 programs/core-sol-bond-stake-sc/src/instructions/vault_address_update.rs diff --git a/programs/core-sol-bond-stake-sc/src/constants.rs b/programs/core-sol-bond-stake-sc/src/constants.rs index a75b637..5044a2d 100644 --- a/programs/core-sol-bond-stake-sc/src/constants.rs +++ b/programs/core-sol-bond-stake-sc/src/constants.rs @@ -11,4 +11,4 @@ pub const MAX_PERCENT: u64 = 10_000; pub const SLOTS_IN_YEAR: u64 = 78_840_000u64; pub const DIVISION_SAFETY_CONST: u64 = 1_000_000_000; -pub const ADMIN_PUBKEY: Pubkey = pubkey!("5RFetgyZyFCAVCZpYWvdJt7JqgtFmm82vz24nGANSbw7"); +pub const ADMIN_PUBKEY: Pubkey = pubkey!("FuMzWZ2bi7QmquTzCrjvsEbmyCt1tF78idxGJQhjTiWu"); diff --git a/programs/core-sol-bond-stake-sc/src/errors.rs b/programs/core-sol-bond-stake-sc/src/errors.rs index 3133268..7a1326e 100644 --- a/programs/core-sol-bond-stake-sc/src/errors.rs +++ b/programs/core-sol-bond-stake-sc/src/errors.rs @@ -40,4 +40,6 @@ pub enum Errors { NotCreator, #[msg("Asset Id mismatch")] AssetIdMismatch, + #[msg("Vault bond id mismatch")] + VaultBondIdMismatch, } diff --git a/programs/core-sol-bond-stake-sc/src/instructions/bond.rs b/programs/core-sol-bond-stake-sc/src/instructions/bond.rs index 4668a3a..6496515 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/bond.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/bond.rs @@ -9,14 +9,13 @@ use mpl_bubblegum::{types::LeafSchema, utils::get_asset_id}; use spl_account_compression::program::SplAccountCompression; use crate::{ - compute_decay, compute_weighted_liveliness_decay, compute_weighted_liveliness_new, get_current_timestamp, update_address_claimable_rewards, AddressBondsRewards, AssetUsage, Bond, BondConfig, Errors, RewardsConfig, State, VaultConfig, ADDRESS_BONDS_REWARDS_SEED, - BOND_CONFIG_SEED, BOND_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, VAULT_CONFIG_SEED, + BOND_CONFIG_SEED, BOND_SEED, REWARDS_CONFIG_SEED, VAULT_CONFIG_SEED, }; #[derive(Accounts)] -#[instruction(bond_config_index: u8, bond_id:u8, amount: u64,nonce: u64)] +#[instruction(bond_config_index: u8, bond_id:u16, amount: u64,nonce: u64)] pub struct BondContext<'info> { #[account( mut, @@ -110,10 +109,9 @@ pub struct BondContext<'info> { pub fn bond<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, BondContext<'info>>, - bond_id: u8, + bond_id: u16, amount: u64, nonce: u64, - is_vault: bool, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -125,22 +123,6 @@ pub fn bond<'a, 'b, 'c: 'info, 'info>( let current_timestamp = get_current_timestamp()?; - let weight_to_be_added = amount * MAX_PERCENT; - msg!("weight_to_be_added: {}", weight_to_be_added); - msg!("weight_to_be_added amount: {}", amount); - msg!("weight_to_be_added percent: {}", MAX_PERCENT); - - let decay = compute_decay( - ctx.accounts.address_bonds_rewards.last_update_timestamp, - current_timestamp, - ctx.accounts.bond_config.lock_period, - ); - - let weighted_liveliness_score_decayed = compute_weighted_liveliness_decay( - ctx.accounts.address_bonds_rewards.weighted_liveliness_score, - decay, - ); - update_address_claimable_rewards( &mut ctx.accounts.rewards_config, &mut ctx.accounts.vault_config, @@ -149,15 +131,6 @@ pub fn bond<'a, 'b, 'c: 'info, 'info>( let address_bonds_rewards = &mut ctx.accounts.address_bonds_rewards; - let weighted_liveliness_score_new = compute_weighted_liveliness_new( - weighted_liveliness_score_decayed, - address_bonds_rewards.address_total_bond_amount, - address_bonds_rewards.address_total_bond_amount + amount, - weight_to_be_added, - 0, - ); - - address_bonds_rewards.weighted_liveliness_score = weighted_liveliness_score_new; address_bonds_rewards.last_update_timestamp = current_timestamp; address_bonds_rewards.address_total_bond_amount += amount; @@ -213,7 +186,6 @@ pub fn bond<'a, 'b, 'c: 'info, 'info>( ctx.accounts.bond.set_inner(Bond { bump: ctx.bumps.bond, state: State::Active.to_code(), - is_vault, unbond_timestamp: current_timestamp.add(ctx.accounts.bond_config.lock_period), bond_timestamp: current_timestamp, bond_amount: amount, diff --git a/programs/core-sol-bond-stake-sc/src/instructions/claim_rewards.rs b/programs/core-sol-bond-stake-sc/src/instructions/claim_rewards.rs index afe2927..f3307f8 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/claim_rewards.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/claim_rewards.rs @@ -5,15 +5,14 @@ use anchor_spl::{ }; use crate::{ - compute_decay, compute_weighted_liveliness_decay, compute_weighted_liveliness_new, - full_math::MulDiv, get_current_timestamp, update_address_claimable_rewards, - AddressBondsRewards, BondConfig, Errors, RewardsConfig, VaultConfig, - ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, + compute_bond_score, full_math::MulDiv, get_current_timestamp, update_address_claimable_rewards, + AddressBondsRewards, Bond, BondConfig, Errors, RewardsConfig, State, VaultConfig, + ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, BOND_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, VAULT_CONFIG_SEED, }; #[derive(Accounts)] -#[instruction(bond_config_index:u8)] +#[instruction(bond_config_index:u8,bond_id:u16)] pub struct ClaimRewards<'info> { #[account( mut, @@ -28,6 +27,18 @@ pub struct ClaimRewards<'info> { )] pub bond_config: Account<'info, BondConfig>, + #[account( + mut, + seeds = [ + BOND_SEED.as_bytes(), + authority.key().as_ref(), + &bond_id.to_le_bytes() + ], + bump=bond.bump, + + )] + pub bond: Account<'info, Bond>, + #[account( mut, seeds=[REWARDS_CONFIG_SEED.as_bytes()], @@ -76,25 +87,26 @@ pub struct ClaimRewards<'info> { pub fn claim_rewards<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, ClaimRewards<'info>>, + bond_id: u16, ) -> Result<()> { let signer_seeds: [&[&[u8]]; 1] = [&[ VAULT_CONFIG_SEED.as_bytes(), &[ctx.accounts.vault_config.bump], ]]; - let current_timestamp = get_current_timestamp()?; - - let decay = compute_decay( - ctx.accounts.address_bonds_rewards.last_update_timestamp, - current_timestamp, - ctx.accounts.bond_config.lock_period, + require!( + ctx.accounts.address_bonds_rewards.vault_bond_id == bond_id + && ctx.accounts.address_bonds_rewards.vault_bond_id != 0, + Errors::VaultBondIdMismatch ); - let weighted_liveliness_score_decayed = compute_weighted_liveliness_decay( - ctx.accounts.address_bonds_rewards.weighted_liveliness_score, - decay, + require!( + ctx.accounts.bond.state == State::Active.to_code(), + Errors::BondIsInactive ); + let current_timestamp = get_current_timestamp()?; + update_address_claimable_rewards( &mut ctx.accounts.rewards_config, &ctx.accounts.vault_config, @@ -110,24 +122,21 @@ pub fn claim_rewards<'a, 'b, 'c: 'info, 'info>( let actual_claimable_amount; - if weighted_liveliness_score_decayed >= 95_00u64 { + let actual_vault_liveliness_score = compute_bond_score( + ctx.accounts.bond_config.lock_period, + current_timestamp, + ctx.accounts.bond.unbond_timestamp, + ); + + if actual_vault_liveliness_score >= 95_00u64 { actual_claimable_amount = address_bonds_rewards.claimable_amount; } else { actual_claimable_amount = address_bonds_rewards .claimable_amount - .mul_div_floor(weighted_liveliness_score_decayed, MAX_PERCENT) + .mul_div_floor(actual_vault_liveliness_score, MAX_PERCENT) .unwrap(); } - let weighted_liveliness_score_new = compute_weighted_liveliness_new( - weighted_liveliness_score_decayed, - address_bonds_rewards.address_total_bond_amount, - address_bonds_rewards.address_total_bond_amount, - 0, - 0, - ); - - address_bonds_rewards.weighted_liveliness_score = weighted_liveliness_score_new; address_bonds_rewards.last_update_timestamp = current_timestamp; let cpi_accounts = TransferChecked { @@ -149,4 +158,4 @@ pub fn claim_rewards<'a, 'b, 'c: 'info, 'info>( address_bonds_rewards.claimable_amount = 0; Ok(()) -} \ No newline at end of file +} diff --git a/programs/core-sol-bond-stake-sc/src/instructions/initialize_address.rs b/programs/core-sol-bond-stake-sc/src/instructions/initialize_address.rs index 6dbd947..f179287 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/initialize_address.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/initialize_address.rs @@ -36,10 +36,10 @@ pub fn initialize_address<'info>(ctx: Context>) -> Resu address: ctx.accounts.authority.key(), address_total_bond_amount: 0, current_index: 0, - weighted_liveliness_score: 0, last_update_timestamp: get_current_timestamp()?, address_rewards_per_share: ctx.accounts.rewards_config.rewards_per_share, claimable_amount: 0, + vault_bond_id: 0, padding: [0; 16], }); diff --git a/programs/core-sol-bond-stake-sc/src/instructions/mod.rs b/programs/core-sol-bond-stake-sc/src/instructions/mod.rs index 6824bdd..2b48bab 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/mod.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/mod.rs @@ -15,3 +15,5 @@ pub mod claim_rewards; pub use claim_rewards::*; pub mod initialize_address; pub use initialize_address::*; +pub mod vault_address_update; +pub use vault_address_update::*; diff --git a/programs/core-sol-bond-stake-sc/src/instructions/renew.rs b/programs/core-sol-bond-stake-sc/src/instructions/renew.rs index 46838f9..fbf493f 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/renew.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/renew.rs @@ -1,15 +1,13 @@ use anchor_lang::prelude::*; use crate::{ - compute_decay, compute_weighted_liveliness_decay, compute_weighted_liveliness_new, - full_math::MulDiv, get_current_timestamp, update_address_claimable_rewards, - AddressBondsRewards, Bond, BondConfig, Errors, RewardsConfig, State, VaultConfig, - ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, BOND_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, - VAULT_CONFIG_SEED, + get_current_timestamp, update_address_claimable_rewards, AddressBondsRewards, Bond, BondConfig, + Errors, RewardsConfig, State, VaultConfig, ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, + BOND_SEED, REWARDS_CONFIG_SEED, VAULT_CONFIG_SEED, }; #[derive(Accounts)] -#[instruction(bond_config_index:u8,bond_id:u8)] +#[instruction(bond_config_index:u8,bond_id:u16)] pub struct Renew<'info> { #[account( seeds=[BOND_CONFIG_SEED.as_bytes(),&bond_config_index.to_be_bytes()], @@ -69,45 +67,6 @@ pub fn renew(ctx: Context) -> Result<()> { Errors::BondIsInactive ); - let weight_to_be_added = bond.bond_amount * MAX_PERCENT; - msg!("weight_to_be_added: {}", weight_to_be_added); - msg!("weight_to_be_added amount: {}", bond.bond_amount); - msg!("weight_to_be_added percent: {}", MAX_PERCENT); - - let weight_to_be_subtracted = if current_timestamp < bond.unbond_timestamp { - bond.bond_amount - .mul_div_floor( - bond.unbond_timestamp - current_timestamp, - ctx.accounts.bond_config.lock_period, - ) - .unwrap() - * MAX_PERCENT - } else { - 0 - }; - msg!("weight_to_be_subtracted: {}", weight_to_be_subtracted); - msg!("weight_to_be_subtracted amount: {}", bond.bond_amount); - if current_timestamp < bond.unbond_timestamp { - msg!( - "weight_to_be_subtracted percent: {}", - (bond.unbond_timestamp - current_timestamp) * MAX_PERCENT - / ctx.accounts.bond_config.lock_period - ); - } else { - msg!("weight_to_be_subtracted percent: 0"); - } - - let decay = compute_decay( - ctx.accounts.address_bonds_rewards.last_update_timestamp, - current_timestamp, - ctx.accounts.bond_config.lock_period, - ); - - let weighted_liveliness_score_decayed = compute_weighted_liveliness_decay( - ctx.accounts.address_bonds_rewards.weighted_liveliness_score, - decay, - ); - update_address_claimable_rewards( &mut ctx.accounts.rewards_config, &ctx.accounts.vault_config, @@ -116,15 +75,6 @@ pub fn renew(ctx: Context) -> Result<()> { let address_bonds_rewards = &mut ctx.accounts.address_bonds_rewards; - let weighted_liveliness_score_new = compute_weighted_liveliness_new( - weighted_liveliness_score_decayed, - address_bonds_rewards.address_total_bond_amount, - address_bonds_rewards.address_total_bond_amount, - weight_to_be_added, - weight_to_be_subtracted, - ); - - address_bonds_rewards.weighted_liveliness_score = weighted_liveliness_score_new; address_bonds_rewards.last_update_timestamp = current_timestamp; bond.unbond_timestamp = current_timestamp + ctx.accounts.bond_config.lock_period; diff --git a/programs/core-sol-bond-stake-sc/src/instructions/stake_rewards.rs b/programs/core-sol-bond-stake-sc/src/instructions/stake_rewards.rs index 17b0463..4a8bb58 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/stake_rewards.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/stake_rewards.rs @@ -1,15 +1,14 @@ use anchor_lang::prelude::*; use crate::{ - compute_decay, compute_weighted_liveliness_decay, compute_weighted_liveliness_new, - full_math::MulDiv, get_current_timestamp, update_address_claimable_rewards, + compute_bond_score, full_math::MulDiv, get_current_timestamp, update_address_claimable_rewards, AddressBondsRewards, Bond, BondConfig, Errors, RewardsConfig, State, VaultConfig, ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, BOND_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, VAULT_CONFIG_SEED, }; #[derive(Accounts)] -#[instruction(bond_config_index:u8,bond_id:u8)] +#[instruction(bond_config_index:u8,bond_id:u16)] pub struct StakeRewards<'info> { #[account( mut, @@ -61,27 +60,19 @@ pub struct StakeRewards<'info> { pub fn stake_rewards<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, StakeRewards<'info>>, + bond_id: u16, ) -> Result<()> { - require!(ctx.accounts.bond.is_vault, Errors::BondIsNotAVault); + require!( + ctx.accounts.address_bonds_rewards.vault_bond_id == bond_id + && ctx.accounts.address_bonds_rewards.vault_bond_id != 0, + Errors::VaultBondIdMismatch + ); require!( ctx.accounts.bond.state == State::Active.to_code(), Errors::BondIsInactive ); - let current_timestamp = get_current_timestamp()?; - - let decay = compute_decay( - ctx.accounts.address_bonds_rewards.last_update_timestamp, - current_timestamp, - ctx.accounts.bond_config.lock_period, - ); - - let weighted_liveliness_score_decayed = compute_weighted_liveliness_decay( - ctx.accounts.address_bonds_rewards.weighted_liveliness_score, - decay, - ); - update_address_claimable_rewards( &mut ctx.accounts.rewards_config, &mut ctx.accounts.vault_config, @@ -95,38 +86,20 @@ pub fn stake_rewards<'a, 'b, 'c: 'info, 'info>( let bond = &mut ctx.accounts.bond; - let weight_to_be_subtracted = if current_timestamp < bond.unbond_timestamp { - bond.bond_amount - .mul_div_floor( - bond.unbond_timestamp - current_timestamp, - ctx.accounts.bond_config.lock_period, - ) - .unwrap() - * MAX_PERCENT - } else { - 0 - }; - msg!("weight_to_be_subtracted: {}", weight_to_be_subtracted); - msg!("weight_to_be_subtracted amount: {}", bond.bond_amount); - msg!("weight_to_be_subtracted amount: {}", bond.bond_amount); - if current_timestamp < bond.unbond_timestamp { - msg!( - "weight_to_be_subtracted percent: {}", - (bond.unbond_timestamp - current_timestamp) * MAX_PERCENT - / ctx.accounts.bond_config.lock_period - ); - } else { - msg!("weight_to_be_subtracted percent: 0"); - } - let actual_claimable_amount; - if weighted_liveliness_score_decayed >= 95_00u64 { + let actual_vault_liveliness_score = compute_bond_score( + ctx.accounts.bond_config.lock_period, + current_timestamp, + bond.unbond_timestamp, + ); + + if actual_vault_liveliness_score >= 95_00u64 { actual_claimable_amount = address_bonds_rewards.claimable_amount; } else { actual_claimable_amount = address_bonds_rewards .claimable_amount - .mul_div_floor(weighted_liveliness_score_decayed, MAX_PERCENT) + .mul_div_floor(actual_vault_liveliness_score, MAX_PERCENT) .unwrap(); } @@ -138,21 +111,7 @@ pub fn stake_rewards<'a, 'b, 'c: 'info, 'info>( address_bonds_rewards.claimable_amount = 0; - let weight_to_be_added = bond.bond_amount * MAX_PERCENT; - msg!("weight_to_be_added: {}", weight_to_be_added); - msg!("weight_to_be_added amount: {}", bond.bond_amount); - msg!("weight_to_be_added percent: {}", MAX_PERCENT); - - let weighted_liveliness_score_new = compute_weighted_liveliness_new( - weighted_liveliness_score_decayed, - address_bonds_rewards.address_total_bond_amount, - address_bonds_rewards.address_total_bond_amount + actual_claimable_amount, - weight_to_be_added, - weight_to_be_subtracted, - ); - address_bonds_rewards.address_total_bond_amount += actual_claimable_amount; - address_bonds_rewards.weighted_liveliness_score = weighted_liveliness_score_new; address_bonds_rewards.last_update_timestamp = current_timestamp; Ok(()) diff --git a/programs/core-sol-bond-stake-sc/src/instructions/topup.rs b/programs/core-sol-bond-stake-sc/src/instructions/topup.rs index 9717f94..0cad09b 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/topup.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/topup.rs @@ -5,15 +5,13 @@ use anchor_spl::{ }; use crate::{ - compute_decay, compute_weighted_liveliness_decay, compute_weighted_liveliness_new, - full_math::MulDiv, get_current_timestamp, update_address_claimable_rewards, - AddressBondsRewards, Bond, BondConfig, Errors, RewardsConfig, State, VaultConfig, - ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, BOND_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, - VAULT_CONFIG_SEED, + get_current_timestamp, update_address_claimable_rewards, AddressBondsRewards, Bond, BondConfig, + Errors, RewardsConfig, State, VaultConfig, ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, + BOND_SEED, REWARDS_CONFIG_SEED, VAULT_CONFIG_SEED, }; #[derive(Accounts)] -#[instruction(bond_config_index:u8,bond_id: u8, amount:u64)] +#[instruction(bond_config_index:u8,bond_id: u16, amount:u64)] pub struct TopUp<'info> { #[account( @@ -90,6 +88,7 @@ pub struct TopUp<'info> { pub fn top_up<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, TopUp<'info>>, + bond_id: u16, amount: u64, ) -> Result<()> { let bond = &mut ctx.accounts.bond; @@ -99,49 +98,13 @@ pub fn top_up<'a, 'b, 'c: 'info, 'info>( Errors::BondIsInactive ); - require!(bond.is_vault, Errors::BondIsNotAVault); - - let current_timestamp = get_current_timestamp()?; - - let weight_to_be_added = (bond.bond_amount + amount) * MAX_PERCENT; - msg!("weight_to_be_added: {}", weight_to_be_added); - msg!("weight_to_be_added amount: {}", bond.bond_amount + amount); - msg!("weight_to_be_added percent: {}", MAX_PERCENT); - - let weight_to_be_subtracted = if current_timestamp < bond.unbond_timestamp { - bond.bond_amount - .mul_div_floor( - bond.unbond_timestamp - current_timestamp, - ctx.accounts.bond_config.lock_period, - ) - .unwrap() - * MAX_PERCENT - } else { - 0 - }; - msg!("weight_to_be_subtracted: {}", weight_to_be_subtracted); - msg!("weight_to_be_subtracted amount: {}", bond.bond_amount); - msg!("weight_to_be_subtracted amount: {}", bond.bond_amount); - if current_timestamp < bond.unbond_timestamp { - msg!( - "weight_to_be_subtracted percent: {}", - (bond.unbond_timestamp - current_timestamp) * MAX_PERCENT - / ctx.accounts.bond_config.lock_period - ); - } else { - msg!("weight_to_be_subtracted percent: 0"); - } - - let decay = compute_decay( - ctx.accounts.address_bonds_rewards.last_update_timestamp, - current_timestamp, - ctx.accounts.bond_config.lock_period, + require!( + ctx.accounts.address_bonds_rewards.vault_bond_id == bond_id + && ctx.accounts.address_bonds_rewards.vault_bond_id != 0, + Errors::VaultBondIdMismatch ); - let weighted_liveliness_score_decayed = compute_weighted_liveliness_decay( - ctx.accounts.address_bonds_rewards.weighted_liveliness_score, - decay, - ); + let current_timestamp = get_current_timestamp()?; update_address_claimable_rewards( &mut ctx.accounts.rewards_config, @@ -151,17 +114,8 @@ pub fn top_up<'a, 'b, 'c: 'info, 'info>( let address_bonds_rewards = &mut ctx.accounts.address_bonds_rewards; - let weighted_liveliness_score_new = compute_weighted_liveliness_new( - weighted_liveliness_score_decayed, - address_bonds_rewards.address_total_bond_amount, - address_bonds_rewards.address_total_bond_amount + amount, - weight_to_be_added, - weight_to_be_subtracted, - ); - address_bonds_rewards.address_total_bond_amount += amount; - address_bonds_rewards.weighted_liveliness_score = weighted_liveliness_score_new; address_bonds_rewards.last_update_timestamp = current_timestamp; let vault_config = &mut ctx.accounts.vault_config; diff --git a/programs/core-sol-bond-stake-sc/src/instructions/vault_address_update.rs b/programs/core-sol-bond-stake-sc/src/instructions/vault_address_update.rs new file mode 100644 index 0000000..2593705 --- /dev/null +++ b/programs/core-sol-bond-stake-sc/src/instructions/vault_address_update.rs @@ -0,0 +1,59 @@ +use anchor_lang::prelude::*; +use mpl_bubblegum::utils::get_asset_id; + +use crate::{ + AddressBondsRewards, Bond, BondConfig, Errors, ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, + BOND_SEED, +}; + +#[derive(Accounts)] +#[instruction(bond_config_index:u8, bond_id:u16)] +pub struct VaultAddressUpdate<'info> { + #[account( + mut, + seeds=[ADDRESS_BONDS_REWARDS_SEED.as_bytes(), authority.key().as_ref()], + bump=address_bonds_rewards.bump, + )] + pub address_bonds_rewards: Box>, + + #[account( + mut, + seeds = [ + BOND_SEED.as_bytes(), + authority.key().as_ref(), + &bond_id.to_le_bytes() + ], + bump=bond.bump, + + )] + pub bond: Account<'info, Bond>, + + #[account( + seeds=[BOND_CONFIG_SEED.as_bytes(),&bond_config_index.to_be_bytes()], + bump=bond_config.bump, + )] + pub bond_config: Account<'info, BondConfig>, + + #[account( + mut, + constraint=bond.owner == authority.key() @ Errors::OwnerMismatch, + constraint=address_bonds_rewards.address==authority.key() @Errors::OwnerMismatch, + )] + pub authority: Signer<'info>, + + pub system_program: Program<'info, System>, +} + +pub fn update_vault_bond(ctx: Context, bond_id: u16, nonce: u64) -> Result<()> { + let asset_id = get_asset_id(&ctx.accounts.bond_config.merkle_tree.key(), nonce); + + require!( + asset_id == ctx.accounts.bond.asset_id, + Errors::AssetIdMismatch + ); + + let address_bonds_rewards = &mut ctx.accounts.address_bonds_rewards; + address_bonds_rewards.vault_bond_id = bond_id; + + Ok(()) +} diff --git a/programs/core-sol-bond-stake-sc/src/instructions/withdraw.rs b/programs/core-sol-bond-stake-sc/src/instructions/withdraw.rs index ef7de55..2b61eef 100644 --- a/programs/core-sol-bond-stake-sc/src/instructions/withdraw.rs +++ b/programs/core-sol-bond-stake-sc/src/instructions/withdraw.rs @@ -5,15 +5,13 @@ use anchor_spl::{ }; use crate::{ - compute_decay, compute_weighted_liveliness_decay, compute_weighted_liveliness_new, - full_math::MulDiv, get_current_timestamp, update_address_claimable_rewards, - AddressBondsRewards, Bond, BondConfig, Errors, RewardsConfig, State, VaultConfig, - ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, BOND_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, - VAULT_CONFIG_SEED, + get_current_timestamp, update_address_claimable_rewards, AddressBondsRewards, Bond, BondConfig, + Errors, RewardsConfig, State, VaultConfig, ADDRESS_BONDS_REWARDS_SEED, BOND_CONFIG_SEED, + BOND_SEED, MAX_PERCENT, REWARDS_CONFIG_SEED, VAULT_CONFIG_SEED, }; #[derive(Accounts)] -#[instruction(bond_config_index:u8,bond_id: u8)] +#[instruction(bond_config_index:u8,bond_id: u16)] pub struct Withdraw<'info> { #[account( seeds=[BOND_CONFIG_SEED.as_bytes(),&bond_config_index.to_be_bytes()], @@ -107,41 +105,6 @@ pub fn withdraw<'a, 'b, 'c: 'info, 'info>( ); let current_timestamp = get_current_timestamp()?; - let weight_to_be_subtracted = if current_timestamp < bond.unbond_timestamp { - bond.bond_amount - .mul_div_floor( - bond.unbond_timestamp - current_timestamp, - ctx.accounts.bond_config.lock_period, - ) - .unwrap() - * MAX_PERCENT - } else { - 0 - }; - msg!("weight_to_be_subtracted: {}", weight_to_be_subtracted); - msg!("weight_to_be_subtracted amount: {}", bond.bond_amount); - msg!("weight_to_be_subtracted amount: {}", bond.bond_amount); - if current_timestamp < bond.unbond_timestamp { - msg!( - "weight_to_be_subtracted percent: {}", - (bond.unbond_timestamp - current_timestamp) * MAX_PERCENT - / ctx.accounts.bond_config.lock_period - ); - } else { - msg!("weight_to_be_subtracted percent: 0"); - } - - let decay = compute_decay( - ctx.accounts.address_bonds_rewards.last_update_timestamp, - current_timestamp, - ctx.accounts.bond_config.lock_period, - ); - - let weighted_liveliness_score_decayed = compute_weighted_liveliness_decay( - ctx.accounts.address_bonds_rewards.weighted_liveliness_score, - decay, - ); - update_address_claimable_rewards( &mut ctx.accounts.rewards_config, vault_config, @@ -150,16 +113,7 @@ pub fn withdraw<'a, 'b, 'c: 'info, 'info>( let address_bonds_rewards = &mut ctx.accounts.address_bonds_rewards; - let weighted_liveliness_score_new = compute_weighted_liveliness_new( - weighted_liveliness_score_decayed, - address_bonds_rewards.address_total_bond_amount, - address_bonds_rewards.address_total_bond_amount - bond.bond_amount, - 0, - weight_to_be_subtracted, - ); - address_bonds_rewards.address_total_bond_amount -= bond.bond_amount; - address_bonds_rewards.weighted_liveliness_score = weighted_liveliness_score_new; address_bonds_rewards.last_update_timestamp = current_timestamp; let mut penalty = 0u64; diff --git a/programs/core-sol-bond-stake-sc/src/lib.rs b/programs/core-sol-bond-stake-sc/src/lib.rs index 98e2d34..7f3a2cb 100644 --- a/programs/core-sol-bond-stake-sc/src/lib.rs +++ b/programs/core-sol-bond-stake-sc/src/lib.rs @@ -145,10 +145,9 @@ pub mod core_sol_bond_stake_sc { pub fn bond<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'info, 'info, BondContext<'info>>, _bond_config_index: u8, - bond_id: u8, + bond_id: u16, amount: u64, nonce: u64, - is_vault: bool, root: [u8; 32], data_hash: [u8; 32], creator_hash: [u8; 32], @@ -157,19 +156,23 @@ pub mod core_sol_bond_stake_sc { ctx.accounts.bond_config.bond_state == State::Active.to_code(), Errors::ProgramIsPaused ); - instructions::bond( - ctx, - bond_id, - amount, - nonce, - is_vault, - root, - data_hash, - creator_hash, - ) + instructions::bond(ctx, bond_id, amount, nonce, root, data_hash, creator_hash) + } + + pub fn update_vault_bond( + ctx: Context, + _bond_config_index: u8, + bond_id: u16, + nonce: u64, + ) -> Result<()> { + require!( + ctx.accounts.bond_config.bond_state == State::Active.to_code(), + Errors::ProgramIsPaused + ); + instructions::update_vault_bond(ctx, bond_id, nonce) } - pub fn renew(ctx: Context, _bond_config_index: u8, _bond_id: u8) -> Result<()> { + pub fn renew(ctx: Context, _bond_config_index: u8, _bond_id: u16) -> Result<()> { require!( ctx.accounts.bond_config.bond_state == State::Active.to_code(), Errors::ProgramIsPaused @@ -180,7 +183,7 @@ pub mod core_sol_bond_stake_sc { pub fn withdraw<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'info, 'info, Withdraw<'info>>, _bond_config_index: u8, - _bond_id: u8, + _bond_id: u16, ) -> Result<()> { require!( ctx.accounts.bond_config.bond_state == State::Active.to_code(), @@ -192,14 +195,14 @@ pub mod core_sol_bond_stake_sc { pub fn top_up<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, TopUp<'info>>, _bond_config_index: u8, - _bond_id: u8, + bond_id: u16, amount: u64, ) -> Result<()> { require!( ctx.accounts.bond_config.bond_state == State::Active.to_code(), Errors::ProgramIsPaused ); - instructions::top_up(ctx, amount) + instructions::top_up(ctx, bond_id, amount) } // Rewards @@ -207,7 +210,7 @@ pub mod core_sol_bond_stake_sc { pub fn stake_rewards<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, StakeRewards<'info>>, _bond_config_index: u8, - _bond_id: u8, + bond_id: u16, ) -> Result<()> { require!( ctx.accounts.bond_config.bond_state == State::Active.to_code(), @@ -217,17 +220,18 @@ pub mod core_sol_bond_stake_sc { ctx.accounts.rewards_config.rewards_state == State::Active.to_code(), Errors::ProgramIsPaused ); - instructions::stake_rewards(ctx) + instructions::stake_rewards(ctx, bond_id) } pub fn claim_rewards<'a, 'b, 'c: 'info, 'info>( ctx: Context<'a, 'b, 'c, 'info, ClaimRewards<'info>>, _bond_config_index: u8, + bond_id: u16, ) -> Result<()> { require!( ctx.accounts.rewards_config.rewards_state == State::Active.to_code(), Errors::ProgramIsPaused ); - instructions::claim_rewards(ctx) + instructions::claim_rewards(ctx, bond_id) } } diff --git a/programs/core-sol-bond-stake-sc/src/libraries/rewards.rs b/programs/core-sol-bond-stake-sc/src/libraries/rewards.rs index 3b749da..195adf6 100644 --- a/programs/core-sol-bond-stake-sc/src/libraries/rewards.rs +++ b/programs/core-sol-bond-stake-sc/src/libraries/rewards.rs @@ -111,69 +111,3 @@ pub fn update_address_claimable_rewards<'info>( Ok(()) } - -pub fn compute_decay(last_update_timestamp: u64, current_timestamp: u64, lock_period: u64) -> u64 { - msg!("current_timestamp: {}", current_timestamp); - msg!("last_update_timestamp: {}", last_update_timestamp); - msg!("lock_period: {}", lock_period); - - let decay = (current_timestamp - last_update_timestamp) - .mul_div_floor(DIVISION_SAFETY_CONST, lock_period) - .unwrap(); - - msg!("decay: {}", decay); - - decay -} - -pub fn compute_weighted_liveliness_decay(weighted_liveliness_score: u64, decay: u64) -> u64 { - msg!( - "last weighted_liveliness_score: {}", - weighted_liveliness_score - ); - - let weighted_liveliness_score_decayed = weighted_liveliness_score - .mul_div_floor( - 1 * DIVISION_SAFETY_CONST.saturating_sub(decay), - DIVISION_SAFETY_CONST, - ) - .unwrap(); - - msg!( - "weighted_liveliness_score_decayed: {}", - weighted_liveliness_score_decayed - ); - - weighted_liveliness_score_decayed -} - -pub fn compute_weighted_liveliness_new( - weighted_liveliness_score_decayed: u64, - address_total_bond_amount_before: u64, - address_total_bond_amount_after: u64, - weight_to_be_added: u64, - weight_to_be_subtracted: u64, -) -> u64 { - msg!( - "address total bond amount before: {}", - address_total_bond_amount_before - ); - msg!( - "address total bond amount after: {}", - address_total_bond_amount_after - ); - - if address_total_bond_amount_after == 0 { - 0u64 - } else { - let new = (weighted_liveliness_score_decayed - .saturating_mul(address_total_bond_amount_before) - .saturating_sub(weight_to_be_subtracted) - .saturating_add(weight_to_be_added)) - .saturating_div(address_total_bond_amount_after); - - msg!("new weighted_liveliness_score: {}", new); - - new - } -} diff --git a/programs/core-sol-bond-stake-sc/src/states/address_bonds_rewards.rs b/programs/core-sol-bond-stake-sc/src/states/address_bonds_rewards.rs index e05ca85..dcc8745 100644 --- a/programs/core-sol-bond-stake-sc/src/states/address_bonds_rewards.rs +++ b/programs/core-sol-bond-stake-sc/src/states/address_bonds_rewards.rs @@ -5,13 +5,13 @@ pub struct AddressBondsRewards { pub bump: u8, pub address: Pubkey, pub address_total_bond_amount: u64, - pub current_index: u8, - pub weighted_liveliness_score: u64, + pub current_index: u16, pub last_update_timestamp: u64, pub address_rewards_per_share: u64, pub claimable_amount: u64, + pub vault_bond_id: u16, pub padding: [u8; 16], } impl Space for AddressBondsRewards { - const INIT_SPACE: usize = 8 + 1 + 32 + 8 + 1 + 8 + 8 + 8 + 8 + 16; + const INIT_SPACE: usize = 8 + 1 + 32 + 8 + 2 + 8 + 8 + 8 + 2 + 16; } diff --git a/programs/core-sol-bond-stake-sc/src/states/bond.rs b/programs/core-sol-bond-stake-sc/src/states/bond.rs index 04637c9..8cb4e4e 100644 --- a/programs/core-sol-bond-stake-sc/src/states/bond.rs +++ b/programs/core-sol-bond-stake-sc/src/states/bond.rs @@ -4,7 +4,6 @@ use anchor_lang::prelude::*; pub struct Bond { pub bump: u8, pub state: u8, - pub is_vault: bool, pub bond_timestamp: u64, pub unbond_timestamp: u64, pub bond_amount: u64, @@ -13,5 +12,5 @@ pub struct Bond { pub padding: [u8; 64], } impl Space for Bond { - const INIT_SPACE: usize = 8 + 1 + 1 + 1 + 8 + 8 + 8 + 32 + 32 + 64; + const INIT_SPACE: usize = 8 + 1 + 1 + 8 + 8 + 8 + 32 + 32 + 64; } diff --git a/programs/core-sol-bond-stake-sc/src/utils.rs b/programs/core-sol-bond-stake-sc/src/utils.rs index 0be9243..e1651a1 100644 --- a/programs/core-sol-bond-stake-sc/src/utils.rs +++ b/programs/core-sol-bond-stake-sc/src/utils.rs @@ -22,3 +22,18 @@ pub fn get_current_timestamp() -> Result { pub fn get_current_slot() -> Result { Ok(clock::Clock::get()?.slot.try_into().unwrap()) } + +pub fn compute_bond_score(lock_period: u64, current_timestamp: u64, unbond_timestamp: u64) -> u64 { + if current_timestamp >= unbond_timestamp { + 0 + } else { + let difference = unbond_timestamp - current_timestamp; + + if lock_period == 0 { + 0 + } else { + let div_result = 10000u64.checked_div(lock_period).unwrap_or(0); + div_result.checked_mul(difference).unwrap_or(0) + } + } +} diff --git a/tests/core-sol-bond-stake-sc.ts b/tests/core-sol-bond-stake-sc.ts index cb660c9..e63587b 100644 --- a/tests/core-sol-bond-stake-sc.ts +++ b/tests/core-sol-bond-stake-sc.ts @@ -1257,7 +1257,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1273,7 +1277,6 @@ describe("core-sol-bond-stake-sc", () => { 1, new anchor.BN(100e9), new anchor.BN(Number(user_nft_leaf_schemas[0].nonce)), - false, Array.from(bs58.decode(user_nft_leaf_schemas[0].id)), Array.from(user_nft_leaf_schemas[0].dataHash), Array.from(user_nft_leaf_schemas[0].creatorHash) @@ -1328,7 +1331,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1354,7 +1361,6 @@ describe("core-sol-bond-stake-sc", () => { 1, new anchor.BN(10e9), new anchor.BN(Number(user_nft_leaf_schemas[0].nonce)), - false, Array.from(bs58.decode(user_nft_leaf_schemas[0].id)), Array.from(user_nft_leaf_schemas[0].dataHash), Array.from(user_nft_leaf_schemas[0].creatorHash) @@ -1401,7 +1407,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([2])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(2).toBuffer("le", 2), + ], program.programId )[0]; @@ -1417,7 +1427,6 @@ describe("core-sol-bond-stake-sc", () => { 2, new anchor.BN(100e9), new anchor.BN(Number(user_nft_leaf_schemas[0].nonce)), - false, Array.from(bs58.decode(user_nft_leaf_schemas[0].id)), Array.from(user_nft_leaf_schemas[0].dataHash), Array.from(user_nft_leaf_schemas[0].creatorHash) @@ -1464,7 +1473,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1479,7 +1492,6 @@ describe("core-sol-bond-stake-sc", () => { 1, new anchor.BN(100e9), new anchor.BN(Number(user_nft_leaf_schemas[0].nonce)), - true, Array.from(bs58.decode(user_nft_leaf_schemas[0].id)), Array.from(user_nft_leaf_schemas[0].dataHash), Array.from(user_nft_leaf_schemas[0].creatorHash) @@ -1511,19 +1523,36 @@ describe("core-sol-bond-stake-sc", () => { ]) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program + let addressBondsRewards = await program.account.addressBondsRewards.fetch( + userBondsRewards ); - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards + expect(addressBondsRewards.currentIndex).to.equal(1); + expect(addressBondsRewards.claimableAmount.toNumber()).to.equal(0); + expect(addressBondsRewards.vaultBondId).to.equal(0); + expect( + addressBondsRewards.addressTotalBondAmount.toNumber() / LAMPORTS_PER_SOL + ).to.eq(100); + + let bondAcc = await program.account.bond.fetch(bond1); + let bondConfigAcc = await program.account.bondConfig.fetch(bondConfigPda1); + + await new Promise((r) => setTimeout(r, 2000)); + + const transactionDetails = await program.provider.connection.getTransaction( + x, + { commitment: "confirmed" } + ); + const blockTime = await program.provider.connection.getBlockTime( + transactionDetails.slot ); - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() == normalWeighted + expect(bondAcc.bondAmount.toNumber()).to.equal(100e9); + expect(bondAcc.state).to.equal(1); + expect(bondAcc.owner.toBase58()).to.equal(user.publicKey.toBase58()); + expect(bondAcc.bondTimestamp.toNumber()).to.equal(blockTime); + expect(bondAcc.unbondTimestamp.toNumber()).to.equal( + blockTime + bondConfigAcc.lockPeriod.toNumber() ); }); @@ -1534,7 +1563,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond2 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([2])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(2).toBuffer("le", 2), + ], program.programId )[0]; @@ -1549,7 +1582,6 @@ describe("core-sol-bond-stake-sc", () => { 2, new anchor.BN(100e9), new anchor.BN(Number(user_nft_leaf_schemas[1].nonce)), - false, Array.from(bs58.decode(user_nft_leaf_schemas[1].id)), Array.from(user_nft_leaf_schemas[1].dataHash), Array.from(user_nft_leaf_schemas[1].creatorHash) @@ -1581,19 +1613,36 @@ describe("core-sol-bond-stake-sc", () => { ]) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program + let addressBondsRewards = await program.account.addressBondsRewards.fetch( + userBondsRewards ); - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards + expect(addressBondsRewards.currentIndex).to.equal(2); + expect(addressBondsRewards.claimableAmount.toNumber()).to.equal(0); + expect(addressBondsRewards.vaultBondId).to.equal(0); + expect( + addressBondsRewards.addressTotalBondAmount.toNumber() / LAMPORTS_PER_SOL + ).to.eq(200); + + let bondAcc = await program.account.bond.fetch(bond2); + let bondConfigAcc = await program.account.bondConfig.fetch(bondConfigPda1); + + await new Promise((r) => setTimeout(r, 2000)); + + const transactionDetails = await program.provider.connection.getTransaction( + x, + { commitment: "confirmed" } + ); + const blockTime = await program.provider.connection.getBlockTime( + transactionDetails.slot ); - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() == normalWeighted + expect(bondAcc.bondAmount.toNumber()).to.equal(100e9); + expect(bondAcc.state).to.equal(1); + expect(bondAcc.owner.toBase58()).to.equal(user.publicKey.toBase58()); + expect(bondAcc.bondTimestamp.toNumber()).to.equal(blockTime); + expect(bondAcc.unbondTimestamp.toNumber()).to.equal( + blockTime + bondConfigAcc.lockPeriod.toNumber() ); }); @@ -1604,7 +1653,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user2.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user2.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1629,7 +1682,6 @@ describe("core-sol-bond-stake-sc", () => { 1, new anchor.BN(100e9), new anchor.BN(Number(user2_nft_leaf_schemas[0].nonce)), - true, Array.from(bs58.decode(user2_nft_leaf_schemas[0].id)), Array.from(user2_nft_leaf_schemas[0].dataHash), Array.from(user2_nft_leaf_schemas[0].creatorHash) @@ -1661,19 +1713,37 @@ describe("core-sol-bond-stake-sc", () => { ]) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program + let addressBondsRewards = await program.account.addressBondsRewards.fetch( + userBondsRewards ); - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards + expect(addressBondsRewards.currentIndex).to.equal(1); + expect(addressBondsRewards.claimableAmount.toNumber()).to.equal(0); + expect(addressBondsRewards.vaultBondId).to.equal(0); + expect( + addressBondsRewards.addressTotalBondAmount.toNumber() / LAMPORTS_PER_SOL + ).to.eq(100); + + let bondAcc = await program.account.bond.fetch(bond1); + let bondConfigAcc = await program.account.bondConfig.fetch(bondConfigPda1); + + await new Promise((r) => setTimeout(r, 2000)); + + const transactionDetails = await program.provider.connection.getTransaction( + x, + { commitment: "confirmed" } + ); + const blockTime = await program.provider.connection.getBlockTime( + transactionDetails.slot ); - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() == normalWeighted + expect(bondAcc.assetId.toBase58()).to.equal(user2_nft_leaf_schemas[0].id); + expect(bondAcc.bondAmount.toNumber()).to.equal(100e9); + expect(bondAcc.state).to.equal(1); + expect(bondAcc.owner.toBase58()).to.equal(user2.publicKey.toBase58()); + expect(bondAcc.bondTimestamp.toNumber()).to.equal(blockTime); + expect(bondAcc.unbondTimestamp.toNumber()).to.equal( + blockTime + bondConfigAcc.lockPeriod.toNumber() ); }); @@ -1684,7 +1754,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user2.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user2.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1717,7 +1791,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1734,20 +1812,25 @@ describe("core-sol-bond-stake-sc", () => { }) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( + let bondAcc = await program.account.bond.fetch(bond1); + let bondConfigAcc = await program.account.bondConfig.fetch(bondConfigPda1); + + await new Promise((r) => setTimeout(r, 2000)); + + const transactionDetails = await program.provider.connection.getTransaction( x, - userBondsRewards, - bondConfigPda1, - program + { commitment: "confirmed" } ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards + const blockTime = await program.provider.connection.getBlockTime( + transactionDetails.slot ); - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() == normalWeighted + expect(bondAcc.state).to.equal(1); + expect(bondAcc.bondTimestamp.toNumber()).to.equal(blockTime); + expect(bondAcc.unbondTimestamp.toNumber()).to.equal( + blockTime + bondConfigAcc.lockPeriod.toNumber() ); + expect(bondAcc.bondAmount.toNumber() / LAMPORTS_PER_SOL).to.equal(100); }); it("TopUp bond 2 by user - bond not vault (should fail)", async () => { @@ -1757,7 +1840,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond2 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([2])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(2).toBuffer("le", 2), + ], program.programId )[0]; @@ -1779,9 +1866,9 @@ describe("core-sol-bond-stake-sc", () => { .rpc(); assert(false, "Should have thrown error"); } catch (err) { - expect((err as anchor.AnchorError).error.errorCode.number).to.equal(6015); + expect((err as anchor.AnchorError).error.errorCode.number).to.equal(6019); expect((err as anchor.AnchorError).error.errorMessage).to.equal( - "Bond is not a vault" + "Vault bond id mismatch" ); } }); @@ -1792,7 +1879,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1828,7 +1919,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user2.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user2.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1857,14 +1952,95 @@ describe("core-sol-bond-stake-sc", () => { } }); - it("TopUp bond 1 by user", async () => { + it("TopUp bond 1 by user - vault not set (should fail)", async () => { + const userBondsRewards = PublicKey.findProgramAddressSync( + [Buffer.from("address_bonds_rewards"), user.publicKey.toBuffer()], + program.programId + )[0]; + + const bond1 = PublicKey.findProgramAddressSync( + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], + program.programId + )[0]; + + try { + let x = await program.methods + .topUp(1, 1, new anchor.BN(100e9)) + .signers([user]) + .accounts({ + addressBondsRewards: userBondsRewards, + bondConfig: bondConfigPda1, + rewardsConfig: rewardsConfigPda, + mintOfTokenSent: itheum_token_mint.publicKey, + bond: bond1, + vaultConfig: vaultConfigPda, + vault: vault_ata, + authority: user.publicKey, + authorityTokenAccount: itheum_token_user_ata, + }) + .rpc(); + assert(false, "Should have thrown error"); + } catch (err) { + expect((err as anchor.AnchorError).error.errorCode.number).to.equal(6019); + expect((err as anchor.AnchorError).error.errorMessage).to.equal( + "Vault bond id mismatch" + ); + } + }); + + it("Set bond id 1 as vault by user", async () => { + const userBondsRewards = PublicKey.findProgramAddressSync( + [Buffer.from("address_bonds_rewards"), user.publicKey.toBuffer()], + program.programId + )[0]; + + const bond1 = PublicKey.findProgramAddressSync( + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], + program.programId + )[0]; + + await program.methods + .updateVaultBond( + 1, + 1, + new anchor.BN(Number(user_nft_leaf_schemas[0].nonce)) + ) + .signers([user]) + .accounts({ + addressBondsRewards: userBondsRewards, + bond: bond1, + bondConfig: bondConfigPda1, + authority: user.publicKey, + }) + .rpc(); + + const userAcc = await program.account.addressBondsRewards.fetch( + userBondsRewards + ); + + expect(userAcc.vaultBondId).to.equal(1); + }); + + it("Topup vault by user", async () => { const userBondsRewards = PublicKey.findProgramAddressSync( [Buffer.from("address_bonds_rewards"), user.publicKey.toBuffer()], program.programId )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1884,21 +2060,96 @@ describe("core-sol-bond-stake-sc", () => { }) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program - ); + let bondAcc = await program.account.bond.fetch(bond1); - const addressBondsFetched = await program.account.addressBondsRewards.fetch( + expect(bondAcc.bondAmount.toNumber() / LAMPORTS_PER_SOL).to.equal(200); + + let userAcc = await program.account.addressBondsRewards.fetch( userBondsRewards ); - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() == normalWeighted + expect( + userAcc.addressTotalBondAmount.toNumber() / LAMPORTS_PER_SOL + ).to.equal(300); + }); + + it("Change vault to bond 2 by user", async () => { + const userBondsRewards = PublicKey.findProgramAddressSync( + [Buffer.from("address_bonds_rewards"), user.publicKey.toBuffer()], + program.programId + )[0]; + + const bond2 = PublicKey.findProgramAddressSync( + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(2).toBuffer("le", 2), + ], + program.programId + )[0]; + + await program.methods + .updateVaultBond( + 1, + 2, + new anchor.BN(Number(user_nft_leaf_schemas[1].nonce)) + ) + .signers([user]) + .accounts({ + addressBondsRewards: userBondsRewards, + bond: bond2, + bondConfig: bondConfigPda1, + authority: user.publicKey, + }) + .rpc(); + + const userAcc = await program.account.addressBondsRewards.fetch( + userBondsRewards ); + + expect(userAcc.vaultBondId).to.equal(2); }); + + it("TopUp bond 1 by user - vault set to other bond (should fail)", async () => { + const userBondsRewards = PublicKey.findProgramAddressSync( + [Buffer.from("address_bonds_rewards"), user.publicKey.toBuffer()], + program.programId + )[0]; + + const bond1 = PublicKey.findProgramAddressSync( + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], + program.programId + )[0]; + + try { + let x = await program.methods + .topUp(1, 1, new anchor.BN(100e9)) + .signers([user]) + .accounts({ + addressBondsRewards: userBondsRewards, + bondConfig: bondConfigPda1, + rewardsConfig: rewardsConfigPda, + mintOfTokenSent: itheum_token_mint.publicKey, + bond: bond1, + vaultConfig: vaultConfigPda, + vault: vault_ata, + authority: user.publicKey, + authorityTokenAccount: itheum_token_user_ata, + }) + .rpc(); + assert(false, "Should have thrown error"); + } catch (err) { + expect((err as anchor.AnchorError).error.errorCode.number).to.equal(6019); + expect((err as anchor.AnchorError).error.errorMessage).to.equal( + "Vault bond id mismatch" + ); + } + }); + it("Withdraw bond 1 by user", async () => { const userBondsRewards = PublicKey.findProgramAddressSync( [Buffer.from("address_bonds_rewards"), user.publicKey.toBuffer()], @@ -1906,10 +2157,20 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; + let balanceBefore = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; + let x = await program.methods .withdraw(1, 1) .signers([user]) @@ -1926,31 +2187,31 @@ describe("core-sol-bond-stake-sc", () => { }) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program - ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( + let userAcc = await program.account.addressBondsRewards.fetch( userBondsRewards ); - const tolerance = normalWeighted * 0.001; - const lowerBound = normalWeighted - tolerance; - const upperBound = normalWeighted + tolerance; + let vaultAcc = await program.account.vaultConfig.fetch(vaultConfigPda); + + let balanceAfter = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() >= lowerBound && - addressBondsFetched.weightedLivelinessScore.toNumber() <= upperBound, - `Score ${addressBondsFetched.weightedLivelinessScore.toNumber()} is out of range!` + expect( + vaultAcc.totalPenalizedAmount.toNumber() / LAMPORTS_PER_SOL + ).to.equal(100); // bond 1 - 200 tokens ; penalty 50% => 100 tokens + expect(vaultAcc.totalBondAmount.toNumber() / LAMPORTS_PER_SOL).to.equal( + 200 ); - assert( - addressBondsFetched.addressTotalBondAmount.toNumber() / - LAMPORTS_PER_SOL == - 100 + expect( + userAcc.addressTotalBondAmount.toNumber() / LAMPORTS_PER_SOL + ).to.equal(100); // remaining + + expect(Number(balanceAfter) / LAMPORTS_PER_SOL).to.equal( + Number(balanceBefore) / LAMPORTS_PER_SOL + 100 ); }); @@ -1961,7 +2222,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -1997,7 +2262,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -2032,7 +2301,11 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; @@ -2096,14 +2369,15 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond2 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([2])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(2).toBuffer("le", 2), + ], program.programId )[0]; - let addressRewardsAccBefore = - await program.account.addressBondsRewards.fetch(userBondsRewards); - - let userBondsBefore = await program.account.addressBondsRewards.fetch( + let userAccBefore = await program.account.addressBondsRewards.fetch( userBondsRewards ); @@ -2118,53 +2392,18 @@ describe("core-sol-bond-stake-sc", () => { bond: bond2, authority: user.publicKey, }) - .rpc({ skipPreflight: true }); - - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program - ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards - ); - - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() == normalWeighted - ); - - let rewardsConfigAcc = await program.account.rewardsConfig.fetch( - rewardsConfigPda - ); - - let total_rewards = await calculateTotalRewardsInInterval( - activation_slot, - rewardsConfigPda, - program - ); + .rpc(); - let userRewardsAcc = await program.account.addressBondsRewards.fetch( + let userAccAfter = await program.account.addressBondsRewards.fetch( userBondsRewards ); - let user_rewards = await calculateUserRewards( - normalWeighted, - addressRewardsAccBefore.addressRewardsPerShare, - userBondsBefore.addressTotalBondAmount, - rewardsConfigPda, - true, - program + expect(userAccAfter.claimableAmount.toNumber()).to.equal( + userAccBefore.claimableAmount.toNumber() + 5e5 ); - - assert( - addressRewardsAccBefore.claimableAmount.toNumber() + - user_rewards / 10 ** 9 === - userRewardsAcc.claimableAmount.toNumber() + expect(userAccAfter.addressRewardsPerShare.toNumber()).to.equal( + userAccBefore.addressRewardsPerShare.toNumber() + 5e3 ); - - assert(rewardsConfigAcc.accumulatedRewards.toNumber() == total_rewards); }); it("Check user2 rewards - (renew bond 1 by user2)", async () => { @@ -2174,14 +2413,15 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond1 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user2.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user2.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; - let addressRewardsAccBefore = - await program.account.addressBondsRewards.fetch(userBondsRewards); - - let userBondsBefore = await program.account.addressBondsRewards.fetch( + let userAccBefore = await program.account.addressBondsRewards.fetch( userBondsRewards ); @@ -2196,55 +2436,19 @@ describe("core-sol-bond-stake-sc", () => { bond: bond1, authority: user2.publicKey, }) - .rpc({ skipPreflight: true }); - - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program - ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards - ); - - assert(addressBondsFetched.addressTotalBondAmount.toNumber() == 100e9); - - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() == normalWeighted - ); - - let rewardsConfigAcc = await program.account.rewardsConfig.fetch( - rewardsConfigPda - ); - - let total_rewards = await calculateTotalRewardsInInterval( - activation_slot, - rewardsConfigPda, - program - ); + .rpc(); - let userRewardsAcc = await program.account.addressBondsRewards.fetch( + let userAccAfter = await program.account.addressBondsRewards.fetch( userBondsRewards ); - let user_rewards = await calculateUserRewards( - normalWeighted, - addressRewardsAccBefore.addressRewardsPerShare, - userBondsBefore.addressTotalBondAmount, - rewardsConfigPda, - true, - program + expect(userAccAfter.claimableAmount.toNumber()).to.equal( + userAccBefore.claimableAmount.toNumber() + 10e5 ); - assert( - addressRewardsAccBefore.claimableAmount.toNumber() + - user_rewards / 10 ** 9 === - userRewardsAcc.claimableAmount.toNumber() + expect(userAccAfter.addressRewardsPerShare.toNumber()).to.equal( + userAccBefore.addressRewardsPerShare.toNumber() + 10e3 ); - - assert(rewardsConfigAcc.accumulatedRewards.toNumber() == total_rewards); }); it("Check user rewards - bond 3 by user", async () => { @@ -2259,14 +2463,15 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond3 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([3])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(3).toBuffer("le", 2), + ], program.programId )[0]; - let addressRewardsAccBefore = - await program.account.addressBondsRewards.fetch(userBondsRewards); - - let userBondsBefore = await program.account.addressBondsRewards.fetch( + let userAccBefore = await program.account.addressBondsRewards.fetch( userBondsRewards ); @@ -2276,7 +2481,6 @@ describe("core-sol-bond-stake-sc", () => { 3, new anchor.BN(100e9), new anchor.BN(Number(user_nft_leaf_schemas[2].nonce)), - false, Array.from(bs58.decode(user_nft_leaf_schemas[2].id)), Array.from(user_nft_leaf_schemas[2].dataHash), Array.from(user_nft_leaf_schemas[2].creatorHash) @@ -2308,70 +2512,102 @@ describe("core-sol-bond-stake-sc", () => { ]) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program - ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( + let userAccAfter = await program.account.addressBondsRewards.fetch( userBondsRewards ); - const tolerance = normalWeighted * 0.0001; - const lowerBound = normalWeighted - tolerance; - const upperBound = normalWeighted + tolerance; - - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() >= lowerBound && - addressBondsFetched.weightedLivelinessScore.toNumber() <= upperBound, - `Score ${addressBondsFetched.weightedLivelinessScore.toNumber()} is out of range!` + expect(userAccAfter.claimableAmount.toNumber()).to.equal( + userAccBefore.claimableAmount.toNumber() + 10e5 ); - let userRewardsAcc = await program.account.addressBondsRewards.fetch( - userBondsRewards + expect(userAccAfter.addressRewardsPerShare.toNumber()).to.equal( + userAccBefore.addressRewardsPerShare.toNumber() + 10e3 ); + }); - let user_rewards = await calculateUserRewards( - addressBondsFetched.weightedLivelinessScore.toNumber(), - addressRewardsAccBefore.addressRewardsPerShare, - userBondsBefore.addressTotalBondAmount, - rewardsConfigPda, - true, - program - ); + it("Stake rewards user2 - no vault set (should fail)", async () => { + const addressBondsRewards = PublicKey.findProgramAddressSync( + [Buffer.from("address_bonds_rewards"), user2.publicKey.toBuffer()], + program.programId + )[0]; - assert( - addressRewardsAccBefore.claimableAmount.toNumber() + - user_rewards / 10 ** 9 === - userRewardsAcc.claimableAmount.toNumber() - ); + const bond = PublicKey.findProgramAddressSync( + [ + Buffer.from("bond"), + user2.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], + program.programId + )[0]; + + try { + let x = await program.methods + .stakeRewards(1, 1) + .signers([user2]) + .accounts({ + addressBondsRewards: addressBondsRewards, + bondConfig: bondConfigPda1, + rewardsConfig: rewardsConfigPda, + vaultConfig: vaultConfigPda, + bond: bond, + authority: user2.publicKey, + }) + .rpc(); + } catch (e) { + expect((e as anchor.AnchorError).error.errorCode.number).to.equal(6019); + expect((e as anchor.AnchorError).error.errorMessage).to.equal( + "Vault bond id mismatch" + ); + } }); - it("Stake rewards user2", async () => { + it("Set vault bond id 1 by user2", async () => { const addressBondsRewards = PublicKey.findProgramAddressSync( [Buffer.from("address_bonds_rewards"), user2.publicKey.toBuffer()], program.programId )[0]; const bond = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user2.publicKey.toBuffer(), Buffer.from([1])], + [ + Buffer.from("bond"), + user2.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], program.programId )[0]; - let rewardsConfigAcc = await program.account.rewardsConfig.fetch( - rewardsConfigPda - ); + await program.methods + .updateVaultBond( + 1, + 1, + new anchor.BN(Number(user2_nft_leaf_schemas[0].nonce)) + ) + .signers([user2]) + .accounts({ + addressBondsRewards: addressBondsRewards, + bond: bond, + bondConfig: bondConfigPda1, + authority: user2.publicKey, + }) + .rpc(); + }); - let addressRewardsAccBefore = - await program.account.addressBondsRewards.fetch(addressBondsRewards); + it("Stake rewards user2 into vault bond", async () => { + const addressBondsRewards = PublicKey.findProgramAddressSync( + [Buffer.from("address_bonds_rewards"), user2.publicKey.toBuffer()], + program.programId + )[0]; - let bondBefore = await program.account.bond.fetch(bond); + const bond = PublicKey.findProgramAddressSync( + [ + Buffer.from("bond"), + user2.publicKey.toBuffer(), + new anchor.BN(1).toBuffer("le", 2), + ], + program.programId + )[0]; - let userBondsBefore = await program.account.addressBondsRewards.fetch( - addressBondsRewards - ); + let bondAccBefore = await program.account.bond.fetch(bond); let x = await program.methods .stakeRewards(1, 1) @@ -2386,49 +2622,22 @@ describe("core-sol-bond-stake-sc", () => { }) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - addressBondsRewards, - bondConfigPda1, - program - ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - addressBondsRewards - ); - - const tolerance = normalWeighted * 0.0001; - const lowerBound = normalWeighted - tolerance; - const upperBound = normalWeighted + tolerance; - - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() >= lowerBound && - addressBondsFetched.weightedLivelinessScore.toNumber() <= upperBound, - `Score ${addressBondsFetched.weightedLivelinessScore.toNumber()} is out of range!` + let rewardsConfigAcc = await program.account.rewardsConfig.fetch( + rewardsConfigPda ); - - let userRewardsAcc = await program.account.addressBondsRewards.fetch( + let bondAccAfter = await program.account.bond.fetch(bond); + let userAcc = await program.account.addressBondsRewards.fetch( addressBondsRewards ); - let bondAfter = await program.account.bond.fetch(bond); - - let user_rewards = await calculateUserRewards( - addressBondsFetched.weightedLivelinessScore.toNumber(), - addressRewardsAccBefore.addressRewardsPerShare, - userBondsBefore.addressTotalBondAmount, - rewardsConfigPda, - false, - program + expect(bondAccAfter.bondAmount.toNumber()).to.equal( + bondAccBefore.bondAmount.toNumber() + 2166600 ); - assert( - addressRewardsAccBefore.claimableAmount.toNumber() + - user_rewards / 10 ** 9 + - bondBefore.bondAmount.toNumber() === - bondAfter.bondAmount.toNumber() + expect(userAcc.claimableAmount.toNumber()).to.equal(0); + expect(userAcc.addressRewardsPerShare.toNumber()).to.equal( + rewardsConfigAcc.rewardsPerShare.toNumber() ); - assert(userRewardsAcc.claimableAmount.toNumber() === 0); }); it("Claim rewards user", async () => { @@ -2437,24 +2646,30 @@ describe("core-sol-bond-stake-sc", () => { program.programId )[0]; - let addressRewardsAccBefore = - await program.account.addressBondsRewards.fetch(addressBondsRewards); - let userBondsBefore = await program.account.addressBondsRewards.fetch( - addressBondsRewards - ); + const bond = PublicKey.findProgramAddressSync( + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(2).toBuffer("le", 2), + ], + program.programId + )[0]; - let user_balance_before = await provider.connection.getTokenAccountBalance( - itheum_token_user_ata - ); + let userBalanceBefore = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; let x = await program.methods - .claimRewards(1) + .claimRewards(1, 2) .signers([user]) .accounts({ addressBondsRewards: addressBondsRewards, bondConfig: bondConfigPda1, rewardsConfig: rewardsConfigPda, vaultConfig: vaultConfigPda, + bond: bond, vault: vault_ata, mintOfTokenToReceive: itheum_token_mint.publicKey, authority: user.publicKey, @@ -2462,51 +2677,28 @@ describe("core-sol-bond-stake-sc", () => { }) .rpc(); - let normalWeighted = await calculateWeightedLivelinessScore( - x, - addressBondsRewards, - bondConfigPda1, - program - ); - - let addressBondsFetched = await program.account.addressBondsRewards.fetch( + let userAccAfter = await program.account.addressBondsRewards.fetch( addressBondsRewards ); - const tolerance = normalWeighted * 0.0001; - const lowerBound = normalWeighted - tolerance; - const upperBound = normalWeighted + tolerance; - - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() >= lowerBound && - addressBondsFetched.weightedLivelinessScore.toNumber() <= upperBound, - `Score ${addressBondsFetched.weightedLivelinessScore.toNumber()} is out of range!` - ); + let userBalanceAfter = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; - let user_rewards = await calculateUserRewards( - addressBondsFetched.weightedLivelinessScore.toNumber(), - addressRewardsAccBefore.addressRewardsPerShare, - userBondsBefore.addressTotalBondAmount, - rewardsConfigPda, - false, - program + let rewardsConfigAcc = await program.account.rewardsConfig.fetch( + rewardsConfigPda ); - let user_balance_after = await provider.connection.getTokenAccountBalance( - itheum_token_user_ata + expect(userAccAfter.addressRewardsPerShare.toNumber()).to.equal( + rewardsConfigAcc.rewardsPerShare.toNumber() ); - - assert( - addressRewardsAccBefore.claimableAmount.toNumber() + - user_rewards / 10 ** 9 + - Number(user_balance_before.value.amount) === - Number(user_balance_after.value.amount) + expect(userAccAfter.claimableAmount.toNumber()).to.equal(0); + expect(userAccAfter.claimableAmount.toNumber()).to.equal(0); + expect(Number(userBalanceAfter)).to.equal( + Number(userBalanceBefore) + 3499800 ); - - let addressRewardsAccAfter = - await program.account.addressBondsRewards.fetch(addressBondsRewards); - - assert(addressRewardsAccAfter.claimableAmount.toNumber() === 0); }); it("Withdraw bond 2 by user", async () => { @@ -2516,10 +2708,20 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond2 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([2])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(2).toBuffer("le", 2), + ], program.programId )[0]; + let userBalanceBefore = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; + let x = await program.methods .withdraw(1, 2) .signers([user]) @@ -2536,28 +2738,17 @@ describe("core-sol-bond-stake-sc", () => { }) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program - ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards - ); - - assert(addressBondsFetched.addressTotalBondAmount.toNumber() === 100e9); + let userBalanceAfter = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; - const tolerance = normalWeighted * 0.001; - const lowerBound = normalWeighted - tolerance; - const upperBound = normalWeighted + tolerance; + let bondAcc = await program.account.bond.fetch(bond2); - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() >= lowerBound && - addressBondsFetched.weightedLivelinessScore.toNumber() <= upperBound, - `Score ${addressBondsFetched.weightedLivelinessScore.toNumber()} is out of range!` - ); + expect(bondAcc.state).to.equal(0); + expect(bondAcc.bondAmount.toNumber()).to.equal(0); + expect(Number(userBalanceAfter)).to.equal(Number(750003499800)); }); it("Withdraw bond 3 by user", async () => { @@ -2567,10 +2758,20 @@ describe("core-sol-bond-stake-sc", () => { )[0]; const bond3 = PublicKey.findProgramAddressSync( - [Buffer.from("bond"), user.publicKey.toBuffer(), Buffer.from([3])], + [ + Buffer.from("bond"), + user.publicKey.toBuffer(), + new anchor.BN(3).toBuffer("le", 2), + ], program.programId )[0]; + let userBalanceBefore = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; + let x = await program.methods .withdraw(1, 3) .signers([user]) @@ -2587,153 +2788,16 @@ describe("core-sol-bond-stake-sc", () => { }) .rpc(); - const normalWeighted = await calculateWeightedLivelinessScore( - x, - userBondsRewards, - bondConfigPda1, - program - ); - - const addressBondsFetched = await program.account.addressBondsRewards.fetch( - userBondsRewards - ); - - assert(addressBondsFetched.addressTotalBondAmount.toNumber() === 0); + let userBalanceAfter = ( + await program.provider.connection.getTokenAccountBalance( + itheum_token_user_ata + ) + ).value.amount; - const tolerance = normalWeighted * 0.001; - const lowerBound = normalWeighted - tolerance; - const upperBound = normalWeighted + tolerance; + let bondAcc = await program.account.bond.fetch(bond3); - assert( - addressBondsFetched.weightedLivelinessScore.toNumber() >= lowerBound && - addressBondsFetched.weightedLivelinessScore.toNumber() <= upperBound, - `Score ${addressBondsFetched.weightedLivelinessScore.toNumber()} is out of range!` - ); + expect(bondAcc.state).to.equal(0); + expect(bondAcc.bondAmount.toNumber()).to.equal(0); + expect(Number(userBalanceAfter)).to.equal(Number(800003499800)); }); }); - -function delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -async function calculateTotalRewardsInInterval( - previous_slot: number, - rewardsConfigPda: PublicKey, - program: anchor.Program -) { - let rewards_config = await program.account.rewardsConfig.fetch( - rewardsConfigPda - ); - - let slots = rewards_config.lastRewardSlot.toNumber() - previous_slot; - - let total_rewards = rewards_config.rewardsPerSlot.mul(new anchor.BN(slots)); - - return total_rewards.toNumber(); -} - -// calculate user rewards based on the total rewards -async function calculateUserRewards( - normalWeighted: number, - address_last_rewards_per_share: anchor.BN, - address_last_total_bond_amount: anchor.BN, - rewards_config: PublicKey, - bypassLiveliness: boolean, - program: anchor.Program -) { - const rewardsAcc = await program.account.rewardsConfig.fetch(rewards_config); - - let user_rewards = address_last_total_bond_amount.mul( - rewardsAcc.rewardsPerShare.sub(address_last_rewards_per_share) - ); - - if (normalWeighted >= 9500 || bypassLiveliness) { - return user_rewards.toNumber(); - } else { - return (user_rewards.toNumber() * normalWeighted) / 10000; - } -} - -async function calculateWeightedLivelinessScore( - signature: TransactionSignature, - userBondsPda: PublicKey, - bondsConfigPda: PublicKey, - program: anchor.Program -) { - let newConn = new Connection("http://localhost:8899", "confirmed"); - - let sigStatus = await newConn.getSignatureStatus(signature); - - let blockTime = await newConn.getBlockTime(sigStatus.context.slot); - if (!blockTime) { - throw new Error("Unable to fetch block time for the provided slot"); - } - - let current_timestamp = blockTime; - - const userBondsAcc = await program.account.addressBondsRewards.fetch( - userBondsPda - ); - - const bondConfigAcc = await program.account.bondConfig.fetch(bondsConfigPda); - - let totalBondAmount = new anchor.BN(0); - let totalBondWeight = new anchor.BN(0); - - for (let bond_id = 1; bond_id <= userBondsAcc.currentIndex; bond_id++) { - const bond_pda = PublicKey.findProgramAddressSync( - [ - Buffer.from("bond"), - userBondsAcc.address.toBuffer(), - Buffer.from([bond_id]), - ], - program.programId - )[0]; - - const bondAcc = await program.account.bond.fetch(bond_pda); - - if (bondAcc.state === 0) { - continue; - } - - const score = computeBondScore( - bondConfigAcc.lockPeriod.toNumber(), - current_timestamp, - bondAcc.unbondTimestamp.toNumber() - ); - - const bond_weight = bondAcc.bondAmount.mul(new anchor.BN(score)); - totalBondWeight = totalBondWeight.add(bond_weight); - - totalBondAmount = totalBondAmount.add(bondAcc.bondAmount); - } - - if ( - totalBondAmount.eq(new anchor.BN(0)) || - totalBondWeight.eq(new anchor.BN(0)) - ) { - return 0; - } - const weighted_score = totalBondWeight.div(totalBondAmount); - - return weighted_score.toNumber(); -} - -function computeBondScore( - lockPeriod: number, - currentTimestamp: number, - unbondTimestamp: number -): number { - if (currentTimestamp >= unbondTimestamp) { - return 0; - } else { - const difference = unbondTimestamp - currentTimestamp; - - if (lockPeriod === 0) { - return 0; - } else { - const divResult = Math.floor(10000 / lockPeriod); - return divResult * difference; - } - } -} From 2fb8a3d4b100ef105c76ca9cb4b729eefef5e20c Mon Sep 17 00:00:00 2001 From: bucurdavid Date: Thu, 7 Nov 2024 09:48:20 +0200 Subject: [PATCH 2/2] fix: more precision on liveliness score --- programs/core-sol-bond-stake-sc/src/utils.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/programs/core-sol-bond-stake-sc/src/utils.rs b/programs/core-sol-bond-stake-sc/src/utils.rs index e1651a1..9158ab3 100644 --- a/programs/core-sol-bond-stake-sc/src/utils.rs +++ b/programs/core-sol-bond-stake-sc/src/utils.rs @@ -1,6 +1,8 @@ use anchor_lang::prelude::*; use solana_program::clock; +use crate::DIVISION_SAFETY_CONST; + #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, PartialEq)] pub enum State { Inactive = 0, @@ -32,8 +34,15 @@ pub fn compute_bond_score(lock_period: u64, current_timestamp: u64, unbond_times if lock_period == 0 { 0 } else { - let div_result = 10000u64.checked_div(lock_period).unwrap_or(0); - div_result.checked_mul(difference).unwrap_or(0) + let div_result = 10_000_000_000_000u64.checked_div(lock_period).unwrap_or(0); + + let liveliness = div_result + .checked_mul(difference) + .unwrap_or(0) + .checked_div(DIVISION_SAFETY_CONST) + .unwrap_or(0); + + liveliness } } }