mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-27 04:37:57 +00:00
Squashed changes. (#262)
This commit is contained in:
@@ -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), || {
|
||||
|
||||
Reference in New Issue
Block a user