Compare commits
6 Commits
bb9738b12f
...
a41616a9bc
Author | SHA1 | Date |
---|---|---|
Christian Kamm | a41616a9bc | |
Christian Kamm | 94e74a71e5 | |
Christian Kamm | dd51d3cbdd | |
Christian Kamm | 4c5d896912 | |
Christian Kamm | 37d6bed309 | |
Christian Kamm | 16d22648f7 |
|
@ -6,7 +6,6 @@ on:
|
|||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
branches: master
|
||||
|
||||
# Allowing manual runs with ability to choose branch
|
||||
workflow_dispatch:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
name: Lint and Test
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
|
|
|
@ -2,6 +2,7 @@ name: Soteria Scan
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: master
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
|
|
|
@ -3251,7 +3251,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
|||
|
||||
[[package]]
|
||||
name = "voter-stake-registry"
|
||||
version = "0.1.11"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"anchor-spl",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@blockworks-foundation/voter-stake-registry-client",
|
||||
"version": "0.1.11",
|
||||
"version": "0.2.0",
|
||||
"description": "Client for Voter-stake-registry which is a voter weight addin for Solana's spl-governance program.",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "voter-stake-registry"
|
||||
version = "0.1.11"
|
||||
version = "0.2.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ use anchor_lang::prelude::*;
|
|||
#[event]
|
||||
#[derive(Debug)]
|
||||
pub struct VoterInfo {
|
||||
/// Voter's total voting power
|
||||
pub voting_power: u64,
|
||||
pub voting_power_unlocked_only: u64,
|
||||
/// Voter's total voting power, when ignoring any effects from lockup
|
||||
pub voting_power_baseline: u64,
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
|
||||
|
@ -34,8 +36,8 @@ pub struct DepositEntryInfo {
|
|||
pub unlocked: u64,
|
||||
/// Voting power implied by this deposit entry
|
||||
pub voting_power: u64,
|
||||
/// Voting power that is not based on lockup
|
||||
pub voting_power_unlocked_only: u64,
|
||||
/// Voting power without any adjustments for lockup
|
||||
pub voting_power_baseline: u64,
|
||||
/// Information about locking, if any
|
||||
pub locking: Option<LockingInfo>,
|
||||
}
|
||||
|
|
|
@ -23,8 +23,8 @@ pub struct ConfigureVotingMint<'info> {
|
|||
///
|
||||
/// * `idx`: index of the rate to be set
|
||||
/// * `digit_shift`: how many digits to shift the native token amount, see below
|
||||
/// * `unlocked_scaled_factor`: vote weight factor for unlocked deposits, in 1/1e9 units
|
||||
/// * `lockup_scaled_factor`: max extra weight for lockups, in 1/1e9 units
|
||||
/// * `baseline_vote_weight_scaled_factor`: vote weight factor for all funds in vault, in 1/1e9 units
|
||||
/// * `max_extra_lockup_vote_weight_scaled_factor`: max extra weight for lockups, in 1/1e9 units
|
||||
/// * `lockup_saturation_secs`: lockup duration at which the full vote weight
|
||||
/// bonus is given to locked up deposits
|
||||
///
|
||||
|
@ -35,8 +35,8 @@ pub struct ConfigureVotingMint<'info> {
|
|||
/// ```
|
||||
/// vote_weight =
|
||||
/// amount * 10^(digit_shift)
|
||||
/// * (unlocked_scaled_factor/1e9
|
||||
/// + lockup_duration_factor * lockup_scaled_factor/1e9)
|
||||
/// * (baseline_vote_weight_scaled_factor/1e9
|
||||
/// + lockup_duration_factor * max_extra_lockup_vote_weight_scaled_factor/1e9)
|
||||
/// ```
|
||||
/// where lockup_duration_factor is a value between 0 and 1, depending on how long
|
||||
/// the amount is locked up. It is 1 when the lockup duration is greater or equal
|
||||
|
@ -46,8 +46,8 @@ pub struct ConfigureVotingMint<'info> {
|
|||
/// u64 limit! There is a check based on the supply of all configured mints, but
|
||||
/// do your own checking too.
|
||||
///
|
||||
/// If you use a single mint, prefer digit_shift=0 and unlocked_scaled_factor +
|
||||
/// lockup_scaled_factor <= 1e9. That way you won't have issues with overflow no
|
||||
/// If you use a single mint, prefer digit_shift=0 and baseline_vote_weight_scaled_factor +
|
||||
/// max_extra_lockup_vote_weight_scaled_factor <= 1e9. That way you won't have issues with overflow no
|
||||
/// matter the size of the mint's supply.
|
||||
///
|
||||
/// Digit shifting is particularly useful when using several voting token mints
|
||||
|
@ -56,8 +56,8 @@ pub struct ConfigureVotingMint<'info> {
|
|||
///
|
||||
/// Example: If you have token A with 6 decimals and token B with 9 decimals, you
|
||||
/// could set up:
|
||||
/// * A with digit_shift=0, unlocked_scaled_factor=2e9, lockup_scaled_factor=0
|
||||
/// * B with digit_shift=-3, unlocked_scaled_factor=1e9, lockup_scaled_factor=1e9
|
||||
/// * A with digit_shift=0, baseline_vote_weight_scaled_factor=2e9, max_extra_lockup_vote_weight_scaled_factor=0
|
||||
/// * B with digit_shift=-3, baseline_vote_weight_scaled_factor=1e9, max_extra_lockup_vote_weight_scaled_factor=1e9
|
||||
///
|
||||
/// That would make 1.0 decimaled tokens of A as valuable as 2.0 decimaled tokens
|
||||
/// of B when unlocked. B tokens could be locked up to double their vote weight. As
|
||||
|
@ -65,16 +65,16 @@ pub struct ConfigureVotingMint<'info> {
|
|||
///
|
||||
/// Note that in this example, you need 1000 native B tokens before receiving 1
|
||||
/// unit of vote weight. If the supplies were significantly lower, you could use
|
||||
/// * A with digit_shift=3, unlocked_scaled_factor=2e9, lockup_scaled_factor=0
|
||||
/// * B with digit_shift=0, unlocked_scaled_factor=1e9, lockup_scaled_factor=1e9
|
||||
/// * A with digit_shift=3, baseline_vote_weight_scaled_factor=2e9, max_extra_lockup_vote_weight_scaled_factor=0
|
||||
/// * B with digit_shift=0, baseline_vote_weight_scaled_factor=1e9, max_extra_lockup_vote_weight_scaled_factor=1e9
|
||||
/// to not lose precision on B tokens.
|
||||
///
|
||||
pub fn configure_voting_mint(
|
||||
ctx: Context<ConfigureVotingMint>,
|
||||
idx: u16,
|
||||
digit_shift: i8,
|
||||
unlocked_scaled_factor: u64,
|
||||
lockup_scaled_factor: u64,
|
||||
baseline_vote_weight_scaled_factor: u64,
|
||||
max_extra_lockup_vote_weight_scaled_factor: u64,
|
||||
lockup_saturation_secs: u64,
|
||||
grant_authority: Option<Pubkey>,
|
||||
) -> Result<()> {
|
||||
|
@ -100,11 +100,12 @@ pub fn configure_voting_mint(
|
|||
registrar.voting_mints[idx] = VotingMintConfig {
|
||||
mint,
|
||||
digit_shift,
|
||||
unlocked_scaled_factor,
|
||||
lockup_scaled_factor,
|
||||
baseline_vote_weight_scaled_factor,
|
||||
max_extra_lockup_vote_weight_scaled_factor,
|
||||
lockup_saturation_secs,
|
||||
grant_authority: grant_authority.unwrap_or_default(),
|
||||
padding: [0; 31],
|
||||
reserved1: [0; 7],
|
||||
reserved2: [0; 7],
|
||||
};
|
||||
|
||||
// Check for overflow in vote weight
|
||||
|
|
|
@ -32,7 +32,7 @@ pub fn log_voter_info(
|
|||
msg!("voter");
|
||||
emit!(VoterInfo {
|
||||
voting_power: voter.weight(registrar)?,
|
||||
voting_power_unlocked_only: voter.weight_from_unlocked(registrar)?,
|
||||
voting_power_baseline: voter.weight_baseline(registrar)?,
|
||||
});
|
||||
|
||||
msg!("deposit_entries");
|
||||
|
@ -71,8 +71,8 @@ pub fn log_voter_info(
|
|||
voting_mint_config_index: deposit.voting_mint_config_idx,
|
||||
unlocked: deposit.amount_unlocked(curr_ts),
|
||||
voting_power: deposit.voting_power(voting_mint_config, curr_ts)?,
|
||||
voting_power_unlocked_only: voting_mint_config
|
||||
.unlocked_vote_weight(deposit.amount_deposited_native)?,
|
||||
voting_power_baseline: voting_mint_config
|
||||
.baseline_vote_weight(deposit.amount_deposited_native)?,
|
||||
locking: locking_info,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -71,8 +71,8 @@ pub mod voter_stake_registry {
|
|||
ctx: Context<ConfigureVotingMint>,
|
||||
idx: u16,
|
||||
digit_shift: i8,
|
||||
unlocked_scaled_factor: u64,
|
||||
lockup_scaled_factor: u64,
|
||||
baseline_vote_weight_scaled_factor: u64,
|
||||
max_extra_lockup_vote_weight_scaled_factor: u64,
|
||||
lockup_saturation_secs: u64,
|
||||
grant_authority: Option<Pubkey>,
|
||||
) -> Result<()> {
|
||||
|
@ -80,8 +80,8 @@ pub mod voter_stake_registry {
|
|||
ctx,
|
||||
idx,
|
||||
digit_shift,
|
||||
unlocked_scaled_factor,
|
||||
lockup_scaled_factor,
|
||||
baseline_vote_weight_scaled_factor,
|
||||
max_extra_lockup_vote_weight_scaled_factor,
|
||||
lockup_saturation_secs,
|
||||
grant_authority,
|
||||
)
|
||||
|
|
|
@ -38,9 +38,10 @@ pub struct DepositEntry {
|
|||
// Points to the VotingMintConfig this deposit uses.
|
||||
pub voting_mint_config_idx: u8,
|
||||
|
||||
pub padding: [u8; 13],
|
||||
pub reserved: [u8; 29],
|
||||
}
|
||||
const_assert!(std::mem::size_of::<DepositEntry>() == 32 + 2 * 8 + 3 + 13);
|
||||
const_assert!(std::mem::size_of::<DepositEntry>() == 32 + 2 * 8 + 3 + 29);
|
||||
const_assert!(std::mem::size_of::<DepositEntry>() % 8 == 0);
|
||||
|
||||
impl DepositEntry {
|
||||
/// # Voting Power Caclulation
|
||||
|
@ -51,24 +52,18 @@ impl DepositEntry {
|
|||
/// For each cliff-locked token, the vote weight is:
|
||||
///
|
||||
/// ```
|
||||
/// voting_power = unlocked_vote_weight
|
||||
/// + lockup_duration_factor * max_lockup_vote_weight
|
||||
/// voting_power = baseline_vote_weight
|
||||
/// + lockup_duration_factor * max_extra_lockup_vote_weight
|
||||
/// ```
|
||||
///
|
||||
/// with
|
||||
/// unlocked_vote_weight and max_lockup_vote_weight from the
|
||||
/// VotingMintConfig
|
||||
/// lockup_duration_factor = lockup_time_remaining / max_lockup_time
|
||||
/// - lockup_duration_factor = min(lockup_time_remaining / lockup_saturation_secs, 1)
|
||||
/// - the VotingMintConfig providing the values for
|
||||
/// baseline_vote_weight, max_extra_lockup_vote_weight, lockup_saturation_secs
|
||||
///
|
||||
/// Linear vesting schedules can be thought of as a sequence of cliff-
|
||||
/// locked tokens and have the matching voting weight.
|
||||
///
|
||||
/// To achieve this with the SPL governance program--which requires a "max
|
||||
/// vote weight"--we attach what amounts to a scalar multiplier between 0
|
||||
/// and 1 to normalize voting power. This multiplier is a function of
|
||||
/// the lockup schedule. Here we will describe two, a one time
|
||||
/// cliff and a linear vesting schedule unlocking daily.
|
||||
///
|
||||
/// ## Cliff Lockup
|
||||
///
|
||||
/// The cliff lockup allows one to lockup their tokens for a set period
|
||||
|
@ -89,10 +84,10 @@ impl DepositEntry {
|
|||
/// voting_power_linear_vesting() below.
|
||||
///
|
||||
pub fn voting_power(&self, voting_mint_config: &VotingMintConfig, curr_ts: i64) -> Result<u64> {
|
||||
let unlocked_vote_weight =
|
||||
voting_mint_config.unlocked_vote_weight(self.amount_deposited_native)?;
|
||||
let baseline_vote_weight =
|
||||
voting_mint_config.baseline_vote_weight(self.amount_deposited_native)?;
|
||||
let max_locked_vote_weight =
|
||||
voting_mint_config.max_lockup_vote_weight(self.amount_initially_locked_native)?;
|
||||
voting_mint_config.max_extra_lockup_vote_weight(self.amount_initially_locked_native)?;
|
||||
let locked_vote_weight = self.voting_power_locked(
|
||||
curr_ts,
|
||||
max_locked_vote_weight,
|
||||
|
@ -102,7 +97,7 @@ impl DepositEntry {
|
|||
locked_vote_weight <= max_locked_vote_weight,
|
||||
InternalErrorBadLockupVoteWeight
|
||||
);
|
||||
unlocked_vote_weight
|
||||
baseline_vote_weight
|
||||
.checked_add(locked_vote_weight)
|
||||
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))
|
||||
}
|
||||
|
@ -367,7 +362,7 @@ mod tests {
|
|||
is_used: true,
|
||||
allow_clawback: false,
|
||||
voting_mint_config_idx: 0,
|
||||
padding: [0; 13],
|
||||
reserved: [0; 29],
|
||||
};
|
||||
let initial_deposit = deposit.clone();
|
||||
let month = deposit.lockup.kind.period_secs() as i64;
|
||||
|
@ -436,28 +431,29 @@ mod tests {
|
|||
start_ts: lockup_start,
|
||||
end_ts: lockup_start + 2 * day,
|
||||
kind: Daily,
|
||||
padding: [0; 15],
|
||||
reserved: [0; 15],
|
||||
},
|
||||
is_used: true,
|
||||
allow_clawback: false,
|
||||
voting_mint_config_idx: 0,
|
||||
padding: [0; 13],
|
||||
reserved: [0; 29],
|
||||
};
|
||||
let voting_mint_config = VotingMintConfig {
|
||||
mint: Pubkey::default(),
|
||||
grant_authority: Pubkey::default(),
|
||||
unlocked_scaled_factor: 1_000_000_000, // 1x
|
||||
lockup_scaled_factor: 1_000_000_000, // 1x
|
||||
baseline_vote_weight_scaled_factor: 1_000_000_000, // 1x
|
||||
max_extra_lockup_vote_weight_scaled_factor: 1_000_000_000, // 1x
|
||||
lockup_saturation_secs: saturation as u64,
|
||||
digit_shift: 0,
|
||||
padding: [0; 31],
|
||||
reserved1: [0; 7],
|
||||
reserved2: [0; 7],
|
||||
};
|
||||
|
||||
let unlocked_vote_weight =
|
||||
voting_mint_config.unlocked_vote_weight(deposit.amount_deposited_native)?;
|
||||
assert_eq!(unlocked_vote_weight, 10_000);
|
||||
let max_locked_vote_weight =
|
||||
voting_mint_config.max_lockup_vote_weight(deposit.amount_initially_locked_native)?;
|
||||
let baseline_vote_weight =
|
||||
voting_mint_config.baseline_vote_weight(deposit.amount_deposited_native)?;
|
||||
assert_eq!(baseline_vote_weight, 10_000);
|
||||
let max_locked_vote_weight = voting_mint_config
|
||||
.max_extra_lockup_vote_weight(deposit.amount_initially_locked_native)?;
|
||||
assert_eq!(max_locked_vote_weight, 10_000);
|
||||
|
||||
// The timestamp 100_000 is very far before the lockup_start timestamp
|
||||
|
|
|
@ -44,9 +44,10 @@ pub struct Lockup {
|
|||
pub kind: LockupKind,
|
||||
|
||||
// Empty bytes for future upgrades.
|
||||
pub padding: [u8; 15],
|
||||
pub reserved: [u8; 15],
|
||||
}
|
||||
const_assert!(std::mem::size_of::<Lockup>() == 2 * 8 + 1 + 15);
|
||||
const_assert!(std::mem::size_of::<Lockup>() % 8 == 0);
|
||||
|
||||
impl Default for Lockup {
|
||||
fn default() -> Self {
|
||||
|
@ -54,7 +55,7 @@ impl Default for Lockup {
|
|||
kind: LockupKind::None,
|
||||
start_ts: 0,
|
||||
end_ts: 0,
|
||||
padding: [0; 15],
|
||||
reserved: [0; 15],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +82,7 @@ impl Lockup {
|
|||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
padding: [0; 15],
|
||||
reserved: [0; 15],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -753,7 +754,7 @@ mod tests {
|
|||
kind: LockupKind::Cliff,
|
||||
start_ts,
|
||||
end_ts,
|
||||
padding: [0u8; 15],
|
||||
reserved: [0u8; 15],
|
||||
};
|
||||
let days_left = l.periods_left(curr_ts)?;
|
||||
assert_eq!(days_left, t.expected_days_left);
|
||||
|
@ -768,7 +769,7 @@ mod tests {
|
|||
kind: LockupKind::Monthly,
|
||||
start_ts,
|
||||
end_ts,
|
||||
padding: [0u8; 15],
|
||||
reserved: [0u8; 15],
|
||||
};
|
||||
let months_left = l.periods_left(curr_ts)?;
|
||||
assert_eq!(months_left, t.expected_months_left);
|
||||
|
@ -788,9 +789,9 @@ mod tests {
|
|||
start_ts,
|
||||
end_ts,
|
||||
kind: t.kind,
|
||||
padding: [0u8; 15],
|
||||
reserved: [0u8; 15],
|
||||
},
|
||||
padding: [0; 13],
|
||||
reserved: [0; 29],
|
||||
};
|
||||
let curr_ts = start_ts + days_to_secs(t.curr_day);
|
||||
let power = d.voting_power_locked(curr_ts, t.amount_deposited, MAX_SECS_LOCKED)?;
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct Registrar {
|
|||
pub realm: Pubkey,
|
||||
pub realm_governing_token_mint: Pubkey,
|
||||
pub realm_authority: Pubkey,
|
||||
pub padding1: [u8; 32],
|
||||
pub reserved1: [u8; 32],
|
||||
|
||||
/// Storage for voting mints and their configuration.
|
||||
/// The length should be adjusted for one's use case.
|
||||
|
@ -20,9 +20,11 @@ pub struct Registrar {
|
|||
/// Debug only: time offset, to allow tests to move forward in time.
|
||||
pub time_offset: i64,
|
||||
pub bump: u8,
|
||||
pub padding2: [u8; 31],
|
||||
pub reserved2: [u8; 7],
|
||||
pub reserved3: [u64; 11], // split because `Default` does not support [u8; 95]
|
||||
}
|
||||
const_assert!(std::mem::size_of::<Registrar>() == 5 * 32 + 4 * 120 + 8 + 1 + 31);
|
||||
const_assert!(std::mem::size_of::<Registrar>() == 5 * 32 + 4 * 152 + 8 + 1 + 95);
|
||||
const_assert!(std::mem::size_of::<Registrar>() % 8 == 0);
|
||||
|
||||
impl Registrar {
|
||||
pub fn clock_unix_timestamp(&self) -> i64 {
|
||||
|
@ -53,10 +55,10 @@ impl Registrar {
|
|||
.ok_or(Error::ErrorCode(ErrorCode::VotingMintNotFound))?;
|
||||
let mint = Account::<Mint>::try_from(mint_account)?;
|
||||
sum = sum
|
||||
.checked_add(voting_mint_config.unlocked_vote_weight(mint.supply)?)
|
||||
.checked_add(voting_mint_config.baseline_vote_weight(mint.supply)?)
|
||||
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))?;
|
||||
sum = sum
|
||||
.checked_add(voting_mint_config.max_lockup_vote_weight(mint.supply)?)
|
||||
.checked_add(voting_mint_config.max_extra_lockup_vote_weight(mint.supply)?)
|
||||
.ok_or(Error::ErrorCode(ErrorCode::VoterWeightOverflow))?;
|
||||
Ok(sum)
|
||||
})
|
||||
|
|
|
@ -12,11 +12,13 @@ pub struct Voter {
|
|||
pub deposits: [DepositEntry; 32],
|
||||
pub voter_bump: u8,
|
||||
pub voter_weight_record_bump: u8,
|
||||
pub padding: [u8; 30],
|
||||
pub reserved: [u8; 94],
|
||||
}
|
||||
const_assert!(std::mem::size_of::<Voter>() == 2 * 32 + 32 * 64 + 2 + 30);
|
||||
const_assert!(std::mem::size_of::<Voter>() == 2 * 32 + 32 * 80 + 2 + 94);
|
||||
const_assert!(std::mem::size_of::<Voter>() % 8 == 0);
|
||||
|
||||
impl Voter {
|
||||
/// The full vote weight available to the voter
|
||||
pub fn weight(&self, registrar: &Registrar) -> Result<u64> {
|
||||
let curr_ts = registrar.clock_unix_timestamp();
|
||||
self.deposits
|
||||
|
@ -31,13 +33,14 @@ impl Voter {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn weight_from_unlocked(&self, registrar: &Registrar) -> Result<u64> {
|
||||
/// The vote weight available to the voter when ignoring any lockup effects
|
||||
pub fn weight_baseline(&self, registrar: &Registrar) -> Result<u64> {
|
||||
self.deposits
|
||||
.iter()
|
||||
.filter(|d| d.is_used)
|
||||
.try_fold(0u64, |sum, d| {
|
||||
registrar.voting_mints[d.voting_mint_config_idx as usize]
|
||||
.unlocked_vote_weight(d.amount_deposited_native)
|
||||
.baseline_vote_weight(d.amount_deposited_native)
|
||||
.map(|vp| sum.checked_add(vp).unwrap())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,11 +18,19 @@ pub struct VotingMintConfig {
|
|||
/// The authority that is allowed to push grants into voters
|
||||
pub grant_authority: Pubkey,
|
||||
|
||||
/// Vote weight factor for unlocked deposits, in 1/SCALED_FACTOR_BASE units.
|
||||
pub unlocked_scaled_factor: u64,
|
||||
/// Vote weight factor for all funds in the account, no matter if locked or not.
|
||||
///
|
||||
/// In 1/SCALED_FACTOR_BASE units.
|
||||
pub baseline_vote_weight_scaled_factor: u64,
|
||||
|
||||
/// Maximum vote weight factor for lockups, in 1/SCALED_FACTOR_BASE units.
|
||||
pub lockup_scaled_factor: u64,
|
||||
/// Maximum extra vote weight factor for lockups.
|
||||
///
|
||||
/// This is the extra votes gained for lockups lasting lockup_saturation_secs or
|
||||
/// longer. Shorter lockups receive only a fraction of the maximum extra vote weight,
|
||||
/// based on lockup_time divided by lockup_saturation_secs.
|
||||
///
|
||||
/// In 1/SCALED_FACTOR_BASE units.
|
||||
pub max_extra_lockup_vote_weight_scaled_factor: u64,
|
||||
|
||||
/// Number of seconds of lockup needed to reach the maximum lockup bonus.
|
||||
pub lockup_saturation_secs: u64,
|
||||
|
@ -31,15 +39,17 @@ pub struct VotingMintConfig {
|
|||
pub digit_shift: i8,
|
||||
|
||||
// Empty bytes for future upgrades.
|
||||
pub padding: [u8; 31],
|
||||
pub reserved1: [u8; 7],
|
||||
pub reserved2: [u64; 7], // split because `Default` does not support [u8; 63]
|
||||
}
|
||||
const_assert!(std::mem::size_of::<VotingMintConfig>() == 2 * 32 + 3 * 8 + 1 + 31);
|
||||
const_assert!(std::mem::size_of::<VotingMintConfig>() == 2 * 32 + 3 * 8 + 1 + 63);
|
||||
const_assert!(std::mem::size_of::<VotingMintConfig>() % 8 == 0);
|
||||
|
||||
impl VotingMintConfig {
|
||||
/// 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.
|
||||
pub fn base_vote_weight(&self, amount_native: u64) -> Result<u64> {
|
||||
fn digit_shift_native(&self, amount_native: u64) -> Result<u64> {
|
||||
let compute = || -> Option<u64> {
|
||||
let val = if self.digit_shift < 0 {
|
||||
(amount_native as u128).checked_div(10u128.pow((-self.digit_shift) as u32))?
|
||||
|
@ -52,10 +62,10 @@ impl VotingMintConfig {
|
|||
}
|
||||
|
||||
/// Apply a factor in SCALED_FACTOR_BASE units.
|
||||
fn apply_factor(base_vote_weight: u64, factor: u64) -> Result<u64> {
|
||||
fn apply_factor(base: u64, factor: u64) -> Result<u64> {
|
||||
let compute = || -> Option<u64> {
|
||||
u64::try_from(
|
||||
(base_vote_weight as u128)
|
||||
(base as u128)
|
||||
.checked_mul(factor as u128)?
|
||||
.checked_div(SCALED_FACTOR_BASE as u128)?,
|
||||
)
|
||||
|
@ -65,19 +75,22 @@ impl VotingMintConfig {
|
|||
}
|
||||
|
||||
/// The vote weight a deposit of a number of native tokens should have.
|
||||
pub fn unlocked_vote_weight(&self, amount_native: u64) -> Result<u64> {
|
||||
///
|
||||
/// 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<u64> {
|
||||
Self::apply_factor(
|
||||
self.base_vote_weight(amount_native)?,
|
||||
self.unlocked_scaled_factor,
|
||||
self.digit_shift_native(amount_native)?,
|
||||
self.baseline_vote_weight_scaled_factor,
|
||||
)
|
||||
}
|
||||
|
||||
/// The maximum vote weight a number of locked up native tokens can have.
|
||||
/// 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_lockup_vote_weight(&self, amount_native: u64) -> Result<u64> {
|
||||
pub fn max_extra_lockup_vote_weight(&self, amount_native: u64) -> Result<u64> {
|
||||
Self::apply_factor(
|
||||
self.base_vote_weight(amount_native)?,
|
||||
self.lockup_scaled_factor,
|
||||
self.digit_shift_native(amount_native)?,
|
||||
self.max_extra_lockup_vote_weight_scaled_factor,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -92,7 +105,8 @@ impl VotingMintConfig {
|
|||
/// want to use the grant / vesting / clawback functionality for non-voting
|
||||
/// tokens like USDC.
|
||||
pub fn grants_vote_weight(&self) -> bool {
|
||||
self.unlocked_scaled_factor > 0 || self.lockup_scaled_factor > 0
|
||||
self.baseline_vote_weight_scaled_factor > 0
|
||||
|| self.max_extra_lockup_vote_weight_scaled_factor > 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,8 +99,8 @@ impl AddinCookie {
|
|||
index: u16,
|
||||
mint: &MintCookie,
|
||||
digit_shift: i8,
|
||||
unlocked_scaled_factor: f64,
|
||||
lockup_scaled_factor: f64,
|
||||
baseline_vote_weight_scaled_factor: f64,
|
||||
max_extra_lockup_vote_weight_scaled_factor: f64,
|
||||
lockup_saturation_secs: u64,
|
||||
grant_authority: Option<Pubkey>,
|
||||
other_mints: Option<&[Pubkey]>,
|
||||
|
@ -111,8 +111,10 @@ impl AddinCookie {
|
|||
&voter_stake_registry::instruction::ConfigureVotingMint {
|
||||
idx: index,
|
||||
digit_shift,
|
||||
unlocked_scaled_factor: (unlocked_scaled_factor * 1e9) as u64,
|
||||
lockup_scaled_factor: (lockup_scaled_factor * 1e9) as u64,
|
||||
baseline_vote_weight_scaled_factor: (baseline_vote_weight_scaled_factor * 1e9)
|
||||
as u64,
|
||||
max_extra_lockup_vote_weight_scaled_factor:
|
||||
(max_extra_lockup_vote_weight_scaled_factor * 1e9) as u64,
|
||||
lockup_saturation_secs,
|
||||
grant_authority,
|
||||
},
|
||||
|
|
|
@ -106,7 +106,7 @@ impl TestContext {
|
|||
processor!(voter_stake_registry::entry),
|
||||
);
|
||||
// intentionally set to half the limit, to catch potential problems early
|
||||
test.set_compute_max_units(110000);
|
||||
test.set_compute_max_units(120000);
|
||||
|
||||
let governance_program_id =
|
||||
Pubkey::from_str(&"GovernanceProgramTest1111111111111111111111").unwrap();
|
||||
|
|
|
@ -137,7 +137,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> {
|
|||
|
||||
let voter_event =
|
||||
deserialize_event::<voter_stake_registry::events::VoterInfo>(&log[2]).unwrap();
|
||||
assert_eq!(voter_event.voting_power_unlocked_only, 12000);
|
||||
assert_eq!(voter_event.voting_power_baseline, 12000);
|
||||
assert_eq!(
|
||||
voter_event.voting_power,
|
||||
12000 + (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11) * 1000 / 12
|
||||
|
@ -150,8 +150,8 @@ async fn test_log_voter_info() -> Result<(), TransportError> {
|
|||
assert_eq!(deposit_event.unlocked, 1000);
|
||||
assert_eq!(deposit_event.voting_power, voter_event.voting_power);
|
||||
assert_eq!(
|
||||
deposit_event.voting_power_unlocked_only,
|
||||
voter_event.voting_power_unlocked_only
|
||||
deposit_event.voting_power_baseline,
|
||||
voter_event.voting_power_baseline
|
||||
);
|
||||
assert!(deposit_event.locking.is_some());
|
||||
let locking = deposit_event.locking.unwrap();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export type VoterStakeRegistry = {
|
||||
"version": "0.1.11",
|
||||
"version": "0.2.0",
|
||||
"name": "voter_stake_registry",
|
||||
"instructions": [
|
||||
{
|
||||
|
@ -82,11 +82,11 @@ export type VoterStakeRegistry = {
|
|||
"type": "i8"
|
||||
},
|
||||
{
|
||||
"name": "unlockedScaledFactor",
|
||||
"name": "baselineVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lockupScaledFactor",
|
||||
"name": "maxExtraLockupVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
@ -755,7 +755,7 @@ export type VoterStakeRegistry = {
|
|||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "padding1",
|
||||
"name": "reserved1",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
|
@ -783,11 +783,20 @@ export type VoterStakeRegistry = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding2",
|
||||
"name": "reserved2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
31
|
||||
7
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved3",
|
||||
"type": {
|
||||
"array": [
|
||||
"u64",
|
||||
11
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -827,11 +836,11 @@ export type VoterStakeRegistry = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
30
|
||||
94
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -872,11 +881,11 @@ export type VoterStakeRegistry = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
13
|
||||
29
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -945,7 +954,7 @@ export type VoterStakeRegistry = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
|
@ -970,11 +979,11 @@ export type VoterStakeRegistry = {
|
|||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "unlockedScaledFactor",
|
||||
"name": "baselineVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lockupScaledFactor",
|
||||
"name": "maxExtraLockupVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
@ -986,11 +995,20 @@ export type VoterStakeRegistry = {
|
|||
"type": "i8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved1",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
31
|
||||
7
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u64",
|
||||
7
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1031,7 +1049,7 @@ export type VoterStakeRegistry = {
|
|||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "votingPowerUnlockedOnly",
|
||||
"name": "votingPowerBaseline",
|
||||
"type": "u64",
|
||||
"index": false
|
||||
}
|
||||
|
@ -1061,7 +1079,7 @@ export type VoterStakeRegistry = {
|
|||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "votingPowerUnlockedOnly",
|
||||
"name": "votingPowerBaseline",
|
||||
"type": "u64",
|
||||
"index": false
|
||||
},
|
||||
|
@ -1267,7 +1285,7 @@ export type VoterStakeRegistry = {
|
|||
};
|
||||
|
||||
export const IDL: VoterStakeRegistry = {
|
||||
"version": "0.1.11",
|
||||
"version": "0.2.0",
|
||||
"name": "voter_stake_registry",
|
||||
"instructions": [
|
||||
{
|
||||
|
@ -1350,11 +1368,11 @@ export const IDL: VoterStakeRegistry = {
|
|||
"type": "i8"
|
||||
},
|
||||
{
|
||||
"name": "unlockedScaledFactor",
|
||||
"name": "baselineVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lockupScaledFactor",
|
||||
"name": "maxExtraLockupVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
@ -2023,7 +2041,7 @@ export const IDL: VoterStakeRegistry = {
|
|||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "padding1",
|
||||
"name": "reserved1",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
|
@ -2051,11 +2069,20 @@ export const IDL: VoterStakeRegistry = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding2",
|
||||
"name": "reserved2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
31
|
||||
7
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved3",
|
||||
"type": {
|
||||
"array": [
|
||||
"u64",
|
||||
11
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2095,11 +2122,11 @@ export const IDL: VoterStakeRegistry = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
30
|
||||
94
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2140,11 +2167,11 @@ export const IDL: VoterStakeRegistry = {
|
|||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
13
|
||||
29
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2213,7 +2240,7 @@ export const IDL: VoterStakeRegistry = {
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
|
@ -2238,11 +2265,11 @@ export const IDL: VoterStakeRegistry = {
|
|||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "unlockedScaledFactor",
|
||||
"name": "baselineVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lockupScaledFactor",
|
||||
"name": "maxExtraLockupVoteWeightScaledFactor",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
|
@ -2254,11 +2281,20 @@ export const IDL: VoterStakeRegistry = {
|
|||
"type": "i8"
|
||||
},
|
||||
{
|
||||
"name": "padding",
|
||||
"name": "reserved1",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
31
|
||||
7
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reserved2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u64",
|
||||
7
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -2299,7 +2335,7 @@ export const IDL: VoterStakeRegistry = {
|
|||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "votingPowerUnlockedOnly",
|
||||
"name": "votingPowerBaseline",
|
||||
"type": "u64",
|
||||
"index": false
|
||||
}
|
||||
|
@ -2329,7 +2365,7 @@ export const IDL: VoterStakeRegistry = {
|
|||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "votingPowerUnlockedOnly",
|
||||
"name": "votingPowerBaseline",
|
||||
"type": "u64",
|
||||
"index": false
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue