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

Feature/authorized agent audited #39

Merged
merged 9 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion program-states/src/state/voter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,33 @@ use anchor_lang::prelude::*;
/// User account for minting voting rights.
#[account(zero_copy)]
pub struct Voter {
/// The deposits that the voter has made.
pub deposits: [DepositEntry; 32],
/// Authorized agent. This pubkey is authorized by the staker/voter to perform permissioned
/// actions that require stake. This is the same as the voter_authority initially, but may be
/// changed by the voter_authority in order to not expose the voter_authority's private key.
pub authorized_agent: Pubkey,
/// The voter_authority is the account that has the right to vote with the voter's stake. This
/// is the account that will sign the vote transactions as well as the account that will sign
/// the withdrawal transactions.
pub voter_authority: Pubkey,
/// The pubkey of the registrar that the voter is registered with.
pub registrar: Pubkey,
/// The total weighted stake that the voter was penalized for. This reduces the voter's
/// effective stake.
pub decreased_weighted_stake_by: u64,
/// The batch minting is restricted until this timestamp.
pub batch_minting_restricted_until: u64,
/// The bump seed used to derive the voter_authority.
pub voter_bump: u8,
/// The bump seed used to derive the voter_weight_record.
pub voter_weight_record_bump: u8,
/// The bitmap of penalties that the voter has incurred.
pub penalties: u8,
/// Reserved for allignment and future use.
pub _reserved1: [u8; 13],
}
const_assert!(std::mem::size_of::<Voter>() == 144 * 32 + 32 + 32 + 8 + 8 + 1 + 1 + 1 + 13);
const_assert!(std::mem::size_of::<Voter>() == 144 * 32 + 32 + 32 + 32 + 8 + 8 + 1 + 1 + 1 + 13);
const_assert!(std::mem::size_of::<Voter>() % 8 == 0);

impl Voter {
Expand Down
2 changes: 1 addition & 1 deletion programs/voter-stake-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ bytemuck = "1.9.1"
spl-governance = { version = "3.1.1", features = ["no-entrypoint"] }
spl-governance-addin-api = "0.1.3"
mplx-staking-states = { path="../../program-states" }
mpl-common-constants = { git = "ssh://git@github.com/adm-metaex/mpl-common-constants", features = ["devnet"] }
mpl-common-constants = { git = "https://github.com/metaplex-foundation/mpl-common-constants", features = ["devnet"] }

[dev-dependencies]
solana-sdk = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use anchor_lang::prelude::*;
use mplx_staking_states::state::{Registrar, Voter};

#[derive(Accounts)]
pub struct ChangeAuthorizedAgent<'info> {
pub registrar: AccountLoader<'info, Registrar>,
#[account(
mut,
seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()],
bump = voter.load()?.voter_bump,
has_one = voter_authority,
has_one = registrar)]
pub voter: AccountLoader<'info, Voter>,
pub voter_authority: Signer<'info>,
}

/// Changes the authorized agent for the voter.
pub fn change_authorized_agent(ctx: Context<ChangeAuthorizedAgent>, agent: Pubkey) -> Result<()> {
let voter = &mut ctx.accounts.voter.load_mut()?;
voter.authorized_agent = agent;

Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ pub struct ChangeDelegate<'info> {
/// The address of the mining account on the rewards program
/// derived from PDA(["mining", delegate wallet addr, reward_pool], rewards_program)
#[account(
mut,
mut,
seeds = [
b"mining",
delegate_voter.load()?.voter_authority.as_ref(),
delegate_voter.load()?.voter_authority.as_ref(),
reward_pool.key().as_ref()
],
bump,
],
bump,
seeds::program = rewards_program.key()
)]
pub new_delegate_mining: UncheckedAccount<'info>,
Expand Down Expand Up @@ -69,10 +69,7 @@ pub struct ChangeDelegate<'info> {
/// Rewards will be recalculated, and the new delegate will start receiving rewards.
/// The old delegate will stop receiving rewards.
/// It might be done once per five days.
pub fn change_delegate(
ctx: Context<ChangeDelegate>,
deposit_entry_index: u8,
) -> Result<()> {
pub fn change_delegate(ctx: Context<ChangeDelegate>, deposit_entry_index: u8) -> Result<()> {
let registrar = &ctx.accounts.registrar.load()?;
let voter = &mut ctx.accounts.voter.load_mut()?;
let voter_authority = voter.voter_authority;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub fn close_voter<'info>(ctx: Context<'_, '_, '_, 'info, CloseVoter<'info>>) ->

// will close all the token accounts owned by the voter
for deposit_vault_raw in ctx.remaining_accounts {
let deposit_vault_ta = Account::<TokenAccount>::try_from(&deposit_vault_raw)
let deposit_vault_ta = Account::<TokenAccount>::try_from(deposit_vault_raw)
.map_err(|_| MplStakingError::DeserializationError)?;
require_keys_eq!(
deposit_vault_ta.owner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use mplx_staking_states::{
pub struct CreateDepositEntry<'info> {
pub registrar: AccountLoader<'info, Registrar>,

// checking the PDA address it just an extra precaution,
// checking the PDA address is just an extra precaution,
// the other constraints must be exhaustive
#[account(
mut,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub fn create_voter(
voter.voter_bump = voter_bump;
voter.voter_weight_record_bump = voter_weight_record_bump;
voter.voter_authority = voter_authority;
voter.authorized_agent = voter_authority;
voter.registrar = ctx.accounts.registrar.key();

let voter_weight_record = &mut ctx.accounts.voter_weight_record;
Expand Down
2 changes: 2 additions & 0 deletions programs/voter-stake-registry/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub use change_authorized_agent::*;
pub use change_delegate::*;
pub use claim::*;
pub use close_deposit_entry::*;
Expand All @@ -15,6 +16,7 @@ pub use unlock_tokens::*;
pub use update_voter_weight_record::*;
pub use withdraw::*;

mod change_authorized_agent;
mod change_delegate;
mod claim;
mod close_deposit_entry;
Expand Down
7 changes: 7 additions & 0 deletions programs/voter-stake-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ pub mod mpl_staking {
instructions::claim(ctx, realm_pubkey)
}

pub fn change_authorized_agent(
ctx: Context<ChangeAuthorizedAgent>,
agent: Pubkey,
) -> Result<()> {
instructions::change_authorized_agent(ctx, agent)
}

pub fn change_delegate(ctx: Context<ChangeDelegate>, deposit_entry_index: u8) -> Result<()> {
instructions::change_delegate(ctx, deposit_entry_index)
}
Expand Down
103 changes: 103 additions & 0 deletions programs/voter-stake-registry/tests/change_authorized_agent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use anchor_spl::token::TokenAccount;
use mplx_staking_states::state::{LockupKind, LockupPeriod};
use program_test::*;
use solana_program_test::*;
use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError};

mod program_test;

#[tokio::test]
async fn stake_with_delegate() -> Result<(), TransportError> {
let context = TestContext::new().await;

let payer = &context.users[0].key;
let realm_authority = Keypair::new();
let realm = context
.governance
.create_realm(
"testrealm",
realm_authority.pubkey(),
&context.mints[0],
payer,
&context.addin.program_id,
)
.await;

let voter_authority = &context.users[1].key;
let token_owner_record = realm
.create_token_owner_record(voter_authority.pubkey(), payer)
.await;

let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap();
let distribution_authority = Keypair::new();
let (registrar, rewards_pool) = context
.addin
.create_registrar(
&realm,
&realm_authority,
payer,
&fill_authority.pubkey(),
&distribution_authority.pubkey(),
&context.rewards.program_id,
)
.await;
context
.addin
.configure_voting_mint(
&registrar,
&realm_authority,
payer,
0,
&context.mints[0],
None,
None,
)
.await;
let _mngo_voting_mint = context
.addin
.configure_voting_mint(
&registrar,
&realm_authority,
payer,
0,
&context.mints[0],
None,
None,
)
.await;

let (deposit_mining, _) = find_deposit_mining_addr(
&context.rewards.program_id,
&voter_authority.pubkey(),
&rewards_pool,
);

let voter = context
.addin
.create_voter(
&registrar,
&token_owner_record,
voter_authority,
payer,
&rewards_pool,
&deposit_mining,
&context.rewards.program_id,
)
.await;

let voter_account = voter.get_voter(&context.solana).await;
assert_eq!(voter_account.authorized_agent, voter.authority.pubkey());

// CREATE AGENT
let authorized_agent = Keypair::new();

context
.addin
.change_authorized_agent(&registrar, &voter, authorized_agent.pubkey())
.await?;

let voter_account = voter.get_voter(&context.solana).await;
assert_eq!(voter_account.authorized_agent, authorized_agent.pubkey());

Ok(())
}
39 changes: 37 additions & 2 deletions programs/voter-stake-registry/tests/program_test/addin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,37 @@ impl AddinCookie {
.process_transaction(&instructions, Some(&[mining_owner]))
.await
}

pub async fn change_authorized_agent(
&self,
registrar: &RegistrarCookie,
voter: &VoterCookie,
agent: Pubkey,
) -> std::result::Result<(), BanksClientError> {
let data =
anchor_lang::InstructionData::data(&mpl_staking::instruction::ChangeAuthorizedAgent {
agent,
});

let accounts = anchor_lang::ToAccountMetas::to_account_metas(
&mpl_staking::accounts::ChangeAuthorizedAgent {
registrar: registrar.address,
voter: voter.address,
voter_authority: voter.authority.pubkey(),
},
None,
);

let instructions = vec![Instruction {
program_id: self.program_id,
accounts,
data,
}];

self.solana
.process_transaction(&instructions, Some(&[&voter.authority]))
.await
}
}

impl VotingMintConfigCookie {
Expand All @@ -829,9 +860,13 @@ impl VotingMintConfigCookie {
}

impl VoterCookie {
pub async fn get_voter(&self, solana: &SolanaCookie) -> Voter {
solana.get_account::<Voter>(self.address).await
}

pub async fn deposit_amount(&self, solana: &SolanaCookie, deposit_id: u8) -> u64 {
solana.get_account::<Voter>(self.address).await.deposits[deposit_id as usize]
.amount_deposited_native
let voter = self.get_voter(solana).await;
voter.deposits[deposit_id as usize].amount_deposited_native
}

pub fn vault_address(&self, mint: &VotingMintConfigCookie) -> Pubkey {
Expand Down
Loading