diff --git a/.gitignore b/.gitignore
index a1f5457ee499f12c2bb84a6e84e8eebe93f37e33..73ff894cba2202642247ed625ad91ab50de10c29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
 /**/target
 **/*.rs.bk
+/keys
+/data*
+/shell.nix
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8be61ba9c75c5c6718214fc2af5de1354d458bb0..1780316370cc17c92486b2e7631f0c4873ea0c2f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,6 +8,7 @@ variables:
   CI_SERVER_NAME:                  "GitLab CI"
   CARGO_HOME:                      "${CI_PROJECT_DIR}/.cargo"
   RUST_TOOLCHAIN:                  "stable"
+  GIT_SUBMODULE_STRATEGY:          "recursive"
 
 cache:
   key:                             "${CI_JOB_NAME}"
@@ -51,4 +52,3 @@ build-linux:
     - master
     - tags
     - web
-
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000000000000000000000000000000000..b01e31d3c98f1e5b04cb937369d92eb105334395
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "yamltests/res/eth2.0-tests"]
+	path = yamltests/res/eth2.0-tests
+	url = https://github.com/ethereum/eth2.0-tests
diff --git a/yamltests/res/eth2.0-tests b/yamltests/res/eth2.0-tests
new file mode 160000
index 0000000000000000000000000000000000000000..3ec28295b0c8365f0ec7ad79cfe933755021ee1b
--- /dev/null
+++ b/yamltests/res/eth2.0-tests
@@ -0,0 +1 @@
+Subproject commit 3ec28295b0c8365f0ec7ad79cfe933755021ee1b
diff --git a/yamltests/src/lib.rs b/yamltests/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2f8db035254b54b149bfbf37a8ecd5400deb5574
--- /dev/null
+++ b/yamltests/src/lib.rs
@@ -0,0 +1,159 @@
+use std::io::{self, Write};
+use std::collections::HashMap;
+
+use serde_derive::{Serialize, Deserialize};
+use ssz::FixedVec;
+use primitives::H256;
+use serenity::{BeaconState, BeaconBlock, Slot, Fork, Timestamp, Validator, Epoch, Shard, Eth1Data, Eth1DataVote, PendingAttestation, Crosslink, BeaconBlockHeader};
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(deny_unknown_fields)]
+pub struct ExpectedBeaconState {
+	// Misc
+	pub slot: Option<Slot>,
+	pub genesis_time: Option<Timestamp>,
+	pub fork: Option<Fork>,
+
+	// Validator registry
+	pub validator_registry: Option<Vec<Validator>>,
+	pub validator_balances: Option<Vec<u64>>,
+	pub validator_registry_update_epoch: Option<Epoch>,
+
+	// Randomness and committees
+	pub latest_randao_mixes: Option<FixedVec<H256>>,
+	pub previous_shuffling_start_shard: Option<Shard>,
+	pub current_shuffling_start_shard: Option<Shard>,
+	pub previous_shuffling_epoch: Option<Epoch>,
+	pub current_shuffling_epoch: Option<Epoch>,
+	pub previous_shuffling_seed: Option<H256>,
+	pub current_shuffling_seed: Option<H256>,
+
+	// Finality
+	pub previous_epoch_attestations: Option<Vec<PendingAttestation>>,
+	pub current_epoch_attestations: Option<Vec<PendingAttestation>>,
+	pub previous_justified_epoch: Option<Epoch>,
+	pub current_justified_epoch: Option<Epoch>,
+	pub previous_justified_root: Option<H256>,
+	pub current_justified_root: Option<H256>,
+	pub justification_bitfield: Option<u64>,
+	pub finalized_epoch: Option<Epoch>,
+	pub finalized_root: Option<H256>,
+
+	// Recent state
+	pub latest_crosslinks: Option<FixedVec<Crosslink>>,
+	pub latest_block_roots: Option<FixedVec<H256>>,
+	pub latest_state_roots: Option<FixedVec<H256>>,
+	pub latest_active_index_roots: Option<FixedVec<H256>>,
+	pub latest_slashed_balances: Option<FixedVec<u64>>,
+	pub latest_block_header: Option<BeaconBlockHeader>,
+	pub historical_roots: Option<Vec<H256>>,
+
+	// Ethereum 1.0 chain data
+	pub latest_eth1_data: Option<Eth1Data>,
+	pub eth1_data_votes: Option<Vec<Eth1DataVote>>,
+	pub deposit_index: Option<u64>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(deny_unknown_fields)]
+pub struct Collection {
+	pub title: String,
+	pub summary: String,
+	pub test_suite: String,
+	pub fork: String,
+	pub test_cases: Vec<Test>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(deny_unknown_fields)]
+pub struct Test {
+	pub name: String,
+	pub config: HashMap<String, String>,
+	pub verify_signatures: bool,
+	pub initial_state: BeaconState,
+	pub blocks: Vec<BeaconBlock>,
+	pub expected_state: ExpectedBeaconState,
+}
+
+pub fn run_collection(coll: Collection, only: Option<&str>) {
+	for test in coll.test_cases {
+		if let Some(only) = only {
+			if test.name != only {
+				continue
+			}
+		}
+		run_test(test);
+	}
+}
+
+pub fn run_test(test: Test) {
+	print!("Running test: {} ...", test.name);
+	io::stdout().flush().ok().expect("Could not flush stdout");
+	let mut state = test.initial_state;
+
+	for block in test.blocks {
+		match serenity::execute_block(&block, &mut state) {
+			Ok(()) => {
+				println!(" done");
+			},
+			Err(err) => {
+				println!(" failed\n");
+				println!("Error: {:?}", err);
+				panic!();
+			}
+		}
+	}
+
+	check_expected(&state, test.expected_state);
+}
+
+pub fn check_expected(state: &BeaconState, expected: ExpectedBeaconState) {
+	macro_rules! check {
+		( $($field:tt,)+ ) => {
+			$(
+				if let Some($field) = expected.$field {
+					if $field != state.$field {
+						println!("\nExpected state check failed for {}", stringify!($field));
+						println!("Expected: {:?}", $field);
+						println!("Actual: {:?}", state.$field);
+						panic!();
+					}
+				}
+			)+
+		}
+	}
+
+	check!(
+		// Misc
+		slot, genesis_time, fork,
+		// Validator registry
+		validator_registry, validator_balances, validator_registry_update_epoch,
+		// Randomness and committees
+		latest_randao_mixes, previous_shuffling_start_shard,
+		current_shuffling_start_shard, previous_shuffling_epoch,
+		current_shuffling_epoch, previous_shuffling_seed,
+		current_shuffling_seed,
+		// Finality
+		previous_epoch_attestations, current_epoch_attestations,
+		previous_justified_epoch, current_justified_epoch,
+		previous_justified_root, current_justified_root,
+		justification_bitfield, finalized_epoch, finalized_root,
+		// Recent state
+		latest_crosslinks, latest_block_roots, latest_state_roots,
+		latest_active_index_roots, latest_slashed_balances,
+		latest_block_header, historical_roots,
+		// Ethereum 1.0 chain data
+		latest_eth1_data, eth1_data_votes, deposit_index,
+	);
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn sanity_check_small_config_32_vals() {
+		let coll = serde_yaml::from_str(&include_str!("../res/eth2.0-tests/state/sanity-check_small-config_32-vals.yaml")).unwrap();
+		run_collection(coll, None);
+	}
+}
diff --git a/yamltests/src/main.rs b/yamltests/src/main.rs
index 438d65d0eac2ce72b471da7a128f54a3234d0a6b..0861eab64b4d7fa1fe1fba91028ec26fb066a2dc 100644
--- a/yamltests/src/main.rs
+++ b/yamltests/src/main.rs
@@ -1,81 +1,8 @@
-use std::collections::HashMap;
 use std::fs::File;
-use std::io::{self, BufReader, Write};
+use std::io::BufReader;
 
 use clap::{App, Arg};
-use serde_derive::{Serialize, Deserialize};
-use ssz::FixedVec;
-use primitives::H256;
-use serenity::{BeaconState, BeaconBlock, Slot, Fork, Timestamp, Validator, Epoch, Shard, Eth1Data, Eth1DataVote, PendingAttestation, Crosslink, BeaconBlockHeader};
-
-#[derive(Serialize, Deserialize, Debug)]
-#[serde(deny_unknown_fields)]
-pub struct ExpectedBeaconState {
-	// Misc
-	pub slot: Option<Slot>,
-	pub genesis_time: Option<Timestamp>,
-	pub fork: Option<Fork>,
-
-	// Validator registry
-	pub validator_registry: Option<Vec<Validator>>,
-	pub validator_balances: Option<Vec<u64>>,
-	pub validator_registry_update_epoch: Option<Epoch>,
-
-	// Randomness and committees
-	pub latest_randao_mixes: Option<FixedVec<H256>>,
-	pub previous_shuffling_start_shard: Option<Shard>,
-	pub current_shuffling_start_shard: Option<Shard>,
-	pub previous_shuffling_epoch: Option<Epoch>,
-	pub current_shuffling_epoch: Option<Epoch>,
-	pub previous_shuffling_seed: Option<H256>,
-	pub current_shuffling_seed: Option<H256>,
-
-	// Finality
-	pub previous_epoch_attestations: Option<Vec<PendingAttestation>>,
-	pub current_epoch_attestations: Option<Vec<PendingAttestation>>,
-	pub previous_justified_epoch: Option<Epoch>,
-	pub current_justified_epoch: Option<Epoch>,
-	pub previous_justified_root: Option<H256>,
-	pub current_justified_root: Option<H256>,
-	pub justification_bitfield: Option<u64>,
-	pub finalized_epoch: Option<Epoch>,
-	pub finalized_root: Option<H256>,
-
-	// Recent state
-	pub latest_crosslinks: Option<FixedVec<Crosslink>>,
-	pub latest_block_roots: Option<FixedVec<H256>>,
-	pub latest_state_roots: Option<FixedVec<H256>>,
-	pub latest_active_index_roots: Option<FixedVec<H256>>,
-	pub latest_slashed_balances: Option<FixedVec<u64>>,
-	pub latest_block_header: Option<BeaconBlockHeader>,
-	pub historical_roots: Option<Vec<H256>>,
-
-	// Ethereum 1.0 chain data
-	pub latest_eth1_data: Option<Eth1Data>,
-	pub eth1_data_votes: Option<Vec<Eth1DataVote>>,
-	pub deposit_index: Option<u64>,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-#[serde(deny_unknown_fields)]
-pub struct Collection {
-	pub title: String,
-	pub summary: String,
-	pub test_suite: String,
-	pub fork: String,
-	pub test_cases: Vec<Test>,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-#[serde(deny_unknown_fields)]
-pub struct Test {
-	pub name: String,
-	pub config: HashMap<String, String>,
-	pub verify_signatures: bool,
-	pub initial_state: BeaconState,
-	pub blocks: Vec<BeaconBlock>,
-	pub expected_state: ExpectedBeaconState,
-}
+use yamltests::{Collection, run_collection};
 
 fn main() {
 	let matches = App::new("yamltests")
@@ -95,73 +22,5 @@ fn main() {
 	let only = matches.value_of("only");
 	let coll = serde_yaml::from_reader::<_, Collection>(BufReader::new(file)).expect("Parse test cases failed");
 
-	for test in coll.test_cases {
-		if let Some(only) = only {
-			if test.name != only {
-				continue
-			}
-		}
-		run_test(test);
-	}
-}
-
-fn run_test(test: Test) {
-	print!("Running test: {} ...", test.name);
-	io::stdout().flush().ok().expect("Could not flush stdout");
-	let mut state = test.initial_state;
-
-	for block in test.blocks {
-		match serenity::execute_block(&block, &mut state) {
-			Ok(()) => {
-				println!(" done");
-			},
-			Err(err) => {
-				println!(" failed\n");
-				println!("Error: {:?}", err);
-				panic!();
-			}
-		}
-	}
-
-	check_expected(&state, test.expected_state);
-}
-
-fn check_expected(state: &BeaconState, expected: ExpectedBeaconState) {
-	macro_rules! check {
-		( $($field:tt,)+ ) => {
-			$(
-				if let Some($field) = expected.$field {
-					if $field != state.$field {
-						println!("\nExpected state check failed for {}", stringify!($field));
-						println!("Expected: {:?}", $field);
-						println!("Actual: {:?}", state.$field);
-						panic!();
-					}
-				}
-			)+
-		}
-	}
-
-	check!(
-		// Misc
-		slot, genesis_time, fork,
-		// Validator registry
-		validator_registry, validator_balances, validator_registry_update_epoch,
-		// Randomness and committees
-		latest_randao_mixes, previous_shuffling_start_shard,
-		current_shuffling_start_shard, previous_shuffling_epoch,
-		current_shuffling_epoch, previous_shuffling_seed,
-		current_shuffling_seed,
-		// Finality
-		previous_epoch_attestations, current_epoch_attestations,
-		previous_justified_epoch, current_justified_epoch,
-		previous_justified_root, current_justified_root,
-		justification_bitfield, finalized_epoch, finalized_root,
-		// Recent state
-		latest_crosslinks, latest_block_roots, latest_state_roots,
-		latest_active_index_roots, latest_slashed_balances,
-		latest_block_header, historical_roots,
-		// Ethereum 1.0 chain data
-		latest_eth1_data, eth1_data_votes, deposit_index,
-	);
+	run_collection(coll, only);
 }