From 325a0b92c08020487798d591b0197ee9dac5d8a4 Mon Sep 17 00:00:00 2001 From: keyvan Date: Thu, 26 Dec 2024 14:01:55 -0800 Subject: [PATCH 1/4] feat(cli): add close publisher caps and claim rewards instruction --- staking/cli/src/cli.rs | 9 +- staking/cli/src/instructions.rs | 201 +++++++++++++++++++++++++++++++- staking/cli/src/main.rs | 11 ++ 3 files changed, 216 insertions(+), 5 deletions(-) diff --git a/staking/cli/src/cli.rs b/staking/cli/src/cli.rs index 174572bd..4508d013 100644 --- a/staking/cli/src/cli.rs +++ b/staking/cli/src/cli.rs @@ -71,7 +71,7 @@ pub enum Action { )] hermes_url: String, - #[clap(long, default_value = "3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5")] + #[clap(long, default_value = "HDwcJBJXjL9FpJ7UBsYBtaDjsBUhuLCUYoz3zr8SWWaQ")] wormhole: Pubkey, }, InitializePoolRewardCustody {}, @@ -110,6 +110,13 @@ pub enum Action { publisher_caps: Pubkey, }, SaveStakeAccountsSnapshot {}, + ClaimRewards { + #[clap(long, help = "Minimum staked tokens")] + min_staked: u64, + #[clap(long, help = "Minimum reward tokens per publisher")] + min_reward: u64, + }, + CloseAllPublisherCaps {}, } pub enum SignerSource { diff --git a/staking/cli/src/instructions.rs b/staking/cli/src/instructions.rs index e6908dfe..07c3dc3b 100644 --- a/staking/cli/src/instructions.rs +++ b/staking/cli/src/instructions.rs @@ -12,7 +12,10 @@ use { TokenAccount, }, }, - base64::Engine, + base64::{ + prelude::BASE64_STANDARD, + Engine, + }, integration_tests::{ integrity_pool::pda::{ get_delegation_record_address, @@ -86,9 +89,11 @@ use { global_config::GlobalConfig, max_voter_weight_record::MAX_VOTER_WEIGHT, positions::{ + DynamicPositionArray, DynamicPositionArrayAccount, PositionData, PositionState, + Target, }, stake_account::StakeAccountMetadataV2, }, @@ -105,9 +110,12 @@ use { }, mem::size_of, }, - wormhole_core_bridge_solana::sdk::{ - WriteEncodedVaaArgs, - VAA_START, + wormhole_core_bridge_solana::{ + sdk::{ + WriteEncodedVaaArgs, + VAA_START, + }, + state::EncodedVaa, }, wormhole_sdk::vaa::{ Body, @@ -870,6 +878,191 @@ pub fn update_y(rpc_client: &RpcClient, signer: &dyn Signer, y: u64) { process_transaction(rpc_client, &[instruction], &[signer]).unwrap(); } +pub fn close_all_publisher_caps(rpc_client: &RpcClient, signer: &dyn Signer) { + let mut data = EncodedVaa::DISCRIMINATOR.to_vec(); + data.extend_from_slice(&[1]); + data.extend_from_slice(&signer.pubkey().to_bytes()); + + rpc_client + .get_program_accounts_with_config( + &publisher_caps::ID, + RpcProgramAccountsConfig { + filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new( + 0, + MemcmpEncodedBytes::Bytes(PublisherCaps::DISCRIMINATOR.to_vec()), + ))]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + data_slice: None, + commitment: None, + min_context_slot: None, + }, + with_context: None, + }, + ) + .unwrap() + .into_iter() + .for_each(|(pubkey, _account)| close_publisher_caps(rpc_client, signer, pubkey)); +} + +pub fn advance_delegation_record( + rpc_client: &RpcClient, + signer: &dyn Signer, + positions: &Pubkey, + min_reward: u64, +) { + let pool_config = get_pool_config_address(); + + let PoolConfig { + pool_data: pool_data_address, + pyth_token_mint, + .. + } = PoolConfig::try_deserialize( + &mut rpc_client + .get_account_data(&pool_config) + .unwrap() + .as_slice(), + ) + .unwrap(); + + let pool_data = PoolData::try_deserialize( + &mut rpc_client.get_account_data(&pool_data_address).unwrap()[..8 + size_of::()] + .as_ref(), + ) + .unwrap(); + + pool_data + .publishers + .iter() + .enumerate() + .for_each(|(publisher_index, publisher)| { + if *publisher == Pubkey::default() { + return; + } + let publisher_stake_account_positions = + if pool_data.publisher_stake_accounts[publisher_index] == Pubkey::default() { + None + } else { + Some(pool_data.publisher_stake_accounts[publisher_index]) + }; + + let publisher_stake_account_custody = + publisher_stake_account_positions.map(get_stake_account_custody_address); + + let accounts = integrity_pool::accounts::AdvanceDelegationRecord { + delegation_record: get_delegation_record_address(*publisher, *positions), + payer: signer.pubkey(), + pool_config, + pool_data: pool_data_address, + pool_reward_custody: get_pool_reward_custody_address(pyth_token_mint), + publisher: *publisher, + publisher_stake_account_positions, + publisher_stake_account_custody, + stake_account_positions: *positions, + stake_account_custody: get_stake_account_custody_address(*positions), + system_program: system_program::ID, + token_program: spl_token::ID, + }; + + let data = integrity_pool::instruction::AdvanceDelegationRecord {}; + + let ix = Instruction { + program_id: integrity_pool::ID, + accounts: accounts.to_account_metas(None), + data: data.data(), + }; + + + let mut transaction = + Transaction::new_with_payer(&[ix.clone()], Some(&signer.pubkey())); + transaction.sign( + &[signer], + rpc_client + .get_latest_blockhash_with_commitment(CommitmentConfig::finalized()) + .unwrap() + .0, + ); + let res = rpc_client.simulate_transaction(&transaction).unwrap(); + let reward_amount: u64 = u64::from_le_bytes( + BASE64_STANDARD + .decode(res.value.return_data.unwrap().data.0) + .unwrap()[..8] + .try_into() + .unwrap(), + ); + + if reward_amount < min_reward { + return; + } + + println!( + "Advance delegation record for pubkey: {:?} publisher: {:?} with reward: {:?}", + positions.to_string(), + publisher, + reward_amount, + ); + + process_transaction(rpc_client, &[ix], &[signer]).unwrap(); + }); +} + +pub fn claim_rewards( + rpc_client: &RpcClient, + signer: &dyn Signer, + min_staked: u64, + min_reward: u64, +) { + let mut data: Vec = rpc_client + .get_program_accounts_with_config( + &staking::ID, + RpcProgramAccountsConfig { + filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new( + 0, + MemcmpEncodedBytes::Bytes(PositionData::discriminator().to_vec()), + ))]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + data_slice: None, + commitment: None, + min_context_slot: None, + }, + with_context: None, + }, + ) + .unwrap() + .into_iter() + .map(|(pubkey, account)| DynamicPositionArrayAccount { + key: pubkey, + lamports: account.lamports, + data: account.data.clone(), + }) + .collect::>(); + + let current_epoch = get_current_epoch(rpc_client); + + let data: Vec = data + .iter_mut() + .filter_map(|positions| { + let acc = positions.to_dynamic_position_array(); + let valid = acc + .get_target_exposure(&Target::IntegrityPool, current_epoch) + .unwrap() + >= min_staked; + if valid { + Some(acc) + } else { + None + } + }) + .collect(); + + + data.iter().enumerate().for_each(|(i, positions)| { + println!("Claiming rewards for account {} / {}", i, data.len()); + advance_delegation_record(rpc_client, signer, positions.acc_info.key, min_reward); + }); +} + pub fn save_stake_accounts_snapshot(rpc_client: &RpcClient) { let data: Vec<(Pubkey, DynamicPositionArrayAccount, Pubkey, Pubkey, Pubkey)> = rpc_client .get_program_accounts_with_config( diff --git a/staking/cli/src/main.rs b/staking/cli/src/main.rs index a06c0b9f..27019b6c 100644 --- a/staking/cli/src/main.rs +++ b/staking/cli/src/main.rs @@ -8,6 +8,8 @@ use { Cli, }, instructions::{ + claim_rewards, + close_all_publisher_caps, close_publisher_caps, create_slash_event, fetch_publisher_caps_and_advance, @@ -97,5 +99,14 @@ fn main() { Action::SaveStakeAccountsSnapshot {} => { save_stake_accounts_snapshot(&rpc_client); } + Action::ClaimRewards { + min_staked, + min_reward, + } => { + claim_rewards(&rpc_client, keypair.as_ref(), min_staked, min_reward); + } + Action::CloseAllPublisherCaps {} => { + close_all_publisher_caps(&rpc_client, keypair.as_ref()); + } } } From 7b11b22a39dd49fba0af2b50d337d0faff034ccf Mon Sep 17 00:00:00 2001 From: keyvan Date: Mon, 30 Dec 2024 12:04:03 -0800 Subject: [PATCH 2/4] wip --- staking/cli/src/instructions.rs | 117 +++++++++++++++++++------------- 1 file changed, 70 insertions(+), 47 deletions(-) diff --git a/staking/cli/src/instructions.rs b/staking/cli/src/instructions.rs index 07c3dc3b..285013d3 100644 --- a/staking/cli/src/instructions.rs +++ b/staking/cli/src/instructions.rs @@ -12,10 +12,7 @@ use { TokenAccount, }, }, - base64::{ - prelude::BASE64_STANDARD, - Engine, - }, + base64::Engine, integration_tests::{ integrity_pool::pda::{ get_delegation_record_address, @@ -258,7 +255,7 @@ pub fn process_transaction( rpc_client: &RpcClient, instructions: &[Instruction], signers: &[&dyn Signer], -) -> Result { +) -> Result> { let mut transaction = Transaction::new_with_payer(instructions, Some(&signers[0].pubkey())); transaction.sign( signers, @@ -283,7 +280,7 @@ pub fn process_transaction( } Err(err) => { println!("transaction err: {err:?}"); - Err(err.get_transaction_error().unwrap()) + Err(err.get_transaction_error()) } } } @@ -905,12 +902,31 @@ pub fn close_all_publisher_caps(rpc_client: &RpcClient, signer: &dyn Signer) { .for_each(|(pubkey, _account)| close_publisher_caps(rpc_client, signer, pubkey)); } +pub struct FetchError {} + +pub fn fetch_delegation_record( + rpc_client: &RpcClient, + key: &Pubkey, +) -> Result { + let delegation_record = DelegationRecord::try_deserialize( + &mut (rpc_client + .get_account_data(key) + .map_err(|_| FetchError {})? + .as_slice()), + ) + .map_err(|_| FetchError {})?; + + Ok(delegation_record) +} + pub fn advance_delegation_record( rpc_client: &RpcClient, signer: &dyn Signer, - positions: &Pubkey, + positions: &DynamicPositionArray, min_reward: u64, + current_epoch: u64, ) { + let positions_pubkey = positions.acc_info.key; let pool_config = get_pool_config_address(); let PoolConfig { @@ -949,8 +965,24 @@ pub fn advance_delegation_record( let publisher_stake_account_custody = publisher_stake_account_positions.map(get_stake_account_custody_address); + let delegation_record_pubkey = + get_delegation_record_address(*publisher, *positions_pubkey); + + let delegation_record = fetch_delegation_record(rpc_client, &delegation_record_pubkey); + + match delegation_record { + Ok(delegation_record) => { + if delegation_record.last_epoch == current_epoch { + return; + } + } + Err(_) => { + return; + } + } + let accounts = integrity_pool::accounts::AdvanceDelegationRecord { - delegation_record: get_delegation_record_address(*publisher, *positions), + delegation_record: get_delegation_record_address(*publisher, *positions_pubkey), payer: signer.pubkey(), pool_config, pool_data: pool_data_address, @@ -958,8 +990,8 @@ pub fn advance_delegation_record( publisher: *publisher, publisher_stake_account_positions, publisher_stake_account_custody, - stake_account_positions: *positions, - stake_account_custody: get_stake_account_custody_address(*positions), + stake_account_positions: *positions_pubkey, + stake_account_custody: get_stake_account_custody_address(*positions_pubkey), system_program: system_program::ID, token_program: spl_token::ID, }; @@ -973,36 +1005,16 @@ pub fn advance_delegation_record( }; - let mut transaction = - Transaction::new_with_payer(&[ix.clone()], Some(&signer.pubkey())); - transaction.sign( - &[signer], - rpc_client - .get_latest_blockhash_with_commitment(CommitmentConfig::finalized()) - .unwrap() - .0, - ); - let res = rpc_client.simulate_transaction(&transaction).unwrap(); - let reward_amount: u64 = u64::from_le_bytes( - BASE64_STANDARD - .decode(res.value.return_data.unwrap().data.0) - .unwrap()[..8] - .try_into() - .unwrap(), - ); - - if reward_amount < min_reward { - return; - } - println!( - "Advance delegation record for pubkey: {:?} publisher: {:?} with reward: {:?}", - positions.to_string(), + "Advance delegation record for pubkey: {:?} publisher: {:?}", + positions_pubkey.to_string(), publisher, - reward_amount, ); - - process_transaction(rpc_client, &[ix], &[signer]).unwrap(); + for _ in 0..10 { + if process_transaction(rpc_client, &[ix.clone()], &[signer]).is_ok() { + break; + } + } }); } @@ -1040,27 +1052,38 @@ pub fn claim_rewards( let current_epoch = get_current_epoch(rpc_client); - let data: Vec = data + let mut data: Vec<(u64, DynamicPositionArray)> = data .iter_mut() .filter_map(|positions| { let acc = positions.to_dynamic_position_array(); - let valid = acc + let exposure = acc .get_target_exposure(&Target::IntegrityPool, current_epoch) - .unwrap() - >= min_staked; - if valid { - Some(acc) + .unwrap(); + if exposure >= min_staked { + Some((exposure, acc)) } else { None } }) .collect(); + data.sort_by_key(|(exposure, _)| *exposure); + data.reverse(); + - data.iter().enumerate().for_each(|(i, positions)| { - println!("Claiming rewards for account {} / {}", i, data.len()); - advance_delegation_record(rpc_client, signer, positions.acc_info.key, min_reward); - }); + data.iter() + .enumerate() + // .skip(120) + .for_each(|(i, (exposure, positions))| { + println!( + "Claiming rewards for account ({} / {}). exposure: {}. pubkey: {:?}", + i, + data.len(), + exposure, + positions.acc_info.key + ); + advance_delegation_record(rpc_client, signer, positions, min_reward, current_epoch); + }); } pub fn save_stake_accounts_snapshot(rpc_client: &RpcClient) { From 2c42865052dac08b62be0529a3cbcf16b279233c Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 31 Dec 2024 08:35:37 -0800 Subject: [PATCH 3/4] remove claim --- staking/cli/src/cli.rs | 6 -- staking/cli/src/instructions.rs | 186 -------------------------------- staking/cli/src/main.rs | 7 -- 3 files changed, 199 deletions(-) diff --git a/staking/cli/src/cli.rs b/staking/cli/src/cli.rs index 4508d013..48600fc0 100644 --- a/staking/cli/src/cli.rs +++ b/staking/cli/src/cli.rs @@ -110,12 +110,6 @@ pub enum Action { publisher_caps: Pubkey, }, SaveStakeAccountsSnapshot {}, - ClaimRewards { - #[clap(long, help = "Minimum staked tokens")] - min_staked: u64, - #[clap(long, help = "Minimum reward tokens per publisher")] - min_reward: u64, - }, CloseAllPublisherCaps {}, } diff --git a/staking/cli/src/instructions.rs b/staking/cli/src/instructions.rs index 285013d3..d1560c6f 100644 --- a/staking/cli/src/instructions.rs +++ b/staking/cli/src/instructions.rs @@ -86,11 +86,9 @@ use { global_config::GlobalConfig, max_voter_weight_record::MAX_VOTER_WEIGHT, positions::{ - DynamicPositionArray, DynamicPositionArrayAccount, PositionData, PositionState, - Target, }, stake_account::StakeAccountMetadataV2, }, @@ -902,190 +900,6 @@ pub fn close_all_publisher_caps(rpc_client: &RpcClient, signer: &dyn Signer) { .for_each(|(pubkey, _account)| close_publisher_caps(rpc_client, signer, pubkey)); } -pub struct FetchError {} - -pub fn fetch_delegation_record( - rpc_client: &RpcClient, - key: &Pubkey, -) -> Result { - let delegation_record = DelegationRecord::try_deserialize( - &mut (rpc_client - .get_account_data(key) - .map_err(|_| FetchError {})? - .as_slice()), - ) - .map_err(|_| FetchError {})?; - - Ok(delegation_record) -} - -pub fn advance_delegation_record( - rpc_client: &RpcClient, - signer: &dyn Signer, - positions: &DynamicPositionArray, - min_reward: u64, - current_epoch: u64, -) { - let positions_pubkey = positions.acc_info.key; - let pool_config = get_pool_config_address(); - - let PoolConfig { - pool_data: pool_data_address, - pyth_token_mint, - .. - } = PoolConfig::try_deserialize( - &mut rpc_client - .get_account_data(&pool_config) - .unwrap() - .as_slice(), - ) - .unwrap(); - - let pool_data = PoolData::try_deserialize( - &mut rpc_client.get_account_data(&pool_data_address).unwrap()[..8 + size_of::()] - .as_ref(), - ) - .unwrap(); - - pool_data - .publishers - .iter() - .enumerate() - .for_each(|(publisher_index, publisher)| { - if *publisher == Pubkey::default() { - return; - } - let publisher_stake_account_positions = - if pool_data.publisher_stake_accounts[publisher_index] == Pubkey::default() { - None - } else { - Some(pool_data.publisher_stake_accounts[publisher_index]) - }; - - let publisher_stake_account_custody = - publisher_stake_account_positions.map(get_stake_account_custody_address); - - let delegation_record_pubkey = - get_delegation_record_address(*publisher, *positions_pubkey); - - let delegation_record = fetch_delegation_record(rpc_client, &delegation_record_pubkey); - - match delegation_record { - Ok(delegation_record) => { - if delegation_record.last_epoch == current_epoch { - return; - } - } - Err(_) => { - return; - } - } - - let accounts = integrity_pool::accounts::AdvanceDelegationRecord { - delegation_record: get_delegation_record_address(*publisher, *positions_pubkey), - payer: signer.pubkey(), - pool_config, - pool_data: pool_data_address, - pool_reward_custody: get_pool_reward_custody_address(pyth_token_mint), - publisher: *publisher, - publisher_stake_account_positions, - publisher_stake_account_custody, - stake_account_positions: *positions_pubkey, - stake_account_custody: get_stake_account_custody_address(*positions_pubkey), - system_program: system_program::ID, - token_program: spl_token::ID, - }; - - let data = integrity_pool::instruction::AdvanceDelegationRecord {}; - - let ix = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: data.data(), - }; - - - println!( - "Advance delegation record for pubkey: {:?} publisher: {:?}", - positions_pubkey.to_string(), - publisher, - ); - for _ in 0..10 { - if process_transaction(rpc_client, &[ix.clone()], &[signer]).is_ok() { - break; - } - } - }); -} - -pub fn claim_rewards( - rpc_client: &RpcClient, - signer: &dyn Signer, - min_staked: u64, - min_reward: u64, -) { - let mut data: Vec = rpc_client - .get_program_accounts_with_config( - &staking::ID, - RpcProgramAccountsConfig { - filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new( - 0, - MemcmpEncodedBytes::Bytes(PositionData::discriminator().to_vec()), - ))]), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64Zstd), - data_slice: None, - commitment: None, - min_context_slot: None, - }, - with_context: None, - }, - ) - .unwrap() - .into_iter() - .map(|(pubkey, account)| DynamicPositionArrayAccount { - key: pubkey, - lamports: account.lamports, - data: account.data.clone(), - }) - .collect::>(); - - let current_epoch = get_current_epoch(rpc_client); - - let mut data: Vec<(u64, DynamicPositionArray)> = data - .iter_mut() - .filter_map(|positions| { - let acc = positions.to_dynamic_position_array(); - let exposure = acc - .get_target_exposure(&Target::IntegrityPool, current_epoch) - .unwrap(); - if exposure >= min_staked { - Some((exposure, acc)) - } else { - None - } - }) - .collect(); - - data.sort_by_key(|(exposure, _)| *exposure); - data.reverse(); - - - data.iter() - .enumerate() - // .skip(120) - .for_each(|(i, (exposure, positions))| { - println!( - "Claiming rewards for account ({} / {}). exposure: {}. pubkey: {:?}", - i, - data.len(), - exposure, - positions.acc_info.key - ); - advance_delegation_record(rpc_client, signer, positions, min_reward, current_epoch); - }); -} - pub fn save_stake_accounts_snapshot(rpc_client: &RpcClient) { let data: Vec<(Pubkey, DynamicPositionArrayAccount, Pubkey, Pubkey, Pubkey)> = rpc_client .get_program_accounts_with_config( diff --git a/staking/cli/src/main.rs b/staking/cli/src/main.rs index 27019b6c..df3a6ba0 100644 --- a/staking/cli/src/main.rs +++ b/staking/cli/src/main.rs @@ -8,7 +8,6 @@ use { Cli, }, instructions::{ - claim_rewards, close_all_publisher_caps, close_publisher_caps, create_slash_event, @@ -99,12 +98,6 @@ fn main() { Action::SaveStakeAccountsSnapshot {} => { save_stake_accounts_snapshot(&rpc_client); } - Action::ClaimRewards { - min_staked, - min_reward, - } => { - claim_rewards(&rpc_client, keypair.as_ref(), min_staked, min_reward); - } Action::CloseAllPublisherCaps {} => { close_all_publisher_caps(&rpc_client, keypair.as_ref()); } From 8460993d7a5018e488d70ddc3f85d8ef6f781081 Mon Sep 17 00:00:00 2001 From: keyvan Date: Tue, 31 Dec 2024 10:09:14 -0800 Subject: [PATCH 4/4] remove unused vva data --- staking/cli/src/instructions.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/staking/cli/src/instructions.rs b/staking/cli/src/instructions.rs index d1560c6f..e2a4c822 100644 --- a/staking/cli/src/instructions.rs +++ b/staking/cli/src/instructions.rs @@ -105,12 +105,9 @@ use { }, mem::size_of, }, - wormhole_core_bridge_solana::{ - sdk::{ - WriteEncodedVaaArgs, - VAA_START, - }, - state::EncodedVaa, + wormhole_core_bridge_solana::sdk::{ + WriteEncodedVaaArgs, + VAA_START, }, wormhole_sdk::vaa::{ Body, @@ -874,10 +871,6 @@ pub fn update_y(rpc_client: &RpcClient, signer: &dyn Signer, y: u64) { } pub fn close_all_publisher_caps(rpc_client: &RpcClient, signer: &dyn Signer) { - let mut data = EncodedVaa::DISCRIMINATOR.to_vec(); - data.extend_from_slice(&[1]); - data.extend_from_slice(&signer.pubkey().to_bytes()); - rpc_client .get_program_accounts_with_config( &publisher_caps::ID,