1616
1717#[ cfg( test) ]
1818use std:: collections:: btree_map;
19- use std:: collections:: btree_map:: Entry ;
20- use std:: collections:: { btree_set, BTreeMap , BTreeSet } ;
19+ use std:: collections:: btree_map:: { BTreeMap , Entry } ;
20+ use std:: collections:: btree_set:: { self , BTreeSet } ;
2121use std:: mem;
2222
2323use ckey:: Address ;
2424use cstate:: { ActionData , ActionDataKeyBuilder , StateResult , TopLevelState , TopState , TopStateView } ;
2525use ctypes:: errors:: RuntimeError ;
2626use primitives:: H256 ;
27- use rlp:: { Decodable , Encodable , Rlp , RlpStream } ;
27+ use rlp:: { decode_list , Decodable , Encodable , Rlp , RlpStream } ;
2828
2929use super :: CUSTOM_ACTION_HANDLER_ID ;
3030
@@ -35,6 +35,8 @@ pub fn get_account_key(address: &Address) -> H256 {
3535lazy_static ! {
3636 pub static ref STAKEHOLDER_ADDRESSES_KEY : H256 =
3737 ActionDataKeyBuilder :: new( CUSTOM_ACTION_HANDLER_ID , 1 ) . append( & "StakeholderAddresses" ) . into_key( ) ;
38+ pub static ref CANDIDATES_KEY : H256 =
39+ ActionDataKeyBuilder :: new( CUSTOM_ACTION_HANDLER_ID , 1 ) . append( & "Candidates" ) . into_key( ) ;
3840}
3941
4042pub fn get_delegation_key ( address : & Address ) -> H256 {
@@ -46,6 +48,7 @@ pub fn get_intermediate_rewards_key() -> H256 {
4648}
4749
4850pub type StakeQuantity = u64 ;
51+ pub type Deposit = u64 ;
4952
5053pub struct StakeAccount < ' a > {
5154 pub address : & ' a Address ,
@@ -194,7 +197,6 @@ impl<'a> Delegation<'a> {
194197 . into ( ) )
195198 }
196199
197- #[ cfg( test) ]
198200 pub fn get_quantity ( & self , delegatee : & Address ) -> StakeQuantity {
199201 self . delegatees . get ( delegatee) . cloned ( ) . unwrap_or ( 0 )
200202 }
@@ -257,6 +259,62 @@ impl IntermediateRewards {
257259 }
258260}
259261
262+ pub struct Candidates ( BTreeMap < Address , Candidate > ) ;
263+ #[ derive( Clone , Debug , Eq , PartialEq , RlpEncodable , RlpDecodable ) ]
264+ pub struct Candidate {
265+ pub address : Address ,
266+ pub deposit : Deposit ,
267+ pub nomination_ends_at : u64 ,
268+ }
269+
270+ impl Candidates {
271+ pub fn load_from_state ( state : & TopLevelState ) -> StateResult < Candidates > {
272+ let key = * CANDIDATES_KEY ;
273+ let candidates = state. action_data ( & key) ?. map ( |data| decode_list :: < Candidate > ( & data) ) . unwrap_or_default ( ) ;
274+ let indexed = candidates. into_iter ( ) . map ( |c| ( c. address , c) ) . collect ( ) ;
275+ Ok ( Candidates ( indexed) )
276+ }
277+
278+ pub fn save_to_state ( & self , state : & mut TopLevelState ) -> StateResult < ( ) > {
279+ let key = * CANDIDATES_KEY ;
280+ if !self . 0 . is_empty ( ) {
281+ let encoded = encode_iter ( self . 0 . values ( ) ) ;
282+ state. update_action_data ( & key, encoded) ?;
283+ } else {
284+ state. remove_action_data ( & key) ;
285+ }
286+ Ok ( ( ) )
287+ }
288+
289+ pub fn get_candidate ( & self , account : & Address ) -> Option < & Candidate > {
290+ self . 0 . get ( & account)
291+ }
292+
293+ #[ cfg( test) ]
294+ pub fn len ( & self ) -> usize {
295+ self . 0 . len ( )
296+ }
297+
298+ pub fn add_deposit ( & mut self , address : & Address , quantity : Deposit , nomination_ends_at : u64 ) {
299+ let candidate = self . 0 . entry ( * address) . or_insert ( Candidate {
300+ address : * address,
301+ deposit : 0 ,
302+ nomination_ends_at : 0 ,
303+ } ) ;
304+ candidate. deposit += quantity;
305+ if candidate. nomination_ends_at < nomination_ends_at {
306+ candidate. nomination_ends_at = nomination_ends_at;
307+ }
308+ }
309+
310+ pub fn drain_expired_candidates ( & mut self , term_index : u64 ) -> Vec < Candidate > {
311+ let ( expired, retained) : ( Vec < _ > , Vec < _ > ) =
312+ self . 0 . values ( ) . cloned ( ) . partition ( |c| c. nomination_ends_at <= term_index) ;
313+ self . 0 = retained. into_iter ( ) . map ( |c| ( c. address , c) ) . collect ( ) ;
314+ expired
315+ }
316+ }
317+
260318fn decode_set < V > ( data : Option < & ActionData > ) -> BTreeSet < V >
261319where
262320 V : Ord + Decodable , {
@@ -354,6 +412,18 @@ where
354412 rlp. drain ( ) . into_vec ( )
355413}
356414
415+ fn encode_iter < ' a , V , I > ( iter : I ) -> Vec < u8 >
416+ where
417+ V : ' a + Encodable ,
418+ I : ExactSizeIterator < Item = & ' a V > + Clone , {
419+ let mut rlp = RlpStream :: new ( ) ;
420+ rlp. begin_list ( iter. clone ( ) . count ( ) ) ;
421+ for value in iter {
422+ rlp. append ( value) ;
423+ }
424+ rlp. drain ( ) . into_vec ( )
425+ }
426+
357427#[ cfg( test) ]
358428mod tests {
359429 use super :: * ;
@@ -719,4 +789,171 @@ mod tests {
719789 assert_eq ! ( BTreeMap :: new( ) , final_rewards. current) ;
720790 assert_eq ! ( current, final_rewards. previous) ;
721791 }
792+
793+ #[ test]
794+ fn candidates_deposit_add ( ) {
795+ let mut state = helpers:: get_temp_state ( ) ;
796+
797+ // Prepare
798+ let account = Address :: random ( ) ;
799+ let deposits = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] ;
800+
801+ for deposit in deposits. iter ( ) {
802+ let mut candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
803+ candidates. add_deposit ( & account, * deposit, 0 ) ;
804+ candidates. save_to_state ( & mut state) . unwrap ( ) ;
805+ }
806+
807+ // Assert
808+ let candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
809+ let candidate = candidates. get_candidate ( & account) ;
810+ assert_ne ! ( candidate, None ) ;
811+ assert_eq ! ( candidate. unwrap( ) . deposit, 55 ) ;
812+ }
813+
814+ #[ test]
815+ fn candidates_deposit_can_be_zero ( ) {
816+ let mut state = helpers:: get_temp_state ( ) ;
817+
818+ // Prepare
819+ let account = Address :: random ( ) ;
820+ let mut candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
821+ candidates. add_deposit ( & account, 0 , 10 ) ;
822+ candidates. save_to_state ( & mut state) . unwrap ( ) ;
823+
824+ // Assert
825+ let candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
826+ let candidate = candidates. get_candidate ( & account) ;
827+ assert_ne ! ( candidate, None ) ;
828+ assert_eq ! ( candidate. unwrap( ) . deposit, 0 ) ;
829+ assert_eq ! ( candidate. unwrap( ) . nomination_ends_at, 10 , "Can be a candidate with 0 deposit" ) ;
830+ }
831+
832+ #[ test]
833+ fn candidates_deposit_should_update_nomination_ends_at ( ) {
834+ let mut state = helpers:: get_temp_state ( ) ;
835+
836+ // Prepare
837+ let account = Address :: random ( ) ;
838+ let deposit_and_nomination_ends_at = [ ( 10 , 11 ) , ( 20 , 22 ) , ( 30 , 33 ) , ( 0 , 44 ) ] ;
839+
840+ for ( deposit, nomination_ends_at) in & deposit_and_nomination_ends_at {
841+ let mut candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
842+ candidates. add_deposit ( & account, * deposit, * nomination_ends_at) ;
843+ candidates. save_to_state ( & mut state) . unwrap ( ) ;
844+ }
845+
846+ // Assert
847+ let candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
848+ let candidate = candidates. get_candidate ( & account) ;
849+ assert_ne ! ( candidate, None ) ;
850+ assert_eq ! ( candidate. unwrap( ) . deposit, 60 ) ;
851+ assert_eq ! (
852+ candidate. unwrap( ) . nomination_ends_at,
853+ 44 ,
854+ "nomination_ends_at should be updated incrementally, and including zero deposit"
855+ ) ;
856+ }
857+
858+ #[ test]
859+ fn candidates_can_remove_expired_deposit ( ) {
860+ let mut state = helpers:: get_temp_state ( ) ;
861+
862+ // Prepare
863+ let candidates_prepared = [
864+ Candidate {
865+ address : Address :: from ( 0 ) ,
866+ deposit : 20 ,
867+ nomination_ends_at : 11 ,
868+ } ,
869+ Candidate {
870+ address : Address :: from ( 1 ) ,
871+ deposit : 30 ,
872+ nomination_ends_at : 22 ,
873+ } ,
874+ Candidate {
875+ address : Address :: from ( 2 ) ,
876+ deposit : 40 ,
877+ nomination_ends_at : 33 ,
878+ } ,
879+ Candidate {
880+ address : Address :: from ( 3 ) ,
881+ deposit : 50 ,
882+ nomination_ends_at : 44 ,
883+ } ,
884+ ] ;
885+
886+ for Candidate {
887+ address,
888+ deposit,
889+ nomination_ends_at,
890+ } in & candidates_prepared
891+ {
892+ let mut candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
893+ candidates. add_deposit ( & address, * deposit, * nomination_ends_at) ;
894+ candidates. save_to_state ( & mut state) . unwrap ( ) ;
895+ }
896+
897+ // Remove Expired
898+ let mut candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
899+ let expired = candidates. drain_expired_candidates ( 22 ) ;
900+ candidates. save_to_state ( & mut state) . unwrap ( ) ;
901+
902+ // Assert
903+ assert_eq ! ( expired[ ..] , candidates_prepared[ 0 ..=1 ] , ) ;
904+ let candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
905+ assert_eq ! ( candidates. len( ) , 2 ) ;
906+ assert_eq ! ( candidates. get_candidate( & candidates_prepared[ 2 ] . address) , Some ( & candidates_prepared[ 2 ] ) ) ;
907+ assert_eq ! ( candidates. get_candidate( & candidates_prepared[ 3 ] . address) , Some ( & candidates_prepared[ 3 ] ) ) ;
908+ }
909+
910+ #[ test]
911+ fn candidates_expire_all_cleanup_state ( ) {
912+ let mut state = helpers:: get_temp_state ( ) ;
913+
914+ // Prepare
915+ let candidates_prepared = [
916+ Candidate {
917+ address : Address :: from ( 0 ) ,
918+ deposit : 20 ,
919+ nomination_ends_at : 11 ,
920+ } ,
921+ Candidate {
922+ address : Address :: from ( 1 ) ,
923+ deposit : 30 ,
924+ nomination_ends_at : 22 ,
925+ } ,
926+ Candidate {
927+ address : Address :: from ( 2 ) ,
928+ deposit : 40 ,
929+ nomination_ends_at : 33 ,
930+ } ,
931+ Candidate {
932+ address : Address :: from ( 3 ) ,
933+ deposit : 50 ,
934+ nomination_ends_at : 44 ,
935+ } ,
936+ ] ;
937+
938+ for Candidate {
939+ address,
940+ deposit,
941+ nomination_ends_at,
942+ } in & candidates_prepared
943+ {
944+ let mut candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
945+ candidates. add_deposit ( & address, * deposit, * nomination_ends_at) ;
946+ candidates. save_to_state ( & mut state) . unwrap ( ) ;
947+ }
948+
949+ // Remove Expired
950+ let mut candidates = Candidates :: load_from_state ( & state) . unwrap ( ) ;
951+ let expired = candidates. drain_expired_candidates ( 99 ) ;
952+ candidates. save_to_state ( & mut state) . unwrap ( ) ;
953+
954+ // Assert
955+ assert_eq ! ( expired[ ..] , candidates_prepared[ 0 ..4 ] ) ;
956+ let result = state. action_data ( & * CANDIDATES_KEY ) . unwrap ( ) ;
957+ assert_eq ! ( result, None ) ;
958+ }
722959}
0 commit comments