From 6dc0e199c406e84c5f0cf9198e6c0fd9702914aa Mon Sep 17 00:00:00 2001 From: Marvin Jones Date: Thu, 13 Aug 2026 16:41:16 -0400 Subject: [PATCH] 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. --- lee/state_machine/src/state/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lee/state_machine/src/state/mod.rs b/lee/state_machine/src/state/mod.rs index 74790d46c..32b085ef0 100644 --- a/lee/state_machine/src/state/mod.rs +++ b/lee/state_machine/src/state/mod.rs @@ -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, private_state: (CommitmentSet, NullifierSet), } @@ -325,6 +329,7 @@ impl V03State { let mut accounts: Vec<(&AccountId, &Account)> = public_state.iter().collect(); accounts.sort_by(|a, b| a.0.as_ref().cmp(b.0.as_ref())); + let account_count = u64::try_from(accounts.len()).expect("account count fits in u64"); let mut hasher = Sha256::new();