diff --git a/Cargo.lock b/Cargo.lock index 095c400ec..6af373c8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5754,7 +5754,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "voter-stake-registry" -version = "0.2.5" +version = "0.3.0" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/packages/helium-admin-cli/src/create-dao.ts b/packages/helium-admin-cli/src/create-dao.ts index 630a68512..c8fd820e3 100644 --- a/packages/helium-admin-cli/src/create-dao.ts +++ b/packages/helium-admin-cli/src/create-dao.ts @@ -327,7 +327,6 @@ export async function run(args: any = process.argv) { await heliumVsrProgram.methods .configureVotingMintV0({ idx: 0, // idx - digitShift: 0, // digit shift baselineVoteWeightScaledFactor: new anchor.BN(BASELINE * 1e9), maxExtraLockupVoteWeightScaledFactor: new anchor.BN(SCALE * 1e9), genesisVotePowerMultiplier: GENESIS_MULTIPLIER, @@ -351,16 +350,6 @@ export async function run(args: any = process.argv) { await sendInstructions(provider, instructions, []); instructions = []; - console.log('Creating max voter record'); - instructions.push( - await heliumVsrProgram.methods - .updateMaxVoterWeightV0() - .accounts({ - registrar, - realmGoverningTokenMint: hntKeypair.publicKey, - }) - .instruction() - ); console.log(registrar.toString()); await sendInstructions(provider, instructions, []); instructions = []; diff --git a/packages/helium-admin-cli/src/create-subdao.ts b/packages/helium-admin-cli/src/create-subdao.ts index 72edca17f..6188df9a4 100644 --- a/packages/helium-admin-cli/src/create-subdao.ts +++ b/packages/helium-admin-cli/src/create-subdao.ts @@ -345,7 +345,6 @@ export async function run(args: any = process.argv) { await heliumVsrProgram.methods .configureVotingMintV0({ idx: 0, // idx - digitShift: -1, // digit shift baselineVoteWeightScaledFactor: new anchor.BN(BASELINE * 1e9), maxExtraLockupVoteWeightScaledFactor: new anchor.BN(SCALE * 1e9), genesisVotePowerMultiplier: 0, @@ -370,17 +369,6 @@ export async function run(args: any = process.argv) { await sendInstructions(provider, instructions, []); instructions = []; - - console.log('Creating max voter record'); - instructions.push( - await heliumVsrProgram.methods - .updateMaxVoterWeightV0() - .accounts({ - registrar, - realmGoverningTokenMint: subdaoKeypair.publicKey, - }) - .instruction() - ); } await sendInstructions(provider, instructions, []); diff --git a/packages/helium-admin-cli/src/reset-vsr-max-voter-record.ts b/packages/helium-admin-cli/src/reset-vsr-max-voter-record.ts deleted file mode 100644 index 30a7b7097..000000000 --- a/packages/helium-admin-cli/src/reset-vsr-max-voter-record.ts +++ /dev/null @@ -1,124 +0,0 @@ -import * as anchor from '@coral-xyz/anchor'; -import { - daoKey, - init as initDao, - subDaoKey, -} from '@helium/helium-sub-daos-sdk'; -import { init as initVsr } from '@helium/voter-stake-registry-sdk'; -import { PublicKey, TransactionInstruction } from '@solana/web3.js'; -import Squads from '@sqds/sdk'; -import os from 'os'; -import yargs from 'yargs/yargs'; -import { loadKeypair, sendInstructionsOrSquads } from './utils'; - -export async function run(args: any = process.argv) { - const yarg = yargs(args).options({ - wallet: { - alias: 'k', - describe: 'Anchor wallet keypair', - default: `${os.homedir()}/.config/solana/id.json`, - }, - url: { - alias: 'u', - default: 'http://127.0.0.1:8899', - describe: 'The solana url', - }, - hntMint: { - type: 'string', - describe: - 'Mint of the HNT token. Only used if --resetDaoRecord flag is set', - }, - dntMint: { - type: 'string', - describe: - 'Mint of the subdao token. Only used if --resetSubDaoRecord flag is set', - }, - resetDaoRecord: { - type: 'boolean', - describe: 'Reset the dao max vote weight record', - default: false, - }, - resetSubDaoRecord: { - type: 'boolean', - describe: 'Reset the subdao max vote weight record', - default: false, - }, - executeTransaction: { - type: 'boolean', - }, - multisig: { - type: 'string', - describe: - 'Address of the squads multisig to be authority. If not provided, your wallet will be the authority', - }, - authorityIndex: { - type: 'number', - describe: 'Authority index for squads. Defaults to 1', - default: 1, - }, - }); - - const argv = await yarg.argv; - process.env.ANCHOR_WALLET = argv.wallet; - process.env.ANCHOR_PROVIDER_URL = argv.url; - anchor.setProvider(anchor.AnchorProvider.local(argv.url)); - - if (argv.resetSubDaoRecord && !argv.dntMint) { - console.log('dnt mint not provided'); - return; - } - - const provider = anchor.getProvider() as anchor.AnchorProvider; - const wallet = new anchor.Wallet(loadKeypair(argv.wallet)); - const hsdProgram = await initDao(provider); - const hvsrProgram = await initVsr(provider); - const instructions: TransactionInstruction[] = []; - - const squads = Squads.endpoint(process.env.ANCHOR_PROVIDER_URL, wallet, { - commitmentOrConfig: 'finalized', - }); - - if (argv.resetDaoRecord) { - console.log('resetting dao maxVoterWeightRecord'); - const hntMint = new PublicKey(argv.hntMint!); - const dao = daoKey(hntMint)[0]; - const daoAcc = await hsdProgram.account.daoV0.fetch(dao); - - instructions.push( - await hvsrProgram.methods - .updateMaxVoterWeightV0() - .accounts({ - registrar: daoAcc.registrar, - realmGoverningTokenMint: hntMint, - }) - .instruction() - ); - } - - if (argv.resetSubDaoRecord) { - console.log('resetting subdao maxVoterWeightRecord'); - const dntMint = new PublicKey(argv.dntMint!); - const subDao = subDaoKey(dntMint)[0]; - const subDaoAcc = await hsdProgram.account.subDaoV0.fetch(subDao); - - instructions.push( - await hvsrProgram.methods - .updateMaxVoterWeightV0() - .accounts({ - registrar: subDaoAcc.registrar, - realmGoverningTokenMint: dntMint, - }) - .instruction() - ); - } - - await sendInstructionsOrSquads({ - provider, - instructions, - executeTransaction: argv.executeTransaction, - squads, - multisig: argv.multisig ? new PublicKey(argv.multisig) : undefined, - authorityIndex: argv.authorityIndex, - signers: [], - }); -} diff --git a/packages/helium-admin-cli/src/reset-vsr-voting-mint.ts b/packages/helium-admin-cli/src/reset-vsr-voting-mint.ts index 889a3fa63..b13555416 100644 --- a/packages/helium-admin-cli/src/reset-vsr-voting-mint.ts +++ b/packages/helium-admin-cli/src/reset-vsr-voting-mint.ts @@ -99,7 +99,6 @@ export async function run(args: any = process.argv) { await hvsrProgram.methods .configureVotingMintV0({ idx: 0, - digitShift: 0, baselineVoteWeightScaledFactor: new anchor.BN(0 * 1e9), maxExtraLockupVoteWeightScaledFactor: new anchor.BN(100 * 1e9), lockupSaturationSecs: new anchor.BN(MAX_LOCKUP), @@ -132,7 +131,6 @@ export async function run(args: any = process.argv) { await hvsrProgram.methods .configureVotingMintV0({ idx: 0, - digitShift: -1, baselineVoteWeightScaledFactor: new anchor.BN(0 * 1e9), maxExtraLockupVoteWeightScaledFactor: new anchor.BN(100 * 1e9), lockupSaturationSecs: new anchor.BN(MAX_LOCKUP), diff --git a/programs/helium-sub-daos/src/instructions/delegation/claim_rewards_v0.rs b/programs/helium-sub-daos/src/instructions/delegation/claim_rewards_v0.rs index e93c6c48f..3bd0ec2df 100644 --- a/programs/helium-sub-daos/src/instructions/delegation/claim_rewards_v0.rs +++ b/programs/helium-sub-daos/src/instructions/delegation/claim_rewards_v0.rs @@ -138,7 +138,7 @@ pub fn handler(ctx: Context, args: ClaimRewardsArgsV0) -> Result // 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 as u128) + delegated_vehnt_at_epoch .checked_mul(ctx.accounts.sub_dao_epoch_info.delegation_rewards_issued as u128) .unwrap() .checked_div(ctx.accounts.sub_dao_epoch_info.vehnt_at_epoch_start as u128) diff --git a/programs/helium-sub-daos/src/instructions/delegation/close_delegation_v0.rs b/programs/helium-sub-daos/src/instructions/delegation/close_delegation_v0.rs index 438ac0f45..de37ee100 100644 --- a/programs/helium-sub-daos/src/instructions/delegation/close_delegation_v0.rs +++ b/programs/helium-sub-daos/src/instructions/delegation/close_delegation_v0.rs @@ -205,10 +205,11 @@ pub fn handler(ctx: Context) -> Result<()> { } // If the position was staked before this epoch, remove it. if current_epoch(delegated_position.start_ts) < curr_epoch { - let vehnt_at_start = position.voting_power( + let vehnt_at_start = u64::try_from(position.voting_power( voting_mint_config, ctx.accounts.sub_dao_epoch_info.start_ts(), - )?; + )?) + .unwrap(); msg!( "Removing {} vehnt from this epoch for this subdao, which currently has {} vehnt", vehnt_at_start, diff --git a/programs/helium-sub-daos/src/utils.rs b/programs/helium-sub-daos/src/utils.rs index 00ed861d5..c7da2a75d 100644 --- a/programs/helium-sub-daos/src/utils.rs +++ b/programs/helium-sub-daos/src/utils.rs @@ -165,14 +165,14 @@ pub trait PrecisePosition { fn voting_power_precise_locked_precise( &self, curr_ts: i64, - max_locked_vote_weight: u64, + max_locked_vote_weight: u128, lockup_saturation_secs: u64, ) -> Result; fn voting_power_precise_cliff_precise( &self, curr_ts: i64, - max_locked_vote_weight: u64, + max_locked_vote_weight: u128, lockup_saturation_secs: u64, ) -> Result; } @@ -184,9 +184,9 @@ impl PrecisePosition for PositionV0 { curr_ts: i64, ) -> Result { let baseline_vote_weight = (voting_mint_config - .baseline_vote_weight(self.amount_deposited_native)? as u128) - .checked_mul(FALL_RATE_FACTOR) - .unwrap(); + .baseline_vote_weight(self.amount_deposited_native)?) + .checked_mul(FALL_RATE_FACTOR) + .unwrap(); let max_locked_vote_weight = voting_mint_config.max_extra_lockup_vote_weight(self.amount_deposited_native)?; let genesis_multiplier = @@ -213,7 +213,7 @@ impl PrecisePosition for PositionV0 { fn voting_power_precise_locked_precise( &self, curr_ts: i64, - max_locked_vote_weight: u64, + max_locked_vote_weight: u128, lockup_saturation_secs: u64, ) -> Result { if self.lockup.expired(curr_ts) || (max_locked_vote_weight == 0) { @@ -238,12 +238,12 @@ impl PrecisePosition for PositionV0 { fn voting_power_precise_cliff_precise( &self, curr_ts: i64, - max_locked_vote_weight: u64, + max_locked_vote_weight: u128, lockup_saturation_secs: u64, ) -> Result { let remaining = min(self.lockup.seconds_left(curr_ts), lockup_saturation_secs); Ok( - (max_locked_vote_weight as u128) + (max_locked_vote_weight) .checked_mul(remaining as u128) .unwrap() .checked_mul(FALL_RATE_FACTOR) diff --git a/programs/voter-stake-registry/Cargo.toml b/programs/voter-stake-registry/Cargo.toml index 6af7e3f2f..bfd1e5c70 100644 --- a/programs/voter-stake-registry/Cargo.toml +++ b/programs/voter-stake-registry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "voter-stake-registry" -version = "0.2.5" +version = "0.3.0" description = "Heliums voter weight plugin for spl-governance" license = "GPL-3.0-or-later" homepage = "https://github.com/helium/helium-program-library" diff --git a/programs/voter-stake-registry/src/instructions/cast_vote_v0.rs b/programs/voter-stake-registry/src/instructions/cast_vote_v0.rs deleted file mode 100644 index 634aaf02b..000000000 --- a/programs/voter-stake-registry/src/instructions/cast_vote_v0.rs +++ /dev/null @@ -1,212 +0,0 @@ -use std::mem::size_of; - -use crate::error::VsrError; -use crate::util::resolve_vote_weight; -use crate::{id, state::*}; -use anchor_lang::prelude::*; -use anchor_lang::Accounts; -use itertools::Itertools; -use solana_program::program::{invoke, invoke_signed}; -use solana_program::system_instruction::{self, create_account}; -use spl_governance_tools::account::AccountMaxSize; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] -pub struct CastVoteArgsV0 { - proposal: Pubkey, - owner: Pubkey, -} - -/// Casts NFT vote. The NFTs used for voting are tracked using NftVoteRecord accounts -/// This instruction updates VoterWeightRecord which is valid for the current Slot and the target Proposal only -/// and hance the instruction has to be executed inside the same transaction as spl-gov.CastVote -/// -/// CastNftVote is cumulative and can be invoked using several transactions if voter owns more than 5 NFTs to calculate total voter_weight -/// In this scenario only the last CastNftVote should be bundled with spl-gov.CastVote in the same transaction -/// -/// CastNftVote instruction and NftVoteRecord are not directional. They don't record vote choice (ex Yes/No) -/// VoteChoice is recorded by spl-gov in VoteRecord and this CastNftVote only tracks voting NFTs -/// -#[derive(Accounts)] -#[instruction(args: CastVoteArgsV0)] -pub struct CastVoteV0<'info> { - pub registrar: Box>, - - #[account( - init_if_needed, - payer = payer, - space = 8 + size_of::(), - seeds = [registrar.key().as_ref(), b"voter-weight-record".as_ref(), args.owner.as_ref()], - bump, - )] - pub voter_weight_record: Account<'info, VoterWeightRecord>, - - // TokenOwnerRecord of the voter who casts the vote - #[account( - owner = registrar.governance_program_id - )] - /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id - pub voter_token_owner_record: UncheckedAccount<'info>, - - /// Authority of the voter who casts the vote - /// It can be either governing_token_owner or its delegate and must sign this instruction - pub voter_authority: Signer<'info>, - - /// The account which pays for the transaction - #[account(mut)] - pub payer: Signer<'info>, - - pub system_program: Program<'info, System>, -} - -/// Casts vote with the NFT -pub fn handler<'info>( - ctx: Context<'_, '_, '_, 'info, CastVoteV0<'info>>, - args: CastVoteArgsV0, -) -> Result<()> { - let registrar = &ctx.accounts.registrar; - let voter_weight_record = &mut ctx.accounts.voter_weight_record; - - voter_weight_record.governing_token_owner = args.owner; - voter_weight_record.realm = registrar.realm; - voter_weight_record.governing_token_mint = registrar.realm_governing_token_mint; - - let mut voter_weight = 0_u64; - - // Ensure all voting nfts in the batch are unique - let mut unique_nft_mints = vec![]; - - let governing_token_owner = resolve_governing_token_owner( - registrar, - &ctx.accounts.voter_token_owner_record, - &ctx.accounts.voter_authority, - voter_weight_record, - )?; - - require_eq!(governing_token_owner, args.owner, VsrError::InvalidOwner); - - for (token_account, position, nft_vote_record_info) in ctx.remaining_accounts.iter().tuples() { - let nft_vote_weight = resolve_vote_weight( - registrar, - &args.owner, - token_account, - position, - &mut unique_nft_mints, - )?; - - voter_weight = voter_weight.checked_add(nft_vote_weight).unwrap(); - - // Increase num active votes - let position_acc: &mut Account = &mut Account::try_from(position)?; - position_acc.num_active_votes += 1; - position_acc.exit(&crate::ID)?; - require!(position.is_writable, VsrError::PositionNotWritable); - - // Create NFT vote record to ensure the same NFT hasn't been already used for voting - // Note: The correct PDA of the NftVoteRecord is validated in create_and_serialize_account_signed - // It ensures the NftVoteRecord is for ('nft-vote-record',proposal,nft_mint) seeds - require!( - nft_vote_record_info.data_is_empty(), - VsrError::NftAlreadyVoted - ); - - // Note: proposal.governing_token_mint must match voter_weight_record.governing_token_mint - // We don't verify it here because spl-gov does the check in cast_vote - // and it would reject voter_weight_record if governing_token_mint doesn't match - - // Note: Once the NFT plugin is enabled the governing_token_mint is used only as identity - // for the voting population and the tokens of that mint are no longer used - let nft_mint = *unique_nft_mints.last().unwrap(); - let nft_vote_record = NftVoteRecord { - proposal: args.proposal, - nft_mint, - governing_token_owner: args.owner, - }; - - let account_size = nft_vote_record.get_max_size().unwrap(); - let mut signers_seeds = get_nft_vote_record_seeds(&args.proposal, &nft_mint).to_vec(); - let (_, bump_seed) = Pubkey::find_program_address(&signers_seeds, &id()); - let bump = &[bump_seed]; - signers_seeds.push(bump); - let rent = Rent::get()?; - let total_lamports = rent.minimum_balance(account_size); - let payer_info = ctx.accounts.payer.to_account_info(); - let system_info = ctx.accounts.system_program.to_account_info(); - let serialized_data = nft_vote_record.try_to_vec().unwrap(); - - // If the account has some lamports already it can't be created using create_account instruction - // Anybody can send lamports to a PDA and by doing so create the account and perform DoS attack by blocking create_account - if nft_vote_record_info.lamports() > 0 { - let top_up_lamports = total_lamports.saturating_sub(nft_vote_record_info.lamports()); - - if top_up_lamports > 0 { - invoke( - &system_instruction::transfer(payer_info.key, nft_vote_record_info.key, top_up_lamports), - &[ - payer_info.clone(), - nft_vote_record_info.clone(), - system_info.clone(), - ], - )?; - } - - invoke_signed( - &system_instruction::allocate(nft_vote_record_info.key, account_size as u64), - &[nft_vote_record_info.clone(), system_info.clone()], - &[&signers_seeds[..]], - )?; - - invoke_signed( - &system_instruction::assign(nft_vote_record_info.key, &id()), - &[nft_vote_record_info.clone(), system_info.clone()], - &[&signers_seeds[..]], - )?; - } else { - // If the PDA doesn't exist use create_account to use lower compute budget - let create_account_instruction = create_account( - payer_info.key, - nft_vote_record_info.key, - total_lamports, - account_size as u64, - &id(), - ); - - invoke_signed( - &create_account_instruction, - &[ - payer_info.clone(), - nft_vote_record_info.clone(), - system_info.clone(), - ], - &[&signers_seeds[..]], - )?; - } - - nft_vote_record_info.data.borrow_mut()[0..8] - .copy_from_slice(&NftVoteRecord::ACCOUNT_DISCRIMINATOR); - nft_vote_record_info.data.borrow_mut()[8..(serialized_data.len() + 8)] - .copy_from_slice(&serialized_data); - nft_vote_record_info.exit(&id())?; - } - - if voter_weight_record.weight_action_target == Some(args.proposal) - && voter_weight_record.weight_action == Some(VoterWeightAction::CastVote) - { - // If cast_nft_vote is called for the same proposal then we keep accumulating the weight - // this way cast_nft_vote can be called multiple times in different transactions to allow voting with any number of NFTs - voter_weight_record.voter_weight = voter_weight_record - .voter_weight - .checked_add(voter_weight) - .unwrap(); - } else { - voter_weight_record.voter_weight = voter_weight; - } - - // The record is only valid as of the current slot - voter_weight_record.voter_weight_expiry = Some(Clock::get()?.slot); - - // The record is only valid for casting vote on the given Proposal - voter_weight_record.weight_action = Some(VoterWeightAction::CastVote); - voter_weight_record.weight_action_target = Some(args.proposal); - - Ok(()) -} diff --git a/programs/voter-stake-registry/src/instructions/configure_voting_mint_v0.rs b/programs/voter-stake-registry/src/instructions/configure_voting_mint_v0.rs index cf7bb2b7e..e4dec80b1 100644 --- a/programs/voter-stake-registry/src/instructions/configure_voting_mint_v0.rs +++ b/programs/voter-stake-registry/src/instructions/configure_voting_mint_v0.rs @@ -25,7 +25,6 @@ pub struct ConfigureVotingMintV0<'info> { #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] pub struct ConfigureVotingMintArgsV0 { pub idx: u16, - pub digit_shift: i8, pub baseline_vote_weight_scaled_factor: u64, pub max_extra_lockup_vote_weight_scaled_factor: u64, pub genesis_vote_power_multiplier: u8, @@ -91,7 +90,6 @@ pub struct ConfigureVotingMintArgsV0 { pub fn handler(ctx: Context, args: ConfigureVotingMintArgsV0) -> Result<()> { let ConfigureVotingMintArgsV0 { idx, - digit_shift, baseline_vote_weight_scaled_factor, max_extra_lockup_vote_weight_scaled_factor, genesis_vote_power_multiplier, @@ -134,18 +132,18 @@ pub fn handler(ctx: Context, args: ConfigureVotingMintArg Some(_) => { registrar.voting_mints[idx] = VotingMintConfigV0 { mint, - digit_shift, baseline_vote_weight_scaled_factor, max_extra_lockup_vote_weight_scaled_factor, genesis_vote_power_multiplier, genesis_vote_power_multiplier_expiration_ts, lockup_saturation_secs, + reserved: 0, } } None => registrar.voting_mints.push(VotingMintConfigV0 { + reserved: 0, mint, - digit_shift, baseline_vote_weight_scaled_factor, max_extra_lockup_vote_weight_scaled_factor, genesis_vote_power_multiplier, diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index ae7112f53..7e88faf46 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -1,4 +1,3 @@ -pub use cast_vote_v0::*; pub use close_position_v0::*; pub use configure_voting_mint_v0::*; pub use deposit_v0::*; @@ -11,13 +10,10 @@ pub use relinquish_vote_v1::*; pub use reset_lockup_v0::*; pub use set_time_offset_v0::*; pub use transfer_v0::*; -pub use update_max_voter_weight_v0::*; pub use update_registrar_authority_v0::*; -pub use update_voter_weight_record_v0::*; pub use vote_v0::*; pub use withdraw_v0::*; -pub mod cast_vote_v0; pub mod close_position_v0; pub mod configure_voting_mint_v0; pub mod deposit_v0; @@ -30,8 +26,6 @@ pub mod relinquish_vote_v1; pub mod reset_lockup_v0; pub mod set_time_offset_v0; pub mod transfer_v0; -pub mod update_max_voter_weight_v0; pub mod update_registrar_authority_v0; -pub mod update_voter_weight_record_v0; pub mod vote_v0; pub mod withdraw_v0; diff --git a/programs/voter-stake-registry/src/instructions/update_max_voter_weight_v0.rs b/programs/voter-stake-registry/src/instructions/update_max_voter_weight_v0.rs deleted file mode 100644 index 489c78574..000000000 --- a/programs/voter-stake-registry/src/instructions/update_max_voter_weight_v0.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::state::*; -use anchor_lang::prelude::*; -use anchor_spl::token::Mint; -use std::mem::size_of; - -#[derive(Accounts)] -pub struct UpdateMaxVoterWeightV0<'info> { - #[account(mut)] - pub payer: Signer<'info>, - #[account( - has_one = realm_governing_token_mint - )] - pub registrar: Box>, - pub realm_governing_token_mint: Account<'info, Mint>, - - #[account( - init_if_needed, - payer = payer, - space = 8 + size_of::(), - seeds = [b"max-voter-weight-record".as_ref(), registrar.realm.as_ref(), registrar.realm_governing_token_mint.as_ref()], - bump, - )] - pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>, - pub system_program: Program<'info, System>, -} - -/// Creates MaxVoterWeightRecord used by spl-gov -/// This instruction should only be executed once per realm/governing_token_mint to create the account -pub fn handler(ctx: Context) -> Result<()> { - let registrar = &ctx.accounts.registrar; - let max_voter_weight_record = &mut ctx.accounts.max_voter_weight_record; - - max_voter_weight_record.realm = registrar.realm; - max_voter_weight_record.governing_token_mint = registrar.realm_governing_token_mint; - max_voter_weight_record.max_voter_weight_expiry = None; - - let config_idx = - registrar.voting_mint_config_index(ctx.accounts.realm_governing_token_mint.key())?; - - let governing_mint_supply = ctx.accounts.realm_governing_token_mint.supply; - - let max_locked_vote_weight = - registrar.voting_mints[config_idx].max_extra_lockup_vote_weight(governing_mint_supply)?; - let genesis_multiplier = registrar.voting_mints[config_idx].genesis_vote_power_multiplier; - - max_voter_weight_record.max_voter_weight = max_locked_vote_weight - .checked_mul(genesis_multiplier as u64) - .unwrap(); - - Ok(()) -} diff --git a/programs/voter-stake-registry/src/instructions/update_voter_weight_record_v0.rs b/programs/voter-stake-registry/src/instructions/update_voter_weight_record_v0.rs deleted file mode 100644 index ff84a8566..000000000 --- a/programs/voter-stake-registry/src/instructions/update_voter_weight_record_v0.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::mem::size_of; - -use crate::error::*; -use crate::state::*; -use crate::util::resolve_vote_weight; -use anchor_lang::prelude::*; -use itertools::Itertools; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] -pub struct UpdateVoterWeightRecordArgsV0 { - voter_weight_action: VoterWeightAction, - owner: Pubkey, -} - -#[derive(Accounts)] -#[instruction(args: UpdateVoterWeightRecordArgsV0)] -pub struct UpdateVoterWeightRecordV0<'info> { - #[account(mut)] - pub payer: Signer<'info>, - pub registrar: Box>, - - #[account( - init_if_needed, - payer = payer, - space = 8 + size_of::(), - seeds = [registrar.key().as_ref(), b"voter-weight-record".as_ref(), args.owner.as_ref()], - bump, - )] - pub voter_weight_record: Account<'info, VoterWeightRecord>, - - // TokenOwnerRecord of the voter who casts the vote - #[account( - owner = registrar.governance_program_id - )] - /// CHECK: Owned by spl-governance instance specified in registrar.governance_program_id - pub voter_token_owner_record: UncheckedAccount<'info>, - - /// Authority of the voter - /// It can be either governing_token_owner or its delegate and must sign this instruction - pub voter_authority: Signer<'info>, - - pub system_program: Program<'info, System>, -} - -/// Calculates the lockup-scaled, time-decayed voting power for the given -/// voter and writes it into a `VoteWeightRecord` account to be used by -/// the SPL governance program. -/// -/// This "revise" instruction must be called immediately before voting, in -/// the same transaction. -pub fn handler( - ctx: Context, - args: UpdateVoterWeightRecordArgsV0, -) -> Result<()> { - let voter_weight_action = args.voter_weight_action; - - match voter_weight_action { - // voter_weight for CastVote action can't be evaluated using this instruction - VoterWeightAction::CastVote => return err!(VsrError::CastVoteIsNotAllowed), - VoterWeightAction::CommentProposal - | VoterWeightAction::CreateGovernance - | VoterWeightAction::CreateProposal - | VoterWeightAction::SignOffProposal => {} - } - - let registrar = &ctx.accounts.registrar; - let voter_weight_record = &mut ctx.accounts.voter_weight_record; - - voter_weight_record.governing_token_owner = args.owner; - voter_weight_record.realm = registrar.realm; - voter_weight_record.governing_token_mint = registrar.realm_governing_token_mint; - - let mut voter_weight = 0u64; - - // Ensure all nfts are unique - let mut unique_nft_mints = vec![]; - - let governing_token_owner = resolve_governing_token_owner( - registrar, - &ctx.accounts.voter_token_owner_record, - &ctx.accounts.voter_authority, - voter_weight_record, - )?; - - require_eq!(governing_token_owner, args.owner, VsrError::InvalidOwner); - - for (token_account, position) in ctx.remaining_accounts.iter().tuples() { - let nft_vote_weight = resolve_vote_weight( - registrar, - &args.owner, - token_account, - position, - &mut unique_nft_mints, - )?; - - voter_weight = voter_weight.checked_add(nft_vote_weight).unwrap(); - } - - voter_weight_record.voter_weight = voter_weight; - - // Record is only valid as of the current slot - voter_weight_record.voter_weight_expiry = Some(Clock::get()?.slot); - - // Set the action to make it specific and prevent being used for voting - voter_weight_record.weight_action = Some(voter_weight_action); - voter_weight_record.weight_action_target = None; - - msg!("Set voter weight to {}", voter_weight); - - Ok(()) -} diff --git a/programs/voter-stake-registry/src/instructions/vote_v0.rs b/programs/voter-stake-registry/src/instructions/vote_v0.rs index 5e0f9885c..8364a1ea7 100644 --- a/programs/voter-stake-registry/src/instructions/vote_v0.rs +++ b/programs/voter-stake-registry/src/instructions/vote_v0.rs @@ -93,10 +93,10 @@ pub fn handler(ctx: Context, args: VoteArgsV0) -> Result<()> { let weight = if marker.weight > 0 { marker.weight } else { - u128::from(ctx.accounts.position.voting_power( + ctx.accounts.position.voting_power( voting_mint_config, ctx.accounts.registrar.clock_unix_timestamp(), - )?) + )? }; marker.weight = weight; diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 2a6b70cc2..d46363c9a 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -55,10 +55,6 @@ pub mod voter_stake_registry { instructions::configure_voting_mint_v0::handler(ctx, args) } - pub fn update_max_voter_weight_v0(ctx: Context) -> Result<()> { - instructions::update_max_voter_weight_v0::handler(ctx) - } - pub fn initialize_position_v0( ctx: Context, args: InitializePositionArgsV0, @@ -86,24 +82,10 @@ pub mod voter_stake_registry { instructions::transfer_v0::handler(ctx, args) } - pub fn update_voter_weight_record_v0( - ctx: Context, - args: UpdateVoterWeightRecordArgsV0, - ) -> Result<()> { - instructions::update_voter_weight_record_v0::handler(ctx, args) - } - pub fn set_time_offset_v0(ctx: Context, time_offset: i64) -> Result<()> { instructions::set_time_offset_v0::handler(ctx, time_offset) } - pub fn cast_vote_v0<'info>( - ctx: Context<'_, '_, '_, 'info, CastVoteV0<'info>>, - args: CastVoteArgsV0, - ) -> Result<()> { - instructions::cast_vote_v0::handler(ctx, args) - } - pub fn relinquish_vote_v0(ctx: Context) -> Result<()> { instructions::relinquish_vote_v0::handler(ctx) } diff --git a/programs/voter-stake-registry/src/state/position.rs b/programs/voter-stake-registry/src/state/position.rs index bbff706bf..9ace63d54 100644 --- a/programs/voter-stake-registry/src/state/position.rs +++ b/programs/voter-stake-registry/src/state/position.rs @@ -61,7 +61,11 @@ impl PositionV0 { // each point in time the lockup should be equivalent to a new lockup // made for the remaining time period. // - pub fn voting_power(&self, voting_mint_config: &VotingMintConfigV0, curr_ts: i64) -> Result { + pub fn voting_power( + &self, + voting_mint_config: &VotingMintConfigV0, + curr_ts: i64, + ) -> Result { let baseline_vote_weight = voting_mint_config.baseline_vote_weight(self.amount_deposited_native)?; let max_locked_vote_weight = @@ -88,7 +92,7 @@ impl PositionV0 { baseline_vote_weight .checked_add(locked_vote_weight) .unwrap() - .checked_mul(genesis_multiplier as u64) + .checked_mul(genesis_multiplier as u128) .ok_or_else(|| error!(VsrError::VoterWeightOverflow)) } @@ -96,9 +100,9 @@ impl PositionV0 { pub fn voting_power_locked( &self, curr_ts: i64, - max_locked_vote_weight: u64, + max_locked_vote_weight: u128, lockup_saturation_secs: u64, - ) -> Result { + ) -> Result { if self.lockup.expired(curr_ts) || (max_locked_vote_weight == 0) { return Ok(0); } @@ -117,19 +121,16 @@ impl PositionV0 { fn voting_power_cliff( &self, curr_ts: i64, - max_locked_vote_weight: u64, + max_locked_vote_weight: u128, lockup_saturation_secs: u64, - ) -> Result { + ) -> Result { let remaining = min(self.lockup.seconds_left(curr_ts), lockup_saturation_secs); Ok( - u64::try_from( - (max_locked_vote_weight as u128) - .checked_mul(remaining as u128) - .unwrap() - .checked_div(lockup_saturation_secs as u128) - .unwrap(), - ) - .unwrap(), + max_locked_vote_weight + .checked_mul(remaining as u128) + .unwrap() + .checked_div(lockup_saturation_secs as u128) + .unwrap(), ) } diff --git a/programs/voter-stake-registry/src/state/registrar.rs b/programs/voter-stake-registry/src/state/registrar.rs index ad476560d..b1d8bcfd6 100644 --- a/programs/voter-stake-registry/src/state/registrar.rs +++ b/programs/voter-stake-registry/src/state/registrar.rs @@ -43,11 +43,11 @@ impl Registrar { .ok_or_else(|| error!(VsrError::VotingMintNotFound)) } - pub fn max_vote_weight(&self, mint_accounts: &[AccountInfo]) -> Result { + pub fn max_vote_weight(&self, mint_accounts: &[AccountInfo]) -> Result { self .voting_mints .iter() - .try_fold(0u64, |mut sum, voting_mint_config| -> Result { + .try_fold(0u128, |mut sum, voting_mint_config| -> Result { if !voting_mint_config.in_use() { return Ok(sum); } @@ -68,7 +68,7 @@ impl Registrar { .checked_add(voting_mint_config.max_extra_lockup_vote_weight(mint.supply)?) .ok_or_else(|| error!(VsrError::VoterWeightOverflow))?; sum = sum - .checked_mul(genesis_multiplier as u64) + .checked_mul(genesis_multiplier as u128) .ok_or_else(|| error!(VsrError::VoterWeightOverflow))?; Ok(sum) }) diff --git a/programs/voter-stake-registry/src/state/voting_mint_config.rs b/programs/voter-stake-registry/src/state/voting_mint_config.rs index 76c506c37..cc94708cc 100644 --- a/programs/voter-stake-registry/src/state/voting_mint_config.rs +++ b/programs/voter-stake-registry/src/state/voting_mint_config.rs @@ -1,7 +1,6 @@ use crate::error::*; use anchor_lang::__private::bytemuck::Zeroable; use anchor_lang::prelude::*; -use std::convert::TryFrom; const SCALED_FACTOR_BASE: u64 = 1_000_000_000; @@ -40,35 +39,17 @@ pub struct VotingMintConfigV0 { // Number of seconds of lockup needed to reach the maximum lockup bonus. pub lockup_saturation_secs: u64, - // Number of digits to shift native amounts, applying a 10^digit_shift factor. - pub digit_shift: i8, + // Used to be digit shift, now reserved + pub reserved: i8, } impl VotingMintConfigV0 { - // Converts an amount in this voting mints's native currency - // to the base vote weight (without the deposit or lockup scalings) - // by applying the digit_shift factor. - fn digit_shift_native(&self, amount_native: u64) -> Result { - let compute = || -> Option { - let val = if self.digit_shift < 0 { - (amount_native as u128).checked_div(10u128.pow((-self.digit_shift) as u32))? - } else { - (amount_native as u128).checked_mul(10u128.pow(self.digit_shift as u32))? - }; - u64::try_from(val).ok() - }; - compute().ok_or_else(|| error!(VsrError::VoterWeightOverflow)) - } - // Apply a factor in SCALED_FACTOR_BASE units. - fn apply_factor(base: u64, factor: u64) -> Result { - let compute = || -> Option { - u64::try_from( - (base as u128) - .checked_mul(factor as u128)? - .checked_div(SCALED_FACTOR_BASE as u128)?, - ) - .ok() + fn apply_factor(base: u64, factor: u64) -> Result { + let compute = || -> Option { + (base as u128) + .checked_mul(factor as u128)? + .checked_div(SCALED_FACTOR_BASE as u128) }; compute().ok_or_else(|| error!(VsrError::VoterWeightOverflow)) } @@ -77,18 +58,15 @@ impl VotingMintConfigV0 { // // This vote_weight is a component for all funds in a voter account, no // matter if locked up or not.// - pub fn baseline_vote_weight(&self, amount_native: u64) -> Result { - Self::apply_factor( - self.digit_shift_native(amount_native)?, - self.baseline_vote_weight_scaled_factor, - ) + pub fn baseline_vote_weight(&self, amount_native: u64) -> Result { + Self::apply_factor(amount_native, self.baseline_vote_weight_scaled_factor) } // The maximum extra vote weight a number of locked up native tokens can have. // Will be multiplied with a factor between 0 and 1 for the lockup duration. - pub fn max_extra_lockup_vote_weight(&self, amount_native: u64) -> Result { + pub fn max_extra_lockup_vote_weight(&self, amount_native: u64) -> Result { Self::apply_factor( - self.digit_shift_native(amount_native)?, + amount_native, self.max_extra_lockup_vote_weight_scaled_factor, ) } diff --git a/programs/voter-stake-registry/src/util.rs b/programs/voter-stake-registry/src/util.rs index 9ce05c80b..21db86518 100644 --- a/programs/voter-stake-registry/src/util.rs +++ b/programs/voter-stake-registry/src/util.rs @@ -12,7 +12,7 @@ pub fn resolve_vote_weight( token_account: &AccountInfo, position: &AccountInfo, unique_nft_mints: &mut Vec, -) -> Result { +) -> Result { let token_account_acc = TokenAccount::try_deserialize(&mut token_account.data.borrow().as_ref())?; require!( token_account_acc.is_initialized(), diff --git a/tests/utils/vsr.ts b/tests/utils/vsr.ts index 98e8e17b7..b72dacd8e 100644 --- a/tests/utils/vsr.ts +++ b/tests/utils/vsr.ts @@ -31,7 +31,6 @@ export async function initVsr( positionUpdateAuthority: PublicKey, genesisVotePowerMultiplierExpirationTs = 1, genesisVotePowerMultiplier = 0, - digitShift = 0 ) { const programVersion = await getGovernanceProgramVersion( program.provider.connection, @@ -71,7 +70,6 @@ export async function initVsr( await program.methods .configureVotingMintV0({ idx: 0, // idx - digitShift: digitShift, // digit shift baselineVoteWeightScaledFactor: new BN(0 * 1e9), maxExtraLockupVoteWeightScaledFactor: new BN(100 * 1e9), // scaled factor genesisVotePowerMultiplier: genesisVotePowerMultiplier, diff --git a/tests/voter-stake-registry.ts b/tests/voter-stake-registry.ts index 9c39fac58..ffc1aff37 100644 --- a/tests/voter-stake-registry.ts +++ b/tests/voter-stake-registry.ts @@ -44,7 +44,6 @@ chai.use(chaiAsPromised); const SECS_PER_DAY = 86400; const SECS_PER_YEAR = 365 * SECS_PER_DAY; const MAX_LOCKUP = 4 * SECS_PER_YEAR; -const DIGIT_SHIFT = 0; const BASELINE = 0; const SCALE = 100; const GENESIS_MULTIPLIER = 3; @@ -141,7 +140,6 @@ describe("voter-stake-registry", () => { await program.methods .configureVotingMintV0({ idx: 0, // idx - digitShift: DIGIT_SHIFT, // digit shift baselineVoteWeightScaledFactor: new anchor.BN(BASELINE * 1e9), maxExtraLockupVoteWeightScaledFactor: new anchor.BN(SCALE * 1e9), genesisVotePowerMultiplier: GENESIS_MULTIPLIER, @@ -220,32 +218,6 @@ describe("voter-stake-registry", () => { return { position, mint: mintKeypair.publicKey }; } - it("should create a maxVoterWeightRecord correctly", async () => { - const instructions: TransactionInstruction[] = []; - - const { - pubkeys: { maxVoterWeightRecord }, - instruction, - } = await program.methods - .updateMaxVoterWeightV0() - .accounts({ - registrar, - }) - .prepare(); - instructions.push(instruction); - - await sendInstructions(provider, instructions, []); - const maxVoterWeightAcc = await program.account.maxVoterWeightRecord.fetch( - maxVoterWeightRecord! - ); - - expectBnAccuracy( - toBN(223_000_000 * SCALE * GENESIS_MULTIPLIER, 8), - maxVoterWeightAcc.maxVoterWeight, - 0.00001 - ); - }); - it("should configure a votingMint correctly", async () => { const registrarAcc = await program.account.registrar.fetch(registrar); console.log(registrarAcc.collection.toBase58()); @@ -253,7 +225,6 @@ describe("voter-stake-registry", () => { registrarAcc.votingMints as VotingMintConfig[] )[0] as VotingMintConfig; - expect(votingMint0.digitShift).to.eq(DIGIT_SHIFT); expect( votingMint0.baselineVoteWeightScaledFactor.eq( new anchor.BN(BASELINE * 1e9) @@ -343,18 +314,6 @@ describe("voter-stake-registry", () => { .rpc({ skipPreflight: true }); }); - const applyDigitShift = (amountNative: number, digitShift: number) => { - let val = 0; - - if (digitShift < 0) { - val = amountNative / 10 ** digitShift; - } else { - val = amountNative * 10 ** digitShift; - } - - return val; - }; - let voteTestCases = [ { name: "genesis constant 1 position (within genesis)", @@ -368,7 +327,7 @@ describe("voter-stake-registry", () => { }, ], expectedVeHnt: - applyDigitShift(10000, DIGIT_SHIFT) * + 10000 * (GENESIS_MULTIPLIER || 1) * (BASELINE + Math.min((SECS_PER_DAY * 200) / MAX_LOCKUP, 1) * SCALE), }, @@ -384,7 +343,7 @@ describe("voter-stake-registry", () => { }, ], expectedVeHnt: - applyDigitShift(10000, DIGIT_SHIFT) * + 10000 * (GENESIS_MULTIPLIER || 1) * (BASELINE + Math.min((SECS_PER_DAY * (200 - 60)) / MAX_LOCKUP, 1) * SCALE), @@ -401,7 +360,7 @@ describe("voter-stake-registry", () => { }, ], expectedVeHnt: - applyDigitShift(10000, DIGIT_SHIFT) * + 10000 * (BASELINE + Math.min((SECS_PER_DAY * 200) / MAX_LOCKUP, 1) * SCALE), }, { @@ -416,7 +375,7 @@ describe("voter-stake-registry", () => { }, ], expectedVeHnt: - applyDigitShift(10000, DIGIT_SHIFT) * + 10000 * (BASELINE + Math.min((SECS_PER_DAY * (200 - 60)) / MAX_LOCKUP, 1) * SCALE), }, diff --git a/utils/vehnt/src/cli/delegated.rs b/utils/vehnt/src/cli/delegated.rs index 20cb8c02d..a9ebfe70d 100644 --- a/utils/vehnt/src/cli/delegated.rs +++ b/utils/vehnt/src/cli/delegated.rs @@ -31,7 +31,7 @@ pub struct Delegated { use anchor_lang::prelude::*; use helium_sub_daos::{ caclulate_vhnt_info, current_epoch, DelegatedPositionV0, - PrecisePosition, SubDaoEpochInfoV0, SubDaoV0, + SubDaoEpochInfoV0, SubDaoV0, }; #[allow(unused)] @@ -188,7 +188,7 @@ impl Delegated { )?; let vehnt = position .position - .voting_power_precise(&voting_mint_config, curr_ts)?; + .voting_power(&voting_mint_config, curr_ts)?; total_vehnt += vehnt; let epoch_infos_by_epoch = epoch_infos_by_subdao_and_epoch