Skip to content

Commit

Permalink
Bugfixes for HIP-138 delegation claims (#756)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChewingGlass authored Dec 13, 2024
1 parent cbba212 commit 8a5c881
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 40 deletions.
23 changes: 12 additions & 11 deletions packages/spl-utils/src/fetchBackwardsCompatibleIdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1340,21 +1340,21 @@ const IDLS_BY_PROGRAM: Record<string, any> = {
isSigner: false,
},
{
name: "subDaoEpochInfo",
name: "daoEpochInfo",
isMut: false,
isSigner: false,
pda: {
seeds: [
{
kind: "const",
type: "string",
value: "sub_dao_epoch_info",
value: "dao_epoch_info",
},
{
kind: "account",
type: "publicKey",
account: "SubDaoV0",
path: "sub_dao",
account: "DaoV0",
path: "dao",
},
{
kind: "arg",
Expand Down Expand Up @@ -2314,6 +2314,14 @@ const IDLS_BY_PROGRAM: Record<string, any> = {
],
},
},
{
name: "delegationRewardsIssued",
type: "u64",
},
{
name: "vehntAtEpochStart",
type: "u64",
},
],
},
},
Expand Down Expand Up @@ -2448,13 +2456,6 @@ const IDLS_BY_PROGRAM: Record<string, any> = {
name: "dcOnboardingFeesPaid",
type: "u64",
},
{
name: "hntDelegationRewardsIssued",
docs: [
"The number of hnt delegation rewards issued this epoch, so that delegators can claim their share of the rewards",
],
type: "u64",
},
{
name: "hntRewardsIssued",
docs: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import {
EPOCH_LENGTH,
PROGRAM_ID as HSD_PROGRAM_ID,
daoEpochInfoKey,
daoKey,
delegatedPositionKey,
init,
Expand All @@ -22,6 +23,7 @@ import {
getAssociatedTokenAddressSync,
} from "@solana/spl-token";
import {
AccountInfo,
Connection,
PublicKey,
SYSVAR_CLOCK_PUBKEY,
Expand Down Expand Up @@ -88,7 +90,7 @@ export const formPositionClaims = async ({
}
return acc;
}, {} as Record<string, SubDao>);

const daoAcc = await hsdProgram.account.daoV0.fetch(Object.values(subDaos)[0].dao);

for (const [idx, position] of positions.entries()) {
bucketedEpochsByPosition[position.pubkey.toBase58()] =
Expand Down Expand Up @@ -127,10 +129,58 @@ export const formPositionClaims = async ({

// Chunk size is 128 because we want each chunk to correspond to the 128 bits in bitmap
for (const chunk of chunks(epochsToClaim, 128)) {
const daoEpochInfoKeys = chunk.map((epoch) =>
daoEpochInfoKey(subDaoAcc.dao, epoch.mul(new BN(EPOCH_LENGTH)))[0]
);
const daoEpochInfoAccounts = await getMultipleAccounts({
connection: hsdProgram.provider.connection,
keys: daoEpochInfoKeys,
});
bucketedEpochsByPosition[position.pubkey.toBase58()].push(
await Promise.all(
chunk.map((epoch) =>
hsdProgram.methods
chunk.map((epoch, index) => {
const daoEpochInfoAccount = daoEpochInfoAccounts[index];
const daoEpochInfoData = hsdProgram.coder.accounts.decode(
"daoEpochInfoV0",
daoEpochInfoAccount?.data
);

if (daoEpochInfoData.delegationRewardsIssued.gt(new BN(0))) {
return hsdProgram.methods
.claimRewardsV1({
epoch,
})
.accountsStrict({
position: position.pubkey,
mint: position.mint,
positionTokenAccount: getAssociatedTokenAddressSync(
position.mint,
provider.wallet.publicKey
),
positionAuthority: provider.wallet.publicKey,
registrar: position.registrar,
dao: DAO,
subDao: delegatedPosition.account!.subDao,
delegatedPosition: delegatedPosition.key,
hntMint: daoAcc.hntMint,
daoEpochInfo: daoEpochInfoKey(subDaoAcc.dao, epoch.mul(new BN(EPOCH_LENGTH)))[0],
delegatorPool: daoAcc.delegatorPool,
delegatorAta: getAssociatedTokenAddressSync(
daoAcc.hntMint,
provider.wallet.publicKey
),
delegatorPoolCircuitBreaker: accountWindowedBreakerKey(
daoAcc.delegatorPool
)[0],
vsrProgram: VSR_PROGRAM_ID,
systemProgram: SystemProgram.programId,
circuitBreakerProgram: CIRCUIT_BREAKER_PROGRAM_ID,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
})
.instruction();
} else {
return hsdProgram.methods
.claimRewardsV0({
epoch,
})
Expand Down Expand Up @@ -165,8 +215,9 @@ export const formPositionClaims = async ({
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
tokenProgram: TOKEN_PROGRAM_ID,
})
.instruction()
)
.instruction();
}
})
)
);
}
Expand All @@ -189,3 +240,20 @@ export const formPositionClaims = async ({

return instructions;
};

async function getMultipleAccounts({
connection,
keys,
}): Promise<AccountInfo<Buffer>[]> {
const batchSize = 100;
const batches = Math.ceil(keys.length / batchSize);
const results: AccountInfo<Buffer>[] = [];

for (let i = 0; i < batches; i++) {
const batchKeys = keys.slice(i * batchSize, (i + 1) * batchSize);
const batchResults = await connection.getMultipleAccountsInfo(batchKeys);
results.push(...batchResults);
}

return results;
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ pub fn handler(
))
.unwrap();

ctx.accounts.dao_epoch_info.vehnt_at_epoch_start +=
ctx.accounts.sub_dao_epoch_info.vehnt_at_epoch_start;

ctx.accounts.dao_epoch_info.epoch = args.epoch;

ctx.accounts.dao_epoch_info.current_hnt_supply = curr_supply
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ pub struct ClaimRewardsV1<'info> {
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()],
seeds = ["dao_epoch_info".as_bytes(), dao.key().as_ref(), &args.epoch.to_le_bytes()],
bump,
constraint = sub_dao_epoch_info.rewards_issued_at.is_some() @ ErrorCode::EpochNotClosed
constraint = dao_epoch_info.done_issuing_rewards @ ErrorCode::EpochNotClosed
)]
pub sub_dao_epoch_info: Box<Account<'info, SubDaoEpochInfoV0>>,
pub dao_epoch_info: Box<Account<'info, DaoEpochInfoV0>>,
#[account(mut)]
pub delegator_pool: Box<Account<'info, TokenAccount>>,
#[account(
Expand Down Expand Up @@ -137,29 +137,22 @@ pub fn handler(ctx: Context<ClaimRewardsV1>, args: ClaimRewardsArgsV0) -> Result
}
}

let delegated_vehnt_at_epoch = position.voting_power(
voting_mint_config,
ctx.accounts.sub_dao_epoch_info.start_ts(),
)?;
let delegated_vehnt_at_epoch =
position.voting_power(voting_mint_config, ctx.accounts.dao_epoch_info.start_ts())?;

msg!("Staked {} veHNT at start of epoch with {} total veHNT delegated to subdao and {} total rewards to subdao",
msg!("Staked {} veHNT at start of epoch with {} total veHNT delegated to dao and {} total rewards to dao",
delegated_vehnt_at_epoch,
ctx.accounts.sub_dao_epoch_info.vehnt_at_epoch_start,
ctx.accounts.sub_dao_epoch_info.hnt_delegation_rewards_issued
ctx.accounts.dao_epoch_info.vehnt_at_epoch_start,
ctx.accounts.dao_epoch_info.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,
)
.checked_mul(ctx.accounts.dao_epoch_info.delegation_rewards_issued as u128)
.unwrap()
.checked_div(ctx.accounts.sub_dao_epoch_info.vehnt_at_epoch_start as u128)
.checked_div(ctx.accounts.dao_epoch_info.vehnt_at_epoch_start as u128)
.unwrap(),
)
.unwrap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ pub fn handler(ctx: Context<DelegateV0>) -> Result<()> {
rewards_issued_at: None,
initialized: false,
dc_onboarding_fees_paid: 0,
hnt_delegation_rewards_issued: 0,
hnt_rewards_issued: 0,
},
},
Expand Down
6 changes: 2 additions & 4 deletions programs/helium-sub-daos/src/instructions/issue_rewards_v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,8 @@ pub fn handler(ctx: Context<IssueRewardsV0>, args: IssueRewardsArgsV0) -> Result
ctx.accounts.sub_dao_epoch_info.hnt_rewards_issued = escrow_amount;
ctx.accounts.dao_epoch_info.num_rewards_issued += 1;
ctx.accounts.sub_dao_epoch_info.rewards_issued_at = Some(Clock::get()?.unix_timestamp);
ctx
.accounts
.sub_dao_epoch_info
.hnt_delegation_rewards_issued = delegation_rewards_amount;
ctx.accounts.dao_epoch_info.delegation_rewards_issued = delegation_rewards_amount;
ctx.accounts.sub_dao_epoch_info.delegation_rewards_issued = 0;
ctx.accounts.dao_epoch_info.done_issuing_rewards =
ctx.accounts.dao.num_sub_daos == ctx.accounts.dao_epoch_info.num_rewards_issued;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub fn handler(ctx: Context<TempResizeAccount>) -> Result<()> {
// This is the dao account.
if account.key() == Pubkey::from_str("BQ3MCuTT5zVBhNfQ4SjMh3NPVhFy73MPV8rjfq5d1zie").unwrap() {
new_size += 104; // Add space for rewards_escrow, delegator_pool, delegator_rewards_percent, proposal_namespace
} else {
new_size += 8; // Delegation rewards issued
}
let rent = Rent::get()?;
let new_minimum_balance = rent.minimum_balance(new_size);
Expand Down
13 changes: 11 additions & 2 deletions programs/helium-sub-daos/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ pub struct DaoEpochInfoV0 {
pub done_issuing_hst_pool: bool,
pub bump_seed: u8,
pub recent_proposals: [RecentProposal; 4],
// The number of delegation rewards issued this epoch, so that delegators can claim their share of the rewards
pub delegation_rewards_issued: u64,
pub vehnt_at_epoch_start: u64,
}

#[derive(Debug, InitSpace, Clone, AnchorSerialize, AnchorDeserialize, Default)]
Expand All @@ -167,6 +170,14 @@ impl DaoEpochInfoV0 {
pub fn size() -> usize {
60 + 8 + std::mem::size_of::<DaoEpochInfoV0>()
}

pub fn start_ts(&self) -> i64 {
i64::try_from(self.epoch).unwrap() * EPOCH_LENGTH
}

pub fn end_ts(&self) -> i64 {
i64::try_from(self.epoch + 1).unwrap() * EPOCH_LENGTH
}
}

#[account]
Expand Down Expand Up @@ -266,8 +277,6 @@ pub struct SubDaoEpochInfoV0 {
pub bump_seed: u8,
pub initialized: bool,
pub dc_onboarding_fees_paid: u64,
/// The number of hnt delegation rewards issued this epoch, so that delegators can claim their share of the rewards
pub hnt_delegation_rewards_issued: u64,
/// The number of hnt rewards issued to the reward escrow this epoch
pub hnt_rewards_issued: u64,
}
Expand Down

0 comments on commit 8a5c881

Please sign in to comment.