Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to extend delegation #766

Merged
merged 7 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
22 changes: 22 additions & 0 deletions packages/helium-sub-daos-sdk/src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,22 @@ export const subDaoEpochInfoResolver = resolveIndividual(
if (subDao) {
const [key] = await subDaoEpochInfoKey(subDao, unixTime - EPOCH_LENGTH, PROGRAM_ID);

return key;
}
}
if (path[path.length - 1] === "prevSubDaoEpochInfo" && args && args[0] && args[0].epoch) {
const unixTime = args[0].epoch.toNumber() * EPOCH_LENGTH;
const subDao = get(accounts, [
...path.slice(0, path.length - 1),
"subDao",
]) as PublicKey;
if (subDao) {
const [key] = await subDaoEpochInfoKey(
subDao,
unixTime - EPOCH_LENGTH,
PROGRAM_ID
);

return key;
}
}
Expand Down Expand Up @@ -266,6 +282,12 @@ export const heliumSubDaosResolvers = combineResolvers(
mint: "mint",
owner: "positionAuthority",
}),
ataResolver({
instruction: "extendExpirationTsV0",
account: "positionTokenAccount",
mint: "mint",
owner: "authority",
}),
resolveIndividual(async ({ args, path, accounts }) => {
if (path[path.length - 1] == "clockwork") {
return THREAD_PID;
Expand Down
29 changes: 23 additions & 6 deletions packages/spl-utils/src/fetchBackwardsCompatibleIdl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1934,11 +1934,6 @@ const IDLS_BY_PROGRAM: Record<string, any> = {
isMut: true,
isSigner: false,
},
{
name: "rewardsEscrow",
isMut: false,
isSigner: false,
},
{
name: "systemProgram",
isMut: false,
Expand All @@ -1963,7 +1958,7 @@ const IDLS_BY_PROGRAM: Record<string, any> = {
args: [],
},
{
name: "addExpirationTs",
name: "extendExpirationTsV0",
accounts: [
{
name: "payer",
Expand All @@ -1974,6 +1969,22 @@ const IDLS_BY_PROGRAM: Record<string, any> = {
name: "position",
isMut: true,
isSigner: false,
relations: ["mint"],
},
{
name: "mint",
isMut: false,
isSigner: false,
},
{
name: "positionTokenAccount",
isMut: false,
isSigner: false,
},
{
name: "authority",
isMut: false,
isSigner: true,
},
{
name: "registrar",
Expand Down Expand Up @@ -2877,6 +2888,12 @@ const IDLS_BY_PROGRAM: Record<string, any> = {
option: "u64",
},
},
{
name: "rewardsEscrow",
type: {
option: "publicKey",
},
},
],
},
},
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
Expand Up @@ -76,9 +76,11 @@ pub fn handler(
// burned hnt since last supply setting.
let curr_supply = ctx.accounts.hnt_mint.supply;
let mut prev_supply = curr_supply;
let mut prev_total_utility_score = 0;
if ctx.accounts.prev_dao_epoch_info.lamports() > 0 {
let info: Account<DaoEpochInfoV0> = Account::try_from(&ctx.accounts.prev_dao_epoch_info)?;
prev_supply = info.current_hnt_supply;
prev_total_utility_score = info.total_utility_score;
}

ctx.accounts.dao_epoch_info.total_rewards = ctx
Expand Down Expand Up @@ -152,40 +154,49 @@ pub fn handler(
.checked_div(&PreciseNumber::new(100000000_u128).unwrap()) // vehnt has 8 decimals
.unwrap();

// Apply a 90 day smooth
let utility_score_prec = vehnt_staked
// Add 12 decimals of precision
.checked_mul(&PreciseNumber::new(1000000000000_u128).unwrap()) // First convert vehnt to 12 decimals
.unwrap()
.checked_div(&PreciseNumber::new(90_u128).unwrap())
.unwrap()
.checked_add(
&PreciseNumber::new(89_u128)
.unwrap()
.checked_mul(
&ctx
.accounts
.prev_sub_dao_epoch_info
.utility_score
.and_then(PreciseNumber::new)
.unwrap_or_else(|| {
vehnt_staked
// Add 12 decimals of precision
.checked_mul(&PreciseNumber::new(1000000000000_u128).unwrap())
.unwrap()
}),
)
.unwrap()
.checked_div(&PreciseNumber::new(90_u128).unwrap())
.unwrap(),
)
.unwrap();

let utility_score = utility_score_prec.to_imprecise().unwrap();

// Store utility scores
epoch_info.utility_score = Some(utility_score);

let prev_epoch_info = &ctx.accounts.prev_sub_dao_epoch_info;
let previous_percentage = prev_epoch_info.previous_percentage;

// Initialize previous percentage if it's not already set
ctx.accounts.prev_sub_dao_epoch_info.previous_percentage = match previous_percentage {
// This was just deployed, so we don't have a previous utility score set
// Set it by using the percentage of the total utility score
0 => match prev_epoch_info.utility_score {
Some(prev_score) => {
if prev_total_utility_score == 0 {
0
} else {
prev_score
.checked_mul(u32::MAX as u128)
.and_then(|x| x.checked_div(prev_total_utility_score))
.map(|x| x as u32)
.unwrap_or(0)
}
}
// Either this is a new subnetwork or this whole program was just deployed
None => match prev_total_utility_score {
// If there is no previous utility score, this is a new program deployment
// Set it by using the percentage of the total utility score
0 => u32::MAX
.checked_div(ctx.accounts.dao.num_sub_daos)
.unwrap_or(0),
// If there is a previous utility score, this is a new subnetwork
_ => 0,
},
},
_ => previous_percentage,
};

// Only increment utility scores when either (a) in prod or (b) testing and we haven't already over-calculated utility scores.
// TODO: We can remove this after breakpoint demo
if !(TESTING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub struct CloseDelegationV0<'info> {
if delegated_position.expiration_ts == 0 {
position.lockup.end_ts
} else {
delegated_position.expiration_ts
min(position.lockup.end_ts, delegated_position.expiration_ts)
}
)
).to_le_bytes()],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ pub fn handler(ctx: Context<DelegateV0>) -> Result<()> {
epoch: genesis_end_epoch,
bump_seed: ctx.bumps["genesis_end_sub_dao_epoch_info"],
sub_dao: sub_dao.key(),
previous_percentage: 0,
dc_burned: 0,
vehnt_at_epoch_start: 0,
vehnt_in_closing_positions: genesis_end_vehnt_correction,
Expand Down
Loading
Loading