diff --git a/staking/Cargo.lock b/staking/Cargo.lock index 97cef11a..89782364 100644 --- a/staking/Cargo.lock +++ b/staking/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -1643,9 +1643,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1658,9 +1658,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1668,15 +1668,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1685,15 +1685,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -1702,21 +1702,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -5281,6 +5281,7 @@ dependencies = [ "byteorder", "chrono", "clap 3.2.25", + "futures", "integration-tests", "integrity-pool", "publisher-caps", @@ -5294,6 +5295,8 @@ dependencies = [ "solana-client", "solana-remote-wallet", "solana-sdk", + "tokio", + "tokio-stream", "uriparse", "wormhole-core-bridge-solana", "wormhole-solana", @@ -5527,9 +5530,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -5576,9 +5579,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -6216,7 +6219,7 @@ dependencies = [ [[package]] name = "wormhole-core" version = "0.1.0" -source = "git+https://github.com/guibescos/wormhole?branch=reisen/sdk-solana#b0da20525fe68408ed2c8b331eb5f63101381936" +source = "git+https://github.com/guibescos/wormhole?branch=reisen%2Fsdk-solana#b0da20525fe68408ed2c8b331eb5f63101381936" dependencies = [ "borsh 0.9.3", "bstr 0.2.17", @@ -6268,7 +6271,7 @@ checksum = "045a3cc929189ffc0df110e4dc04421ed3226543f47a302088e6175b97b7d75f" [[package]] name = "wormhole-solana" version = "0.1.0" -source = "git+https://github.com/guibescos/wormhole?branch=reisen/sdk-solana#b0da20525fe68408ed2c8b331eb5f63101381936" +source = "git+https://github.com/guibescos/wormhole?branch=reisen%2Fsdk-solana#b0da20525fe68408ed2c8b331eb5f63101381936" dependencies = [ "borsh 0.9.3", "bstr 0.2.17", diff --git a/staking/cli/Cargo.toml b/staking/cli/Cargo.toml index 9a3e3cf8..6ef93f21 100644 --- a/staking/cli/Cargo.toml +++ b/staking/cli/Cargo.toml @@ -27,3 +27,6 @@ uriparse = "0.6.4" solana-remote-wallet = "1.18.16" solana-account-decoder = "1.18.16" chrono = "0.4.38" +futures = "0.3.31" +tokio = "1.42.0" +tokio-stream = "0.1.17" diff --git a/staking/cli/src/cli.rs b/staking/cli/src/cli.rs index 174572bd..a5d811ab 100644 --- a/staking/cli/src/cli.rs +++ b/staking/cli/src/cli.rs @@ -48,68 +48,12 @@ fn get_keypair_from_file(path: &str) -> Result { #[allow(clippy::large_enum_variant)] #[derive(Subcommand, Debug)] pub enum Action { - #[clap(about = "Initialize pool")] - InitializePool { - #[clap( - long, - help = "Keypair pool data account", - parse(try_from_str = get_keypair_from_file) - )] - pool_data_keypair: Keypair, - #[clap(long, help = "Y parameter")] - y: u64, - #[clap(long, help = "Reward program authority parameter")] - reward_program_authority: Pubkey, - #[clap(long, help = "Slash custody parameter")] - slash_custody: Pubkey, + ClaimRewards { + #[clap(long, help = "Minimum staked tokens")] + min_staked: u64, + #[clap(long, help = "Minimum reward tokens per publisher")] + min_reward: u64, }, - Advance { - #[clap( - long, - help = "Url of hermes to fetch publisher caps", - default_value = "https://hermes-beta.pyth.network/" - )] - hermes_url: String, - - #[clap(long, default_value = "3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5")] - wormhole: Pubkey, - }, - InitializePoolRewardCustody {}, - UpdateDelegationFee { - #[clap(long, help = "New fee")] - delegation_fee: u64, - }, - SetPublisherStakeAccount { - #[clap(long, help = "Publisher")] - publisher: Pubkey, - #[clap(long, help = "Stake account positions")] - stake_account_positions: Pubkey, - }, - CreateSlashEvent { - #[clap(long, help = "Publisher")] - publisher: Pubkey, - #[clap(long, help = "Amount")] - slash_ratio: u64, - }, - UpdateRewardProgramAuthority { - #[clap(long, help = "New reward program authority")] - new_reward_program_authority: Pubkey, - }, - Slash { - #[clap(long, help = "Publisher")] - publisher: Pubkey, - #[clap(long, help = "Stake account positions")] - stake_account_positions: Pubkey, - }, - UpdateY { - #[clap(long, help = "New Y")] - y: u64, - }, - ClosePublisherCaps { - #[clap(long, help = "Publisher caps")] - publisher_caps: Pubkey, - }, - SaveStakeAccountsSnapshot {}, } pub enum SignerSource { diff --git a/staking/cli/src/instructions.rs b/staking/cli/src/instructions.rs index e6908dfe..116d83b0 100644 --- a/staking/cli/src/instructions.rs +++ b/staking/cli/src/instructions.rs @@ -1,5 +1,6 @@ use { anchor_lang::{ + pubkey, AccountDeserialize, Discriminator, InstructionData, @@ -13,6 +14,10 @@ use { }, }, base64::Engine, + futures::{ + future::join_all, + StreamExt, + }, integration_tests::{ integrity_pool::pda::{ get_delegation_record_address, @@ -48,7 +53,8 @@ use { serde_wormhole::RawMessage, solana_account_decoder::UiAccountEncoding, solana_client::{ - rpc_client::RpcClient, + nonblocking::rpc_client::RpcClient, + // rpc_client::RpcClient, rpc_config::{ RpcAccountInfoConfig, RpcProgramAccountsConfig, @@ -86,9 +92,12 @@ use { global_config::GlobalConfig, max_voter_weight_record::MAX_VOTER_WEIGHT, positions::{ + DynamicPositionArray, DynamicPositionArrayAccount, PositionData, PositionState, + Target, + TargetWithParameters, }, stake_account::StakeAccountMetadataV2, }, @@ -105,9 +114,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, @@ -119,759 +131,213 @@ use { }, }; -pub fn init_publisher_caps(rpc_client: &RpcClient, payer: &dyn Signer) -> Pubkey { - let publisher_caps = Keypair::new(); - let create_account_ix = create_account( - &payer.pubkey(), - &publisher_caps.pubkey(), - rpc_client - .get_minimum_balance_for_rent_exemption(PublisherCaps::LEN) - .unwrap(), - PublisherCaps::LEN.try_into().unwrap(), - &publisher_caps::ID, - ); - - let accounts = publisher_caps::accounts::InitPublisherCaps { - signer: payer.pubkey(), - publisher_caps: publisher_caps.pubkey(), - }; - - let instruction_data = publisher_caps::instruction::InitPublisherCaps {}; - - let instruction = Instruction { - program_id: publisher_caps::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction( - rpc_client, - &[ - create_account_ix, - instruction, - ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), - ], - &[payer, &publisher_caps], - ) - .unwrap(); - publisher_caps.pubkey() -} -pub fn write_publisher_caps( - rpc_client: &RpcClient, - payer: &dyn Signer, - publisher_caps: Pubkey, - index: usize, - chunk: &[u8], -) { - let accounts = publisher_caps::accounts::WritePublisherCaps { - write_authority: payer.pubkey(), - publisher_caps, - }; - - let instruction_data = publisher_caps::instruction::WritePublisherCaps { - index: index.try_into().unwrap(), - data: chunk.to_vec(), - }; - - let instruction = Instruction { - program_id: publisher_caps::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction(rpc_client, &[instruction], &[payer]).unwrap(); -} - -pub fn close_publisher_caps(rpc_client: &RpcClient, payer: &dyn Signer, publisher_caps: Pubkey) { - let accounts = publisher_caps::accounts::ClosePublisherCaps { - write_authority: payer.pubkey(), - publisher_caps, - }; - - let instruction_data = publisher_caps::instruction::ClosePublisherCaps {}; - - let instruction = Instruction { - program_id: publisher_caps::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction(rpc_client, &[instruction], &[payer]).unwrap(); -} - - -pub fn verify_publisher_caps( - rpc_client: &RpcClient, - payer: &dyn Signer, - publisher_caps: Pubkey, - encoded_vaa: Pubkey, - merkle_proofs: Vec, -) { - let accounts = publisher_caps::accounts::VerifyPublisherCaps { - signer: payer.pubkey(), - publisher_caps, - encoded_vaa, - }; - - let instruction_data = publisher_caps::instruction::VerifyPublisherCaps { - proof: merkle_proofs[0].proof.to_vec(), - }; - - let instruction = Instruction { - program_id: publisher_caps::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction( - rpc_client, - &[ - instruction, - ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), - ], - &[payer], - ) - .unwrap(); -} - -pub fn deserialize_accumulator_update_data( - accumulator_message: Vec, -) -> (Vec, Vec) { - let accumulator_update_data = - AccumulatorUpdateData::try_from_slice(accumulator_message.as_slice()).unwrap(); - - match accumulator_update_data.proof { - Proof::WormholeMerkle { vaa, updates } => return (vaa.as_ref().to_vec(), updates), - } -} - -pub fn process_transaction( +pub async fn process_transaction( rpc_client: &RpcClient, instructions: &[Instruction], signers: &[&dyn Signer], -) -> Result { - let mut transaction = Transaction::new_with_payer(instructions, Some(&signers[0].pubkey())); +) -> Result<(), Option> { + let mut instructions = instructions.to_vec(); + instructions.push(ComputeBudgetInstruction::set_compute_unit_price(10000)); + let mut transaction = Transaction::new_with_payer(&instructions, Some(&signers[0].pubkey())); transaction.sign( signers, rpc_client .get_latest_blockhash_with_commitment(CommitmentConfig::finalized()) + .await .unwrap() .0, ); - let transaction_signature_res = rpc_client - .send_and_confirm_transaction_with_spinner_and_config( - &transaction, - CommitmentConfig::confirmed(), - RpcSendTransactionConfig { - skip_preflight: true, - ..Default::default() - }, - ); - match transaction_signature_res { - Ok(signature) => { - println!("Transaction successful : {signature:?}"); - Ok(signature) - } - Err(err) => { - println!("transaction err: {err:?}"); - Err(err.get_transaction_error().unwrap()) - } - } -} - -pub fn process_write_encoded_vaa( - rpc_client: &RpcClient, - vaa: &[u8], - wormhole: Pubkey, - payer: &dyn Signer, -) -> Pubkey { - let encoded_vaa_keypair = Keypair::new(); - let encoded_vaa_size: usize = vaa.len() + VAA_START; - - let create_encoded_vaa = system_instruction::create_account( - &payer.pubkey(), - &encoded_vaa_keypair.pubkey(), - Rent::default().minimum_balance(encoded_vaa_size), - encoded_vaa_size as u64, - &wormhole, - ); - let init_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::InitEncodedVaa { - write_authority: payer.pubkey(), - encoded_vaa: encoded_vaa_keypair.pubkey(), - } - .to_account_metas(None); - - let init_encoded_vaa_instruction = Instruction { - program_id: wormhole, - accounts: init_encoded_vaa_accounts, - data: wormhole_core_bridge_solana::instruction::InitEncodedVaa.data(), - }; - - process_transaction( - rpc_client, - &[create_encoded_vaa, init_encoded_vaa_instruction], - &[payer, &encoded_vaa_keypair], - ) - .unwrap(); - - for i in (0..vaa.len()).step_by(1000) { - let chunk = &vaa[i..min(i + 1000, vaa.len())]; - - write_encoded_vaa( - rpc_client, - payer, - &encoded_vaa_keypair.pubkey(), - &wormhole, - i, - chunk, - ); - } - - let (header, _): (Header, Body<&RawMessage>) = serde_wormhole::from_slice(vaa).unwrap(); - let guardian_set = GuardianSet::key(&wormhole, header.guardian_set_index); - - let request_compute_units_instruction: Instruction = - ComputeBudgetInstruction::set_compute_unit_limit(600_000); - - let verify_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::VerifyEncodedVaaV1 { - guardian_set, - write_authority: payer.pubkey(), - draft_vaa: encoded_vaa_keypair.pubkey(), - } - .to_account_metas(None); - - let verify_encoded_vaa_instruction = Instruction { - program_id: wormhole, - accounts: verify_encoded_vaa_accounts, - data: wormhole_core_bridge_solana::instruction::VerifyEncodedVaaV1 {}.data(), - }; - - process_transaction( - rpc_client, - &[ - verify_encoded_vaa_instruction, - request_compute_units_instruction, - ], - &[payer], - ) - .unwrap(); - - - encoded_vaa_keypair.pubkey() -} + for _ in 0..10 { + rpc_client + .send_transaction_with_config( + &transaction, + RpcSendTransactionConfig { + skip_preflight: true, + max_retries: Some(0), + ..Default::default() + }, + ) + .await + .unwrap(); -pub fn write_encoded_vaa( - rpc_client: &RpcClient, - payer: &dyn Signer, - encoded_vaa: &Pubkey, - wormhole: &Pubkey, - index: usize, - chunk: &[u8], -) { - let write_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::WriteEncodedVaa { - write_authority: payer.pubkey(), - draft_vaa: *encoded_vaa, + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; } - .to_account_metas(None); - - let write_encoded_vaa_accounts_instruction = Instruction { - program_id: *wormhole, - accounts: write_encoded_vaa_accounts.clone(), - data: wormhole_core_bridge_solana::instruction::WriteEncodedVaa { - args: WriteEncodedVaaArgs { - index: index as u32, - data: chunk.to_vec(), - }, - } - .data(), - }; - process_transaction( - rpc_client, - &[write_encoded_vaa_accounts_instruction], - &[payer], - ) - .unwrap(); + Ok(()) } -pub fn close_encoded_vaa( - rpc_client: &RpcClient, - payer: &dyn Signer, - encoded_vaa: Pubkey, - wormhole: &Pubkey, -) { - let close_encoded_vaa_accounts = wormhole_core_bridge_solana::accounts::CloseEncodedVaa { - write_authority: payer.pubkey(), - encoded_vaa, - } - .to_account_metas(None); - - let close_encoded_vaa_instruction = Instruction { - program_id: *wormhole, - accounts: close_encoded_vaa_accounts, - data: wormhole_core_bridge_solana::instruction::CloseEncodedVaa {}.data(), - }; - - process_transaction(rpc_client, &[close_encoded_vaa_instruction], &[payer]).unwrap(); +pub async fn get_current_time(rpc_client: &RpcClient) -> i64 { + let slot = rpc_client.get_slot().await.unwrap(); + rpc_client.get_block_time(slot).await.unwrap() } -pub fn initialize_reward_custody(rpc_client: &RpcClient, payer: &dyn Signer) { - let pool_config = get_pool_config_address(); - - let PoolConfig { - pyth_token_mint, .. - } = PoolConfig::try_deserialize( - &mut rpc_client - .get_account_data(&pool_config) - .unwrap() - .as_slice(), - ) - .unwrap(); - - let create_ata_ix = spl_associated_token_account::instruction::create_associated_token_account( - &payer.pubkey(), - &pool_config, - &pyth_token_mint, - &spl_token::ID, - ); - - process_transaction(rpc_client, &[create_ata_ix], &[payer]).unwrap(); -} - -pub fn advance(rpc_client: &RpcClient, payer: &dyn Signer, publisher_caps: Pubkey) { - let pool_config = get_pool_config_address(); - - let PoolConfig { - pool_data, - pyth_token_mint, - .. - } = PoolConfig::try_deserialize( - &mut rpc_client - .get_account_data(&pool_config) - .unwrap() - .as_slice(), - ) - .unwrap(); - - let pool_reward_custody = get_pool_reward_custody_address(pyth_token_mint); - - let accounts = integrity_pool::accounts::Advance { - signer: payer.pubkey(), - pool_config, - publisher_caps, - pool_data, - pool_reward_custody, - }; - - let instruction_data = integrity_pool::instruction::Advance {}; - - let instruction = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction( - rpc_client, - &[ - instruction, - ComputeBudgetInstruction::set_compute_unit_limit(1_400_000), - ], - &[payer], - ) - .unwrap(); -} - -pub fn initialize_pool( - rpc_client: &RpcClient, - payer: &dyn Signer, - pool_data_keypair: &Keypair, - reward_program_authority: Pubkey, - y: u64, - slash_custody: Pubkey, -) { - let pool_data_space: u64 = PoolData::LEN.try_into().unwrap(); - let config_address = get_config_address(); - - let rent = rpc_client - .get_minimum_balance_for_rent_exemption(pool_data_space.try_into().unwrap()) - .unwrap(); - - let create_pool_data_acc_ix = create_account( - &payer.pubkey(), - &pool_data_keypair.pubkey(), - rent, - pool_data_space, - &integrity_pool::ID, - ); - - let pool_config_pubkey = get_pool_config_address(); - - let initialize_pool_data = integrity_pool::instruction::InitializePool { - reward_program_authority, - y, - }; - - let initialize_pool_accs = integrity_pool::accounts::InitializePool { - payer: payer.pubkey(), - pool_data: pool_data_keypair.pubkey(), - pool_config: pool_config_pubkey, - config_account: config_address, - slash_custody, - system_program: system_program::ID, - }; - - let initialize_pool_ix = Instruction::new_with_bytes( - integrity_pool::ID, - &initialize_pool_data.data(), - initialize_pool_accs.to_account_metas(None), - ); - - - process_transaction( - rpc_client, - &[create_pool_data_acc_ix, initialize_pool_ix], - &[payer, pool_data_keypair], - ) - .unwrap(); -} - -pub fn get_current_time(rpc_client: &RpcClient) -> i64 { - let slot = rpc_client.get_slot().unwrap(); - rpc_client.get_block_time(slot).unwrap() -} - -pub fn get_current_epoch(rpc_client: &RpcClient) -> u64 { - let slot = rpc_client.get_slot().unwrap(); - let blocktime = rpc_client.get_block_time(slot).unwrap(); +pub async fn get_current_epoch(rpc_client: &RpcClient) -> u64 { + let slot = rpc_client.get_slot().await.unwrap(); + let blocktime = rpc_client.get_block_time(slot).await.unwrap(); blocktime as u64 / EPOCH_DURATION } -pub fn fetch_publisher_caps_and_advance( - rpc_client: &RpcClient, - payer: &dyn Signer, - wormhole: Pubkey, - hermes_url: String, -) { - let pool_config = get_pool_config_address(); - - let PoolConfig { - pool_data: pool_data_address, - .. - } = 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(); - - if pool_data.last_updated_epoch == get_current_epoch(rpc_client) { - println!("Pool data is already updated"); - return; - } - - let client = Client::new(); - let response = client - .get(format!( - "{}v2/updates/publisher_stake_caps/latest?encoding=base64", - hermes_url - )) - .send() - .unwrap(); - - let json: serde_json::Value = response.json().unwrap(); - let encoded_message = json["binary"]["data"][0].as_str().unwrap(); - - //decode tmp from base64 - let message = base64::prelude::BASE64_STANDARD - .decode(encoded_message) - .unwrap(); - - let (vaa, merkle_proofs) = deserialize_accumulator_update_data(message); - - - let encoded_vaa = process_write_encoded_vaa(rpc_client, vaa.as_slice(), wormhole, payer); - - - let publisher_caps = init_publisher_caps(rpc_client, payer); - - - let publisher_caps_message_bytes = - Vec::::from(merkle_proofs.first().unwrap().message.clone()); - - - for i in (0..publisher_caps_message_bytes.len()).step_by(1000) { - let chunk = - &publisher_caps_message_bytes[i..min(i + 1000, publisher_caps_message_bytes.len())]; - - write_publisher_caps(rpc_client, payer, publisher_caps, i, chunk); - } - - verify_publisher_caps( - rpc_client, - payer, - publisher_caps, - encoded_vaa, - merkle_proofs, - ); - - - println!( - "Initialized publisher caps with pubkey : {:?}", - publisher_caps - ); - - advance(rpc_client, payer, publisher_caps); - close_publisher_caps(rpc_client, payer, publisher_caps); - close_encoded_vaa(rpc_client, payer, encoded_vaa, &wormhole); -} - -pub fn update_delegation_fee(rpc_client: &RpcClient, payer: &dyn Signer, delegation_fee: u64) { - let pool_config = get_pool_config_address(); +pub struct FetchError {} - let PoolConfig { pool_data, .. } = PoolConfig::try_deserialize( - &mut rpc_client - .get_account_data(&pool_config) - .unwrap() - .as_slice(), - ) - .unwrap(); - - let accounts = integrity_pool::accounts::UpdateDelegationFee { - reward_program_authority: payer.pubkey(), - pool_config, - pool_data, - system_program: system_program::ID, - }; - - let instruction_data = integrity_pool::instruction::UpdateDelegationFee { delegation_fee }; - - let instruction = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction(rpc_client, &[instruction], &[payer]).unwrap(); -} - -pub fn set_publisher_stake_account( +pub async fn fetch_delegation_record( rpc_client: &RpcClient, - signer: &dyn Signer, - publisher: &Pubkey, - stake_account_positions: &Pubkey, -) { - let pool_config = get_pool_config_address(); - - let PoolConfig { pool_data, .. } = PoolConfig::try_deserialize( - &mut rpc_client - .get_account_data(&pool_config) - .unwrap() - .as_slice(), + key: Pubkey, +) -> Result { + let delegation_record = DelegationRecord::try_deserialize( + &mut (rpc_client + .get_account_data(&key) + .await + .map_err(|_| FetchError {})? + .as_slice()), ) - .unwrap(); - - let accounts = integrity_pool::accounts::SetPublisherStakeAccount { - signer: signer.pubkey(), - publisher: *publisher, - current_stake_account_positions_option: None, - new_stake_account_positions_option: Some(*stake_account_positions), - pool_config, - pool_data, - }; - - let instruction_data = integrity_pool::instruction::SetPublisherStakeAccount {}; + .map_err(|_| FetchError {})?; - let instruction = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction(rpc_client, &[instruction], &[signer]).unwrap(); + Ok(delegation_record) } -pub fn create_slash_event( +pub async fn advance_delegation_record<'a>( rpc_client: &RpcClient, signer: &dyn Signer, - publisher: &Pubkey, - slash_ratio: u64, -) { - let pool_config = get_pool_config_address(); + positions: &DynamicPositionArray<'a>, + min_reward: u64, + current_epoch: u64, + pool_data: &PoolData, + pool_data_address: &Pubkey, + pyth_token_mint: &Pubkey, + pool_config: &Pubkey, + index: usize, +) -> bool { + let positions_pubkey = positions.acc_info.key; - let PoolConfig { - pool_data: pool_data_address, - slash_custody, - .. - } = PoolConfig::try_deserialize( - &mut rpc_client - .get_account_data(&pool_config) - .unwrap() - .as_slice(), - ) - .unwrap(); + // First collect all potential instruction data + let potential_instructions: Vec<_> = pool_data + .publishers + .iter() + .enumerate() + .filter_map(|(publisher_index, publisher)| { + if *publisher == Pubkey::default() { + return None; + } - let pool_data = PoolData::try_deserialize( - &mut rpc_client.get_account_data(&pool_data_address).unwrap()[..8 + size_of::()] - .as_ref(), - ) - .unwrap(); + let publisher_exposure = { + let mut publisher_exposure = 0; + for i in 0..positions.get_position_capacity() { + if let Some(position) = positions.read_position(i).unwrap() { + if (position.target_with_parameters + == TargetWithParameters::IntegrityPool { + publisher: *publisher, + }) + { + publisher_exposure += position.amount; + } + } + } + publisher_exposure + }; - let publisher_index = pool_data.get_publisher_index(publisher).unwrap(); - let index = pool_data.num_slash_events[publisher_index]; + if publisher_exposure == 0 { + return None; + } - let accounts = integrity_pool::accounts::CreateSlashEvent { - payer: signer.pubkey(), - reward_program_authority: signer.pubkey(), - publisher: *publisher, - slash_custody, - pool_config, - pool_data: pool_data_address, - slash_event: get_slash_event_address(index, *publisher), - system_program: system_program::ID, - }; + 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 instruction_data = integrity_pool::instruction::CreateSlashEvent { index, slash_ratio }; + let publisher_stake_account_custody = + publisher_stake_account_positions.map(get_stake_account_custody_address); - let instruction = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; + Some(( + *publisher, + publisher_stake_account_positions, + publisher_stake_account_custody, + )) + }) + .collect(); - process_transaction(rpc_client, &[instruction], &[signer]).unwrap(); -} + println!( + "Position {:?} with index {} has {} potential instructions", + positions_pubkey, + index, + potential_instructions.len() + ); -pub fn update_reward_program_authority( - rpc_client: &RpcClient, - signer: &dyn Signer, - new_reward_program_authority: &Pubkey, -) { - let pool_config = get_pool_config_address(); + // Fetch all delegation records concurrently + let delegation_records = join_all(potential_instructions.iter().map(|(publisher, _, _)| { + let delegation_record_pubkey = get_delegation_record_address(*publisher, *positions_pubkey); + fetch_delegation_record(rpc_client, delegation_record_pubkey) + })) + .await; - let accounts = integrity_pool::accounts::UpdateRewardProgramAuthority { - reward_program_authority: signer.pubkey(), - pool_config, - system_program: system_program::ID, - }; + // Process results and create instructions + let mut instructions = Vec::new(); + for ( + (publisher, publisher_stake_account_positions, publisher_stake_account_custody), + delegation_record, + ) in potential_instructions.into_iter().zip(delegation_records) + { + // Skip if we couldn't fetch the record or if it's already processed for current epoch + match delegation_record { + Ok(delegation_record) => { + if delegation_record.last_epoch == current_epoch { + continue; + } + } + Err(_) => {} + } - let instruction_data = integrity_pool::instruction::UpdateRewardProgramAuthority { - reward_program_authority: *new_reward_program_authority, - }; + let accounts = integrity_pool::accounts::AdvanceDelegationRecord { + delegation_record: get_delegation_record_address(publisher, *positions_pubkey), + payer: signer.pubkey(), + pool_config: *pool_config, + pool_data: *pool_data_address, + pool_reward_custody: get_pool_reward_custody_address(*pyth_token_mint), + 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 {}; + + instructions.push(Instruction { + program_id: integrity_pool::ID, + accounts: accounts.to_account_metas(None), + data: data.data(), + }); + } - let instruction = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; + // Process instructions in chunks of 5 + if !instructions.is_empty() { + println!( + "Advancing delegation record for pubkey: {:?}, number of instructions: {}", + positions_pubkey.to_string(), + instructions.len(), + ); - process_transaction(rpc_client, &[instruction], &[signer]).unwrap(); + for chunk in instructions.chunks(5) { + process_transaction(rpc_client, chunk, &[signer]) + .await + .unwrap(); + } + return true; // Instructions were processed + } + false // No instructions were processed } -pub fn slash( +pub async fn claim_rewards( rpc_client: &RpcClient, signer: &dyn Signer, - publisher: &Pubkey, - stake_account_positions: &Pubkey, + min_staked: u64, + min_reward: u64, ) { - let pool_config = get_pool_config_address(); - let PoolConfig { - pool_data, - slash_custody, - .. - } = PoolConfig::try_deserialize( - &mut rpc_client - .get_account_data(&pool_config) - .unwrap() - .as_slice(), - ) - .unwrap(); - - let delegation_record = get_delegation_record_address(*publisher, *stake_account_positions); - let DelegationRecord { - next_slash_event_index, - .. - } = { - let delegation_record_account_data = rpc_client.get_account_data(&delegation_record); - if let Ok(data) = delegation_record_account_data { - DelegationRecord::try_deserialize(&mut data.as_slice()).unwrap() - } else { - DelegationRecord { - last_epoch: 0, - next_slash_event_index: 0, - } - } - }; - - - let stake_account_metadata = get_stake_account_metadata_address(*stake_account_positions); - let stake_account_custody = get_stake_account_custody_address(*stake_account_positions); - let custody_authority = get_stake_account_custody_authority_address(*stake_account_positions); - let config_account = get_config_address(); - let governance_target_account = get_target_address(); - - - let accounts = integrity_pool::accounts::Slash { - signer: signer.pubkey(), - pool_data, - pool_config, - slash_event: get_slash_event_address(next_slash_event_index, *publisher), - delegation_record, - publisher: *publisher, - stake_account_positions: *stake_account_positions, - stake_account_metadata, - stake_account_custody, - config_account, - governance_target_account, - slash_custody, - custody_authority, - staking_program: staking::ID, - token_program: spl_token::ID, - }; - - let instruction_data = integrity_pool::instruction::Slash { - index: next_slash_event_index, - }; - - let instruction = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction(rpc_client, &[instruction], &[signer]).unwrap(); -} - -pub fn update_y(rpc_client: &RpcClient, signer: &dyn Signer, y: u64) { - let pool_config = get_pool_config_address(); - - let accounts = integrity_pool::accounts::UpdateY { - reward_program_authority: signer.pubkey(), - pool_config, - system_program: system_program::ID, - }; - - let instruction_data = integrity_pool::instruction::UpdateY { y }; - - let instruction = Instruction { - program_id: integrity_pool::ID, - accounts: accounts.to_account_metas(None), - data: instruction_data.data(), - }; - - process_transaction(rpc_client, &[instruction], &[signer]).unwrap(); -} - -pub fn save_stake_accounts_snapshot(rpc_client: &RpcClient) { - let data: Vec<(Pubkey, DynamicPositionArrayAccount, Pubkey, Pubkey, Pubkey)> = rpc_client + let mut data: Vec = rpc_client .get_program_accounts_with_config( &staking::ID, RpcProgramAccountsConfig { @@ -888,223 +354,106 @@ pub fn save_stake_accounts_snapshot(rpc_client: &RpcClient) { with_context: None, }, ) + .await .unwrap() .into_iter() - .map(|(pubkey, account)| { - ( - pubkey, - DynamicPositionArrayAccount { - key: pubkey, - lamports: account.lamports, - data: account.data.clone(), - }, - get_stake_account_metadata_address(pubkey), - get_stake_account_custody_address(pubkey), - get_stake_account_custody_authority_address(pubkey), - ) + .map(|(pubkey, account)| DynamicPositionArrayAccount { + key: pubkey, + lamports: account.lamports, + data: account.data.clone(), }) .collect::>(); - let metadata_accounts_data = rpc_client - .get_program_accounts_with_config( - &staking::ID, - RpcProgramAccountsConfig { - filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new( - 0, - MemcmpEncodedBytes::Bytes(StakeAccountMetadataV2::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)| { - ( - pubkey, - StakeAccountMetadataV2::try_deserialize(&mut account.data.as_slice()).unwrap(), - ) + let current_epoch = get_current_epoch(rpc_client).await; + + 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::>(); + .collect(); + + data.sort_by_key(|(exposure, _)| *exposure); + data.reverse(); + - let config = GlobalConfig::try_deserialize( + 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(&get_config_address()) + .get_account_data(&pool_config) + .await .unwrap() .as_slice(), ) .unwrap(); - let current_time = get_current_time(rpc_client); - - let metadata_account_data_locked: HashMap = metadata_accounts_data - .iter() - .map(|(pubkey, metadata)| { - ( - *pubkey, - metadata - .lock - .get_unvested_balance(current_time, config.pyth_token_list_time) - .unwrap(), - ) - }) - .collect::>(); - - let data = data - .into_iter() - .map( - |(pubkey, account, metadata_pubkey, custody_pubkey, custody_authority_pubkey)| { - ( - pubkey, - account, - metadata_pubkey, - custody_pubkey, - custody_authority_pubkey, - *metadata_account_data_locked.get(&metadata_pubkey).unwrap(), - ) - }, - ) - .collect::>(); - - // We need to check the actual tokens accounts, since you can initialize a stake account with an - // arbitrary vesting schedule but 0 tokens - let locked_token_accounts_pubkeys = data - .iter() - .filter(|(_, _, _, _, _, locked_amount)| *locked_amount > 0u64) - .map(|(_, _, _, token_account_pubkey, _, _)| *token_account_pubkey) - .collect::>(); - let mut locked_token_accounts_actual_amounts: HashMap = HashMap::new(); - for chunk in locked_token_accounts_pubkeys.chunks(100) { - rpc_client - .get_multiple_accounts(chunk) + let pool_data = PoolData::try_deserialize( + &mut &rpc_client + .get_account_data(&pool_data_address) + .await .unwrap() - .into_iter() - .enumerate() - .for_each(|(index, account)| { - locked_token_accounts_actual_amounts.insert( - chunk[index], - TokenAccount::try_deserialize(&mut account.unwrap().data.as_slice()) - .unwrap() - .amount, - ); - }); - } + .as_slice()[..8 + size_of::()], + ) + .unwrap(); - let data = data - .into_iter() - .map( - |( - pubkey, - account, - metadata_pubkey, - custody_pubkey, - custody_authority_pubkey, - locked_amount, - )| { - ( - pubkey, - account, - metadata_pubkey, - custody_pubkey, - custody_authority_pubkey, - min( - locked_amount, - *locked_token_accounts_actual_amounts - .get(&custody_pubkey) - .unwrap_or(&0u64), - ), - ) - }, - ) - .collect::>(); + println!("Processing {} accounts", data.len()); + // Initialize results vector with true to process all indexes in first round + let mut active_positions = vec![true; data.len()]; - let current_epoch = get_current_epoch(rpc_client); - let data = data - .into_iter() - .map( - |( - pubkey, - mut account, - metadata_pubkey, - custody_pubkey, - custody_authority_pubkey, - locked_amount, - )| { - let dynamic_position_array = account.to_dynamic_position_array(); - let owner = dynamic_position_array.owner().unwrap(); - let staked_in_governance = compute_voter_weight( - &dynamic_position_array, + loop { + let futures = data + .iter() + .enumerate() + .filter(|(i, _)| active_positions[*i]) + .map(|(i, (_, positions))| { + advance_delegation_record( + rpc_client, + signer, + positions, + min_reward, current_epoch, - MAX_VOTER_WEIGHT, - MAX_VOTER_WEIGHT, + &pool_data, + &pool_data_address, + &pyth_token_mint, + &pool_config, + i, ) - .unwrap(); + }) + .collect::>(); + + let futures = tokio_stream::iter(futures); + let results = futures.buffered(20).collect::>().await; + + println!("Finished processing {} accounts", results.len()); + // Update active_positions based on results + let mut result_index = 0; + for i in 0..active_positions.len() { + if active_positions[i] { + active_positions[i] = results[result_index]; + result_index += 1; + } + } - let staked_in_ois = { - let mut amount = 0u64; - for i in 0..dynamic_position_array.get_position_capacity() { - if let Some(position) = dynamic_position_array.read_position(i).unwrap() { - match position.get_current_position(current_epoch).unwrap() { - PositionState::LOCKED | PositionState::PREUNLOCKING => { - if !position.is_voting() { - amount += position.amount; - } - } - _ => {} - } - } - } - amount - }; - ( - pubkey, - metadata_pubkey, - custody_pubkey, - custody_authority_pubkey, - owner, - locked_amount, - staked_in_governance, - staked_in_ois, - ) - }, - ) - .collect::>(); + // If no delegations were advanced, we're done + if !results.iter().any(|&active| active) { + break; + } - let timestamp = chrono::Utc::now().format("%Y-%m-%d_%H:%M:%S").to_string(); - let file = File::create(format!("snapshots/snapshot-{}.csv", timestamp)).unwrap(); - let mut writer = BufWriter::new(file); - // Write the header - writeln!(writer, "positions_pubkey,metadata_pubkey,custody_pubkey,custody_authority_pubkey,owner,locked_amount,staked_in_governance,staked_in_ois").unwrap(); - // Write the data - for ( - pubkey, - metadata_pubkey, - custody_pubkey, - custody_authority_pubkey, - owner, - locked_amount, - staked_in_governance, - staked_in_ois, - ) in data - { - writeln!( - writer, - "{},{},{},{},{},{},{},{}", - pubkey, - metadata_pubkey, - custody_pubkey, - custody_authority_pubkey, - owner, - locked_amount, - staked_in_governance, - staked_in_ois - ) - .unwrap(); + println!("We will retry after 10 seconds!"); + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; } } diff --git a/staking/cli/src/main.rs b/staking/cli/src/main.rs index a06c0b9f..6b780a39 100644 --- a/staking/cli/src/main.rs +++ b/staking/cli/src/main.rs @@ -7,25 +7,13 @@ use { Action, Cli, }, - instructions::{ - close_publisher_caps, - create_slash_event, - fetch_publisher_caps_and_advance, - initialize_pool, - initialize_reward_custody, - save_stake_accounts_snapshot, - set_publisher_stake_account, - slash, - update_delegation_fee, - update_reward_program_authority, - update_y, - }, - solana_client::rpc_client::RpcClient, + instructions::claim_rewards, + solana_client::nonblocking::rpc_client::RpcClient, solana_sdk::commitment_config::CommitmentConfig, }; - -fn main() { +#[tokio::main] +async fn main() { let Cli { keypair, rpc_url, @@ -34,68 +22,9 @@ fn main() { let rpc_client = RpcClient::new_with_commitment(rpc_url, CommitmentConfig::confirmed()); match action { - Action::InitializePool { - pool_data_keypair, - reward_program_authority, - y, - slash_custody, - } => { - initialize_pool( - &rpc_client, - keypair.as_ref(), - &pool_data_keypair, - reward_program_authority, - y, - slash_custody, - ); - } - Action::Advance { - hermes_url, - wormhole, - } => { - fetch_publisher_caps_and_advance(&rpc_client, keypair.as_ref(), wormhole, hermes_url); - } - Action::InitializePoolRewardCustody {} => { - initialize_reward_custody(&rpc_client, keypair.as_ref()); - } - Action::UpdateDelegationFee { delegation_fee } => { - update_delegation_fee(&rpc_client, keypair.as_ref(), delegation_fee) - } - Action::SetPublisherStakeAccount { - publisher, - stake_account_positions, - } => set_publisher_stake_account( - &rpc_client, - keypair.as_ref(), - &publisher, - &stake_account_positions, - ), - Action::CreateSlashEvent { - publisher, - slash_ratio, - } => create_slash_event(&rpc_client, keypair.as_ref(), &publisher, slash_ratio), - Action::UpdateRewardProgramAuthority { - new_reward_program_authority, - } => update_reward_program_authority( - &rpc_client, - keypair.as_ref(), - &new_reward_program_authority, - ), - Action::Slash { - publisher, - stake_account_positions, - } => slash( - &rpc_client, - keypair.as_ref(), - &publisher, - &stake_account_positions, - ), - Action::UpdateY { y } => update_y(&rpc_client, keypair.as_ref(), y), - Action::ClosePublisherCaps { publisher_caps } => { - close_publisher_caps(&rpc_client, keypair.as_ref(), publisher_caps) - } - 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).await, } } diff --git a/staking/programs/staking/src/state/positions.rs b/staking/programs/staking/src/state/positions.rs index d6e9d15a..46999a1b 100644 --- a/staking/programs/staking/src/state/positions.rs +++ b/staking/programs/staking/src/state/positions.rs @@ -216,9 +216,7 @@ impl<'a> DynamicPositionArray<'a> { let mut exposure: u64 = 0; for i in 0..self.get_position_capacity() { if let Some(position) = self.read_position(i)? { - if position.target_with_parameters.get_target() == *target - && position.get_current_position(current_epoch)? != PositionState::UNLOCKED - { + if position.target_with_parameters.get_target() == *target { exposure = exposure .checked_add(position.amount) .ok_or_else(|| error!(ErrorCode::GenericOverflow))?; @@ -228,6 +226,7 @@ impl<'a> DynamicPositionArray<'a> { Ok(exposure) } + /// This function is used to reduce the number of positions in the array by merging equivalent /// positions. Sometimes some positions have the same `target_with_parameters`, /// `activation_epoch` and `unlocking_start`. These can obviously be merged, but this is not