mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 19:11:04 +00:00
Tests and docs, plus some fixes.
This commit is contained in:
@@ -96,3 +96,16 @@ impl Slicable for Vec<u8> {
|
||||
u32::from_slice(&data[0..4]).map(|i| (i + 4) as usize)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn vec_is_slicable() {
|
||||
let v = b"Hello world".to_vec();
|
||||
v.as_slice_then(|ref slice|
|
||||
assert_eq!(slice, &b"\x0b\0\0\0Hello world")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +34,19 @@ use runtime::{staking, system, session};
|
||||
|
||||
// TRANSACTION API
|
||||
|
||||
pub fn propose(transactor: &AccountID, proposal: &Proposal) {
|
||||
/// Propose a sensitive action to be taken. Any action that is enactable by `Proposal` is valid.
|
||||
/// Proposal is by the `transactor` and will automatically count as an approval. Transactor must
|
||||
/// be a current validator. It is illegal to propose when there is already a proposal in effect.
|
||||
pub fn propose(validator: &AccountID, proposal: &Proposal) {
|
||||
if Proposal::lookup(b"gov:pro").is_some() {
|
||||
panic!("there may only be one proposal per era.");
|
||||
}
|
||||
proposal.store(b"gov:pro");
|
||||
approve(transactor, staking::current_era());
|
||||
approve(validator, staking::current_era());
|
||||
}
|
||||
|
||||
/// Approve the current era's proposal. Transactor must be a validator. This may not be done more
|
||||
/// than once for any validator in an era.
|
||||
pub fn approve(validator: &AccountID, era_index: BlockNumber) {
|
||||
if era_index != staking::current_era() {
|
||||
panic!("approval vote applied on non-current era.")
|
||||
@@ -59,18 +64,25 @@ pub fn approve(validator: &AccountID, era_index: BlockNumber) {
|
||||
true.store(&key);
|
||||
}
|
||||
|
||||
/// Set the proportion of validators that must approve for a proposal to be enacted at the end of
|
||||
/// its era. The value, `ppm`, is measured as a fraction of 1000 rounded down to the nearest whole
|
||||
/// validator. `1000` would require the approval of all validators; `667` would require two-thirds
|
||||
/// (or there abouts) of validators.
|
||||
pub fn set_approval_ppm_required(ppm: u32) {
|
||||
ppm.store(b"gov:apr");
|
||||
}
|
||||
|
||||
// INSPECTION API
|
||||
|
||||
/// The proportion of validators required for a propsal to be approved measured as the number out
|
||||
/// of 1000.
|
||||
pub fn approval_ppm_required() -> u32 {
|
||||
Storable::lookup(b"gov:apr").unwrap_or(1000)
|
||||
}
|
||||
|
||||
/// The number of concrete validator approvals required for a proposal to pass.
|
||||
pub fn approvals_required() -> u32 {
|
||||
approval_ppm_required() * staking::validator_count() as u32 / 1000
|
||||
approval_ppm_required() * session::validator_count() as u32 / 1000
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
@@ -80,18 +92,270 @@ pub fn end_of_an_era() {
|
||||
// tally up votes for the current proposal, if any. enact if there are sufficient approvals.
|
||||
if let Some(proposal) = Proposal::lookup(b"gov:pro") {
|
||||
kill(b"gov:pro");
|
||||
let approved: u32 = session::validators().into_iter()
|
||||
.map(|v| bool::take(&v.to_keyed_vec(b"gov:app:")).map(|_| 1).unwrap_or(0))
|
||||
.sum();
|
||||
if approved >= approvals_required() {
|
||||
let approvals_required = approvals_required();
|
||||
let approved = session::validators().into_iter()
|
||||
.filter_map(|v| bool::take(&v.to_keyed_vec(b"gov:app:")))
|
||||
.take(approvals_required as usize)
|
||||
.count() as u32;
|
||||
if approved == approvals_required {
|
||||
proposal.enact();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// TODO
|
||||
use super::*;
|
||||
use runtime_support::{with_externalities, twox_128};
|
||||
use keyedvec::KeyedVec;
|
||||
use joiner::Joiner;
|
||||
use testing::{one, two, TestExternalities};
|
||||
use primitives::AccountID;
|
||||
use proposal::InternalFunction;
|
||||
use runtime::{staking, session};
|
||||
use environment::with_env;
|
||||
|
||||
fn new_test_ext() -> TestExternalities {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
|
||||
TestExternalities { storage: map![
|
||||
twox_128(b"gov:apr").to_vec() => vec![].join(&667u32),
|
||||
twox_128(b"ses:len").to_vec() => vec![].join(&1u64),
|
||||
twox_128(b"ses:val:len").to_vec() => vec![].join(&3u32),
|
||||
twox_128(&0u32.to_keyed_vec(b"ses:val:")).to_vec() => one.to_vec(),
|
||||
twox_128(&1u32.to_keyed_vec(b"ses:val:")).to_vec() => two.to_vec(),
|
||||
twox_128(&2u32.to_keyed_vec(b"ses:val:")).to_vec() => three.to_vec(),
|
||||
twox_128(b"sta:wil:len").to_vec() => vec![].join(&3u32),
|
||||
twox_128(&0u32.to_keyed_vec(b"sta:wil:")).to_vec() => one.to_vec(),
|
||||
twox_128(&1u32.to_keyed_vec(b"sta:wil:")).to_vec() => two.to_vec(),
|
||||
twox_128(&2u32.to_keyed_vec(b"sta:wil:")).to_vec() => three.to_vec(),
|
||||
twox_128(b"sta:spe").to_vec() => vec![].join(&1u64),
|
||||
twox_128(b"sta:vac").to_vec() => vec![].join(&3u64),
|
||||
twox_128(b"sta:era").to_vec() => vec![].join(&1u64)
|
||||
], }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn majority_voting_should_work() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Approve it. Era length changes.
|
||||
with_env(|e| e.block_number = 1);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
approve(&two, 1);
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn majority_voting_should_work_after_unsuccessful_previous() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Fail it.
|
||||
with_env(|e| e.block_number = 1);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 1);
|
||||
|
||||
// Block 2: Make proposal. Approve it. It should change era length.
|
||||
with_env(|e| e.block_number = 2);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
approve(&two, 2);
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minority_voting_should_not_succeed() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Will have only 1 vote. No change.
|
||||
with_env(|e| e.block_number = 1);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn old_voting_should_be_illegal() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Will have only 1 vote. No change.
|
||||
with_env(|e| e.block_number = 1);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
approve(&two, 0);
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn double_voting_should_be_illegal() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Will have only 1 vote. No change.
|
||||
with_env(|e| e.block_number = 1);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
approve(&two, 1);
|
||||
approve(&two, 1);
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn over_proposing_should_be_illegal() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Will have only 1 vote. No change.
|
||||
with_env(|e| e.block_number = 1);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
propose(&two, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn approving_without_proposal_should_be_illegal() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Will have only 1 vote. No change.
|
||||
with_env(|e| e.block_number = 1);
|
||||
approve(&two, 1);
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn non_validator_approving_should_be_illegal() {
|
||||
let one = one();
|
||||
let two = two();
|
||||
let three = [3u8; 32];
|
||||
let four = [4u8; 32];
|
||||
let mut t = new_test_ext();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
assert_eq!(staking::era_length(), 1u64);
|
||||
assert_eq!(staking::current_era(), 1u64);
|
||||
assert_eq!(session::validator_count(), 3usize);
|
||||
assert_eq!(session::validators(), vec![one.clone(), two.clone(), three.clone()]);
|
||||
assert!(!session::validators().into_iter().position(|v| &v == &one).is_none());
|
||||
|
||||
// Block 1: Make proposal. Will have only 1 vote. No change.
|
||||
with_env(|e| e.block_number = 1);
|
||||
propose(&one, &Proposal {
|
||||
function: InternalFunction::StakingSetSessionsPerEra,
|
||||
input_data: vec![].join(&2u64),
|
||||
});
|
||||
approve(&four, 1);
|
||||
staking::check_new_era();
|
||||
assert_eq!(staking::era_length(), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,11 @@ pub fn length() -> BlockNumber {
|
||||
Storable::lookup_default(b"ses:len")
|
||||
}
|
||||
|
||||
/// The number of validators currently.
|
||||
pub fn validator_count() -> usize {
|
||||
ValidatorStorageVec::count() as usize
|
||||
}
|
||||
|
||||
/// The current era index.
|
||||
pub fn current_index() -> BlockNumber {
|
||||
Storable::lookup_default(b"ses:ind")
|
||||
|
||||
@@ -56,7 +56,7 @@ pub fn kill(key: &[u8]) {
|
||||
impl<T: Sized + Slicable> Storable for T {
|
||||
fn lookup(key: &[u8]) -> Option<Self> {
|
||||
Slicable::set_as_slice(&|out, offset|
|
||||
runtime_support::read_storage(&twox_128(key)[..], out, offset) == out.len()
|
||||
runtime_support::read_storage(&twox_128(key)[..], out, offset) >= out.len()
|
||||
)
|
||||
}
|
||||
fn store(&self, key: &[u8]) {
|
||||
@@ -104,3 +104,89 @@ pub trait StorageVec {
|
||||
Storable::lookup_default(&b"len".to_keyed_vec(Self::PREFIX))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use runtime_support::with_externalities;
|
||||
use testing::{TestExternalities, HexDisplay};
|
||||
use runtime_support::{storage, twox_128};
|
||||
|
||||
#[test]
|
||||
fn integers_can_be_stored() {
|
||||
let mut t = TestExternalities { storage: HashMap::new(), };
|
||||
with_externalities(&mut t, || {
|
||||
let x = 69u32;
|
||||
x.store(b":test");
|
||||
let y = u32::lookup(b":test").unwrap();
|
||||
assert_eq!(x, y);
|
||||
});
|
||||
with_externalities(&mut t, || {
|
||||
let x = 69426942i64;
|
||||
x.store(b":test");
|
||||
let y = i64::lookup(b":test").unwrap();
|
||||
assert_eq!(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bools_can_be_stored() {
|
||||
let mut t = TestExternalities { storage: HashMap::new(), };
|
||||
with_externalities(&mut t, || {
|
||||
let x = true;
|
||||
x.store(b":test");
|
||||
let y = bool::lookup(b":test").unwrap();
|
||||
assert_eq!(x, y);
|
||||
});
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
let x = false;
|
||||
x.store(b":test");
|
||||
let y = bool::lookup(b":test").unwrap();
|
||||
assert_eq!(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vecs_can_be_retrieved() {
|
||||
let mut t = TestExternalities { storage: HashMap::new(), };
|
||||
with_externalities(&mut t, || {
|
||||
runtime_support::set_storage(&twox_128(b":test"), b"\x0b\0\0\0Hello world");
|
||||
let x = b"Hello world".to_vec();
|
||||
println!("Hex: {}", HexDisplay::from(&storage(&twox_128(b":test"))));
|
||||
let y = <Vec<u8>>::lookup(b":test").unwrap();
|
||||
assert_eq!(x, y);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vecs_can_be_stored() {
|
||||
let mut t = TestExternalities { storage: HashMap::new(), };
|
||||
let x = b"Hello world".to_vec();
|
||||
|
||||
with_externalities(&mut t, || {
|
||||
x.store(b":test");
|
||||
});
|
||||
|
||||
println!("Ext is {:?}", t);
|
||||
with_externalities(&mut t, || {
|
||||
println!("Hex: {}", HexDisplay::from(&storage(&twox_128(b":test"))));
|
||||
let y = <Vec<u8>>::lookup(b":test").unwrap();
|
||||
assert_eq!(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proposals_can_be_stored() {
|
||||
use proposal::{Proposal, InternalFunction};
|
||||
let mut t = TestExternalities { storage: HashMap::new(), };
|
||||
with_externalities(&mut t, || {
|
||||
let x = Proposal { function: InternalFunction::StakingSetSessionsPerEra, input_data: b"Hello world".to_vec() };
|
||||
x.store(b":test");
|
||||
let y = Proposal::lookup(b":test").unwrap();
|
||||
assert_eq!(x, y);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user