1use serde::{Deserialize, Serialize};
6use serde_big_array::BigArray;
7
8use crate::{Balance, BlockHeight};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12#[repr(u8)]
13pub enum ValidatorStatus {
14 Active = 0,
16 Inactive = 1,
18 Jailed = 2,
20 Unbonding = 3,
22}
23
24impl ValidatorStatus {
25 pub fn from_byte(b: u8) -> Option<Self> {
27 match b {
28 0 => Some(ValidatorStatus::Active),
29 1 => Some(ValidatorStatus::Inactive),
30 2 => Some(ValidatorStatus::Jailed),
31 3 => Some(ValidatorStatus::Unbonding),
32 _ => None,
33 }
34 }
35
36 pub fn can_validate(&self) -> bool {
38 matches!(self, ValidatorStatus::Active)
39 }
40}
41
42impl Default for ValidatorStatus {
43 fn default() -> Self {
44 ValidatorStatus::Inactive
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct ValidatorInfo {
51 pub pubkey: [u8; 32],
53 pub stake: Balance,
55 pub total_delegated: Balance,
57 pub commission_bps: u16,
59 pub status: ValidatorStatus,
61 pub joined_at: BlockHeight,
63 pub jailed_until: BlockHeight,
65 pub slash_count: u32,
67 pub pending_rewards: Balance,
69 pub metadata: Vec<u8>,
71}
72
73impl ValidatorInfo {
74 pub fn new(pubkey: [u8; 32], stake: Balance, commission_bps: u16, joined_at: BlockHeight) -> Self {
76 Self {
77 pubkey,
78 stake,
79 total_delegated: 0,
80 commission_bps: commission_bps.min(10000), status: ValidatorStatus::Active,
82 joined_at,
83 jailed_until: 0,
84 slash_count: 0,
85 pending_rewards: 0,
86 metadata: Vec::new(),
87 }
88 }
89
90 pub fn total_stake(&self) -> Balance {
92 self.stake.saturating_add(self.total_delegated)
93 }
94
95 pub fn add_delegation(&mut self, amount: Balance) {
97 self.total_delegated = self.total_delegated.saturating_add(amount);
98 }
99
100 pub fn remove_delegation(&mut self, amount: Balance) {
102 self.total_delegated = self.total_delegated.saturating_sub(amount);
103 }
104
105 pub fn is_jailed(&self) -> bool {
107 self.status == ValidatorStatus::Jailed
108 }
109
110 pub fn can_unjail(&self, current_height: BlockHeight) -> bool {
112 self.is_jailed() && current_height >= self.jailed_until
113 }
114
115 pub fn apply_slash(&mut self, penalty_bps: u16) {
117 let penalty = (self.stake * penalty_bps as u128) / 10000;
118 self.stake = self.stake.saturating_sub(penalty);
119 self.slash_count += 1;
120 }
121
122 pub fn jail(&mut self, until_height: BlockHeight) {
124 self.status = ValidatorStatus::Jailed;
125 self.jailed_until = until_height;
126 }
127
128 pub fn unjail(&mut self) {
130 self.status = ValidatorStatus::Active;
131 self.jailed_until = 0;
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
137#[repr(u8)]
138pub enum StakingOperation {
139 CreateValidator = 0,
141 AddStake = 1,
143 Unstake = 2,
145 UpdateValidator = 3,
147 Unjail = 4,
149 ClaimRewards = 5,
151 Delegate = 6,
154 Undelegate = 7,
156 ClaimDelegationRewards = 8,
158 WithdrawUnbonded = 9,
160 SubmitEvidence = 10,
163}
164
165impl StakingOperation {
166 pub fn from_byte(b: u8) -> Option<Self> {
168 match b {
169 0 => Some(StakingOperation::CreateValidator),
170 1 => Some(StakingOperation::AddStake),
171 2 => Some(StakingOperation::Unstake),
172 3 => Some(StakingOperation::UpdateValidator),
173 4 => Some(StakingOperation::Unjail),
174 5 => Some(StakingOperation::ClaimRewards),
175 6 => Some(StakingOperation::Delegate),
176 7 => Some(StakingOperation::Undelegate),
177 8 => Some(StakingOperation::ClaimDelegationRewards),
178 9 => Some(StakingOperation::WithdrawUnbonded),
179 10 => Some(StakingOperation::SubmitEvidence),
180 _ => None,
181 }
182 }
183
184 pub fn requires_validator(&self) -> bool {
186 matches!(
187 self,
188 StakingOperation::AddStake
189 | StakingOperation::Unstake
190 | StakingOperation::UpdateValidator
191 | StakingOperation::Unjail
192 | StakingOperation::ClaimRewards
193 )
194 }
195
196 pub fn is_delegation(&self) -> bool {
198 matches!(
199 self,
200 StakingOperation::Delegate
201 | StakingOperation::Undelegate
202 | StakingOperation::ClaimDelegationRewards
203 | StakingOperation::WithdrawUnbonded
204 )
205 }
206
207 pub fn is_slashing(&self) -> bool {
209 matches!(self, StakingOperation::SubmitEvidence)
210 }
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
215pub struct StakingTxData {
216 pub operation: StakingOperation,
218 pub data: Vec<u8>,
220}
221
222impl StakingTxData {
223 pub fn new(operation: StakingOperation, data: Vec<u8>) -> Self {
225 Self { operation, data }
226 }
227}
228
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
231pub struct CreateValidatorData {
232 pub stake: Balance,
234 pub commission_bps: u16,
236 pub metadata: Vec<u8>,
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242pub struct AddStakeData {
243 pub amount: Balance,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249pub struct UnstakeData {
250 pub amount: Balance,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
256pub struct UpdateValidatorData {
257 pub commission_bps: Option<u16>,
259 pub metadata: Option<Vec<u8>>,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
269pub struct DelegationInfo {
270 pub delegator: [u8; 32],
272 pub validator_pubkey: [u8; 32],
274 pub amount: Balance,
276 pub pending_rewards: Balance,
278 pub delegated_at: BlockHeight,
280}
281
282impl DelegationInfo {
283 pub fn new(delegator: [u8; 32], validator_pubkey: [u8; 32], amount: Balance, delegated_at: BlockHeight) -> Self {
285 Self {
286 delegator,
287 validator_pubkey,
288 amount,
289 pending_rewards: 0,
290 delegated_at,
291 }
292 }
293
294 pub fn add_stake(&mut self, amount: Balance) {
296 self.amount = self.amount.saturating_add(amount);
297 }
298
299 pub fn remove_stake(&mut self, amount: Balance) -> Balance {
301 let removed = amount.min(self.amount);
302 self.amount = self.amount.saturating_sub(removed);
303 removed
304 }
305
306 pub fn add_rewards(&mut self, rewards: Balance) {
308 self.pending_rewards = self.pending_rewards.saturating_add(rewards);
309 }
310
311 pub fn claim_rewards(&mut self) -> Balance {
313 let rewards = self.pending_rewards;
314 self.pending_rewards = 0;
315 rewards
316 }
317
318 pub fn apply_slash(&mut self, penalty_bps: u16) {
320 let penalty = (self.amount * penalty_bps as u128) / 10000;
321 self.amount = self.amount.saturating_sub(penalty);
322 }
323}
324
325#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
327pub struct UnbondingDelegation {
328 pub delegator: [u8; 32],
330 pub validator_pubkey: [u8; 32],
332 pub amount: Balance,
334 pub completion_height: BlockHeight,
336}
337
338impl UnbondingDelegation {
339 pub fn new(
341 delegator: [u8; 32],
342 validator_pubkey: [u8; 32],
343 amount: Balance,
344 completion_height: BlockHeight,
345 ) -> Self {
346 Self {
347 delegator,
348 validator_pubkey,
349 amount,
350 completion_height,
351 }
352 }
353
354 pub fn is_complete(&self, current_height: BlockHeight) -> bool {
356 current_height >= self.completion_height
357 }
358}
359
360#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
362pub struct DelegateData {
363 pub validator_pubkey: [u8; 32],
365 pub amount: Balance,
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
371pub struct UndelegateData {
372 pub validator_pubkey: [u8; 32],
374 pub amount: Balance,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
380pub struct ClaimDelegationRewardsData {
381 pub validator_pubkey: [u8; 32],
383}
384
385#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
387pub struct WithdrawUnbondedData {
388 pub validator_pubkey: Option<[u8; 32]>,
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
398#[repr(u8)]
399pub enum EvidenceType {
400 DoubleSign = 0,
402 Downtime = 1,
404}
405
406impl EvidenceType {
407 pub fn from_byte(b: u8) -> Option<Self> {
409 match b {
410 0 => Some(EvidenceType::DoubleSign),
411 1 => Some(EvidenceType::Downtime),
412 _ => None,
413 }
414 }
415
416 pub fn name(&self) -> &'static str {
418 match self {
419 EvidenceType::DoubleSign => "double_sign",
420 EvidenceType::Downtime => "downtime",
421 }
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
427pub struct DoubleSignEvidence {
428 pub validator_pubkey: [u8; 32],
430 pub height: BlockHeight,
432 pub block_hash_1: [u8; 32],
434 #[serde(with = "BigArray")]
436 pub signature_1: [u8; 64],
437 pub block_hash_2: [u8; 32],
439 #[serde(with = "BigArray")]
441 pub signature_2: [u8; 64],
442 pub submitted_at: BlockHeight,
444}
445
446impl DoubleSignEvidence {
447 pub fn new(
449 validator_pubkey: [u8; 32],
450 height: BlockHeight,
451 block_hash_1: [u8; 32],
452 signature_1: [u8; 64],
453 block_hash_2: [u8; 32],
454 signature_2: [u8; 64],
455 submitted_at: BlockHeight,
456 ) -> Self {
457 Self {
458 validator_pubkey,
459 height,
460 block_hash_1,
461 signature_1,
462 block_hash_2,
463 signature_2,
464 submitted_at,
465 }
466 }
467
468 pub fn is_valid(&self) -> bool {
470 self.block_hash_1 != self.block_hash_2
472 }
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
477pub struct DowntimeEvidence {
478 pub validator_pubkey: [u8; 32],
480 pub start_height: BlockHeight,
482 pub end_height: BlockHeight,
484 pub missed_blocks: u64,
486 pub submitted_at: BlockHeight,
488}
489
490impl DowntimeEvidence {
491 pub fn new(
493 validator_pubkey: [u8; 32],
494 start_height: BlockHeight,
495 end_height: BlockHeight,
496 missed_blocks: u64,
497 submitted_at: BlockHeight,
498 ) -> Self {
499 Self {
500 validator_pubkey,
501 start_height,
502 end_height,
503 missed_blocks,
504 submitted_at,
505 }
506 }
507
508 pub fn exceeds_threshold(&self, threshold: u64) -> bool {
510 self.missed_blocks >= threshold
511 }
512}
513
514#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
516pub struct ValidatorSigningInfo {
517 pub validator_pubkey: [u8; 32],
519 pub start_height: BlockHeight,
521 pub index_offset: u64,
523 pub missed_blocks_counter: u64,
525 pub tombstoned: bool,
527 pub jailed_until: BlockHeight,
529}
530
531impl ValidatorSigningInfo {
532 pub fn new(validator_pubkey: [u8; 32], start_height: BlockHeight) -> Self {
534 Self {
535 validator_pubkey,
536 start_height,
537 index_offset: 0,
538 missed_blocks_counter: 0,
539 tombstoned: false,
540 jailed_until: 0,
541 }
542 }
543
544 pub fn increment_missed(&mut self) {
546 self.missed_blocks_counter = self.missed_blocks_counter.saturating_add(1);
547 }
548
549 pub fn reset_missed(&mut self) {
551 self.missed_blocks_counter = 0;
552 }
553
554 pub fn exceeds_threshold(&self, threshold: u64) -> bool {
556 self.missed_blocks_counter >= threshold
557 }
558
559 pub fn tombstone(&mut self) {
561 self.tombstoned = true;
562 }
563}
564
565#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
567pub struct SlashingRecord {
568 pub validator_pubkey: [u8; 32],
570 pub evidence_type: EvidenceType,
572 pub slashed_at: BlockHeight,
574 pub validator_slash_amount: Balance,
576 pub delegation_slash_amount: Balance,
578 pub jailed_until: BlockHeight,
580 pub tombstoned: bool,
582 pub slash_fraction_bps: u16,
584}
585
586impl SlashingRecord {
587 pub fn new(
589 validator_pubkey: [u8; 32],
590 evidence_type: EvidenceType,
591 slashed_at: BlockHeight,
592 validator_slash_amount: Balance,
593 delegation_slash_amount: Balance,
594 jailed_until: BlockHeight,
595 tombstoned: bool,
596 slash_fraction_bps: u16,
597 ) -> Self {
598 Self {
599 validator_pubkey,
600 evidence_type,
601 slashed_at,
602 validator_slash_amount,
603 delegation_slash_amount,
604 jailed_until,
605 tombstoned,
606 slash_fraction_bps,
607 }
608 }
609
610 pub fn total_slashed(&self) -> Balance {
612 self.validator_slash_amount.saturating_add(self.delegation_slash_amount)
613 }
614}
615
616#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
618pub struct SubmitEvidenceData {
619 pub evidence_type: EvidenceType,
621 pub evidence: Vec<u8>,
623}
624
625#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
627pub struct StakingParams {
628 pub min_validator_stake: Balance,
630 pub max_validators: u32,
632 pub unbonding_period: BlockHeight,
634 pub max_commission_bps: u16,
636 pub double_sign_slash_bps: u16,
638 pub downtime_slash_bps: u16,
640 pub double_sign_jail_duration: BlockHeight,
642 pub downtime_jail_duration: BlockHeight,
644 pub downtime_threshold: u64,
646 pub epoch_length: BlockHeight,
648 pub stake_weighted_selection: bool,
650}
651
652impl Default for StakingParams {
653 fn default() -> Self {
654 Self {
655 min_validator_stake: 1_000_000_000_000_000_000, max_validators: 100,
657 unbonding_period: 100_800, max_commission_bps: 10000, double_sign_slash_bps: 500, downtime_slash_bps: 10, double_sign_jail_duration: 14400, downtime_jail_duration: 2400, downtime_threshold: 500, epoch_length: 14400, stake_weighted_selection: true, }
667 }
668}
669
670impl StakingParams {
671 pub fn epoch_for_height(&self, height: BlockHeight) -> u64 {
673 if self.epoch_length == 0 {
674 return 0;
675 }
676 height / self.epoch_length
677 }
678
679 pub fn is_epoch_boundary(&self, height: BlockHeight) -> bool {
681 if self.epoch_length == 0 {
682 return false;
683 }
684 height > 0 && height % self.epoch_length == 0
685 }
686
687 pub fn epoch_start_height(&self, epoch: u64) -> BlockHeight {
689 epoch * self.epoch_length
690 }
691
692 pub fn epoch_end_height(&self, epoch: u64) -> BlockHeight {
694 (epoch + 1) * self.epoch_length - 1
695 }
696}
697
698#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
704pub struct ValidatorSetEntry {
705 pub pubkey: [u8; 32],
707 pub voting_power: Balance,
709 pub commission_bps: u16,
711}
712
713impl ValidatorSetEntry {
714 pub fn new(pubkey: [u8; 32], voting_power: Balance, commission_bps: u16) -> Self {
716 Self {
717 pubkey,
718 voting_power,
719 commission_bps,
720 }
721 }
722}
723
724#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
726pub struct ValidatorSet {
727 pub epoch: u64,
729 pub active_from: BlockHeight,
731 pub validators: Vec<ValidatorSetEntry>,
733 pub total_voting_power: Balance,
735 pub proposer_seed: [u8; 32],
737}
738
739impl ValidatorSet {
740 pub fn new(epoch: u64, active_from: BlockHeight, validators: Vec<ValidatorSetEntry>, proposer_seed: [u8; 32]) -> Self {
742 let total_voting_power = validators.iter().map(|v| v.voting_power).sum();
743 Self {
744 epoch,
745 active_from,
746 validators,
747 total_voting_power,
748 proposer_seed,
749 }
750 }
751
752 pub fn len(&self) -> usize {
754 self.validators.len()
755 }
756
757 pub fn is_empty(&self) -> bool {
759 self.validators.is_empty()
760 }
761
762 pub fn pubkeys(&self) -> Vec<[u8; 32]> {
764 self.validators.iter().map(|v| v.pubkey).collect()
765 }
766
767 pub fn contains(&self, pubkey: &[u8; 32]) -> bool {
769 self.validators.iter().any(|v| &v.pubkey == pubkey)
770 }
771
772 pub fn get(&self, pubkey: &[u8; 32]) -> Option<&ValidatorSetEntry> {
774 self.validators.iter().find(|v| &v.pubkey == pubkey)
775 }
776
777 pub fn get_stake_weighted_proposer(&self, height: BlockHeight) -> Option<[u8; 32]> {
779 if self.validators.is_empty() || self.total_voting_power == 0 {
780 return None;
781 }
782
783 let mut seed_input = [0u8; 40];
785 seed_input[..32].copy_from_slice(&self.proposer_seed);
786 seed_input[32..40].copy_from_slice(&height.to_le_bytes());
787
788 let hash = blake3::hash(&seed_input);
790 let hash_bytes = hash.as_bytes();
791
792 let selection_bytes: [u8; 16] = hash_bytes[..16].try_into().unwrap();
794 let selection_value = u128::from_le_bytes(selection_bytes);
795
796 let selection_point = selection_value % (self.total_voting_power as u128);
798
799 let mut cumulative = 0u128;
801 for validator in &self.validators {
802 cumulative += validator.voting_power as u128;
803 if selection_point < cumulative {
804 return Some(validator.pubkey);
805 }
806 }
807
808 Some(self.validators[0].pubkey)
810 }
811
812 pub fn get_round_robin_proposer(&self, height: BlockHeight) -> Option<[u8; 32]> {
814 if self.validators.is_empty() {
815 return None;
816 }
817 let idx = (height as usize) % self.validators.len();
818 Some(self.validators[idx].pubkey)
819 }
820
821 pub fn voting_power(&self, pubkey: &[u8; 32]) -> Balance {
823 self.get(pubkey).map(|v| v.voting_power).unwrap_or(0)
824 }
825
826 pub fn voting_power_percentage(&self, pubkey: &[u8; 32]) -> u16 {
828 if self.total_voting_power == 0 {
829 return 0;
830 }
831 let power = self.voting_power(pubkey);
832 ((power * 10000) / self.total_voting_power) as u16
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839
840 #[test]
841 fn test_validator_status_from_byte() {
842 assert_eq!(ValidatorStatus::from_byte(0), Some(ValidatorStatus::Active));
843 assert_eq!(ValidatorStatus::from_byte(1), Some(ValidatorStatus::Inactive));
844 assert_eq!(ValidatorStatus::from_byte(2), Some(ValidatorStatus::Jailed));
845 assert_eq!(ValidatorStatus::from_byte(3), Some(ValidatorStatus::Unbonding));
846 assert_eq!(ValidatorStatus::from_byte(99), None);
847 }
848
849 #[test]
850 fn test_validator_info_creation() {
851 let pubkey = [1u8; 32];
852 let validator = ValidatorInfo::new(pubkey, 1000, 500, 100);
853
854 assert_eq!(validator.stake, 1000);
855 assert_eq!(validator.commission_bps, 500);
856 assert_eq!(validator.status, ValidatorStatus::Active);
857 assert!(!validator.is_jailed());
858 }
859
860 #[test]
861 fn test_validator_slash() {
862 let pubkey = [1u8; 32];
863 let mut validator = ValidatorInfo::new(pubkey, 10000, 500, 100);
864
865 validator.apply_slash(500); assert_eq!(validator.stake, 9500);
868 assert_eq!(validator.slash_count, 1);
869 }
870
871 #[test]
872 fn test_validator_jail_unjail() {
873 let pubkey = [1u8; 32];
874 let mut validator = ValidatorInfo::new(pubkey, 1000, 500, 100);
875
876 validator.jail(200);
877 assert!(validator.is_jailed());
878 assert!(!validator.can_unjail(150));
879 assert!(validator.can_unjail(200));
880
881 validator.unjail();
882 assert!(!validator.is_jailed());
883 assert_eq!(validator.status, ValidatorStatus::Active);
884 }
885
886 #[test]
887 fn test_staking_operation_from_byte() {
888 assert_eq!(StakingOperation::from_byte(0), Some(StakingOperation::CreateValidator));
889 assert_eq!(StakingOperation::from_byte(4), Some(StakingOperation::Unjail));
890 assert_eq!(StakingOperation::from_byte(99), None);
891 }
892
893 #[test]
894 fn test_commission_cap() {
895 let pubkey = [1u8; 32];
896 let validator = ValidatorInfo::new(pubkey, 1000, 15000, 100); assert_eq!(validator.commission_bps, 10000); }
900
901 #[test]
906 fn test_validator_delegation() {
907 let pubkey = [1u8; 32];
908 let mut validator = ValidatorInfo::new(pubkey, 1000, 500, 100);
909
910 assert_eq!(validator.total_stake(), 1000);
911 assert_eq!(validator.total_delegated, 0);
912
913 validator.add_delegation(500);
914 assert_eq!(validator.total_delegated, 500);
915 assert_eq!(validator.total_stake(), 1500);
916
917 validator.remove_delegation(200);
918 assert_eq!(validator.total_delegated, 300);
919 assert_eq!(validator.total_stake(), 1300);
920 }
921
922 #[test]
923 fn test_delegation_info_creation() {
924 let delegator = [2u8; 32];
925 let validator_pubkey = [1u8; 32];
926 let delegation = DelegationInfo::new(delegator, validator_pubkey, 1000, 100);
927
928 assert_eq!(delegation.delegator, delegator);
929 assert_eq!(delegation.validator_pubkey, validator_pubkey);
930 assert_eq!(delegation.amount, 1000);
931 assert_eq!(delegation.pending_rewards, 0);
932 assert_eq!(delegation.delegated_at, 100);
933 }
934
935 #[test]
936 fn test_delegation_stake_operations() {
937 let delegator = [2u8; 32];
938 let validator_pubkey = [1u8; 32];
939 let mut delegation = DelegationInfo::new(delegator, validator_pubkey, 1000, 100);
940
941 delegation.add_stake(500);
942 assert_eq!(delegation.amount, 1500);
943
944 let removed = delegation.remove_stake(700);
945 assert_eq!(removed, 700);
946 assert_eq!(delegation.amount, 800);
947
948 let removed = delegation.remove_stake(1000);
950 assert_eq!(removed, 800);
951 assert_eq!(delegation.amount, 0);
952 }
953
954 #[test]
955 fn test_delegation_rewards() {
956 let delegator = [2u8; 32];
957 let validator_pubkey = [1u8; 32];
958 let mut delegation = DelegationInfo::new(delegator, validator_pubkey, 1000, 100);
959
960 delegation.add_rewards(100);
961 assert_eq!(delegation.pending_rewards, 100);
962
963 delegation.add_rewards(50);
964 assert_eq!(delegation.pending_rewards, 150);
965
966 let claimed = delegation.claim_rewards();
967 assert_eq!(claimed, 150);
968 assert_eq!(delegation.pending_rewards, 0);
969 }
970
971 #[test]
972 fn test_delegation_slash() {
973 let delegator = [2u8; 32];
974 let validator_pubkey = [1u8; 32];
975 let mut delegation = DelegationInfo::new(delegator, validator_pubkey, 10000, 100);
976
977 delegation.apply_slash(500); assert_eq!(delegation.amount, 9500);
979 }
980
981 #[test]
982 fn test_unbonding_delegation() {
983 let delegator = [2u8; 32];
984 let validator_pubkey = [1u8; 32];
985 let unbonding = UnbondingDelegation::new(delegator, validator_pubkey, 500, 200);
986
987 assert!(!unbonding.is_complete(100));
988 assert!(!unbonding.is_complete(199));
989 assert!(unbonding.is_complete(200));
990 assert!(unbonding.is_complete(300));
991 }
992
993 #[test]
994 fn test_delegation_operations() {
995 assert_eq!(StakingOperation::from_byte(6), Some(StakingOperation::Delegate));
996 assert_eq!(StakingOperation::from_byte(7), Some(StakingOperation::Undelegate));
997 assert_eq!(StakingOperation::from_byte(8), Some(StakingOperation::ClaimDelegationRewards));
998 assert_eq!(StakingOperation::from_byte(9), Some(StakingOperation::WithdrawUnbonded));
999
1000 assert!(StakingOperation::Delegate.is_delegation());
1001 assert!(StakingOperation::Undelegate.is_delegation());
1002 assert!(!StakingOperation::CreateValidator.is_delegation());
1003 }
1004}