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, 46.equ CB_LIMIT_IX_LEN, 57.equ SET_CU_LIMIT_DISC, 2
Six constants. The first three are the standard sysvar input-region offsets (same as fee_ceiling and program_allowlist): account 0's pubkey at 0x10, its data length at 0x58, its data block at 0x60. See balance_floor for why those specific numbers fall out of the per-account header layout.
Why EXPECTED_IX_DATA_LEN = 4? Our ix data is one u32 holding the floor (4 bytes). We use a u32 instead of a u64 because SetComputeUnitLimit's units field is itself a u32, there is no point storing a floor that ComputeBudget cannot represent. 1 u32 = 4 bytes.
Why CB_LIMIT_IX_LEN = 5? A ComputeBudget SetComputeUnitLimit instruction's data is 1 discriminator byte + 1 u32 units = 5 bytes total.
Why SET_CU_LIMIT_DISC = 2? ComputeBudget's discriminator table: RequestHeapFrame = 1, SetComputeUnitLimit = 2, SetComputeUnitPrice = 3, SetLoadedAccountsDataSizeLimit = 4. We want variant 2 (distinct from fee_ceiling, which wanted variant 3).
lines 11-23
Verify account 0 is 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
Same four-chunk sysvar pubkey compare as the other two sysvar-walking guards. Any mismatch exits 3 with 'bad account'. See fee_ceiling for the full explanation.
lines 25-37
Locate our own ix inside the sysvar data
25 ldxdw r2, [r1 + ACCT0_DATA_LEN]26 mov64 r3, r127 add64 r3, ACCT0_DATA28 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]
Same sysvar walk as fee_ceiling. r3 = pointer to the sysvar's serialized data. r5 = current_instruction_index, read from the LAST 2 bytes of the sysvar data (where the runtime stamps it). r9 = offsets[current_idx] (the byte offset of our own ix's serialization within the sysvar data).
r4 = r3 + r9 = base of our own ix's serialized form. Every byte we read from here is relative to r4.
lines 39-50
Skip the per-ix header to land on our ceiling u32
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 ldxw r6, [r9 + 2]
Same num_accounts * 33 + 34 hop as fee_ceiling to skip past num_accounts (2 bytes) + the account metas (33 bytes each) + the program_id (32 bytes), landing on our ix_data_len.
Validate ix_data_len == 4. Mismatch exits 2.
ldxw r6, [r9 + 2] reads the u32 floor into r6. ldxw loads 4 bytes (vs ldxdw's 8), zero-extending the top 32 bits of r6. r6 now holds the floor for the rest of the program. The +2 is because ix_data_len is itself a u16 (2 bytes), so the actual ix data starts 2 bytes past the length field.
lines 52-54
Loop setup with a 'found' flag
52 ldxh r8, [r3 + 0]53 mov64 r7, 054 mov64 r2, 0
r8 = num_instructions. r7 = 0 (outer counter). r2 = 0 (the found flag).
Why a found flag? This guard has to enforce something subtler than fee_ceiling. It requires that AT LEAST ONE SetComputeUnitLimit greater than or equal to the floor exists in the tx, AND that no SetComputeUnitLimit is below the floor. Single pass through the ixs: set r2 = 1 when we see a valid one. After the loop, if r2 is still 0, no SetComputeUnitLimit was present at all, so we fail.
lines 56-73
Walk every ix, compute its program_id pointer
56loop:57 jge r7, r8, check_found58 59 mov64 r4, r760 lsh64 r4, 161 add64 r4, r362 ldxh r5, [r4 + 2]63 64 mov64 r9, r365 add64 r9, r566 67 ldxh r4, [r9 + 0]68 69 mov64 r5, r470 mul64 r5, 3371 add64 r5, r972 add64 r5, 2
jge r7, r8, check_found exits the loop into the found-flag check (not directly into success). Even with no violation seen, we still need to verify we found at least one matching ix.
Same pattern as fee_ceiling for locating ix r7's program_id: offsets table, skip num_accounts * 33 + 2 bytes to land on the 32-byte program_id.
lines 74-90
Is this ix a ComputeBudget call?
74 lddw r4, cb_program_id75 76 ldxdw r0, [r5 + 0]77 ldxdw r1, [r4 + 0]78 jne r0, r1, next_ix79 80 ldxdw r0, [r5 + 8]81 ldxdw r1, [r4 + 8]82 jne r0, r1, next_ix83 84 ldxdw r0, [r5 + 16]85 ldxdw r1, [r4 + 16]86 jne r0, r1, next_ix87 88 ldxdw r0, [r5 + 24]89 ldxdw r1, [r4 + 24]90 jne r0, r1, next_ix
Load cb_program_id (the ComputeBudget program's 32-byte pubkey from rodata). Four-chunk u64 compare. First mismatch branches to next_ix (this ix is not a ComputeBudget call, move on).
lines 92-101
Is it specifically SetComputeUnitLimit, and does it meet the floor?
92 ldxh r4, [r5 + 32]93 jne r4, CB_LIMIT_IX_LEN, next_ix94 95 ldxb r4, [r5 + 34]96 jne r4, SET_CU_LIMIT_DISC, next_ix97 98 ldxw r4, [r5 + 35]99 jlt r4, r6, cu_too_low100 101 mov64 r2, 1
ix_data_len at offset 32 (right after the 32-byte program_id) must equal 5. Discriminator at offset 34 (program_id + ix_data_len u16) must equal 2. A length of 5 plus a discriminator of 2 effectively narrows us to SetComputeUnitLimit, distinct from fee_ceiling's price check (length 9, disc 3).
If both match, ldxw r4, [r5 + 35] reads the u32 units (4 bytes after the discriminator). ldxw zero-extends so the top 32 bits of r4 are 0.
jlt r4, r6, cu_too_low is strict less-than: if units is less than the floor, exit 1 immediately. Even one undersized SetComputeUnitLimit fails the whole tx, there is no point continuing the walk after a violation.
Otherwise, mov64 r2, 1 marks 'found a valid SetComputeUnitLimit' and falls through to next_ix.
lines 103-108
Advance and check the found flag at the end
103next_ix:104 add64 r7, 1105 ja loop106 107check_found:108 jeq r2, 0, cu_too_low
next_ix: add64 r7, 1; ja loop. Walk every ix.
check_found: jeq r2, 0, cu_too_low. If r2 is still 0 after the loop, no SetComputeUnitLimit was found anywhere in the tx. Fail with cu_too_low (exit 1).
This is the 'missing SetComputeUnitLimit also fails' contract. The guard treats 'no explicit limit declared' the same as 'limit below floor'. Solana would otherwise apply a default per-ix CU budget; we do not trust that default. The caller must declare what budget they expect.
lines 110-112
Happy path exit
110ok:111 mov64 r0, 0112 exit
ok: mov64 r0, 0; exit. At least one SetComputeUnitLimit was found and met the floor, and no SetComputeUnitLimit was below the floor. Success.
lines 114-133
Three failure paths, three exit codes
114cu_too_low:115 lddw r1, msg_low116 mov64 r2, 10117 call sol_log_118 mov64 r0, 1119 exit120 121bad_ix_data:122 lddw r1, msg_bad123 mov64 r2, 11124 call sol_log_125 mov64 r0, 2126 exit127 128bad_account:129 lddw r1, msg_acct130 mov64 r2, 11131 call sol_log_132 mov64 r0, 3133 exit
cu_too_low (exit 1): either no SetComputeUnitLimit was found at all, or one was below the floor. Log 'cu too low' (10 bytes). Same exit code for both cases because they mean the same thing semantically: the tx's compute budget does not meet the caller's minimum.
bad_ix_data (exit 2): our own ix data was not exactly 4 bytes. Log 'bad ix data' (11 bytes).
bad_account (exit 3): account 0 was not the Instructions sysvar. Log 'bad account' (11 bytes).
lines 135-140
Read-only data
135.rodata136 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, 0x00137 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, 0x00138 msg_low: .ascii "cu too low"139 msg_bad: .ascii "bad ix data"140 msg_acct: .ascii "bad account"
Two 32-byte pubkey constants (the Instructions sysvar key and the ComputeBudget program id) plus three log strings. Same shape as fee_ceiling because both guards walk the same sysvar looking for ComputeBudget instructions, just for different opcodes (and a different comparison direction).