Deploy only ever runs natively (never inside a real zkVM guest), so a
chained call into it never needed to carry the bytecode through an
intermediary guest's own execution in the first place: the dispatcher
already holds the top-level Message for the whole transaction and can
resolve its raw_payload directly, at any chain-call depth. Un-ignores
loader_deploys_program_via_chained_call, previously blocked by the
believed need for guests to forward large payloads through their own
instruction_data.
ChainedCall::raw_payload is now dead code with this change (no guest
ever populated it) and is removed, along with every "raw_payload: None"
guest-side literal that existed only to satisfy the field.
The cherry-picked Deploy raw_payload change added a new ChainedCall field
that several ChainedCall struct literals predating this rebase didn't set,
breaking compilation. Rebuild guest artifacts and the test fixture to match.
instruction_data is word-serialized (risc0_zkvm::serde) for guest consumption,
which encodes Vec<u8> at 4 bytes/word since it doesn't route through
serialize_bytes — a Deploy transaction's wire size ran ~4x its raw bytecode.
Add Message/ChainedCall::raw_payload, a plain borsh-encoded Vec<u8> alongside
instruction_data, and move Deploy's bytecode there instead; Instruction::Deploy
now only carries update_auth.
Also stop transmitting and storing the ~32KB RISC0 platform kernel on every
single deployment: it's byte-identical across every guest artifact in this
repo, so Deploy now carries only the program-specific user_elf and
execute_deploy reconstructs the full two-ELF binary from an embedded kernel
constant to compute image_id. Segment accounts store user_elf only, roughly
halving their footprint for a typical program.
Measured on real Deploy dispatch (claimer, ~366KB): encoded transaction size
dropped from 1,464,777 bytes to 334,094 bytes.
CallerData and the PDA derivation primitives keyed authorization off a
program's image id (ProgramId), so two deployments of identical bytecode
with different update_auth collided on the same PDA. Switch derivation,
wire fields, and comparisons to the program's real dispatch AccountId
throughout the host, guest, and test-harness code, including the
privacy-circuit test harness's default dispatch address and the
wrapped_token/ping_receiver governance-authorization check, which
compared against the wrong address via the legacy bijection.
Programs dispatch at the address seeded via with_programs/a live Deploy
(loader_core::immutable_deploy_account_id), not the bijection
AccountId::from(program_id) used by the legacy ProgramDeploymentTransaction
storage shape. This sweep threads the correct address through
lee/lez/integration_tests/wallet-ffi call sites and fixes 7 dispatch-address
bugs the mismatch was masking: bijection-vs-real-PDA mismatches in
lez/wallet's native_token_transfer facade, integration_tests'
auth_transfer/private and private_pda suites, wallet-ffi's
generic_transaction FFI boundary, a stale assertion in program_deployment.rs,
and a stale expected-error string in cross_zone_state_machine.rs.
Also includes a full clippy/fmt pass: doc-comment reflow, #[expect(...)]
attribute additions, redundant type-annotation/unused-import removal, and
two assert!s added purely for bounds-check elision on already-guarded
slices — no logic changes. Both CI clippy invocations and cargo fmt --check
are clean.
Verified: RISC0_DEV_MODE=1 cargo test -p lee --lib (214 passed) and the
broader sanity set across lee/wallet/bridge_lock_core/ping_core/
cross_zone_outbox_core/sequencer_core (mock features) both green.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
deploy_pda_seed's first paragraph just restated its parameter names;
kept only the domain-separation rationale. chained_call_forwarder.rs's
doc comment was floating over an unrelated type alias instead of being
a module-level //! doc.
chained_call_forwarder.rs and the deploy tests in sequencer_core were
added on top of the pre-migration ProgramId-typed ChainedCall/Message/
ProgramInput/ProgramOutput API and didn't conflict during the rebase
since they're new code, so they needed the same field rename/.into()
treatment already applied everywhere else on -3-1.
Adds tests for the remaining execute_deploy failure modes (invalid
bytecode, wrong target account, wrong number of accounts) and a
generic chained_call_forwarder test guest to exercise Deploy invoked
via a chained call rather than only top-level.
The chained-call test is #[ignore]d: it surfaces a real limitation,
not a bug in the test. The forwarding program has to carry the
deployed bytecode through its own instruction_data to build the
chained call, which blows the interpreted 32M-cycle public-execution
cap for any realistically-sized program — the native Deploy fast-path
only covers the loader's own execution, not the caller's. Root cause
is ChainedCall/Message still referencing programs by ProgramId rather
than AccountId, which also means dispatch can't locate a
Deploy-created (PDA-addressed) program at all. Tracked for
marvin/program-as-account-3-1.
ping_receiver's renounce_authority/update_sources, wrapped_token's
renounce_authority/update_sources, authority_proxy, and
cross_zone_state_machine integration tests all landed on dev after this
branch's self/caller-identity and ChainedCall/Message AccountId migrations,
so they still referenced the old ProgramId-based fields.
ProgramInput/ProgramOutput.self_program_id/caller_program_id, and the
dispatcher's CallerData.program_id, now carry AccountId (renamed to
self_account_id/caller_account_id) instead of ProgramId. These fields
are self-reported/cross-checked dispatch bookkeeping, not RISC0 image
identity, and AccountId already crosses the guest/host boundary this
way via every pre_state.account_id.
ProgramId is now confined to what's actually image-id-keyed:
env::verify, Program.id (from compute_image_id()), and the
for_public_pda/for_private_pda derivation formulas, each recovering
the real ProgramId from AccountId via the existing bijection exactly
where needed.
Rebuilds artifacts and the prebuilt sequencer db fixture to match.
Chained-call and public-transaction dispatch now address the target
program directly by AccountId instead of routing through ProgramId and
converting internally, closing the gap that blocked PDA-addressed
program invocation. The field is named program_account_id (not
account_id) to stay unambiguous next to the account_ids list it sits
beside in the same structs.
Execution/PDA-derivation logic that fundamentally needs the RISC0 image
id (self_program_id, caller_program_id, env::verify, PDA seed
derivation) stays ProgramId-typed, recovering it from the dispatched
AccountId via the existing bijection where needed.
BREAKING CHANGE: WrappedTokenConfig and ReceiverConfig gain a governance field and CrossZoneConfig gains source_governance, so the config layouts change and every program image id moves.