From 41da13238e6db2709cf6bdaa223ea894da8d1397 Mon Sep 17 00:00:00 2001
From: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Date: Fri, 5 Jul 2019 13:37:33 +0200
Subject: [PATCH] fix: replace who with SlashRecipient

SlashRecipient is a struct that contain the account_id for a given validator
along with exposure mapping to its nominators to be slashed correctly
---
 srml/slashing/src/lib.rs | 115 +++++++++++++++++++++++++++------------
 1 file changed, 80 insertions(+), 35 deletions(-)

diff --git a/srml/slashing/src/lib.rs b/srml/slashing/src/lib.rs
index 369f494ce61..f037478f351 100644
--- a/srml/slashing/src/lib.rs
+++ b/srml/slashing/src/lib.rs
@@ -26,29 +26,63 @@ use primitives::traits::{SimpleArithmetic, MaybeSerializeDebug};
 mod fraction;
 pub use fraction::Fraction;
 
+/// Nominator exposure
+#[derive(Default)]
+pub struct NominatorExposure<AccountId, Balance> {
+	/// The stash account of the nominator in question.
+	account_id: AccountId,
+	/// Amount of funds exposed.
+	value: Balance,
+}
+
+/// Exposure for a reported validator
+#[derive(Default)]
+pub struct SlashRecipient<AccountId, Balance> {
+	/// Validator account id
+	pub account_id: AccountId,
+	/// Own balance
+	pub value: Balance,
+	/// The portions of nominators stashes that are exposed.
+	pub nominators: Vec<NominatorExposure<AccountId, Balance>>,
+}
+
 /// Report rolling data misconduct and apply slash accordingly
 // Pre-condition: the actual implementor of `OnSlashing` has access to
 // `session_index` and `number of validators`
-pub fn rolling_data<OS: OnSlashing<M>, M: Misconduct>(
-	who: &[M::AccountId],
+pub fn rolling_data<M, OS, Balance>(
+	misbehaved: &[SlashRecipient<M::AccountId, Balance>],
 	misconduct: &mut M
-) -> u8 {
-	let seve = misconduct.on_misconduct(who);
-	OS::slash(who, seve);
+) -> u8
+where
+	M: Misconduct,
+	OS: OnSlashing<M, Balance>,
+{
+	let seve = misconduct.on_misconduct(misbehaved);
+	OS::slash(misbehaved, seve);
 	misconduct.as_misconduct_level(seve)
 }
 
 /// Report era misconduct but do not perform any slashing
-pub fn era_data<OS: OnSlashing<M>, M: Misconduct>(who: &[M::AccountId], misconduct: &mut M) {
+pub fn era_data<M, OS, Balance>(who: &[SlashRecipient<M::AccountId, Balance>], misconduct: &mut M)
+where
+	M: Misconduct,
+	OS: OnSlashing<M, Balance>,
+{
 	let seve = misconduct.on_misconduct(who);
 	OS::slash(who, seve);
 }
 
 /// Slash in the end of era
-pub fn end_of_era<OS: OnSlashing<E>, E: OnEndEra>(end_of_era: &E) -> u8 {
+///
+/// Safety: Make sure call this exactly once and in the end of era
+pub fn end_of_era<E, Balance, OS>(end_of_era: &E) -> u8
+where
+	E: OnEndEra,
+	OS: OnSlashing<E, Balance>,
+{
 	let seve = end_of_era.severity();
 	let misbehaved = end_of_era.misbehaved();
-	OS::slash(&misbehaved, seve);
+	OS::slash(&misbehaved[..], seve);
 	end_of_era.as_misconduct_level(seve)
 }
 
@@ -58,26 +92,26 @@ pub trait Misconduct: system::Trait {
 	type Severity: SimpleArithmetic + Codec + Copy + MaybeSerializeDebug + Default + Into<u128>;
 
 	/// Report misconduct and estimates the current severity level
-	fn on_misconduct(&mut self, who: &[Self::AccountId]) -> Fraction<Self::Severity>;
+	fn on_misconduct<B>(&mut self, misbehaved: &[SlashRecipient<Self::AccountId, B>]) -> Fraction<Self::Severity>;
 
 	/// Convert severity level into misconduct level (1, 2, 3 or 4)
 	fn as_misconduct_level(&self, severity: Fraction<Self::Severity>) -> u8;
 }
 
-/// Trait to call end in end of era to apply slash that occured only during the era
+/// Apply slash that occurred only during the era
 pub trait OnEndEra: Misconduct {
 	/// Get severity level accumulated during the current the era
 	fn severity(&self) -> Fraction<Self::Severity>;
 
 	/// Get all misbehaved validators of the current era
-	fn misbehaved(&self) -> Vec<Self::AccountId>;
+	fn misbehaved<B>(&self) -> Vec<SlashRecipient<Self::AccountId, B>>;
 }
 
 /// Slash misbehaved, should be implemented by some `module` that has access to currency
 // In practice this is likely to be the `Staking module`
-pub trait OnSlashing<M: Misconduct> {
+pub trait OnSlashing<M: Misconduct, Balance> {
 	/// Slash
-	fn slash(who: &[M::AccountId], severity: Fraction<M::Severity>);
+	fn slash(who: &[SlashRecipient<M::AccountId, Balance>], severity: Fraction<M::Severity>);
 }
 
 #[cfg(test)]
@@ -90,11 +124,12 @@ mod test {
 	use srml_support::{impl_outer_origin, parameter_types, traits::{Currency, Get}};
 	use rstd::marker::PhantomData;
 
-	/// The AccountId alias in this test module.
 	type AccountId = u64;
 	type BlockNumber = u64;
 	type Balance = u64;
 	type Staking = srml_staking::Module<Test>;
+	type BalanceOf<T> = <<T as srml_staking::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
+	type ExtendedBalance = u128;
 
 	pub struct CurrencyToVoteHandler;
 
@@ -192,36 +227,46 @@ mod test {
 			1
 		}
 
-		fn on_misconduct(&mut self, _misbehaved: &[Self::AccountId]) -> Fraction<Self::Severity> {
+		fn on_misconduct<B>(&mut self, _misbehaved: &[SlashRecipient<Self::AccountId, B>]) -> Fraction<Self::Severity> {
 			Fraction::zero()
 		}
 	}
 
-	pub struct StakingSlasher<T>(PhantomData<T>);
-
-	impl<T: srml_staking::Trait + Misconduct> OnSlashing<T> for StakingSlasher<T> {
-		fn slash(to_punish: &[T::AccountId], severity: Fraction<T::Severity>) {
-
-			type BalanceOf<T> = <<T as srml_staking::Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::Balance;
-			type ExtendedBalance = u128;
+	pub struct StakingSlasher<T, U> {
+		t: PhantomData<T>,
+		u: PhantomData<U>,
+	}
 
+	impl<T, B> OnSlashing<T, B> for StakingSlasher<T, B>
+	where
+		T: srml_staking::Trait + Misconduct,
+		B: Into<u128> + Clone,
+	{
+		fn slash(to_punish: &[SlashRecipient<T::AccountId, B>], severity: Fraction<T::Severity>) {
 			// hack to convert both to `u128` and calculate the amount to slash
 			// then convert it back `BalanceOf<T>`
 			let to_balance = |b: ExtendedBalance|
 				<T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
-			let to_u128 = |b: BalanceOf<T>|
-				<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
-
-			for who in to_punish {
-				// WARN: exposure need to be taken in to account here which isn't
-				let balance = to_u128(T::Currency::free_balance(who));
-				// (balance * denominator) / numerator
+			let slash = |balance: u128, severity: Fraction<T::Severity>| {
 				let d = balance.saturating_mul(severity.denominator().into());
 				let n = severity.numerator().into();
-				let slash_amount = to_balance(d.checked_div(n).unwrap_or(0));
-				<srml_staking::Module<T>>::slash_validator(who, slash_amount);
-			}
+				to_balance(d.checked_div(n).unwrap_or(0))
+			};
 
+			for who in to_punish {
+				let balance: u128 = who.value.clone().into();
+				let slash_amount = slash(balance, severity);
+
+				// slash the validator
+				T::Currency::slash(&who.account_id, slash_amount);
+
+				// slash nominators for the same severity
+				for nominator in &who.nominators {
+					let balance: u128 = nominator.value.clone().into();
+					let slash_amount = slash(balance, severity);
+					T::Currency::slash(&nominator.account_id, slash_amount);
+				}
+			}
 		}
 	}
 
@@ -230,9 +275,9 @@ mod test {
 	#[should_panic]
 	fn it_works() {
 		let mut misconduct = Test;
-		let misbehaved = [0_u64, 1, 2];
+		let misbehaved: Vec<SlashRecipient<u64, u64>> = vec![SlashRecipient::default(), SlashRecipient::default()];
 
-		era_data::<StakingSlasher<Test>, Test>(&misbehaved, &mut misconduct);
-		let _misconduct_level = rolling_data::<StakingSlasher<Test>, Test>(&misbehaved, &mut misconduct);
+		era_data::<_, StakingSlasher<Test, u64>, _>(&misbehaved, &mut misconduct);
+		let _ = rolling_data::<_, StakingSlasher<Test, u64>, _>(&misbehaved, &mut misconduct);
 	}
 }
-- 
GitLab