Skip to content

Commit

Permalink
Add the ability to extend delegation and use percent-wise smoothing (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ChewingGlass authored Jan 8, 2025
1 parent 0bbb384 commit d79d10b
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 55 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
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

0 comments on commit d79d10b

Please sign in to comment.