diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index fee288cb2b7f8e1d685816dc2ef2a20cbcfc5038..019c14b2c7dfdfa9a66dc8fd6ffc46bb2654a576 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -23,7 +23,9 @@
 #![recursion_limit = "256"]
 
 use codec::{Decode, Encode, MaxEncodedLen};
-use frame_election_provider_support::{onchain, ExtendedBalance, SequentialPhragmen, VoteWeight};
+use frame_election_provider_support::{
+	onchain, ElectionDataProvider, ExtendedBalance, SequentialPhragmen, VoteWeight,
+};
 use frame_support::{
 	construct_runtime,
 	pallet_prelude::Get,
@@ -660,6 +662,25 @@ impl onchain::BoundedConfig for OnChainSeqPhragmen {
 	type TargetsBound = ConstU32<2_000>;
 }
 
+impl pallet_election_provider_multi_phase::MinerConfig for Runtime {
+	type AccountId = AccountId;
+	type MaxLength = MinerMaxLength;
+	type MaxWeight = MinerMaxWeight;
+	type Solution = NposSolution16;
+	type MaxVotesPerVoter =
+	<<Self as pallet_election_provider_multi_phase::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter;
+
+	// The unsigned submissions have to respect the weight of the submit_unsigned call, thus their
+	// weight estimate function is wired to this call's weight.
+	fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
+		<
+			<Self as pallet_election_provider_multi_phase::Config>::WeightInfo
+			as
+			pallet_election_provider_multi_phase::WeightInfo
+		>::submit_unsigned(v, t, a, d)
+	}
+}
+
 impl pallet_election_provider_multi_phase::Config for Runtime {
 	type Event = Event;
 	type Currency = Balances;
@@ -669,9 +690,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
 	type BetterUnsignedThreshold = BetterUnsignedThreshold;
 	type BetterSignedThreshold = ();
 	type OffchainRepeat = OffchainRepeat;
-	type MinerMaxWeight = MinerMaxWeight;
-	type MinerMaxLength = MinerMaxLength;
 	type MinerTxPriority = MultiPhaseUnsignedPriority;
+	type MinerConfig = Self;
 	type SignedMaxSubmissions = ConstU32<10>;
 	type SignedRewardBase = SignedRewardBase;
 	type SignedDepositBase = SignedDepositBase;
@@ -682,7 +702,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
 	type SlashHandler = (); // burn slashes
 	type RewardHandler = (); // nothing to do upon rewards
 	type DataProvider = Staking;
-	type Solution = NposSolution16;
 	type Fallback = onchain::BoundedExecution<OnChainSeqPhragmen>;
 	type GovernanceFallback = onchain::BoundedExecution<OnChainSeqPhragmen>;
 	type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Self>, OffchainRandomBalancing>;
@@ -2017,7 +2036,7 @@ mod tests {
 	#[test]
 	fn perbill_as_onchain_accuracy() {
 		type OnChainAccuracy =
-			<<Runtime as pallet_election_provider_multi_phase::Config>::Solution as NposSolution>::Accuracy;
+			<<Runtime as pallet_election_provider_multi_phase::MinerConfig>::Solution as NposSolution>::Accuracy;
 		let maximum_chain_accuracy: Vec<UpperOf<OnChainAccuracy>> = (0..MaxNominations::get())
 			.map(|_| <UpperOf<OnChainAccuracy>>::from(OnChainAccuracy::one().deconstruct()))
 			.collect();
diff --git a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs
index a1c643754158b352404cd66eccea6a29311e402a..a8195df7305ffa1073f5afbc60d8f8c0cfcfff5e 100644
--- a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs
+++ b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs
@@ -39,15 +39,15 @@ fn solution_with_size<T: Config>(
 	size: SolutionOrSnapshotSize,
 	active_voters_count: u32,
 	desired_targets: u32,
-) -> Result<RawSolution<SolutionOf<T>>, &'static str> {
+) -> Result<RawSolution<SolutionOf<T::MinerConfig>>, &'static str> {
 	ensure!(size.targets >= desired_targets, "must have enough targets");
 	ensure!(
-		size.targets >= (<SolutionOf<T>>::LIMIT * 2) as u32,
+		size.targets >= (<SolutionOf<T::MinerConfig>>::LIMIT * 2) as u32,
 		"must have enough targets for unique votes."
 	);
 	ensure!(size.voters >= active_voters_count, "must have enough voters");
 	ensure!(
-		(<SolutionOf<T>>::LIMIT as u32) < desired_targets,
+		(<SolutionOf<T::MinerConfig>>::LIMIT as u32) < desired_targets,
 		"must have enough winners to give them votes."
 	);
 
@@ -74,10 +74,10 @@ fn solution_with_size<T: Config>(
 			// chose a random subset of winners.
 			let winner_votes: BoundedVec<_, _> = winners
 				.as_slice()
-				.choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT)
+				.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
 				.cloned()
 				.try_collect()
-				.expect("<SolutionOf<T>>::LIMIT is the correct bound; qed.");
+				.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
 			let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
 			(voter, stake, winner_votes)
 		})
@@ -92,10 +92,10 @@ fn solution_with_size<T: Config>(
 	let rest_voters = (active_voters_count..size.voters)
 		.map(|i| {
 			let votes: BoundedVec<_, _> = (&non_winners)
-				.choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT)
+				.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
 				.cloned()
 				.try_collect()
-				.expect("<SolutionOf<T>>::LIMIT is the correct bound; qed.");
+				.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
 			let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
 			(voter, stake, votes)
 		})
@@ -120,12 +120,12 @@ fn solution_with_size<T: Config>(
 	// down the road.
 	T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake));
 
-	let cache = helpers::generate_voter_cache::<T>(&all_voters);
-	let stake_of = helpers::stake_of_fn::<T>(&all_voters, &cache);
-	let voter_index = helpers::voter_index_fn::<T>(&cache);
-	let target_index = helpers::target_index_fn::<T>(&targets);
-	let voter_at = helpers::voter_at_fn::<T>(&all_voters);
-	let target_at = helpers::target_at_fn::<T>(&targets);
+	let cache = helpers::generate_voter_cache::<T::MinerConfig>(&all_voters);
+	let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&all_voters, &cache);
+	let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
+	let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
+	let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&all_voters);
+	let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets);
 
 	let assignments = active_voters
 		.iter()
@@ -143,7 +143,8 @@ fn solution_with_size<T: Config>(
 		.collect::<Vec<_>>();
 
 	let solution =
-		<SolutionOf<T>>::from_assignment(&assignments, &voter_index, &target_index).unwrap();
+		<SolutionOf<T::MinerConfig>>::from_assignment(&assignments, &voter_index, &target_index)
+			.unwrap();
 	let score = solution.clone().score(stake_of, voter_at, target_at).unwrap();
 	let round = <MultiPhase<T>>::round();
 
@@ -480,14 +481,14 @@ frame_benchmarking::benchmarks! {
 		let witness = SolutionOrSnapshotSize { voters: v, targets: t };
 		let RawSolution { solution, .. } = solution_with_size::<T>(witness, a, d)?;
 		let RoundSnapshot { voters, targets } = MultiPhase::<T>::snapshot().ok_or("snapshot missing")?;
-		let voter_at = helpers::voter_at_fn::<T>(&voters);
-		let target_at = helpers::target_at_fn::<T>(&targets);
+		let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&voters);
+		let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets);
 		let mut assignments = solution.into_assignment(voter_at, target_at).expect("solution generated by `solution_with_size` must be valid.");
 
 		// make a voter cache and some helper functions for access
-		let cache = helpers::generate_voter_cache::<T>(&voters);
-		let voter_index = helpers::voter_index_fn::<T>(&cache);
-		let target_index = helpers::target_index_fn::<T>(&targets);
+		let cache = helpers::generate_voter_cache::<T::MinerConfig>(&voters);
+		let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
+		let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
 
 		// sort assignments by decreasing voter stake
 		assignments.sort_by_key(|crate::unsigned::Assignment::<T> { who, .. }| {
@@ -504,21 +505,21 @@ frame_benchmarking::benchmarks! {
 			.collect::<Result<Vec<_>, _>>()
 			.unwrap();
 
-		let encoded_size_of = |assignments: &[IndexAssignmentOf<T>]| {
-			SolutionOf::<T>::try_from(assignments).map(|solution| solution.encoded_size())
+		let encoded_size_of = |assignments: &[IndexAssignmentOf<T::MinerConfig>]| {
+			SolutionOf::<T::MinerConfig>::try_from(assignments).map(|solution| solution.encoded_size())
 		};
 
 		let desired_size = Percent::from_percent(100 - f.saturated_into::<u8>())
 			.mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap());
 		log!(trace, "desired_size = {}", desired_size);
 	}: {
-		MultiPhase::<T>::trim_assignments_length(
+		crate::Miner::<T::MinerConfig>::trim_assignments_length(
 			desired_size.saturated_into(),
 			&mut index_assignments,
 			&encoded_size_of,
 		).unwrap();
 	} verify {
-		let solution = SolutionOf::<T>::try_from(index_assignments.as_slice()).unwrap();
+		let solution = SolutionOf::<T::MinerConfig>::try_from(index_assignments.as_slice()).unwrap();
 		let encoding = solution.encode();
 		log!(
 			trace,
diff --git a/substrate/frame/election-provider-multi-phase/src/helpers.rs b/substrate/frame/election-provider-multi-phase/src/helpers.rs
index 48da194cc65d9a1fcbb4475711a815827496d7c4..0a7240c5d27af326ed88694b4c1925bfbd4519cc 100644
--- a/substrate/frame/election-provider-multi-phase/src/helpers.rs
+++ b/substrate/frame/election-provider-multi-phase/src/helpers.rs
@@ -17,7 +17,10 @@
 
 //! Some helper functions/macros for this crate.
 
-use crate::{unsigned::VoterOf, Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight};
+use crate::{
+	unsigned::{MinerConfig, MinerVoterOf},
+	SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight,
+};
 use sp_std::{collections::btree_map::BTreeMap, prelude::*};
 
 #[macro_export]
@@ -30,11 +33,22 @@ macro_rules! log {
 	};
 }
 
+// This is only useful for a context where a `<T: Config>` is not in scope.
+#[macro_export]
+macro_rules! log_no_system {
+	($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => {
+		log::$level!(
+			target: $crate::LOG_TARGET,
+			concat!("🗳 ", $pattern) $(, $values)*
+		)
+	};
+}
+
 /// Generate a btree-map cache of the voters and their indices.
 ///
 /// This can be used to efficiently build index getter closures.
-pub fn generate_voter_cache<T: Config>(
-	snapshot: &Vec<VoterOf<T>>,
+pub fn generate_voter_cache<T: MinerConfig>(
+	snapshot: &Vec<MinerVoterOf<T>>,
 ) -> BTreeMap<T::AccountId, usize> {
 	let mut cache: BTreeMap<T::AccountId, usize> = BTreeMap::new();
 	snapshot.iter().enumerate().for_each(|(i, (x, _, _))| {
@@ -54,7 +68,7 @@ pub fn generate_voter_cache<T: Config>(
 /// ## Warning
 ///
 /// Note that this will represent the snapshot data from which the `cache` is generated.
-pub fn voter_index_fn<T: Config>(
+pub fn voter_index_fn<T: MinerConfig>(
 	cache: &BTreeMap<T::AccountId, usize>,
 ) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
 	move |who| {
@@ -68,7 +82,7 @@ pub fn voter_index_fn<T: Config>(
 ///
 /// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is
 /// borrowed.
-pub fn voter_index_fn_owned<T: Config>(
+pub fn voter_index_fn_owned<T: MinerConfig>(
 	cache: BTreeMap<T::AccountId, usize>,
 ) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> {
 	move |who| {
@@ -83,7 +97,7 @@ pub fn voter_index_fn_owned<T: Config>(
 /// ## Warning
 ///
 /// Note that this will represent the snapshot data from which the `cache` is generated.
-pub fn voter_index_fn_usize<T: Config>(
+pub fn voter_index_fn_usize<T: MinerConfig>(
 	cache: &BTreeMap<T::AccountId, usize>,
 ) -> impl Fn(&T::AccountId) -> Option<usize> + '_ {
 	move |who| cache.get(who).cloned()
@@ -96,8 +110,8 @@ pub fn voter_index_fn_usize<T: Config>(
 ///
 /// Not meant to be used in production.
 #[cfg(test)]
-pub fn voter_index_fn_linear<T: Config>(
-	snapshot: &Vec<VoterOf<T>>,
+pub fn voter_index_fn_linear<T: MinerConfig>(
+	snapshot: &Vec<MinerVoterOf<T>>,
 ) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
 	move |who| {
 		snapshot
@@ -114,7 +128,7 @@ pub fn voter_index_fn_linear<T: Config>(
 /// Note: to the extent possible, the returned function should be cached and reused. Producing that
 /// function requires a `O(n log n)` data transform. Each invocation of that function completes
 /// in `O(log n)`.
-pub fn target_index_fn<T: Config>(
+pub fn target_index_fn<T: MinerConfig>(
 	snapshot: &Vec<T::AccountId>,
 ) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
 	let cache: BTreeMap<_, _> =
@@ -134,7 +148,7 @@ pub fn target_index_fn<T: Config>(
 ///
 /// Not meant to be used in production.
 #[cfg(test)]
-pub fn target_index_fn_linear<T: Config>(
+pub fn target_index_fn_linear<T: MinerConfig>(
 	snapshot: &Vec<T::AccountId>,
 ) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
 	move |who| {
@@ -147,8 +161,8 @@ pub fn target_index_fn_linear<T: Config>(
 
 /// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter
 /// account using a linearly indexible snapshot.
-pub fn voter_at_fn<T: Config>(
-	snapshot: &Vec<VoterOf<T>>,
+pub fn voter_at_fn<T: MinerConfig>(
+	snapshot: &Vec<MinerVoterOf<T>>,
 ) -> impl Fn(SolutionVoterIndexOf<T>) -> Option<T::AccountId> + '_ {
 	move |i| {
 		<SolutionVoterIndexOf<T> as TryInto<usize>>::try_into(i)
@@ -159,7 +173,7 @@ pub fn voter_at_fn<T: Config>(
 
 /// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target
 /// account using a linearly indexible snapshot.
-pub fn target_at_fn<T: Config>(
+pub fn target_at_fn<T: MinerConfig>(
 	snapshot: &Vec<T::AccountId>,
 ) -> impl Fn(SolutionTargetIndexOf<T>) -> Option<T::AccountId> + '_ {
 	move |i| {
@@ -173,8 +187,8 @@ pub fn target_at_fn<T: Config>(
 ///
 /// This is not optimized and uses a linear search.
 #[cfg(test)]
-pub fn stake_of_fn_linear<T: Config>(
-	snapshot: &Vec<VoterOf<T>>,
+pub fn stake_of_fn_linear<T: MinerConfig>(
+	snapshot: &Vec<MinerVoterOf<T>>,
 ) -> impl Fn(&T::AccountId) -> VoteWeight + '_ {
 	move |who| {
 		snapshot
@@ -191,8 +205,8 @@ pub fn stake_of_fn_linear<T: Config>(
 ///
 /// The cache need must be derived from the same snapshot. Zero is returned if a voter is
 /// non-existent.
-pub fn stake_of_fn<'a, T: Config>(
-	snapshot: &'a Vec<VoterOf<T>>,
+pub fn stake_of_fn<'a, T: MinerConfig>(
+	snapshot: &'a Vec<MinerVoterOf<T>>,
 	cache: &'a BTreeMap<T::AccountId, usize>,
 ) -> impl Fn(&T::AccountId) -> VoteWeight + 'a {
 	move |who| {
diff --git a/substrate/frame/election-provider-multi-phase/src/lib.rs b/substrate/frame/election-provider-multi-phase/src/lib.rs
index 4aea5b9da4794486a431f3bd878b178dbd9f5049..36d2373c9f623a76a5e935ffee4a546bc408b20e 100644
--- a/substrate/frame/election-provider-multi-phase/src/lib.rs
+++ b/substrate/frame/election-provider-multi-phase/src/lib.rs
@@ -103,7 +103,7 @@
 //!
 //! Validators will only submit solutions if the one that they have computed is sufficiently better
 //! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the
-//! weight of the solution to [`pallet::Config::MinerMaxWeight`].
+//! weight of the solution to [`MinerConfig::MaxWeight`].
 //!
 //! The unsigned phase can be made passive depending on how the previous signed phase went, by
 //! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always
@@ -276,16 +276,18 @@ pub use signed::{
 	BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission, SignedSubmissionOf,
 	SignedSubmissions, SubmissionIndicesOf,
 };
+pub use unsigned::{Miner, MinerConfig};
 
 /// The solution type used by this crate.
-pub type SolutionOf<T> = <T as Config>::Solution;
+pub type SolutionOf<T> = <T as MinerConfig>::Solution;
 
 /// The voter index. Derived from [`SolutionOf`].
 pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex;
 /// The target index. Derived from [`SolutionOf`].
 pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex;
 /// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`].
-pub type SolutionAccuracyOf<T> = <SolutionOf<T> as NposSolution>::Accuracy;
+pub type SolutionAccuracyOf<T> =
+	<SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy;
 /// The fallback election type.
 pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error;
 
@@ -488,7 +490,7 @@ pub enum ElectionError<T: Config> {
 	/// An error happened in the feasibility check sub-system.
 	Feasibility(FeasibilityError),
 	/// An error in the miner (offchain) sub-system.
-	Miner(unsigned::MinerError<T>),
+	Miner(unsigned::MinerError),
 	/// An error happened in the data provider.
 	DataProvider(&'static str),
 	/// An error nested in the fallback.
@@ -520,8 +522,8 @@ impl<T: Config> From<FeasibilityError> for ElectionError<T> {
 	}
 }
 
-impl<T: Config> From<unsigned::MinerError<T>> for ElectionError<T> {
-	fn from(e: unsigned::MinerError<T>) -> Self {
+impl<T: Config> From<unsigned::MinerError> for ElectionError<T> {
+	fn from(e: unsigned::MinerError) -> Self {
 		ElectionError::Miner(e)
 	}
 }
@@ -605,12 +607,14 @@ pub mod pallet {
 		#[pallet::constant]
 		type MinerTxPriority: Get<TransactionPriority>;
 
-		/// Maximum weight that the miner should consume.
+		/// Configurations of the embedded miner.
 		///
-		/// The miner will ensure that the total weight of the unsigned solution will not exceed
-		/// this value, based on [`WeightInfo::submit_unsigned`].
-		#[pallet::constant]
-		type MinerMaxWeight: Get<Weight>;
+		/// Any external software implementing this can use the [`unsigned::Miner`] type provided,
+		/// which can mine new solutions and trim them accordingly.
+		type MinerConfig: crate::unsigned::MinerConfig<
+			AccountId = Self::AccountId,
+			MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
+		>;
 
 		/// Maximum number of signed submissions that can be queued.
 		///
@@ -624,7 +628,9 @@ pub mod pallet {
 
 		/// Maximum weight of a signed solution.
 		///
-		/// This should probably be similar to [`Config::MinerMaxWeight`].
+		/// If [`Config::MinerConfig`] is being implemented to submit signed solutions (outside of
+		/// this pallet), then [`MinerConfig::solution_weight`] is used to compare against
+		/// this value.
 		#[pallet::constant]
 		type SignedMaxWeight: Get<Weight>;
 
@@ -652,11 +658,11 @@ pub mod pallet {
 		/// are only over a single block, but once multi-block elections are introduced they will
 		/// take place over multiple blocks.
 		#[pallet::constant]
-		type MaxElectingVoters: Get<SolutionVoterIndexOf<Self>>;
+		type MaxElectingVoters: Get<SolutionVoterIndexOf<Self::MinerConfig>>;
 
 		/// The maximum number of electable targets to put in the snapshot.
 		#[pallet::constant]
-		type MaxElectableTargets: Get<SolutionTargetIndexOf<Self>>;
+		type MaxElectableTargets: Get<SolutionTargetIndexOf<Self::MinerConfig>>;
 
 		/// Handler for the slashed deposits.
 		type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
@@ -664,30 +670,12 @@ pub mod pallet {
 		/// Handler for the rewards.
 		type RewardHandler: OnUnbalanced<PositiveImbalanceOf<Self>>;
 
-		/// Maximum length (bytes) that the mined solution should consume.
-		///
-		/// The miner will ensure that the total length of the unsigned solution will not exceed
-		/// this value.
-		#[pallet::constant]
-		type MinerMaxLength: Get<u32>;
-
 		/// Something that will provide the election data.
 		type DataProvider: ElectionDataProvider<
 			AccountId = Self::AccountId,
 			BlockNumber = Self::BlockNumber,
 		>;
 
-		/// The solution type.
-		type Solution: codec::Codec
-			+ Default
-			+ PartialEq
-			+ Eq
-			+ Clone
-			+ sp_std::fmt::Debug
-			+ Ord
-			+ NposSolution
-			+ TypeInfo;
-
 		/// Configuration for the fallback.
 		type Fallback: InstantElectionProvider<
 			AccountId = Self::AccountId,
@@ -824,12 +812,12 @@ pub mod pallet {
 			use sp_std::mem::size_of;
 			// The index type of both voters and targets need to be smaller than that of usize (very
 			// unlikely to be the case, but anyhow)..
-			assert!(size_of::<SolutionVoterIndexOf<T>>() <= size_of::<usize>());
-			assert!(size_of::<SolutionTargetIndexOf<T>>() <= size_of::<usize>());
+			assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
+			assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
 
 			// ----------------------------
 			// Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`].
-			let max_vote: usize = <SolutionOf<T> as NposSolution>::LIMIT;
+			let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
 
 			// 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf<OffchainAccuracy>`.
 			let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T>>> = (0..max_vote)
@@ -850,7 +838,7 @@ pub mod pallet {
 			// solution cannot represent any voters more than `LIMIT` anyhow.
 			assert_eq!(
 				<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
-				<SolutionOf<T> as NposSolution>::LIMIT as u32,
+				<SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
 			);
 
 			// While it won't cause any failures, setting `SignedMaxRefunds` gt
@@ -887,7 +875,7 @@ pub mod pallet {
 		))]
 		pub fn submit_unsigned(
 			origin: OriginFor<T>,
-			raw_solution: Box<RawSolution<SolutionOf<T>>>,
+			raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
 			witness: SolutionOrSnapshotSize,
 		) -> DispatchResultWithPostInfo {
 			ensure_none(origin)?;
@@ -976,7 +964,7 @@ pub mod pallet {
 		#[pallet::weight(T::WeightInfo::submit())]
 		pub fn submit(
 			origin: OriginFor<T>,
-			raw_solution: Box<RawSolution<SolutionOf<T>>>,
+			raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
 		) -> DispatchResult {
 			let who = ensure_signed(origin)?;
 
@@ -991,7 +979,7 @@ pub mod pallet {
 			let size = Self::snapshot_metadata().ok_or(Error::<T>::MissingSnapshotMetadata)?;
 
 			ensure!(
-				Self::feasibility_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(),
+				Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(),
 				Error::<T>::SignedTooMuchWeight,
 			);
 
@@ -1429,7 +1417,7 @@ impl<T: Config> Pallet<T> {
 
 	/// Checks the feasibility of a solution.
 	pub fn feasibility_check(
-		raw_solution: RawSolution<SolutionOf<T>>,
+		raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
 		compute: ElectionCompute,
 	) -> Result<ReadySolution<T::AccountId>, FeasibilityError> {
 		let RawSolution { solution, score, round } = raw_solution;
@@ -1459,10 +1447,10 @@ impl<T: Config> Pallet<T> {
 			Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?;
 
 		// ----- Start building. First, we need some closures.
-		let cache = helpers::generate_voter_cache::<T>(&snapshot_voters);
-		let voter_at = helpers::voter_at_fn::<T>(&snapshot_voters);
-		let target_at = helpers::target_at_fn::<T>(&snapshot_targets);
-		let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
+		let cache = helpers::generate_voter_cache::<T::MinerConfig>(&snapshot_voters);
+		let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&snapshot_voters);
+		let target_at = helpers::target_at_fn::<T::MinerConfig>(&snapshot_targets);
+		let voter_index = helpers::voter_index_fn_usize::<T::MinerConfig>(&cache);
 
 		// Then convert solution -> assignment. This will fail if any of the indices are gibberish,
 		// namely any of the voters or targets.
@@ -1493,7 +1481,7 @@ impl<T: Config> Pallet<T> {
 		})?;
 
 		// ----- Start building support. First, we need one more closure.
-		let stake_of = helpers::stake_of_fn::<T>(&snapshot_voters, &cache);
+		let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&snapshot_voters, &cache);
 
 		// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
 		let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of)
@@ -1803,8 +1791,8 @@ mod tests {
 	use super::*;
 	use crate::{
 		mock::{
-			multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MultiPhase,
-			Runtime, SignedMaxSubmissions, System, TargetIndex, Targets,
+			multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MockedWeightInfo,
+			MultiPhase, Runtime, SignedMaxSubmissions, System, TargetIndex, Targets,
 		},
 		Phase,
 	};
@@ -2123,7 +2111,7 @@ mod tests {
 			// set the solution balancing to get the desired score.
 			crate::mock::Balancing::set(Some((2, 0)));
 
-			let (solution, _) = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
+			let (solution, _) = MultiPhase::mine_solution().unwrap();
 			// Default solution's score.
 			assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. }));
 
@@ -2147,7 +2135,7 @@ mod tests {
 	#[test]
 	fn number_of_voters_allowed_2sec_block() {
 		// Just a rough estimate with the substrate weights.
-		assert!(!MockWeightInfo::get());
+		assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real);
 
 		let all_voters: u32 = 10_000;
 		let all_targets: u32 = 5_000;
diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs
index 38d9c8dfc1b7e10262349dc93916140d180f0d93..afce24ff6e3f073c95ceb895e9593c3dfcfee852 100644
--- a/substrate/frame/election-provider-multi-phase/src/mock.rs
+++ b/substrate/frame/election-provider-multi-phase/src/mock.rs
@@ -16,7 +16,7 @@
 // limitations under the License.
 
 use super::*;
-use crate as multi_phase;
+use crate::{self as multi_phase, unsigned::MinerConfig};
 use frame_election_provider_support::{
 	data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
 };
@@ -239,6 +239,13 @@ impl pallet_balances::Config for Runtime {
 	type WeightInfo = ();
 }
 
+#[derive(Eq, PartialEq, Debug, Clone, Copy)]
+pub enum MockedWeightInfo {
+	Basic,
+	Complex,
+	Real,
+}
+
 parameter_types! {
 	pub static Targets: Vec<AccountId> = vec![10, 20, 30, 40];
 	pub static Voters: Vec<VoterOf<Runtime>> = vec![
@@ -269,7 +276,7 @@ parameter_types! {
 	pub static OffchainRepeat: BlockNumber = 5;
 	pub static MinerMaxWeight: Weight = BlockWeights::get().max_block;
 	pub static MinerMaxLength: u32 = 256;
-	pub static MockWeightInfo: bool = false;
+	pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real;
 	pub static MaxElectingVoters: VoterIndex = u32::max_value();
 	pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value();
 
@@ -314,85 +321,6 @@ impl InstantElectionProvider for MockFallback {
 	}
 }
 
-// Hopefully this won't be too much of a hassle to maintain.
-pub struct DualMockWeightInfo;
-impl multi_phase::weights::WeightInfo for DualMockWeightInfo {
-	fn on_initialize_nothing() -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::on_initialize_nothing()
-		}
-	}
-	fn create_snapshot_internal(v: u32, t: u32) -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::create_snapshot_internal(v, t)
-		}
-	}
-	fn on_initialize_open_signed() -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::on_initialize_open_signed()
-		}
-	}
-	fn on_initialize_open_unsigned() -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::on_initialize_open_unsigned()
-		}
-	}
-	fn elect_queued(a: u32, d: u32) -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::elect_queued(a, d)
-		}
-	}
-	fn finalize_signed_phase_accept_solution() -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::finalize_signed_phase_accept_solution()
-		}
-	}
-	fn finalize_signed_phase_reject_solution() -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::finalize_signed_phase_reject_solution()
-		}
-	}
-	fn submit() -> Weight {
-		if MockWeightInfo::get() {
-			Zero::zero()
-		} else {
-			<() as multi_phase::weights::WeightInfo>::submit()
-		}
-	}
-	fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight {
-		if MockWeightInfo::get() {
-			// 10 base
-			// 5 per edge.
-			(10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight))
-		} else {
-			<() as multi_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d)
-		}
-	}
-	fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight {
-		if MockWeightInfo::get() {
-			// 10 base
-			// 5 per edge.
-			(10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight))
-		} else {
-			<() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d)
-		}
-	}
-}
-
 parameter_types! {
 	pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0));
 }
@@ -410,6 +338,24 @@ impl BenchmarkingConfig for TestBenchmarkingConfig {
 	const MAXIMUM_TARGETS: u32 = 200;
 }
 
+impl MinerConfig for Runtime {
+	type AccountId = AccountId;
+	type MaxLength = MinerMaxLength;
+	type MaxWeight = MinerMaxWeight;
+	type MaxVotesPerVoter = <StakingMock as ElectionDataProvider>::MaxVotesPerVoter;
+	type Solution = TestNposSolution;
+
+	fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
+		match MockWeightInfo::get() {
+			MockedWeightInfo::Basic =>
+				(10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)),
+			MockedWeightInfo::Complex => (0 * v + 0 * t + 1000 * a + 0 * d) as Weight,
+			MockedWeightInfo::Real =>
+				<() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d),
+		}
+	}
+}
+
 impl crate::Config for Runtime {
 	type Event = Event;
 	type Currency = Balances;
@@ -419,8 +365,6 @@ impl crate::Config for Runtime {
 	type BetterUnsignedThreshold = BetterUnsignedThreshold;
 	type BetterSignedThreshold = BetterSignedThreshold;
 	type OffchainRepeat = OffchainRepeat;
-	type MinerMaxWeight = MinerMaxWeight;
-	type MinerMaxLength = MinerMaxLength;
 	type MinerTxPriority = MinerTxPriority;
 	type SignedRewardBase = SignedRewardBase;
 	type SignedDepositBase = SignedDepositBase;
@@ -432,14 +376,14 @@ impl crate::Config for Runtime {
 	type SlashHandler = ();
 	type RewardHandler = ();
 	type DataProvider = StakingMock;
-	type WeightInfo = DualMockWeightInfo;
+	type WeightInfo = ();
 	type BenchmarkingConfig = TestBenchmarkingConfig;
 	type Fallback = MockFallback;
 	type GovernanceFallback = NoFallback<Self>;
 	type ForceOrigin = frame_system::EnsureRoot<AccountId>;
-	type Solution = TestNposSolution;
 	type MaxElectingVoters = MaxElectingVoters;
 	type MaxElectableTargets = MaxElectableTargets;
+	type MinerConfig = Self;
 	type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
 }
 
@@ -562,7 +506,7 @@ impl ExtBuilder {
 		<MinerMaxWeight>::set(weight);
 		self
 	}
-	pub fn mock_weight_info(self, mock: bool) -> Self {
+	pub fn mock_weight_info(self, mock: MockedWeightInfo) -> Self {
 		<MockWeightInfo>::set(mock);
 		self
 	}
diff --git a/substrate/frame/election-provider-multi-phase/src/signed.rs b/substrate/frame/election-provider-multi-phase/src/signed.rs
index 5f61eb7575da4103a60edc3476f73d0d11be9eaa..eca75139f925a873807ba346c0294ff964d1603f 100644
--- a/substrate/frame/election-provider-multi-phase/src/signed.rs
+++ b/substrate/frame/election-provider-multi-phase/src/signed.rs
@@ -18,9 +18,9 @@
 //! The signed phase implementation.
 
 use crate::{
-	Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, ReadySolution,
-	SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, SolutionOf,
-	SolutionOrSnapshotSize, Weight, WeightInfo,
+	unsigned::MinerConfig, Config, ElectionCompute, Pallet, QueuedSolution, RawSolution,
+	ReadySolution, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap,
+	SolutionOf, SolutionOrSnapshotSize, Weight, WeightInfo,
 };
 use codec::{Decode, Encode, HasCompact};
 use frame_election_provider_support::NposSolution;
@@ -93,8 +93,11 @@ pub type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
 pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
 	<T as frame_system::Config>::AccountId,
 >>::NegativeImbalance;
-pub type SignedSubmissionOf<T> =
-	SignedSubmission<<T as frame_system::Config>::AccountId, BalanceOf<T>, SolutionOf<T>>;
+pub type SignedSubmissionOf<T> = SignedSubmission<
+	<T as frame_system::Config>::AccountId,
+	BalanceOf<T>,
+	<<T as crate::Config>::MinerConfig as MinerConfig>::Solution,
+>;
 
 pub type SubmissionIndicesOf<T> =
 	BoundedBTreeMap<ElectionScore, u32, <T as Config>::SignedMaxSubmissions>;
@@ -482,12 +485,12 @@ impl<T: Config> Pallet<T> {
 		T::SlashHandler::on_unbalanced(negative_imbalance);
 	}
 
-	/// The feasibility weight of the given raw solution.
-	pub fn feasibility_weight_of(
-		raw_solution: &RawSolution<SolutionOf<T>>,
+	/// The weight of the given raw solution.
+	pub fn solution_weight_of(
+		raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
 		size: SolutionOrSnapshotSize,
 	) -> Weight {
-		T::WeightInfo::feasibility_check(
+		T::MinerConfig::solution_weight(
 			size.voters,
 			size.targets,
 			raw_solution.solution.voter_count() as u32,
@@ -503,12 +506,12 @@ impl<T: Config> Pallet<T> {
 	/// 2. a per-byte deposit, for renting the state usage.
 	/// 3. a per-weight deposit, for the potential weight usage in an upcoming on_initialize
 	pub fn deposit_for(
-		raw_solution: &RawSolution<SolutionOf<T>>,
+		raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
 		size: SolutionOrSnapshotSize,
 	) -> BalanceOf<T> {
 		let encoded_len: u32 = raw_solution.encoded_size().saturated_into();
 		let encoded_len: BalanceOf<T> = encoded_len.into();
-		let feasibility_weight = Self::feasibility_weight_of(raw_solution, size);
+		let feasibility_weight = Self::solution_weight_of(raw_solution, size);
 
 		let len_deposit = T::SignedDepositByte::get().saturating_mul(encoded_len);
 		let weight_deposit =
@@ -525,8 +528,8 @@ mod tests {
 	use super::*;
 	use crate::{
 		mock::{
-			balances, raw_solution, roll_to, Balances, ExtBuilder, MultiPhase, Origin, Runtime,
-			SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight,
+			balances, raw_solution, roll_to, Balances, ExtBuilder, MockedWeightInfo, MultiPhase,
+			Origin, Runtime, SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight,
 		},
 		Error, Perbill, Phase,
 	};
@@ -955,14 +958,13 @@ mod tests {
 	fn cannot_consume_too_much_future_weight() {
 		ExtBuilder::default()
 			.signed_weight(40)
-			.mock_weight_info(true)
+			.mock_weight_info(MockedWeightInfo::Basic)
 			.build_and_execute(|| {
 				roll_to(15);
 				assert!(MultiPhase::current_phase().is_signed());
 
-				let (raw, witness) =
-					MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
-				let solution_weight = <Runtime as Config>::WeightInfo::feasibility_check(
+				let (raw, witness) = MultiPhase::mine_solution().unwrap();
+				let solution_weight = <Runtime as MinerConfig>::solution_weight(
 					witness.voters,
 					witness.targets,
 					raw.solution.voter_count() as u32,
diff --git a/substrate/frame/election-provider-multi-phase/src/unsigned.rs b/substrate/frame/election-provider-multi-phase/src/unsigned.rs
index 126dcb10416cc36bd88b1340628559d1480f81b2..9a1b52d3545694e88e15db898f5409aa46fddc65 100644
--- a/substrate/frame/election-provider-multi-phase/src/unsigned.rs
+++ b/substrate/frame/election-provider-multi-phase/src/unsigned.rs
@@ -20,14 +20,15 @@
 use crate::{
 	helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution,
 	ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight,
-	WeightInfo,
 };
 use codec::Encode;
-use frame_election_provider_support::{NposSolution, NposSolver, PerThing128};
-use frame_support::{dispatch::DispatchResult, ensure, traits::Get};
+use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight};
+use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec};
 use frame_system::offchain::SubmitTransaction;
+use scale_info::TypeInfo;
 use sp_npos_elections::{
 	assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult,
+	ElectionScore,
 };
 use sp_runtime::{
 	offchain::storage::{MutateStorageError, StorageValueRef},
@@ -47,6 +48,12 @@ pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-ele
 /// voted.
 pub type VoterOf<T> = frame_election_provider_support::VoterOf<<T as Config>::DataProvider>;
 
+/// Same as [`VoterOf`], but parameterized by the `MinerConfig`.
+pub type MinerVoterOf<T> = frame_election_provider_support::Voter<
+	<T as MinerConfig>::AccountId,
+	<T as MinerConfig>::MaxVotesPerVoter,
+>;
+
 /// The relative distribution of a voter's stake among the winning targets.
 pub type Assignment<T> =
 	sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, SolutionAccuracyOf<T>>;
@@ -59,7 +66,7 @@ pub type IndexAssignmentOf<T> = frame_election_provider_support::IndexAssignment
 pub type SolverErrorOf<T> = <<T as Config>::Solver as NposSolver>::Error;
 /// Error type for operations related to the OCW npos solution miner.
 #[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)]
-pub enum MinerError<T: Config> {
+pub enum MinerError {
 	/// An internal error in the NPoS elections crate.
 	NposElections(sp_npos_elections::Error),
 	/// Snapshot data was unavailable unexpectedly.
@@ -81,23 +88,23 @@ pub enum MinerError<T: Config> {
 	/// There are no more voters to remove to trim the solution.
 	NoMoreVoters,
 	/// An error from the solver.
-	Solver(SolverErrorOf<T>),
+	Solver,
 }
 
-impl<T: Config> From<sp_npos_elections::Error> for MinerError<T> {
+impl From<sp_npos_elections::Error> for MinerError {
 	fn from(e: sp_npos_elections::Error) -> Self {
 		MinerError::NposElections(e)
 	}
 }
 
-impl<T: Config> From<FeasibilityError> for MinerError<T> {
+impl From<FeasibilityError> for MinerError {
 	fn from(e: FeasibilityError) -> Self {
 		MinerError::Feasibility(e)
 	}
 }
 
 /// Save a given call into OCW storage.
-fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError<T>> {
+fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
 	log!(debug, "saving a call to the offchain storage.");
 	let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL);
 	match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
@@ -115,7 +122,7 @@ fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError<T>> {
 }
 
 /// Get a saved solution from OCW storage if it exists.
-fn restore_solution<T: Config>() -> Result<Call<T>, MinerError<T>> {
+fn restore_solution<T: Config>() -> Result<Call<T>, MinerError> {
 	StorageValueRef::persistent(OFFCHAIN_CACHED_CALL)
 		.get()
 		.ok()
@@ -146,9 +153,46 @@ fn ocw_solution_exists<T: Config>() -> bool {
 }
 
 impl<T: Config> Pallet<T> {
+	/// Mine a new npos solution.
+	///
+	/// The Npos Solver type, `S`, must have the same AccountId and Error type as the
+	/// [`crate::Config::Solver`] in order to create a unified return type.
+	pub fn mine_solution(
+	) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> {
+		let RoundSnapshot { voters, targets } =
+			Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
+		let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
+		let (solution, score, size) = Miner::<T::MinerConfig>::mine_solution_with_snapshot::<
+			T::Solver,
+		>(voters, targets, desired_targets)?;
+		let round = Self::round();
+		Ok((RawSolution { solution, score, round }, size))
+	}
+
+	/// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which
+	/// is ready to be submitted to the chain.
+	///
+	/// Will always reduce the solution as well.
+	pub fn prepare_election_result<Accuracy: PerThing128>(
+		election_result: ElectionResult<T::AccountId, Accuracy>,
+	) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> {
+		let RoundSnapshot { voters, targets } =
+			Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
+		let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
+		let (solution, score, size) =
+			Miner::<T::MinerConfig>::prepare_election_result_with_snapshot(
+				election_result,
+				voters,
+				targets,
+				desired_targets,
+			)?;
+		let round = Self::round();
+		Ok((RawSolution { solution, score, round }, size))
+	}
+
 	/// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit
 	/// if our call's score is greater than that of the cached solution.
-	pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError<T>> {
+	pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> {
 		log!(debug, "miner attempting to restore or compute an unsigned solution.");
 
 		let call = restore_solution::<T>()
@@ -162,7 +206,7 @@ impl<T: Config> Pallet<T> {
 					Err(MinerError::SolutionCallInvalid)
 				}
 			})
-			.or_else::<MinerError<T>, _>(|error| {
+			.or_else::<MinerError, _>(|error| {
 				log!(debug, "restoring solution failed due to {:?}", error);
 				match error {
 					MinerError::NoStoredSolution => {
@@ -193,7 +237,7 @@ impl<T: Config> Pallet<T> {
 	}
 
 	/// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction.
-	pub fn mine_check_save_submit() -> Result<(), MinerError<T>> {
+	pub fn mine_check_save_submit() -> Result<(), MinerError> {
 		log!(debug, "miner attempting to compute an unsigned solution.");
 
 		let call = Self::mine_checked_call()?;
@@ -202,7 +246,7 @@ impl<T: Config> Pallet<T> {
 	}
 
 	/// Mine a new solution as a call. Performs all checks.
-	pub fn mine_checked_call() -> Result<Call<T>, MinerError<T>> {
+	pub fn mine_checked_call() -> Result<Call<T>, MinerError> {
 		// get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID.
 		let (raw_solution, witness) = Self::mine_and_check()?;
 
@@ -219,7 +263,7 @@ impl<T: Config> Pallet<T> {
 		Ok(call)
 	}
 
-	fn submit_call(call: Call<T>) -> Result<(), MinerError<T>> {
+	fn submit_call(call: Call<T>) -> Result<(), MinerError> {
 		log!(debug, "miner submitting a solution as an unsigned transaction");
 
 		SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
@@ -230,9 +274,9 @@ impl<T: Config> Pallet<T> {
 	//
 	// Performance: note that it internally clones the provided solution.
 	pub fn basic_checks(
-		raw_solution: &RawSolution<SolutionOf<T>>,
+		raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
 		solution_type: &str,
-	) -> Result<(), MinerError<T>> {
+	) -> Result<(), MinerError> {
 		Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| {
 			log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err);
 			MinerError::PreDispatchChecksFailed(err)
@@ -255,45 +299,155 @@ impl<T: Config> Pallet<T> {
 	/// If you want a checked solution and submit it at the same time, use
 	/// [`Pallet::mine_check_save_submit`].
 	pub fn mine_and_check(
-	) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> {
-		let (raw_solution, witness) = Self::mine_solution::<T::Solver>()?;
+	) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> {
+		let (raw_solution, witness) = Self::mine_solution()?;
 		Self::basic_checks(&raw_solution, "mined")?;
 		Ok((raw_solution, witness))
 	}
 
-	/// Mine a new npos solution.
+	/// Checks if an execution of the offchain worker is permitted at the given block number, or
+	/// not.
 	///
-	/// The Npos Solver type, `S`, must have the same AccountId and Error type as the
-	/// [`crate::Config::Solver`] in order to create a unified return type.
-	pub fn mine_solution<S>(
-	) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>>
-	where
-		S: NposSolver<AccountId = T::AccountId, Error = SolverErrorOf<T>>,
-	{
-		let RoundSnapshot { voters, targets } =
-			Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
-		let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
+	/// This makes sure that
+	/// 1. we don't run on previous blocks in case of a re-org
+	/// 2. we don't run twice within a window of length `T::OffchainRepeat`.
+	///
+	/// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()`
+	/// is returned, `now` is written in storage and will be used in further calls as the baseline.
+	pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> {
+		let threshold = T::OffchainRepeat::get();
+		let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
 
-		S::solve(desired_targets as usize, targets, voters)
-			.map_err(|e| MinerError::Solver::<T>(e))
-			.and_then(|e| Self::prepare_election_result::<S::Accuracy>(e))
+		let mutate_stat = last_block.mutate::<_, &'static str, _>(
+			|maybe_head: Result<Option<T::BlockNumber>, _>| {
+				match maybe_head {
+					Ok(Some(head)) if now < head => Err("fork."),
+					Ok(Some(head)) if now >= head && now <= head + threshold =>
+						Err("recently executed."),
+					Ok(Some(head)) if now > head + threshold => {
+						// we can run again now. Write the new head.
+						Ok(now)
+					},
+					_ => {
+						// value doesn't exists. Probably this node just booted up. Write, and run
+						Ok(now)
+					},
+				}
+			},
+		);
+
+		match mutate_stat {
+			// all good
+			Ok(_) => Ok(()),
+			// failed to write.
+			Err(MutateStorageError::ConcurrentModification(_)) =>
+				Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")),
+			// fork etc.
+			Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)),
+		}
 	}
 
-	/// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which
-	/// is ready to be submitted to the chain.
+	/// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned
+	/// transaction.
 	///
-	/// Will always reduce the solution as well.
-	pub fn prepare_election_result<Accuracy: PerThing128>(
-		election_result: ElectionResult<T::AccountId, Accuracy>,
-	) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> {
-		// NOTE: This code path is generally not optimized as it is run offchain. Could use some at
-		// some point though.
+	/// Can optionally also be called during dispatch, if needed.
+	///
+	/// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's
+	/// code, so that we do less and less storage reads here.
+	pub fn unsigned_pre_dispatch_checks(
+		raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
+	) -> DispatchResult {
+		// ensure solution is timely. Don't panic yet. This is a cheap check.
+		ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission);
 
-		// storage items. Note: we have already read this from storage, they must be in cache.
-		let RoundSnapshot { voters, targets } =
-			Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
-		let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
+		// ensure round is current
+		ensure!(Self::round() == raw_solution.round, Error::<T>::OcwCallWrongEra);
 
+		// ensure correct number of winners.
+		ensure!(
+			Self::desired_targets().unwrap_or_default() ==
+				raw_solution.solution.unique_targets().len() as u32,
+			Error::<T>::PreDispatchWrongWinnerCount,
+		);
+
+		// ensure score is being improved. Panic henceforth.
+		ensure!(
+			Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution
+				.score
+				.strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())),
+			Error::<T>::PreDispatchWeakSubmission,
+		);
+
+		Ok(())
+	}
+}
+
+/// Configurations for a miner that comes with this pallet.
+pub trait MinerConfig {
+	/// The account id type.
+	type AccountId: Ord + Clone + codec::Codec + sp_std::fmt::Debug;
+	/// The solution that the miner is mining.
+	type Solution: codec::Codec
+		+ Default
+		+ PartialEq
+		+ Eq
+		+ Clone
+		+ sp_std::fmt::Debug
+		+ Ord
+		+ NposSolution
+		+ TypeInfo;
+	/// Maximum number of votes per voter in the snapshots.
+	type MaxVotesPerVoter;
+	/// Maximum length of the solution that the miner is allowed to generate.
+	///
+	/// Solutions are trimmed to respect this.
+	type MaxLength: Get<u32>;
+	/// Maximum weight of the solution that the miner is allowed to generate.
+	///
+	/// Solutions are trimmed to respect this.
+	///
+	/// The weight is computed using `solution_weight`.
+	type MaxWeight: Get<Weight>;
+	/// Something that can compute the weight of a solution.
+	///
+	/// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`].
+	fn solution_weight(voters: u32, targets: u32, active_voters: u32, degree: u32) -> Weight;
+}
+
+/// A base miner, suitable to be used for both signed and unsigned submissions.
+pub struct Miner<T: MinerConfig>(sp_std::marker::PhantomData<T>);
+impl<T: MinerConfig> Miner<T> {
+	/// Same as [`Pallet::mine_solution`], but the input snapshot data must be given.
+	pub fn mine_solution_with_snapshot<S>(
+		voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>,
+		targets: Vec<T::AccountId>,
+		desired_targets: u32,
+	) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize), MinerError>
+	where
+		S: NposSolver<AccountId = T::AccountId>,
+	{
+		S::solve(desired_targets as usize, targets.clone(), voters.clone())
+			.map_err(|e| {
+				log_no_system!(error, "solver error: {:?}", e);
+				MinerError::Solver
+			})
+			.and_then(|e| {
+				Self::prepare_election_result_with_snapshot::<S::Accuracy>(
+					e,
+					voters,
+					targets,
+					desired_targets,
+				)
+			})
+	}
+
+	/// Same as [`Pallet::prepare_election_result`], but the input snapshot mut be given as inputs.
+	pub fn prepare_election_result_with_snapshot<Accuracy: PerThing128>(
+		election_result: ElectionResult<T::AccountId, Accuracy>,
+		voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>,
+		targets: Vec<T::AccountId>,
+		desired_targets: u32,
+	) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize), MinerError> {
 		// now make some helper closures.
 		let cache = helpers::generate_voter_cache::<T>(&voters);
 		let voter_index = helpers::voter_index_fn::<T>(&cache);
@@ -355,11 +509,11 @@ impl<T: Config> Pallet<T> {
 		Self::trim_assignments_weight(
 			desired_targets,
 			size,
-			T::MinerMaxWeight::get(),
+			T::MaxWeight::get(),
 			&mut index_assignments,
 		);
 		Self::trim_assignments_length(
-			T::MinerMaxLength::get(),
+			T::MaxLength::get(),
 			&mut index_assignments,
 			&encoded_size_of,
 		)?;
@@ -370,43 +524,7 @@ impl<T: Config> Pallet<T> {
 		// re-calc score.
 		let score = solution.clone().score(stake_of, voter_at, target_at)?;
 
-		let round = Self::round();
-		Ok((RawSolution { solution, score, round }, size))
-	}
-
-	/// Greedily reduce the size of the solution to fit into the block w.r.t. weight.
-	///
-	/// The weight of the solution is foremost a function of the number of voters (i.e.
-	/// `assignments.len()`). Aside from this, the other components of the weight are invariant. The
-	/// number of winners shall not be changed (otherwise the solution is invalid) and the
-	/// `ElectionSize` is merely a representation of the total number of stakers.
-	///
-	/// Thus, we reside to stripping away some voters from the `assignments`.
-	///
-	/// Note that the solution is already computed, and the winners are elected based on the merit
-	/// of the entire stake in the system. Nonetheless, some of the voters will be removed further
-	/// down the line.
-	///
-	/// Indeed, the score must be computed **after** this step. If this step reduces the score too
-	/// much or remove a winner, then the solution must be discarded **after** this step.
-	pub fn trim_assignments_weight(
-		desired_targets: u32,
-		size: SolutionOrSnapshotSize,
-		max_weight: Weight,
-		assignments: &mut Vec<IndexAssignmentOf<T>>,
-	) {
-		let maximum_allowed_voters =
-			Self::maximum_voter_for_weight::<T::WeightInfo>(desired_targets, size, max_weight);
-		let removing: usize =
-			assignments.len().saturating_sub(maximum_allowed_voters.saturated_into());
-		log!(
-			debug,
-			"from {} assignments, truncating to {} for weight, removing {}",
-			assignments.len(),
-			maximum_allowed_voters,
-			removing,
-		);
-		assignments.truncate(maximum_allowed_voters as usize);
+		Ok((solution, score, size))
 	}
 
 	/// Greedily reduce the size of the solution to fit into the block w.r.t length.
@@ -427,7 +545,7 @@ impl<T: Config> Pallet<T> {
 		max_allowed_length: u32,
 		assignments: &mut Vec<IndexAssignmentOf<T>>,
 		encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>,
-	) -> Result<(), MinerError<T>> {
+	) -> Result<(), MinerError> {
 		// Perform a binary search for the max subset of which can fit into the allowed
 		// length. Having discovered that, we can truncate efficiently.
 		let max_allowed_length: usize = max_allowed_length.saturated_into();
@@ -470,7 +588,7 @@ impl<T: Config> Pallet<T> {
 		// after this point, we never error.
 		// check before edit.
 
-		log!(
+		log_no_system!(
 			debug,
 			"from {} assignments, truncating to {} for length, removing {}",
 			assignments.len(),
@@ -482,10 +600,45 @@ impl<T: Config> Pallet<T> {
 		Ok(())
 	}
 
+	/// Greedily reduce the size of the solution to fit into the block w.r.t. weight.
+	///
+	/// The weight of the solution is foremost a function of the number of voters (i.e.
+	/// `assignments.len()`). Aside from this, the other components of the weight are invariant. The
+	/// number of winners shall not be changed (otherwise the solution is invalid) and the
+	/// `ElectionSize` is merely a representation of the total number of stakers.
+	///
+	/// Thus, we reside to stripping away some voters from the `assignments`.
+	///
+	/// Note that the solution is already computed, and the winners are elected based on the merit
+	/// of the entire stake in the system. Nonetheless, some of the voters will be removed further
+	/// down the line.
+	///
+	/// Indeed, the score must be computed **after** this step. If this step reduces the score too
+	/// much or remove a winner, then the solution must be discarded **after** this step.
+	pub fn trim_assignments_weight(
+		desired_targets: u32,
+		size: SolutionOrSnapshotSize,
+		max_weight: Weight,
+		assignments: &mut Vec<IndexAssignmentOf<T>>,
+	) {
+		let maximum_allowed_voters =
+			Self::maximum_voter_for_weight(desired_targets, size, max_weight);
+		let removing: usize =
+			assignments.len().saturating_sub(maximum_allowed_voters.saturated_into());
+		log_no_system!(
+			debug,
+			"from {} assignments, truncating to {} for weight, removing {}",
+			assignments.len(),
+			maximum_allowed_voters,
+			removing,
+		);
+		assignments.truncate(maximum_allowed_voters as usize);
+	}
+
 	/// Find the maximum `len` that a solution can have in order to fit into the block weight.
 	///
 	/// This only returns a value between zero and `size.nominators`.
-	pub fn maximum_voter_for_weight<W: WeightInfo>(
+	pub fn maximum_voter_for_weight(
 		desired_winners: u32,
 		size: SolutionOrSnapshotSize,
 		max_weight: Weight,
@@ -499,7 +652,7 @@ impl<T: Config> Pallet<T> {
 
 		// helper closures.
 		let weight_with = |active_voters: u32| -> Weight {
-			W::submit_unsigned(size.voters, size.targets, active_voters, desired_winners)
+			T::solution_weight(size.voters, size.targets, active_voters, desired_winners)
 		};
 
 		let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result<u32, ()> {
@@ -553,176 +706,65 @@ impl<T: Config> Pallet<T> {
 		);
 		final_decision
 	}
-
-	/// Checks if an execution of the offchain worker is permitted at the given block number, or
-	/// not.
-	///
-	/// This makes sure that
-	/// 1. we don't run on previous blocks in case of a re-org
-	/// 2. we don't run twice within a window of length `T::OffchainRepeat`.
-	///
-	/// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()`
-	/// is returned, `now` is written in storage and will be used in further calls as the baseline.
-	pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError<T>> {
-		let threshold = T::OffchainRepeat::get();
-		let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
-
-		let mutate_stat = last_block.mutate::<_, &'static str, _>(
-			|maybe_head: Result<Option<T::BlockNumber>, _>| {
-				match maybe_head {
-					Ok(Some(head)) if now < head => Err("fork."),
-					Ok(Some(head)) if now >= head && now <= head + threshold =>
-						Err("recently executed."),
-					Ok(Some(head)) if now > head + threshold => {
-						// we can run again now. Write the new head.
-						Ok(now)
-					},
-					_ => {
-						// value doesn't exists. Probably this node just booted up. Write, and run
-						Ok(now)
-					},
-				}
-			},
-		);
-
-		match mutate_stat {
-			// all good
-			Ok(_) => Ok(()),
-			// failed to write.
-			Err(MutateStorageError::ConcurrentModification(_)) =>
-				Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")),
-			// fork etc.
-			Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)),
-		}
-	}
-
-	/// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned
-	/// transaction.
-	///
-	/// Can optionally also be called during dispatch, if needed.
-	///
-	/// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's
-	/// code, so that we do less and less storage reads here.
-	pub fn unsigned_pre_dispatch_checks(
-		raw_solution: &RawSolution<SolutionOf<T>>,
-	) -> DispatchResult {
-		// ensure solution is timely. Don't panic yet. This is a cheap check.
-		ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission);
-
-		// ensure round is current
-		ensure!(Self::round() == raw_solution.round, Error::<T>::OcwCallWrongEra);
-
-		// ensure correct number of winners.
-		ensure!(
-			Self::desired_targets().unwrap_or_default() ==
-				raw_solution.solution.unique_targets().len() as u32,
-			Error::<T>::PreDispatchWrongWinnerCount,
-		);
-
-		// ensure score is being improved. Panic henceforth.
-		ensure!(
-			Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution
-				.score
-				.strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())),
-			Error::<T>::PreDispatchWeakSubmission,
-		);
-
-		Ok(())
-	}
 }
 
 #[cfg(test)]
 mod max_weight {
 	#![allow(unused_variables)]
 	use super::*;
-	use crate::mock::MultiPhase;
-
-	struct TestWeight;
-	impl crate::weights::WeightInfo for TestWeight {
-		fn elect_queued(a: u32, d: u32) -> Weight {
-			unreachable!()
-		}
-		fn create_snapshot_internal(v: u32, t: u32) -> Weight {
-			unreachable!()
-		}
-		fn on_initialize_nothing() -> Weight {
-			unreachable!()
-		}
-		fn on_initialize_open_signed() -> Weight {
-			unreachable!()
-		}
-		fn on_initialize_open_unsigned() -> Weight {
-			unreachable!()
-		}
-		fn finalize_signed_phase_accept_solution() -> Weight {
-			unreachable!()
-		}
-		fn finalize_signed_phase_reject_solution() -> Weight {
-			unreachable!()
-		}
-		fn submit() -> Weight {
-			unreachable!()
-		}
-		fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight {
-			(0 * v + 0 * t + 1000 * a + 0 * d) as Weight
-		}
-		fn feasibility_check(v: u32, _t: u32, a: u32, d: u32) -> Weight {
-			unreachable!()
-		}
-	}
-
+	use crate::mock::{MockWeightInfo, Runtime};
 	#[test]
 	fn find_max_voter_binary_search_works() {
 		let w = SolutionOrSnapshotSize { voters: 10, targets: 0 };
-
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2990), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2999), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3000), 3);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 3);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 5500), 5);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 7777), 7);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 9999), 9);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_000), 10);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_999), 10);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 11_000), 10);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 22_000), 10);
+		MockWeightInfo::set(crate::mock::MockedWeightInfo::Complex);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1990), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2990), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2999), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3000), 3);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 3);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 5500), 5);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 7777), 7);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 9999), 9);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 10_000), 10);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 10_999), 10);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 11_000), 10);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 22_000), 10);
 
 		let w = SolutionOrSnapshotSize { voters: 1, targets: 0 };
 
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1990), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 1);
 
 		let w = SolutionOrSnapshotSize { voters: 2, targets: 0 };
 
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2);
-		assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 2);
+		assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 2);
 	}
 }
 
@@ -988,8 +1030,7 @@ mod tests {
 			assert_eq!(MultiPhase::desired_targets().unwrap(), 2);
 
 			// mine seq_phragmen solution with 2 iters.
-			let (solution, witness) =
-				MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
+			let (solution, witness) = MultiPhase::mine_solution().unwrap();
 
 			// ensure this solution is valid.
 			assert!(MultiPhase::queued_solution().is_none());
@@ -1002,14 +1043,13 @@ mod tests {
 	fn miner_trims_weight() {
 		ExtBuilder::default()
 			.miner_weight(100)
-			.mock_weight_info(true)
+			.mock_weight_info(crate::mock::MockedWeightInfo::Basic)
 			.build_and_execute(|| {
 				roll_to(25);
 				assert!(MultiPhase::current_phase().is_unsigned());
 
-				let (raw, witness) =
-					MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
-				let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
+				let (raw, witness) = MultiPhase::mine_solution().unwrap();
+				let solution_weight = <Runtime as MinerConfig>::solution_weight(
 					witness.voters,
 					witness.targets,
 					raw.solution.voter_count() as u32,
@@ -1022,9 +1062,8 @@ mod tests {
 				// now reduce the max weight
 				<MinerMaxWeight>::set(25);
 
-				let (raw, witness) =
-					MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
-				let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
+				let (raw, witness) = MultiPhase::mine_solution().unwrap();
+				let solution_weight = <Runtime as MinerConfig>::solution_weight(
 					witness.voters,
 					witness.targets,
 					raw.solution.voter_count() as u32,
@@ -1044,8 +1083,7 @@ mod tests {
 			assert!(MultiPhase::current_phase().is_unsigned());
 
 			// Force the number of winners to be bigger to fail
-			let (mut solution, _) =
-				MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
+			let (mut solution, _) = MultiPhase::mine_solution().unwrap();
 			solution.solution.votes1[0].1 = 4;
 
 			assert_eq!(
@@ -1460,8 +1498,12 @@ mod tests {
 			let solution_clone = solution.clone();
 
 			// when
-			MultiPhase::trim_assignments_length(encoded_len, &mut assignments, encoded_size_of)
-				.unwrap();
+			Miner::<Runtime>::trim_assignments_length(
+				encoded_len,
+				&mut assignments,
+				encoded_size_of,
+			)
+			.unwrap();
 
 			// then
 			let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
@@ -1481,7 +1523,7 @@ mod tests {
 			let solution_clone = solution.clone();
 
 			// when
-			MultiPhase::trim_assignments_length(
+			Miner::<Runtime>::trim_assignments_length(
 				encoded_len as u32 - 1,
 				&mut assignments,
 				encoded_size_of,
@@ -1514,8 +1556,12 @@ mod tests {
 				.unwrap();
 
 			// when
-			MultiPhase::trim_assignments_length(encoded_len - 1, &mut assignments, encoded_size_of)
-				.unwrap();
+			Miner::<Runtime>::trim_assignments_length(
+				encoded_len - 1,
+				&mut assignments,
+				encoded_size_of,
+			)
+			.unwrap();
 
 			// then
 			assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment");
@@ -1542,11 +1588,11 @@ mod tests {
 			assert_eq!(min_solution_size, SolutionOf::<Runtime>::LIMIT);
 
 			// all of this should not panic.
-			MultiPhase::trim_assignments_length(0, &mut assignments, encoded_size_of.clone())
+			Miner::<Runtime>::trim_assignments_length(0, &mut assignments, encoded_size_of.clone())
 				.unwrap();
-			MultiPhase::trim_assignments_length(1, &mut assignments, encoded_size_of.clone())
+			Miner::<Runtime>::trim_assignments_length(1, &mut assignments, encoded_size_of.clone())
 				.unwrap();
-			MultiPhase::trim_assignments_length(
+			Miner::<Runtime>::trim_assignments_length(
 				min_solution_size as u32,
 				&mut assignments,
 				encoded_size_of,
@@ -1563,7 +1609,7 @@ mod tests {
 
 			// trim to min solution size.
 			let min_solution_size = SolutionOf::<Runtime>::LIMIT as u32;
-			MultiPhase::trim_assignments_length(
+			Miner::<Runtime>::trim_assignments_length(
 				min_solution_size,
 				&mut assignments,
 				encoded_size_of,
@@ -1582,15 +1628,15 @@ mod tests {
 			roll_to(25);
 
 			// how long would the default solution be?
-			let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
-			let max_length = <Runtime as Config>::MinerMaxLength::get();
+			let solution = MultiPhase::mine_solution().unwrap();
+			let max_length = <Runtime as MinerConfig>::MaxLength::get();
 			let solution_size = solution.0.solution.encoded_size();
 			assert!(solution_size <= max_length as usize);
 
 			// now set the max size to less than the actual size and regenerate
-			<Runtime as Config>::MinerMaxLength::set(solution_size as u32 - 1);
-			let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
-			let max_length = <Runtime as Config>::MinerMaxLength::get();
+			<Runtime as MinerConfig>::MaxLength::set(solution_size as u32 - 1);
+			let solution = MultiPhase::mine_solution().unwrap();
+			let max_length = <Runtime as MinerConfig>::MaxLength::get();
 			let solution_size = solution.0.solution.encoded_size();
 			assert!(solution_size <= max_length as usize);
 		});