diff --git a/src/disco/gui/fd_gui_tile.c b/src/disco/gui/fd_gui_tile.c index ae4d4b4df34..5dba3b7ac23 100644 --- a/src/disco/gui/fd_gui_tile.c +++ b/src/disco/gui/fd_gui_tile.c @@ -376,7 +376,7 @@ after_frag( fd_gui_ctx_t * ctx, ctx->peers->votes[ vote_count ].stake = vote_state->stake; ctx->peers->votes[ vote_count ].last_vote_slot = vote_state->last_vote_slot; ctx->peers->votes[ vote_count ].last_vote_timestamp = vote_state->last_vote_timestamp; - ctx->peers->votes[ vote_count ].commission = vote_state->commission; + ctx->peers->votes[ vote_count ].commission = (uchar)(vote_state->commission_bps / 100U); // ctx->peers->votes[ vote_count ].epoch = fd_ulong_if( !vote_state->credits_cnt, ULONG_MAX, vote_state->epoch[ 0 ] ); // ctx->peers->votes[ vote_count ].epoch_credits = fd_ulong_if( !vote_state->credits_cnt, ULONG_MAX, vote_state->credits[ 0 ] ); diff --git a/src/discof/restore/utils/fd_ssload.c b/src/discof/restore/utils/fd_ssload.c index bf748a43d07..cb92954cffb 100644 --- a/src/discof/restore/utils/fd_ssload.c +++ b/src/discof/restore/utils/fd_ssload.c @@ -221,7 +221,7 @@ fd_ssload_recover( fd_snapshot_manifest_t * manifest, fd_vote_state_ele_t * vote_state = fd_vote_states_update( vote_stakes_prev, (fd_pubkey_t *)elem->vote ); vote_state->node_account = *(fd_pubkey_t *)elem->identity; - vote_state->commission = elem->commission; + vote_state->commission_bps = (ushort)(elem->commission * 100U); vote_state->last_vote_timestamp = elem->timestamp; vote_state->last_vote_slot = elem->slot; vote_state->stake = elem->stake; @@ -259,7 +259,7 @@ fd_ssload_recover( fd_snapshot_manifest_t * manifest, if( FD_UNLIKELY( !elem->stake ) ) continue; fd_vote_state_ele_t * vote_state = fd_vote_states_update( vote_stakes_prev_prev, (fd_pubkey_t *)elem->vote ); vote_state->node_account = *(fd_pubkey_t *)elem->identity; - vote_state->commission = elem->commission; + vote_state->commission_bps = (ushort)(elem->commission * 100U); vote_state->last_vote_timestamp = elem->timestamp; vote_state->last_vote_slot = elem->slot; vote_state->stake = elem->stake; @@ -280,7 +280,7 @@ fd_ssload_recover( fd_snapshot_manifest_t * manifest, fd_vote_state_ele_t * vote_state = fd_vote_states_update( vote_states, (fd_pubkey_t *)elem->vote_account_pubkey ); vote_state->node_account = *(fd_pubkey_t *)elem->node_account_pubkey; - vote_state->commission = elem->commission; + vote_state->commission_bps = (ushort)(elem->commission * 100U); vote_state->last_vote_timestamp = elem->last_timestamp; vote_state->last_vote_slot = elem->last_slot; vote_state->stake = elem->stake; diff --git a/src/flamenco/features/fd_features_generated.c b/src/flamenco/features/fd_features_generated.c index cddd02fa8c5..71924755ae8 100644 --- a/src/flamenco/features/fd_features_generated.c +++ b/src/flamenco/features/fd_features_generated.c @@ -1763,6 +1763,12 @@ fd_feature_id_t const ids[] = { .name = "delay_commission_updates", .cleaned_up = {UINT_MAX, UINT_MAX, UINT_MAX} }, + { .index = offsetof(fd_features_t, commission_rate_in_basis_points)>>3, + .id = {"\xcb\x2d\x5e\xc6\xdb\xd8\x88\xd3\xda\xf5\x45\x1b\x70\x19\x53\x07\xdd\x79\xf6\xd3\x71\x9c\x8b\xe1\x53\x8d\x09\xaf\x98\x5e\x6e\x14"}, + /* Eg7tXEwMZzS98xaZ1YHUbdRHsaYZiCsSaR6sKgxreoaj */ + .name = "commission_rate_in_basis_points", + .cleaned_up = {UINT_MAX, UINT_MAX, UINT_MAX} }, + { .index = ULONG_MAX } }; /* TODO replace this with fd_map_perfect */ @@ -2028,6 +2034,7 @@ fd_feature_id_query( ulong prefix ) { case 0x010f656d89a4e808: return &ids[ 256 ]; case 0xfc12b1cef363afa7: return &ids[ 257 ]; case 0x2058ca8e0a3dda9a: return &ids[ 258 ]; + case 0xd388d8dbc65e2dcb: return &ids[ 259 ]; default: break; } return NULL; @@ -2292,4 +2299,5 @@ FD_STATIC_ASSERT( offsetof( fd_features_t, enable_bls12_381_syscall FD_STATIC_ASSERT( offsetof( fd_features_t, enable_alt_bn128_g2_syscalls )>>3==256UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, switch_to_chacha8_turbine )>>3==257UL, layout ); FD_STATIC_ASSERT( offsetof( fd_features_t, delay_commission_updates )>>3==258UL, layout ); +FD_STATIC_ASSERT( offsetof( fd_features_t, commission_rate_in_basis_points )>>3==259UL, 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 b5d2221bb56..162c40545da 100644 --- a/src/flamenco/features/fd_features_generated.h +++ b/src/flamenco/features/fd_features_generated.h @@ -8,10 +8,10 @@ #endif /* FEATURE_ID_CNT is the number of features in ids */ -#define FD_FEATURE_ID_CNT (259UL) +#define FD_FEATURE_ID_CNT (260UL) /* Feature set ID calculated from all feature names */ -#define FD_FEATURE_SET_ID (708229304U) +#define FD_FEATURE_SET_ID (2002210363U) union fd_features { ulong f[ FD_FEATURE_ID_CNT ]; @@ -275,5 +275,6 @@ union fd_features { /* 0x010f656d89a4e808 */ ulong enable_alt_bn128_g2_syscalls; /* 0xfc12b1cef363afa7 */ ulong switch_to_chacha8_turbine; /* 0x2058ca8e0a3dda9a */ ulong delay_commission_updates; + /* 0xd388d8dbc65e2dcb */ ulong commission_rate_in_basis_points; }; }; diff --git a/src/flamenco/features/feature_map.json b/src/flamenco/features/feature_map.json index 9b30367a18c..9502ee68077 100644 --- a/src/flamenco/features/feature_map.json +++ b/src/flamenco/features/feature_map.json @@ -257,5 +257,6 @@ {"name":"enable_bls12_381_syscall","pubkey":"b1sraWPVFdcUizB2LV5wQTeMuK8M313bi5bHjco5eVU"}, {"name":"enable_alt_bn128_g2_syscalls","pubkey":"bn1hKNURMGQaQoEVxahcEAcqiX3NwRs6hgKKNSLeKxH"}, {"name":"switch_to_chacha8_turbine","pubkey":"CHaChatUnR3s6cPyPMMGNJa3VdQQ8PNH2JqdD4LpCKnB"}, - {"name":"delay_commission_updates","pubkey":"BRUoCu28xjjPkDcNm7iY9a8LqgftZko99ioXz84wivXh"} + {"name":"delay_commission_updates","pubkey":"BRUoCu28xjjPkDcNm7iY9a8LqgftZko99ioXz84wivXh"}, + {"name":"commission_rate_in_basis_points","pubkey":"Eg7tXEwMZzS98xaZ1YHUbdRHsaYZiCsSaR6sKgxreoaj"} ] diff --git a/src/flamenco/rewards/fd_rewards.c b/src/flamenco/rewards/fd_rewards.c index 1b5ae13b67b..3c1afcd6590 100644 --- a/src/flamenco/rewards/fd_rewards.c +++ b/src/flamenco/rewards/fd_rewards.c @@ -12,6 +12,9 @@ #include "../accdb/fd_accdb_sync.h" #include "fd_epoch_rewards.h" +/* https://github.com/anza-xyz/agave/blob/master/runtime/src/inflation_rewards/mod.rs#L244 */ +#define FD_MAX_INFLATION_REWARDS_BPS (10000U) + /* https://github.com/anza-xyz/agave/blob/7117ed9653ce19e8b2dea108eff1f3eb6a3378a7/sdk/src/inflation.rs#L85 */ static double total( fd_inflation_t const * inflation, double year ) { @@ -277,22 +280,24 @@ typedef struct fd_commission_split fd_commission_split_t; /// returns commission split as (voter_portion, staker_portion, was_split) tuple /// /// if commission calculation is 100% one way or other, indicate with false for was_split +/// +/// commission_bps is in basis points (0-10000), where 10000 = 100% -// https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L543 +// https://github.com/anza-xyz/agave/blob/master/runtime/src/inflation_rewards/mod.rs#L243 void -fd_vote_commission_split( uchar commission, +fd_vote_commission_split( ushort commission_bps, ulong on, fd_commission_split_t * result ) { - uint commission_split = fd_uint_min( (uint)commission, 100 ); - result->is_split = (commission_split != 0 && commission_split != 100); - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L545 + uint commission_split = fd_uint_min( (uint)commission_bps, FD_MAX_INFLATION_REWARDS_BPS ); + result->is_split = (commission_split != 0 && commission_split != FD_MAX_INFLATION_REWARDS_BPS); + // https://github.com/anza-xyz/agave/blob/master/runtime/src/inflation_rewards/mod.rs#L247 if( commission_split==0U ) { result->voter_portion = 0; result->staker_portion = on; return; } - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L546 - if( commission_split==100U ) { + // https://github.com/anza-xyz/agave/blob/master/runtime/src/inflation_rewards/mod.rs#L248 + if( commission_split==FD_MAX_INFLATION_REWARDS_BPS ) { result->voter_portion = on; result->staker_portion = 0; return; @@ -309,11 +314,11 @@ fd_vote_commission_split( uchar commission, // being rewarded at all. Thus, note that we intentionally discard // any residual fractional lamports. - // https://github.com/anza-xyz/agave/blob/v2.0.1/sdk/program/src/vote/state/mod.rs#L548 + // https://github.com/anza-xyz/agave/blob/master/runtime/src/inflation_rewards/mod.rs#L256 result->voter_portion = - (ulong)((uint128)on * (uint128)commission_split / (uint128)100); + (ulong)((uint128)on * (uint128)commission_split / (uint128)FD_MAX_INFLATION_REWARDS_BPS); result->staker_portion = - (ulong)((uint128)on * (uint128)( 100 - commission_split ) / (uint128)100); + (ulong)((uint128)on * (uint128)( FD_MAX_INFLATION_REWARDS_BPS - commission_split ) / (uint128)FD_MAX_INFLATION_REWARDS_BPS); } /* https://github.com/anza-xyz/agave/blob/cbc8320d35358da14d79ebcada4dfb6756ffac79/programs/stake/src/rewards.rs#L33 */ @@ -323,7 +328,7 @@ redeem_rewards( fd_accdb_user_t * accdb, fd_stake_history_t const * stake_history, fd_stake_delegation_t const * stake, fd_vote_state_ele_t const * vote_state, - uchar commission, + ushort commission_bps, ulong rewarded_epoch, ulong total_rewards, uint128 total_points, @@ -378,7 +383,7 @@ redeem_rewards( fd_accdb_user_t * accdb, } fd_commission_split_t split_result; - fd_vote_commission_split( commission, rewards, &split_result ); + fd_vote_commission_split( commission_bps, rewards, &split_result ); if( split_result.is_split && (split_result.voter_portion == 0 || split_result.staker_portion == 0) ) { return 1; } @@ -495,25 +500,27 @@ calculate_reward_points_partitioned( fd_bank_t * bank, return total_points; } -/* Get commission rate for reward calculation +/* Get commission rate in basis points for reward calculation + + Returns commission in basis points (0-10000). - FIXME: permalink when Agave 4.0 is cut */ -static uchar -get_commission_rate( fd_bank_t * bank, - fd_pubkey_t const * voter_acc, - uchar current_commission ) { + https://github.com/anza-xyz/agave/blob/master/runtime/src/bank/partitioned_epoch_rewards/calculation.rs#L467 */ +static ushort +get_commission_rate_bps( fd_bank_t * bank, + fd_pubkey_t const * voter_acc, + ushort current_commission_bps ) { if( !FD_FEATURE_ACTIVE_BANK( bank, delay_commission_updates ) ) { - return current_commission; + return current_commission_bps; } - /* Delayed commission rate */ + /* Delayed commission rate - use snapshot from previous epochs */ fd_vote_state_ele_t const * prev_prev_ele = fd_vote_states_query_const( fd_bank_vote_states_prev_prev_query( bank ), voter_acc ); - if( FD_LIKELY( prev_prev_ele ) ) return prev_prev_ele->commission; + if( FD_LIKELY( prev_prev_ele ) ) return prev_prev_ele->commission_bps; fd_vote_state_ele_t const * prev_ele = fd_vote_states_query_const( fd_bank_vote_states_prev_query( bank ), voter_acc ); - if( FD_LIKELY( prev_ele ) ) return prev_ele->commission; + if( FD_LIKELY( prev_ele ) ) return prev_ele->commission_bps; - return current_commission; + return current_commission_bps; } /* Calculates epoch rewards for stake/vote accounts. @@ -587,7 +594,7 @@ calculate_stake_vote_rewards( fd_bank_t * bank, fd_vote_state_credits_t * realc_credit = !!runtime_stack->stakes.prev_vote_credits_used ? &runtime_stack->stakes.vote_credits[ vote_state_ele->idx ] : NULL; - uchar commission = get_commission_rate( bank, voter_acc, vote_state_ele->commission ); + ushort commission_bps = get_commission_rate_bps( bank, voter_acc, vote_state_ele->commission_bps ); /* redeem_rewards is actually just responsible for calculating the vote and stake rewards for each stake account. It does not do @@ -599,7 +606,7 @@ calculate_stake_vote_rewards( fd_bank_t * bank, stake_history, stake_delegation, vote_state_ele, - commission, + commission_bps, rewarded_epoch, total_rewards, total_points, @@ -616,7 +623,7 @@ calculate_stake_vote_rewards( fd_bank_t * bank, fd_bank_slot_get( bank ), stake_delegation->stake_account, *voter_acc, - commission, + commission_bps, (long)calculated_stake_rewards->voter_rewards, (long)calculated_stake_rewards->staker_rewards, (long)calculated_stake_rewards->new_credits_observed ); diff --git a/src/flamenco/runtime/Local.mk b/src/flamenco/runtime/Local.mk index 54e0ecf4265..6a728bc42be 100644 --- a/src/flamenco/runtime/Local.mk +++ b/src/flamenco/runtime/Local.mk @@ -81,6 +81,8 @@ $(call make-unit-test,test_accounts_resize_delta,tests/test_accounts_resize_delt $(call run-unit-test,test_accounts_resize_delta,) $(call make-unit-test,test_delay_commission_updates,tests/test_delay_commission_updates,fd_flamenco fd_funk fd_ballet fd_util) $(call run-unit-test,test_delay_commission_updates,) +$(call make-unit-test,test_commission_rate_in_basis_points,tests/test_commission_rate_in_basis_points,fd_flamenco fd_funk fd_ballet fd_util) +$(call run-unit-test,test_commission_rate_in_basis_points,) endif endif endif diff --git a/src/flamenco/runtime/program/fd_vote_program.c b/src/flamenco/runtime/program/fd_vote_program.c index eaf16257c86..3078df84749 100644 --- a/src/flamenco/runtime/program/fd_vote_program.c +++ b/src/flamenco/runtime/program/fd_vote_program.c @@ -932,6 +932,71 @@ update_commission( fd_exec_instr_ctx_t * ctx, ); } +/* SIMD-0291: Update commission rate in basis points + https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_state/mod.rs#L871-L906 */ +static int +update_commission_bps( fd_exec_instr_ctx_t * ctx, + int target_version, + fd_borrowed_account_t * vote_account, + ushort commission_bps, + fd_commission_kind_t * kind, + fd_pubkey_t const * signers[static FD_TXN_SIG_MAX], + ulong signers_cnt ) { + + /* SIMD-0291: Commission Rate in Basis Points + Requires SIMD-0185: Vote State V4 + Requires SIMD-0249: Delay Commission Updates + https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_processor.rs#L324-L333 */ + if( !FD_FEATURE_ACTIVE_BANK( ctx->bank, commission_rate_in_basis_points ) || + !FD_FEATURE_ACTIVE_BANK( ctx->bank, delay_commission_updates ) || + target_version != VOTE_STATE_TARGET_VERSION_V4 ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + } + + /* TODO: fill in this block when block_revenue_sharing is implemented + https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_state/mod.rs#L880-L884 */ + if( kind->discriminant == fd_commission_kind_enum_block_revenue ) { + return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + } + + /* https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_state/mod.rs#L886-L889 */ + int rc = get_vote_state_handler_checked( + vote_account, + target_version, + false, + ctx->runtime->vote_program.update_commission.vote_state_mem, + ctx->runtime->vote_program.update_commission.authorized_voters_mem, + ctx->runtime->vote_program.update_commission.landed_votes_mem + ); + if( FD_UNLIKELY( rc ) ) return rc; + + fd_vote_state_versioned_t * vote_state_versioned = + (fd_vote_state_versioned_t *)ctx->runtime->vote_program.update_commission.vote_state_mem; + + /* Require authorized withdrawer to sign. + https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_state/mod.rs#L893-L894 */ + rc = fd_vote_verify_authorized_signer( + fd_vsv_get_authorized_withdrawer( vote_state_versioned ), + signers, + signers_cnt + ); + if( FD_UNLIKELY( rc ) ) return rc; + + /* https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_state/mod.rs#L896-L903 */ + if( vote_state_versioned->discriminant == fd_vote_state_versioned_enum_v4 ) { + vote_state_versioned->inner.v4.inflation_rewards_commission_bps = commission_bps; + } + /* TODO: add BlockRevenue case when implementing block_revenue_sharing */ + + /* Write back to account */ + return fd_vsv_set_vote_account_state( + ctx, + vote_account, + vote_state_versioned, + ctx->runtime->vote_program.update_commission.vote_lockout_mem + ); +} + /* https://github.com/anza-xyz/agave/blob/v3.1.1/programs/vote/src/vote_state/mod.rs#L848C8-L903 */ static int withdraw( fd_exec_instr_ctx_t * ctx, @@ -2081,7 +2146,75 @@ fd_vote_program_execute( fd_exec_instr_ctx_t * ctx ) { break; } + /* InitializeAccountV2 + * + * Instruction: + * https://github.com/anza-xyz/solana-sdk/blob/master/vote-interface/src/instruction.rs#L195-L200 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_processor.rs#L302-L319 + * + * Notes: + * - TODO: implement with bls_pubkey_management_in_vote_account + */ + case fd_vote_instruction_enum_initialize_account_v2: { + return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + } + + /* UpdateCommissionCollector + * + * Instruction: + * https://github.com/anza-xyz/solana-sdk/blob/master/vote-interface/src/instruction.rs#L202-L210 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_processor.rs#L343-L371 + * + * Notes: + * - TODO: implement with custom_commission_collector + */ + case fd_vote_instruction_enum_update_commission_collector: { + return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + } + + /* UpdateCommissionBps + * + * Instruction: + * https://github.com/anza-xyz/solana-sdk/blob/master/vote-interface/src/instruction.rs#L212-L221 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_processor.rs#L320-L342 + * + */ + case fd_vote_instruction_enum_update_commission_bps: { + rc = update_commission_bps( + ctx, + target_version, + &me, + instruction->inner.update_commission_bps.commission_bps, + &instruction->inner.update_commission_bps.kind, + signers, + signers_cnt + ); + break; + } + + /* DepositDelegatorRewards + * + * Instruction: + * https://github.com/anza-xyz/solana-sdk/blob/master/vote-interface/src/instruction.rs#L261-L265 + * + * Processor: + * https://github.com/anza-xyz/agave/blob/master/programs/vote/src/vote_processor.rs#L372-L391 + * + * Notes: + * - TODO: implement with block_revenue_sharing + */ + case fd_vote_instruction_enum_deposit_delegator_rewards: { + return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + } + default: + /* unreachable due to limited_deserialize */ FD_LOG_CRIT(( "unsupported vote instruction: %u", instruction->discriminant )); } diff --git a/src/flamenco/runtime/tests/fd_dump_pb.c b/src/flamenco/runtime/tests/fd_dump_pb.c index e126996953c..820b6172130 100644 --- a/src/flamenco/runtime/tests/fd_dump_pb.c +++ b/src/flamenco/runtime/tests/fd_dump_pb.c @@ -481,7 +481,7 @@ create_synthetic_vote_account_from_vote_state( fd_vote_state_ele_t const * vot .v3 = { .node_pubkey = vote_state->node_account, .authorized_withdrawer = vote_state->node_account, - .commission = vote_state->commission, + .commission = (uchar)(vote_state->commission_bps / 100U), .root_slot = 0UL, .has_root_slot = 0, .last_timestamp = { diff --git a/src/flamenco/runtime/tests/test_commission_rate_in_basis_points.c b/src/flamenco/runtime/tests/test_commission_rate_in_basis_points.c new file mode 100644 index 00000000000..de292d8951b --- /dev/null +++ b/src/flamenco/runtime/tests/test_commission_rate_in_basis_points.c @@ -0,0 +1,788 @@ +/* Test for commission_rate_in_basis_points (SIMD-0291) + + Vote Program Tests: + Feature OFF: update_commission_bps returns InvalidInstructionData + Feature ON: update_commission_bps sets V4 account commission in basis points + + Rewards Tests: + Feature OFF: commission split uses percent (0-100) + Feature ON: commission split uses basis points (0-10000, values >10000 capped) */ + +#include "../fd_acc_pool.h" +#include "../fd_runtime.h" +#include "../fd_runtime_stack.h" +#include "../fd_bank.h" +#include "../fd_system_ids.h" +#include "../sysvar/fd_sysvar_rent.h" +#include "../sysvar/fd_sysvar_epoch_schedule.h" +#include "../sysvar/fd_sysvar_stake_history.h" +#include "../sysvar/fd_sysvar_clock.h" +#include "../sysvar/fd_sysvar_cache.h" +#include "../program/fd_builtin_programs.h" +#include "../program/fd_vote_program.h" +#include "../program/fd_stake_program.h" +#include "../../accdb/fd_accdb_admin_v1.h" +#include "../../accdb/fd_accdb_impl_v1.h" +#include "../../features/fd_features.h" +#include "../../accdb/fd_accdb_sync.h" +#include "../../log_collector/fd_log_collector.h" +#include "../../stakes/fd_vote_states.h" +#include "../../stakes/fd_stake_delegations.h" +#include "../../types/fd_types.h" + +#define TEST_SLOTS_PER_EPOCH (4UL) +#define TEST_ACC_POOL_ACCOUNT_CNT (32UL) +#define TEST_LAMPORTS (10000UL) +#define TEST_STAKE_AMOUNT (10000000000UL) +#define TEST_VOTE_CREDITS (1000UL) +#define VOTE_IX_UPDATE_COMMISSION (5U) +#define VOTE_IX_UPDATE_COMMISSION_BPS (18U) +#define FD_VOTE_STATE_V3_SZ (3762UL) +#define TEST_CAPITALIZATION (259526316000UL) + +struct test_env { + fd_wksp_t * wksp; + ulong tag; + fd_banks_t banks[1]; + fd_bank_t bank[1]; + void * funk_mem; + fd_accdb_admin_t accdb_admin[1]; + fd_accdb_user_t accdb[1]; + fd_funk_txn_xid_t xid; + int xid_is_rooted; + fd_runtime_stack_t * runtime_stack; + fd_runtime_t * runtime; + fd_txn_in_t txn_in; + fd_txn_out_t txn_out[1]; + fd_log_collector_t log_collector[1]; + int txn_needs_cancel; +}; +typedef struct test_env test_env_t; + +static fd_pubkey_t const validator_key = { .ul = { 0x1111111111111111UL, 0, 0, 0 } }; +static fd_pubkey_t const authority_key = { .ul = { 0xAAAAAAAAAAAAAAAAUL, 0, 0, 0 } }; +static fd_pubkey_t const staker_key = { .ul = { 0x4444444444444444UL, 0, 0, 0 } }; + +static void +create_account_raw( fd_accdb_user_t * user, + fd_funk_txn_xid_t const * xid, + fd_pubkey_t const * pubkey, + ulong lamports, + uint dlen, + uchar * data, + fd_pubkey_t const * owner ) { + fd_accdb_rw_t rw[1]; + FD_TEST( fd_accdb_open_rw( user, rw, xid, pubkey, dlen, FD_ACCDB_FLAG_CREATE ) ); + if( data && dlen ) fd_accdb_ref_data_set( user, rw, data, dlen ); + rw->meta->lamports = lamports; + rw->meta->slot = 0UL; + rw->meta->executable = 0; + if( owner ) memcpy( rw->meta->owner, owner->key, 32UL ); + else memset( rw->meta->owner, 0UL, 32UL ); + fd_accdb_close_rw( user, rw ); +} + +static test_env_t * +test_env_create_with_features( test_env_t * env, + fd_wksp_t * wksp, + int delay_commission, + int commission_rate_bps, + int vote_state_v4 ) { + fd_memset( env, 0, sizeof(test_env_t) ); + env->wksp = wksp; + env->tag = 1UL; + + env->funk_mem = fd_wksp_alloc_laddr( wksp, fd_funk_align(), fd_funk_footprint( 16UL, 1024UL ), env->tag ); + FD_TEST( env->funk_mem ); + FD_TEST( fd_funk_new( env->funk_mem, env->tag, 17UL, 16UL, 1024UL ) ); + FD_TEST( fd_accdb_admin_v1_init( env->accdb_admin, env->funk_mem ) ); + FD_TEST( fd_accdb_user_v1_init( env->accdb, env->funk_mem ) ); + + fd_banks_data_t * banks_data = fd_wksp_alloc_laddr( wksp, fd_banks_align(), fd_banks_footprint( 2UL, 2UL ), env->tag ); + fd_banks_locks_t * banks_locks = fd_wksp_alloc_laddr( wksp, alignof(fd_banks_locks_t), sizeof(fd_banks_locks_t), env->tag ); + FD_TEST( banks_data && banks_locks ); + fd_banks_locks_init( banks_locks ); + FD_TEST( fd_banks_join( env->banks, fd_banks_new( banks_data, 2UL, 2UL, 0, 8888UL ), banks_locks ) ); + FD_TEST( fd_banks_init_bank( env->bank, env->banks ) ); + + env->bank->data->flags &= (ulong)~FD_BANK_FLAGS_FROZEN; + fd_bank_cost_tracker_t * cost_tracker_pool = fd_bank_get_cost_tracker_pool( env->bank->data ); + env->bank->data->cost_tracker_pool_idx = fd_bank_cost_tracker_pool_idx_acquire( cost_tracker_pool ); + + env->runtime_stack = fd_wksp_alloc_laddr( wksp, alignof(fd_runtime_stack_t), sizeof(fd_runtime_stack_t), env->tag ); + FD_TEST( env->runtime_stack ); + fd_memset( env->runtime_stack, 0, sizeof(fd_runtime_stack_t) ); + + fd_funk_txn_xid_t root[1]; + fd_funk_txn_xid_set_root( root ); + env->xid = (fd_funk_txn_xid_t){ .ul = { 0UL, env->bank->data->idx } }; + fd_accdb_attach_child( env->accdb_admin, root, &env->xid ); + + fd_rent_t rent = { .lamports_per_uint8_year = 3480UL, .exemption_threshold = 2.0, .burn_percent = 50 }; + fd_bank_rent_set( env->bank, rent ); + fd_sysvar_rent_write( env->bank, env->accdb, &env->xid, NULL, &rent ); + + fd_epoch_schedule_t epoch_schedule = { + .slots_per_epoch = TEST_SLOTS_PER_EPOCH, .leader_schedule_slot_offset = TEST_SLOTS_PER_EPOCH, + .warmup = 0, .first_normal_epoch = 0UL, .first_normal_slot = 0UL + }; + fd_bank_epoch_schedule_set( env->bank, epoch_schedule ); + fd_sysvar_epoch_schedule_write( env->bank, env->accdb, &env->xid, NULL, &epoch_schedule ); + fd_sysvar_stake_history_init( env->bank, env->accdb, &env->xid, NULL ); + fd_sysvar_clock_init( env->bank, env->accdb, &env->xid, NULL ); + + fd_blockhashes_t * bhq = fd_blockhashes_init( fd_bank_block_hash_queue_modify( env->bank ), 12345UL ); + fd_hash_t dummy_hash = {0}; + fd_memset( dummy_hash.uc, 0xAB, FD_HASH_FOOTPRINT ); + fd_blockhashes_push_new( bhq, &dummy_hash )->fee_calculator.lamports_per_signature = 0UL; + + fd_inflation_t inflation = { .initial = 0.08, .terminal = 0.015, .taper = 0.15, + .foundation = 0.05, .foundation_term = 7.0, .unused = 0.0 }; + fd_bank_inflation_set( env->bank, inflation ); + fd_bank_slots_per_year_set( env->bank, 78892314UL ); + fd_bank_capitalization_set( env->bank, TEST_CAPITALIZATION ); + fd_bank_slot_set( env->bank, 0UL ); + fd_bank_epoch_set( env->bank, 0UL ); + + fd_features_t features = {0}; + fd_features_disable_all( &features ); + if( delay_commission ) features.delay_commission_updates = 0UL; + if( commission_rate_bps ) features.commission_rate_in_basis_points = 0UL; + if( vote_state_v4 ) features.vote_state_v4 = 0UL; + fd_bank_features_set( env->bank, features ); + + fd_builtin_programs_init( env->bank, env->accdb, &env->xid, NULL ); + + env->runtime = fd_wksp_alloc_laddr( wksp, alignof(fd_runtime_t), sizeof(fd_runtime_t), env->tag ); + uchar * acc_pool_mem = fd_wksp_alloc_laddr( wksp, fd_acc_pool_align(), fd_acc_pool_footprint( TEST_ACC_POOL_ACCOUNT_CNT ), env->tag ); + fd_acc_pool_t * acc_pool = fd_acc_pool_join( fd_acc_pool_new( acc_pool_mem, TEST_ACC_POOL_ACCOUNT_CNT ) ); + FD_TEST( acc_pool ); + + env->runtime->accdb = &env->accdb[0]; + env->runtime->progcache = NULL; + env->runtime->status_cache = NULL; + env->runtime->acc_pool = acc_pool; + fd_log_collector_init( env->log_collector, 0 ); + env->runtime->log.log_collector = env->log_collector; + env->runtime->log.enable_log_collector = 0; + env->runtime->log.dumping_mem = NULL; + env->runtime->log.enable_vm_tracing = 0; + env->runtime->log.tracing_mem = NULL; + env->runtime->log.capture_ctx = NULL; + + return env; +} + +static void +test_env_destroy( test_env_t * env ) { + if( env->txn_needs_cancel ) { + env->txn_out[0].err.is_committable = 0; + fd_runtime_cancel_txn( env->runtime, &env->txn_out[0] ); + } + if( !env->xid_is_rooted ) fd_accdb_cancel( env->accdb_admin, &env->xid ); + if( env->runtime ) { + if( env->runtime->acc_pool ) fd_wksp_free_laddr( env->runtime->acc_pool ); + fd_wksp_free_laddr( env->runtime ); + } + fd_wksp_free_laddr( env->runtime_stack ); + fd_wksp_free_laddr( env->banks->data ); + fd_wksp_free_laddr( env->banks->locks ); + void * accdb_shfunk = fd_accdb_admin_v1_funk( env->accdb_admin )->shmem; + fd_accdb_admin_fini( env->accdb_admin ); + fd_accdb_user_fini( env->accdb ); + fd_wksp_free_laddr( fd_funk_delete( accdb_shfunk ) ); + fd_wksp_reset( env->wksp, (uint)env->tag ); +} + +static void +create_vote_account_v3( test_env_t * env, uchar commission, ulong epoch_credits_epoch ) { + uchar * vote_state_data = fd_wksp_alloc_laddr( env->wksp, 8UL, FD_VOTE_STATE_V3_SZ, env->tag ); + uchar * pool_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 1024UL, env->tag ); + uchar * treap_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 1024UL, env->tag ); + uchar * epoch_cred_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 2048UL, env->tag ); + fd_memset( vote_state_data, 0, FD_VOTE_STATE_V3_SZ ); + + fd_vote_state_versioned_t vsv[1]; + fd_vote_state_versioned_new_disc( vsv, fd_vote_state_versioned_enum_v3 ); + fd_vote_state_v3_t * vs = &vsv->inner.v3; + vs->node_pubkey = authority_key; + vs->authorized_withdrawer = authority_key; + vs->commission = commission; + + vs->authorized_voters.pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, 1UL ) ); + vs->authorized_voters.treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, 1UL ) ); + fd_vote_authorized_voter_t * voter_ele = fd_vote_authorized_voters_pool_ele_acquire( vs->authorized_voters.pool ); + *voter_ele = (fd_vote_authorized_voter_t){ .epoch = 0UL, .pubkey = authority_key, .prio = authority_key.ul[0] }; + fd_vote_authorized_voters_treap_ele_insert( vs->authorized_voters.treap, voter_ele, vs->authorized_voters.pool ); + + vs->epoch_credits = deq_fd_vote_epoch_credits_t_join( deq_fd_vote_epoch_credits_t_new( epoch_cred_mem, 64UL ) ); + if( epoch_credits_epoch != ULONG_MAX ) { + fd_vote_epoch_credits_t * cred = deq_fd_vote_epoch_credits_t_push_tail_nocopy( vs->epoch_credits ); + cred->epoch = epoch_credits_epoch; + cred->credits = TEST_VOTE_CREDITS; + cred->prev_credits = 0UL; + } + + fd_bincode_encode_ctx_t encode = { .data = vote_state_data, .dataend = vote_state_data + FD_VOTE_STATE_V3_SZ }; + FD_TEST( fd_vote_state_versioned_encode( vsv, &encode ) == FD_BINCODE_SUCCESS ); + + fd_pubkey_t vote_program = fd_solana_vote_program_id; + create_account_raw( env->accdb, &env->xid, &validator_key, TEST_LAMPORTS, FD_VOTE_STATE_V3_SZ, vote_state_data, &vote_program ); + + fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( env->bank ); + fd_vote_state_ele_t * ele = fd_vote_states_update( vote_states, &validator_key ); + ele->node_account = authority_key; + ele->commission_bps = (ushort)(commission * 100U); + ele->stake = TEST_LAMPORTS; + fd_bank_vote_states_end_locking_modify( env->bank ); + + fd_wksp_free_laddr( epoch_cred_mem ); + fd_wksp_free_laddr( treap_mem ); + fd_wksp_free_laddr( pool_mem ); + fd_wksp_free_laddr( vote_state_data ); +} + +static void +create_vote_account_v4( test_env_t * env, ushort commission_bps, ulong epoch_credits_epoch ) { + uchar * vote_state_data = fd_wksp_alloc_laddr( env->wksp, 8UL, FD_VOTE_STATE_V4_SZ, env->tag ); + uchar * pool_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 1024UL, env->tag ); + uchar * treap_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 1024UL, env->tag ); + uchar * epoch_cred_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 2048UL, env->tag ); + uchar * votes_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 4096UL, env->tag ); + fd_memset( vote_state_data, 0, FD_VOTE_STATE_V4_SZ ); + + fd_vote_state_versioned_t vsv[1]; + fd_vote_state_versioned_new_disc( vsv, fd_vote_state_versioned_enum_v4 ); + fd_vote_state_v4_t * vs = &vsv->inner.v4; + vs->node_pubkey = authority_key; + vs->authorized_withdrawer = authority_key; + vs->inflation_rewards_collector = validator_key; + vs->block_revenue_collector = validator_key; + vs->inflation_rewards_commission_bps = commission_bps; + vs->block_revenue_commission_bps = 0; + vs->pending_delegator_rewards = 0UL; + vs->has_bls_pubkey_compressed = 0; + + vs->authorized_voters.pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, 1UL ) ); + vs->authorized_voters.treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, 1UL ) ); + fd_vote_authorized_voter_t * voter_ele = fd_vote_authorized_voters_pool_ele_acquire( vs->authorized_voters.pool ); + *voter_ele = (fd_vote_authorized_voter_t){ .epoch = 0UL, .pubkey = authority_key, .prio = authority_key.ul[0] }; + fd_vote_authorized_voters_treap_ele_insert( vs->authorized_voters.treap, voter_ele, vs->authorized_voters.pool ); + + vs->votes = deq_fd_landed_vote_t_join( deq_fd_landed_vote_t_new( votes_mem, 32UL ) ); + vs->has_root_slot = 0; + + vs->epoch_credits = deq_fd_vote_epoch_credits_t_join( deq_fd_vote_epoch_credits_t_new( epoch_cred_mem, 64UL ) ); + if( epoch_credits_epoch != ULONG_MAX ) { + fd_vote_epoch_credits_t * cred = deq_fd_vote_epoch_credits_t_push_tail_nocopy( vs->epoch_credits ); + cred->epoch = epoch_credits_epoch; + cred->credits = TEST_VOTE_CREDITS; + cred->prev_credits = 0UL; + } + + fd_bincode_encode_ctx_t encode = { .data = vote_state_data, .dataend = vote_state_data + FD_VOTE_STATE_V4_SZ }; + FD_TEST( fd_vote_state_versioned_encode( vsv, &encode ) == FD_BINCODE_SUCCESS ); + + fd_pubkey_t vote_program = fd_solana_vote_program_id; + create_account_raw( env->accdb, &env->xid, &validator_key, TEST_LAMPORTS, FD_VOTE_STATE_V4_SZ, vote_state_data, &vote_program ); + + fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( env->bank ); + fd_vote_state_ele_t * ele = fd_vote_states_update( vote_states, &validator_key ); + ele->node_account = authority_key; + ele->commission_bps = commission_bps; + ele->stake = TEST_LAMPORTS; + fd_bank_vote_states_end_locking_modify( env->bank ); + + fd_wksp_free_laddr( votes_mem ); + fd_wksp_free_laddr( epoch_cred_mem ); + fd_wksp_free_laddr( treap_mem ); + fd_wksp_free_laddr( pool_mem ); + fd_wksp_free_laddr( vote_state_data ); +} + +static void +create_stake_account( test_env_t * env ) { + uchar * stake_data = fd_wksp_alloc_laddr( env->wksp, 8UL, FD_STAKE_STATE_V2_SZ, env->tag ); + fd_memset( stake_data, 0, FD_STAKE_STATE_V2_SZ ); + + fd_stake_state_v2_t state[1]; + fd_stake_state_v2_new_disc( state, fd_stake_state_v2_enum_stake ); + state->inner.stake.meta = (fd_stake_meta_t){ + .rent_exempt_reserve = 2282880UL, + .authorized = { .staker = authority_key, .withdrawer = authority_key } + }; + state->inner.stake.stake = (fd_stake_t){ + .delegation = (fd_delegation_t){ + .voter_pubkey = validator_key, .stake = TEST_STAKE_AMOUNT, + .activation_epoch = 0UL, .deactivation_epoch = ULONG_MAX, .warmup_cooldown_rate = 0.25 + }, + .credits_observed = 0UL + }; + + fd_bincode_encode_ctx_t encode = { .data = stake_data, .dataend = stake_data + FD_STAKE_STATE_V2_SZ }; + FD_TEST( fd_stake_state_v2_encode( state, &encode ) == FD_BINCODE_SUCCESS ); + + fd_pubkey_t stake_program = fd_solana_stake_program_id; + create_account_raw( env->accdb, &env->xid, &staker_key, TEST_STAKE_AMOUNT + 2282880UL, FD_STAKE_STATE_V2_SZ, stake_data, &stake_program ); + fd_wksp_free_laddr( stake_data ); + + fd_stake_delegations_t * delegations = fd_bank_stake_delegations_delta_locking_modify( env->bank ); + fd_stake_delegations_update( delegations, &staker_key, &validator_key, TEST_STAKE_AMOUNT, 0UL, ULONG_MAX, 0UL, 0.25 ); + fd_bank_stake_delegations_delta_end_locking_modify( env->bank ); +} + +static void +set_commission_prev_bps( test_env_t * env, ushort commission_bps ) { + fd_vote_states_t * vs = fd_bank_vote_states_prev_modify( env->bank ); + fd_vote_state_ele_t * ele = fd_vote_states_update( vs, &validator_key ); + ele->node_account = authority_key; + ele->commission_bps = commission_bps; + ele->stake = TEST_LAMPORTS; +} + +static void +set_commission_prev_prev_bps( test_env_t * env, ushort commission_bps ) { + fd_vote_states_t * vs = fd_bank_vote_states_prev_prev_modify( env->bank ); + fd_vote_state_ele_t * ele = fd_vote_states_update( vs, &validator_key ); + ele->node_account = authority_key; + ele->commission_bps = commission_bps; + ele->stake = TEST_LAMPORTS; +} + +static ushort +get_vote_commission_bps_v4( test_env_t * env ) { + fd_accdb_ro_t ro[1]; + FD_TEST( fd_accdb_open_ro( env->accdb, ro, &env->xid, &validator_key ) ); + uchar const * data = fd_accdb_ref_data_const( ro ); + ulong dlen = fd_accdb_ref_data_sz( ro ); + + fd_bincode_decode_ctx_t decode = { .data = data, .dataend = data + dlen }; + ulong total_sz = 0; + FD_TEST( fd_vote_state_versioned_decode_footprint( &decode, &total_sz ) == 0 ); + + uchar * mem = fd_wksp_alloc_laddr( env->wksp, 8UL, total_sz, env->tag ); + decode.data = data; + fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( mem, &decode ); + + ushort commission_bps = 0; + if( vsv->discriminant == fd_vote_state_versioned_enum_v4 ) + commission_bps = vsv->inner.v4.inflation_rewards_commission_bps; + else if( vsv->discriminant == fd_vote_state_versioned_enum_v3 ) + commission_bps = (ushort)(vsv->inner.v3.commission * 100U); + + fd_wksp_free_laddr( mem ); + fd_accdb_close_ro( env->accdb, ro ); + return commission_bps; +} + +static uchar +get_vote_commission_v3( test_env_t * env ) { + fd_accdb_ro_t ro[1]; + FD_TEST( fd_accdb_open_ro( env->accdb, ro, &env->xid, &validator_key ) ); + uchar const * data = fd_accdb_ref_data_const( ro ); + ulong dlen = fd_accdb_ref_data_sz( ro ); + + fd_bincode_decode_ctx_t decode = { .data = data, .dataend = data + dlen }; + ulong total_sz = 0; + FD_TEST( fd_vote_state_versioned_decode_footprint( &decode, &total_sz ) == 0 ); + + uchar * mem = fd_wksp_alloc_laddr( env->wksp, 8UL, total_sz, env->tag ); + decode.data = data; + fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( mem, &decode ); + + uchar commission = vsv->inner.v3.commission; + fd_wksp_free_laddr( mem ); + fd_accdb_close_ro( env->accdb, ro ); + return commission; +} + +static ulong +get_balance( test_env_t * env, fd_pubkey_t const * pubkey ) { + fd_accdb_ro_t ro[1]; + if( !fd_accdb_open_ro( env->accdb, ro, &env->xid, pubkey ) ) return 0UL; + ulong balance = fd_accdb_ref_lamports( ro ); + fd_accdb_close_ro( env->accdb, ro ); + return balance; +} + +static void +setup_slot( test_env_t * env, ulong slot ) { + fd_funk_txn_xid_t parent_xid = env->xid; + fd_funk_txn_xid_t new_xid = { .ul = { slot, env->bank->data->idx } }; + fd_accdb_attach_child( env->accdb_admin, &parent_xid, &new_xid ); + env->xid = new_xid; + fd_bank_slot_set( env->bank, slot ); + + fd_epoch_schedule_t const * es = fd_bank_epoch_schedule_query( env->bank ); + ulong epoch = fd_slot_to_epoch( es, slot, NULL ); + fd_sol_sysvar_clock_t clock = { .slot = slot, .epoch = epoch }; + fd_sysvar_clock_write( env->bank, env->accdb, &env->xid, NULL, &clock ); + FD_TEST( fd_sysvar_cache_restore( env->bank, env->accdb, &env->xid ) ); +} + +static int +process_slot( test_env_t * env, ulong slot ) { + fd_bank_t * parent = env->bank; + ulong parent_slot = fd_bank_slot_get( parent ); + ulong parent_idx = parent->data->idx; + FD_TEST( parent->data->flags & FD_BANK_FLAGS_FROZEN ); + + ulong new_idx = fd_banks_new_bank( env->bank, env->banks, parent_idx, 0L )->data->idx; + fd_bank_t * new_bank = fd_banks_clone_from_parent( env->bank, env->banks, new_idx ); + fd_bank_slot_set( new_bank, slot ); + fd_bank_parent_slot_set( new_bank, parent_slot ); + + fd_epoch_schedule_t const * es = fd_bank_epoch_schedule_query( new_bank ); + fd_bank_epoch_set( new_bank, fd_slot_to_epoch( es, slot, NULL ) ); + + fd_funk_txn_xid_t xid = { .ul = { slot, new_idx } }; + fd_funk_txn_xid_t parent_xid = { .ul = { parent_slot, parent_idx } }; + fd_accdb_attach_child( env->accdb_admin, &parent_xid, &xid ); + env->xid = xid; + + int is_epoch_boundary = 0; + fd_runtime_block_execute_prepare( env->banks, env->bank, env->accdb, env->runtime_stack, NULL, &is_epoch_boundary ); + + fd_banks_mark_bank_frozen( env->banks, new_bank ); + fd_accdb_advance_root( env->accdb_admin, &xid ); + fd_banks_advance_root( env->banks, new_idx ); + env->xid_is_rooted = 1; + + return is_epoch_boundary; +} + +static void +advance_to_epoch( test_env_t * env, ulong target_epoch ) { + ulong current = fd_bank_slot_get( env->bank ); + ulong target = target_epoch * TEST_SLOTS_PER_EPOCH; + for( ulong slot = current + 1; slot <= target; slot++ ) { + int boundary = process_slot( env, slot ); + if( slot == target ) FD_TEST( boundary ); + } +} + +struct txn_instr { uchar program_id_idx; uchar * account_idxs; ushort account_idxs_cnt; uchar * data; ushort data_sz; }; +typedef struct txn_instr txn_instr_t; + +static uchar * txn_add( uchar * cur, void const * data, ulong sz ) { + fd_memcpy( cur, data, sz ); + return cur + sz; +} + +static uchar * txn_add_u8( uchar * cur, uchar val ) { + *cur = val; + return cur + 1; +} + +static uchar * txn_add_cu16( uchar * cur, ushort val ) { + uchar buf[3]; + fd_bincode_encode_ctx_t ctx = { .data = buf, .dataend = buf + 3 }; + FD_TEST( fd_bincode_compact_u16_encode( &val, &ctx ) == FD_BINCODE_SUCCESS ); + return txn_add( cur, buf, (ulong)((uchar *)ctx.data - buf) ); +} + +static ulong +txn_serialize( uchar * buf, ulong num_signers, ulong num_readonly_unsigned, + ulong num_keys, fd_pubkey_t * keys, txn_instr_t * instrs, ushort instr_cnt ) { + uchar * cur = buf; + fd_signature_t sig = {0}; + fd_hash_t blockhash = {0}; + fd_memset( blockhash.uc, 0xAB, FD_HASH_FOOTPRINT ); + + cur = txn_add_u8( cur, 1 ); + cur = txn_add( cur, &sig, FD_TXN_SIGNATURE_SZ ); + cur = txn_add_u8( cur, (uchar)num_signers ); + cur = txn_add_u8( cur, 0 ); + cur = txn_add_u8( cur, (uchar)num_readonly_unsigned ); + cur = txn_add_cu16( cur, (ushort)num_keys ); + for( ushort i = 0; i < num_keys; i++ ) + cur = txn_add( cur, &keys[i], sizeof(fd_pubkey_t) ); + cur = txn_add( cur, &blockhash, sizeof(fd_hash_t) ); + cur = txn_add_cu16( cur, instr_cnt ); + for( ushort i = 0; i < instr_cnt; i++ ) { + cur = txn_add_u8( cur, instrs[i].program_id_idx ); + cur = txn_add_cu16( cur, instrs[i].account_idxs_cnt ); + cur = txn_add( cur, instrs[i].account_idxs, instrs[i].account_idxs_cnt ); + cur = txn_add_cu16( cur, instrs[i].data_sz ); + cur = txn_add( cur, instrs[i].data, instrs[i].data_sz ); + } + return (ulong)(cur - buf); +} + +static int +execute_update_commission( test_env_t * env, uchar new_commission ) { + uchar instr_data[5]; + FD_STORE( uint, instr_data, VOTE_IX_UPDATE_COMMISSION ); + instr_data[4] = new_commission; + + fd_pubkey_t vote_program = fd_solana_vote_program_id; + fd_pubkey_t keys[3] = { authority_key, validator_key, vote_program }; + uchar account_idxs[2] = { 1, 0 }; + txn_instr_t instrs[1] = {{ .program_id_idx = 2, .account_idxs = account_idxs, .account_idxs_cnt = 2, .data = instr_data, .data_sz = 5 }}; + + fd_txn_p_t txn_p = {0}; + ulong sz = txn_serialize( txn_p.payload, 1, 1, 3, keys, instrs, 1 ); + FD_TEST( fd_txn_parse( txn_p.payload, sz, TXN( &txn_p ), NULL ) ); + + env->txn_in.txn = &txn_p; + env->txn_in.bundle.is_bundle = 0; + fd_runtime_prepare_and_execute_txn( env->runtime, env->bank, &env->txn_in, &env->txn_out[0] ); + env->txn_needs_cancel = 1; + + int success = env->txn_out[0].err.is_committable && env->txn_out[0].err.txn_err == FD_RUNTIME_EXECUTE_SUCCESS; + if( success ) { + fd_runtime_commit_txn( env->runtime, env->bank, &env->txn_out[0] ); + env->txn_needs_cancel = 0; + } + return success; +} + +static int +execute_update_commission_bps( test_env_t * env, ushort commission_bps, uint kind ) { + uchar instr_data[10]; + FD_STORE( uint, instr_data, VOTE_IX_UPDATE_COMMISSION_BPS ); + FD_STORE( ushort, instr_data + 4, commission_bps ); + FD_STORE( uint, instr_data + 6, kind ); + + fd_pubkey_t vote_program = fd_solana_vote_program_id; + fd_pubkey_t keys[3] = { authority_key, validator_key, vote_program }; + uchar account_idxs[2] = { 1, 0 }; + txn_instr_t instrs[1] = {{ .program_id_idx = 2, .account_idxs = account_idxs, .account_idxs_cnt = 2, .data = instr_data, .data_sz = 10 }}; + + fd_txn_p_t txn_p = {0}; + ulong sz = txn_serialize( txn_p.payload, 1, 1, 3, keys, instrs, 1 ); + FD_TEST( fd_txn_parse( txn_p.payload, sz, TXN( &txn_p ), NULL ) ); + + env->txn_in.txn = &txn_p; + env->txn_in.bundle.is_bundle = 0; + fd_runtime_prepare_and_execute_txn( env->runtime, env->bank, &env->txn_in, &env->txn_out[0] ); + env->txn_needs_cancel = 1; + + int success = env->txn_out[0].err.is_committable && env->txn_out[0].err.txn_err == FD_RUNTIME_EXECUTE_SUCCESS; + if( success ) { + fd_runtime_commit_txn( env->runtime, env->bank, &env->txn_out[0] ); + env->txn_needs_cancel = 0; + } + return success; +} + +typedef struct { + char const * name; + int delay_commission; + int commission_rate_bps; + int vote_state_v4; + int use_v3_account; + ushort commission_bps; + uint kind; + int expect_success; + ushort expected_commission_bps; + int expected_err; +} vote_program_test_t; + +static void +run_vote_program_test( fd_wksp_t * wksp, vote_program_test_t const * t ) { + test_env_t env[1]; + test_env_create_with_features( env, wksp, t->delay_commission, t->commission_rate_bps, t->vote_state_v4 ); + setup_slot( env, 1 ); + + create_account_raw( env->accdb, &env->xid, &authority_key, TEST_LAMPORTS, 0, NULL, NULL ); + if( t->use_v3_account ) create_vote_account_v3( env, 10, ULONG_MAX ); + else create_vote_account_v4( env, 1000, ULONG_MAX ); + + int success = execute_update_commission_bps( env, t->commission_bps, t->kind ); + + if( t->expect_success ) { + FD_TEST( success ); + FD_TEST( get_vote_commission_bps_v4( env ) == t->expected_commission_bps ); + } else { + FD_TEST( !success ); + FD_TEST( env->txn_out[0].err.exec_err == t->expected_err ); + } + + test_env_destroy( env ); +} + +/* Instruction succeeds with V4 account and all features enabled */ +static void +test_update_commission_bps_success( fd_wksp_t * wksp ) { + ushort values[] = { 0, 1, 5000, 9999, 10000, 15000 }; + for( ulong i = 0; i < sizeof(values)/sizeof(values[0]); i++ ) { + vote_program_test_t t = { + .delay_commission = 1, + .commission_rate_bps = 1, + .vote_state_v4 = 1, + .use_v3_account = 0, + .commission_bps = values[i], + .kind = fd_commission_kind_enum_inflation_rewards, + .expect_success = 1, + .expected_commission_bps = values[i], + }; + run_vote_program_test( wksp, &t ); + } +} + +/* Instruction fails when vote_state_v4 feature disabled (uses V3 target) */ +static void +test_update_commission_bps_v3_fails( fd_wksp_t * wksp ) { + vote_program_test_t t = { + .delay_commission = 1, + .commission_rate_bps = 1, + .vote_state_v4 = 0, + .use_v3_account = 1, + .commission_bps = 2500, + .kind = fd_commission_kind_enum_inflation_rewards, + .expect_success = 0, + .expected_err = FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, + }; + run_vote_program_test( wksp, &t ); +} + +/* Instruction fails when commission_rate_in_basis_points feature disabled */ +static void +test_update_commission_bps_missing_feature( fd_wksp_t * wksp ) { + vote_program_test_t cases[] = { + { .delay_commission = 1, .commission_rate_bps = 0, .vote_state_v4 = 1 }, + { .delay_commission = 0, .commission_rate_bps = 1, .vote_state_v4 = 1 }, + }; + for( ulong i = 0; i < sizeof(cases)/sizeof(cases[0]); i++ ) { + vote_program_test_t t = cases[i]; + t.use_v3_account = 0; + t.commission_bps = 2500; + t.kind = fd_commission_kind_enum_inflation_rewards; + t.expect_success = 0; + t.expected_err = FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; + run_vote_program_test( wksp, &t ); + } +} + +/* Instruction fails with CommissionKind::BlockRevenue (not yet supported) */ +static void +test_update_commission_bps_block_revenue_fails( fd_wksp_t * wksp ) { + vote_program_test_t t = { + .delay_commission = 1, + .commission_rate_bps = 1, + .vote_state_v4 = 1, + .use_v3_account = 0, + .commission_bps = 2500, + .kind = fd_commission_kind_enum_block_revenue, + .expect_success = 0, + .expected_err = FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA, + }; + run_vote_program_test( wksp, &t ); +} + +/* Legacy update_commission still works with V3 accounts */ +static void +test_legacy_update_commission( fd_wksp_t * wksp ) { + test_env_t env[1]; + test_env_create_with_features( env, wksp, 1, 1, 0 ); + setup_slot( env, 1 ); + + create_account_raw( env->accdb, &env->xid, &authority_key, TEST_LAMPORTS, 0, NULL, NULL ); + create_vote_account_v3( env, 5, ULONG_MAX ); + + FD_TEST( get_vote_commission_v3( env ) == 5 ); + FD_TEST( execute_update_commission( env, 10 ) ); + FD_TEST( get_vote_commission_v3( env ) == 10 ); + + test_env_destroy( env ); +} + +typedef struct { + char const * name; + ushort commission_bps; + ushort commission_prev_bps; + ushort commission_prev_prev_bps; + ulong expected_vote_balance; + ulong expected_stake_rewards; +} rewards_test_t; + +static void +run_rewards_test( fd_wksp_t * wksp, rewards_test_t const * t ) { + test_env_t env[1]; + test_env_create_with_features( env, wksp, 1, 1, 1 ); + + create_account_raw( env->accdb, &env->xid, &authority_key, TEST_LAMPORTS, 0, NULL, NULL ); + create_vote_account_v4( env, t->commission_bps, 1UL ); + create_stake_account( env ); + + set_commission_prev_bps( env, t->commission_prev_bps ); + set_commission_prev_prev_bps( env, t->commission_prev_prev_bps ); + + fd_banks_mark_bank_frozen( env->banks, env->bank ); + fd_accdb_advance_root( env->accdb_admin, &env->xid ); + + advance_to_epoch( env, 2UL ); + + ulong final_vote = get_balance( env, &validator_key ); + FD_TEST( final_vote == t->expected_vote_balance ); + + fd_epoch_rewards_t const * er = fd_bank_epoch_rewards_query( env->bank ); + FD_TEST( er ); + ulong stake_diff = (er->total_stake_rewards > t->expected_stake_rewards) + ? (er->total_stake_rewards - t->expected_stake_rewards) + : (t->expected_stake_rewards - er->total_stake_rewards); + FD_TEST( stake_diff <= 1UL ); + + test_env_destroy( env ); +} + +/* Rewards split correctly at 12.34% (1234 bps) */ +static void +test_rewards_precision( fd_wksp_t * wksp ) { + rewards_test_t t = { + .commission_bps = 9999, + .commission_prev_bps = 1234, /* used for rewards calculation */ + .commission_prev_prev_bps = 5000, + .expected_vote_balance = 10123UL, + .expected_stake_rewards = 877UL, + }; + run_rewards_test( wksp, &t ); +} + +/* Commission > 10000 bps capped to 100% during rewards calculation */ +static void +test_rewards_capping( fd_wksp_t * wksp ) { + rewards_test_t t = { + .commission_bps = 1000, + .commission_prev_bps = 15000, /* used for rewards calculation */ + .commission_prev_prev_bps = 2000, + .expected_vote_balance = 11000UL, + .expected_stake_rewards = 0UL, + }; + run_rewards_test( wksp, &t ); +} + +int +main( int argc, char ** argv ) { + fd_boot( &argc, &argv ); + + ulong cpu_idx = fd_tile_cpu_id( fd_tile_idx() ); + if( cpu_idx > fd_shmem_cpu_cnt() ) cpu_idx = 0UL; + + char const * _page_sz = fd_env_strip_cmdline_cstr( &argc, &argv, "--page-sz", NULL, "normal" ); + ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1572864UL ); + ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( cpu_idx ) ); + + ulong page_sz = fd_cstr_to_shmem_page_sz( _page_sz ); + if( FD_UNLIKELY( !page_sz ) ) FD_LOG_ERR(( "unsupported --page-sz" )); + + fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); + FD_TEST( wksp ); + + /* Vote program instruction tests */ + test_update_commission_bps_success( wksp ); + test_update_commission_bps_v3_fails( wksp ); + test_update_commission_bps_missing_feature( wksp ); + test_update_commission_bps_block_revenue_fails( wksp ); + test_legacy_update_commission( wksp ); + + /* Rewards calculation tests */ + test_rewards_precision( wksp ); + test_rewards_capping( wksp ); + + fd_wksp_delete_anonymous( wksp ); + FD_LOG_NOTICE(( "pass" )); + fd_halt(); + return 0; +} diff --git a/src/flamenco/runtime/tests/test_delay_commission_updates.c b/src/flamenco/runtime/tests/test_delay_commission_updates.c index 76c0dbce451..be383ae901e 100644 --- a/src/flamenco/runtime/tests/test_delay_commission_updates.c +++ b/src/flamenco/runtime/tests/test_delay_commission_updates.c @@ -200,7 +200,7 @@ test_env_destroy( test_env_t * env ) { ============================================================================ */ static void -create_vote_account( test_env_t * env, uchar commission, ulong epoch_credits_epoch ) { +create_vote_account( test_env_t * env, ushort commission_bps, ulong epoch_credits_epoch ) { uchar * vote_state_data = fd_wksp_alloc_laddr( env->wksp, 8UL, FD_VOTE_STATE_V3_SZ, env->tag ); uchar * pool_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 1024UL, env->tag ); uchar * treap_mem = fd_wksp_alloc_laddr( env->wksp, 16UL, 1024UL, env->tag ); @@ -212,7 +212,7 @@ create_vote_account( test_env_t * env, uchar commission, ulong epoch_credits_epo fd_vote_state_v3_t * vs = &vsv->inner.v3; vs->node_pubkey = authority_key; vs->authorized_withdrawer = authority_key; - vs->commission = commission; + vs->commission = (uchar)(commission_bps / 100U); vs->authorized_voters.pool = fd_vote_authorized_voters_pool_join( fd_vote_authorized_voters_pool_new( pool_mem, 1UL ) ); vs->authorized_voters.treap = fd_vote_authorized_voters_treap_join( fd_vote_authorized_voters_treap_new( treap_mem, 1UL ) ); @@ -236,9 +236,9 @@ create_vote_account( test_env_t * env, uchar commission, ulong epoch_credits_epo fd_vote_states_t * vote_states = fd_bank_vote_states_locking_modify( env->bank ); fd_vote_state_ele_t * ele = fd_vote_states_update( vote_states, &validator_key ); - ele->node_account = authority_key; - ele->commission = commission; - ele->stake = TEST_LAMPORTS; + ele->node_account = authority_key; + ele->commission_bps = commission_bps; + ele->stake = TEST_LAMPORTS; fd_bank_vote_states_end_locking_modify( env->bank ); fd_wksp_free_laddr( epoch_cred_mem ); @@ -279,29 +279,29 @@ create_stake_account( test_env_t * env ) { } static void -set_commission_prev( test_env_t * env, uchar commission ) { +set_commission_prev( test_env_t * env, ushort commission_bps ) { fd_vote_states_t * vs = fd_bank_vote_states_prev_modify( env->bank ); fd_vote_state_ele_t * ele = fd_vote_states_update( vs, &validator_key ); - ele->node_account = authority_key; - ele->commission = commission; - ele->stake = TEST_LAMPORTS; + ele->node_account = authority_key; + ele->commission_bps = commission_bps; + ele->stake = TEST_LAMPORTS; } static void -set_commission_prev_prev( test_env_t * env, uchar commission ) { +set_commission_prev_prev( test_env_t * env, ushort commission_bps ) { fd_vote_states_t * vs = fd_bank_vote_states_prev_prev_modify( env->bank ); fd_vote_state_ele_t * ele = fd_vote_states_update( vs, &validator_key ); - ele->node_account = authority_key; - ele->commission = commission; - ele->stake = TEST_LAMPORTS; + ele->node_account = authority_key; + ele->commission_bps = commission_bps; + ele->stake = TEST_LAMPORTS; } /* ============================================================================ Account Query Helpers ============================================================================ */ -static uchar -get_vote_commission( test_env_t * env ) { +static ushort +get_vote_commission_bps( test_env_t * env ) { fd_accdb_ro_t ro[1]; FD_TEST( fd_accdb_open_ro( env->accdb, ro, &env->xid, &validator_key ) ); uchar const * data = fd_accdb_ref_data_const( ro ); @@ -315,15 +315,15 @@ get_vote_commission( test_env_t * env ) { decode.data = data; fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( mem, &decode ); - uchar commission = 0; + ushort commission_bps = 0; if( vsv->discriminant == fd_vote_state_versioned_enum_v3 ) - commission = vsv->inner.v3.commission; + commission_bps = (ushort)(vsv->inner.v3.commission * 100U); else if( vsv->discriminant == fd_vote_state_versioned_enum_v1_14_11 ) - commission = vsv->inner.v1_14_11.commission; + commission_bps = (ushort)(vsv->inner.v1_14_11.commission * 100U); fd_wksp_free_laddr( mem ); fd_accdb_close_ro( env->accdb, ro ); - return commission; + return commission_bps; } static ulong @@ -511,20 +511,20 @@ static void test_vote_commission_update( fd_wksp_t * wksp, int feature_enabled, ulong slot, - uchar old_commission, - uchar new_commission, + ushort old_commission_bps, + ushort new_commission_bps, int expect_success ) { test_env_t env[1]; test_env_create( env, wksp, feature_enabled ); setup_slot( env, slot ); create_account_raw( env->accdb, &env->xid, &authority_key, TEST_LAMPORTS, 0, NULL, NULL ); - create_vote_account( env, old_commission, ULONG_MAX ); + create_vote_account( env, old_commission_bps, ULONG_MAX ); - FD_TEST( get_vote_commission( env ) == old_commission ); - int success = execute_update_commission( env, new_commission ); + FD_TEST( get_vote_commission_bps( env ) == old_commission_bps ); + int success = execute_update_commission( env, (uchar)(new_commission_bps / 100U) ); FD_TEST( success == expect_success ); - FD_TEST( get_vote_commission( env ) == (expect_success ? new_commission : old_commission) ); + FD_TEST( get_vote_commission_bps( env ) == (expect_success ? new_commission_bps : old_commission_bps) ); if( !expect_success ) { FD_TEST( env->txn_out[0].err.custom_err == FD_VOTE_ERR_COMMISSION_UPDATE_TOO_LATE ); @@ -533,12 +533,12 @@ test_vote_commission_update( fd_wksp_t * wksp, test_env_destroy( env ); } typedef struct { - int feature_enabled; - uchar current_commission; - uchar prev_commission; /* UCHAR_MAX = don't set */ - uchar prev_prev_commission; /* UCHAR_MAX = don't set */ - ulong expected_vote_balance; - ulong expected_stake_rewards; + int feature_enabled; + ushort current_commission_bps; + ushort prev_commission_bps; /* USHORT_MAX = don't set */ + ushort prev_prev_commission_bps; /* USHORT_MAX = don't set */ + ulong expected_vote_balance; + ulong expected_stake_rewards; } rewards_test_params_t; static void @@ -547,11 +547,11 @@ test_rewards( fd_wksp_t * wksp, rewards_test_params_t const * p ) { test_env_create( env, wksp, p->feature_enabled ); create_account_raw( env->accdb, &env->xid, &authority_key, TEST_LAMPORTS, 0, NULL, NULL ); - create_vote_account( env, p->current_commission, 1UL ); + create_vote_account( env, p->current_commission_bps, 1UL ); create_stake_account( env ); - if( p->prev_commission != UCHAR_MAX ) set_commission_prev( env, p->prev_commission ); - if( p->prev_prev_commission != UCHAR_MAX ) set_commission_prev_prev( env, p->prev_prev_commission ); + if( p->prev_commission_bps != USHORT_MAX ) set_commission_prev( env, p->prev_commission_bps ); + if( p->prev_prev_commission_bps != USHORT_MAX ) set_commission_prev_prev( env, p->prev_prev_commission_bps ); ulong initial_vote = get_balance( env, &validator_key ); FD_TEST( initial_vote == TEST_LAMPORTS ); @@ -594,57 +594,57 @@ main( int argc, char ** argv ) { /* Vote Program Tests: testing setting the commission rate */ FD_LOG_NOTICE(( "Vote Program: delay_commission_updates OFF, first half of epoch" )); - test_vote_commission_update( wksp, 0, 1, 5, 10, 1 ); + test_vote_commission_update( wksp, 0, 1, 500, 1000, 1 ); FD_LOG_NOTICE(( "Vote Program: delay_commission_updates OFF, second half of epoch" )); - test_vote_commission_update( wksp, 0, 3, 5, 10, 0 ); + test_vote_commission_update( wksp, 0, 3, 500, 1000, 0 ); FD_LOG_NOTICE(( "Vote Program: delay_commission_updates OFF, second half of epoch" )); - test_vote_commission_update( wksp, 0, 3, 10, 5, 1 ); + test_vote_commission_update( wksp, 0, 3, 1000, 500, 1 ); FD_LOG_NOTICE(( "Vote Program: delay_commission_updates ON, second half of epoch" )); - test_vote_commission_update( wksp, 1, 3, 5, 10, 1 ); + test_vote_commission_update( wksp, 1, 3, 500, 1000, 1 ); /* Rewards Tests: testing the delayed commission rate behavior */ FD_LOG_NOTICE(( "Rewards: delay_commission_updates OFF, 10%% commission: vote=100, stake=900" )); test_rewards( wksp, &(rewards_test_params_t){ - .feature_enabled = 0, - .current_commission = 10, - .prev_commission = 5, - .prev_prev_commission = 2, - .expected_vote_balance = 10100UL, /* 10000 + 100 */ - .expected_stake_rewards = 900UL, + .feature_enabled = 0, + .current_commission_bps = 1000, + .prev_commission_bps = 500, + .prev_prev_commission_bps = 200, + .expected_vote_balance = 10100UL, /* 10000 + 100 */ + .expected_stake_rewards = 900UL, }); FD_LOG_NOTICE(( "Rewards: delay_commission_updates ON, uses prev (20%%): vote=200, stake=800" )); test_rewards( wksp, &(rewards_test_params_t){ - .feature_enabled = 1, - .current_commission = 10, - .prev_commission = 20, - .prev_prev_commission = 5, - .expected_vote_balance = 10200UL, /* 10000 + 200 */ - .expected_stake_rewards = 800UL, + .feature_enabled = 1, + .current_commission_bps = 1000, + .prev_commission_bps = 2000, + .prev_prev_commission_bps = 500, + .expected_vote_balance = 10200UL, /* 10000 + 200 */ + .expected_stake_rewards = 800UL, }); FD_LOG_NOTICE(( "Rewards: delay_commission_updates ON, no prev_prev, falls back to prev (20%%): vote=200, stake=800" )); test_rewards( wksp, &(rewards_test_params_t){ - .feature_enabled = 1, - .current_commission = 10, - .prev_commission = 20, - .prev_prev_commission = UCHAR_MAX, - .expected_vote_balance = 10200UL, /* 10000 + 200 */ - .expected_stake_rewards = 800UL, + .feature_enabled = 1, + .current_commission_bps = 1000, + .prev_commission_bps = 2000, + .prev_prev_commission_bps = USHORT_MAX, + .expected_vote_balance = 10200UL, /* 10000 + 200 */ + .expected_stake_rewards = 800UL, }); FD_LOG_NOTICE(( "Rewards: delay_commission_updates ON, no prev or prev_prev, uses current (10%%): vote=100, stake=900" )); test_rewards( wksp, &(rewards_test_params_t){ - .feature_enabled = 1, - .current_commission = 10, - .prev_commission = UCHAR_MAX, - .prev_prev_commission = UCHAR_MAX, - .expected_vote_balance = 10100UL, /* 10000 + 100 */ - .expected_stake_rewards = 900UL, + .feature_enabled = 1, + .current_commission_bps = 1000, + .prev_commission_bps = USHORT_MAX, + .prev_prev_commission_bps = USHORT_MAX, + .expected_vote_balance = 10100UL, /* 10000 + 100 */ + .expected_stake_rewards = 900UL, }); fd_wksp_delete_anonymous( wksp ); diff --git a/src/flamenco/stakes/fd_vote_states.c b/src/flamenco/stakes/fd_vote_states.c index bd6d06f062f..54cb143207a 100644 --- a/src/flamenco/stakes/fd_vote_states.c +++ b/src/flamenco/stakes/fd_vote_states.c @@ -277,34 +277,34 @@ fd_vote_states_update_from_account( fd_vote_states_t * vote_states, } fd_pubkey_t node_account; - uchar commission; + ushort commission_bps; long last_vote_timestamp; ulong last_vote_slot; switch( vsv->discriminant ) { case fd_vote_state_versioned_enum_v0_23_5: node_account = vsv->inner.v0_23_5.node_pubkey; - commission = vsv->inner.v0_23_5.commission; + commission_bps = (ushort)( vsv->inner.v0_23_5.commission * 100 ); last_vote_timestamp = vsv->inner.v0_23_5.last_timestamp.timestamp; last_vote_slot = vsv->inner.v0_23_5.last_timestamp.slot; break; case fd_vote_state_versioned_enum_v1_14_11: node_account = vsv->inner.v1_14_11.node_pubkey; - commission = vsv->inner.v1_14_11.commission; + commission_bps = (ushort)( vsv->inner.v1_14_11.commission * 100 ); last_vote_timestamp = vsv->inner.v1_14_11.last_timestamp.timestamp; last_vote_slot = vsv->inner.v1_14_11.last_timestamp.slot; break; case fd_vote_state_versioned_enum_v3: node_account = vsv->inner.v3.node_pubkey; - commission = vsv->inner.v3.commission; + commission_bps = (ushort)( vsv->inner.v3.commission * 100 ); last_vote_timestamp = vsv->inner.v3.last_timestamp.timestamp; last_vote_slot = vsv->inner.v3.last_timestamp.slot; break; case fd_vote_state_versioned_enum_v4: - /* Commission calculation is deliberate according to this: - https://github.com/anza-xyz/agave/blob/v3.1.1/vote/src/vote_state_view/field_frames.rs#L353 */ + /* For V4 accounts, always read inflation_rewards_commission_bps directly. + This is always in basis points (0-10000). */ node_account = vsv->inner.v4.node_pubkey; - commission = (uchar)fd_ushort_min( vsv->inner.v4.inflation_rewards_commission_bps/100, UCHAR_MAX ); + commission_bps = vsv->inner.v4.inflation_rewards_commission_bps; last_vote_timestamp = vsv->inner.v4.last_timestamp.timestamp; last_vote_slot = vsv->inner.v4.last_timestamp.slot; break; @@ -315,7 +315,7 @@ fd_vote_states_update_from_account( fd_vote_states_t * vote_states, fd_vote_state_ele_t * vote_state = fd_vote_states_update( vote_states, vote_account ); vote_state->node_account = node_account; - vote_state->commission = commission; + vote_state->commission_bps = commission_bps; vote_state->last_vote_timestamp = last_vote_timestamp; vote_state->last_vote_slot = last_vote_slot; diff --git a/src/flamenco/stakes/fd_vote_states.h b/src/flamenco/stakes/fd_vote_states.h index 8b59d28303d..fa5abf8c554 100644 --- a/src/flamenco/stakes/fd_vote_states.h +++ b/src/flamenco/stakes/fd_vote_states.h @@ -121,7 +121,7 @@ struct fd_vote_state_ele { fd_pubkey_t node_account; ulong last_vote_slot; long last_vote_timestamp; - uchar commission; + ushort commission_bps; }; typedef struct fd_vote_state_ele fd_vote_state_ele_t; diff --git a/src/flamenco/stakes/test_vote_states.c b/src/flamenco/stakes/test_vote_states.c index 6deb798e68c..1bbc755500a 100644 --- a/src/flamenco/stakes/test_vote_states.c +++ b/src/flamenco/stakes/test_vote_states.c @@ -52,7 +52,7 @@ int main( int argc, char ** argv ) { fd_vote_state_ele_t * vote_state_ele = fd_vote_states_update( vote_states, &vote_account_0 ); vote_state_ele->node_account = node_account_0; - vote_state_ele->commission = 50; + vote_state_ele->commission_bps = 5000; /* 50% in basis points */ vote_state_ele->last_vote_timestamp = 100L; vote_state_ele->last_vote_slot = 1000UL; vote_state_ele->stake = 10UL; @@ -61,7 +61,7 @@ int main( int argc, char ** argv ) { vote_state_ele = fd_vote_states_update( vote_states, &vote_account_1 ); vote_state_ele->node_account = node_account_0; - vote_state_ele->commission = 51; + vote_state_ele->commission_bps = 5100; /* 51% in basis points */ vote_state_ele->last_vote_timestamp = 100L; vote_state_ele->last_vote_slot = 10000UL; @@ -73,7 +73,7 @@ int main( int argc, char ** argv ) { FD_TEST( memcmp( &vote_state_ele->node_account, &node_account_0, sizeof(fd_pubkey_t) ) == 0 ); FD_TEST( vote_state_ele->last_vote_slot == 1000UL ); FD_TEST( vote_state_ele->last_vote_timestamp == 100L ); - FD_TEST( vote_state_ele->commission == 50 ); + FD_TEST( vote_state_ele->commission_bps == 5000 ); FD_TEST( vote_state_ele->stake == 10UL ); fd_vote_state_ele_t * vote_state_ele_1 = fd_vote_states_query( vote_states, &vote_account_1 ); @@ -82,7 +82,7 @@ int main( int argc, char ** argv ) { FD_TEST( memcmp( &vote_state_ele_1->node_account, &node_account_0, sizeof(fd_pubkey_t) ) == 0 ); FD_TEST( vote_state_ele_1->last_vote_slot == 10000UL ); FD_TEST( vote_state_ele_1->last_vote_timestamp == 100L ); - FD_TEST( vote_state_ele_1->commission == 51 ); + FD_TEST( vote_state_ele_1->commission_bps == 5100 ); fd_vote_states_reset_stakes( vote_states ); diff --git a/src/flamenco/types/fd_types.c b/src/flamenco/types/fd_types.c index 92327b7309c..b54ba981d1a 100644 --- a/src/flamenco/types/fd_types.c +++ b/src/flamenco/types/fd_types.c @@ -4837,6 +4837,108 @@ ulong fd_vote_authorize_checked_with_seed_args_size( fd_vote_authorize_checked_w return size; } +FD_FN_PURE uchar fd_commission_kind_is_inflation_rewards(fd_commission_kind_t const * self) { + return self->discriminant == 0; +} +FD_FN_PURE uchar fd_commission_kind_is_block_revenue(fd_commission_kind_t const * self) { + return self->discriminant == 1; +} +int fd_commission_kind_inner_decode_footprint( uint discriminant, fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + int err; + switch (discriminant) { + case 0: { + return FD_BINCODE_SUCCESS; + } + case 1: { + return FD_BINCODE_SUCCESS; + } + default: return FD_BINCODE_ERR_ENCODING; + } +} +static int fd_commission_kind_decode_footprint_inner( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + if( ctx->data>=ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + uint discriminant = 0; + int err = fd_bincode_uint32_decode( &discriminant, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return fd_commission_kind_inner_decode_footprint( discriminant, ctx, total_sz ); +} +int fd_commission_kind_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + *total_sz += sizeof(fd_commission_kind_t); + void const * start_data = ctx->data; + int err = fd_commission_kind_decode_footprint_inner( ctx, total_sz ); + if( ctx->data>ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + ctx->data = start_data; + return err; +} +static void fd_commission_kind_decode_inner( void * struct_mem, void * * alloc_mem, fd_bincode_decode_ctx_t * ctx ) { + fd_commission_kind_t * self = (fd_commission_kind_t *)struct_mem; + fd_bincode_uint32_decode_unsafe( &self->discriminant, ctx ); +} +void * fd_commission_kind_decode( void * mem, fd_bincode_decode_ctx_t * ctx ) { + fd_commission_kind_t * self = (fd_commission_kind_t *)mem; + fd_commission_kind_new( self ); + void * alloc_region = (uchar *)mem + sizeof(fd_commission_kind_t); + void * * alloc_mem = &alloc_region; + fd_commission_kind_decode_inner( mem, alloc_mem, ctx ); + return self; +} + +ulong fd_commission_kind_size( fd_commission_kind_t const * self ) { + ulong size = 0; + size += sizeof(uint); + switch (self->discriminant) { + } + return size; +} + +int fd_commission_kind_encode( fd_commission_kind_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err = fd_bincode_uint32_encode( self->discriminant, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return err; +} + +int fd_update_commission_bps_encode( fd_update_commission_bps_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_bincode_uint16_encode( self->commission_bps, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_commission_kind_encode( &self->kind, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +static int fd_update_commission_bps_decode_footprint_inner( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + if( ctx->data>=ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + int err = 0; + err = fd_bincode_uint16_decode_footprint( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_commission_kind_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + return 0; +} +int fd_update_commission_bps_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { + *total_sz += sizeof(fd_update_commission_bps_t); + void const * start_data = ctx->data; + int err = fd_update_commission_bps_decode_footprint_inner( ctx, total_sz ); + if( ctx->data>ctx->dataend ) { return FD_BINCODE_ERR_OVERFLOW; }; + ctx->data = start_data; + return err; +} +static void fd_update_commission_bps_decode_inner( void * struct_mem, void * * alloc_mem, fd_bincode_decode_ctx_t * ctx ) { + fd_update_commission_bps_t * self = (fd_update_commission_bps_t *)struct_mem; + fd_bincode_uint16_decode_unsafe( &self->commission_bps, ctx ); + fd_commission_kind_decode_inner( &self->kind, alloc_mem, ctx ); +} +void * fd_update_commission_bps_decode( void * mem, fd_bincode_decode_ctx_t * ctx ) { + fd_update_commission_bps_t * self = (fd_update_commission_bps_t *)mem; + fd_update_commission_bps_new( self ); + void * alloc_region = (uchar *)mem + sizeof(fd_update_commission_bps_t); + void * * alloc_mem = &alloc_region; + fd_update_commission_bps_decode_inner( mem, alloc_mem, ctx ); + return self; +} +void fd_update_commission_bps_new(fd_update_commission_bps_t * self) { + fd_memset( self, 0, sizeof(fd_update_commission_bps_t) ); + fd_commission_kind_new( &self->kind ); +} FD_FN_PURE uchar fd_vote_instruction_is_initialize_account(fd_vote_instruction_t const * self) { return self->discriminant == 0; } @@ -4885,6 +4987,18 @@ FD_FN_PURE uchar fd_vote_instruction_is_tower_sync(fd_vote_instruction_t const * FD_FN_PURE uchar fd_vote_instruction_is_tower_sync_switch(fd_vote_instruction_t const * self) { return self->discriminant == 15; } +FD_FN_PURE uchar fd_vote_instruction_is_initialize_account_v2(fd_vote_instruction_t const * self) { + return self->discriminant == 16; +} +FD_FN_PURE uchar fd_vote_instruction_is_update_commission_collector(fd_vote_instruction_t const * self) { + return self->discriminant == 17; +} +FD_FN_PURE uchar fd_vote_instruction_is_update_commission_bps(fd_vote_instruction_t const * self) { + return self->discriminant == 18; +} +FD_FN_PURE uchar fd_vote_instruction_is_deposit_delegator_rewards(fd_vote_instruction_t const * self) { + return self->discriminant == 19; +} void fd_vote_instruction_inner_new( fd_vote_instruction_inner_t * self, uint discriminant ); int fd_vote_instruction_inner_decode_footprint( uint discriminant, fd_bincode_decode_ctx_t * ctx, ulong * total_sz ) { int err; @@ -4967,6 +5081,24 @@ int fd_vote_instruction_inner_decode_footprint( uint discriminant, fd_bincode_de if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; } + case 16: { + return FD_BINCODE_SUCCESS; + } + case 17: { + err = fd_commission_kind_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; + } + case 18: { + err = fd_update_commission_bps_decode_footprint_inner( ctx, total_sz ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; + } + case 19: { + err = fd_bincode_uint64_decode_footprint( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + return FD_BINCODE_SUCCESS; + } default: return FD_BINCODE_ERR_ENCODING; } } @@ -5050,6 +5182,21 @@ static void fd_vote_instruction_inner_decode_inner( fd_vote_instruction_inner_t fd_tower_sync_switch_decode_inner( &self->tower_sync_switch, alloc_mem, ctx ); break; } + case 16: { + break; + } + case 17: { + fd_commission_kind_decode_inner( &self->update_commission_collector, alloc_mem, ctx ); + break; + } + case 18: { + fd_update_commission_bps_decode_inner( &self->update_commission_bps, alloc_mem, ctx ); + break; + } + case 19: { + fd_bincode_uint64_decode_unsafe( &self->deposit_delegator_rewards, ctx ); + break; + } } } static void fd_vote_instruction_decode_inner( void * struct_mem, void * * alloc_mem, fd_bincode_decode_ctx_t * ctx ) { @@ -5128,6 +5275,20 @@ void fd_vote_instruction_inner_new( fd_vote_instruction_inner_t * self, uint dis fd_tower_sync_switch_new( &self->tower_sync_switch ); break; } + case 16: { + break; + } + case 17: { + fd_commission_kind_new( &self->update_commission_collector ); + break; + } + case 18: { + fd_update_commission_bps_new( &self->update_commission_bps ); + break; + } + case 19: { + break; + } default: break; // FD_LOG_ERR(( "unhandled type")); } } @@ -5204,6 +5365,18 @@ ulong fd_vote_instruction_size( fd_vote_instruction_t const * self ) { size += fd_tower_sync_switch_size( &self->inner.tower_sync_switch ); break; } + case 17: { + size += fd_commission_kind_size( &self->inner.update_commission_collector ); + break; + } + case 18: { + size += fd_update_commission_bps_size( &self->inner.update_commission_bps ); + break; + } + case 19: { + size += sizeof(ulong); + break; + } } return size; } @@ -5286,6 +5459,21 @@ int fd_vote_instruction_inner_encode( fd_vote_instruction_inner_t const * self, if( FD_UNLIKELY( err ) ) return err; break; } + case 17: { + err = fd_commission_kind_encode( &self->update_commission_collector, ctx ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + case 18: { + err = fd_update_commission_bps_encode( &self->update_commission_bps, ctx ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + case 19: { + err = fd_bincode_uint64_encode( self->deposit_delegator_rewards, 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 011283b8ed2..b2ec2f04e2c 100644 --- a/src/flamenco/types/fd_types.h +++ b/src/flamenco/types/fd_types.h @@ -950,6 +950,22 @@ struct fd_vote_authorize_checked_with_seed_args { typedef struct fd_vote_authorize_checked_with_seed_args fd_vote_authorize_checked_with_seed_args_t; #define FD_VOTE_AUTHORIZE_CHECKED_WITH_SEED_ARGS_ALIGN alignof(fd_vote_authorize_checked_with_seed_args_t) +/* https://github.com/anza-xyz/agave/blob/master/sdk/vote-interface/src/instruction.rs */ +struct fd_commission_kind { + uint discriminant; +}; +typedef struct fd_commission_kind fd_commission_kind_t; +#define FD_COMMISSION_KIND_ALIGN alignof(fd_commission_kind_t) + +/* https://github.com/anza-xyz/agave/blob/master/sdk/vote-interface/src/instruction.rs */ +/* Encoded Size: Fixed (6 bytes) */ +struct fd_update_commission_bps { + ushort commission_bps; + fd_commission_kind_t kind; +}; +typedef struct fd_update_commission_bps fd_update_commission_bps_t; +#define FD_UPDATE_COMMISSION_BPS_ALIGN alignof(fd_update_commission_bps_t) + union fd_vote_instruction_inner { fd_vote_init_t initialize_account; fd_vote_authorize_pubkey_t authorize; @@ -966,6 +982,9 @@ union fd_vote_instruction_inner { fd_compact_vote_state_update_switch_t compact_update_vote_state_switch; fd_tower_sync_t tower_sync; fd_tower_sync_switch_t tower_sync_switch; + fd_commission_kind_t update_commission_collector; + fd_update_commission_bps_t update_commission_bps; + ulong deposit_delegator_rewards; }; typedef union fd_vote_instruction_inner fd_vote_instruction_inner_t; @@ -2149,6 +2168,27 @@ static inline ulong fd_vote_authorize_checked_with_seed_args_align( void ) { ret int fd_vote_authorize_checked_with_seed_args_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ); void * fd_vote_authorize_checked_with_seed_args_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); +static inline void fd_commission_kind_new_disc( fd_commission_kind_t * self, uint discriminant ) { self->discriminant = discriminant; } +static inline void fd_commission_kind_new( fd_commission_kind_t * self ) { self->discriminant = (uint)ULONG_MAX; } +int fd_commission_kind_encode( fd_commission_kind_t const * self, fd_bincode_encode_ctx_t * ctx ); +ulong fd_commission_kind_size( fd_commission_kind_t const * self ); +static inline ulong fd_commission_kind_align( void ) { return FD_COMMISSION_KIND_ALIGN; } +int fd_commission_kind_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ); +void * fd_commission_kind_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); + +FD_FN_PURE uchar fd_commission_kind_is_inflation_rewards( fd_commission_kind_t const * self ); +FD_FN_PURE uchar fd_commission_kind_is_block_revenue( fd_commission_kind_t const * self ); +enum { +fd_commission_kind_enum_inflation_rewards = 0, +fd_commission_kind_enum_block_revenue = 1, +}; +void fd_update_commission_bps_new( fd_update_commission_bps_t * self ); +int fd_update_commission_bps_encode( fd_update_commission_bps_t const * self, fd_bincode_encode_ctx_t * ctx ); +static inline ulong fd_update_commission_bps_size( fd_update_commission_bps_t const * self ) { (void)self; return 6UL; } +static inline ulong fd_update_commission_bps_align( void ) { return FD_UPDATE_COMMISSION_BPS_ALIGN; } +int fd_update_commission_bps_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ); +void * fd_update_commission_bps_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); + void fd_vote_instruction_new_disc( fd_vote_instruction_t * self, uint discriminant ); void fd_vote_instruction_new( fd_vote_instruction_t * self ); int fd_vote_instruction_encode( fd_vote_instruction_t const * self, fd_bincode_encode_ctx_t * ctx ); @@ -2173,6 +2213,10 @@ FD_FN_PURE uchar fd_vote_instruction_is_compact_update_vote_state( fd_vote_instr FD_FN_PURE uchar fd_vote_instruction_is_compact_update_vote_state_switch( fd_vote_instruction_t const * self ); FD_FN_PURE uchar fd_vote_instruction_is_tower_sync( fd_vote_instruction_t const * self ); FD_FN_PURE uchar fd_vote_instruction_is_tower_sync_switch( fd_vote_instruction_t const * self ); +FD_FN_PURE uchar fd_vote_instruction_is_initialize_account_v2( fd_vote_instruction_t const * self ); +FD_FN_PURE uchar fd_vote_instruction_is_update_commission_collector( fd_vote_instruction_t const * self ); +FD_FN_PURE uchar fd_vote_instruction_is_update_commission_bps( fd_vote_instruction_t const * self ); +FD_FN_PURE uchar fd_vote_instruction_is_deposit_delegator_rewards( fd_vote_instruction_t const * self ); enum { fd_vote_instruction_enum_initialize_account = 0, fd_vote_instruction_enum_authorize = 1, @@ -2190,6 +2234,10 @@ fd_vote_instruction_enum_compact_update_vote_state = 12, fd_vote_instruction_enum_compact_update_vote_state_switch = 13, fd_vote_instruction_enum_tower_sync = 14, fd_vote_instruction_enum_tower_sync_switch = 15, +fd_vote_instruction_enum_initialize_account_v2 = 16, +fd_vote_instruction_enum_update_commission_collector = 17, +fd_vote_instruction_enum_update_commission_bps = 18, +fd_vote_instruction_enum_deposit_delegator_rewards = 19, }; static inline void fd_system_program_instruction_create_account_new( fd_system_program_instruction_create_account_t * self ) { fd_memset( self, 0, sizeof(fd_system_program_instruction_create_account_t) ); } int fd_system_program_instruction_create_account_encode( fd_system_program_instruction_create_account_t const * self, fd_bincode_encode_ctx_t * ctx ); diff --git a/src/flamenco/types/fd_types.json b/src/flamenco/types/fd_types.json index 8e6326daf82..59d4a9fcf32 100644 --- a/src/flamenco/types/fd_types.json +++ b/src/flamenco/types/fd_types.json @@ -692,6 +692,24 @@ ], "comment": "https://github.com/solana-labs/solana/blob/8f2c8b8388a495d2728909e30460aa40dcc5d733/programs/vote/src/vote_state/mod.rs#L252" }, + { + "name": "commission_kind", + "type": "enum", + "variants": [ + { "name": "inflation_rewards" }, + { "name": "block_revenue" } + ], + "comment": "https://github.com/anza-xyz/agave/blob/master/sdk/vote-interface/src/instruction.rs" + }, + { + "name": "update_commission_bps", + "type": "struct", + "fields": [ + { "name": "commission_bps", "type": "ushort" }, + { "name": "kind", "type": "commission_kind" } + ], + "comment": "https://github.com/anza-xyz/agave/blob/master/sdk/vote-interface/src/instruction.rs" + }, { "name": "vote_instruction", "type": "enum", @@ -711,7 +729,11 @@ { "name": "compact_update_vote_state", "type": "compact_vote_state_update"}, { "name": "compact_update_vote_state_switch", "type": "compact_vote_state_update_switch"}, { "name": "tower_sync", "type": "tower_sync"}, - { "name": "tower_sync_switch", "type": "tower_sync_switch"} + { "name": "tower_sync_switch", "type": "tower_sync_switch"}, + { "name": "initialize_account_v2" }, + { "name": "update_commission_collector", "type": "commission_kind" }, + { "name": "update_commission_bps", "type": "update_commission_bps" }, + { "name": "deposit_delegator_rewards", "type": "ulong" } ], "comment": "https://github.com/firedancer-io/solana/blob/53a4e5d6c58b2ffe89b09304e4437f8ca198dadd/programs/vote/src/vote_instruction.rs#L21" },