Skip to content

Commit ba391d1

Browse files
authored
fuzz: refactor fixture processing harnesses to use &mut self (#77)
1 parent ed4689c commit ba391d1

File tree

8 files changed

+521
-128
lines changed

8 files changed

+521
-128
lines changed

harness/src/fuzz/check.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
//! Checks to run against a fixture when validating.
2+
3+
use {
4+
crate::result::{Check, InstructionResult},
5+
solana_sdk::{
6+
account::{Account, ReadableAccount},
7+
pubkey::Pubkey,
8+
},
9+
};
10+
11+
/// Checks to run against a fixture when validating.
12+
///
13+
/// Similar to Mollusk's `result::Check`, this allows a developer to dictate
14+
/// the type of checks to run on the fixture's effects.
15+
///
16+
/// Keep in mind that validation of fixtures works slightly differently than
17+
/// typical Mollusk unit tests. In a Mollusk test, you can provide the value to
18+
/// compare a portion of the result against (ie. compute units). However, when
19+
/// comparing the result of a Mollusk invocation against a fixture, the value
20+
/// from the fixture itself is used.
21+
///
22+
/// For that reason, these are unary variants, and do not offer the developer a
23+
/// way to provide values to check against.
24+
pub enum FixtureCheck {
25+
/// Validate compute units consumed.
26+
ComputeUnits,
27+
/// Validate the program result.
28+
ProgramResult,
29+
/// Validate the return data.
30+
ReturnData,
31+
/// Validate all resulting accounts.
32+
AllResultingAccounts {
33+
/// Whether or not to validate each account's data.
34+
data: bool,
35+
/// Whether or not to validate each account's lamports.
36+
lamports: bool,
37+
/// Whether or not to validate each account's owner.
38+
owner: bool,
39+
/// Whether or not to validate each account's space.
40+
space: bool,
41+
},
42+
/// Validate the resulting accounts at certain addresses.
43+
OnlyResultingAccounts {
44+
/// The addresses on which to apply the validation.
45+
addresses: Vec<Pubkey>,
46+
/// Whether or not to validate each account's data.
47+
data: bool,
48+
/// Whether or not to validate each account's lamports.
49+
lamports: bool,
50+
/// Whether or not to validate each account's owner.
51+
owner: bool,
52+
/// Whether or not to validate each account's space.
53+
space: bool,
54+
},
55+
/// Validate all of the resulting accounts _except_ the provided addresses.
56+
AllResultingAccountsExcept {
57+
/// The addresses on which to _not_ apply the validation.
58+
ignore_addresses: Vec<Pubkey>,
59+
/// On non-ignored accounts, whether or not to validate each account's
60+
/// data.
61+
data: bool,
62+
/// On non-ignored accounts, whether or not to validate each account's
63+
/// lamports.
64+
lamports: bool,
65+
/// On non-ignored accounts, whether or not to validate each account's
66+
/// owner.
67+
owner: bool,
68+
/// On non-ignored accounts, whether or not to validate each account's
69+
/// space.
70+
space: bool,
71+
},
72+
}
73+
74+
impl FixtureCheck {
75+
/// Validate all possible checks for all resulting accounts.
76+
///
77+
/// Note: To omit certain checks, use the variant directly, ie.
78+
/// `FixtureCheck::AllResultingAccounts { data: false, .. }`.
79+
pub fn all_resulting_accounts() -> Self {
80+
Self::AllResultingAccounts {
81+
data: true,
82+
lamports: true,
83+
owner: true,
84+
space: true,
85+
}
86+
}
87+
88+
/// Validate all possible checks for only the resulting accounts at certain
89+
/// addresses.
90+
///
91+
/// Note: To omit certain checks, use the variant directly, ie.
92+
/// `FixtureCheck::OnlyResultingAccounts { data: false, .. }`.
93+
pub fn only_resulting_accounts(addresses: &[Pubkey]) -> Self {
94+
Self::OnlyResultingAccounts {
95+
addresses: addresses.to_vec(),
96+
data: true,
97+
lamports: true,
98+
owner: true,
99+
space: true,
100+
}
101+
}
102+
103+
/// Validate all possible checks for all of the resulting accounts _except_
104+
/// the provided addresses.
105+
///
106+
/// Note: To omit certain checks, use the variant directly, ie.
107+
/// `FixtureCheck::AllResultingAccountsExcept { data: false, .. }`.
108+
pub fn all_resulting_accounts_except(ignore_addresses: &[Pubkey]) -> Self {
109+
Self::AllResultingAccountsExcept {
110+
ignore_addresses: ignore_addresses.to_vec(),
111+
data: true,
112+
lamports: true,
113+
owner: true,
114+
space: true,
115+
}
116+
}
117+
}
118+
119+
fn add_account_checks<'a>(
120+
checks: &mut Vec<Check<'a>>,
121+
accounts: impl Iterator<Item = &'a (Pubkey, Account)>,
122+
data: bool,
123+
lamports: bool,
124+
owner: bool,
125+
space: bool,
126+
) {
127+
for (pubkey, account) in accounts {
128+
let mut builder = Check::account(pubkey);
129+
if data {
130+
builder = builder.data(account.data());
131+
}
132+
if lamports {
133+
builder = builder.lamports(account.lamports());
134+
}
135+
if owner {
136+
builder = builder.owner(account.owner());
137+
}
138+
if space {
139+
builder = builder.space(account.data().len());
140+
}
141+
checks.push(builder.build());
142+
}
143+
}
144+
145+
pub(crate) fn evaluate_results_with_fixture_checks(
146+
expected: &InstructionResult,
147+
result: &InstructionResult,
148+
fixture_checks: &[FixtureCheck],
149+
) {
150+
let mut checks = vec![];
151+
152+
for fixture_check in fixture_checks {
153+
match fixture_check {
154+
FixtureCheck::ComputeUnits => {
155+
checks.push(Check::compute_units(expected.compute_units_consumed))
156+
}
157+
FixtureCheck::ProgramResult => {
158+
checks.push(Check::program_result(expected.program_result.clone()))
159+
}
160+
FixtureCheck::ReturnData => checks.push(Check::return_data(&expected.return_data)),
161+
FixtureCheck::AllResultingAccounts {
162+
data,
163+
lamports,
164+
owner,
165+
space,
166+
} => {
167+
add_account_checks(
168+
&mut checks,
169+
expected.resulting_accounts.iter(),
170+
*data,
171+
*lamports,
172+
*owner,
173+
*space,
174+
);
175+
}
176+
FixtureCheck::OnlyResultingAccounts {
177+
addresses,
178+
data,
179+
lamports,
180+
owner,
181+
space,
182+
} => {
183+
add_account_checks(
184+
&mut checks,
185+
expected
186+
.resulting_accounts
187+
.iter()
188+
.filter(|(pubkey, _)| addresses.contains(pubkey)),
189+
*data,
190+
*lamports,
191+
*owner,
192+
*space,
193+
);
194+
}
195+
FixtureCheck::AllResultingAccountsExcept {
196+
ignore_addresses,
197+
data,
198+
lamports,
199+
owner,
200+
space,
201+
} => {
202+
add_account_checks(
203+
&mut checks,
204+
expected
205+
.resulting_accounts
206+
.iter()
207+
.filter(|(pubkey, _)| !ignore_addresses.contains(pubkey)),
208+
*data,
209+
*lamports,
210+
*owner,
211+
*space,
212+
);
213+
}
214+
}
215+
}
216+
217+
result.run_checks(&checks);
218+
}

harness/src/fuzz/firedancer.rs

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use {
88
crate::{
99
accounts::{compile_accounts, CompiledAccounts},
10-
result::{Check, InstructionResult},
10+
result::InstructionResult,
1111
Mollusk, DEFAULT_LOADER_KEY,
1212
},
1313
mollusk_svm_fuzz_fixture_firedancer::{
@@ -22,6 +22,7 @@ use {
2222
solana_compute_budget::compute_budget::ComputeBudget,
2323
solana_sdk::{
2424
account::Account,
25+
feature_set::FeatureSet,
2526
instruction::{AccountMeta, Instruction, InstructionError},
2627
pubkey::Pubkey,
2728
},
@@ -66,17 +67,12 @@ fn num_to_instr_err(num: i32, custom_code: u32) -> InstructionError {
6667
}
6768

6869
fn build_fixture_context(
69-
mollusk: &Mollusk,
70-
instruction: &Instruction,
7170
accounts: &[(Pubkey, Account)],
71+
compute_budget: &ComputeBudget,
72+
feature_set: &FeatureSet,
73+
instruction: &Instruction,
74+
slot: u64,
7275
) -> FuzzContext {
73-
let Mollusk {
74-
compute_budget,
75-
feature_set,
76-
slot, // FD-Fuzz feature only.
77-
..
78-
} = mollusk;
79-
8076
let loader_key = if BUILTIN_PROGRAM_IDS.contains(&instruction.program_id) {
8177
solana_sdk::native_loader::id()
8278
} else {
@@ -100,14 +96,22 @@ fn build_fixture_context(
10096
instruction_accounts,
10197
instruction_data: instruction.data.clone(),
10298
compute_units_available: compute_budget.compute_unit_limit,
103-
slot_context: FuzzSlotContext { slot: *slot },
99+
slot_context: FuzzSlotContext { slot },
104100
epoch_context: FuzzEpochContext {
105101
feature_set: feature_set.clone(),
106102
},
107103
}
108104
}
109105

110-
fn parse_fixture_context(context: &FuzzContext) -> (Mollusk, Instruction, Vec<(Pubkey, Account)>) {
106+
pub struct ParsedFixtureContext {
107+
pub accounts: Vec<(Pubkey, Account)>,
108+
pub compute_budget: ComputeBudget,
109+
pub feature_set: FeatureSet,
110+
pub instruction: Instruction,
111+
pub slot: u64,
112+
}
113+
114+
pub(crate) fn parse_fixture_context(context: &FuzzContext) -> ParsedFixtureContext {
111115
let FuzzContext {
112116
program_id,
113117
accounts,
@@ -128,13 +132,6 @@ fn parse_fixture_context(context: &FuzzContext) -> (Mollusk, Instruction, Vec<(P
128132
.map(|(key, acct, _)| (*key, acct.clone()))
129133
.collect::<Vec<_>>();
130134

131-
let mollusk = Mollusk {
132-
compute_budget,
133-
feature_set: epoch_context.feature_set.clone(),
134-
slot: slot_context.slot,
135-
..Default::default()
136-
};
137-
138135
let metas = instruction_accounts
139136
.iter()
140137
.map(|ia| {
@@ -152,7 +149,13 @@ fn parse_fixture_context(context: &FuzzContext) -> (Mollusk, Instruction, Vec<(P
152149

153150
let instruction = Instruction::new_with_bytes(*program_id, instruction_data, metas);
154151

155-
(mollusk, instruction, accounts)
152+
ParsedFixtureContext {
153+
accounts,
154+
compute_budget,
155+
feature_set: epoch_context.feature_set.clone(),
156+
instruction,
157+
slot: slot_context.slot,
158+
}
156159
}
157160

158161
fn build_fixture_effects(context: &FuzzContext, result: &InstructionResult) -> FuzzEffects {
@@ -195,9 +198,9 @@ fn build_fixture_effects(context: &FuzzContext, result: &InstructionResult) -> F
195198
}
196199
}
197200

198-
fn parse_fixture_effects(
199-
mollusk: &Mollusk,
201+
pub(crate) fn parse_fixture_effects(
200202
accounts: &[(Pubkey, Account)],
203+
compute_unit_limit: u64,
201204
effects: &FuzzEffects,
202205
) -> InstructionResult {
203206
let raw_result = if effects.program_result == 0 {
@@ -229,10 +232,7 @@ fn parse_fixture_effects(
229232
program_result,
230233
raw_result,
231234
execution_time: 0, // TODO: Omitted for now.
232-
compute_units_consumed: mollusk
233-
.compute_budget
234-
.compute_unit_limit
235-
.saturating_sub(effects.compute_units_available),
235+
compute_units_consumed: compute_unit_limit.saturating_sub(effects.compute_units_available),
236236
return_data,
237237
resulting_accounts,
238238
}
@@ -250,9 +250,14 @@ pub fn build_fixture_from_mollusk_test(
250250
instruction: &Instruction,
251251
accounts: &[(Pubkey, Account)],
252252
result: &InstructionResult,
253-
_checks: &[Check],
254253
) -> FuzzFixture {
255-
let input = build_fixture_context(mollusk, instruction, accounts);
254+
let input = build_fixture_context(
255+
accounts,
256+
&mollusk.compute_budget,
257+
&mollusk.feature_set,
258+
instruction,
259+
mollusk.slot, // FD-fuzz feature only.
260+
);
256261
// This should probably be built from the checks, but there's currently no
257262
// mechanism to enforce full check coverage on a result.
258263
let output = build_fixture_effects(&input, result);
@@ -265,15 +270,14 @@ pub fn build_fixture_from_mollusk_test(
265270

266271
pub fn load_firedancer_fixture(
267272
fixture: &mollusk_svm_fuzz_fixture_firedancer::Fixture,
268-
) -> (
269-
Mollusk,
270-
Instruction,
271-
Vec<(Pubkey, Account)>,
272-
InstructionResult,
273-
) {
274-
let (mollusk, instruction, accounts) = parse_fixture_context(&fixture.input);
275-
let result = parse_fixture_effects(&mollusk, &accounts, &fixture.output);
276-
(mollusk, instruction, accounts, result)
273+
) -> (ParsedFixtureContext, InstructionResult) {
274+
let parsed = parse_fixture_context(&fixture.input);
275+
let result = parse_fixture_effects(
276+
&parsed.accounts,
277+
parsed.compute_budget.compute_unit_limit,
278+
&fixture.output,
279+
);
280+
(parsed, result)
277281
}
278282

279283
#[test]

0 commit comments

Comments
 (0)