mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 03:31:05 +00:00
Society pallet (#4170)
* Introduce efficient Hash-based RNG streamer * Initial draft of the society module * Introduce a test * Dual-pot logic * Vouching * Use chacha * Half way through moving to cliff payout. * Fixes * Add some tests * Remove printlns * Merge remote-tracking branch 'origin/gav-verified-id' into gav-verified-id # Conflicts: # frame/identity/src/lib.rs * Merge remote-tracking branch 'origin/gav-verified-id' into gav-verified-id # Conflicts: # frame/identity/src/lib.rs * Fix `slash_payout`, add test * Test for multi-slash_payout * Add docs to `put_bid` function and `bidding_works` test * Add strikes to test * Add comments to `rotate_period` * Implement `suspend_member` * Off chain iteration of suspended members using linked_map * Half of suspended candidate * Finish suspend_candidate, need tests * Resolve mistakes and feedback, add `suspended_candidate_rejected` test * Remove logic which increases payout time after un-suspension * Fix error in `slash_suspended_candidates`, add member check to `vote` * Fix vouch rewards, dont create zero payouts, add tests for vouch * Test unvouch * Unbid tests * Add lifecycle events, fix `add_member` to update `MembershipChanged` * Head cannot be removed from society * Use `add_member` in `found` to ensure `MembershipChanged` is triggered * Use `Judgement` enum for suspended candidate judgement * Make society instantiable * Implement challenges * Remove extra text in test * Remove `BlockNumber` return from `slash_payout` * Add bad vote causes slash test * Update frame/society/src/lib.rs Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * Add consts to module metadata * Check `suspended_member` cant bid * Increase strength of payout check, **must** be a member. * Start pallet documentation * Finish docs * Update library names, use decl_error * Prevent double bids, add test * Use `map` for vouching member, and introduce banned vouchers * Remove leftover docs * Vouching handles removed member lifecycles w/ tests * `take` the votes when tallying, add comprehensive checks before vouch or bid * Check votes are cleaned up * Check vote is for a valid candidate, add vote event * Defender vote event * Fix `judge_suspended_candidate`, add weight docs * First pass fixes (blank lines, formatting, no operational) * Bump copyright year * Make `add_member` infallible * More feedback updates * Add storage access complexity * Fix logic for AutoUnbid * Complete weight documentation * Optimize logic which used to result in double storage read. * Use Bid struct rather than tuple * Introduce `MaxMembers` configuration constant * Add comment about fringe scenario where `MaxMembers` could go over, NBD * Change MaxMembership to configurable storage item with ability for root to update * Make membership challenges skew toward failure. If no one at all votes, or the vote is tied, the user will be suspended from society. This means, that the user simply needs to vote for themselves to stay in society assuming no one else participates. * Refactor `is_candidate`as to avoid possible double storage reads in the future. * Blank lines Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "pallet-society"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.101", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] }
|
||||
sp-io ={ path = "../../primitives/io", default-features = false }
|
||||
sp-runtime = { version = "2.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" }
|
||||
frame-support = { version = "2.0.0", default-features = false, path = "../support" }
|
||||
frame-system = { version = "2.0.0", default-features = false, path = "../system" }
|
||||
rand_chacha = { version = "0.2", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { version = "2.0.0", path = "../../primitives/core" }
|
||||
pallet-balances = { version = "2.0.0", path = "../balances" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"serde",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"rand_chacha/std",
|
||||
"sp-std/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_support::{impl_outer_origin, parameter_types};
|
||||
use sp_core::H256;
|
||||
// The testing primitives are very useful for avoiding having to work with signatures
|
||||
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are requried.
|
||||
use sp_runtime::{
|
||||
Perbill, traits::{BlakeTwo256, IdentityLookup, OnInitialize, OnFinalize}, testing::Header,
|
||||
};
|
||||
use frame_system::EnsureSignedBy;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test {}
|
||||
}
|
||||
|
||||
// For testing the module, we construct most of a mock runtime. This means
|
||||
// first constructing a configuration type (`Test`) which `impl`s each of the
|
||||
// configuration traits of modules we want to use.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct Test;
|
||||
parameter_types! {
|
||||
pub const CandidateDeposit: u64 = 25;
|
||||
pub const WrongSideDeduction: u64 = 2;
|
||||
pub const MaxStrikes: u32 = 2;
|
||||
pub const RotationPeriod: u64 = 4;
|
||||
pub const PeriodSpend: u64 = 1000;
|
||||
pub const MaxLockDuration: u64 = 100;
|
||||
pub const FounderSetAccount: u64 = 1;
|
||||
pub const SuspensionJudgementSetAccount: u64 = 2;
|
||||
pub const ChallengePeriod: u64 = 8;
|
||||
pub const MaxMembers: u32 = 100;
|
||||
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: u32 = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
|
||||
pub const ExistentialDeposit: u64 = 0;
|
||||
pub const TransferFee: u64 = 0;
|
||||
pub const CreationFee: u64 = 0;
|
||||
}
|
||||
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Call = ();
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = u128;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type MaximumBlockWeight = MaximumBlockWeight;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type Version = ();
|
||||
type ModuleToIndex = ();
|
||||
}
|
||||
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
type OnFreeBalanceZero = ();
|
||||
type OnNewAccount = ();
|
||||
type Event = ();
|
||||
type TransferPayment = ();
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type TransferFee = TransferFee;
|
||||
type CreationFee = CreationFee;
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
type Event = ();
|
||||
type Currency = pallet_balances::Module<Self>;
|
||||
type Randomness = ();
|
||||
type CandidateDeposit = CandidateDeposit;
|
||||
type WrongSideDeduction = WrongSideDeduction;
|
||||
type MaxStrikes = MaxStrikes;
|
||||
type PeriodSpend = PeriodSpend;
|
||||
type MembershipChanged = ();
|
||||
type RotationPeriod = RotationPeriod;
|
||||
type MaxLockDuration = MaxLockDuration;
|
||||
type FounderOrigin = EnsureSignedBy<FounderSetAccount, u128>;
|
||||
type SuspensionJudgementOrigin = EnsureSignedBy<SuspensionJudgementSetAccount, u128>;
|
||||
type ChallengePeriod = ChallengePeriod;
|
||||
}
|
||||
|
||||
pub type Society = Module<Test>;
|
||||
pub type System = frame_system::Module<Test>;
|
||||
pub type Balances = pallet_balances::Module<Test>;
|
||||
|
||||
pub struct EnvBuilder {
|
||||
members: Vec<u128>,
|
||||
balance: u64,
|
||||
balances: Vec<(u128, u64)>,
|
||||
pot: u64,
|
||||
max_members: u32,
|
||||
}
|
||||
|
||||
impl EnvBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
members: vec![10],
|
||||
balance: 10_000,
|
||||
balances: vec![
|
||||
(10, 50),
|
||||
(20, 50),
|
||||
(30, 50),
|
||||
(40, 50),
|
||||
(50, 50),
|
||||
(60, 50),
|
||||
],
|
||||
pot: 0,
|
||||
max_members: 100,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute<R, F: FnOnce() -> R>(mut self, f: F) -> R {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
self.balances.push((Society::account_id(), self.balance.max(self.pot)));
|
||||
pallet_balances::GenesisConfig::<Test> {
|
||||
balances: self.balances,
|
||||
vesting: vec![],
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
GenesisConfig::<Test>{
|
||||
members: self.members,
|
||||
pot: self.pot,
|
||||
max_members: self.max_members,
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
let mut ext: sp_io::TestExternalities = t.into();
|
||||
ext.execute_with(f)
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn with_members(mut self, m: Vec<u128>) -> Self {
|
||||
self.members = m;
|
||||
self
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn with_balances(mut self, b: Vec<(u128, u64)>) -> Self {
|
||||
self.balances = b;
|
||||
self
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn with_pot(mut self, p: u64) -> Self {
|
||||
self.pot = p;
|
||||
self
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn with_balance(mut self, b: u64) -> Self {
|
||||
self.balance = b;
|
||||
self
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn with_max_members(mut self, n: u32) -> Self {
|
||||
self.max_members = n;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Run until a particular block.
|
||||
pub fn run_to_block(n: u64) {
|
||||
while System::block_number() < n {
|
||||
if System::block_number() > 1 {
|
||||
System::on_finalize(System::block_number());
|
||||
}
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
System::on_initialize(System::block_number());
|
||||
Society::on_initialize(System::block_number());
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a bid struct using input parameters.
|
||||
pub fn create_bid<AccountId, Balance>(
|
||||
value: Balance,
|
||||
who: AccountId,
|
||||
kind: BidKind<AccountId, Balance>
|
||||
) -> Bid<AccountId, Balance>
|
||||
{
|
||||
Bid {
|
||||
who,
|
||||
kind,
|
||||
value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,744 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
use super::*;
|
||||
use mock::*;
|
||||
|
||||
use frame_support::{assert_ok, assert_noop};
|
||||
use sp_runtime::traits::BadOrigin;
|
||||
|
||||
#[test]
|
||||
fn founding_works() {
|
||||
EnvBuilder::new().with_members(vec![]).execute(|| {
|
||||
// Account 1 is set as the founder origin
|
||||
// Account 5 cannot start a society
|
||||
assert_noop!(Society::found(Origin::signed(5), 20), BadOrigin);
|
||||
// Account 1 can start a society, where 10 is the founding member
|
||||
assert_ok!(Society::found(Origin::signed(1), 10));
|
||||
// Society members only include 10
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// 10 is the head of the society
|
||||
assert_eq!(Society::head(), Some(10));
|
||||
// Cannot start another society
|
||||
assert_noop!(Society::found(Origin::signed(1), 20), Error::<Test, _>::AlreadyFounded);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_new_member_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
assert_eq!(Balances::free_balance(20), 50);
|
||||
// Bid causes Candidate Deposit to be reserved.
|
||||
assert_ok!(Society::bid(Origin::signed(20), 0));
|
||||
assert_eq!(Balances::free_balance(20), 25);
|
||||
assert_eq!(Balances::reserved_balance(20), 25);
|
||||
// Rotate period every 4 blocks
|
||||
run_to_block(4);
|
||||
// 20 is now a candidate
|
||||
assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]);
|
||||
// 10 (a member) can vote for the candidate
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, true));
|
||||
// Rotate period every 4 blocks
|
||||
run_to_block(8);
|
||||
// 20 is now a member of the society
|
||||
assert_eq!(Society::members(), vec![10, 20]);
|
||||
// Reserved balance is returned
|
||||
assert_eq!(Balances::free_balance(20), 50);
|
||||
assert_eq!(Balances::reserved_balance(20), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bidding_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Users make bids of various amounts
|
||||
assert_ok!(Society::bid(Origin::signed(60), 1900));
|
||||
assert_ok!(Society::bid(Origin::signed(50), 500));
|
||||
assert_ok!(Society::bid(Origin::signed(40), 400));
|
||||
assert_ok!(Society::bid(Origin::signed(30), 300));
|
||||
// Rotate period
|
||||
run_to_block(4);
|
||||
// Pot is 1000 after "PeriodSpend"
|
||||
assert_eq!(Society::pot(), 1000);
|
||||
assert_eq!(Balances::free_balance(Society::account_id()), 10_000);
|
||||
// Choose smallest bidding users whose total is less than pot
|
||||
assert_eq!(Society::candidates(), vec![
|
||||
create_bid(300, 30, BidKind::Deposit(25)),
|
||||
create_bid(400, 40, BidKind::Deposit(25)),
|
||||
]);
|
||||
// A member votes for these candidates to join the society
|
||||
assert_ok!(Society::vote(Origin::signed(10), 30, true));
|
||||
assert_ok!(Society::vote(Origin::signed(10), 40, true));
|
||||
run_to_block(8);
|
||||
// Candidates become members after a period rotation
|
||||
assert_eq!(Society::members(), vec![10, 30, 40]);
|
||||
// Pot is increased by 1000, but pays out 700 to the members
|
||||
assert_eq!(Balances::free_balance(Society::account_id()), 9_300);
|
||||
assert_eq!(Society::pot(), 1_300);
|
||||
// Left over from the original bids is 50 who satisfies the condition of bid less than pot.
|
||||
assert_eq!(Society::candidates(), vec![ create_bid(500, 50, BidKind::Deposit(25)) ]);
|
||||
// 40, now a member, can vote for 50
|
||||
assert_ok!(Society::vote(Origin::signed(40), 50, true));
|
||||
run_to_block(12);
|
||||
// 50 is now a member
|
||||
assert_eq!(Society::members(), vec![10, 30, 40, 50]);
|
||||
// Pot is increased by 1000, and 500 is paid out. Total payout so far is 1200.
|
||||
assert_eq!(Society::pot(), 1_800);
|
||||
assert_eq!(Balances::free_balance(Society::account_id()), 8_800);
|
||||
// No more candidates satisfy the requirements
|
||||
assert_eq!(Society::candidates(), vec![]);
|
||||
assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around
|
||||
// Next period
|
||||
run_to_block(16);
|
||||
// Same members
|
||||
assert_eq!(Society::members(), vec![10, 30, 40, 50]);
|
||||
// Pot is increased by 1000 again
|
||||
assert_eq!(Society::pot(), 2_800);
|
||||
// No payouts
|
||||
assert_eq!(Balances::free_balance(Society::account_id()), 8_800);
|
||||
// Candidate 60 now qualifies based on the increased pot size.
|
||||
assert_eq!(Society::candidates(), vec![ create_bid(1900, 60, BidKind::Deposit(25)) ]);
|
||||
// Candidate 60 is voted in.
|
||||
assert_ok!(Society::vote(Origin::signed(50), 60, true));
|
||||
run_to_block(20);
|
||||
// 60 joins as a member
|
||||
assert_eq!(Society::members(), vec![10, 30, 40, 50, 60]);
|
||||
// Pay them
|
||||
assert_eq!(Society::pot(), 1_900);
|
||||
assert_eq!(Balances::free_balance(Society::account_id()), 6_900);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbidding_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// 20 and 30 make bids
|
||||
assert_ok!(Society::bid(Origin::signed(20), 1000));
|
||||
assert_ok!(Society::bid(Origin::signed(30), 0));
|
||||
// Balances are reserved
|
||||
assert_eq!(Balances::free_balance(30), 25);
|
||||
assert_eq!(Balances::reserved_balance(30), 25);
|
||||
// Must know right position to unbid + cannot unbid someone else
|
||||
assert_noop!(Society::unbid(Origin::signed(30), 1), Error::<Test, _>::BadPosition);
|
||||
// Can unbid themselves with the right position
|
||||
assert_ok!(Society::unbid(Origin::signed(30), 0));
|
||||
// Balance is returned
|
||||
assert_eq!(Balances::free_balance(30), 50);
|
||||
assert_eq!(Balances::reserved_balance(30), 0);
|
||||
// 20 wins candidacy
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![ create_bid(1000, 20, BidKind::Deposit(25)) ]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payout_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Original balance of 50
|
||||
assert_eq!(Balances::free_balance(20), 50);
|
||||
assert_ok!(Society::bid(Origin::signed(20), 1000));
|
||||
run_to_block(4);
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, true));
|
||||
run_to_block(8);
|
||||
// payout not ready
|
||||
assert_noop!(Society::payout(Origin::signed(20)), Error::<Test, _>::NoPayout);
|
||||
run_to_block(9);
|
||||
// payout should be here
|
||||
assert_ok!(Society::payout(Origin::signed(20)));
|
||||
assert_eq!(Balances::free_balance(20), 1050);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_new_member_skeptic_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
assert_eq!(Strikes::<Test>::get(10), 0);
|
||||
assert_ok!(Society::bid(Origin::signed(20), 0));
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]);
|
||||
run_to_block(8);
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
assert_eq!(Strikes::<Test>::get(10), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_new_member_reject_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Starting Balance
|
||||
assert_eq!(Balances::free_balance(20), 50);
|
||||
// 20 makes a bid
|
||||
assert_ok!(Society::bid(Origin::signed(20), 0));
|
||||
assert_eq!(Balances::free_balance(20), 25);
|
||||
assert_eq!(Balances::reserved_balance(20), 25);
|
||||
// Rotation Period
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]);
|
||||
// We say no
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, false));
|
||||
run_to_block(8);
|
||||
// User is not added as member
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// User is suspended
|
||||
assert_eq!(Society::candidates(), vec![]);
|
||||
assert_eq!(Society::suspended_candidate(20).is_some(), true);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_payout_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
assert_eq!(Balances::free_balance(20), 50);
|
||||
assert_ok!(Society::bid(Origin::signed(20), 1000));
|
||||
run_to_block(4);
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, true));
|
||||
run_to_block(8);
|
||||
// payout in queue
|
||||
assert_eq!(Payouts::<Test>::get(20), vec![(9, 1000)]);
|
||||
assert_noop!(Society::payout(Origin::signed(20)), Error::<Test, _>::NoPayout);
|
||||
// slash payout
|
||||
assert_eq!(Society::slash_payout(&20, 500), 500);
|
||||
assert_eq!(Payouts::<Test>::get(20), vec![(9, 500)]);
|
||||
run_to_block(9);
|
||||
// payout should be here, but 500 less
|
||||
assert_ok!(Society::payout(Origin::signed(20)));
|
||||
assert_eq!(Balances::free_balance(20), 550);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_payout_multi_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
assert_eq!(Balances::free_balance(20), 50);
|
||||
// create a few payouts
|
||||
Society::bump_payout(&20, 5, 100);
|
||||
Society::bump_payout(&20, 10, 100);
|
||||
Society::bump_payout(&20, 15, 100);
|
||||
Society::bump_payout(&20, 20, 100);
|
||||
// payouts in queue
|
||||
assert_eq!(Payouts::<Test>::get(20), vec![(5, 100), (10, 100), (15, 100), (20, 100)]);
|
||||
// slash payout
|
||||
assert_eq!(Society::slash_payout(&20, 250), 250);
|
||||
assert_eq!(Payouts::<Test>::get(20), vec![(15, 50), (20, 100)]);
|
||||
// slash again
|
||||
assert_eq!(Society::slash_payout(&20, 50), 50);
|
||||
assert_eq!(Payouts::<Test>::get(20), vec![(20, 100)]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspended_member_lifecycle_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Add 20 to members, who is not the head and can be suspended/removed.
|
||||
assert_ok!(Society::add_member(&20));
|
||||
assert_eq!(<Members<Test>>::get(), vec![10, 20]);
|
||||
assert_eq!(Strikes::<Test>::get(20), 0);
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(20), None);
|
||||
|
||||
// Let's suspend account 20 by giving them 2 strikes by not voting
|
||||
assert_ok!(Society::bid(Origin::signed(30), 0));
|
||||
run_to_block(8);
|
||||
assert_eq!(Strikes::<Test>::get(20), 1);
|
||||
assert_ok!(Society::bid(Origin::signed(40), 0));
|
||||
run_to_block(16);
|
||||
|
||||
// Strike 2 is accumulated, and 20 is suspended :(
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(20), Some(()));
|
||||
assert_eq!(<Members<Test>>::get(), vec![10]);
|
||||
|
||||
// Suspended members cannot get payout
|
||||
Society::bump_payout(&20, 10, 100);
|
||||
assert_noop!(Society::payout(Origin::signed(20)), Error::<Test, _>::NotMember);
|
||||
|
||||
// Normal people cannot make judgement
|
||||
assert_noop!(Society::judge_suspended_member(Origin::signed(20), 20, true), BadOrigin);
|
||||
|
||||
// Suspension judgment origin can judge thee
|
||||
// Suspension judgement origin forgives the suspended member
|
||||
assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, true));
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(20), None);
|
||||
assert_eq!(<Members<Test>>::get(), vec![10, 20]);
|
||||
|
||||
// Let's suspend them again, directly
|
||||
Society::suspend_member(&20);
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(20), Some(()));
|
||||
// Suspension judgement origin does not forgive the suspended member
|
||||
assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false));
|
||||
// Cleaned up
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(20), None);
|
||||
assert_eq!(<Members<Test>>::get(), vec![10]);
|
||||
assert_eq!(<Payouts<Test>>::get(20), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suspended_candidate_rejected_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Starting Balance
|
||||
assert_eq!(Balances::free_balance(20), 50);
|
||||
assert_eq!(Balances::free_balance(Society::account_id()), 10000);
|
||||
// 20 makes a bid
|
||||
assert_ok!(Society::bid(Origin::signed(20), 0));
|
||||
assert_eq!(Balances::free_balance(20), 25);
|
||||
assert_eq!(Balances::reserved_balance(20), 25);
|
||||
// Rotation Period
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]);
|
||||
// We say no
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, false));
|
||||
run_to_block(8);
|
||||
// User is not added as member
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// User is suspended
|
||||
assert_eq!(Society::candidates(), vec![]);
|
||||
assert_eq!(Society::suspended_candidate(20).is_some(), true);
|
||||
|
||||
// Normal user cannot make judgement on suspended candidate
|
||||
assert_noop!(Society::judge_suspended_candidate(Origin::signed(20), 20, Judgement::Approve), BadOrigin);
|
||||
|
||||
// Suspension judgement origin makes no direct judgement
|
||||
assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Rebid));
|
||||
// They are placed back in bid pool, repeat suspension process
|
||||
// Rotation Period
|
||||
run_to_block(12);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]);
|
||||
// We say no
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, false));
|
||||
run_to_block(16);
|
||||
// User is not added as member
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// User is suspended
|
||||
assert_eq!(Society::candidates(), vec![]);
|
||||
assert_eq!(Society::suspended_candidate(20).is_some(), true);
|
||||
|
||||
// Suspension judgement origin rejects the candidate
|
||||
assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Reject));
|
||||
// User is slashed
|
||||
assert_eq!(Balances::free_balance(20), 25);
|
||||
assert_eq!(Balances::reserved_balance(20), 0);
|
||||
// Funds are deposited to society account
|
||||
assert_eq!(Balances::free_balance(Society::account_id()), 10025);
|
||||
// Cleaned up
|
||||
assert_eq!(Society::candidates(), vec![]);
|
||||
assert_eq!(<SuspendedCandidates<Test>>::get(20), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vouch_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// 10 is the only member
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// A non-member cannot vouch
|
||||
assert_noop!(Society::vouch(Origin::signed(1), 20, 1000, 100), Error::<Test, _>::NotMember);
|
||||
// A member can though
|
||||
assert_ok!(Society::vouch(Origin::signed(10), 20, 1000, 100));
|
||||
assert_eq!(<Vouching<Test>>::get(10), Some(VouchingStatus::Vouching));
|
||||
// A member cannot vouch twice at the same time
|
||||
assert_noop!(Society::vouch(Origin::signed(10), 30, 100, 0), Error::<Test, _>::AlreadyVouching);
|
||||
// Vouching creates the right kind of bid
|
||||
assert_eq!(<Bids<Test>>::get(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]);
|
||||
// Vouched user can become candidate
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]);
|
||||
// Vote yes
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, true));
|
||||
// Vouched user can win
|
||||
run_to_block(8);
|
||||
assert_eq!(Society::members(), vec![10, 20]);
|
||||
// Voucher wins a portion of the payment
|
||||
assert_eq!(<Payouts<Test>>::get(10), vec![(9, 100)]);
|
||||
// Vouched user wins the rest
|
||||
assert_eq!(<Payouts<Test>>::get(20), vec![(9, 900)]);
|
||||
// 10 is no longer vouching
|
||||
assert_eq!(<Vouching<Test>>::get(10), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voucher_cannot_win_more_than_bid() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// 10 is the only member
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// 10 vouches, but asks for more than the bid
|
||||
assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 1000));
|
||||
// Vouching creates the right kind of bid
|
||||
assert_eq!(<Bids<Test>>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]);
|
||||
// Vouched user can become candidate
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]);
|
||||
// Vote yes
|
||||
assert_ok!(Society::vote(Origin::signed(10), 20, true));
|
||||
// Vouched user can win
|
||||
run_to_block(8);
|
||||
assert_eq!(Society::members(), vec![10, 20]);
|
||||
// Voucher wins as much as the bid
|
||||
assert_eq!(<Payouts<Test>>::get(10), vec![(9, 100)]);
|
||||
// Vouched user gets nothing
|
||||
assert_eq!(<Payouts<Test>>::get(20), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unvouch_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// 10 is the only member
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// 10 vouches for 20
|
||||
assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0));
|
||||
// 20 has a bid
|
||||
assert_eq!(<Bids<Test>>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]);
|
||||
// 10 is vouched
|
||||
assert_eq!(<Vouching<Test>>::get(10), Some(VouchingStatus::Vouching));
|
||||
// To unvouch, you must know the right bid position
|
||||
assert_noop!(Society::unvouch(Origin::signed(10), 2), Error::<Test, _>::BadPosition);
|
||||
// 10 can unvouch with the right position
|
||||
assert_ok!(Society::unvouch(Origin::signed(10), 0));
|
||||
// 20 no longer has a bid
|
||||
assert_eq!(<Bids<Test>>::get(), vec![]);
|
||||
// 10 is no longer vouching
|
||||
assert_eq!(<Vouching<Test>>::get(10), None);
|
||||
|
||||
// Cannot unvouch after they become candidate
|
||||
assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0));
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]);
|
||||
assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::<Test, _>::BadPosition);
|
||||
// 10 is still vouching until candidate is approved or rejected
|
||||
assert_eq!(<Vouching<Test>>::get(10), Some(VouchingStatus::Vouching));
|
||||
run_to_block(8);
|
||||
// In this case candidate is denied and suspended
|
||||
assert!(Society::suspended_candidate(&20).is_some());
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// User is stuck vouching until judgement origin resolves suspended candidate
|
||||
assert_eq!(<Vouching<Test, _>>::get(10), Some(VouchingStatus::Vouching));
|
||||
// Judge denies candidate
|
||||
assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Reject));
|
||||
// 10 is banned from vouching
|
||||
assert_eq!(<Vouching<Test, _>>::get(10), Some(VouchingStatus::Banned));
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
|
||||
// 10 cannot vouch again
|
||||
assert_noop!(Society::vouch(Origin::signed(10), 30, 100, 0), Error::<Test, _>::AlreadyVouching);
|
||||
// 10 cannot unvouch either, so they are banned forever.
|
||||
assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::<Test, _>::NotVouching);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbid_vouch_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// 10 is the only member
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
// 10 vouches for 20
|
||||
assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0));
|
||||
// 20 has a bid
|
||||
assert_eq!(<Bids<Test>>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]);
|
||||
// 10 is vouched
|
||||
assert_eq!(<Vouching<Test>>::get(10), Some(VouchingStatus::Vouching));
|
||||
// 20 doesn't want to be a member and can unbid themselves.
|
||||
assert_ok!(Society::unbid(Origin::signed(20), 0));
|
||||
// Everything is cleaned up
|
||||
assert_eq!(<Vouching<Test>>::get(10), None);
|
||||
assert_eq!(<Bids<Test>>::get(), vec![]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn head_cannot_be_removed() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// 10 is the only member and head
|
||||
assert_eq!(Society::members(), vec![10]);
|
||||
assert_eq!(Society::head(), Some(10));
|
||||
// 10 can still accumulate strikes
|
||||
assert_ok!(Society::bid(Origin::signed(20), 0));
|
||||
run_to_block(8);
|
||||
assert_eq!(Strikes::<Test>::get(10), 1);
|
||||
assert_ok!(Society::bid(Origin::signed(30), 0));
|
||||
run_to_block(16);
|
||||
assert_eq!(Strikes::<Test>::get(10), 2);
|
||||
// Awkwardly they can obtain more than MAX_STRIKES...
|
||||
assert_ok!(Society::bid(Origin::signed(40), 0));
|
||||
run_to_block(24);
|
||||
assert_eq!(Strikes::<Test>::get(10), 3);
|
||||
|
||||
// Replace the head
|
||||
assert_ok!(Society::bid(Origin::signed(50), 0));
|
||||
run_to_block(28);
|
||||
assert_ok!(Society::vote(Origin::signed(10), 50, true));
|
||||
assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around
|
||||
run_to_block(32);
|
||||
assert_eq!(Society::members(), vec![10, 50]);
|
||||
assert_eq!(Society::head(), Some(50));
|
||||
|
||||
// 10 can now be suspended for strikes
|
||||
assert_ok!(Society::bid(Origin::signed(60), 0));
|
||||
run_to_block(36);
|
||||
// The candidate is rejected, so voting approve will give a strike
|
||||
assert_ok!(Society::vote(Origin::signed(10), 60, true));
|
||||
run_to_block(40);
|
||||
assert_eq!(Strikes::<Test>::get(10), 0);
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(10), Some(()));
|
||||
assert_eq!(Society::members(), vec![50]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn challenges_work() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Add some members
|
||||
assert_ok!(Society::add_member(&20));
|
||||
assert_ok!(Society::add_member(&30));
|
||||
assert_ok!(Society::add_member(&40));
|
||||
// Check starting point
|
||||
assert_eq!(Society::members(), vec![10, 20, 30, 40]);
|
||||
assert_eq!(Society::defender(), None);
|
||||
// 20 will be challenged during the challenge rotation
|
||||
run_to_block(8);
|
||||
assert_eq!(Society::defender(), Some(20));
|
||||
// They can always free vote for themselves
|
||||
assert_ok!(Society::defender_vote(Origin::signed(20), true));
|
||||
// If no one else votes, nothing happens
|
||||
run_to_block(16);
|
||||
assert_eq!(Society::members(), vec![10, 20, 30, 40]);
|
||||
// New challenge period
|
||||
assert_eq!(Society::defender(), Some(20));
|
||||
// Non-member cannot challenge
|
||||
assert_noop!(Society::defender_vote(Origin::signed(1), true), Error::<Test, _>::NotMember);
|
||||
// 3 people say accept, 1 reject
|
||||
assert_ok!(Society::defender_vote(Origin::signed(10), true));
|
||||
assert_ok!(Society::defender_vote(Origin::signed(20), true));
|
||||
assert_ok!(Society::defender_vote(Origin::signed(30), true));
|
||||
assert_ok!(Society::defender_vote(Origin::signed(40), false));
|
||||
run_to_block(24);
|
||||
// 20 survives
|
||||
assert_eq!(Society::members(), vec![10, 20, 30, 40]);
|
||||
// One more time
|
||||
assert_eq!(Society::defender(), Some(20));
|
||||
// 2 people say accept, 2 reject
|
||||
assert_ok!(Society::defender_vote(Origin::signed(10), true));
|
||||
assert_ok!(Society::defender_vote(Origin::signed(20), true));
|
||||
assert_ok!(Society::defender_vote(Origin::signed(30), false));
|
||||
assert_ok!(Society::defender_vote(Origin::signed(40), false));
|
||||
run_to_block(32);
|
||||
// 20 is suspended
|
||||
assert_eq!(Society::members(), vec![10, 30, 40]);
|
||||
assert_eq!(Society::suspended_member(20), Some(()));
|
||||
// New defender is chosen
|
||||
assert_eq!(Society::defender(), Some(40));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_vote_slash_works() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Add some members
|
||||
assert_ok!(Society::add_member(&20));
|
||||
assert_ok!(Society::add_member(&30));
|
||||
assert_ok!(Society::add_member(&40));
|
||||
// Create some payouts
|
||||
Society::bump_payout(&10, 5, 100);
|
||||
Society::bump_payout(&20, 5, 100);
|
||||
Society::bump_payout(&30, 5, 100);
|
||||
Society::bump_payout(&40, 5, 100);
|
||||
// Check starting point
|
||||
assert_eq!(Society::members(), vec![10, 20, 30, 40]);
|
||||
assert_eq!(<Payouts<Test>>::get(10), vec![(5, 100)]);
|
||||
assert_eq!(<Payouts<Test>>::get(20), vec![(5, 100)]);
|
||||
assert_eq!(<Payouts<Test>>::get(30), vec![(5, 100)]);
|
||||
assert_eq!(<Payouts<Test>>::get(40), vec![(5, 100)]);
|
||||
// Create a new bid
|
||||
assert_ok!(Society::bid(Origin::signed(50), 1000));
|
||||
run_to_block(4);
|
||||
assert_ok!(Society::vote(Origin::signed(10), 50, false));
|
||||
assert_ok!(Society::vote(Origin::signed(20), 50, true));
|
||||
assert_ok!(Society::vote(Origin::signed(30), 50, false));
|
||||
assert_ok!(Society::vote(Origin::signed(40), 50, false));
|
||||
run_to_block(8);
|
||||
// Wrong voter gained a strike
|
||||
assert_eq!(<Strikes<Test>>::get(10), 0);
|
||||
assert_eq!(<Strikes<Test>>::get(20), 1);
|
||||
assert_eq!(<Strikes<Test>>::get(30), 0);
|
||||
assert_eq!(<Strikes<Test>>::get(40), 0);
|
||||
// Their payout is slashed, a random person is rewarded
|
||||
assert_eq!(<Payouts<Test>>::get(10), vec![(5, 100), (9,2)]);
|
||||
assert_eq!(<Payouts<Test>>::get(20), vec![(5, 98)]);
|
||||
assert_eq!(<Payouts<Test>>::get(30), vec![(5, 100)]);
|
||||
assert_eq!(<Payouts<Test>>::get(40), vec![(5, 100)]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_cannot_bid_twice() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Cannot bid twice
|
||||
assert_ok!(Society::bid(Origin::signed(20), 100));
|
||||
assert_noop!(Society::bid(Origin::signed(20), 100), Error::<Test, _>::AlreadyBid);
|
||||
// Cannot bid when vouched
|
||||
assert_ok!(Society::vouch(Origin::signed(10), 30, 100, 100));
|
||||
assert_noop!(Society::bid(Origin::signed(30), 100), Error::<Test, _>::AlreadyBid);
|
||||
// Cannot vouch when already bid
|
||||
assert_ok!(Society::add_member(&50));
|
||||
assert_noop!(Society::vouch(Origin::signed(50), 20, 100, 100), Error::<Test, _>::AlreadyBid);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vouching_handles_removed_member_with_bid() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Add a member
|
||||
assert_ok!(Society::add_member(&20));
|
||||
// Have that member vouch for a user
|
||||
assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100));
|
||||
// That user is now a bid and the member is vouching
|
||||
assert_eq!(<Bids<Test>>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]);
|
||||
assert_eq!(<Vouching<Test>>::get(20), Some(VouchingStatus::Vouching));
|
||||
// Suspend that member
|
||||
Society::suspend_member(&20);
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(20), Some(()));
|
||||
// Nothing changes yet
|
||||
assert_eq!(<Bids<Test>>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]);
|
||||
assert_eq!(<Vouching<Test>>::get(20), Some(VouchingStatus::Vouching));
|
||||
// Remove member
|
||||
assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false));
|
||||
// Bid is removed, vouching status is removed
|
||||
assert_eq!(<Bids<Test>>::get(), vec![]);
|
||||
assert_eq!(<Vouching<Test>>::get(20), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vouching_handles_removed_member_with_candidate() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Add a member
|
||||
assert_ok!(Society::add_member(&20));
|
||||
// Have that member vouch for a user
|
||||
assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100));
|
||||
// That user is now a bid and the member is vouching
|
||||
assert_eq!(<Bids<Test>>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]);
|
||||
assert_eq!(<Vouching<Test>>::get(20), Some(VouchingStatus::Vouching));
|
||||
// Make that bid a candidate
|
||||
run_to_block(4);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]);
|
||||
// Suspend that member
|
||||
Society::suspend_member(&20);
|
||||
assert_eq!(<SuspendedMembers<Test>>::get(20), Some(()));
|
||||
// Nothing changes yet
|
||||
assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]);
|
||||
assert_eq!(<Vouching<Test>>::get(20), Some(VouchingStatus::Vouching));
|
||||
// Remove member
|
||||
assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false));
|
||||
// Vouching status is removed, but candidate is still in the queue
|
||||
assert_eq!(<Vouching<Test>>::get(20), None);
|
||||
assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]);
|
||||
// Candidate wins
|
||||
assert_ok!(Society::vote(Origin::signed(10), 30, true));
|
||||
run_to_block(8);
|
||||
assert_eq!(Society::members(), vec![10, 30]);
|
||||
// Payout does not go to removed member
|
||||
assert_eq!(<Payouts<Test>>::get(20), vec![]);
|
||||
assert_eq!(<Payouts<Test>>::get(30), vec![(9, 1000)]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn votes_are_working() {
|
||||
EnvBuilder::new().execute(|| {
|
||||
// Users make bids of various amounts
|
||||
assert_ok!(Society::bid(Origin::signed(50), 500));
|
||||
assert_ok!(Society::bid(Origin::signed(40), 400));
|
||||
assert_ok!(Society::bid(Origin::signed(30), 300));
|
||||
// Rotate period
|
||||
run_to_block(4);
|
||||
// A member votes for these candidates to join the society
|
||||
assert_ok!(Society::vote(Origin::signed(10), 30, true));
|
||||
assert_ok!(Society::vote(Origin::signed(10), 40, true));
|
||||
// You cannot vote for a non-candidate
|
||||
assert_noop!(Society::vote(Origin::signed(10), 50, true), Error::<Test, _>::NotCandidate);
|
||||
// Votes are stored
|
||||
assert_eq!(<Votes<Test>>::get(30, 10), Some(Vote::Approve));
|
||||
assert_eq!(<Votes<Test>>::get(40, 10), Some(Vote::Approve));
|
||||
assert_eq!(<Votes<Test>>::get(50, 10), None);
|
||||
run_to_block(8);
|
||||
// Candidates become members after a period rotation
|
||||
assert_eq!(Society::members(), vec![10, 30, 40]);
|
||||
// Votes are cleaned up
|
||||
assert_eq!(<Votes<Test>>::get(30, 10), None);
|
||||
assert_eq!(<Votes<Test>>::get(40, 10), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_limits_work() {
|
||||
EnvBuilder::new().with_pot(100000).execute(|| {
|
||||
// Max bids is 1000, when extra bids come in, it pops the larger ones off the stack.
|
||||
// Try to put 1010 users into the bid pool
|
||||
for i in (100..1110).rev() {
|
||||
// Give them some funds
|
||||
let _ = Balances::make_free_balance_be(&(i as u128), 1000);
|
||||
assert_ok!(Society::bid(Origin::signed(i as u128), i));
|
||||
}
|
||||
let bids = <Bids<Test>>::get();
|
||||
// Length is 1000
|
||||
assert_eq!(bids.len(), 1000);
|
||||
// First bid is smallest number (100)
|
||||
assert_eq!(bids[0], create_bid(100, 100, BidKind::Deposit(25)));
|
||||
// Last bid is smallest number + 99 (1099)
|
||||
assert_eq!(bids[999], create_bid(1099, 1099, BidKind::Deposit(25)));
|
||||
// Rotate period
|
||||
run_to_block(4);
|
||||
// Max of 10 candidates
|
||||
assert_eq!(Society::candidates().len(), 10);
|
||||
// Fill up membership, max 100, we will do just 95
|
||||
for i in 2000..2095 {
|
||||
assert_ok!(Society::add_member(&(i as u128)));
|
||||
}
|
||||
// Remember there was 1 original member, so 96 total
|
||||
assert_eq!(Society::members().len(), 96);
|
||||
// Rotate period
|
||||
run_to_block(8);
|
||||
// Only of 4 candidates possible now
|
||||
assert_eq!(Society::candidates().len(), 4);
|
||||
// Fill up members with suspended candidates from the first rotation
|
||||
for i in 100..104 {
|
||||
assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), i, Judgement::Approve));
|
||||
}
|
||||
assert_eq!(Society::members().len(), 100);
|
||||
// Can't add any more members
|
||||
assert_noop!(Society::add_member(&98), Error::<Test, _>::MaxMembers);
|
||||
// However, a fringe scenario allows for in-progress candidates to increase the membership
|
||||
// pool, but it has no real after-effects.
|
||||
for i in Society::members().iter() {
|
||||
assert_ok!(Society::vote(Origin::signed(*i), 110, true));
|
||||
assert_ok!(Society::vote(Origin::signed(*i), 111, true));
|
||||
assert_ok!(Society::vote(Origin::signed(*i), 112, true));
|
||||
}
|
||||
// Rotate period
|
||||
run_to_block(12);
|
||||
// Members length is over 100, no problem...
|
||||
assert_eq!(Society::members().len(), 103);
|
||||
// No candidates because full
|
||||
assert_eq!(Society::candidates().len(), 0);
|
||||
// Increase member limit
|
||||
assert_ok!(Society::set_max_members(Origin::ROOT, 200));
|
||||
// Rotate period
|
||||
run_to_block(16);
|
||||
// Candidates are back!
|
||||
assert_eq!(Society::candidates().len(), 10);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user