Skip to content

Commit 02ed403

Browse files
committed
fix(solana): derive the omitted compute-unit limit instead of assuming the cap
When a transaction carries no SetComputeUnitLimit, the runtime requests 200,000 compute units per non-ComputeBudget instruction, capped at 1,400,000 -- it does not request the cap unconditionally. Assuming the cap could not understate the fee, but it overstated it badly: a transfer alongside a unit-price instruction is charged on 200,000 CUs and the device displayed seven times that as the "Maximum priority fee". Deriving the limit keeps the no-understatement property, because it is exactly what the runtime will charge, and makes the screen describe the transaction actually being signed. The test that pinned the old rule is updated. It now covers the realistic 1-instruction case and the cap, which SOL_MAX_INSTRUCTIONS (8) lets a transaction reach exactly with seven non-budget instructions -- so the clamp stays as defence rather than as a reachable path.
1 parent defc9ea commit 02ed403

2 files changed

Lines changed: 63 additions & 7 deletions

File tree

lib/firmware/solana.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,17 +799,34 @@ void solana_formatTokenAmount(char* buf, size_t len, uint64_t amount,
799799
snprintf(buf, len, "%llu.%s %s", (unsigned long long)whole, frac_str, symbol);
800800
}
801801

802+
/* Solana's own default when a transaction carries no SetComputeUnitLimit:
803+
200,000 compute units per non-ComputeBudget instruction, capped at
804+
1,400,000. See the runtime's compute_budget_processor. */
805+
#define SOL_DEFAULT_CU_PER_INSTRUCTION 200000u
806+
#define SOL_MAX_CU_LIMIT 1400000u
807+
808+
static bool solana_isComputeBudgetInstruction(uint8_t type) {
809+
return type == SOL_INSTR_COMPUTE_BUDGET_HEAP_FRAME ||
810+
type == SOL_INSTR_COMPUTE_BUDGET_UNIT_LIMIT ||
811+
type == SOL_INSTR_COMPUTE_BUDGET_UNIT_PRICE ||
812+
type == SOL_INSTR_COMPUTE_BUDGET_LOADED_ACCOUNTS_SIZE;
813+
}
814+
802815
bool solana_calculatePriorityFee(const SolanaParsedTx* tx, uint64_t* fee_out,
803816
bool* has_fee) {
804817
const uint64_t divisor = 1000000u;
805818
uint64_t price = 0;
806-
uint64_t limit = 1400000u;
819+
uint64_t limit = 0;
807820
bool seen_price = false;
808821
bool seen_limit = false;
822+
uint64_t non_budget_instructions = 0;
809823
*has_fee = false;
810824

811825
for (uint8_t i = 0; i < tx->num_instructions; i++) {
812826
const SolanaParsedInstruction* pi = &tx->instructions[i];
827+
if (!solana_isComputeBudgetInstruction((uint8_t)pi->type)) {
828+
non_budget_instructions++;
829+
}
813830
if (pi->type == SOL_INSTR_COMPUTE_BUDGET_UNIT_PRICE) {
814831
if (seen_price) return false;
815832
seen_price = true;
@@ -820,6 +837,21 @@ bool solana_calculatePriorityFee(const SolanaParsedTx* tx, uint64_t* fee_out,
820837
limit = pi->extra_value;
821838
}
822839
}
840+
841+
if (!seen_limit) {
842+
/* Not the 1,400,000 cap.
843+
*
844+
* Assuming the cap whenever SetComputeUnitLimit was absent overstated the
845+
* screen badly: a transfer plus a unit-price instruction is charged on
846+
* 200,000 CUs, and the device showed seven times that as the "Maximum
847+
* priority fee". It is an upper bound, so nothing was ever understated --
848+
* but a maximum the runtime will never reach is not the transaction's
849+
* maximum, and this release line is about screens that describe the thing
850+
* being signed. num_instructions is a uint8_t, so this cannot overflow. */
851+
limit = non_budget_instructions * SOL_DEFAULT_CU_PER_INSTRUCTION;
852+
if (limit > SOL_MAX_CU_LIMIT) limit = SOL_MAX_CU_LIMIT;
853+
}
854+
823855
if (!seen_price || price == 0) return true;
824856

825857
uint64_t whole = price / divisor;

unittests/firmware/solana.cpp

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -666,14 +666,38 @@ TEST(Solana, PriorityFeeCalculationIsRoundedAndOverflowSafe) {
666666
EXPECT_TRUE(has_fee);
667667
EXPECT_EQ(fee, 70000000ULL);
668668

669-
/* No explicit limit uses the protocol maximum so the displayed liability
670-
cannot understate the fee. */
671-
tx.num_instructions = 1;
672-
tx.instructions[0].type = SOL_INSTR_COMPUTE_BUDGET_UNIT_PRICE;
673-
tx.instructions[0].extra_value = 2000000;
669+
/* With no explicit limit, use the limit the RUNTIME will request: 200,000
670+
compute units per non-ComputeBudget instruction, capped at 1,400,000.
671+
672+
This replaces an earlier rule that assumed the 1,400,000 cap whenever
673+
SetComputeUnitLimit was absent. That could not understate the fee, but it
674+
overstated it badly -- a transfer alongside a unit-price instruction is
675+
charged on 200,000 CUs and was shown as seven times that. Deriving the
676+
limit still cannot understate what the runtime charges, because it is
677+
exactly what the runtime charges. */
678+
memset(&tx, 0, sizeof(tx));
679+
tx.num_instructions = 2;
680+
tx.instructions[0].type = SOL_INSTR_SYSTEM_TRANSFER;
681+
tx.instructions[1].type = SOL_INSTR_COMPUTE_BUDGET_UNIT_PRICE;
682+
tx.instructions[1].extra_value = 2000000;
683+
ASSERT_TRUE(solana_calculatePriorityFee(&tx, &fee, &has_fee));
684+
EXPECT_TRUE(has_fee);
685+
EXPECT_EQ(fee, 400000ULL); /* 2 lamports/CU * 1 * 200,000 CUs */
686+
687+
/* Seven non-budget instructions reach the 1,400,000 cap exactly, which is
688+
also the most SOL_MAX_INSTRUCTIONS (8) allows alongside a price
689+
instruction. The clamp stays as defence rather than as a reachable path. */
690+
memset(&tx, 0, sizeof(tx));
691+
tx.num_instructions = 8;
692+
for (int i = 0; i < 7; i++)
693+
tx.instructions[i].type = SOL_INSTR_SYSTEM_TRANSFER;
694+
tx.instructions[7].type = SOL_INSTR_COMPUTE_BUDGET_UNIT_PRICE;
695+
tx.instructions[7].extra_value = 2000000;
674696
ASSERT_TRUE(solana_calculatePriorityFee(&tx, &fee, &has_fee));
675697
EXPECT_TRUE(has_fee);
676-
EXPECT_EQ(fee, 2800000ULL);
698+
EXPECT_EQ(fee, 2800000ULL); /* capped: 2 * 1,400,000 */
699+
700+
memset(&tx, 0, sizeof(tx));
677701

678702
tx.num_instructions = 2;
679703
tx.instructions[0].type = SOL_INSTR_COMPUTE_BUDGET_UNIT_LIMIT;

0 commit comments

Comments
 (0)