Skip to content

Commit ac6527d

Browse files
authored
test vectors test (#65)
1 parent fdf5285 commit ac6527d

File tree

7 files changed

+173
-6
lines changed

7 files changed

+173
-6
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ target/
99
# MSVC Windows builds of rustc generate these, which store debugging information
1010
*.pdb
1111

12-
.DS_Store
12+
.DS_Store
13+
14+
harness/tests/test-vectors/

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ num-format = "0.4.4"
3535
prost = "0.10"
3636
prost-build = "0.10"
3737
prost-types = "0.10"
38+
rayon = "1.10.0"
3839
serde = "1.0.203"
3940
serde_json = "1.0.117"
4041
serial_test = "2.0"

harness/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ solana-timings = { workspace = true }
3737

3838
[dev-dependencies]
3939
criterion = { workspace = true }
40+
rayon = { workspace = true }
4041
serial_test = { workspace = true }
4142

4243
[[bench]]

harness/src/fuzz/firedancer.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fn build_fixture_context(
7373
let Mollusk {
7474
compute_budget,
7575
feature_set,
76-
sysvars,
76+
slot, // FD-Fuzz feature only.
7777
..
7878
} = mollusk;
7979

@@ -100,9 +100,7 @@ fn build_fixture_context(
100100
instruction_accounts,
101101
instruction_data: instruction.data.clone(),
102102
compute_units_available: compute_budget.compute_unit_limit,
103-
slot_context: FuzzSlotContext {
104-
slot: sysvars.clock.slot,
105-
},
103+
slot_context: FuzzSlotContext { slot: *slot },
106104
epoch_context: FuzzEpochContext {
107105
feature_set: feature_set.clone(),
108106
},
@@ -118,8 +116,8 @@ fn parse_fixture_context(
118116
instruction_accounts,
119117
instruction_data,
120118
compute_units_available,
119+
slot_context,
121120
epoch_context,
122-
..
123121
} = context;
124122

125123
let compute_budget = ComputeBudget {
@@ -135,6 +133,7 @@ fn parse_fixture_context(
135133
let mollusk = Mollusk {
136134
compute_budget,
137135
feature_set: epoch_context.feature_set.clone(),
136+
slot: slot_context.slot,
138137
..Default::default()
139138
};
140139

harness/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ pub struct Mollusk {
6666
pub fee_structure: FeeStructure,
6767
pub program_cache: ProgramCache,
6868
pub sysvars: Sysvars,
69+
#[cfg(feature = "fuzz-fd")]
70+
pub slot: u64,
6971
}
7072

7173
impl Default for Mollusk {
@@ -94,6 +96,8 @@ impl Default for Mollusk {
9496
fee_structure: FeeStructure::default(),
9597
program_cache: ProgramCache::default(),
9698
sysvars: Sysvars::default(),
99+
#[cfg(feature = "fuzz-fd")]
100+
slot: 0,
97101
}
98102
}
99103
}

harness/tests/fd_test_vectors.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#![cfg(feature = "fuzz-fd")]
2+
3+
use {
4+
mollusk_svm::fuzz::firedancer::{build_fixture_from_mollusk_test, load_firedancer_fixture},
5+
mollusk_svm_fuzz_fixture_firedancer::{account::SeedAddress, Fixture},
6+
rayon::prelude::*,
7+
solana_sdk::{
8+
account::AccountSharedData, feature_set::FeatureSet, pubkey::Pubkey,
9+
transaction_context::InstructionAccount,
10+
},
11+
std::{assert_eq, fs, path::Path, process::Command},
12+
};
13+
14+
const TEST_VECTORS_PATH: &str = "tests/test-vectors";
15+
const TEST_VECTORS_REPOSITORY: &str = "https://github.com/firedancer-io/test-vectors.git";
16+
const TEST_VECTORS_TO_TEST: &[&str] = &[
17+
"instr/fixtures/address-lookup-table",
18+
"instr/fixtures/config",
19+
"instr/fixtures/stake",
20+
// Add more here!
21+
];
22+
23+
#[test]
24+
fn test_load_firedancer_fixtures() {
25+
let test_vectors_out_dir = Path::new(TEST_VECTORS_PATH);
26+
27+
// Fetch the test vectors.
28+
if test_vectors_out_dir.exists() {
29+
Command::new("git")
30+
.arg("-C")
31+
.arg(test_vectors_out_dir)
32+
.arg("fetch")
33+
.status()
34+
.expect("Failed to execute git pull");
35+
} else {
36+
Command::new("git")
37+
.arg("clone")
38+
.arg("--depth=1")
39+
.arg(TEST_VECTORS_REPOSITORY)
40+
.arg(test_vectors_out_dir)
41+
.status()
42+
.expect("Failed to execute git clone");
43+
}
44+
45+
// Attempt to go fixture -> Mollusk -> fixture and compare.
46+
TEST_VECTORS_TO_TEST.par_iter().for_each(|directory| {
47+
fs::read_dir(test_vectors_out_dir.join(directory))
48+
.unwrap()
49+
.par_bridge()
50+
.for_each(|entry| {
51+
let path = entry.unwrap().path();
52+
if path.is_file() && path.extension().is_some_and(|ext| ext == "fix") {
53+
let loaded_fixture = Fixture::load_from_blob_file(path.to_str().unwrap());
54+
let (mollusk, instruction, accounts, result) =
55+
load_firedancer_fixture(&loaded_fixture);
56+
let generated_fixture = build_fixture_from_mollusk_test(
57+
&mollusk,
58+
&instruction,
59+
&accounts,
60+
&result,
61+
/* checks */ &[],
62+
);
63+
64+
assert_eq!(loaded_fixture.metadata, generated_fixture.metadata);
65+
assert_eq!(
66+
loaded_fixture.input.program_id,
67+
generated_fixture.input.program_id,
68+
);
69+
// Sometimes ordering is not the same because of the `KeyMap`.
70+
// Contents should match though.
71+
compare_accounts(
72+
&loaded_fixture.input.accounts,
73+
&generated_fixture.input.accounts,
74+
);
75+
compare_instruction_accounts(
76+
&loaded_fixture.input.instruction_accounts,
77+
&generated_fixture.input.instruction_accounts,
78+
);
79+
assert_eq!(
80+
loaded_fixture.input.compute_units_available,
81+
generated_fixture.input.compute_units_available,
82+
);
83+
assert_eq!(
84+
loaded_fixture.input.slot_context,
85+
generated_fixture.input.slot_context,
86+
);
87+
// Feature set is not always ordered the same as a side effect
88+
// of `HashMap`.
89+
compare_feature_sets(
90+
&loaded_fixture.input.epoch_context.feature_set,
91+
&generated_fixture.input.epoch_context.feature_set,
92+
);
93+
assert_eq!(
94+
loaded_fixture.output.program_result,
95+
generated_fixture.output.program_result,
96+
);
97+
assert_eq!(
98+
loaded_fixture.output.program_custom_code,
99+
generated_fixture.output.program_custom_code,
100+
);
101+
compare_accounts(
102+
&loaded_fixture.output.modified_accounts,
103+
&generated_fixture.output.modified_accounts,
104+
);
105+
assert_eq!(
106+
loaded_fixture.output.compute_units_available,
107+
generated_fixture.output.compute_units_available,
108+
);
109+
// assert_eq!(
110+
// loaded_fixture.output.return_data,
111+
// generated_fixture.output.return_data,
112+
// );
113+
}
114+
});
115+
});
116+
}
117+
118+
fn compare_accounts(
119+
a: &[(Pubkey, AccountSharedData, Option<SeedAddress>)],
120+
b: &[(Pubkey, AccountSharedData, Option<SeedAddress>)],
121+
) -> bool {
122+
if a.len() != b.len() {
123+
return false;
124+
}
125+
126+
let mut a_sorted = a.to_vec();
127+
let mut b_sorted = b.to_vec();
128+
129+
// Sort by Pubkey
130+
a_sorted.sort_by(|(pubkey_a, _, _), (pubkey_b, _, _)| pubkey_a.cmp(pubkey_b));
131+
b_sorted.sort_by(|(pubkey_a, _, _), (pubkey_b, _, _)| pubkey_a.cmp(pubkey_b));
132+
133+
// Compare sorted lists
134+
a_sorted == b_sorted
135+
}
136+
137+
fn compare_instruction_accounts(a: &[InstructionAccount], b: &[InstructionAccount]) -> bool {
138+
if a.len() != b.len() {
139+
return false;
140+
}
141+
142+
let mut a_sorted = a.to_vec();
143+
let mut b_sorted = b.to_vec();
144+
145+
// Sort by Pubkey
146+
a_sorted.sort_by(|ia_a, ia_b| ia_a.index_in_transaction.cmp(&ia_b.index_in_transaction));
147+
b_sorted.sort_by(|ia_a, ia_b| ia_a.index_in_transaction.cmp(&ia_b.index_in_transaction));
148+
149+
// Compare sorted lists
150+
a_sorted == b_sorted
151+
}
152+
153+
fn compare_feature_sets(from_fixture: &FeatureSet, from_mollusk: &FeatureSet) {
154+
assert_eq!(from_fixture.active.len(), from_mollusk.active.len());
155+
assert_eq!(from_fixture.inactive.len(), from_mollusk.inactive.len());
156+
for f in from_fixture.active.keys() {
157+
assert!(from_mollusk.active.contains_key(f));
158+
}
159+
}

0 commit comments

Comments
 (0)