From 67de81f09f4cca98ce053fcfb7c3d310f6c76afc Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 26 Sep 2024 19:29:53 +0100 Subject: [PATCH 1/9] added authorized agent --- program-states/Cargo.toml | 1 - program-states/src/state/voter.rs | 13 ++++++++++++- programs/voter-stake-registry/Cargo.toml | 1 - .../src/instructions/create_deposit_entry.rs | 2 +- .../src/instructions/create_voter.rs | 1 + 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/program-states/Cargo.toml b/program-states/Cargo.toml index c24dcf71..898a96d2 100644 --- a/program-states/Cargo.toml +++ b/program-states/Cargo.toml @@ -1,4 +1,3 @@ -cargo-features = ["workspace-inheritance"] [package] name = "mplx-staking-states" version = "0.0.1" diff --git a/program-states/src/state/voter.rs b/program-states/src/state/voter.rs index 51d25730..36b6eb08 100644 --- a/program-states/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -4,17 +4,28 @@ 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::() == 144 * 32 + 32 + 32 + 8 + 8 + 1 + 1 + 1 + 13); +const_assert!(std::mem::size_of::() == 144 * 32 + 32 + 32 + 32 + 8 + 8 + 1 + 1 + 1 + 13); const_assert!(std::mem::size_of::() % 8 == 0); impl Voter { diff --git a/programs/voter-stake-registry/Cargo.toml b/programs/voter-stake-registry/Cargo.toml index d746a05f..f88931d1 100644 --- a/programs/voter-stake-registry/Cargo.toml +++ b/programs/voter-stake-registry/Cargo.toml @@ -1,4 +1,3 @@ -cargo-features = ["workspace-inheritance"] [package] name = "mpl-staking" version = "0.1.0" diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index de043780..83f01f3c 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -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, diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 8bd56b23..1e068b75 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -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; From 6383825d2061e0fd80319c19eeae04657c16de94 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 26 Sep 2024 19:51:28 +0100 Subject: [PATCH 2/9] change_authorized_agent method + fmt --- program-states/src/state/voter.rs | 11 +++++-- .../instructions/change_authorized_agent.rs | 32 +++++++++++++++++++ .../src/instructions/mod.rs | 2 ++ programs/voter-stake-registry/src/lib.rs | 7 ++++ 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 programs/voter-stake-registry/src/instructions/change_authorized_agent.rs diff --git a/program-states/src/state/voter.rs b/program-states/src/state/voter.rs index 36b6eb08..37bb97dd 100644 --- a/program-states/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -6,13 +6,18 @@ use anchor_lang::prelude::*; 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. + /// 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. + /// 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. + /// 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, diff --git a/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs b/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs new file mode 100644 index 00000000..8caa608b --- /dev/null +++ b/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs @@ -0,0 +1,32 @@ +use crate::cpi_instructions; +use anchor_lang::prelude::*; +use mplx_staking_states::{ + error::MplStakingError, + registrar_seeds, + state::{Registrar, Voter}, +}; + +#[derive(Accounts)] +pub struct ChangeAuthorizedAgent<'info> { + pub registrar: AccountLoader<'info, Registrar>, + + // checking the PDA address is just an extra precaution, + // the other constraints must be exhaustive + #[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, agent: Pubkey) -> Result<()> { + let voter = &mut ctx.accounts.voter.load_mut()?; + voter.authorized_agent = agent; + let voter_authority = voter.voter_authority; + + Ok(()) +} diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index 1ec88a62..c806c998 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -1,3 +1,4 @@ +pub use change_authorized_agent::*; pub use change_delegate::*; pub use claim::*; pub use close_deposit_entry::*; @@ -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; diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 3c05cb10..bb2f2db7 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -163,6 +163,13 @@ pub mod mpl_staking { instructions::claim(ctx, realm_pubkey) } + pub fn change_authorized_agent( + ctx: Context, + agent: Pubkey, + ) -> Result<()> { + instructions::change_authorized_agent(ctx, agent) + } + pub fn change_delegate(ctx: Context, deposit_entry_index: u8) -> Result<()> { instructions::change_delegate(ctx, deposit_entry_index) } From d4cecd3ee22590911f7f31411af185b3d1e019a9 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 26 Sep 2024 20:01:23 +0100 Subject: [PATCH 3/9] cleanup --- .../src/instructions/change_authorized_agent.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs b/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs index 8caa608b..ba878d8c 100644 --- a/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs +++ b/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs @@ -1,10 +1,5 @@ -use crate::cpi_instructions; use anchor_lang::prelude::*; -use mplx_staking_states::{ - error::MplStakingError, - registrar_seeds, - state::{Registrar, Voter}, -}; +use mplx_staking_states::state::{Registrar, Voter}; #[derive(Accounts)] pub struct ChangeAuthorizedAgent<'info> { @@ -26,7 +21,6 @@ pub struct ChangeAuthorizedAgent<'info> { pub fn change_authorized_agent(ctx: Context, agent: Pubkey) -> Result<()> { let voter = &mut ctx.accounts.voter.load_mut()?; voter.authorized_agent = agent; - let voter_authority = voter.voter_authority; Ok(()) } From 7352488481a5ef0f4d7f11047a52b5ab657e0d53 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 27 Sep 2024 09:31:18 +0100 Subject: [PATCH 4/9] returned the fix for tests in place --- program-states/Cargo.toml | 1 + programs/voter-stake-registry/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/program-states/Cargo.toml b/program-states/Cargo.toml index 898a96d2..c24dcf71 100644 --- a/program-states/Cargo.toml +++ b/program-states/Cargo.toml @@ -1,3 +1,4 @@ +cargo-features = ["workspace-inheritance"] [package] name = "mplx-staking-states" version = "0.0.1" diff --git a/programs/voter-stake-registry/Cargo.toml b/programs/voter-stake-registry/Cargo.toml index f88931d1..d746a05f 100644 --- a/programs/voter-stake-registry/Cargo.toml +++ b/programs/voter-stake-registry/Cargo.toml @@ -1,3 +1,4 @@ +cargo-features = ["workspace-inheritance"] [package] name = "mpl-staking" version = "0.1.0" From eb93fcead565b92a41e7603f330bd5b9ffc75d46 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 27 Sep 2024 11:12:36 +0100 Subject: [PATCH 5/9] fmt --- .../src/instructions/change_delegate.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/change_delegate.rs b/programs/voter-stake-registry/src/instructions/change_delegate.rs index 871fc79c..08254405 100644 --- a/programs/voter-stake-registry/src/instructions/change_delegate.rs +++ b/programs/voter-stake-registry/src/instructions/change_delegate.rs @@ -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>, @@ -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, - deposit_entry_index: u8, -) -> Result<()> { +pub fn change_delegate(ctx: Context, 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; From 844803f6fa6ba8179f386414f6f968fe3502e9cd Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 27 Sep 2024 11:32:13 +0100 Subject: [PATCH 6/9] using the foundation repo for constants repo --- Cargo.lock | 2 +- programs/voter-stake-registry/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17fc0e4a..34567589 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1980,7 +1980,7 @@ dependencies = [ [[package]] name = "mpl-common-constants" version = "0.1.0" -source = "git+ssh://git@github.com/adm-metaex/mpl-common-constants#cfbd4284f2963c797ca2cff68b1f5878f8d2795e" +source = "git+https://github.com/metaplex-foundation/mpl-common-constants#46675e94a5f5e383543d08e71e2ef8e111edb52a" [[package]] name = "mpl-staking" diff --git a/programs/voter-stake-registry/Cargo.toml b/programs/voter-stake-registry/Cargo.toml index d746a05f..61a19926 100644 --- a/programs/voter-stake-registry/Cargo.toml +++ b/programs/voter-stake-registry/Cargo.toml @@ -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 } From d60dcafc357d245fb25f6b0105a4f88e2fceaeea Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 27 Sep 2024 11:40:55 +0100 Subject: [PATCH 7/9] clippy code --- programs/voter-stake-registry/src/instructions/close_voter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/voter-stake-registry/src/instructions/close_voter.rs b/programs/voter-stake-registry/src/instructions/close_voter.rs index 0dc7a029..390bac24 100644 --- a/programs/voter-stake-registry/src/instructions/close_voter.rs +++ b/programs/voter-stake-registry/src/instructions/close_voter.rs @@ -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::::try_from(&deposit_vault_raw) + let deposit_vault_ta = Account::::try_from(deposit_vault_raw) .map_err(|_| MplStakingError::DeserializationError)?; require_keys_eq!( deposit_vault_ta.owner, From cbfdf97084e68bf427a12d01332d45c3f86be178 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Fri, 27 Sep 2024 12:00:37 +0100 Subject: [PATCH 8/9] manual fmt --- .../src/instructions/change_authorized_agent.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs b/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs index ba878d8c..fd27fbac 100644 --- a/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs +++ b/programs/voter-stake-registry/src/instructions/change_authorized_agent.rs @@ -4,15 +4,12 @@ use mplx_staking_states::state::{Registrar, Voter}; #[derive(Accounts)] pub struct ChangeAuthorizedAgent<'info> { pub registrar: AccountLoader<'info, Registrar>, - - // checking the PDA address is just an extra precaution, - // the other constraints must be exhaustive #[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)] + 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>, } From 0734a767839d222ef04e1c984acbeb72fbe9ad54 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 27 Sep 2024 15:46:48 +0300 Subject: [PATCH 9/9] add basic test for change_authorized_agent --- .../tests/change_authorized_agent.rs | 103 ++++++++++++++++++ .../tests/program_test/addin.rs | 39 ++++++- 2 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 programs/voter-stake-registry/tests/change_authorized_agent.rs diff --git a/programs/voter-stake-registry/tests/change_authorized_agent.rs b/programs/voter-stake-registry/tests/change_authorized_agent.rs new file mode 100644 index 00000000..39d41114 --- /dev/null +++ b/programs/voter-stake-registry/tests/change_authorized_agent.rs @@ -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( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + None, + None, + ) + .await; + let _mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &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( + ®istrar, + &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(®istrar, &voter, authorized_agent.pubkey()) + .await?; + + let voter_account = voter.get_voter(&context.solana).await; + assert_eq!(voter_account.authorized_agent, authorized_agent.pubkey()); + + Ok(()) +} diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index e91ae01e..7d86d71b 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -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 { @@ -829,9 +860,13 @@ impl VotingMintConfigCookie { } impl VoterCookie { + pub async fn get_voter(&self, solana: &SolanaCookie) -> Voter { + solana.get_account::(self.address).await + } + pub async fn deposit_amount(&self, solana: &SolanaCookie, deposit_id: u8) -> u64 { - solana.get_account::(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 {