diff --git a/substrate/demo/runtime/src/runtime/council.rs b/substrate/demo/runtime/src/runtime/council.rs index f9c30b3235196749414fb4447271af42be38c1a7..620405bdfdecb76add1b51a32b87bceb7c88a741 100644 --- a/substrate/demo/runtime/src/runtime/council.rs +++ b/substrate/demo/runtime/src/runtime/council.rs @@ -265,13 +265,7 @@ pub mod public { assert_eq!(index, vote_index()); if !storage::exists(&signed.to_keyed_vec(LAST_ACTIVE_OF)) { // not yet a voter - deduct bond. - let b = staking::balance(signed); - assert!(b >= voting_bond()); - // TODO: this is no good as it precludes active stakers. check that when we allow - // deductions of actively staked balances that things in the staking module don't - // break. -// assert!(staking::unlock_block(signed) == staking::LockStatus::Liquid); - staking::internal::set_balance(signed, b - voting_bond()); + staking::internal::reserve_balance(signed, voting_bond()); storage::put(VOTERS, &{ let mut v: Vec<AccountId> = storage::get_or_default(VOTERS); v.push(signed.clone()); @@ -288,16 +282,16 @@ pub mod public { /// /// May be called by anyone. Returns the voter deposit to `signed`. pub fn reap_inactive_voter(signed: &AccountId, signed_index: u32, who: &AccountId, who_index: u32, assumed_vote_index: VoteIndex) { - assert!(!presentation_active()); - assert!(voter_last_active(signed).is_some()); + assert!(!presentation_active(), "cannot reap during presentation period"); + assert!(voter_last_active(signed).is_some(), "reaper must be a voter"); let last_active = voter_last_active(who).expect("target for inactivity cleanup must be active"); - assert!(assumed_vote_index == vote_index()); - assert!(last_active < assumed_vote_index - inactivity_grace_period()); + assert!(assumed_vote_index == vote_index(), "vote index not current"); + assert!(last_active < assumed_vote_index - inactivity_grace_period(), "cannot reap during grace perid"); let voters = voters(); let signed_index = signed_index as usize; let who_index = who_index as usize; - assert!(signed_index < voters.len() && voters[signed_index] == *signed); - assert!(who_index < voters.len() && voters[who_index] == *who); + assert!(signed_index < voters.len() && voters[signed_index] == *signed, "bad reporter index"); + assert!(who_index < voters.len() && voters[who_index] == *who, "bad target index"); // will definitely kill one of signed or who now. @@ -315,41 +309,40 @@ pub mod public { voters ); if valid { - staking::internal::set_balance(signed, staking::balance(signed) + voting_bond()); + staking::internal::transfer_reserved_balance(who, signed, voting_bond()); + } else { + staking::internal::slash_reserved(signed, voting_bond()); } } /// Remove a voter. All votes are cancelled and the voter deposit is returned. pub fn retract_voter(signed: &AccountId, index: u32) { - assert!(!presentation_active()); - assert!(storage::exists(&signed.to_keyed_vec(LAST_ACTIVE_OF))); + assert!(!presentation_active(), "cannot retract when presenting"); + assert!(storage::exists(&signed.to_keyed_vec(LAST_ACTIVE_OF)), "cannot retract non-voter"); let voters = voters(); let index = index as usize; - assert!(index < voters.len() && voters[index] == *signed); + assert!(index < voters.len(), "retraction index invalid"); + assert!(voters[index] == *signed, "retraction index mismatch"); remove_voter(signed, index, voters); - staking::internal::set_balance(signed, staking::balance(signed) + voting_bond()); + staking::internal::unreserve_balance(signed, voting_bond()); } /// Submit oneself for candidacy. /// /// Account must have enough transferrable funds in it to pay the bond. pub fn submit_candidacy(signed: &AccountId, slot: u32) { - assert!(!is_a_candidate(signed)); - let b = staking::balance(signed); - let candidacy_bond = candidacy_bond(); - assert!(b >= candidacy_bond); - assert!(staking::unlock_block(signed) == staking::LockStatus::Liquid); + assert!(!is_a_candidate(signed), "duplicate candidate submission"); + assert!(staking::internal::deduct_unbonded(signed, candidacy_bond()), "candidate has not enough funds"); let slot = slot as usize; let count = storage::get_or_default::<u32>(CANDIDATE_COUNT) as usize; let candidates: Vec<AccountId> = storage::get_or_default(CANDIDATES); assert!( (slot == count && count == candidates.len()) || - (slot < candidates.len() && candidates[slot] == AccountId::default()) + (slot < candidates.len() && candidates[slot] == AccountId::default()), + "invalid candidate slot" ); - staking::internal::set_balance(signed, b - candidacy_bond); - let mut candidates = candidates; if slot == candidates.len() { candidates.push(signed.clone()); @@ -365,20 +358,19 @@ pub mod public { /// Only works if the block number >= current_vote().0 and < current_vote().0 + presentation_duration() /// `signed` should have at least pub fn present(signed: &AccountId, candidate: &AccountId, total: Balance, index: VoteIndex) { - assert_eq!(index, vote_index()); + assert_eq!(index, vote_index(), "index not current"); let (_, _, expiring): (BlockNumber, u32, Vec<AccountId>) = storage::get(NEXT_FINALISE) - .expect("present can only be called after a tally is started."); - let b = staking::balance(signed); + .expect("cannot present outside of presentation period"); let stakes: Vec<Balance> = storage::get_or_default(SNAPSHOTED_STAKES); let voters: Vec<AccountId> = storage::get_or_default(VOTERS); let bad_presentation_punishment = present_slash_per_voter() * voters.len() as Balance; - assert!(b >= bad_presentation_punishment); + assert!(staking::can_slash(signed, bad_presentation_punishment), "presenter must have sufficient slashable funds"); let mut leaderboard = leaderboard().expect("leaderboard must exist while present phase active"); - assert!(total > leaderboard[0].0); + assert!(total > leaderboard[0].0, "candidate not worthy of leaderboard"); if let Some(p) = active_council().iter().position(|&(ref c, _)| c == candidate) { - assert!(p < expiring.len()); + assert!(p < expiring.len(), "candidate must not form a duplicated member if elected"); } let (registered_since, candidate_index): (VoteIndex, u32) = @@ -400,7 +392,7 @@ pub mod public { leaderboard.sort_by_key(|&(t, _)| t); storage::put(LEADERBOARD, &leaderboard); } else { - staking::internal::set_balance(signed, b - bad_presentation_punishment); + staking::internal::slash(signed, bad_presentation_punishment); } } } @@ -500,6 +492,8 @@ fn finalise_tally() { let leaderboard: Vec<(Balance, AccountId)> = storage::take(LEADERBOARD).unwrap_or_default(); let new_expiry = system::block_number() + term_duration(); + println!("Finalising tally {} {}, {:?}", system::block_number(), coming, leaderboard); + // return bond to winners. let candidacy_bond = candidacy_bond(); for &(_, ref w) in leaderboard.iter() @@ -507,7 +501,9 @@ fn finalise_tally() { .take_while(|&&(b, _)| b != 0) .take(coming as usize) { - staking::internal::set_balance(w, staking::balance(w) + candidacy_bond); + println!("Refunding by {:?}, {:?}", candidacy_bond, staking::balance(w)); + staking::internal::refund(w, candidacy_bond); + println!("Refunded to {:?}", staking::balance(w)); } // set the new council. @@ -690,7 +686,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "invalid candidate slot")] fn candidate_submission_not_using_free_slot_should_panic() { let mut t = new_test_ext_with_candidate_holes(); @@ -701,7 +697,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "invalid candidate slot")] fn bad_candidate_slot_submission_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 1); @@ -711,7 +707,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "invalid candidate slot")] fn non_free_candidate_slot_submission_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 1); @@ -722,7 +718,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "duplicate candidate submission")] fn dupe_candidate_submission_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 1); @@ -732,6 +728,16 @@ mod tests { }); } + #[test] + #[should_panic(expected = "candidate has not enough funds")] + fn poor_candidate_submission_should_panic() { + with_externalities(&mut new_test_ext(), || { + with_env(|e| e.block_number = 1); + assert_eq!(candidates(), Vec::<AccountId>::new()); + public::submit_candidacy(&One, 0); + }); + } + #[test] fn voting_should_work() { with_externalities(&mut new_test_ext(), || { @@ -828,7 +834,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "retraction index mismatch")] fn invalid_retraction_index_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 1); @@ -840,7 +846,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "retraction index invalid")] fn overflow_retraction_index_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 1); @@ -851,7 +857,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "cannot retract non-voter")] fn non_voter_retraction_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 1); @@ -875,9 +881,9 @@ mod tests { with_env(|e| e.block_number = 6); assert!(presentation_active()); - public::present(&Dave, &Bob, 8, 0); - public::present(&Dave, &Eve, 38, 0); - assert_eq!(leaderboard(), Some(vec![(0, AccountId::default()), (0, AccountId::default()), (8, Bob.into()), (38, Eve.into())])); + public::present(&Dave, &Bob, 11, 0); + public::present(&Dave, &Eve, 41, 0); + assert_eq!(leaderboard(), Some(vec![(0, AccountId::default()), (0, AccountId::default()), (11, Bob.into()), (41, Eve.into())])); internal::end_block(); @@ -895,6 +901,8 @@ mod tests { #[test] fn double_presentations_should_be_punished() { with_externalities(&mut new_test_ext(), || { + assert!(staking::can_slash(&Dave, 10)); + with_env(|e| e.block_number = 4); public::submit_candidacy(&Bob, 0); public::submit_candidacy(&Eve, 1); @@ -903,9 +911,9 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 0); - public::present(&Dave, &Eve, 38, 0); - public::present(&Dave, &Eve, 38, 0); + public::present(&Dave, &Bob, 11, 0); + public::present(&Dave, &Eve, 41, 0); + public::present(&Dave, &Eve, 41, 0); internal::end_block(); assert_eq!(active_council(), vec![(Eve.to_raw_public(), 11), (Bob.into(), 11)]); @@ -922,7 +930,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 0); + public::present(&Dave, &Bob, 11, 0); internal::end_block(); with_env(|e| e.block_number = 8); @@ -931,7 +939,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 10); - public::present(&Dave, &Eve, 38, 1); + public::present(&Dave, &Eve, 41, 1); internal::end_block(); public::reap_inactive_voter( @@ -943,12 +951,12 @@ mod tests { assert_eq!(voters(), vec![Eve.to_raw_public()]); assert_eq!(approvals_of(&Bob).len(), 0); assert_eq!(staking::balance(&Bob), 17); - assert_eq!(staking::balance(&Eve), 50); + assert_eq!(staking::balance(&Eve), 53); }); } #[test] - #[should_panic] + #[should_panic(expected = "candidate must not form a duplicated member if elected")] fn presenting_for_double_election_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -957,7 +965,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 0); + public::present(&Dave, &Bob, 11, 0); internal::end_block(); with_env(|e| e.block_number = 8); @@ -966,7 +974,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 10); - public::present(&Dave, &Bob, 8, 1); + public::present(&Dave, &Bob, 11, 1); }); } @@ -979,7 +987,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 0); + public::present(&Dave, &Bob, 11, 0); internal::end_block(); with_env(|e| e.block_number = 8); @@ -988,7 +996,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 10); - public::present(&Dave, &Eve, 38, 1); + public::present(&Dave, &Eve, 41, 1); internal::end_block(); with_env(|e| e.block_number = 11); @@ -1003,12 +1011,12 @@ mod tests { assert_eq!(voters(), vec![Eve.to_raw_public()]); assert_eq!(approvals_of(&Bob).len(), 0); assert_eq!(staking::balance(&Bob), 17); - assert_eq!(staking::balance(&Eve), 50); + assert_eq!(staking::balance(&Eve), 53); }); } #[test] - #[should_panic] + #[should_panic(expected = "bad reporter index")] fn retracting_inactive_voter_with_bad_reporter_index_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -1038,7 +1046,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "bad target index")] fn retracting_inactive_voter_with_bad_target_index_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -1082,10 +1090,10 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 0); - public::present(&Dave, &Charlie, 18, 0); - public::present(&Dave, &Dave, 28, 0); - public::present(&Dave, &Eve, 38, 0); + public::present(&Dave, &Bob, 11, 0); + public::present(&Dave, &Charlie, 21, 0); + public::present(&Dave, &Dave, 31, 0); + public::present(&Dave, &Eve, 41, 0); internal::end_block(); with_env(|e| e.block_number = 8); @@ -1093,8 +1101,8 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 10); - public::present(&Dave, &Bob, 8, 1); - public::present(&Dave, &Charlie, 18, 1); + public::present(&Dave, &Bob, 11, 1); + public::present(&Dave, &Charlie, 21, 1); internal::end_block(); public::reap_inactive_voter( @@ -1110,7 +1118,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "reaper must be a voter")] fn attempting_to_retract_inactive_voter_by_nonvoter_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -1119,7 +1127,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 0); + public::present(&Dave, &Bob, 11, 0); internal::end_block(); with_env(|e| e.block_number = 8); @@ -1128,7 +1136,7 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 10); - public::present(&Dave, &Eve, 38, 1); + public::present(&Dave, &Eve, 41, 1); internal::end_block(); public::reap_inactive_voter( @@ -1140,7 +1148,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "candidate not worthy of leaderboard")] fn presenting_loser_should_panic() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -1157,11 +1165,11 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Alice, 57, 0); - public::present(&Dave, &Charlie, 18, 0); - public::present(&Dave, &Dave, 28, 0); - public::present(&Dave, &Eve, 38, 0); - public::present(&Dave, &Bob, 8, 0); + public::present(&Dave, &Alice, 60, 0); + public::present(&Dave, &Charlie, 21, 0); + public::present(&Dave, &Dave, 31, 0); + public::present(&Dave, &Eve, 41, 0); + public::present(&Dave, &Bob, 11, 0); }); } @@ -1182,23 +1190,23 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 0); - public::present(&Dave, &Alice, 57, 0); - public::present(&Dave, &Charlie, 18, 0); - public::present(&Dave, &Dave, 28, 0); - public::present(&Dave, &Eve, 38, 0); + public::present(&Dave, &Bob, 11, 0); + public::present(&Dave, &Alice, 60, 0); + public::present(&Dave, &Charlie, 21, 0); + public::present(&Dave, &Dave, 31, 0); + public::present(&Dave, &Eve, 41, 0); assert_eq!(leaderboard(), Some(vec![ - (18, Charlie.into()), - (28, Dave.into()), - (38, Eve.into()), - (57, Alice.to_raw_public()) + (21, Charlie.into()), + (31, Dave.into()), + (41, Eve.into()), + (60, Alice.to_raw_public()) ])); }); } #[test] - #[should_panic] + #[should_panic(expected = "cannot present outside of presentation period")] fn present_panics_outside_of_presentation_period() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -1208,7 +1216,7 @@ mod tests { } #[test] - #[should_panic] + #[should_panic(expected = "index not current")] fn present_panics_with_invalid_vote_index() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -1219,12 +1227,12 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Bob, 8, 1); + public::present(&Dave, &Bob, 11, 1); }); } #[test] - #[should_panic] + #[should_panic(expected = "presenter must have sufficient slashable funds")] fn present_panics_when_presenter_is_poor() { with_externalities(&mut new_test_ext(), || { with_env(|e| e.block_number = 4); @@ -1238,7 +1246,7 @@ mod tests { with_env(|e| e.block_number = 6); assert_eq!(staking::balance(&Alice), 1); - public::present(&Alice, &Alice, 17, 0); + public::present(&Alice, &Alice, 30, 0); }); } @@ -1283,21 +1291,21 @@ mod tests { with_env(|e| e.block_number = 6); assert!(presentation_active()); - public::present(&Dave, &Alice, 57, 0); + public::present(&Dave, &Alice, 60, 0); assert_eq!(leaderboard(), Some(vec![ (0, AccountId::default()), (0, AccountId::default()), (0, AccountId::default()), - (57, Alice.to_raw_public()) + (60, Alice.to_raw_public()) ])); - public::present(&Dave, &Charlie, 18, 0); - public::present(&Dave, &Dave, 28, 0); - public::present(&Dave, &Eve, 38, 0); + public::present(&Dave, &Charlie, 21, 0); + public::present(&Dave, &Dave, 31, 0); + public::present(&Dave, &Eve, 41, 0); assert_eq!(leaderboard(), Some(vec![ - (18, Charlie.into()), - (28, Dave.into()), - (38, Eve.into()), - (57, Alice.to_raw_public()) + (21, Charlie.into()), + (31, Dave.into()), + (41, Eve.into()), + (60, Alice.to_raw_public()) ])); internal::end_block(); @@ -1338,10 +1346,10 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 6); - public::present(&Dave, &Alice, 57, 0); - public::present(&Dave, &Charlie, 18, 0); - public::present(&Dave, &Dave, 28, 0); - public::present(&Dave, &Eve, 38, 0); + public::present(&Dave, &Alice, 60, 0); + public::present(&Dave, &Charlie, 21, 0); + public::present(&Dave, &Dave, 31, 0); + public::present(&Dave, &Eve, 41, 0); internal::end_block(); with_env(|e| e.block_number = 8); @@ -1350,8 +1358,8 @@ mod tests { internal::end_block(); with_env(|e| e.block_number = 10); - public::present(&Dave, &Charlie, 75, 1); - public::present(&Dave, &Dave, 28, 1); + public::present(&Dave, &Charlie, 81, 1); + public::present(&Dave, &Dave, 31, 1); internal::end_block(); assert!(!presentation_active()); diff --git a/substrate/demo/runtime/src/runtime/democracy.rs b/substrate/demo/runtime/src/runtime/democracy.rs index 044d7c0633959a14d4fd38af3e9b026611caaafa..41c635ddee4c382e199114e807e45c976fc94d03 100644 --- a/substrate/demo/runtime/src/runtime/democracy.rs +++ b/substrate/demo/runtime/src/runtime/democracy.rs @@ -178,10 +178,7 @@ pub mod public { /// Propose a sensitive action to be taken. pub fn propose(signed: &AccountId, proposal: &Proposal, value: Balance) { assert!(value >= minimum_deposit()); - let b = staking::balance(signed); - assert!(b >= value); - - staking::internal::set_balance(signed, b - value); + assert!(staking::internal::deduct_unbonded(signed, value)); let index: PropIndex = storage::get_or_default(PUBLIC_PROP_COUNT); storage::put(PUBLIC_PROP_COUNT, &(index + 1)); @@ -194,14 +191,12 @@ pub mod public { /// Propose a sensitive action to be taken. pub fn second(signed: &AccountId, proposal: PropIndex) { - let b = staking::balance(signed); let key = proposal.to_keyed_vec(DEPOSIT_OF); let mut deposit: (Balance, Vec<AccountId>) = storage::get(&key).expect("can only second an existing proposal"); - assert!(b >= deposit.0); - deposit.1.push(*signed); + assert!(staking::internal::deduct_unbonded(signed, deposit.0)); - staking::internal::set_balance(signed, b - deposit.0); + deposit.1.push(*signed); storage::put(&key, &deposit); } @@ -258,7 +253,7 @@ pub mod internal { .expect("depositors always exist for current proposals"); // refund depositors for d in &depositors { - staking::internal::set_balance(d, staking::balance(d) + deposit); + staking::internal::refund(d, deposit); } storage::put(PUBLIC_PROPS, &public_props); inject_referendum(now + voting_period(), proposal, VoteThreshold::SuperMajorityApprove); diff --git a/substrate/demo/runtime/src/runtime/staking.rs b/substrate/demo/runtime/src/runtime/staking.rs index 29b6ab793b296febd103aacb4d9620cf28fdb898..fea37d938359a6525135fac95b362044771eba8d 100644 --- a/substrate/demo/runtime/src/runtime/staking.rs +++ b/substrate/demo/runtime/src/runtime/staking.rs @@ -17,6 +17,7 @@ //! Staking manager: Handles balances and periodically determines the best set of validators. use rstd::prelude::*; +use rstd::cmp; use rstd::cell::RefCell; use rstd::collections::btree_map::{BTreeMap, Entry}; use runtime_io::{print, blake2_256}; @@ -42,6 +43,7 @@ pub const INTENTION_AT: &[u8] = b"sta:wil:"; pub const INTENTION_COUNT: &[u8] = b"sta:wil:len"; pub const BALANCE_OF: &[u8] = b"sta:bal:"; +pub const RESERVED_BALANCE_OF: &[u8] = b"sta:lbo:"; pub const BONDAGE_OF: &[u8] = b"sta:bon:"; pub const CODE_OF: &[u8] = b"sta:cod:"; pub const STORAGE_OF: &[u8] = b"sta:sto:"; @@ -84,9 +86,26 @@ pub fn last_era_length_change() -> BlockNumber { /// The balance of a given account. pub fn balance(who: &AccountId) -> Balance { + free_balance(who) + reserved_balance(who) +} + +/// The balance of a given account. +pub fn free_balance(who: &AccountId) -> Balance { storage::get_or_default(&who.to_keyed_vec(BALANCE_OF)) } +/// The amount of the balance of a given account that is exterally reserved; this can still get +/// slashed, but gets slashed last of all. +pub fn reserved_balance(who: &AccountId) -> Balance { + storage::get_or_default(&who.to_keyed_vec(RESERVED_BALANCE_OF)) +} + +/// Some result as `slash(who, value)` (but without the side-effects) asuming there are no +/// balance changes in the meantime. +pub fn can_slash(who: &AccountId, value: Balance) -> bool { + balance(who) >= value +} + /// The block at which the `who`'s funds become entirely liquid. pub fn bondage(who: &AccountId) -> Bondage { storage::get_or_default(&who.to_keyed_vec(BONDAGE_OF)) @@ -388,7 +407,7 @@ pub mod internal { /// Set the balance of an account. /// Needless to say, this is super low-level and accordingly dangerous. Ensure any modules that /// use it are auditted to the hilt. - pub fn set_balance(who: &AccountId, value: Balance) { + pub fn set_free_balance(who: &AccountId, value: Balance) { storage::put(&who.to_keyed_vec(BALANCE_OF), &value); } @@ -399,6 +418,72 @@ pub mod internal { new_era(); } } + + /// Deduct from an unbonded balance. true if it happened. + pub fn deduct_unbonded(who: &AccountId, value: Balance) -> bool { + if let LockStatus::Liquid = unlock_block(who) { + let b = free_balance(who); + if b >= value { + set_free_balance(who, b - value); + return true; + } + } + false + } + + /// Refund some balance. + pub fn refund(who: &AccountId, value: Balance) { + set_free_balance(who, free_balance(who) + value) + } + + /// Will slash any balance, but prefer free over reserved. + pub fn slash(who: &AccountId, value: Balance) -> bool { + let free_balance = free_balance(who); + let free_slash = cmp::min(free_balance, value); + set_free_balance(who, free_balance - free_slash); + if free_slash < value { + slash_reserved(who, value - free_slash) + } else { + true + } + } + + /// Moves `value` from balance to reserved balance. + pub fn reserve_balance(who: &AccountId, value: Balance) { + let b = free_balance(who); + assert!(b >= value); + set_free_balance(who, b - value); + set_reserved_balance(who, reserved_balance(who) + value); + } + + /// Moves `value` from reserved balance to balance. + pub fn unreserve_balance(who: &AccountId, value: Balance) { + let b = reserved_balance(who); + let value = cmp::min(b, value); + set_reserved_balance(who, b - value); + set_free_balance(who, free_balance(who) + value); + } + + /// Moves `value` from reserved balance to balance. + pub fn slash_reserved(who: &AccountId, value: Balance) -> bool { + let b = reserved_balance(who); + let slash = cmp::min(b, value); + set_reserved_balance(who, b - slash); + value == slash + } + + /// Moves `value` from reserved balance to balance. + pub fn transfer_reserved_balance(slashed: &AccountId, beneficiary: &AccountId, value: Balance) { + let b = reserved_balance(slashed); + let value = cmp::min(b, value); + set_reserved_balance(slashed, b - value); + set_free_balance(beneficiary, free_balance(beneficiary) + value); + } +} + +/// Set the reserved portion of `who`'s balance. +fn set_reserved_balance(who: &AccountId, value: Balance) { + storage::put(&who.to_keyed_vec(RESERVED_BALANCE_OF), &value); } /// The era has changed - enact new staking set.