Skip to content

Commit

Permalink
Add the ability to extend delegation
Browse files Browse the repository at this point in the history
  • Loading branch information
ChewingGlass committed Jan 7, 2025
1 parent 6ebc744 commit 430f154
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 21 deletions.
11 changes: 9 additions & 2 deletions packages/helium-admin-cli/src/add-expiration-to-delegations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { min } from "bn.js";
import os from "os";
import yargs from "yargs/yargs";
import { loadKeypair } from "./utils";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";

export async function run(args: any = process.argv) {
const yarg = yargs(args).options({
Expand Down Expand Up @@ -58,17 +59,23 @@ export async function run(args: any = process.argv) {

const currTs = await getSolanaUnixTimestamp(provider);
const currTsBN = new anchor.BN(currTs.toString());
const proxyEndTs = proxyConfig.seasons.find(s => currTsBN.gt(s.start))?.end;
const proxyEndTs = proxyConfig.seasons.reverse().find(s => currTsBN.gte(s.start))?.end;
for (const [delegation, position] of zip(needsMigration, positionAccs)) {
const subDao = delegation.account.subDao;
const positionTokenAccount = (
await provider.connection.getTokenLargestAccounts(position.mint)
).value[0].address;
instructions.push(
await hsdProgram.methods
.addExpirationTs()
.extendExpirationTsV0()
.accountsStrict({
payer: wallet.publicKey,
position: delegation.account.position,
delegatedPosition: delegation.publicKey,
registrar: registrarK,
mint: position.mint,
authority: wallet.publicKey,
positionTokenAccount,
dao,
subDao: delegation.account.subDao,
oldClosingTimeSubDaoEpochInfo: subDaoEpochInfoKey(
Expand Down
3 changes: 2 additions & 1 deletion packages/helium-sub-daos-sdk/src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,10 @@ export const heliumSubDaosResolvers = combineResolvers(
owner: "positionAuthority",
}),
ataResolver({
instruction: "extendExpirationTsV0",
account: "positionTokenAccount",
mint: "mint",
owner: "positionAuthority",
owner: "authority",
}),
resolveIndividual(async ({ args, path, accounts }) => {
if (path[path.length - 1] == "clockwork") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { BN, Program } from "@coral-xyz/anchor";
import {
PROGRAM_ID,
delegatedPositionKey,
init,
subDaoEpochInfoKey,
} from "@helium/helium-sub-daos-sdk";
import { sendInstructions } from "@helium/spl-utils";
import { PublicKey, TransactionInstruction } from "@solana/web3.js";
import { useAsyncCallback } from "react-async-hook";
import { useHeliumVsrState } from "../contexts/heliumVsrContext";
import { PositionWithMeta, SubDaoWithMeta } from "../sdk/types";
import { fetchBackwardsCompatibleIdl } from "@helium/spl-utils";
import { PROGRAM_ID as PROXY_PROGRAM_ID, init as initProxy } from "@helium/nft-proxy-sdk";
import { PROGRAM_ID as VSR_PROGRAM_ID, init as initVsr } from "@helium/voter-stake-registry-sdk";
import { useSolanaUnixNow } from "@helium/helium-react-hooks";
export const useExtendDelegation = () => {
const { provider } = useHeliumVsrState();
const now = useSolanaUnixNow(60 * 5 * 1000)
const { error, loading, execute } = useAsyncCallback(
async ({
position,
programId = PROGRAM_ID,
onInstructions,
}: {
position: PositionWithMeta;
programId?: PublicKey;
// Instead of sending the transaction, let the caller decide
onInstructions?: (
instructions: TransactionInstruction[]
) => Promise<void>;
}) => {
const isInvalid =
!now || !provider || !provider.wallet || !position.isDelegated;
const idl = await fetchBackwardsCompatibleIdl(programId, provider as any);
const hsdProgram = await init(provider as any, programId, idl);
const proxyProgram = await initProxy(provider as any, PROXY_PROGRAM_ID, idl);
const vsrProgram = await initVsr(provider as any, VSR_PROGRAM_ID, idl);

if (loading) return;

if (isInvalid || !hsdProgram) {
throw new Error("Unable to extend delegation, Invalid params");
} else {
const instructions: TransactionInstruction[] = [];

const delegatedPosKey = delegatedPositionKey(position.pubkey)[0];
const delegatedPosAcc =
await hsdProgram.account.delegatedPositionV0.fetch(delegatedPosKey);
const registrarAcc = await vsrProgram.account.registrar.fetch(
position.registrar
);
const proxyConfigAcc = await proxyProgram.account.proxyConfigV0.fetch(
registrarAcc.proxyConfig
);
const newExpirationTs = proxyConfigAcc.seasons.reverse().find(
(season) => new BN(now!).gte(season.start)
)?.end;
if (!newExpirationTs) {
throw new Error("No new valid expiration ts found");
}
const oldExpirationTs = delegatedPosAcc.expirationTs;

const oldSubDaoEpochInfo = subDaoEpochInfoKey(
delegatedPosAcc.subDao,
oldExpirationTs
)[0];
const newSubDaoEpochInfo = subDaoEpochInfoKey(
delegatedPosAcc.subDao,
newExpirationTs
)[0];
instructions.push(
await hsdProgram.methods
.extendExpirationTsV0()
.accounts({
position: position.pubkey,
subDao: delegatedPosAcc.subDao,
oldClosingTimeSubDaoEpochInfo: oldSubDaoEpochInfo,
closingTimeSubDaoEpochInfo: newSubDaoEpochInfo,
})
.instruction()
);

if (onInstructions) {
await onInstructions(instructions);
} else {
await sendInstructions(provider, instructions);
}
}
}
);

return {
error,
loading,
delegatePosition: execute,
};
};
1 change: 1 addition & 0 deletions packages/voter-stake-registry-hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { useClaimPositionRewards } from "./hooks/useClaimPositionRewards";
export { useClosePosition } from "./hooks/useClosePosition";
export { useCreatePosition } from "./hooks/useCreatePosition";
export { useDao } from "./hooks/useDao";
export { useExtendDelegation } from "./hooks/useExtendDelegation";
export { useDelegatePosition } from "./hooks/useDelegatePosition";
export { useDelegatedPositions } from "./hooks/useDelegatedPositions";
export { useExtendPosition } from "./hooks/useExtendPosition";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
use std::{cmp::min, str::FromStr};

use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, TokenAccount};
use nft_proxy::ProxyConfigV0;
use voter_stake_registry::state::{PositionV0, Registrar};

use crate::{
caclulate_vhnt_info, current_epoch, id, DaoV0, DelegatedPositionV0, SubDaoEpochInfoV0, SubDaoV0,
TESTING,
};

#[derive(Accounts)]
pub struct AddExpirationTs<'info> {
pub struct ExtendExpirationTsV0<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
mut,
address = if TESTING {
payer.key()
} else {
Pubkey::from_str("hprdnjkbziK8NqhThmAn5Gu4XqrBbctX8du4PfJdgvW").unwrap()
}
has_one = mint,
)]
pub payer: Signer<'info>,
#[account(mut)]
pub position: Account<'info, PositionV0>,
pub mint: Box<Account<'info, Mint>>,
#[account(
token::mint = mint,
constraint = position_token_account.amount > 0
)]
pub position_token_account: Box<Account<'info, TokenAccount>>,
#[account(
constraint = authority.key() == position_token_account.owner || authority.key() == Pubkey::from_str("hprdnjkbziK8NqhThmAn5Gu4XqrBbctX8du4PfJdgvW").unwrap()
)]
pub authority: Signer<'info>,
#[account(
has_one = proxy_config
)]
Expand All @@ -41,13 +47,16 @@ pub struct AddExpirationTs<'info> {
has_one = position,
has_one = sub_dao,
bump = delegated_position.bump_seed,
constraint = TESTING || delegated_position.expiration_ts == 0
)]
pub delegated_position: Account<'info, DelegatedPositionV0>,
#[account(
mut,
seeds = ["sub_dao_epoch_info".as_bytes(), sub_dao.key().as_ref(), &current_epoch(
position.lockup.end_ts
if delegated_position.expiration_ts == 0 {
position.lockup.end_ts
} else {
delegated_position.expiration_ts
}
).to_le_bytes()],
bump,
)]
Expand Down Expand Up @@ -85,7 +94,7 @@ pub struct AddExpirationTs<'info> {
pub system_program: Program<'info, System>,
}

pub fn handler(ctx: Context<AddExpirationTs>) -> Result<()> {
pub fn handler(ctx: Context<ExtendExpirationTsV0>) -> Result<()> {
let position = &mut ctx.accounts.position;
let registrar = &ctx.accounts.registrar;
let voting_mint_config = &registrar.voting_mints[position.voting_mint_config_idx as usize];
Expand Down
4 changes: 2 additions & 2 deletions programs/helium-sub-daos/src/instructions/delegation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
pub mod add_expiration_ts;
pub mod claim_rewards_v0;
pub mod claim_rewards_v1;
pub mod close_delegation_v0;
pub mod delegate_v0;
pub mod extend_expiration_ts_v0;
pub mod reset_lockup_v0;
pub mod track_vote_v0;
pub mod transfer_v0;

pub use add_expiration_ts::*;
pub use claim_rewards_v0::*;
pub use claim_rewards_v1::*;
pub use close_delegation_v0::*;
pub use delegate_v0::*;
pub use extend_expiration_ts_v0::*;
pub use reset_lockup_v0::*;
pub use track_vote_v0::*;
pub use transfer_v0::*;
4 changes: 2 additions & 2 deletions programs/helium-sub-daos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ pub mod helium_sub_daos {
initialize_hnt_delegator_pool::handler(ctx)
}

pub fn add_expiration_ts(ctx: Context<AddExpirationTs>) -> Result<()> {
add_expiration_ts::handler(ctx)
pub fn extend_expiration_ts_v0(ctx: Context<ExtendExpirationTsV0>) -> Result<()> {
extend_expiration_ts_v0::handler(ctx)
}

pub fn temp_resize_account(ctx: Context<TempResizeAccount>) -> Result<()> {
Expand Down
5 changes: 3 additions & 2 deletions tests/helium-sub-daos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1277,19 +1277,20 @@ describe("helium-sub-daos", () => {
const expectedFallRates = subDaoEpochInfo.fallRatesFromClosingPositions.toString();
const expectedVehntInClosingPositions = subDaoEpochInfo.vehntInClosingPositions.toString();

console.log("dat old one is ", closingTimeSubDaoEpochInfo!.toBase58());
const newClosingTimeSubDaoEpochInfo = subDaoEpochInfoKey(
subDao,
seasonEnd
)[0]
await program.methods
.addExpirationTs()
.extendExpirationTsV0()
.accounts({
position,
subDao,
oldClosingTimeSubDaoEpochInfo: closingTimeSubDaoEpochInfo,
closingTimeSubDaoEpochInfo: newClosingTimeSubDaoEpochInfo,
authority: positionAuthorityKp.publicKey,
})
.signers([positionAuthorityKp])
.rpc({ skipPreflight: true });

const oldSubDaoEpochInfo = await program.account.subDaoEpochInfoV0.fetch(closingTimeSubDaoEpochInfo!);
Expand Down

0 comments on commit 430f154

Please sign in to comment.