hopper-framework 0.1.0

Zero-copy state framework for Solana. Typed account architecture, phased execution, validation graphs, zero-copy collections, layout evolution, and cross-program interfaces. Built on Hopper Native. no_std, no_alloc, no proc macros required.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
# Changelog

All notable changes to Hopper land here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) once
1.0 ships; pre-1.0 minor versions may break the API.

## [Unreleased]

### Added

- **`hopper-svm` Tier 4 — Language bindings (TypeScript / Python).**
  Closes the SVM roadmap by exposing the harness to non-Rust
  test code. Three pieces:

  **`hopper-svm-ffi`** — new C-ABI wrapper crate. Single
  `lib.rs` (~700 lines) exposes the `HopperSvm` surface as
  `extern "C"` functions through opaque handle types
  (`HopperSvmHandle`, `ExecutionResultHandle`). Builds three
  artifacts via `cargo build -p hopper-svm-ffi`: `cdylib` for
  dynamic loading, `staticlib` for embedding in compiled
  extensions, `rlib` for Rust integration tests. Forwards the
  `bpf-execution` feature so host languages can pick the
  Phase 1 (lightweight) or Phase 2 (real `.so` execution)
  build. Surface coverage:
  - Lifecycle: `hopper_svm_new`, `hopper_svm_with_solana_runtime`,
    `hopper_svm_set_compute_budget`, `hopper_svm_free`.
  - Account state: `hopper_svm_set_account`,
    `hopper_svm_get_lamports`, `hopper_svm_get_account_data`.
    The FFI keeps an internal account cache aligned with the
    Rust crate's stateless `process_instruction(ix, accounts)`
    model — host code seeds accounts once, dispatches feed the
    cache through, post-state replaces the cache.
  - Dispatch: `hopper_svm_dispatch` returns an opaque outcome
    handle; eight accessor functions read out logs, error,
    consumed units, transaction fee, post-accounts, return
    data.
  - String marshalling: `HopperFfiString` (`{ ptr, len }`)
    instead of null-terminated C strings — log lines may
    contain interior nulls. `hopper_svm_string_free` releases
    every owned string.
  - Version probe: `hopper_svm_ffi_version` returns the crate
    semver as a static null-terminated string for host-side
    compatibility checks.

  6 unit tests pin the FFI edges: string round-trip + empty
  handling, lifecycle smoke (new → with_solana_runtime →
  set_compute_budget → free), `u64::MAX` sentinel for unknown
  lamports, set + get account round-trip, replace-existing-
  account-in-place, version-string format.

  **`bindings/typescript/`** — `@hopper/svm` npm package.
  Minimal scaffold (no compile step beyond `tsc`):
  - `package.json` declaring `koffi` as the only runtime dep.
  - `tsconfig.json` strict-mode, ES2022 target.
  - `src/index.ts` (~450 lines) — wraps the FFI through
    `koffi.func` declarations, exposes high-level `HopperSvm`
    + `ExecutionResult` classes with `dispose()` + identical
    semantics to the Rust crate. Includes a hand-written
    base58 codec (no `bs58` dep) since pubkey display is the
    only place it's needed.
  - `README.md` covering build setup
    (`HOPPER_SVM_LIB_PATH` env var) + a quick-start example.

  **`bindings/python/`** — `hopper-svm` PyPI package via
  `cffi` ABI mode (no compile step):
  - `pyproject.toml` declaring `cffi >= 1.16` as the only
    runtime dep, Python ≥ 3.10.
  - `hopper_svm/__init__.py` re-exports the public API.
  - `hopper_svm/core.py` (~400 lines) — `cffi.FFI` `cdef`
    declarations matching the C surface exactly, plus
    Pythonic `HopperSvm` + `ExecutionResult` classes that
    are context managers (`with` blocks free the handle).
    Also includes a tiny base58 codec.
  - `README.md` mirroring the TS shape with Python idioms.

  Both bindings drive the *same* shared library — one source
  of truth in Rust, two host-language ergonomic shapes. Adding
  a new FFI export means: edit `hopper-svm-ffi/src/lib.rs`,
  add the koffi `lib.func` line in TS, add the cffi `cdef`
  line in Python. The pattern is mechanical enough that future
  surface growth doesn't drift between the bindings.

- **`hopper-svm` Tier 3 — Niche syscalls (introspection,
  obsolete sysvars, curve25519, heavy-crypto stubs).** New
  module `hopper-svm/src/bpf/tier3_syscalls.rs` plus the
  matching adapter shims in `bpf/adapters.rs` and the
  registrations in `bpf/engine.rs`. Closes the syscall surface
  gap — programs that link these names now load and dispatch
  cleanly under `--features bpf-execution`.

  **Introspection (fully implemented):**
  - `sol_get_stack_height` — reports `BpfContext::cpi_depth`.
    Outermost program runs at depth 1; each
    `sol_invoke_signed_*` increments. CU pinned at 100.
  - `sol_remaining_compute_units` — reads the meter post the
    syscall's own charge. CU pinned at 100.
  - `sol_get_processed_sibling_instruction` — Hopper Phase 2
    doesn't yet ledger sibling instructions, so the syscall
    returns `0` (mainnet's empty-list convention) for any
    index. CU pinned at 100.

  **Obsolete-but-referenced sysvars (stub buffers matching a
  fresh-validator state):**
  - `sol_get_slothashes_sysvar` — empty SlotHashes vec
    (`len(u64) = 0`).
  - `sol_get_slothistory_sysvar` — all-zero SlotHistory
    bitvec (16,392 bytes: 16,384 bitvec + 8 next_slot).
  - `sol_get_stakehistory_sysvar` — empty StakeHistory vec
    (`len(u64) = 0`).

  **Generic `sol_get_sysvar` (fully implemented):**
  - Mainnet wire `(sysvar_id_addr, offset, out_addr, length)`.
  - Adapter resolves the 32-byte ID, looks up bytes via the
    existing per-sysvar handlers (Clock, Rent, EpochSchedule,
    EpochRewards, LastRestartSlot, plus the new SlotHashes /
    SlotHistory / StakeHistory stubs), copies the requested
    `[offset, offset + length)` slice into `out`. CU pinned at
    100.

  **Curve25519 (fully implemented — uses existing
  `curve25519-dalek` dep):**
  - `sol_curve_validate_point` — Edwards (curve = 0) or
    Ristretto (curve = 1). Returns `0` for valid, `1` for
    invalid (mainnet's inverted-bool convention). Edwards
    validation includes the prime-order subgroup check
    (`is_torsion_free`); Ristretto validation is the
    decompression check. CU pinned at 159.
  - `sol_curve_group_op` — Add (op = 0), Sub (op = 1),
    Mul-by-scalar (op = 2). Edwards or Ristretto. Returns `0`
    on success (writes 32-byte result), `1` on invalid input
    (off-curve operand or non-canonical scalar). CU pinned at
    2,000.

  **Heavy crypto — clear-error stubs (Tier 4 work):**
  - `sol_poseidon` — Poseidon hash. Returns a structured
    `Custom` error naming `light-poseidon` as the canonical
    backing crate.
  - `sol_big_mod_exp` — RSA-style modular exponentiation.
    Stub names `num-bigint`.
  - `sol_alt_bn128_group_op` / `sol_alt_bn128_compression` —
    BN254 / alt_bn128 ops. Stub names `ark-bn254`.

  Why stubs instead of "unknown syscall": mainnet programs
  that link these names but don't always call them at runtime
  (feature-flagged paths, fallback branches) load cleanly
  under Hopper. Programs that DO hit the path see an
  actionable error message naming the missing dep, which
  surfaces the next step instead of a cryptic VM trap. CU
  pinned at 1 per stub call so the meter accounting stays
  honest if a future Tier 4 release wires up real impls
  behind the same names.

  **20+ unit tests** in `tier3_syscalls.rs` pin every layer:
  introspection (stack_height returns cpi_depth, remaining
  reports post-charge), sysvar buffer writes, generic accessor
  copy semantics + out-of-range error, curve validation
  (basepoint valid, junk invalid, unknown curve errors), group
  ops (add basepoint to itself round-trips, unknown op
  errors), stub messages contain actionable crate names, and
  out-of-meter short-circuiting on every syscall.

  Adapter layer in `bpf/adapters.rs` adds 13 new
  `declare_builtin_function!` shims (one per syscall),
  registered against the canonical sbpf names in
  `bpf/engine.rs::build_loader`.

- **`hopper-svm` Tier 2 (e) — Config / Stake / Vote programs.**
  Three native simulators close the validator-side parity gap
  (matching `quasar-svm`'s "default-loaded programs" surface)
  without pulling in any validator crates as dependencies. All
  three are registered automatically by
  `HopperSvm::with_solana_runtime()` and individually
  selectable via `with_config_program()` / `with_stake_program()`
  / `with_vote_program()`.

  - **`hopper-svm/src/spl/config_program.rs`** —
    `ConfigProgramSimulator` for `Config1111111111111111111111111111111111111`.
    Single-instruction shape: parses
    `keys_len(u64) + n Ã- (Pubkey + bool) + user_data`, validates
    every signer-flagged key against the instruction's account
    metas, writes user data into account 0 (padding/zeroing
    trailing bytes so a smaller Store doesn't leak the previous
    write). 3 unit tests pin: writes user data + zeroes tail,
    rejects unsigned signer-flagged key, rejects oversized
    payload. CU pinned at 450 (mainnet baseline).

  - **`hopper-svm/src/spl/stake_program.rs`** —
    `StakeProgramSimulator` for `Stake11111111111111111111111111111111111111`.
    Lifecycle slice: `Initialize` (0), `Authorize` (1),
    `DelegateStake` (2), `Withdraw` (4), `Deactivate` (5).
    Hand-coded 200-byte state layout matches upstream
    `StakeStateV2` bincode encoding so accounts round-trip
    cleanly: discriminator (u32 LE) + Meta
    (rent_exempt_reserve, authorized.staker,
    authorized.withdrawer, lockup.unix_timestamp, lockup.epoch,
    lockup.custodian) + optional Delegation (voter_pubkey,
    stake, activation_epoch, deactivation_epoch,
    warmup_cooldown_rate=0.25, credits_observed). `Withdraw`
    enforces a locked floor of `rent_exempt_reserve +
    active_stake` while delegated and not fully cooled, falling
    to just the rent-exempt floor once deactivation has cleared.
    Variants outside the slice (Split, Merge,
    AuthorizeWithSeed, InitializeChecked, AuthorizeChecked,
    AuthorizeCheckedWithSeed, SetLockup, SetLockupChecked,
    Redelegate, MoveStake, MoveLamports, DeactivateDelinquent)
    return a clear "not supported" error. 5 unit tests pin:
    Initialize writes Meta, full delegate-then-deactivate flow,
    Authorize swaps staker, Withdraw respects locked floor,
    unsupported variant errors. CU pinned at 750.

  - **`hopper-svm/src/spl/vote_program.rs`** —
    `VoteProgramSimulator` for `Vote111111111111111111111111111111111111111`.
    Administrative slice: `InitializeAccount` (0), `Authorize`
    (1), `Withdraw` (3), `UpdateValidatorIdentity` (4),
    `UpdateCommission` (5). Modeled header-slice (112 bytes):
    version (u32 LE) + node_pubkey + authorized_withdrawer +
    commission (u8) + authorized_voter at offset 80. Phase 1
    intentionally diverges from upstream's exact bincode for the
    versioned tail (lockouts, BTreeMap epoch->voter mapping)
    since application programs don't touch the TowerBFT
    machinery. Vote-emitting variants (Vote, VoteSwitch,
    UpdateVoteState{,Switch}, CompactUpdateVoteState,
    TowerSync{,Switch}, AuthorizeChecked, AuthorizeWithSeed)
    return a clear "not supported" error. 5 unit tests pin:
    InitializeAccount writes header, Authorize changes voter,
    Withdraw respects rent-exempt floor, UpdateCommission
    rejects commission > 100, unsupported variant errors. CU
    pinned at 3,000.

  Three new builders: `HopperSvm::with_config_program()`,
  `HopperSvm::with_stake_program()`,
  `HopperSvm::with_vote_program()`.
  `HopperSvm::with_solana_runtime()` now registers the full
  Solana validator-side surface: System (default) + Compute
  Budget + ALT + Config + Stake + Vote + SPL Token + Token-2022
  + ATA — closing the last out-of-the-box parity gap with
  `quasar-svm`.

- **`hopper-svm` Tier 2 (d) — Address Lookup Tables.** Two new
  modules close the v0-transaction support gap:
  - **`hopper-svm/src/alt.rs`** — byte-layout helpers
    + the `LookupTableMeta` struct + `read_meta` / `write_meta` /
    `address_count` / `read_address` / `append_addresses` /
    `resolve_lookup`. Wire format matches mainnet exactly:
    56-byte fixed header (discriminator u32, deactivation_slot
    u64, last_extended_slot u64, last_extended_slot_start_index
    u8, authority COption + 32-byte slot, 2-byte pad), then
    addresses packed 32 bytes each at offsets 56, 88, 120, …
    Hand-coded layout to avoid `bincode` dep growth. Constants
    pinned: `LOOKUP_TABLE_META_SIZE = 56`,
    `LOOKUP_TABLE_MAX_ADDRESSES = 256`,
    `DEACTIVATION_COOLDOWN_SLOTS = 513`. **6 unit tests** pin
    every layout edge: meta round-trips, frozen tables zero
    the authority slot, append-and-read, append rejects
    overflow, resolve writable-then-readonly, resolve rejects
    out-of-bounds, closeable-after-cooldown.
  - **`hopper-svm/src/spl/alt_program.rs`** —
    `AltProgramSimulator`, the BuiltinProgram impl for
    `AddressLookupTab1e1111111111111111111111111111`. Five
    instruction tags:
    - **0 / Create** — derives the PDA from
      `(authority, recent_slot, bump_seed)` under the ALT
      program ID, validates the supplied target address
      matches, allocates the 56-byte meta header, sets the
      authority + active state.
    - **1 / Freeze** — clears the authority field. Frozen
      tables can't Extend / Deactivate / Close.
    - **2 / Extend** — appends a `Vec<Pubkey>` to the data
      region, updates `last_extended_slot` +
      `last_extended_slot_start_index`, charges rent to the
      payer.
    - **3 / Deactivate** — writes the current slot into the
      table's `deactivation_slot`, starting the 513-slot
      cooldown.
    - **4 / Close** — moves lamports out, zeroes the data,
      reassigns ownership to the system program. Requires
      `is_closeable`: deactivated AND
      `current_slot - deactivation_slot > 513`.

  Authority enforcement on every state-mutating instruction —
  Extend / Deactivate / Close all check that `signer ==
  stored_authority` and reject otherwise. Frozen tables are
  immutable.

  **2 end-to-end unit tests** in `alt_program.rs`:
  - `full_alt_lifecycle` — create → extend → deactivate →
    close-rejected-before-cooldown → close-succeeds-after.
    Pins every state-machine transition + the cooldown
    boundary.
  - `freeze_blocks_extend` — Freeze → Extend rejected.

  **`HopperSvm::with_alt_program()`** registers the simulator
  against the canonical ALT program ID. **`HopperSvm::with_solana_runtime()`**
  now includes ALT in the registered set.

  **`HopperSvm::resolve_address_table_lookup(accounts,
  table_address, writable_indexes, readonly_indexes)`** —
  the v0-transaction resolution path. Reads the table from
  the supplied account state, expands writable + readonly
  index lists into concrete `(Vec<Pubkey>, Vec<Pubkey>)`
  pairs. Rejects: missing table account, invalid
  discriminator, table that's been deactivated past the
  cooldown (closed-but-not-yet-Closed state), out-of-bounds
  indexes. Tests that simulate v0 transactions can use this
  to project lookup-table-shaped messages onto the standard
  flat `AccountMeta` shape `process_transaction` consumes.

- **`hopper-svm` Tier 2 (c) — fee-payer accounting +
  transaction-level CU budget.** New
  `hopper-svm/src/fees.rs` ships `FeeCalculator`
  (default 5000 lamports/signature, configurable via
  `set_fee_calculator`), `count_unique_signers` for dedupe
  across the chain, `priority_fee` and `total_fee` formulas.

  New `HopperSvm::process_transaction(ixs, accounts, fee_payer)`
  is the **fee-aware** chain dispatcher:
  1. Computes `total_fee = base_fee + priority_fee`:
     - `base_fee = 5000 Ã- num_unique_signers`
     - `priority_fee = compute_unit_limit Ã- micro_lamports_per_cu / 1_000_000`
  2. Deducts the fee from `fee_payer` up front. If insufficient,
     aborts before the first instruction with
     `HopperSvmError::InsufficientFunds`.
  3. Runs every instruction in the chain; **fee stays
     deducted even if a later instruction fails** (matches
     mainnet's anti-spam rule).
  4. Returns `HopperExecutionResult` with new
     `transaction_fee_paid: u64` field populated.

  Compute-budget integration: the compute-budget program's
  `SetComputeUnitPrice` handler now writes into a shared
  `priority_fee_micro_lamports_per_cu` cell on `HopperSvm` so
  programs that include
  `ComputeBudgetInstruction::set_compute_unit_price(N)` early
  in their chain see the new rate applied to subsequent
  `process_transaction` calls. Setter helpers
  `set_priority_fee_micro_lamports_per_cu` and
  `priority_fee_micro_lamports_per_cu()` exposed for tests
  that want to seed the value directly.

  4 unit tests in `fees.rs` pin: default mainnet rate (5000
  lamports/sig), priority-fee integer-division semantics
  (sub-lamport remainders truncated), unique-signer dedupe
  across instruction-list boundaries, base+priority sum.

  `process_instruction_chain` keeps the no-fee semantics for
  fast unit tests; `process_transaction` is the
  mainnet-equivalent path.

- **`hopper-svm` Tier 2 (b) — system program parity.** All 8
  remaining system-program variants implemented, closing the
  surface to mainnet:
  - **Tag 3 — `CreateAccountWithSeed`**: parses
    base/seed/lamports/space/owner from the body, validates the
    target address against `Pubkey::create_with_seed`, allocates
    + assigns + funds the new account.
  - **Tag 9 — `AssignWithSeed`**: reassigns the owner of a
    seed-derived account; same derivation check.
  - **Tag 10 — `TransferWithSeed`**: debits a seed-derived
    source; the from_base must sign.
  - **Tag 4 — `AdvanceNonceAccount`**: rotates the durable
    nonce; the new nonce is deterministically synthesised from
    the harness's current `clock.slot` (since Hopper doesn't
    track recent_blockhashes — that sysvar is deprecated).
    Same-slot calls produce the same nonce; different-slot
    calls produce different nonces.
  - **Tag 5 — `WithdrawNonceAccount`**: moves lamports out of a
    nonce account; only the stored authority can withdraw.
  - **Tag 6 — `InitializeNonceAccount`**: writes the canonical
    80-byte nonce state with the requested authority and a
    starting durable nonce.
  - **Tag 7 — `AuthorizeNonceAccount`**: changes the stored
    authority; current authority must sign.
  - **Tag 11 — `UpgradeNonceAccount`**: legacy → current
    `Versions` migration (no-op since both layouts share the
    same byte shape in our impl).

  **Nonce-state byte layout** is hand-coded directly:
  ```text
  [0..4]   Versions tag (1 = Current)
  [4..8]   State tag (0 = Uninitialized, 1 = Initialized)
  [8..40]  authority (Pubkey)
  [40..72] durable_nonce (Hash, 32 bytes)
  [72..80] fee_calculator.lamports_per_signature (u64 LE)
  ```
  No `bincode` dep growth — the format is stable on mainnet
  and the 80-byte layout matches the upstream
  `solana_sdk::nonce::state::Versions`. Helper functions
  `read_nonce_state` / `write_nonce_state` /
  `synthesise_nonce_from_slot` keep the wire shape isolated
  in one spot for future maintenance.

  **9 new unit tests** added to `system_program.rs`:
  CreateAccountWithSeed validates derivation + rejects
  address mismatch, AssignWithSeed round trip,
  TransferWithSeed round trip, InitializeNonceAccount writes
  the 80-byte state, AdvanceNonceAccount changes nonce with
  slot, AuthorizeNonceAccount rejects wrong signer,
  WithdrawNonceAccount moves lamports, nonce-state
  read/write round-trip.

  **System program coverage now matches mainnet's hot path
  exactly.** Programs that use `system_instruction::transfer_with_seed`,
  durable-nonce transactions, or any of the other previously-
  rejected variants now run unmodified.

- **`hopper-svm` Tier 2 (a) — quasar-svm DX parity block.**
  Five small wins that close the surface gap with
  `quasar-svm`'s public API:
  - **`ExecutionOutcome::execution_time_us`** — wall-clock
    timing in microseconds, measured around the entire
    dispatch (built-in or BPF + post-validation). Stamped
    onto every outcome via a thin `dispatch_one` /
    `dispatch_one_inner` split. Mirrors quasar's
    `ExecutionResult.execution_time_us`. Read via
    `HopperExecutionResult::execution_time_us()`.
  - **Typed `assert_error(&expected)`** on
    `HopperExecutionResult` — structural equality on
    `HopperSvmError::describe()`. Mirrors quasar's
    `result.assert_error(ProgramError::InsufficientFunds)`.
    `assert_error_contains` (substring match) stays for
    flexible cases.
  - **Sysvar convenience setters** on `HopperSvm`:
    `set_clock`, `set_rent`, `set_epoch_schedule`,
    `set_last_restart_slot`, `set_epoch_rewards`. The big
    one: **`warp_to_slot(N)`** updates `clock.slot`,
    advances `unix_timestamp` by 400ms-per-slot (Solana's
    target slot duration), and recomputes `clock.epoch`
    against the configured `EpochSchedule`. Mirrors
    `quasar-svm`'s `svm.sysvars.warp_to_slot(200)`.
  - **`HopperSvm::with_solana_runtime()`** — chained
    builder that registers the full Solana runtime: system
    program (already from `new()`) + Compute Budget program
    + SPL Token + SPL Token-2022 + Associated Token Account
    simulators. Mirrors quasar's "SPL programs loaded by
    default on `QuasarSvm::new()`" without making `new()`
    itself heavyweight — fault-injection tests keep the
    bare `new()`, full-runtime tests call
    `.with_solana_runtime()`.
  - **`assert_inner_instruction_count(expected)`** on
    `HopperExecutionResult` — pin the CPI count for a
    program's instruction (e.g. "this transfer makes
    exactly 2 CPIs: system create + token init"). Plus
    `format_inner_instructions()` for ad-hoc transcripts.
    Mirrors `mollusk-svm`'s
    `Check::inner_instruction_count(N)` and quasar's
    equivalent.

  Together, these close the surface-level public-API gap
  with quasar-svm: every public method or field quasar
  exposes has a Hopper-side equivalent (and Hopper has
  more — `decode_header`, `hopper_accounts`,
  `decoded_logs`, the validation-policy switch, `Engine`
  trait seam). The remaining quasar-svm gap is bundled
  real `.so` files (we ship simulators) and the
  TypeScript / Python bindings (separate effort tracked as
  Tier 4).

- **`hopper-svm` Tier 1 — pre/post account validation.** New
  `hopper-svm/src/validation.rs` ships
  `validate_post_state(program_id, metas, pre, post, policy)`
  that enforces six structural invariants between an
  instruction's pre- and post-state, mirroring what
  `solana-bpf-loader-program` checks on mainnet:
  1. **Read-only accounts cannot change** — lamports, data,
     owner, executable all immutable when `meta.is_writable`
     is false.
  2. **Lamport conservation** across all metas — the runtime
     does not mint or destroy lamports.
  3. **Data writes require ownership** — `pre.owner == program_id`,
     with an exception for the system program creating an
     account (empty pre, system_program owner).
  4. **Owner reassignment requires ownership** — `pre.owner == program_id`,
     with the same creation exception. Backs
     `system_instruction::assign`.
  5. **Executable flag is immutable** — programs cannot toggle
     it; the BPF loader's `Finalize` is the only legitimate
     setter, and Phase 1 doesn't simulate that path.
  6. **Lamport debits require ownership** — `n.lamports < p.lamports`
     requires `pre.owner == program_id`. Anyone can credit any
     account; only the owner can debit. Combined with rule 2
     this makes "lamport theft" structurally impossible
     without ownership.

  Wired into `HopperSvm::dispatch_one` as a post-execution
  check on every successful instruction, both Phase 1 (built-in)
  and Phase 2 (BPF) paths. Validation failures roll back
  account mutations to the pre-state and surface a structured
  `HopperSvmError::AccountValidationFailed { account, reason }`
  so the test sees the offending account + the specific rule
  that fired. The validation step also writes a
  `Validation failed: <reason>` line into the log transcript
  so snapshot tests catch rule trips.

  **Default `Strict`, opt out via `with_lax_validation()`** for
  fast unit tests where structural invariants don't apply
  (hand-written Rust simulators don't always follow Solana's
  account-mutation rules). New `ValidationPolicy` enum exposed
  through the `hopper_svm` re-export. **9 unit tests** pin
  every rule against both passing and failing fixtures: identity,
  read-only mutation rejection, lamport-conservation rejection,
  legitimate transfer passes, non-owner data write rejection,
  data write on creation allowed, non-owner assign rejection,
  owner-self-assign passes, executable toggle rejection,
  non-owner lamport debit rejection, non-owner lamport credit
  allowed, lax policy disables all checks.

  This is the bug-revealing layer: a Hopper program that
  incorrectly mutates a non-owned account, breaks lamport
  conservation, or toggles the executable flag now FAILS the
  `hopper-svm` test with a clear pointer at the rule that
  fired, instead of silently passing locally and reverting on
  mainnet.

- **`hopper-svm` Tier 1 — Compute Budget program builtin.**
  New `hopper-svm/src/compute_budget_program.rs` ships
  `ComputeBudgetProgramSimulator` registered via
  `HopperSvm::with_compute_budget_program()`. Handles the five
  compute-budget instructions: `RequestUnits` (deprecated, tag
  0), `RequestHeapFrame` (1), `SetComputeUnitLimit` (2),
  `SetComputeUnitPrice` (3), `SetLoadedAccountsDataSizeLimit`
  (4). The signature feature: `SetComputeUnitLimit` writes to
  a shared `pending_cu_limit` cell on `HopperSvm`; the next
  `dispatch_one` reads + applies it as the budget override
  for that instruction. This makes
  `ComputeBudgetInstruction::set_compute_unit_limit(N)` take
  effect for subsequent instructions in
  `process_instruction_chain`, matching mainnet's
  transaction-level budget semantics within Hopper's
  instruction-by-instruction dispatch model. `MAX_COMPUTE_UNIT_LIMIT
  = 1_400_000` enforced (mainnet's hard cap). 4 unit tests
  pin the pending-cell write, the over-max rejection (cell
  untouched on rejection), the `RequestHeapFrame` Phase-1
  warning behavior, and the unknown-tag error message.

- **`hopper-svm` Tier 1 — `inner_instructions` field on
  ExecutionOutcome.** New `inner_instructions: Vec<InnerInstruction>`
  field on `ExecutionOutcome`. Each entry captures `program_id`,
  `accounts: Vec<AccountMeta>`, `data: Vec<u8>`, and
  `stack_height: u32` (1 = outermost; 2 = first-level CPI; …;
  capped at `MAX_CPI_DEPTH = 4`). Populated by
  `bpf::cpi::dispatch_cpi` after each successful inner call —
  the parent CPI is recorded first, then the inner call's own
  `inner_instructions` (CPIs the inner program made) are
  flattened in dispatch order. New `InnerInstruction` type
  exported from `crate::engine`. New
  `HopperExecutionResult::inner_instructions()` accessor.
  Mirrors the `inner_instructions` slice mainnet records on
  transaction metadata. **2 new unit tests** in
  `bpf/cpi.rs`: `dispatch_records_inner_instruction` pins the
  single-call recording with correct stack_height, and
  `nested_cpis_flatten_in_dispatch_order` pins that nested
  CPIs flatten parent-before-child in dispatch order with
  correct stack-height assignment.

- **`hopper-svm` Tier 1 — Token-2022 simulator.** New
  `hopper-svm/src/spl/token_2022.rs` ships
  `SplToken2022Simulator` registered via
  `HopperSvm::with_spl_token_2022_simulator()`. The legacy 9
  tags (0/1/3/4/5/7/8/9) delegate to the SPL Token
  simulator's logic — the on-disk Mint/Account layout is
  identical for non-extension accounts, and the owner check
  inside the Token handlers compares against `ctx.program_id`
  which is the Token-2022 ID at this dispatch site. Extension
  tags (22+) return a structured "Phase 1 doesn't support
  Token-2022 extensions yet" error pointing at
  `add_program(&id, "spl_token_2022")` for the real `.so`
  fallback. 3 unit tests pin transfer-via-delegation,
  extension-tag rejection, and InitializeMint preserving the
  Token-2022 owner.

- **`hopper-svm` Tier 1 — ATA simulator.** New
  `hopper-svm/src/spl/ata.rs` ships `SplAtaSimulator`
  registered via `HopperSvm::with_spl_associated_token_simulator()`.
  Handles `Create` (tag 0) and `CreateIdempotent` (tag 1).
  Validates the derived ATA address via
  `get_associated_token_address_with_program_id`, the system
  + token program addresses, and the mint's owner; allocates
  + initialises the token account inline (no CPI dispatch
  needed because the operation is deterministic and we have
  direct account-state access). Idempotent path validates
  the existing ATA matches the requested `(wallet, mint)`
  pair — wrong-mint reuse is a structured rejection. 4 unit
  tests pin: legacy-Token ATA create, Token-2022 ATA create
  (different derived address), wrong-derived-address
  rejection, idempotent-on-existing no-op + idempotent-on-
  wrong-existing rejection.

  Also adds `HopperSvm::with_spl_simulators()` — convenience
  builder that registers all three SPL simulators in one call.

- **`hopper-svm` Tier 1 — bundled SPL Token simulator.** New
  `hopper-svm/src/spl/token.rs` ships `SplTokenSimulator`,
  a pure-Rust `BuiltinProgram` impl of the 8 most-used SPL
  Token instructions: `InitializeMint` (0), `InitializeAccount`
  (1), `Transfer` (3), `Approve` (4), `Revoke` (5), `MintTo`
  (7), `Burn` (8), `CloseAccount` (9). Register against
  `SPL_TOKEN_PROGRAM_ID` via the new
  `HopperSvm::with_spl_token_simulator()` builder method.

  **Why a simulator and not a bundled `.so`?** Three reasons:
  - **Version stability.** No `.so` bytes to re-vendor on
    every Anza release; the Rust source updates with our
    semver cycle.
  - **Speed.** Phase-1 builtin dispatch is 10-100Ã- faster than
    going through the BPF interpreter for the same
    instructions. Token transfers in tests run essentially
    free.
  - **Hopper-owned.** Every layer is hand-written Rust we can
    audit. Embedding third-party `.so` bytes would be an
    opaque trust dependency.

  Validation matches `spl_token::processor` end-to-end against
  the wire format: account-owner check (must be SPL Token
  program ID), mint-mismatch rejection on `Transfer` and
  `Burn`, signer = owner OR delegate (with delegated-amount
  decrement) on auth paths, `is_initialized` enforcement on
  `Mint` and on token-account state, supply / amount overflow
  detection via checked arithmetic, `CloseAccount` rejecting
  non-empty accounts.

  Unsupported tags (`FreezeAccount`, `*Checked` variants,
  `SetAuthority`, `InitializeMultisig`, etc. — 16 total) return
  a structured `BuiltinError` with the supported-tag list so
  test failures are actionable. Programs that need an
  unsupported instruction can fall back to BPF by registering
  the real SPL `.so` via `HopperSvm::add_program`.

  Token-2022 + ATA simulators land in subsequent passes; the
  same `BuiltinProgram` pattern applies. **3 unit tests** pin
  the happy path (initialize_mint → initialize_account →
  mint_to → transfer → burn against a single state machine
  with every numeric invariant verified), the mint-mismatch
  rejection, the close-account-rejects-non-empty rule, and
  the unsupported-tag error message.

- **Hopper segment system polish — DX cohesion pass.** Audit
  of `crates/hopper-core/src/account/`, `segment_map.rs`, and
  the macro-emitted accessors found three documentation gaps
  worth tightening:
  - Read-only segment escape (`<field>_segment_ref`) and the
    pre-declared `<field>_<seg>_ref` accessor now have the
    same RAII-contract paragraph as their mutable
    counterparts. Authors hovering over the generated
    accessor see the lease semantics inline.
  - Module-level cross-link added between `segment_map`
    (compile-time `StaticSegment`) and `account::registry`
    (runtime `SegmentDescriptor`). Resolves a recurring
    "should I use SegmentMap or SegmentRegistry?" question.
  - The "compile-time vs runtime segments" decision matrix is
    now in both module headers, so a developer landing in
    either file sees both options.

  No code changes — just documentation tightening that's
  safe to ship and immediately improves the DX of the
  segment system.

- **`hopper-svm` Phase 2.3 step 12 — `sol_invoke_signed_rust`
  fully implemented.** The Rust-ABI CPI variant — what
  `solana_program::program::invoke_signed` emits by default —
  now parses through Rust's `Rc<RefCell<&mut u64>>` and
  `Rc<RefCell<&mut [u8]>>` AccountInfo wrappers + the
  three-level-nested `&[&[&[u8]]]` signer-seeds shape, runs
  the same `cpi::verify_signer_seeds` + `cpi::dispatch_cpi`
  pipeline as the C variant, and writes mutations back
  through the resolved Rust-shape pointers. Realloc-across-
  CPI honored via the `data_len` slot inside the
  `RefCell<&mut [u8]>`.

  **Layout pinned in one file.** All version-sensitive
  offsets (`AccountInfo`'s 48-byte struct, `RcInner<T>`'s
  16-byte ref-count header, `RefCell<T>`'s 8-byte borrow
  flag, `Vec<T>`'s 24-byte ptr-cap-len header, the 34-byte
  `AccountMeta` stride, etc.) live in
  `bpf/cpi_rust::layout` as named constants. A future Anza
  toolchain bump that shifts any of these is a one-file
  fixup. Six unit tests pin every constant against its
  expected value so a silent shift produces a test failure
  rather than wrong runtime behaviour.

  **Pointer-chase helpers** in `cpi_rust`:
  - `follow_rc_refcell_u64(rc_ptr) → u64_addr` — walks
    `RcInner` (skip 16) → `RefCell` (skip 8) → `&mut u64`
    (read pointer).
  - `follow_rc_refcell_slice(rc_ptr) → (data_addr, data_len)`
    — same chain, but the value at the end is a 16-byte fat
    pointer.
  - `parse_account_infos`, `parse_instruction`,
    `parse_signer_seeds` — full structured readers for the
    three top-level wire shapes the Rust ABI passes.
  - `build_parsed_cpi` — the entry point the syscall
    adapter calls. Returns a `(ParsedCpi, Vec<RustAccountInfo>)`
    tuple where the second element captures every writeback
    address the post-call sync needs.

  With Rust-ABI in place, `hopper-svm` Phase 2 covers **both
  CPI variants** end-to-end. The BPF surface is now
  feature-complete for the typical Hopper test workload:
  programs that use the standard `invoke_signed`,
  `invoke_signed_unchecked`, all sysvars, all crypto
  syscalls, all log + memory + return-data syscalls, and PDA
  derivation should run unmodified under `hopper-svm` once
  `cargo check --features bpf-execution` clears.

  Phase 2.4+ remaining: integration smoke tests against a
  compiled `.so`, the obsolete sysvars (`SlotHashes`,
  `SlotHistory`, `StakeHistory`, `Fees`,
  `RecentBlockhashes`) most modern Hopper programs don't
  read, the `inner_instructions` slice on `ExecutionOutcome`,
  and JIT execution.
- **`hopper-svm` Phase 2.2 step 11 — CPI polish: log
  threading, realloc-across-CPI, EpochRewards sysvar.** Three
  follow-ups to the step-10 CPI implementation:
  - **Inner-instruction logs land in the outer transcript.**
    The `CpiDispatcher` closure type now takes
    `&mut LogCapture` so the inner program's `Program <id>
    invoke [N+1]`, `Program log:`, and `Program <id> success`
    framing append directly to the outer call's log buffer.
    Test-side snapshot diffs over CPI now read as one
    coherent transcript across the depth boundary. The
    `LogCapture::invoke` / `success` framing already tracks
    depth; sharing the buffer is what makes the depth
    tracking observable. New unit test
    `dispatcher_logs_thread_into_outer_transcript` pins the
    cross-boundary append.
  - **Realloc tail across CPI**. Phase 2.1's writeback
    clamped at the originally-mapped data capacity, so an
    inner program that grew an account's data was silently
    truncated. Phase 2.2 honors the
    `MAX_PERMITTED_DATA_INCREASE = 10240`-byte realloc tail
    the parameter buffer already reserves: the writeback
    accepts up to `original_data_len + 10240` bytes,
    updates the `SolAccountInfo`'s `data_len` field at byte
    offset 16 of its 56-byte record, and emits a
    `Program log: warning: CPI realloc truncated …`
    diagnostic if the inner call exceeded even the tail.
    The writeback path now produces results bit-identical to
    what `solana-bpf-loader-program` does for a CPI that
    reallocs an account.
  - **`sol_get_epoch_rewards_sysvar`** — final sysvar in
    scope. 96-byte wire layout (u64 distribution_starting_block_height
    + u64 num_partitions + 32-byte parent_blockhash + u128
    total_points + u64 total_rewards + u64 distributed_rewards
    + bool active + 15-byte zero pad — `#[repr(C)]`-shaped to
    match `solana_sdk::epoch_rewards::EpochRewards`). New
    `EpochRewards` struct + `Sysvars::epoch_rewards` field
    (defaults to "no rewards distributing"). Registered as
    the 25th syscall in the engine loader. New unit test
    `epoch_rewards_layout_canonical` pins every field offset
    + the 15-byte zero pad against the canonical wire format.

  **Phase 2 cumulative coverage**: 25 syscalls — 12 Phase 2.0
  + 2 PDA + 5 sysvar + 1 heap + 3 crypto + 2 CPI. The BPF
  surface is feature-complete for the typical Hopper test
  workload. Remaining for Phase 2.3+: the Rust-ABI CPI
  variant (`sol_invoke_signed_rust`), inner-instruction
  tracking field on `ExecutionOutcome` (the upstream
  `inner_instructions` slice), and the obsolete sysvars
  (`SlotHashes`, `SlotHistory`, `StakeHistory`, `Fees`,
  `RecentBlockhashes`) most modern Hopper programs don't
  read.
- **`hopper-svm` Phase 2.1 step 10 — CPI wired in.** Adds
  `sol_invoke_signed_c` (full implementation) and
  `sol_invoke_signed_rust` (registered with structured
  "Phase 2.2" error so programs see clean failure rather than
  "missing syscall"). The C-ABI variant ships:
  - **Wire-format parsing** of `SolInstruction` (40 bytes →
    program_id / metas / data pointers + lengths),
    `SolAccountMeta` (16 bytes per entry — pubkey_addr + flags),
    `SolAccountInfo` (56 bytes per entry — key_addr +
    lamports_addr + data_len + data_addr + owner_addr +
    rent_epoch + flags), and the nested `SolSignerSeeds[m]`
    pointing at `SolSignerSeed[q]` pointing at byte slices.
    Every pointer translation goes through `MemoryMapping::map`
    so an out-of-region read fails the syscall cleanly instead
    of segfaulting the host.
  - **Signer-seed verification** — for each meta marked
    `is_signer` in the inner instruction, either the same
    pubkey was a signer in the outer instruction (forwarded-
    signature path), or one of the supplied seed sets derives
    to the signer's pubkey under the calling program's ID
    (PDA-signing path). Reuses `do_sol_create_program_address`
    from step 6 — no new crypto. Returns
    `cpi_err::SIGNER_SEEDS_INVALID = 3` on mismatch.
  - **Recursive dispatch** through a `CpiDispatcher` closure
    on `BpfContext` that the engine populates with a clone of
    the `HopperSvm`. The closure runs `HopperSvm::dispatch_one`
    one depth deeper. **Depth bounded at `MAX_CPI_DEPTH = 4`**
    matching mainnet — `cpi_err::DEPTH_EXCEEDED = 2` past the
    limit.
  - **Account-state writeback** — after the inner call returns,
    every modified writable account's lamports / owner / data
    are written back through the SolAccountInfo pointers the
    caller supplied. Outer program continues reading from
    those addresses on resume so mutations are visible.
    Phase 2.1 clamps writeback at the originally-mapped data
    capacity; Phase 2.2 will plumb the realloc tail into the
    SolAccountInfo's `data_len` so growth across CPI is
    observable.
  - **CU costs** — 1 000 CU per invoke (matches mainnet's
    `invoke_units`).
  - **`MAX_SIGNERS = 16`** enforced — a 17th signer-seed set
    is `cpi_err::TOO_MANY_SIGNERS = 4`.
  - **6 new unit tests** in `bpf/cpi.rs` pin: outer-signer
    pass-through accepted, unauthorised signer rejected,
    `MAX_SIGNERS` enforcement, PDA-derived signer accepted
    (round-trips through `do_sol_create_program_address`),
    `dispatch_cpi` rejects recursion past `MAX_CPI_DEPTH`,
    `dispatch_cpi` returns `FAILED` when no dispatcher is
    configured (Phase 1 path).

  **Phase 2.2 (next session)**: full `sol_invoke_signed_rust`
  parsing through the `Rc<RefCell<…>>` AccountInfo layout
  (Rust-internal layout that drifts between toolchain
  versions; deferring keeps 2.1 stable), realloc tail
  plumbing across CPI boundaries, inner-instruction tracking
  for the `inner_instructions` log field. After 2.2, the BPF
  surface is feature-complete for the typical Hopper test
  workload.

  With CPI in place, `hopper-svm` Phase 2 covers **24 syscalls**
  (12 Phase 2.0 + 2 PDA + 4 sysvar + 1 heap + 3 crypto + 2 CPI),
  matching the typical-program coverage of `mollusk-svm` while
  remaining 100% Hopper-owned above the eBPF interpreter.
- **`hopper-svm` Phase 2.1 step 9 — crypto syscalls wired in.**
  Adds `sol_keccak256_`, `sol_blake3`, and
  `sol_secp256k1_recover_` to the BPF engine. Each delegates to
  a published, well-vetted crate:
  - `sol_keccak256_` → `sha3 = "0.10"`'s `Keccak256` (the legacy
    Ethereum variant — *not* the standardised SHA3-256, which
    differs in padding and produces different digests).
  - `sol_blake3` → `blake3 = "1"`.
  - `sol_secp256k1_recover_` → `secp256k1 = "0.29"` with the
    `recovery` feature.
  Wire shape: hashes accept the same `(addr, len)` chunk-list
  format as `sol_log_data` and the PDA-derive seed list — the
  `translate_seeds` adapter helper is reused. secp256k1 recover
  takes a 32-byte hash, recovery id (0..=3), 64-byte (r || s)
  signature, and writes a 64-byte uncompressed public key
  (X || Y, no leading 0x04 marker — matches upstream's wire).
  CU costs match the production runtime: hash = 85 base + 1
  per 16-byte chunk, secp256k1 recover = 25_000 flat. **8 new
  unit tests** pin known-good digests for Keccak-256
  (empty input → c5d24601…, "abc" → 4e03657a… — pinning these
  exact hex strings catches the SHA3-vs-Keccak swap bug class),
  BLAKE3 empty input → af1349b9…, streaming (multi-chunk) hashing
  matches one-shot, hash CU formula at 0/1/16/17/32 bytes,
  secp256k1 rejects bad signatures, secp256k1 rejects out-of-
  range recovery_id (7), out-of-meter on hash returns OutOfMeter
  without touching the output.

  Phase 2.1 remaining: **CPI** (`sol_invoke_signed_*`) — the
  recursive harness-dispatch one. After that, the BPF surface
  is feature-complete for the typical Hopper test workload.
- **`hopper-svm` Phase 2.1 step 8 — heap allocator wired in.**
  Adds `sol_alloc_free_` to the BPF engine. Bump-allocator
  semantics (matches upstream `agave-syscalls::SyscallAllocFree`):
  alloc returns monotonically-increasing 8-byte-aligned VM
  addresses inside the 32 KiB heap region at `MM_HEAP_START`;
  free is a no-op. Heap exhaustion returns null (0) without
  moving the cursor, so a subsequent alloc that fits can still
  proceed. Cursor lives on `BpfContext::heap_cursor` and resets
  to 0 at every fresh instruction (matches per-instruction
  allocator lifetime). New constants exposed: `HEAP_ALIGN = 8`,
  `HEAP_SIZE = 32 KiB`, `HEAP_VM_START = 0x300000000`. **6 new
  unit tests** pin: monotonic VM addresses with expected offsets
  (16, 16+24, etc.), 1-byte alloc rounds to 8, 9-byte rounds to
  16, free-is-noop (cursor unchanged after a free), exhaustion
  returns null and leaves cursor untouched (subsequent fitting
  allocs still succeed), zero-size alloc returns the cursor
  address without moving it (sentinel pattern), out-of-meter
  returns null without partial cursor movement.

  Phase 2.1 remaining: crypto syscalls (`sol_keccak256_`,
  `sol_secp256k1_recover_`, `sol_blake3`), CPI
  (`sol_invoke_signed_*`).
- **`hopper-svm` Phase 2.1 step 7 — sysvar fetches wired in.**
  Adds `sol_get_clock_sysvar`, `sol_get_rent_sysvar`,
  `sol_get_epoch_schedule_sysvar`, `sol_get_last_restart_slot_sysvar`
  to the BPF engine. Each writes a fixed-size `#[repr(C)]`-shaped
  byte buffer that's wire-compatible with the upstream
  `solana_sdk::sysvar::*` structs:
  - `Clock` — 40 bytes (5 Ã- 8-byte fields).
  - `Rent` — 24 bytes (u64 + f64 + u8 + 7-byte zero pad).
  - `EpochSchedule` — 40 bytes (2 Ã- u64 + bool with 7-byte pad +
    2 Ã- u64).
  - `LastRestartSlot` — 8 bytes (single u64).
  Sysvar state lives on `Sysvars` (extended this release with
  `EpochSchedule` + `LastRestartSlot` fields, defaulted to
  mainnet-typical values). The engine snapshots sysvars onto
  `BpfContext::sysvars` at instruction start so a program sees
  a consistent view even if the outer test code mutates the
  harness mid-chain. **6 new unit tests** pin canonical layout
  for each sysvar (specifically including the zero-padding
  bytes — a future change to `Rent` or `EpochSchedule` can't
  silently leak garbage into the wire format), short-buffer
  rejection, and out-of-meter short-circuit.

  Phase 2.1 remaining: heap alloc (`sol_alloc_free_`), crypto
  syscalls (`sol_keccak256_`, `sol_secp256k1_recover_`,
  `sol_blake3`), CPI (`sol_invoke_signed_*`).
- **`hopper-svm` Phase 2.1 — PDA derivation syscalls wired in.**
  Adds `sol_create_program_address` and `sol_try_find_program_address`
  to the BPF engine's syscall registry. Pure compute (SHA-256 of
  `seedâ‚€ â€- seed₁ â€- … â€- program_id â€- "ProgramDerivedAddress"`,
  followed by an Ed25519 curve-point rejection through
  `curve25519-dalek`); no harness recursion, so there's no
  semantic surprise area. Rejects the same edge cases the
  upstream runtime does: `>16 seeds`, individual seed `>32
  bytes`, candidate landing on the curve. `try_find` walks
  bumps `255 → 0` until [`do_sol_create_program_address`] returns
  Ok, charging 1500 CU per attempt — same per-attempt cost as
  the production runtime. Adds `sha2 = "0.10"` and
  `curve25519-dalek = "4"` (small, Anza-vetted, already used
  elsewhere in the workspace) as deps. **7 new unit tests**:
  determinism across calls, `MAX_SEEDS` enforcement, `MAX_SEED_LEN`
  enforcement, per-call CU charging, `try_find` termination +
  determinism, marker-string pin (`"ProgramDerivedAddress"`),
  `try_find` returns None at the seed-count limit. Phase 2.1
  remaining: sysvar reads (`sol_get_*_sysvar`), heap alloc
  (`sol_alloc_free_`), crypto (`sol_keccak256_`,
  `sol_secp256k1_recover_`, `sol_blake3`), CPI
  (`sol_invoke_signed_*`).
- **`hopper-svm` Phase 2 — real BPF execution wired in.**
  Feature-gated behind `--features bpf-execution` (off by default
  so Phase 1's slim built-in path stays the out-of-the-box
  experience). When enabled, `HopperSvm::add_program(&id, "name")`
  loads `target/deploy/<name>.so`, registers it against `id`, and
  subsequent `process_instruction` calls dispatch real Hopper BPF
  programs through Anza's canonical `solana-sbpf 0.20`
  interpreter.
  - **Five-module split** under `hopper-svm/src/bpf/`:
    `parameter` (canonical Solana parameter-buffer serialiser /
    deserialiser, sbpf-independent, fully tested), `context`
    (`BpfContext` impls `solana_sbpf::vm::ContextObject`),
    `syscalls` (pure-Rust `do_*` logic), `adapters`
    (`declare_builtin_function!` calls — the only file that
    touches sbpf macro/translation API surface), `engine`
    (`BpfEngine` with ELF loading + memory regions + VM lifecycle).
    Drift between sbpf minor versions concentrates in `adapters`
    and `engine`; the rest stays untouched.
  - **12 syscalls in scope this release**: `sol_log_`,
    `sol_log_64_`, `sol_log_pubkey`, `sol_log_compute_units_`,
    `sol_log_data`, `sol_panic_`, `sol_memcpy_`, `sol_memset_`,
    `sol_memcmp_`, `sol_memmove_`, `sol_set_return_data`,
    `sol_get_return_data`. CU costs match the production runtime
    defaults so Phase 2 CU readouts equal mainnet figures.
  - **Phase 2.1 deferred**: CPI (`sol_invoke_signed_*` —
    recursive harness dispatch + signer-seed verification + acct
    remapping each have subtleties warranting focused work),
    sysvar reads (`sol_get_*_sysvar`), PDA derivation
    (`sol_create_program_address` / `sol_try_find_program_address`),
    heap alloc (`sol_alloc_free_`), and the crypto syscalls
    (`sol_keccak256_`, `sol_secp256k1_recover_`, `sol_blake3`).
    Each is documented in `bpf/syscalls.rs`'s module header.
  - **Memory layout** matches the production runtime: rodata at
    `MM_RODATA_START`, stack (256 KiB) at `MM_STACK_START`, heap
    (32 KiB) at `MM_HEAP_START`, parameter buffer at
    `MM_INPUT_START`. Stack and heap match
    `solana_program_runtime`'s defaults so Hopper test CU
    readouts reflect production stack-frame and heap-cost
    accounting.
  - **Failure semantics** match Phase 1: VM error or captured
    `sol_panic_` rolls back partial mutations and surfaces a
    `HopperSvmError::BuiltinError` with the program ID and
    captured message.
  - **27 unit tests** added across the five Phase-2 modules
    (parameter buffer round-trip + duplicate-meta compaction +
    realloc tail observation, BpfContext meter saturation,
    syscall logic for every `do_*` including out-of-meter
    short-circuit and base64 padding for all six length-mod-3
    cases, adapter range-overlap detection, engine ELF-miss
    returns `None`, merge-accounts replace-and-append).
  - **Build verification caveat**: the sandbox running this
    pass had no Rust toolchain, so `cargo check --features
    bpf-execution` was not runnable locally. The `adapters` and
    `engine` modules are written against `solana-sbpf 0.20.0`'s
    docs.rs surface; first-compile fixups (if any) will
    concentrate in three places: (1) `declare_builtin_function!`
    macro syntax for adapters, (2) `MemoryRegion::new_writable`
    / `MemoryMapping::new` argument ordering, (3)
    `Executable::from_elf` + `EbpfVm::new` + `execute_program`
    return shapes. Each is one of small, well-bounded sites the
    user's "I'll keep it up to date" maintenance commitment
    covers.
- **`hopper-svm` crate — Hopper-native execution harness, no
  external SVM wrapper.** Wholesale rewrite of the previous
  `mollusk-svm`-wrapped Phase 1. The harness is now Hopper's
  through and through: built-in program registry, system-program
  processor, compute meter, log buffer, sysvar state (clock + rent
  with deterministic defaults), account input/output, error model,
  and Hopper-aware result decoders are all implemented here from
  scratch. **Zero dependency on `mollusk-svm`, `quasar-svm`, or any
  other framework's harness.** Phase 1 ships a complete working
  built-in execution path:
  - `HopperSvm::new()` registers the system program by default.
  - `BuiltinProgram` trait + `InvokeContext` let users register
    custom built-ins for unit-test simulators or fault injection.
  - `SystemProgram` reference impl covers `CreateAccount`,
    `Transfer`, `Allocate`, `Assign` (the four most-used variants;
    `nonce` and `seed` siblings raise a clear "Phase 2" error so a
    test that hits them fails fast with an actionable message).
  - `Engine` trait is the seam for Phase 2: a future `BpfEngine`
    that wraps `solana-sbpf` lands as one new module plus one
    extra fall-through line in `HopperSvm::dispatch_one` — no
    other module changes.
  - `process_instruction_chain` carries account state forward
    across the chain and emits a chained log transcript with
    `# ix[N]` section dividers. Failure aborts the chain and rolls
    back partial mutations atomically (matching the on-chain
    runtime's all-or-nothing instruction effects).
  - `ComputeBudget` charges the system program a fixed 150 CU
    (matches mainnet) and configurable defaults for custom
    built-ins.
  - `LogCapture` emits the runtime's exact wire format —
    `Program <id> invoke [N]` / `Program log: <msg>` /
    `Program <id> consumed N of M compute units` /
    `Program <id> success` — so snapshot tests stay portable.
  - `HopperExecutionResult::decode_header(&pk)` reads the 16-byte
    Hopper account header by address; `hopper_accounts()` filters
    resulting accounts to only those with a valid Hopper header;
    `decoded_logs()` strips runtime framing and returns only
    program-emitted lines for snapshot stability.
  - Token factories (`create_keyed_system_account`,
    `create_keyed_mint_account[_with_program]`,
    `create_keyed_token_account[_with_program]`,
    `create_keyed_associated_token_account[_with_program]`)
    serialise SPL wire shapes via `Pack` directly — no SVM
    involvement, pure data construction.
  - Native error type `HopperSvmError` covers `UnknownProgram`,
    `BuiltinError`, `OutOfComputeUnits`, `EmptyChain`,
    `AccountIndexOutOfBounds`, `UnknownAccount`,
    `InsufficientFunds`, `AccountNotWritable`, `AccountNotSigner`,
    `Custom(u32)`. Every variant has a `describe()` for clean
    panic messages from `assert_success` / `assert_error_contains`.
  - 20 unit tests across the eight modules pin every behavior listed
    above, including system-transfer end-to-end, transfer-from-wrong-owner
    rejection, CreateAccount-on-initialised-target rejection,
    failure-rollback semantics, runtime log-format compliance,
    compute-meter saturation, ATA-derivation parity with
    `spl-associated-token-account`, and SPL `Pack` round-trip.

  **Phase 2 (planned, not in this release)**: wires
  `solana-sbpf 0.20` (Anza's canonical eBPF interpreter, the
  foundation every Solana SVM is built on) for real `.so` execution,
  the full Solana syscall surface (`sol_log_*`, `sol_panic_`,
  `sol_mem*`, `sol_get_*_sysvar`, `sol_create_program_address`,
  `sol_invoke_signed`, `sol_log_compute_units`, `sol_log_data`),
  CPI dispatch back into the harness, and the parameter-buffer
  serialisation Solana programs expect. The seam (`Engine` trait)
  is in place; the new file is a self-contained engine impl.
- **Interactive HTML flamegraph for `hopper profile elf`.** New
  `--html <out.html>` flag emits a self-contained interactive
  flamegraph — no CDN, no external resources, no JS framework: a
  single HTML file the user can open in any browser. Each symbol is
  a horizontal bar sized proportionally to its byte count, colour-cued
  by delta vs. baseline (lime if shrunk, orange if grown, neutral
  if unchanged). Hover for a tooltip with the full demangled name,
  byte size, percentage, and delta; click to pin a bar; type in the
  search box to filter by substring; `Esc` to clear. `--open` flag
  spawns the user's default browser pointed at the file (uses
  `open` on macOS, `xdg-open` on Linux, `cmd /c start` on Windows).
- **`--baseline <folded.txt>` flag for `hopper profile elf`.** Loads
  a previously-saved Brendan-Gregg folded-stack file and adds a
  delta column to both the terminal output and the HTML
  flamegraph. Per-symbol delta uses a "split at the *last* space"
  parser so symbol names containing spaces (Rust trait-method
  names like `<T as Trait>::method`) round-trip correctly.
- **`#[derive(Accounts)]` — Anchor-spelled drop-in for `#[hopper::context]`.**
  Anchor users porting code now get the spelling they expect without
  losing any of Hopper's account-attr surface. Identical generated code
  to the attribute form: same binder type, same accessors, same
  constraint validation pipeline, same Hopper-specific sugar
  (segment-tagged `mut(field, …)`, `read(...)`, the inline
  `#[hopper::pipeline]` / `#[hopper::receipt]` / `#[hopper::invariant]`
  stack). The full Anchor-grade constraint set is supported in either
  spelling: `init`, `init_if_needed`, `mut`, `signer`, `seeds`, `bump`,
  `payer`, `space`, `has_one`, `owner`, `address`, `constraint`,
  `token::*`, `mint::*`, `associated_token::*`, the Token-2022
  extension gates (`non_transferable`, `immutable_owner`,
  `transfer_hook`, `metadata_pointer`, …), `dup`, `sweep`,
  `executable`, `rent_exempt`, `realloc`, `zero`, `close`. The derive
  registers `account`, `signer`, `instruction`, and `validate` as
  helper attributes so `#[account(...)]`, `#[signer]`,
  `#[instruction(...)]`, and `#[validate]` field/struct annotations
  compile without an extra `use`. Lives in
  `hopper-macros-proc::derive_accounts`, exported through the
  `Accounts` symbol from `hopper::prelude::*`. Implementation reuses
  `context::expand_inner` behind a single `emit_struct: bool` flag —
  zero duplication of the constraint surface.
- **CLI polish pass two — final Quasar parity sweep.** Three commands
  Quasar shipped that Hopper still lacked, plus shared visual polish.
  - **`hopper add [-i|-s|-e <name>]`** — incremental scaffolding for
    an existing project. `-i/--instruction` creates
    `src/instructions/<name>.rs` with a Hopper-shaped context stub,
    wires it through `src/instructions/mod.rs` and
    `mod instructions;` in `lib.rs`, and (for projects using the
    `#[hopper::program]` style dispatch) injects a stub
    `#[instruction(N)]` handler at the next-available
    discriminator. For projects using a manual `match *disc` block,
    prints a "wire it in by hand" hint instead of guessing. `-s/--state`
    creates or appends to `src/state.rs` with a
    `#[hopper::state(disc = N, version = 1)]` struct picking the
    next-unused discriminator. `-e/--error` creates or appends to
    `src/errors.rs` with a discriminated `pub enum` plus a
    `From<...> for ProgramError` impl. All edits idempotent —
    re-running on the same name errors rather than overwriting.
  - **`hopper clean [-a|--all]`** — clear `target/{deploy,idl,client,
    profile,hopper}` while preserving `*-keypair.json` files (losing
    a program keypair means losing the on-chain program address —
    Quasar makes the same exception). With `-a`, also runs
    `cargo clean` for a full target wipe.
  - **Animated `hopper init` opening — leap reveal.** First-time
    interactive runs play a one-second FIGlet `HOPPER` animation:
    each row arrives from below with an ease-out-back bounce,
    leaving a trail of green grass-dots. Quasar has the blue nebula
    sweep; Hopper gets the leap. Auto-disables on subsequent runs
    (the wizard sets `ui.animation = false` after the first save),
    when stdout isn't a TTY, when `NO_COLOR` is set, or when the
    user toggles it off in `~/.hopper/wizard.toml`.
  - **Shared `style` module.** Centralised `bold`, `dim`, `color`,
    `success`, `fail`, `warn`, `step`, `human_size` helpers under
    `tools/hopper-cli/src/style.rs`. Auto-respects `NO_COLOR` and
    TTY detection, with explicit `--no-color` flag and
    `ui.color = false` config override. Replaces the ad-hoc ANSI
    escapes that were sprinkled through `cmd::lifecycle`. Build-size
    delta lines now colour the delta itself: green when shrinking,
    yellow when growing.
- **CLI polish — Quasar parity-plus pass.** `hopper init` now drops into
  an interactive `dialoguer` wizard when invoked without a `<path>`,
  prompting for project name, template, testing framework, and git
  policy. Choices are persisted to `~/.hopper/wizard.toml` so the
  second run skips the prompts. Four templates ship: `minimal`,
  `nft-mint` (uses `hopper-metaplex`), `token-2022-vault` (extension
  screening), `defi-vault` (segment-safe authority + balance with
  PDA verification). Pass `--template <name>` to pick one
  non-interactively, `--yes`/`-y` to use saved defaults without
  prompts, `--no-git` to skip git, `--interactive` to force the
  wizard even with a path.
- **`Hopper.toml` project config.** `hopper init` writes a declarative
  `Hopper.toml` at the project root with `[project]`,
  `[toolchain]`, `[testing]`, and `[backend]` sections. The rest of
  the CLI reads it to know how to build / test / deploy.
- **Binary-size delta on `hopper build`.** SBF builds now print a per-
  artefact summary: `✔ my_program.so   56.6 KiB  (-1.2 KiB)`. New
  binaries print `(new)`; unchanged binaries are silent.
- **Git automation in `hopper init`.** `commit` policy runs `git init`
  + initial commit; `init` policy runs `git init` only; `skip`
  leaves git alone.
- **`hopperzero.dev`** is the canonical project domain. Crate metadata,
  README headers, and footer links now point there.
- **`hopper-metaplex`** crate (optional, behind `--features metaplex`):
  `CreateMetadataAccountV3`, `CreateMasterEditionV3`,
  `UpdateMetadataAccountV2` builders, plus `metadata_pda` /
  `master_edition_pda` derivation helpers and a `BorshTape`
  stack-buffer encoder. Closes the Quasar-parity Metaplex gap.
- **`examples/hopper-nft-mint`** — reference NFT-mint program using the
  new Metaplex builders end-to-end (1-of-1 NFT with locked master
  edition).
- **Benchmark repo split** — Anchor, Anza Pinocchio, and lazy-dispatch
  parity targets now live in the sibling `hopper-bench` product repo with
  competitor locks, raw logs, and runner scripts. Release-facing framework
  docs only publish same-provenance benchmark columns.
- **`examples/hopper-token-2022-transfer-hook`** — Token-2022 transfer
  hook validation reference program.
- **DSL parity additions**: `#[derive(HopperInitSpace)]` standalone
  derive; `#[hopper::access_control(expr)]` handler attribute;
  `executable` and `rent_exempt = enforce|skip` field keywords;
  `init_if_needed` field keyword; `hopper_load!(slice => [a, b])`
  destructuring sugar; `err!` and `error!` short-form aliases in the
  prelude.
- **`hopper schema export --anchor-idl`** — emit Anchor 0.30-shaped
  IDL JSON from a `ProgramManifest`. Codama remains the preferred
  interop path; this exists for the long tail of wallets/explorers
  that still consume Anchor IDL.
- **`hopper-runtime::rent`** — `check_rent_exempt(account)` and
  `minimum_balance(data_len)` helpers backing the
  `rent_exempt = enforce` field keyword.
- **`docs/UNSAFE_INVARIANTS.md`** — supplemented with the full
  hopper-native unsafe surface (every `AccountView` unsafe method,
  `raw_input.rs`, `lazy.rs`, `pda.rs`, `mem.rs`, plus the expanded
  `cpi.rs` invariants).
- **Per-crate READMEs** for every crate under `crates/`, with
  hopperzero.dev / docs.rs / crates.io badges.
- **`AUDIT.md`** — comprehensive deep-review of Hopper vs Pinocchio,
  Quasar, Anchor zero-copy with verified parity findings, gap list,
  and implementation-pass record.

### Changed

- **README.md** Getting Started leads with the proc-macro path
  (`#[hopper::state]` / `#[hopper::program]`) for Anchor refugees;
  declarative `hopper_layout!` is the "Day Two" subsection.
- **Bench column** previously labelled "Pinocchio-style" is now
  "Pinocchio" (Anza). Pre-rename CU numbers retained with a
  `(deprecated)` marker so the historical record stays intact.
- **README** "What You Get" table extended with the new capabilities
  (Metaplex builders, Anchor IDL emit, full guard family,
  constraint vocabulary, Token-2022 extensions, handler attributes).
- **`cpi::invoke_unchecked` / `cpi::invoke_signed_unchecked`** now
  carry explicit seven-item `# Safety` invariant lists.
- **README "Segment-level borrows" row** correctly attributes
  `SegmentBorrowRegistry` to `hopper-runtime` (not `hopper-core`).

### Pre-publication checklist

- LICENSE: Apache-2.0, present at repo root.
- CONTRIBUTING.md: see [CONTRIBUTING.md]CONTRIBUTING.md.
- SECURITY.md: see [SECURITY.md]SECURITY.md.
- All published crates have `homepage = "https://hopperzero.dev"`,
  version-pinned `documentation = "https://docs.rs/crate/<crate>/0.1.0"`, and a per-crate
  `README.md`.

## [0.1.0] - 2026-05-10

### Added

- First public release line for the Hopper framework, CLI, and public companion
  crates. The top-level package target is `hopper-framework` with library crate
  name `hopper`. See [`AUDIT.md`]AUDIT.md for a full feature audit.