test(lee): add Deploy validation-failure and chained-call coverage

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.
This commit is contained in:
Marvin Jones
2026-08-14 16:49:21 -04:00
parent 78e7b46a58
commit a6aaea54f3
3 changed files with 168 additions and 0 deletions
@@ -0,0 +1,45 @@
use lee_core::program::{
AccountPostState, ChainedCall, ProgramId, ProgramInput, ProgramOutput, read_lee_inputs,
};
/// Forwards a single chained call to `target_program_id` with `instruction_data`, passing
/// through whatever `pre_states` this program itself was invoked with unchanged.
///
/// Exists purely as test infrastructure: lets a test exercise "program X invokes program Y via
/// a chained call" for an arbitrary Y and instruction, without needing a purpose-built guest for
/// every target program under test.
type Instruction = (ProgramId, Vec<u32>);
fn main() {
let (
ProgramInput {
self_program_id,
caller_program_id,
pre_states,
instruction: (target_program_id, instruction_data),
},
instruction_words,
) = read_lee_inputs::<Instruction>();
let post_states = pre_states
.iter()
.map(|pre| AccountPostState::new(pre.account.clone()))
.collect();
let chained_call = ChainedCall {
program_id: target_program_id,
instruction_data,
pre_states: pre_states.clone(),
pda_seeds: vec![],
};
ProgramOutput::new(
self_program_id,
caller_program_id,
instruction_words,
pre_states,
post_states,
)
.with_chained_calls(vec![chained_call])
.write();
}
+13
View File
@@ -56,6 +56,19 @@ pub const fn pda_spend_proxy() -> Program {
Program::new_unchecked(PDA_SPEND_PROXY_ID, Cow::Borrowed(PDA_SPEND_PROXY_ELF))
}
#[must_use]
#[inline]
pub const fn chained_call_forwarder() -> Program {
use guests::{CHAINED_CALL_FORWARDER_ELF, CHAINED_CALL_FORWARDER_ID, CHAINED_CALL_FORWARDER_PATH};
let _unused = CHAINED_CALL_FORWARDER_PATH;
Program::new_unchecked(
CHAINED_CALL_FORWARDER_ID,
Cow::Borrowed(CHAINED_CALL_FORWARDER_ELF),
)
}
#[must_use]
#[inline]
pub const fn time_locked_transfer() -> Program {