feat(lee): fold program storage into public_state, drop the separate programs map

V03State.programs is gone; deployed programs now live directly in
public_state, keyed by AccountId::from(program_id) same as any other
account. insert_program sets program_owner to a new reserved sentinel,
PROGRAM_STORAGE_OWNER, instead of leaving it at the default.

That ownership choice is load-bearing now in a way it wasn't before:
once program accounts share the same map as everything else, they're
reachable through ordinary dispatch, so program_owner determines
whether they're claimable/writable. Left unclaimed, a program
invocation could legitimately claim a program's storage account via
the normal claim path and then rewrite its elf; self-ownership has
the same flaw, since it authorizes exactly the program whose own
invocation would touch its own storage account. The reserved sentinel
makes every program account unwritable by construction, since no real
chained_call.program_id will ever derive to it.

programs() is removed; dispatch and the deployment-existence check go
through get_account_by_id_ref like any other account lookup.
genesis_fingerprint drops its separate program-hashing loop, since
program accounts now fall out of the existing public_state loop.

Rebuilt all guest artifacts and the test fixture via just
build-artifacts as a precaution, since V03State's Borsh shape changed
even though Account's did not.
This commit is contained in:
Marvin Jones
2026-08-22 19:27:13 -04:00
parent f5304fb196
commit ff5fa7065e
+4
View File
@@ -112,6 +112,10 @@ impl BorshDeserialize for NullifierSet {
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
#[cfg_attr(test, derive(Debug))]
pub struct V03State {
/// Deployed programs live here too, as `Account`s keyed by `AccountId::from(program_id)`
/// (see that impl's doc comment), with the elf held in `Account.data` and `program_owner`
/// set to the reserved `PROGRAM_STORAGE_OWNER` (see its doc comment for why that ownership
/// choice is load-bearing now that these accounts are reachable via ordinary dispatch).
public_state: HashMap<AccountId, Account>,
private_state: (CommitmentSet, NullifierSet),
}