Skip to content

Commit 3298207

Browse files
authored
[2.0] feat: harness: api to process fixtures (#42)
1 parent aa5b533 commit 3298207

File tree

7 files changed

+500
-39
lines changed

7 files changed

+500
-39
lines changed

harness/src/fuzz/firedancer.rs

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use {
88
crate::{
99
accounts::{compile_accounts, CompiledAccounts},
1010
result::{Check, InstructionResult},
11+
sysvar::Sysvars,
1112
Mollusk, DEFAULT_LOADER_KEY,
1213
},
1314
mollusk_svm_fuzz_fixture_firedancer::{
@@ -19,9 +20,10 @@ use {
1920
metadata::Metadata as FuzzMetadata,
2021
Fixture as FuzzFixture,
2122
},
23+
solana_compute_budget::compute_budget::ComputeBudget,
2224
solana_sdk::{
2325
account::AccountSharedData,
24-
instruction::{Instruction, InstructionError},
26+
instruction::{AccountMeta, Instruction, InstructionError},
2527
pubkey::Pubkey,
2628
},
2729
};
@@ -54,6 +56,16 @@ fn instr_err_to_num(error: &InstructionError) -> i32 {
5456
i32::from_le_bytes((&serialized_err[0..4]).try_into().unwrap()) + 1
5557
}
5658

59+
fn num_to_instr_err(num: i32, custom_code: u32) -> InstructionError {
60+
let val = (num - 1) as u64;
61+
let le = val.to_le_bytes();
62+
let mut deser = bincode::deserialize(&le).unwrap();
63+
if custom_code != 0 && matches!(deser, InstructionError::Custom(_)) {
64+
deser = InstructionError::Custom(custom_code);
65+
}
66+
deser
67+
}
68+
5769
fn build_fixture_context(
5870
mollusk: &Mollusk,
5971
instruction: &Instruction,
@@ -98,6 +110,56 @@ fn build_fixture_context(
98110
}
99111
}
100112

113+
fn parse_fixture_context(
114+
context: &FuzzContext,
115+
) -> (Mollusk, Instruction, Vec<(Pubkey, AccountSharedData)>) {
116+
let FuzzContext {
117+
program_id,
118+
accounts,
119+
instruction_accounts,
120+
instruction_data,
121+
compute_units_available,
122+
epoch_context,
123+
..
124+
} = context;
125+
126+
let compute_budget = ComputeBudget {
127+
compute_unit_limit: *compute_units_available,
128+
..Default::default()
129+
};
130+
131+
let accounts = accounts
132+
.iter()
133+
.map(|(key, acct, _)| (*key, acct.clone()))
134+
.collect::<Vec<_>>();
135+
136+
let mollusk = Mollusk {
137+
compute_budget,
138+
feature_set: epoch_context.feature_set.clone(),
139+
sysvars: Sysvars::fill_from_accounts(&accounts),
140+
..Default::default()
141+
};
142+
143+
let metas = instruction_accounts
144+
.iter()
145+
.map(|ia| {
146+
let pubkey = accounts
147+
.get(ia.index_in_caller as usize)
148+
.expect("Index out of bounds")
149+
.0;
150+
AccountMeta {
151+
pubkey,
152+
is_signer: ia.is_signer,
153+
is_writable: ia.is_writable,
154+
}
155+
})
156+
.collect::<Vec<_>>();
157+
158+
let instruction = Instruction::new_with_bytes(*program_id, instruction_data, metas);
159+
160+
(mollusk, instruction, accounts)
161+
}
162+
101163
fn build_fixture_effects(context: &FuzzContext, result: &InstructionResult) -> FuzzEffects {
102164
let mut program_custom_code = 0;
103165
let program_result = match &result.raw_result {
@@ -136,6 +198,47 @@ fn build_fixture_effects(context: &FuzzContext, result: &InstructionResult) -> F
136198
}
137199
}
138200

201+
fn parse_fixture_effects(
202+
mollusk: &Mollusk,
203+
accounts: &[(Pubkey, AccountSharedData)],
204+
effects: &FuzzEffects,
205+
) -> InstructionResult {
206+
let raw_result = if effects.program_result == 0 {
207+
Ok(())
208+
} else {
209+
Err(num_to_instr_err(
210+
effects.program_result,
211+
effects.program_custom_code,
212+
))
213+
};
214+
215+
let program_result = raw_result.clone().into();
216+
217+
let resulting_accounts = accounts
218+
.iter()
219+
.map(|(key, acct)| {
220+
let resulting_account = effects
221+
.modified_accounts
222+
.iter()
223+
.find(|(k, _, _)| k == key)
224+
.map(|(_, acct, _)| acct.clone())
225+
.unwrap_or_else(|| acct.clone());
226+
(*key, resulting_account)
227+
})
228+
.collect();
229+
230+
InstructionResult {
231+
program_result,
232+
raw_result,
233+
execution_time: 0, // TODO: Omitted for now.
234+
compute_units_consumed: mollusk
235+
.compute_budget
236+
.compute_unit_limit
237+
.saturating_sub(effects.compute_units_available),
238+
resulting_accounts,
239+
}
240+
}
241+
139242
fn instruction_metadata() -> FuzzMetadata {
140243
FuzzMetadata {
141244
// Mollusk is always an instruction harness.
@@ -160,3 +263,50 @@ pub fn build_fixture_from_mollusk_test(
160263
output,
161264
}
162265
}
266+
267+
pub fn load_firedancer_fixture(
268+
fixture: &mollusk_svm_fuzz_fixture_firedancer::Fixture,
269+
) -> (
270+
Mollusk,
271+
Instruction,
272+
Vec<(Pubkey, AccountSharedData)>,
273+
InstructionResult,
274+
) {
275+
let (mollusk, instruction, accounts) = parse_fixture_context(&fixture.input);
276+
let result = parse_fixture_effects(&mollusk, &accounts, &fixture.output);
277+
(mollusk, instruction, accounts, result)
278+
}
279+
280+
#[test]
281+
fn test_num_to_instr_err() {
282+
[
283+
InstructionError::InvalidArgument,
284+
InstructionError::InvalidInstructionData,
285+
InstructionError::InvalidAccountData,
286+
InstructionError::AccountDataTooSmall,
287+
InstructionError::InsufficientFunds,
288+
InstructionError::IncorrectProgramId,
289+
InstructionError::MissingRequiredSignature,
290+
InstructionError::AccountAlreadyInitialized,
291+
InstructionError::UninitializedAccount,
292+
InstructionError::UnbalancedInstruction,
293+
InstructionError::ModifiedProgramId,
294+
InstructionError::Custom(0),
295+
InstructionError::Custom(1),
296+
InstructionError::Custom(2),
297+
InstructionError::Custom(5),
298+
InstructionError::Custom(400),
299+
InstructionError::Custom(600),
300+
InstructionError::Custom(1_000),
301+
]
302+
.into_iter()
303+
.for_each(|ie| {
304+
let mut custom_code = 0;
305+
if let InstructionError::Custom(c) = &ie {
306+
custom_code = *c;
307+
}
308+
let result = instr_err_to_num(&ie);
309+
let err = num_to_instr_err(result, custom_code);
310+
assert_eq!(ie, err);
311+
})
312+
}

harness/src/fuzz/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#[cfg(feature = "fuzz-fd")]
2-
mod firedancer;
2+
pub mod firedancer;
33
#[cfg(feature = "fuzz")]
4-
mod mollusk;
4+
pub mod mollusk;
55

66
use {
77
crate::{

harness/src/fuzz/mollusk.rs

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ use {
1515
sysvars::Sysvars as FuzzSysvars, Fixture as FuzzFixture,
1616
},
1717
solana_sdk::{
18-
account::AccountSharedData, instruction::Instruction, pubkey::Pubkey,
18+
account::AccountSharedData,
19+
instruction::{Instruction, InstructionError},
20+
pubkey::Pubkey,
1921
slot_hashes::SlotHashes,
22+
sysvar::last_restart_slot::LastRestartSlot,
2023
},
2124
};
2225

@@ -34,26 +37,63 @@ impl From<&Sysvars> for FuzzSysvars {
3437
}
3538
}
3639

40+
impl From<&FuzzSysvars> for Sysvars {
41+
fn from(input: &FuzzSysvars) -> Self {
42+
let slot_hashes = SlotHashes::new(&input.slot_hashes);
43+
Self {
44+
clock: input.clock.clone(),
45+
epoch_rewards: input.epoch_rewards.clone(),
46+
epoch_schedule: input.epoch_schedule.clone(),
47+
last_restart_slot: LastRestartSlot::default(),
48+
rent: input.rent.clone(),
49+
slot_hashes,
50+
stake_history: input.stake_history.clone(),
51+
}
52+
}
53+
}
54+
3755
impl From<&InstructionResult> for FuzzEffects {
3856
fn from(input: &InstructionResult) -> Self {
3957
let compute_units_consumed = input.compute_units_consumed;
4058
let execution_time = input.execution_time;
59+
4160
let program_result = match &input.program_result {
4261
ProgramResult::Success => 0,
4362
ProgramResult::Failure(e) => u64::from(e.clone()) as u32,
4463
ProgramResult::UnknownError(_) => u32::MAX, //TODO
4564
};
4665

47-
let resulting_accounts = input
48-
.resulting_accounts
49-
.iter()
50-
.map(|(pubkey, account)| (*pubkey, account.clone()))
51-
.collect();
66+
let resulting_accounts = input.resulting_accounts.clone();
67+
68+
Self {
69+
compute_units_consumed,
70+
execution_time,
71+
program_result,
72+
resulting_accounts,
73+
}
74+
}
75+
}
76+
77+
impl From<&FuzzEffects> for InstructionResult {
78+
fn from(input: &FuzzEffects) -> Self {
79+
let compute_units_consumed = input.compute_units_consumed;
80+
let execution_time = input.execution_time;
81+
82+
let raw_result = if input.program_result == 0 {
83+
Ok(())
84+
} else {
85+
Err(InstructionError::from(input.program_result))
86+
};
87+
88+
let program_result = raw_result.clone().into();
89+
90+
let resulting_accounts = input.resulting_accounts.clone();
5291

5392
Self {
5493
compute_units_consumed,
5594
execution_time,
5695
program_result,
96+
raw_result,
5797
resulting_accounts,
5898
}
5999
}
@@ -86,6 +126,34 @@ fn build_fixture_context(
86126
}
87127
}
88128

129+
fn parse_fixture_context(
130+
context: &FuzzContext,
131+
) -> (Mollusk, Instruction, Vec<(Pubkey, AccountSharedData)>) {
132+
let FuzzContext {
133+
compute_budget,
134+
feature_set,
135+
sysvars,
136+
program_id,
137+
instruction_accounts,
138+
instruction_data,
139+
accounts,
140+
} = context;
141+
142+
let mollusk = Mollusk {
143+
compute_budget: *compute_budget,
144+
feature_set: feature_set.clone(),
145+
sysvars: sysvars.into(),
146+
..Default::default()
147+
};
148+
149+
let instruction =
150+
Instruction::new_with_bytes(*program_id, instruction_data, instruction_accounts.clone());
151+
152+
let accounts = accounts.clone();
153+
154+
(mollusk, instruction, accounts)
155+
}
156+
89157
pub fn build_fixture_from_mollusk_test(
90158
mollusk: &Mollusk,
91159
instruction: &Instruction,
@@ -99,3 +167,16 @@ pub fn build_fixture_from_mollusk_test(
99167
let output = FuzzEffects::from(result);
100168
FuzzFixture { input, output }
101169
}
170+
171+
pub fn load_fixture(
172+
fixture: &mollusk_svm_fuzz_fixture::Fixture,
173+
) -> (
174+
Mollusk,
175+
Instruction,
176+
Vec<(Pubkey, AccountSharedData)>,
177+
InstructionResult,
178+
) {
179+
let (mollusk, instruction, accounts) = parse_fixture_context(&fixture.input);
180+
let result = InstructionResult::from(&fixture.output);
181+
(mollusk, instruction, accounts, result)
182+
}

0 commit comments

Comments
 (0)