Skip to content
Snippets Groups Projects
Commit baa0cfcc authored by Gav's avatar Gav
Browse files

Tests and docs, plus some fixes.

parent cf7bd8a6
Branches
No related merge requests found
......@@ -151,4 +151,20 @@ mod tests {
false
}));
}
#[test]
fn read_storage_works() {
let mut t = TestExternalities { storage: map![
b":test".to_vec() => b"\x0b\0\0\0Hello world".to_vec()
], };
with_externalities(&mut t, || {
let mut v = [0u8; 4];
assert!(read_storage(b":test", &mut v[..], 0) >= 4);
assert_eq!(v, [11u8, 0, 0, 0]);
let mut w = [0u8; 11];
assert!(read_storage(b":test", &mut w[..], 4) >= 11);
assert_eq!(&w, b"Hello world");
});
}
}
......@@ -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);
});
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment