diff --git a/fuzz/fixture/proto/invoke.proto b/fuzz/fixture/proto/invoke.proto index e6872601..e2d14d61 100644 --- a/fuzz/fixture/proto/invoke.proto +++ b/fuzz/fixture/proto/invoke.proto @@ -64,14 +64,17 @@ message InstrEffects { // Program return code. Zero is success, errors are non-zero. uint32 program_result = 3; + // The instruction return data. + bytes return_data = 4; + // Copies of accounts that were provided to the instruction. May be in an // arbitrary order. The pubkey of each account is unique in this list. Each // account address must also be in the InstrContext. - repeated AcctState resulting_accounts = 4; + repeated AcctState resulting_accounts = 5; } // An instruction processing test fixture. message InstrFixture { InstrContext input = 1; InstrEffects output = 2; -} \ No newline at end of file +} diff --git a/fuzz/fixture/src/effects.rs b/fuzz/fixture/src/effects.rs index e1c54212..c5194d7c 100644 --- a/fuzz/fixture/src/effects.rs +++ b/fuzz/fixture/src/effects.rs @@ -14,6 +14,7 @@ pub struct Effects { pub execution_time: u64, // Program return code. Zero is success, errors are non-zero. pub program_result: u32, + pub return_data: Vec, /// Resulting accounts with state, to be checked post-simulation. pub resulting_accounts: Vec<(Pubkey, AccountSharedData)>, } @@ -24,6 +25,7 @@ impl From for Effects { compute_units_consumed, execution_time, program_result, + return_data, resulting_accounts, } = value; @@ -34,6 +36,7 @@ impl From for Effects { compute_units_consumed, execution_time, program_result, + return_data, resulting_accounts, } } @@ -45,6 +48,7 @@ impl From for ProtoEffects { compute_units_consumed, execution_time, program_result, + return_data, resulting_accounts, } = value; @@ -55,6 +59,7 @@ impl From for ProtoEffects { compute_units_consumed, execution_time, program_result, + return_data, resulting_accounts, } } diff --git a/harness/src/fuzz/firedancer.rs b/harness/src/fuzz/firedancer.rs index 58cdba14..5cc9bc41 100644 --- a/harness/src/fuzz/firedancer.rs +++ b/harness/src/fuzz/firedancer.rs @@ -169,6 +169,8 @@ fn build_fixture_effects(context: &FuzzContext, result: &InstructionResult) -> F } }; + let return_data = result.return_data.clone(); + let modified_accounts = context .accounts .iter() @@ -191,7 +193,7 @@ fn build_fixture_effects(context: &FuzzContext, result: &InstructionResult) -> F compute_units_available: context .compute_units_available .saturating_sub(result.compute_units_consumed), - return_data: Vec::new(), // TODO: Mollusk doesn't capture return data. + return_data, } } @@ -210,6 +212,7 @@ fn parse_fixture_effects( }; let program_result = raw_result.clone().into(); + let return_data = effects.return_data.clone(); let resulting_accounts = accounts .iter() @@ -232,6 +235,7 @@ fn parse_fixture_effects( .compute_budget .compute_unit_limit .saturating_sub(effects.compute_units_available), + return_data, resulting_accounts, } } diff --git a/harness/src/fuzz/mollusk.rs b/harness/src/fuzz/mollusk.rs index 1154e2ba..63c49ad9 100644 --- a/harness/src/fuzz/mollusk.rs +++ b/harness/src/fuzz/mollusk.rs @@ -56,6 +56,7 @@ impl From<&InstructionResult> for FuzzEffects { fn from(input: &InstructionResult) -> Self { let compute_units_consumed = input.compute_units_consumed; let execution_time = input.execution_time; + let return_data = input.return_data.clone(); let program_result = match &input.program_result { ProgramResult::Success => 0, @@ -69,6 +70,7 @@ impl From<&InstructionResult> for FuzzEffects { compute_units_consumed, execution_time, program_result, + return_data, resulting_accounts, } } @@ -78,6 +80,7 @@ impl From<&FuzzEffects> for InstructionResult { fn from(input: &FuzzEffects) -> Self { let compute_units_consumed = input.compute_units_consumed; let execution_time = input.execution_time; + let return_data = input.return_data.clone(); let raw_result = if input.program_result == 0 { Ok(()) @@ -94,6 +97,7 @@ impl From<&FuzzEffects> for InstructionResult { execution_time, program_result, raw_result, + return_data, resulting_accounts, } } diff --git a/harness/src/lib.rs b/harness/src/lib.rs index fdb1704f..a66e366e 100644 --- a/harness/src/lib.rs +++ b/harness/src/lib.rs @@ -217,6 +217,8 @@ impl Mollusk { } }; + let return_data = transaction_context.get_return_data().1.to_vec(); + let resulting_accounts: Vec<(Pubkey, AccountSharedData)> = accounts .iter() .map(|(pubkey, account)| { @@ -236,6 +238,7 @@ impl Mollusk { execution_time: timings.details.execute_us, program_result: invoke_result.clone().into(), raw_result: invoke_result, + return_data, resulting_accounts, } } diff --git a/harness/src/result.rs b/harness/src/result.rs index 0d0aeab5..803e41b2 100644 --- a/harness/src/result.rs +++ b/harness/src/result.rs @@ -51,6 +51,8 @@ pub struct InstructionResult { pub program_result: ProgramResult, /// The raw result of the program's execution. pub raw_result: Result<(), InstructionError>, + /// The return data produced by the instruction, if any. + pub return_data: Vec, /// The resulting accounts after executing the instruction. /// /// This includes all accounts provided to the processor, in the order @@ -66,6 +68,7 @@ impl Default for InstructionResult { execution_time: 0, program_result: ProgramResult::Success, raw_result: Ok(()), + return_data: vec![], resulting_accounts: vec![], } } @@ -111,6 +114,15 @@ impl InstructionResult { actual_result, check_result, ); } + CheckType::ReturnData(return_data) => { + let check_return_data = return_data; + let actual_return_data = &self.return_data; + assert_eq!( + actual_return_data, check_return_data, + "CHECK: return_data: got {:?}, expected {:?}", + actual_return_data, check_return_data, + ); + } CheckType::ResultingAccount(account) => { let pubkey = account.pubkey; let resulting_account = self @@ -199,6 +211,7 @@ impl InstructionResult { b.resulting_accounts.len(), "resulting accounts length mismatch" ); + assert_eq!(self.return_data, b.return_data, "return data mismatch"); for (a, b) in self .resulting_accounts .iter() @@ -217,6 +230,8 @@ enum CheckType<'a> { ExecutionTime(u64), /// Check the result code of the program's execution. ProgramResult(ProgramResult), + /// Check the return data produced by executing the instruction. + ReturnData(Vec), /// Check a resulting account after executing the instruction. ResultingAccount(AccountCheck<'a>), } @@ -255,6 +270,11 @@ impl<'a> Check<'a> { Check::new(CheckType::ProgramResult(ProgramResult::UnknownError(error))) } + /// Check the return data produced by executing the instruction. + pub fn return_data(return_data: Vec) -> Self { + Check::new(CheckType::ReturnData(return_data)) + } + /// Check a resulting account after executing the instruction. pub fn account(pubkey: &Pubkey) -> AccountCheckBuilder { AccountCheckBuilder::new(pubkey) diff --git a/harness/tests/fd_test_vectors.rs b/harness/tests/fd_test_vectors.rs index 703a4307..87379494 100644 --- a/harness/tests/fd_test_vectors.rs +++ b/harness/tests/fd_test_vectors.rs @@ -106,10 +106,10 @@ fn test_load_firedancer_fixtures() { loaded_fixture.output.compute_units_available, generated_fixture.output.compute_units_available, ); - // assert_eq!( - // loaded_fixture.output.return_data, - // generated_fixture.output.return_data, - // ); + assert_eq!( + loaded_fixture.output.return_data, + generated_fixture.output.return_data, + ); } }); });