From 81508bc231c88b0a811d996578c4524062f398f2 Mon Sep 17 00:00:00 2001 From: Maciek Malik <142581992+0xMMBD@users.noreply.github.com> Date: Fri, 30 Aug 2024 14:46:15 +0200 Subject: [PATCH] Refactor ComputeBudget fees screen - calculate max fees from unit limit and unit price (#15) * Refactor ComputeBudget fees screen - calculate max fees from unit limit and unit price * Fix lint error * Fix max fees calculation - include signatures count * Fix unit tests * Improved logic for calculate_max_fee --- libsol/compute_budget_instruction.c | 54 ++++++++++++---------- libsol/compute_budget_instruction.h | 19 +++++++- libsol/compute_budget_instruction_test.c | 5 +- libsol/message.c | 4 +- libsol/message_test.c | 2 +- libsol/transaction_printers.c | 58 ++++++++++++++++++------ 6 files changed, 97 insertions(+), 45 deletions(-) diff --git a/libsol/compute_budget_instruction.c b/libsol/compute_budget_instruction.c index 0d16ab04..f15032ce 100644 --- a/libsol/compute_budget_instruction.c +++ b/libsol/compute_budget_instruction.c @@ -46,48 +46,52 @@ static int parse_loaded_accounts_data_size_limit( return 0; } -static int print_compute_budget_unit_price(ComputeBudgetChangeUnitPriceInfo* info, - const PrintConfig* print_config) { - UNUSED(print_config); - - SummaryItem* item; - - item = transaction_summary_general_item(); - summary_item_set_u64(item, "Unit price", info->units); - - return 0; +static uint32_t calculate_max_fee(const ComputeBudgetFeeInfo* info) { + uint32_t max_fee = FEE_LAMPORTS_PER_SIGNATURE * info->signatures_count; + + if (info->change_unit_price != NULL) { + uint32_t max_compute = 0; + if (info->change_unit_limit != NULL) { + max_compute = info->change_unit_limit->units; + } else { + max_compute = + MIN(info->instructions_count * MAX_CU_PER_INSTRUCTION, MAX_CU_PER_TRANSACTION); + } + return max_fee + (info->change_unit_price->units * max_compute); + } + return max_fee; } -static int print_compute_budget_unit_limit(ComputeBudgetChangeUnitLimitInfo* info, - const PrintConfig* print_config) { +static int print_compute_budget_max_fee(uint32_t max_fee, const PrintConfig* print_config) { UNUSED(print_config); SummaryItem* item; item = transaction_summary_general_item(); - summary_item_set_u64(item, "Unit limit", info->units); + summary_item_set_u64(item, "Max fees", max_fee); return 0; } -int print_compute_budget(ComputeBudgetInfo* info, const PrintConfig* print_config) { - switch (info->kind) { - case ComputeBudgetChangeUnitLimit: - return print_compute_budget_unit_limit(&info->change_unit_limit, print_config); - case ComputeBudgetChangeUnitPrice: - return print_compute_budget_unit_price(&info->change_unit_price, print_config); - case ComputeBudgetRequestHeapFrame: - case ComputeBudgetSetLoadedAccountsDataSizeLimit: - break; - } - return 1; +/** + * Display transaction max fees + * RequestHeapFrame and SetLoadedAccountsDataSizeLimit instruction kinds + * are omitted on purpose as they currently do not display any data on the screen + */ +void print_compute_budget(ComputeBudgetFeeInfo* info, const PrintConfig* print_config) { + uint32_t transaction_max_fee = calculate_max_fee(info); + print_compute_budget_max_fee(transaction_max_fee, print_config); } -int parse_compute_budget_instructions(const Instruction* instruction, ComputeBudgetInfo* info) { +int parse_compute_budget_instructions(const Instruction* instruction, + const MessageHeader* header, + ComputeBudgetInfo* info) { Parser parser = {instruction->data, instruction->data_length}; BAIL_IF(parse_compute_budget_instruction_kind(&parser, &info->kind)); + info->signatures_count = header->pubkeys_header.num_required_signatures; + switch (info->kind) { case ComputeBudgetRequestHeapFrame: return parse_request_heap_frame_instruction(&parser, &info->request_heap_frame); diff --git a/libsol/compute_budget_instruction.h b/libsol/compute_budget_instruction.h index 7ac61b4f..c68f3e67 100644 --- a/libsol/compute_budget_instruction.h +++ b/libsol/compute_budget_instruction.h @@ -5,6 +5,11 @@ #include "sol/parser.h" #include "sol/print_config.h" +// https://solana.com/docs/core/fees#compute-unit-limit +#define MAX_CU_PER_INSTRUCTION 200000 +#define MAX_CU_PER_TRANSACTION 1400000 +#define FEE_LAMPORTS_PER_SIGNATURE 5000 + extern const Pubkey compute_budget_program_id; enum ComputeBudgetInstructionKind { @@ -32,6 +37,7 @@ typedef struct ComputeBudgetSetLoadedAccountsDataSizeLimitInfo { typedef struct ComputeBudgetInfo { enum ComputeBudgetInstructionKind kind; + size_t signatures_count; union { ComputeBudgetRequestHeapFrameInfo request_heap_frame; ComputeBudgetChangeUnitLimitInfo change_unit_limit; @@ -40,6 +46,15 @@ typedef struct ComputeBudgetInfo { }; } ComputeBudgetInfo; -int parse_compute_budget_instructions(const Instruction* instruction, ComputeBudgetInfo* info); +typedef struct ComputeBudgetFeeInfo { + ComputeBudgetChangeUnitLimitInfo* change_unit_limit; + ComputeBudgetChangeUnitPriceInfo* change_unit_price; + size_t instructions_count; + size_t signatures_count; +} ComputeBudgetFeeInfo; + +int parse_compute_budget_instructions(const Instruction* instruction, + const MessageHeader* header, + ComputeBudgetInfo* info); -int print_compute_budget(ComputeBudgetInfo* info, const PrintConfig* print_config); +void print_compute_budget(ComputeBudgetFeeInfo* info, const PrintConfig* print_config); diff --git a/libsol/compute_budget_instruction_test.c b/libsol/compute_budget_instruction_test.c index 26e51644..40e861bf 100644 --- a/libsol/compute_budget_instruction_test.c +++ b/libsol/compute_budget_instruction_test.c @@ -56,9 +56,12 @@ void test_parse_compute_budget_instructions_invalid_kind() { Instruction instruction = {.data = message, .data_length = sizeof(message), .accounts_length = 0}; + + MessageHeader* header = {0}; + ComputeBudgetInfo info; - int result = parse_compute_budget_instructions(&instruction, &info); + int result = parse_compute_budget_instructions(&instruction, header, &info); assert(result == 1); // Invalid instruction kind for ComputeBudget program } diff --git a/libsol/message.c b/libsol/message.c index 77180c36..181f9fb5 100644 --- a/libsol/message.c +++ b/libsol/message.c @@ -81,7 +81,9 @@ int process_message_body(const uint8_t* message_body, break; } case ProgramIdComputeBudget: { - if (parse_compute_budget_instructions(&instruction, &info->compute_budget) == 0) { + if (parse_compute_budget_instructions(&instruction, + header, + &info->compute_budget) == 0) { info->kind = program_id; } break; diff --git a/libsol/message_test.c b/libsol/message_test.c index 48c08930..14dd4fec 100644 --- a/libsol/message_test.c +++ b/libsol/message_test.c @@ -190,7 +190,7 @@ void test_process_message_body_transfer_with_compute_budget_limit_and_unit_price 0, 0, 0, 0, 0, 0, 0 }; - process_message_body_and_sanity_check(message, sizeof(message), 6); + process_message_body_and_sanity_check(message, sizeof(message), 5); } diff --git a/libsol/transaction_printers.c b/libsol/transaction_printers.c index d8d1ded4..91c5848d 100644 --- a/libsol/transaction_printers.c +++ b/libsol/transaction_printers.c @@ -669,6 +669,48 @@ static int print_transaction_nonce_processed(const PrintConfig* print_config, return 1; } +InstructionInfo* const* preprocess_compute_budget_instructions(const PrintConfig* print_config, + InstructionInfo* const* infos, + size_t* infos_length) { + size_t infos_length_initial = *infos_length; + if (infos_length_initial > 1) { + // Iterate over infos and print compute budget instructions and offset pointers + // Handle ComputeBudget instructions first due to tech limitations of the + // print_transaction_nonce_processed. We can get one or 4 ComputeBudget instructions in a + // single transaction, so we are not able to handle it in a static switch case. + ComputeBudgetFeeInfo compute_budget_fee_info = {.change_unit_limit = NULL, + .change_unit_price = NULL, + .instructions_count = infos_length_initial, + .signatures_count = 0}; + for (size_t info_idx = 0; info_idx < infos_length_initial; ++info_idx) { + InstructionInfo* instruction_info = infos[0]; + if (instruction_info->kind == ProgramIdComputeBudget) { + compute_budget_fee_info.signatures_count = + instruction_info->compute_budget.signatures_count; + // Unit limit and unit price needs to be aggregated + // before displaying as this is needed for calculating max fee properly + if (instruction_info->compute_budget.kind == ComputeBudgetChangeUnitLimit) { + compute_budget_fee_info.change_unit_limit = + &instruction_info->compute_budget.change_unit_limit; + } + if (instruction_info->compute_budget.kind == ComputeBudgetChangeUnitPrice) { + compute_budget_fee_info.change_unit_price = + &instruction_info->compute_budget.change_unit_price; + } + infos++; + (*infos_length)--; + } + } + if (compute_budget_fee_info.change_unit_limit || + compute_budget_fee_info.change_unit_price) { + // We do not want to display anything related to the compute budget + // if no instructions of this type were present in the transaction + print_compute_budget(&compute_budget_fee_info, print_config); + } + } + return infos; +} + int print_transaction(const PrintConfig* print_config, InstructionInfo* const* infos, size_t infos_length) { @@ -681,21 +723,7 @@ int print_transaction(const PrintConfig* print_config, infos_length--; } - if (infos_length > 1) { - // Iterate over infos and print compute budget instructions and offset pointers - // Handle ComputeBudget instructions first due to tech limitations of the - // print_transaction_nonce_processed. We can get one or 4 ComputeBudget instructions in a - // single transaction, so we are not able to handle it in a static switch case. - size_t infos_length_initial = infos_length; - for (size_t info_idx = 0; info_idx < infos_length_initial; ++info_idx) { - InstructionInfo* instruction_info = infos[0]; - if (instruction_info->kind == ProgramIdComputeBudget) { - print_compute_budget(&instruction_info->compute_budget, print_config); - infos++; - infos_length--; - } - } - } + infos = preprocess_compute_budget_instructions(print_config, infos, &infos_length); return print_transaction_nonce_processed(print_config, infos, infos_length); }