lines 1-7
Constants for the byte offsets and the magic numbers we are scanning for
1.equ ACCT0_KEY, 0x00102.equ ACCT0_DATA_LEN, 0x00583.equ ACCT0_DATA, 0x00604 5.equ EXPECTED_IX_DATA_LEN, 86.equ CB_PRICE_IX_LEN, 97.equ SET_CU_PRICE_DISC, 3
Six constants. The first three say where the Instructions sysvar lives inside our input region. The last three say what we are looking for inside that sysvar.
Why ACCT0_KEY = 0x0010, ACCT0_DATA_LEN = 0x0058, ACCT0_DATA = 0x0060? Same per-account header math as balance_floor. The first 8 bytes of the input region are the u64 account count. Then for account 0: 8 bytes of dup tag + flags + padding, then 32-byte pubkey at offset 16 (0x10), then 32-byte owner at 48, then u64 lamports at 80, then u64 data_len at 88 (0x58), then the data itself starts at 96 (0x60). These are the standard offsets for any guard that declares one account.
Why EXPECTED_IX_DATA_LEN = 8? Our own ix data is one u64 ceiling (the per-CU priority fee cap), nothing else. 1 u64 = 8 bytes.
Why CB_PRICE_IX_LEN = 9? A ComputeBudget SetComputeUnitPrice instruction's data is 1 discriminator byte + 1 u64 price = 9 bytes. We will use this to filter out other ComputeBudget variants while walking the tx.
Why SET_CU_PRICE_DISC = 3? ComputeBudget uses single-byte discriminators to tell its variants apart: RequestHeapFrame = 1, SetComputeUnitLimit = 2, SetComputeUnitPrice = 3, SetLoadedAccountsDataSizeLimit = 4. We only care about variant 3.
lines 11-23
Verify account 0 is actually the Instructions sysvar
11 lddw r2, sysvar_ix_key12 ldxdw r3, [r1 + ACCT0_KEY + 0]13 ldxdw r4, [r2 + 0]14 jne r3, r4, bad_account15 ldxdw r3, [r1 + ACCT0_KEY + 8]16 ldxdw r4, [r2 + 8]17 jne r3, r4, bad_account18 ldxdw r3, [r1 + ACCT0_KEY + 16]19 ldxdw r4, [r2 + 16]20 jne r3, r4, bad_account21 ldxdw r3, [r1 + ACCT0_KEY + 24]22 ldxdw r4, [r2 + 24]23 jne r3, r4, bad_account
The Instructions sysvar is a special Solana sysvar that contains the full serialized list of every instruction in the current transaction. It is the only way for a program to inspect its sibling instructions. fee_ceiling NEEDS that introspection because it has to find every ComputeBudget SetComputeUnitPrice in the tx and check it against the ceiling.
The sysvar lives at a fixed pubkey (Sysvar1nstructions1111111111111111111111111). If account 0 is anything else, this guard cannot do its job. Exit 3 with 'bad account'.
lddw r2, sysvar_ix_key loads the address of a 32-byte rodata constant holding the sysvar's raw pubkey bytes. Four pairs of ldxdw + jne compare account 0's pubkey (32 bytes starting at 0x10) against that constant, 8 bytes at a time. First mismatch jumps to bad_account.
Why check this at all? The SDK always wires the sysvar in, but a hand-crafted tx could pass any account. We refuse anything else before reading bytes we would otherwise interpret as serialized instructions.
lines 25-27
Set up the sysvar data pointer
25 ldxdw r2, [r1 + ACCT0_DATA_LEN]26 mov64 r3, r127 add64 r3, ACCT0_DATA
Account 0 is confirmed to be the Instructions sysvar. ldxdw r2 reads the total length of the sysvar's data block into r2. mov64 r3, r1; add64 r3, ACCT0_DATA puts r3 at byte 0 of the sysvar's data. Every offset from here on is computed from r3, not from r1.
lines 29-37
Find our own ix using current_instruction_index
29 mov64 r4, r330 add64 r4, r231 sub64 r4, 232 ldxh r5, [r4 + 0]33 34 mov64 r4, r535 lsh64 r4, 136 add64 r4, r337 ldxh r9, [r4 + 2]
The Instructions sysvar data is laid out as: a 2-byte num_instructions count at the start, then a 2-byte offset entry for each instruction (each entry is the byte offset within the sysvar data where that instruction's serialized form lives), then the serialized instructions themselves, and finally the LAST 2 bytes are current_instruction_index (the index of the instruction the runtime is currently executing, which is THIS guard).
r4 = r3 + r2 - 2 puts r4 at the address of the last 2 bytes. ldxh reads 2 bytes (a u16) into r5. That is our own index in the tx.
Now compute the address of offsets[current_idx]. Each offset entry is 2 bytes, so we multiply current_idx by 2 (lsh64 r4, 1 is left-shift by 1 = multiply by 2, cheaper than mul64). Add r3 to get the absolute address. The +2 in ldxh r9, [r4 + 2] skips the num_instructions u16 at the very start of the sysvar data, so we land on the right offsets-table entry.
lines 39-50
Skip the ix header to land on our own ceiling u64
39 mov64 r4, r340 add64 r4, r941 42 ldxh r5, [r4 + 0]43 mov64 r9, r544 mul64 r9, 3345 add64 r9, 3446 add64 r9, r447 48 ldxh r5, [r9 + 0]49 jne r5, EXPECTED_IX_DATA_LEN, bad_ix_data50 ldxdw r6, [r9 + 2]
r4 = r3 + r9 = the base of our own ix's serialized form within the sysvar. Each serialized ix has this layout: 2-byte num_accounts, then num_accounts account metas (33 bytes each = 1 flag byte + 32-byte pubkey), then 32-byte program_id, then 2-byte ix_data_len, then ix_data.
ldxh r5, [r4 + 0] reads num_accounts. We need to skip past: 2 bytes (num_accounts) + 33 * num_accounts (account metas) + 32 (program_id) = 34 + 33 * num_accounts. lsh64 r4, 1 then add64 r4, 1 would not work in one go, so the code splits: mul64 r9, 33; add64 r9, 34; add64 r9, r4. r9 = pointer to our ix_data_len.
ldxh r5, [r9 + 0] reads ix_data_len. jne r5, 8, bad_ix_data verifies it equals 8.
ldxdw r6, [r9 + 2] reads the u64 ceiling from our ix data, 8 bytes past the length field. The +2 is because ix_data_len is a u16 (2 bytes), not a u64. r6 now holds the per-CU ceiling for the rest of the program.
52 ldxh r8, [r3 + 0]53 mov64 r7, 0
ldxh r8, [r3 + 0] reads num_instructions (the very first u16 of the sysvar data). r8 is the loop bound. mov64 r7, 0 initializes the outer counter. We will walk every instruction in the tx, checking each one for a SetComputeUnitPrice that exceeds the ceiling.
lines 55-71
Walk every ix, compute its program_id pointer
55loop:56 jge r7, r8, ok57 58 mov64 r4, r759 lsh64 r4, 160 add64 r4, r361 ldxh r5, [r4 + 2]62 63 mov64 r9, r364 add64 r9, r565 66 ldxh r4, [r9 + 0]67 68 mov64 r5, r469 mul64 r5, 3370 add64 r5, r971 add64 r5, 2
jge r7, r8, ok: counter reached num_instructions with no violation found, success exit.
Same offsets-table dance as the previous block, but for ix at index r7 instead of our own ix. offsets[r7] gives the byte offset of ix r7's serialization. r9 = base of ix r7.
This time we want to skip to the program_id (not the ix_data_len). Skip 2 (num_accounts) + 33 * num_accounts (account metas). r5 = pointer to ix r7's 32-byte program_id.
lines 73-89
Is this ix a ComputeBudget call?
73 lddw r4, cb_program_id74 75 ldxdw r0, [r5 + 0]76 ldxdw r1, [r4 + 0]77 jne r0, r1, next_ix78 79 ldxdw r0, [r5 + 8]80 ldxdw r1, [r4 + 8]81 jne r0, r1, next_ix82 83 ldxdw r0, [r5 + 16]84 ldxdw r1, [r4 + 16]85 jne r0, r1, next_ix86 87 ldxdw r0, [r5 + 24]88 ldxdw r1, [r4 + 24]89 jne r0, r1, next_ix
lddw r4, cb_program_id loads the address of a 32-byte rodata constant holding ComputeBudget's program_id (ComputeBudget111111111111111111111111111111).
Four pairs of ldxdw + jne compare ix r7's program_id against the constant. First mismatch branches to next_ix (this ix is not a ComputeBudget call, move on). All four match means this ix targets ComputeBudget, continue checking.
lines 91-98
Is it specifically SetComputeUnitPrice, and does it exceed the ceiling?
91 ldxh r4, [r5 + 32]92 jne r4, CB_PRICE_IX_LEN, next_ix93 94 ldxb r4, [r5 + 34]95 jne r4, SET_CU_PRICE_DISC, next_ix96 97 ldxdw r4, [r5 + 35]98 jgt r4, r6, fee_too_high
We know ix r7 targets ComputeBudget. ComputeBudget has four variants; we only care about SetComputeUnitPrice. Check the shape:
ldxh r4, [r5 + 32] reads ix_data_len at offset 32 from the program_id (program_id is 32 bytes wide, so ix_data_len follows it). Must equal 9. The other ComputeBudget variants (SetComputeUnitLimit = 5 bytes, RequestHeapFrame = 5 bytes, SetLoadedAccountsDataSizeLimit = 5 bytes) all have shorter data, so a length of 9 effectively narrows us to SetComputeUnitPrice.
ldxb r4, [r5 + 34] reads the discriminator byte (offset 34 = program_id end at 32 + ix_data_len u16 at 32-33 = first ix data byte at 34). Must equal 3.
If both match, ldxdw r4, [r5 + 35] reads the u64 price (8 bytes after the discriminator).
jgt r4, r6, fee_too_high: if the bid price is STRICTLY greater than our ceiling, fail with exit 1. Equal passes (the boundary is inclusive on purpose, paying exactly the ceiling is fine).
lines 100-106
Advance and ok exit
100next_ix:101 add64 r7, 1102 ja loop103 104ok:105 mov64 r0, 0106 exit
next_ix: add64 r7, 1; ja loop. Increment the counter, jump back. We walk EVERY instruction in the tx. Solana's runtime already rejects txs with duplicate ComputeBudget variants, but the guard is more conservative: if a duplicate ever slips through, both get checked.
ok: mov64 r0, 0; exit. We walked the whole tx, no SetComputeUnitPrice exceeded the ceiling. Success.
lines 108-127
Three failure paths, three exit codes
108fee_too_high:109 lddw r1, msg_high110 mov64 r2, 12111 call sol_log_112 mov64 r0, 1113 exit114 115bad_ix_data:116 lddw r1, msg_bad117 mov64 r2, 11118 call sol_log_119 mov64 r0, 2120 exit121 122bad_account:123 lddw r1, msg_acct124 mov64 r2, 11125 call sol_log_126 mov64 r0, 3127 exit
fee_too_high (exit 1): a SetComputeUnitPrice was higher than the ceiling. Log 'fee too high' (12 bytes).
bad_ix_data (exit 2): our own ix data was not exactly 8 bytes. Log 'bad ix data' (11 bytes).
bad_account (exit 3): account 0 was not the Instructions sysvar. Log 'bad account' (11 bytes).
All three follow the same pattern: load the log string into r1, the length into r2, call sol_log_, set r0 to the exit code, exit.
lines 129-134
Read-only data: two pubkeys and three strings
129.rodata130 sysvar_ix_key: .byte 0x06, 0xa7, 0xd5, 0x17, 0x18, 0x7b, 0xd1, 0x66, 0x35, 0xda, 0xd4, 0x04, 0x55, 0xfd, 0xc2, 0xc0, 0xc1, 0x24, 0xc6, 0x8f, 0x21, 0x56, 0x75, 0xa5, 0xdb, 0xba, 0xcb, 0x5f, 0x08, 0x00, 0x00, 0x00131 cb_program_id: .byte 0x03, 0x06, 0x46, 0x6f, 0xe5, 0x21, 0x17, 0x32, 0xff, 0xec, 0xad, 0xba, 0x72, 0xc3, 0x9b, 0xe7, 0xbc, 0x8c, 0xe5, 0xbb, 0xc5, 0xf7, 0x12, 0x6b, 0x2c, 0x43, 0x9b, 0x3a, 0x40, 0x00, 0x00, 0x00132 msg_high: .ascii "fee too high"133 msg_bad: .ascii "bad ix data"134 msg_acct: .ascii "bad account"
Two 32-byte pubkey constants (the Instructions sysvar key and the ComputeBudget program id) plus three log strings. The pubkeys are baked into the .so at link time, same as the log strings. No relocations, no dynamic allocation, the whole program is one self-contained chunk.