From 5c5987213ff2c62d661553a4750c1ec42d07a0e7 Mon Sep 17 00:00:00 2001 From: Kunal Bhargava Date: Tue, 30 Jul 2024 22:38:59 +0000 Subject: [PATCH] runtime: move stake and move lamports instructions --- contrib/ledger-gen/run_cluster.py | 4 +- contrib/ledger-gen/src/ledgers.rs | 8 + contrib/ledger-gen/src/main.rs | 4 +- contrib/ledger-gen/src/stake.rs | 130 ++++++ src/flamenco/features/fd_features_generated.c | 7 + src/flamenco/features/fd_features_generated.h | 3 +- src/flamenco/features/feature_map.json | 3 +- .../runtime/program/fd_stake_program.c | 369 ++++++++++++++++++ .../runtime/tests/run_ledger_tests_all.txt | 2 + src/flamenco/types/fd_types.c | 36 ++ src/flamenco/types/fd_types.h | 7 +- src/flamenco/types/fd_types.json | 6 +- 12 files changed, 571 insertions(+), 8 deletions(-) create mode 100644 contrib/ledger-gen/src/stake.rs diff --git a/contrib/ledger-gen/run_cluster.py b/contrib/ledger-gen/run_cluster.py index 1816d703c4..0bc5074b7d 100644 --- a/contrib/ledger-gen/run_cluster.py +++ b/contrib/ledger-gen/run_cluster.py @@ -83,7 +83,7 @@ async def first_cluster_validator(expected_shred_version, expected_genesis_hash, vote_pubkey = await get_pubkey(vote_key, solana_source_directory) process = await asyncio.create_subprocess_shell( - f"{solana_binary('agave-validator', solana_source_directory)} --enable-rpc-transaction-history --allow-private-addr --identity {identity_key} --ledger {ledger_path} --limit-ledger-size 100000000 --dynamic-port-range 8000-8100 --no-snapshot-fetch --no-poh-speed-test --no-os-network-limits-test --vote-account {vote_pubkey} --expected-shred-version {expected_shred_version} --expected-genesis-hash {expected_genesis_hash} --no-wait-for-vote-to-start-leader --no-incremental-snapshots --full-snapshot-interval-slots {snapshot_interval} --maximum-full-snapshots-to-retain {snapshots_to_retain} --rpc-port 8899 --gossip-port 8010 --full-rpc-api --tpu-enable-udp --log {ledger_path}/validator.log", + f"{solana_binary('agave-validator', solana_source_directory)} --enable-rpc-transaction-history --allow-private-addr --identity {identity_key} --ledger {ledger_path} --limit-ledger-size 100000000 --dynamic-port-range 8000-8100 --no-snapshot-fetch --no-poh-speed-test --no-os-network-limits-test --vote-account {vote_pubkey} --expected-shred-version {expected_shred_version} --expected-genesis-hash {expected_genesis_hash} --no-wait-for-vote-to-start-leader --no-incremental-snapshots --full-snapshot-interval-slots {snapshot_interval} --snapshot-interval-slots {snapshot_interval} --maximum-full-snapshots-to-retain {snapshots_to_retain} --rpc-port 8899 --gossip-port 8010 --full-rpc-api --tpu-enable-udp --log {ledger_path}/validator.log", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) @@ -180,7 +180,7 @@ async def spawn_solana_cluster(nodes, output_dir, solana_source_directory, tick_ ) stdout, _ = await process.communicate() - if os.path.exists(os.path.join(output_dir, "node-ledger-0", "snapshot", str(snapshot_interval), "state_complete")): + if os.path.exists(os.path.join(output_dir, "node-ledger-0", "snapshot", str(snapshot_interval), "state_complete")) or os.path.exists(os.path.join(output_dir, "node-ledger-0", "snapshots", str(snapshot_interval), "state_complete")): break await asyncio.sleep(1) diff --git a/contrib/ledger-gen/src/ledgers.rs b/contrib/ledger-gen/src/ledgers.rs index 499eeab839..35138807d9 100644 --- a/contrib/ledger-gen/src/ledgers.rs +++ b/contrib/ledger-gen/src/ledgers.rs @@ -11,6 +11,7 @@ use { }; use crate::bpf_loader; +use crate::stake; /// CI Link: gs://firedancer-ci-resources/v18multi-bpf-loader.tar.gz pub fn bpf_loader_ledger(client: &RpcClient, arc_client: &Arc, payer: &Keypair, program_data: &Vec, account_data: &Vec) { @@ -29,3 +30,10 @@ pub fn bpf_loader_ledger(client: &RpcClient, arc_client: &Arc, payer: bpf_loader::close_redeploy_same_slot(&client, &arc_client, &payer, &program_data, &account_data); bpf_loader::close_redeploy_diff_slot(&client, &arc_client, &payer, &program_data, &account_data); } + +/// CI Link: gs://firedancer-ci-resources/v203-move-stake.tar.gz +/// CI Link: gs://firedancer-ci-resources/v203-move-lamports.tar.gz +pub fn stake_ledger(client: &RpcClient, payer: &Keypair) { + // stake::move_stake(&client, &payer); + stake::move_lamports(&client, &payer); +} diff --git a/contrib/ledger-gen/src/main.rs b/contrib/ledger-gen/src/main.rs index 2f612719e8..c45511a812 100644 --- a/contrib/ledger-gen/src/main.rs +++ b/contrib/ledger-gen/src/main.rs @@ -20,6 +20,7 @@ mod utils; mod bpf_loader; mod nonce; +mod stake; /// Workflow for Creating Ledgers /// * Set up all buffer accounts @@ -53,5 +54,6 @@ fn main() { let account_data = vec![0u8; 4]; // ----------------------- ONLY CHANGE BELOW THIS LINE ----------------------- - ledgers::bpf_loader_ledger(&rpc_client, &arc_client, &payer, &program_data, &account_data); + // ledgers::bpf_loader_ledger(&rpc_client, &arc_client, &payer, &program_data, &account_data); + ledgers::stake_ledger(&rpc_client, &payer); } diff --git a/contrib/ledger-gen/src/stake.rs b/contrib/ledger-gen/src/stake.rs new file mode 100644 index 0000000000..1dc2bdebb3 --- /dev/null +++ b/contrib/ledger-gen/src/stake.rs @@ -0,0 +1,130 @@ +use { + solana_sdk::{ + signature::{Keypair, Signer, read_keypair_file}, + commitment_config::{CommitmentConfig}, + feature::{self, Feature}, + feature_set, + nonce::{State as NonceState}, + system_instruction, + system_program, + message::Message, + transaction::Transaction, + stake::{ + self, + instruction::{self as stake_instruction, LockupArgs, StakeError}, + state::{ + Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeStateV2, + }, + tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent}, + }, + }, + solana_client::{ + rpc_client::{RpcClient}, + }, + solana_rpc_client_nonce_utils::{get_account_with_commitment, nonblocking}, + solana_cli::{ + spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balance}, + } +}; + +use crate::instructions; +use crate::utils; + +pub fn move_lamports(client: &RpcClient, payer: &Keypair) { + let from_stake_account = Keypair::new(); + + let authorized = Authorized { + staker: payer.pubkey(), + withdrawer: payer.pubkey(), + }; + + let create_from_stake_account_instruction = stake_instruction::create_account_checked( + &payer.pubkey(), + &from_stake_account.pubkey(), + &authorized, + 1000000000, + ); + + let transaction = utils::create_message_and_sign(&create_from_stake_account_instruction, &payer, vec![&payer, &from_stake_account], client.get_latest_blockhash().unwrap()); + let _ = client.send_and_confirm_transaction(&transaction).unwrap(); + println!("Created From Stake Account {:?} - Slot: {:?}", from_stake_account.pubkey(), client.get_slot_with_commitment(CommitmentConfig::processed()).unwrap()); + + let to_stake_account = Keypair::new(); + + let create_to_stake_account_instruction = stake_instruction::create_account_checked( + &payer.pubkey(), + &to_stake_account.pubkey(), + &authorized, + 1000000000, + ); + + let transaction = utils::create_message_and_sign(&create_to_stake_account_instruction, &payer, vec![&payer, &to_stake_account], client.get_latest_blockhash().unwrap()); + let _ = client.send_and_confirm_transaction(&transaction).unwrap(); + println!("Created To Stake Account {:?} - Slot: {:?}", to_stake_account.pubkey(), client.get_slot_with_commitment(CommitmentConfig::processed()).unwrap()); + + let move_lamports_instruction = vec![stake_instruction::move_lamports( + &from_stake_account.pubkey(), + &to_stake_account.pubkey(), + &payer.pubkey(), + 10000000, + )]; + let transaction = utils::create_message_and_sign(&move_lamports_instruction, &payer, vec![&payer], client.get_latest_blockhash().unwrap()); + let _ = client.send_and_confirm_transaction(&transaction).unwrap(); + println!("Moved Lamport from {:?} to {:?} - Slot: {:?}", from_stake_account.pubkey(), to_stake_account.pubkey(), client.get_slot_with_commitment(CommitmentConfig::processed()).unwrap()); +} + +pub fn move_stake(client: &RpcClient, payer: &Keypair) { + let from_stake_account = Keypair::new(); + + let authorized = Authorized { + staker: payer.pubkey(), + withdrawer: payer.pubkey(), + }; + + let create_from_stake_account_instructions = stake_instruction::create_account_checked( + &payer.pubkey(), + &from_stake_account.pubkey(), + &authorized, + 1000000000, + ); + + let transaction = utils::create_message_and_sign(&create_from_stake_account_instructions, &payer, vec![&payer, &from_stake_account], client.get_latest_blockhash().unwrap()); + let _ = client.send_and_confirm_transaction(&transaction).unwrap(); + println!("Created From Stake Account {:?} - Slot: {:?}", from_stake_account.pubkey(), client.get_slot_with_commitment(CommitmentConfig::processed()).unwrap()); + + let voter = read_keypair_file("/data/kbhargava/ledgers/ledger-gen-cluster/keys-0/vote.json").unwrap(); + + let delegate_stake_account_instruction = vec![stake_instruction::delegate_stake( + &from_stake_account.pubkey(), + &payer.pubkey(), + &voter.pubkey(), + )]; + let transaction = utils::create_message_and_sign(&delegate_stake_account_instruction, &payer, vec![&payer], client.get_latest_blockhash().unwrap()); + let _ = client.send_and_confirm_transaction(&transaction).unwrap(); + println!("Delegated Stake Account {:?} to {:?} - Slot: {:?}", from_stake_account.pubkey(), voter.pubkey(), client.get_slot_with_commitment(CommitmentConfig::processed()).unwrap()); + + let to_stake_account = Keypair::new(); + + let create_to_stake_account_instruction = stake_instruction::create_account_checked( + &payer.pubkey(), + &to_stake_account.pubkey(), + &authorized, + 1000000000, + ); + + let transaction = utils::create_message_and_sign(&create_to_stake_account_instruction, &payer, vec![&payer, &to_stake_account], client.get_latest_blockhash().unwrap()); + let _ = client.send_and_confirm_transaction(&transaction).unwrap(); + println!("Created To Stake Account {:?} - Slot: {:?}", to_stake_account.pubkey(), client.get_slot_with_commitment(CommitmentConfig::processed()).unwrap()); + + utils::wait_atleast_n_slots(&client, 1000); + let move_lamports_instruction = vec![stake_instruction::move_stake( + &from_stake_account.pubkey(), + &to_stake_account.pubkey(), + &payer.pubkey(), + 100000000, + )]; + + let transaction = utils::create_message_and_sign(&move_lamports_instruction, &payer, vec![&payer], client.get_latest_blockhash().unwrap()); + let _ = client.send_and_confirm_transaction(&transaction).unwrap(); + println!("Moved Stake from {:?} to {:?} - Slot: {:?}", from_stake_account.pubkey(), to_stake_account.pubkey(), client.get_slot_with_commitment(CommitmentConfig::processed()).unwrap()); +} \ No newline at end of file diff --git a/src/flamenco/features/fd_features_generated.c b/src/flamenco/features/fd_features_generated.c index e0304a5be7..7e19b3e40d 100644 --- a/src/flamenco/features/fd_features_generated.c +++ b/src/flamenco/features/fd_features_generated.c @@ -1131,6 +1131,11 @@ fd_feature_id_t const ids[] = { /* zkhiy5oLowR7HY4zogXjCjeMXyruLqBwSWH21qcFtnv */ .name = "zk_elgamal_proof_program_enabled" }, + { .index = offsetof(fd_features_t, move_stake_and_move_lamports_ixs)>>3, + .id = {"\x61\xf9\x9e\xbb\xb5\x47\x90\x0b\xb6\x5d\x66\x44\xb8\x45\xca\x36\xc5\xaf\x9f\x6f\xe1\x6d\x73\x7e\x83\x64\xb5\x59\x1c\x46\x2b\x63"}, + /* 7bTK6Jis8Xpfrs8ZoUfiMDPazTcdPcTWheZFJTA5Z6X4 */ + .name = "move_stake_and_move_lamports_ixs" }, + { .index = ULONG_MAX } }; @@ -1342,6 +1347,7 @@ fd_feature_id_query( ulong prefix ) { case 0xafe148ad652172dd: return &ids[ 197 ]; case 0x91a7af96555ea309: return &ids[ 198 ]; case 0x8e1411a93085cb0e: return &ids[ 199 ]; + case 0x0b9047b5bb9ef961: return &ids[ 200 ]; default: break; } @@ -1550,5 +1556,6 @@ FD_STATIC_ASSERT( offsetof( fd_features_t, simplify_alt_bn128_syscall_error_code FD_STATIC_ASSERT( offsetof( fd_features_t, abort_on_invalid_curve )>>3==197UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, ed25519_precompile_verify_strict )>>3==198UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, zk_elgamal_proof_program_enabled )>>3==199UL, layout ); +FD_STATIC_ASSERT( offsetof( fd_features_t, move_stake_and_move_lamports_ixs )>>3==200UL, layout ); FD_STATIC_ASSERT( sizeof( fd_features_t )>>3==FD_FEATURE_ID_CNT, layout ); diff --git a/src/flamenco/features/fd_features_generated.h b/src/flamenco/features/fd_features_generated.h index d55d5655b9..dca2499f23 100644 --- a/src/flamenco/features/fd_features_generated.h +++ b/src/flamenco/features/fd_features_generated.h @@ -6,7 +6,7 @@ /* FEATURE_ID_CNT is the number of features in ids */ -#define FD_FEATURE_ID_CNT (200UL) +#define FD_FEATURE_ID_CNT (201UL) union fd_features { @@ -213,6 +213,7 @@ union fd_features { /* 0xafe148ad652172dd */ ulong abort_on_invalid_curve; /* 0x91a7af96555ea309 */ ulong ed25519_precompile_verify_strict; /* 0x8e1411a93085cb0e */ ulong zk_elgamal_proof_program_enabled; + /* 0x0b9047b5bb9ef961 */ ulong move_stake_and_move_lamports_ixs; }; }; diff --git a/src/flamenco/features/feature_map.json b/src/flamenco/features/feature_map.json index be04973264..5b2c85a1d6 100644 --- a/src/flamenco/features/feature_map.json +++ b/src/flamenco/features/feature_map.json @@ -198,5 +198,6 @@ {"name":"simplify_alt_bn128_syscall_error_codes","pubkey": "JDn5q3GBeqzvUa7z67BbmVHVdE3EbUAjvFep3weR3jxX","cleaned_up":1180,"comment":"only impl the activated path"}, {"name":"abort_on_invalid_curve","pubkey":"FuS3FPfJDKSNot99ECLXtp3rueq36hMNStJkPJwWodLh"}, {"name":"ed25519_precompile_verify_strict","pubkey":"ed9tNscbWLYBooxWA7FE2B5KHWs8A6sxfY8EzezEcoo","cleaned_up":2000,"comment":"only impl the activated path"}, - {"name":"zk_elgamal_proof_program_enabled","pubkey":"zkhiy5oLowR7HY4zogXjCjeMXyruLqBwSWH21qcFtnv"} + {"name":"zk_elgamal_proof_program_enabled","pubkey":"zkhiy5oLowR7HY4zogXjCjeMXyruLqBwSWH21qcFtnv"}, + {"name":"move_stake_and_move_lamports_ixs","pubkey":"7bTK6Jis8Xpfrs8ZoUfiMDPazTcdPcTWheZFJTA5Z6X4"} ] diff --git a/src/flamenco/runtime/program/fd_stake_program.c b/src/flamenco/runtime/program/fd_stake_program.c index 0884b3afb1..732ce4cc91 100644 --- a/src/flamenco/runtime/program/fd_stake_program.c +++ b/src/flamenco/runtime/program/fd_stake_program.c @@ -1857,6 +1857,316 @@ merge( fd_exec_instr_ctx_t const * ctx, return 0; } +// https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L136 +static int +move_stake_or_lamports_shared_checks( fd_exec_instr_ctx_t const * invoke_context, + fd_borrowed_account_t * source_account, + ulong lamports, + fd_borrowed_account_t * destination_account, + ulong stake_authority_index, + merge_kind_t * source_merge_kind, + merge_kind_t * destination_merge_kind, + uint * custom_err ) { + int rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L145-L153 + if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( invoke_context->instr, stake_authority_index ) ) ) { + return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; + } + + fd_pubkey_t const * stake_authority_pubkey = &invoke_context->instr->acct_pubkeys[stake_authority_index]; + fd_pubkey_t const * signers[FD_TXN_SIG_MAX] = { stake_authority_pubkey }; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L158 + if( FD_UNLIKELY( 0!=memcmp( &source_account->const_meta->info.owner, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) || + 0!=memcmp( &destination_account->const_meta->info.owner, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) ) + return FD_EXECUTOR_INSTR_ERR_INCORRECT_PROGRAM_ID; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L163 + if( FD_UNLIKELY( 0==memcmp( &source_account->pubkey, &destination_account->pubkey, sizeof(fd_pubkey_t) ) ) ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L168 + if( FD_UNLIKELY( !fd_instr_acc_is_writable( invoke_context->instr, source_account->pubkey ) || + !fd_instr_acc_is_writable( invoke_context->instr, destination_account->pubkey ) ) ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L173 + if ( lamports == 0 ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L177-L180 + fd_sol_sysvar_clock_t const * clock = fd_sysvar_cache_clock( invoke_context->slot_ctx->sysvar_cache ); + if( FD_UNLIKELY( !clock ) ) + return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR; + + fd_stake_history_t const * stake_history = fd_sysvar_cache_stake_history( invoke_context->slot_ctx->sysvar_cache ); + if( FD_UNLIKELY( !stake_history ) ) + return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L182 + fd_stake_state_v2_t source_account_state = { 0 }; + rc = get_state( source_account, fd_scratch_virtual(), &source_account_state ); + if( FD_UNLIKELY( rc ) ) return rc; + + rc = get_if_mergeable( invoke_context, + &source_account_state, + source_account->const_meta->info.lamports, + clock, + stake_history, + source_merge_kind, + &invoke_context->txn_ctx->custom_err ); + if( FD_UNLIKELY( rc ) ) + return rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L191 + rc = authorized_check( &meta( source_merge_kind )->authorized, signers, STAKE_AUTHORIZE_STAKER ); + if( FD_UNLIKELY( rc ) ) + return rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L197 + fd_stake_state_v2_t destination_account_state = { 0 }; + rc = get_state( destination_account, fd_scratch_virtual(), &destination_account_state ); + if( FD_UNLIKELY( rc ) ) return rc; + + rc = get_if_mergeable( invoke_context, + &destination_account_state, + destination_account->const_meta->info.lamports, + clock, + stake_history, + destination_merge_kind, + &invoke_context->txn_ctx->custom_err ); + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L206 + rc = metas_can_merge( invoke_context, meta( source_merge_kind ), meta( destination_merge_kind ), clock, custom_err ); + if( FD_UNLIKELY( rc ) ) return rc; + + return 0; +} + +// https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L789 +static int +move_stake(fd_exec_instr_ctx_t const * ctx, + ulong source_account_index, + ulong lamports, + ulong destination_account_index, + ulong stake_authority_index, + uint * custom_err ) { + int rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L798-L804 + fd_borrowed_account_t * source_account = NULL; + rc = fd_instr_borrowed_account_view_idx( ctx, source_account_index, &source_account ); + if( FD_UNLIKELY( rc ) ) return rc; + + fd_borrowed_account_t * destination_account = NULL; + rc = fd_instr_borrowed_account_view_idx( ctx, destination_account_index, &destination_account ); + if( FD_UNLIKELY( rc ) ) return rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L804 + merge_kind_t source_merge_kind = {0}; + merge_kind_t destination_merge_kind = {0}; + rc = move_stake_or_lamports_shared_checks( ctx, + source_account, + lamports, + destination_account, + stake_authority_index, + &source_merge_kind, + &destination_merge_kind, + &ctx->txn_ctx->custom_err ); + if( FD_UNLIKELY( rc ) ) return rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L816 + if( FD_UNLIKELY( source_account->const_meta->dlen != stake_state_v2_size_of() || + destination_account->const_meta->dlen != stake_state_v2_size_of() ) ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L823 + if ( source_merge_kind.discriminant != merge_kind_fully_active ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + fd_stake_meta_t * source_meta = &source_merge_kind.inner.fully_active.meta; + fd_stake_t * source_stake = &source_merge_kind.inner.fully_active.stake; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L827 + ulong minimum_delegation = get_minimum_delegation( ctx->slot_ctx ); + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L831 + if ( FD_UNLIKELY( source_stake->delegation.stake < lamports ) ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + + ulong source_final_stake = source_stake->delegation.stake - lamports; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L836 + if( FD_UNLIKELY( source_final_stake != 0 && source_final_stake < minimum_delegation ) ) + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L841 + fd_stake_meta_t * destination_meta = NULL; + switch ( destination_merge_kind.discriminant ) { + case merge_kind_fully_active: { + fd_stake_t * destination_stake = &destination_merge_kind.inner.fully_active.stake; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L844 + if ( FD_UNLIKELY( 0!=memcmp( &source_stake->delegation.voter_pubkey, &destination_stake->delegation.voter_pubkey, sizeof(fd_pubkey_t) ) ) ) { + *custom_err = FD_STAKE_ERR_VOTE_ADDRESS_MISMATCH; + return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR; + } + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L848 + ulong destination_effective_stake = 0; + rc = fd_ulong_checked_add( destination_stake->delegation.stake, lamports, &destination_effective_stake ); + if( FD_UNLIKELY( rc ) ) return FD_EXECUTOR_INSTR_ERR_ARITHMETIC_OVERFLOW; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L855 + if ( FD_UNLIKELY( destination_effective_stake < minimum_delegation ) ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + } + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L859 + rc = merge_delegation_stake_and_credits_observed( + ctx, destination_stake, lamports, source_stake->credits_observed ); + if( FD_UNLIKELY( rc ) ) return rc; + destination_meta = &destination_merge_kind.inner.fully_active.meta; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L867 + fd_stake_state_v2_t new_destination_state = { + .discriminant = fd_stake_state_v2_enum_stake, + .inner = { .stake = { + .meta = *destination_meta, + .stake = *destination_stake, + .stake_flags = STAKE_FLAGS_EMPTY} } }; + rc = set_state( ctx, destination_account_index, &new_destination_state ); + if( FD_UNLIKELY( rc ) ) return rc; + + break; + } + case merge_kind_inactive: { + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L877 + if( lamports < minimum_delegation ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + } + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L881 + fd_stake_t * destination_stake = source_stake; + destination_stake->delegation.stake = lamports; + + destination_meta = &destination_merge_kind.inner.inactive.meta; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L886 + fd_stake_state_v2_t new_destination_state = { + .discriminant = fd_stake_state_v2_enum_stake, + .inner = { .stake = { + .meta = *destination_meta, + .stake = *destination_stake, + .stake_flags = STAKE_FLAGS_EMPTY} } }; + rc = set_state( ctx, destination_account_index, &new_destination_state ); + if( FD_UNLIKELY( rc ) ) return rc; + break; + } + default: + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L894 + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + } + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L897-L910 + if ( source_final_stake == 0) { + fd_stake_state_v2_t new_source_state = { .discriminant = fd_stake_state_v2_enum_initialized, + .inner = { .initialized = { .meta = *source_meta} } }; + rc = set_state( ctx, source_account_index, &new_source_state ); + if( FD_UNLIKELY( rc ) ) return rc; + + } else { + source_stake->delegation.stake = source_final_stake; + + fd_stake_state_v2_t new_source_state = { .discriminant = fd_stake_state_v2_enum_stake, + .inner = { .stake = { .meta = *source_meta, + .stake = *source_stake, + .stake_flags = STAKE_FLAGS_EMPTY } } }; + rc = set_state( ctx, source_account_index, &new_source_state ); + if( FD_UNLIKELY( rc ) ) return rc; + } + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L911-L914 + rc = fd_account_checked_sub_lamports( ctx, source_account_index, lamports ); + if( FD_UNLIKELY( rc ) ) return rc; + rc = fd_account_checked_add_lamports( ctx, destination_account_index, lamports ); + if( FD_UNLIKELY( rc ) ) return rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L915-L923 + if ( FD_UNLIKELY( fd_account_get_lamports2( ctx, source_account_index ) < source_meta->rent_exempt_reserve ) || + fd_account_get_lamports2( ctx, destination_account_index ) < destination_meta->rent_exempt_reserve ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + } + + return FD_EXECUTOR_INSTR_SUCCESS; +} + +// https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L928 +static int +move_lamports(fd_exec_instr_ctx_t const * ctx, + ulong source_account_index, + ulong lamports, + ulong destination_account_index, + ulong stake_authority_index ) { + int rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L937-L942 + fd_borrowed_account_t * source_account = NULL; + rc = fd_instr_borrowed_account_view_idx( ctx, source_account_index, &source_account ); + if( FD_UNLIKELY( rc ) ) return rc; + + fd_borrowed_account_t * destination_account = NULL; + rc = fd_instr_borrowed_account_view_idx( ctx, destination_account_index, &destination_account ); + if( FD_UNLIKELY( rc ) ) return rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L943 + merge_kind_t source_merge_kind = {0}; + merge_kind_t destination_merge_kind = {0}; + rc = move_stake_or_lamports_shared_checks( ctx, + source_account, + lamports, + destination_account, + stake_authority_index, + &source_merge_kind, + &destination_merge_kind, + &ctx->txn_ctx->custom_err ); + if( FD_UNLIKELY( rc ) ) return rc; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L953-L963 + ulong source_free_lamports; + switch ( source_merge_kind.discriminant ) { + case merge_kind_fully_active: { + source_free_lamports = fd_ulong_sat_sub( source_account->const_meta->info.lamports, + fd_ulong_sat_sub( + source_merge_kind.inner.fully_active.stake.delegation.stake, + source_merge_kind.inner.fully_active.meta.rent_exempt_reserve ) ); + + break; + } + case merge_kind_inactive: { + source_free_lamports = fd_ulong_sat_sub ( destination_account->const_meta->info.lamports, + source_merge_kind.inner.inactive.meta.rent_exempt_reserve ); + break; + } + default: + return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; + } + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L964 + if ( FD_UNLIKELY( lamports > source_free_lamports ) ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; + } + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_state.rs#L968-L970 + rc = fd_account_checked_sub_lamports( ctx, source_account_index, lamports ); + if( FD_UNLIKELY( rc ) ) return rc; + + rc = fd_account_checked_add_lamports( ctx, destination_account_index, lamports ); + if( FD_UNLIKELY( rc ) ) return rc; + + return FD_EXECUTOR_INSTR_SUCCESS; +} + static int withdraw( fd_exec_instr_ctx_t const * ctx, uchar stake_account_index, @@ -2719,6 +3029,65 @@ fd_stake_program_execute( fd_exec_instr_ctx_t ctx ) { return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; } + /* MoveStake + * + * Instruction: + * https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/sdk/program/src/stake/instruction.rs#L330 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L356 + */ + case fd_stake_instruction_enum_move_stake: { + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L359 + if( FD_LIKELY( FD_FEATURE_ACTIVE( ctx.slot_ctx, move_stake_and_move_lamports_ixs ) ) ) { + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L361 + if( FD_UNLIKELY( ctx.instr->acct_cnt<3 ) ) + return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS; + + ulong lamports = instruction->inner.move_stake; + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L362 + rc = move_stake( &ctx, + 0UL, + lamports, + 1UL, + 2UL, + &ctx.txn_ctx->custom_err ); + } else { + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L372 + return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + } + break; + } + /* MoveLamports + * + * Instruction: + * https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/sdk/program/src/stake/instruction.rs#L345 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L375 + */ + case fd_stake_instruction_enum_move_lamports: { + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L378 + if( FD_LIKELY( FD_FEATURE_ACTIVE( ctx.slot_ctx, move_stake_and_move_lamports_ixs ) ) ) { + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L380 + if( FD_UNLIKELY( ctx.instr->acct_cnt < 3 ) ) + return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L381 + ulong lamports = instruction->inner.move_lamports; + + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L381 + rc = move_lamports( &ctx, + 0UL, + lamports, + 1UL, + 2UL ); + } else { + // https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/programs/stake/src/stake_instruction.rs#L391 + return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + } + break; + } default: FD_LOG_ERR(( "unsupported stake instruction: %u", instruction->discriminant )); } diff --git a/src/flamenco/runtime/tests/run_ledger_tests_all.txt b/src/flamenco/runtime/tests/run_ledger_tests_all.txt index 2c7df80148..1bf7790aa8 100644 --- a/src/flamenco/runtime/tests/run_ledger_tests_all.txt +++ b/src/flamenco/runtime/tests/run_ledger_tests_all.txt @@ -49,3 +49,5 @@ src/flamenco/runtime/tests/run_ledger_test.sh -l testnet-281688085 -s snapshot-2 src/flamenco/runtime/tests/run_ledger_test.sh -l mainnet-277660422 -s snapshot-277660421-7fen26CUSpoKnaTXr3cAettDPuF6rsFcu7bEXNz9yhbM.tar.zst -p 30 -y 16 -m 5000000 -e 277660423 -c 1190 src/flamenco/runtime/tests/run_ledger_test.sh -l mainnet-277876060 -s snapshot-277876059-9vTzJ6qWyH7qkw11V5Np8wutCtExSw5XzethrdFfsjUk.tar.zst -p 30 -y 16 -m 5000000 -e 277876061 -c 1190 src/flamenco/runtime/tests/run_ledger_test.sh -l mainnet-277927063 -s snapshot-277927062-HqChvv7URAqMvvSx2WdfzL1HJFp21C1UoQHfzKxHyXtJ.tar.zst -p 30 -y 16 -m 5000000 -e 277927065 -c 1190 +src/flamenco/runtime/tests/run_ledger_test.sh -l v203-move-stake -s snapshot-4467-5yzwTpG83nRbokX2iKNYPCPod6fJu3aE8pBEE4SyqLCb.tar.zst -p 30 -y 16 -m 5000000 -e 4470 -o 7bTK6Jis8Xpfrs8ZoUfiMDPazTcdPcTWheZFJTA5Z6X4 +src/flamenco/runtime/tests/run_ledger_test.sh -l v203-move-lamports -s snapshot-650-Q8pVZ4jfmzpAGwAytPs4PJXmb4iaBXwVhvVjc8AaAXh.tar.zst -p 30 -y 16 -m 5000000 -e 655 -o 7bTK6Jis8Xpfrs8ZoUfiMDPazTcdPcTWheZFJTA5Z6X4 diff --git a/src/flamenco/types/fd_types.c b/src/flamenco/types/fd_types.c index a08b098ca7..b21bef554d 100644 --- a/src/flamenco/types/fd_types.c +++ b/src/flamenco/types/fd_types.c @@ -18328,6 +18328,12 @@ FD_FN_PURE uchar fd_stake_instruction_is_deactivate_delinquent(fd_stake_instruct FD_FN_PURE uchar fd_stake_instruction_is_redelegate(fd_stake_instruction_t const * self) { return self->discriminant == 15; } +FD_FN_PURE uchar fd_stake_instruction_is_move_stake(fd_stake_instruction_t const * self) { + return self->discriminant == 16; +} +FD_FN_PURE uchar fd_stake_instruction_is_move_lamports(fd_stake_instruction_t const * self) { + return self->discriminant == 17; +} void fd_stake_instruction_inner_new( fd_stake_instruction_inner_t * self, uint discriminant ); int fd_stake_instruction_inner_decode_preflight( uint discriminant, fd_bincode_decode_ctx_t * ctx ) { int err; @@ -18398,6 +18404,12 @@ int fd_stake_instruction_inner_decode_preflight( uint discriminant, fd_bincode_d case 15: { return FD_BINCODE_SUCCESS; } + case 16: { + return FD_BINCODE_SUCCESS; + } + case 17: { + return FD_BINCODE_SUCCESS; + } default: return FD_BINCODE_ERR_ENCODING; } } @@ -18460,6 +18472,14 @@ void fd_stake_instruction_inner_decode_unsafe( fd_stake_instruction_inner_t * se case 15: { break; } + case 16: { + fd_bincode_uint64_decode_unsafe( &self->move_stake, ctx ); + break; + } + case 17: { + fd_bincode_uint64_decode_unsafe( &self->move_lamports, ctx ); + break; + } } } int fd_stake_instruction_decode( fd_stake_instruction_t * self, fd_bincode_decode_ctx_t * ctx ) { @@ -18540,6 +18560,12 @@ void fd_stake_instruction_inner_new( fd_stake_instruction_inner_t * self, uint d case 15: { break; } + case 16: { + break; + } + case 17: { + break; + } default: break; // FD_LOG_ERR(( "unhandled type")); } } @@ -18731,6 +18757,16 @@ int fd_stake_instruction_inner_encode( fd_stake_instruction_inner_t const * self if( FD_UNLIKELY( err ) ) return err; break; } + case 16: { + err = fd_bincode_uint64_encode( self->move_stake, ctx ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + case 17: { + err = fd_bincode_uint64_encode( self->move_lamports, ctx ); + if( FD_UNLIKELY( err ) ) return err; + break; + } } return FD_BINCODE_SUCCESS; } diff --git a/src/flamenco/types/fd_types.h b/src/flamenco/types/fd_types.h index 167feb4d01..fb5ffcb278 100644 --- a/src/flamenco/types/fd_types.h +++ b/src/flamenco/types/fd_types.h @@ -2993,6 +2993,8 @@ union fd_stake_instruction_inner { fd_stake_instruction_authorize_t authorize; ulong split; ulong withdraw; + ulong move_stake; + ulong move_lamports; fd_lockup_args_t set_lockup; fd_authorize_with_seed_args_t authorize_with_seed; fd_stake_authorize_t authorize_checked; @@ -6311,7 +6313,8 @@ FD_FN_PURE uchar fd_stake_instruction_is_authorize_checked_with_seed( fd_stake_i FD_FN_PURE uchar fd_stake_instruction_is_set_lockup_checked( fd_stake_instruction_t const * self ); FD_FN_PURE uchar fd_stake_instruction_is_get_minimum_delegation( fd_stake_instruction_t const * self ); FD_FN_PURE uchar fd_stake_instruction_is_deactivate_delinquent( fd_stake_instruction_t const * self ); -FD_FN_PURE uchar fd_stake_instruction_is_redelegate( fd_stake_instruction_t const * self ); +FD_FN_PURE uchar fd_stake_instruction_is_move_stake( fd_stake_instruction_t const * self ); +FD_FN_PURE uchar fd_stake_instruction_is_move_lamports( fd_stake_instruction_t const * self ); enum { fd_stake_instruction_enum_initialize = 0, fd_stake_instruction_enum_authorize = 1, @@ -6329,6 +6332,8 @@ fd_stake_instruction_enum_set_lockup_checked = 12, fd_stake_instruction_enum_get_minimum_delegation = 13, fd_stake_instruction_enum_deactivate_delinquent = 14, fd_stake_instruction_enum_redelegate = 15, +fd_stake_instruction_enum_move_stake = 16, +fd_stake_instruction_enum_move_lamports = 17, }; void fd_stake_meta_new( fd_stake_meta_t * self ); int fd_stake_meta_decode( fd_stake_meta_t * self, fd_bincode_decode_ctx_t * ctx ); diff --git a/src/flamenco/types/fd_types.json b/src/flamenco/types/fd_types.json index 49cefe2c7c..16ef08eff7 100644 --- a/src/flamenco/types/fd_types.json +++ b/src/flamenco/types/fd_types.json @@ -1405,9 +1405,11 @@ { "name": "set_lockup_checked", "type": "lockup_checked_args" }, { "name": "get_minimum_delegation" }, { "name": "deactivate_delinquent" }, - { "name": "redelegate" } + { "name": "redelegate" }, + { "name": "move_stake", "type": "ulong" }, + { "name": "move_lamports", "type": "ulong" } ], - "comment": "https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/sdk/program/src/stake/instruction.rs#L58" + "comment": "https://github.com/anza-xyz/agave/blob/cdff19c7807b006dd63429114fb1d9573bf74172/sdk/program/src/stake/instruction.rs#L96" }, { "name": "stake_meta",