-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cd643ed
commit 6e11f4f
Showing
9 changed files
with
310 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
171 changes: 171 additions & 0 deletions
171
programs/helium-sub-daos/src/instructions/delegation/claim_rewards_v1.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
use anchor_lang::prelude::*; | ||
use anchor_spl::{ | ||
associated_token::AssociatedToken, | ||
token::{Mint, Token, TokenAccount}, | ||
}; | ||
use circuit_breaker::{ | ||
cpi::{accounts::TransferV0, transfer_v0}, | ||
CircuitBreaker, TransferArgsV0, | ||
}; | ||
use voter_stake_registry::{ | ||
state::{PositionV0, Registrar}, | ||
VoterStakeRegistry, | ||
}; | ||
|
||
use crate::{current_epoch, error::ErrorCode, state::*, TESTING}; | ||
|
||
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] | ||
pub struct ClaimRewardsArgsV0 { | ||
pub epoch: u64, | ||
} | ||
|
||
#[derive(Accounts)] | ||
#[instruction(args: ClaimRewardsArgsV0)] | ||
pub struct ClaimRewardsV0<'info> { | ||
#[account( | ||
seeds = [b"position".as_ref(), mint.key().as_ref()], | ||
seeds::program = vsr_program.key(), | ||
bump = position.bump_seed, | ||
has_one = mint, | ||
has_one = registrar, | ||
)] | ||
pub position: Box<Account<'info, PositionV0>>, | ||
pub mint: Box<Account<'info, Mint>>, | ||
#[account( | ||
token::mint = mint, | ||
token::authority = position_authority, | ||
constraint = position_token_account.amount > 0 | ||
)] | ||
pub position_token_account: Box<Account<'info, TokenAccount>>, | ||
#[account(mut)] | ||
pub position_authority: Signer<'info>, | ||
pub registrar: Box<Account<'info, Registrar>>, | ||
#[account( | ||
has_one = registrar, | ||
has_one = hnt_mint | ||
)] | ||
pub dao: Box<Account<'info, DaoV0>>, | ||
|
||
#[account( | ||
mut, | ||
has_one = hnt_delegator_pool, | ||
has_one = dao, | ||
)] | ||
pub sub_dao: Account<'info, SubDaoV0>, | ||
#[account( | ||
mut, | ||
has_one = sub_dao, | ||
seeds = ["delegated_position".as_bytes(), position.key().as_ref()], | ||
bump, | ||
)] | ||
pub delegated_position: Account<'info, DelegatedPositionV0>, | ||
|
||
pub hnt_mint: Box<Account<'info, Mint>>, | ||
|
||
#[account( | ||
seeds = ["sub_dao_epoch_info".as_bytes(), sub_dao.key().as_ref(), &args.epoch.to_le_bytes()], | ||
bump, | ||
constraint = sub_dao_epoch_info.rewards_issued_at.is_some() @ ErrorCode::EpochNotClosed | ||
)] | ||
pub sub_dao_epoch_info: Box<Account<'info, SubDaoEpochInfoV0>>, | ||
#[account(mut)] | ||
pub hnt_delegator_pool: Box<Account<'info, TokenAccount>>, | ||
#[account( | ||
init_if_needed, | ||
payer = position_authority, | ||
associated_token::mint = hnt_mint, | ||
associated_token::authority = position_authority, | ||
)] | ||
pub delegator_ata: Box<Account<'info, TokenAccount>>, | ||
|
||
/// CHECK: checked via cpi | ||
#[account( | ||
mut, | ||
seeds = ["account_windowed_breaker".as_bytes(), hnt_delegator_pool.key().as_ref()], | ||
seeds::program = circuit_breaker_program.key(), | ||
bump | ||
)] | ||
pub delegator_pool_circuit_breaker: AccountInfo<'info>, | ||
|
||
pub vsr_program: Program<'info, VoterStakeRegistry>, | ||
pub system_program: Program<'info, System>, | ||
pub circuit_breaker_program: Program<'info, CircuitBreaker>, | ||
pub associated_token_program: Program<'info, AssociatedToken>, | ||
pub token_program: Program<'info, Token>, | ||
} | ||
|
||
impl<'info> ClaimRewardsV0<'info> { | ||
fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, TransferV0<'info>> { | ||
let cpi_accounts = TransferV0 { | ||
from: self.hnt_delegator_pool.to_account_info(), | ||
to: self.delegator_ata.to_account_info(), | ||
owner: self.sub_dao.to_account_info(), | ||
circuit_breaker: self.delegator_pool_circuit_breaker.to_account_info(), | ||
token_program: self.token_program.to_account_info(), | ||
}; | ||
|
||
CpiContext::new(self.circuit_breaker_program.to_account_info(), cpi_accounts) | ||
} | ||
} | ||
|
||
pub fn handler(ctx: Context<ClaimRewardsV0>, args: ClaimRewardsArgsV0) -> Result<()> { | ||
// load the vehnt information | ||
let position = &mut ctx.accounts.position; | ||
let registrar = &ctx.accounts.registrar; | ||
let voting_mint_config = ®istrar.voting_mints[position.voting_mint_config_idx as usize]; | ||
|
||
let delegated_position = &mut ctx.accounts.delegated_position; | ||
|
||
// check epoch that's being claimed is over | ||
let epoch = current_epoch(registrar.clock_unix_timestamp()); | ||
if !TESTING { | ||
require_gt!(epoch, args.epoch, ErrorCode::EpochNotOver); | ||
if delegated_position.is_claimed(args.epoch)? { | ||
return Err(error!(ErrorCode::InvalidClaimEpoch)); | ||
} | ||
} | ||
|
||
let delegated_vehnt_at_epoch = position.voting_power( | ||
voting_mint_config, | ||
ctx.accounts.sub_dao_epoch_info.start_ts(), | ||
)?; | ||
|
||
msg!("Staked {} veHNT at start of epoch with {} total veHNT delegated to subdao and {} total rewards to subdao", | ||
delegated_vehnt_at_epoch, | ||
ctx.accounts.sub_dao_epoch_info.vehnt_at_epoch_start, | ||
ctx.accounts.sub_dao_epoch_info.hnt_delegation_rewards_issued | ||
); | ||
|
||
// calculate the position's share of that epoch's rewards | ||
// rewards = staking_rewards_issued * staked_vehnt_at_epoch / total_vehnt | ||
let rewards = u64::try_from( | ||
delegated_vehnt_at_epoch | ||
.checked_mul( | ||
ctx | ||
.accounts | ||
.sub_dao_epoch_info | ||
.hnt_delegation_rewards_issued as u128, | ||
) | ||
.unwrap() | ||
.checked_div(ctx.accounts.sub_dao_epoch_info.vehnt_at_epoch_start as u128) | ||
.unwrap(), | ||
) | ||
.unwrap(); | ||
|
||
delegated_position.set_claimed(args.epoch)?; | ||
|
||
let amount_left = ctx.accounts.hnt_delegator_pool.amount; | ||
transfer_v0( | ||
ctx.accounts.transfer_ctx().with_signer(&[&[ | ||
b"sub_dao", | ||
ctx.accounts.sub_dao.dnt_mint.as_ref(), | ||
&[ctx.accounts.sub_dao.bump_seed], | ||
]]), | ||
// Due to rounding down of vehnt fall rates it's possible the vehnt on the dao does not exactly match the | ||
// vehnt remaining. It could be off by a little bit of dust. | ||
TransferArgsV0 { | ||
amount: std::cmp::min(rewards, amount_left), | ||
}, | ||
)?; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
programs/helium-sub-daos/src/instructions/initialize_hnt_delegator_pool.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
use anchor_lang::prelude::*; | ||
use anchor_spl::{ | ||
self, | ||
associated_token::AssociatedToken, | ||
token::{Mint, Token, TokenAccount}, | ||
}; | ||
use circuit_breaker::{ | ||
cpi::{accounts::InitializeAccountWindowedBreakerV0, initialize_account_windowed_breaker_v0}, | ||
CircuitBreaker, InitializeAccountWindowedBreakerArgsV0, ThresholdType as CBThresholdType, | ||
WindowedCircuitBreakerConfigV0 as CBWindowedCircuitBreakerConfigV0, | ||
}; | ||
use shared_utils::resize_to_fit; | ||
|
||
use crate::{state::*, sub_dao_seeds, EPOCH_LENGTH}; | ||
|
||
#[derive(Accounts)] | ||
pub struct InitializeHntDelegatorPoolV0<'info> { | ||
#[account(mut)] | ||
pub payer: Signer<'info>, | ||
#[account( | ||
mut, | ||
has_one = authority, | ||
has_one = hnt_mint | ||
)] | ||
pub dao: Box<Account<'info, DaoV0>>, | ||
pub authority: Signer<'info>, | ||
#[account(mut, has_one = dao)] | ||
pub sub_dao: Box<Account<'info, SubDaoV0>>, | ||
pub hnt_mint: Box<Account<'info, Mint>>, | ||
/// CHECK: Initialized via cpi | ||
#[account( | ||
mut, | ||
seeds = ["account_windowed_breaker".as_bytes(), delegator_pool.key().as_ref()], | ||
seeds::program = circuit_breaker_program.key(), | ||
bump | ||
)] | ||
pub delegator_pool_circuit_breaker: AccountInfo<'info>, | ||
#[account( | ||
init, | ||
payer = payer, | ||
seeds = ["delegator_pool".as_bytes(), hnt_mint.key().as_ref()], | ||
bump, | ||
token::mint = hnt_mint, | ||
token::authority = sub_dao, | ||
)] | ||
pub delegator_pool: Box<Account<'info, TokenAccount>>, | ||
|
||
pub system_program: Program<'info, System>, | ||
pub token_program: Program<'info, Token>, | ||
pub circuit_breaker_program: Program<'info, CircuitBreaker>, | ||
pub associated_token_program: Program<'info, AssociatedToken>, | ||
} | ||
|
||
impl<'info> InitializeHntDelegatorPoolV0<'info> { | ||
fn initialize_delegator_pool_breaker_ctx( | ||
&self, | ||
) -> CpiContext<'_, '_, '_, 'info, InitializeAccountWindowedBreakerV0<'info>> { | ||
let cpi_accounts = InitializeAccountWindowedBreakerV0 { | ||
payer: self.payer.to_account_info(), | ||
circuit_breaker: self.delegator_pool_circuit_breaker.to_account_info(), | ||
token_account: self.delegator_pool.to_account_info(), | ||
owner: self.sub_dao.to_account_info(), | ||
token_program: self.token_program.to_account_info(), | ||
system_program: self.system_program.to_account_info(), | ||
}; | ||
CpiContext::new(self.circuit_breaker_program.to_account_info(), cpi_accounts) | ||
} | ||
} | ||
|
||
pub fn handler(ctx: Context<InitializeHntDelegatorPoolV0>) -> Result<()> { | ||
let signer_seeds: &[&[&[u8]]] = &[sub_dao_seeds!(ctx.accounts.sub_dao)]; | ||
|
||
initialize_account_windowed_breaker_v0( | ||
ctx | ||
.accounts | ||
.initialize_delegator_pool_breaker_ctx() | ||
.with_signer(signer_seeds), | ||
InitializeAccountWindowedBreakerArgsV0 { | ||
authority: ctx.accounts.sub_dao.authority, | ||
config: CBWindowedCircuitBreakerConfigV0 { | ||
window_size_seconds: u64::try_from(EPOCH_LENGTH).unwrap(), | ||
threshold_type: CBThresholdType::Absolute, | ||
// Roughly 25% of the daily emissions | ||
threshold: ctx.accounts.dao.emission_schedule[0].emissions_per_epoch / 25, | ||
}, | ||
owner: ctx.accounts.sub_dao.key(), | ||
}, | ||
)?; | ||
ctx.accounts.sub_dao.hnt_delegator_pool = ctx.accounts.delegator_pool.key(); | ||
|
||
resize_to_fit( | ||
&ctx.accounts.payer.to_account_info(), | ||
&ctx.accounts.system_program.to_account_info(), | ||
&ctx.accounts.sub_dao, | ||
)?; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.