Squashed changes. (#262)

This commit is contained in:
Gav Wood
2018-06-28 17:29:58 +02:00
committed by GitHub
parent 5ed1549bee
commit e263c8cf5d
3 changed files with 231 additions and 20 deletions
+115 -15
View File
@@ -135,7 +135,9 @@ decl_module! {
pub enum Call where aux: T::PublicAux {
fn transfer(aux, dest: RawAddress<T::AccountId, T::AccountIndex>, value: T::Balance) -> Result = 0;
fn stake(aux) -> Result = 1;
fn unstake(aux) -> Result = 2;
fn unstake(aux, index: u32) -> Result = 2;
fn nominate(aux, target: RawAddress<T::AccountId, T::AccountIndex>) -> Result = 3;
fn unnominate(aux, target_index: u32) -> Result = 4;
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -157,6 +159,7 @@ decl_storage! {
// The length of a staking era in sessions.
pub SessionsPerEra get(sessions_per_era): b"sta:spe" => required T::BlockNumber;
// The total amount of stake on the system.
// TODO: this doesn't actually track total stake yet - it should do.
pub TotalStake get(total_stake): b"sta:tot" => required T::Balance;
// The fee to be paid for making a transaction; the base.
pub TransactionBaseFee get(transaction_base_fee): b"sta:basefee" => required T::Balance;
@@ -172,7 +175,6 @@ decl_storage! {
pub CreationFee get(creation_fee): b"sta:creation_fee" => required T::Balance;
// The fee required to create a contract. At least as big as ReclaimRebate.
pub ContractFee get(contract_fee): b"sta:contract_fee" => required T::Balance;
// Maximum reward, per validator, that is provided per acceptable session.
pub SessionReward get(session_reward): b"sta:session_reward" => required T::Balance;
// Slash, per validator that is taken per abnormal era end.
@@ -181,11 +183,19 @@ decl_storage! {
// The current era index.
pub CurrentEra get(current_era): b"sta:era" => required T::BlockNumber;
// All the accounts with a desire to stake.
pub Intentions: b"sta:wil:" => default Vec<T::AccountId>;
pub Intentions get(intentions): b"sta:wil:" => default Vec<T::AccountId>;
// All nominator -> nominee relationships.
pub Nominating get(nominating): b"sta:nominating" => map [ T::AccountId => T::AccountId ];
// Nominators for a particular account.
pub NominatorsFor get(nominators_for): b"sta:nominators_for" => default map [ T::AccountId => Vec<T::AccountId> ];
// Nominators for a particular account that is in action right now.
pub CurrentNominatorsFor get(current_nominators_for): b"sta:current_nominators_for" => default map [ T::AccountId => Vec<T::AccountId> ];
// The next value of sessions per era.
pub NextSessionsPerEra get(next_sessions_per_era): b"sta:nse" => T::BlockNumber;
// The session index at which the era length last changed.
pub LastEraLengthChange get(last_era_length_change): b"sta:lec" => default T::BlockNumber;
// The current era stake threshold
pub StakeThreshold get(stake_threshold): b"sta:stake_threshold" => required T::Balance;
// The next free enumeration set.
pub NextEnumSet get(next_enum_set): b"sta:next_enum" => required T::AccountIndex;
@@ -305,27 +315,82 @@ impl<T: Trait> Module<T> {
///
/// Effects will be felt at the beginning of the next era.
fn stake(aux: &T::PublicAux) -> Result {
let aux = aux.ref_into();
ensure!(Self::nominating(aux).is_none(), "Cannot stake if already nominating.");
let mut intentions = <Intentions<T>>::get();
// can't be in the list twice.
ensure!(intentions.iter().find(|&t| t == aux.ref_into()).is_none(), "Cannot stake if already staked.");
intentions.push(aux.ref_into().clone());
ensure!(intentions.iter().find(|&t| t == aux).is_none(), "Cannot stake if already staked.");
intentions.push(aux.clone());
<Intentions<T>>::put(intentions);
<Bondage<T>>::insert(aux.ref_into(), T::BlockNumber::max_value());
<Bondage<T>>::insert(aux, T::BlockNumber::max_value());
Ok(())
}
/// Retract the desire to stake for the transactor.
///
/// Effects will be felt at the beginning of the next era.
fn unstake(aux: &T::PublicAux) -> Result {
fn unstake(aux: &T::PublicAux, position: u32) -> Result {
let aux = aux.ref_into();
let position = position as usize;
let mut intentions = <Intentions<T>>::get();
let position = intentions.iter().position(|t| t == aux.ref_into()).ok_or("Cannot unstake if not already staked.")?;
// let position = intentions.iter().position(|t| t == aux.ref_into()).ok_or("Cannot unstake if not already staked.")?;
if intentions.get(position) != Some(aux) {
return Err("Invalid index")
}
intentions.swap_remove(position);
<Intentions<T>>::put(intentions);
<Bondage<T>>::insert(aux.ref_into(), Self::current_era() + Self::bonding_duration());
Ok(())
}
fn nominate(aux: &T::PublicAux, target: RawAddress<T::AccountId, T::AccountIndex>) -> Result {
let target = Self::lookup(target)?;
let aux = aux.ref_into();
ensure!(Self::nominating(aux).is_none(), "Cannot nominate if already nominating.");
ensure!(Self::intentions().iter().find(|&t| t == aux.ref_into()).is_none(), "Cannot nominate if already staked.");
// update nominators_for
let mut t = Self::nominators_for(&target);
t.push(aux.clone());
<NominatorsFor<T>>::insert(&target, t);
// update nominating
<Nominating<T>>::insert(aux, &target);
// Update bondage
<Bondage<T>>::insert(aux.ref_into(), T::BlockNumber::max_value());
Ok(())
}
/// Will panic if called when source isn't currently nominating target.
/// Updates Nominating, NominatorsFor and NominationBalance.
fn unnominate(aux: &T::PublicAux, target_index: u32) -> Result {
let source = aux.ref_into();
let target_index = target_index as usize;
let target = <Nominating<T>>::get(source).ok_or("Account must be nominating")?;
let mut t = Self::nominators_for(&target);
if t.get(target_index) != Some(source) {
return Err("Invalid target index")
}
// Ok - all valid.
// update nominators_for
t.swap_remove(target_index);
<NominatorsFor<T>>::insert(&target, t);
// update nominating
<Nominating<T>>::remove(source);
// update bondage
<Bondage<T>>::insert(aux.ref_into(), Self::current_era() + Self::bonding_duration());
Ok(())
}
// PRIV DISPATCH
/// Set the number of sessions in an era.
@@ -496,13 +561,30 @@ impl<T: Trait> Module<T> {
let reward = Self::session_reward() * T::Balance::sa(percent) / T::Balance::sa(65536usize);
// apply good session reward
for v in <session::Module<T>>::validators().iter() {
let _ = Self::reward(v, reward); // will never fail as validator accounts must be created, but even if it did, it's just a missed reward.
let noms = Self::current_nominators_for(v);
let total = noms.iter().map(Self::voting_balance).fold(Self::voting_balance(v), |acc, x| acc + x);
if !total.is_zero() {
let safe_mul_rational = |b| b * reward / total;// TODO: avoid overflow
for n in noms.iter() {
let _ = Self::reward(n, safe_mul_rational(Self::voting_balance(n)));
}
let _ = Self::reward(v, safe_mul_rational(Self::voting_balance(v)));
}
}
} else {
// slash
let early_era_slash = Self::early_era_slash();
for v in <session::Module<T>>::validators().iter() {
Self::slash(v, early_era_slash);
if let Some(rem) = Self::slash(v, early_era_slash) {
let noms = Self::current_nominators_for(v);
let total = noms.iter().map(Self::voting_balance).fold(Zero::zero(), |acc, x| acc + x);
for n in noms.iter() {
//let r = Self::voting_balance(n) * reward / total; // correct formula, but might overflow with large slash * total.
let quant = T::Balance::sa(1usize << 31);
let s = (Self::voting_balance(n) * quant / total) * rem / quant; // avoid overflow by using quant as a denominator.
let _ = Self::slash(n, s); // best effort - not much that can be done on fail.
}
}
}
}
if ((session_index - Self::last_era_length_change()) % Self::sessions_per_era()).is_zero() || !normal_rotation {
@@ -510,6 +592,11 @@ impl<T: Trait> Module<T> {
}
}
/// Balance of a (potential) validator that includes all nominators.
fn nomination_balance(who: &T::AccountId) -> T::Balance {
Self::nominators_for(who).iter().map(Self::voting_balance).fold(Zero::zero(), |acc, x| acc + x)
}
/// The era has changed - enact new staking set.
///
/// NOTE: This always happens immediately before a session change to ensure that new validators
@@ -530,17 +617,30 @@ impl<T: Trait> Module<T> {
// combination of validators, then use session::internal::set_validators().
// for now, this just orders would-be stakers by their balances and chooses the top-most
// <ValidatorCount<T>>::get() of them.
// TODO: this is not sound. this should be moved to an off-chain solution mechanism.
let mut intentions = <Intentions<T>>::get()
.into_iter()
.map(|v| (Self::voting_balance(&v), v))
.map(|v| (Self::voting_balance(&v) + Self::nomination_balance(&v), v))
.collect::<Vec<_>>();
intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1));
<session::Module<T>>::set_validators(
&intentions.into_iter()
<StakeThreshold<T>>::put(
if intentions.len() > 0 {
let i = (<ValidatorCount<T>>::get() as usize).min(intentions.len() - 1);
intentions[i].0.clone()
} else { Zero::zero() }
);
let vals = &intentions.into_iter()
.map(|(_, v)| v)
.take(<ValidatorCount<T>>::get() as usize)
.collect::<Vec<_>>()
);
.collect::<Vec<_>>();
for v in <session::Module<T>>::validators().iter() {
<CurrentNominatorsFor<T>>::remove(v);
}
for v in vals.iter() {
<CurrentNominatorsFor<T>>::insert(v, Self::nominators_for(v));
}
<session::Module<T>>::set_validators(vals);
}
fn enum_set_size() -> T::AccountIndex {
@@ -98,7 +98,7 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64
contract_fee: 0,
reclaim_rebate: 0,
session_reward: reward,
early_era_slash: if monied { 10 } else { 0 },
early_era_slash: if monied { 20 } else { 0 },
}.build_storage());
t.extend(timestamp::GenesisConfig::<Test>{
period: 5
@@ -79,11 +79,18 @@ fn slashing_should_work() {
assert_eq!(Session::current_index(), 1);
assert_eq!(Staking::voting_balance(&10), 11);
System::set_block_number(4);
System::set_block_number(6);
Timestamp::set_timestamp(30); // on time.
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 2);
assert_eq!(Staking::voting_balance(&10), 21);
System::set_block_number(7);
Timestamp::set_timestamp(100); // way too late - early exit.
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::current_index(), 2);
assert_eq!(Session::current_index(), 3);
assert_eq!(Staking::voting_balance(&10), 1);
});
}
@@ -192,7 +199,7 @@ fn staking_should_work() {
// Block 3: Unstake highest, introduce another staker. No change yet.
System::set_block_number(3);
assert_ok!(Staking::stake(&3));
assert_ok!(Staking::unstake(&4));
assert_ok!(Staking::unstake(&4, Staking::intentions().iter().position(|&x| x == 4).unwrap() as u32));
assert_eq!(Staking::current_era(), 1);
Session::check_rotate_session();
@@ -214,7 +221,7 @@ fn staking_should_work() {
// Block 7: Unstake three. No change yet.
System::set_block_number(7);
assert_ok!(Staking::unstake(&3));
assert_ok!(Staking::unstake(&3, Staking::intentions().iter().position(|&x| x == 3).unwrap() as u32));
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 3]);
@@ -225,6 +232,110 @@ fn staking_should_work() {
});
}
#[test]
fn nominating_and_rewards_should_work() {
with_externalities(&mut new_test_ext(0, 1, 1, 0, true, 10), || {
assert_eq!(Staking::era_length(), 1);
assert_eq!(Staking::validator_count(), 2);
assert_eq!(Staking::bonding_duration(), 3);
assert_eq!(Session::validators(), vec![10, 20]);
System::set_block_number(1);
assert_ok!(Staking::stake(&1));
assert_ok!(Staking::stake(&2));
assert_ok!(Staking::stake(&3));
assert_ok!(Staking::nominate(&4, 1.into()));
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::validators(), vec![1, 3]); // 4 + 1, 3
assert_eq!(Staking::voting_balance(&1), 10);
assert_eq!(Staking::voting_balance(&2), 20);
assert_eq!(Staking::voting_balance(&3), 30);
assert_eq!(Staking::voting_balance(&4), 40);
System::set_block_number(2);
assert_ok!(Staking::unnominate(&4, 0));
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 2);
assert_eq!(Session::validators(), vec![3, 2]);
assert_eq!(Staking::voting_balance(&1), 12);
assert_eq!(Staking::voting_balance(&2), 20);
assert_eq!(Staking::voting_balance(&3), 40);
assert_eq!(Staking::voting_balance(&4), 48);
System::set_block_number(3);
assert_ok!(Staking::stake(&4));
assert_ok!(Staking::unstake(&3, Staking::intentions().iter().position(|&x| x == 3).unwrap() as u32));
assert_ok!(Staking::nominate(&3, 1.into()));
Session::check_rotate_session();
assert_eq!(Session::validators(), vec![1, 4]);
assert_eq!(Staking::voting_balance(&1), 12);
assert_eq!(Staking::voting_balance(&2), 30);
assert_eq!(Staking::voting_balance(&3), 50);
assert_eq!(Staking::voting_balance(&4), 48);
System::set_block_number(4);
Session::check_rotate_session();
assert_eq!(Staking::voting_balance(&1), 13);
assert_eq!(Staking::voting_balance(&2), 30);
assert_eq!(Staking::voting_balance(&3), 58);
assert_eq!(Staking::voting_balance(&4), 58);
});
}
#[test]
fn nominating_slashes_should_work() {
with_externalities(&mut new_test_ext(0, 2, 2, 0, true, 10), || {
assert_eq!(Staking::era_length(), 4);
assert_eq!(Staking::validator_count(), 2);
assert_eq!(Staking::bonding_duration(), 3);
assert_eq!(Session::validators(), vec![10, 20]);
System::set_block_number(2);
Session::check_rotate_session();
Timestamp::set_timestamp(15);
System::set_block_number(4);
assert_ok!(Staking::stake(&1));
assert_ok!(Staking::stake(&3));
assert_ok!(Staking::nominate(&2, 3.into()));
assert_ok!(Staking::nominate(&4, 1.into()));
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::validators(), vec![1, 3]); // 1 + 4, 3 + 2
assert_eq!(Staking::voting_balance(&1), 10);
assert_eq!(Staking::voting_balance(&2), 20);
assert_eq!(Staking::voting_balance(&3), 30);
assert_eq!(Staking::voting_balance(&4), 40);
System::set_block_number(5);
Timestamp::set_timestamp(100); // late
assert_eq!(Session::blocks_remaining(), 1);
assert!(Session::broken_validation());
Session::check_rotate_session();
assert_eq!(Staking::current_era(), 2);
assert_eq!(Staking::voting_balance(&1), 0);
assert_eq!(Staking::voting_balance(&2), 20);
assert_eq!(Staking::voting_balance(&3), 10);
assert_eq!(Staking::voting_balance(&4), 30);
});
}
#[test]
fn double_staking_should_fail() {
with_externalities(&mut new_test_ext(0, 1, 2, 0, true, 0), || {
System::set_block_number(1);
assert_ok!(Staking::stake(&1));
assert_noop!(Staking::stake(&1), "Cannot stake if already staked.");
assert_noop!(Staking::nominate(&1, 1.into()), "Cannot nominate if already staked.");
assert_ok!(Staking::nominate(&2, 1.into()));
assert_noop!(Staking::stake(&2), "Cannot stake if already nominating.");
assert_noop!(Staking::nominate(&2, 1.into()), "Cannot nominate if already nominating.");
});
}
#[test]
fn staking_eras_work() {
with_externalities(&mut new_test_ext(0, 1, 2, 0, true, 0), || {